diff options
author | David Runge <david.runge@frqrec.com> | 2012-08-14 20:34:46 +0200 |
---|---|---|
committer | David Runge <david.runge@frqrec.com> | 2012-08-14 20:34:46 +0200 |
commit | 839d8e3820c6097f89bdc096e264485d063c1943 (patch) | |
tree | 77a0229565ee67b1f9cb8fa26529038f346fa8b8 | |
parent | a679c43512da6aac8ea5f8e4d68d9fdd5ea1b51d (diff) | |
download | thesoundofpeople-839d8e3820c6097f89bdc096e264485d063c1943.tar.gz thesoundofpeople-839d8e3820c6097f89bdc096e264485d063c1943.tar.bz2 thesoundofpeople-839d8e3820c6097f89bdc096e264485d063c1943.tar.xz thesoundofpeople-839d8e3820c6097f89bdc096e264485d063c1943.zip |
Classes and howto
-rw-r--r-- | SNP.sc | 54 | ||||
-rw-r--r-- | SNPDict.sc | 223 | ||||
-rw-r--r-- | SNPFile.sc | 167 | ||||
-rw-r--r-- | SNPInfo.sc | 149 | ||||
-rw-r--r-- | SNPParser.sc | 107 | ||||
-rw-r--r-- | SNPResolver.sc | 196 | ||||
-rw-r--r-- | SNPSonificator.sc | 379 | ||||
-rw-r--r-- | howto.scd | 41 |
8 files changed, 1316 insertions, 0 deletions
@@ -0,0 +1,54 @@ +SNP{//TODO: use vectorial represntations only, strip chromosome (redundant, as stored in SNPDict already), + var <chromosome;//chromosome as Symbol + var <vecChromosome;//corresponding number + var <id;//rsid of the SNP + var <position;//position of SNP on own chromosome + var <base;//base of the SNP (single or pair) + var <vecBase;//vectorial representation of the base (pair) + var <>resolver;//the other (two) combination(s) of the position + var <vecResolver;//vectorial representation of the other combination(s) + + *new{ + arg aChromosome, aPosition = 0, aId, aBase, aResolver; + ^super.new.init(aChromosome, aPosition, aId, aBase, aResolver); + } + + init{ + arg aChromosome, aPosition, aId, aBase, aResolver; + chromosome = aChromosome.asSymbol; + vecChromosome = SNPInfo.convertChromosome(chromosome); + position = aPosition.asFloat; + id = aId.asSymbol; + base = aBase.asSymbol; + resolver = aResolver.asArray; + vecBase = SNPInfo.baseToVec(base); + vecResolver = SNPInfo.baseToVec(resolver); + } + + hasResolver{//check if this SNP has a resolver + var resolved = true; + if(resolver.isArray,{ + resolver.do({ + arg item,i; + if(item==\none,{ + resolved = false + }); + }); + ^resolved; + },{ + if(resolver!=\none,{ + ^true; + },{ + ^false; + }); + }); + } + + updateResolver{//update a resolver and its vector representation + arg newResolver; + if(newResolver!=resolver,{ + resolver = newResolver; + vecResolver = SNPInfo.baseToVec(resolver); + }); + } +} diff --git a/SNPDict.sc b/SNPDict.sc new file mode 100644 index 0000000..155dad7 --- /dev/null +++ b/SNPDict.sc @@ -0,0 +1,223 @@ +SNPDict{ + const <emptyBasePair = #[\none,\none]; + const <emptyBase = #[\none]; + var <snpDict; + var <snpArr; + var <positionLookup; + var <unknownLookup; + var <unknownArr; + var <unknownToDelete; + var <>length; + var <positions = 0; + var <unknowns = 0; + var <userID; + + *new{ + arg aLength, aUserID = 0; + ^super.new.init(aLength, aUserID); + } + + init{ + arg aLength, aUserID; + userID = aUserID.asInt; + length = aLength.asInt; + snpDict = Dictionary.new(length); + //snpArr = Array.new(length); + positionLookup = Dictionary.new(length); + unknownLookup = Dictionary.new(length); + //unknownArr = Array.new(length); + } + + updatePositionLookup{//sort the dictionary's position lookup table when updating a new position + arg position, positionAdd; + if(positionAdd,{//add or remove a position in the lookup table + positionLookup.add(position.asFloat -> true); + },{ + positionLookup.removeAt(position.asFloat); + }); + } + + updateUnknownLookup{//sort the dictionary's unknown lookup table when updating a SNP with unknown resolver + arg position, positionAdd; + if(positionAdd,{ + unknownLookup.add(position.asFloat -> true); + },{ + unknownLookup.removeAt(position.asFloat); + }); + } + + orderLookup{//order the lookup tables (position and unknown) and store them to Arrays (for sequential access) + arg table; + switch(table, + 0,{ + snpArr = positionLookup.order; + ("Done sorting position lookup table. Positions: "++snpArr.size.asString).postln; + ^true; + }, + 1,{ + unknownArr = unknownLookup.order; + ("Done sorting unknown lookup table. Unknowns: "++snpArr.size.asString).postln; + ^true; + }, + 2,{ + snpArr = positionLookup.order; + ("Done sorting position lookup table. Positions: "++snpArr.size.asString).postln; + unknownArr = unknownLookup.order; + ("Done sorting unknown lookup table. Unknowns: "++snpArr.size.asString).postln; + unknownLookup = nil; + positionLookup = nil; + ^true; + },{ + ("Unknown operation on sorting lookup tables: "++table.asString).postln; + ^false; + } + ); + } + + storeSNP{//store a combo to the snpDict and the lookup tables when reading from opensnp.org -file + arg snp, position; + var positionHolder, new = 0;//the SNP holder in snpDict + if(snpDict.includesKey(position.asSymbol), {//if there is an entry already, add this one after it + snpDict.at(position.asSymbol).add(snp.chromosome -> snp); + new = -1; + },{//else create the first one in this slot + positionHolder = Dictionary.new(25); + positionHolder.add(snp.chromosome -> snp); + snpDict.put(position.asSymbol, positionHolder); + this.updatePositionLookup(position, true); + new = 1; + positions = positions+1; + }); + if(snp.hasResolver.not,{//if the SNP has no resolver, update the unknown lookup table + this.updateUnknownLookup(position, true); + }); + ^new; + } + + storeSNPFromFile{//store a combo to the snpDict and the lookup tables when reading from own file + arg snp, position; + var positionHolder, new = 0;//the SNP holder in snpDict + if(snpDict.includesKey(position.asSymbol), {//if there is an entry already, add this one after it + snpDict.at(position.asSymbol).add(snp.chromosome -> snp); + },{//else create the first one in this slot + positionHolder = Dictionary.new(25); + positionHolder.add(snp.chromosome -> snp); + snpDict.put(position.asSymbol, positionHolder); + }); + } + + initLookupFromFile{//init lookup table from file with size given + arg posLookup, unLookup; + snpArr = Array.new(posLookup); + unknownArr = Array.new(unLookup); + } + + addPositionLookupFromFile{//put a position to the position lookup table when reading from own file + arg posArr, posSNP; + snpArr.add(posSNP); + } + + addUnknownLookupFromFile{//put a position to the position lookup table when reading from own file + arg posArr, posSNP; + unknownArr.add(posSNP); + } + + updateResolver{//update a SNP resolver at a given position + arg position, id, resolver; + var resolverLeft; + if(resolver!=SNPInfo.emptyBasePair && resolver!=SNPInfo.emptyBase,{//if the resolver is none, don't do anything + snpDict.at(position.asSymbol).do({ + arg snpItem; + if(id==snpItem.id, { + snpItem.resolver_(resolver); + ("Resolver updated.").postln; + ^true; + }); + }); +// resolverLeft = this.noneResolverAtPosition(position); +// if(resolverLeft == false,{//if there are no unknowns left, delete position from lookup table +// unknownArr.removeAt(unknownArr.indexOf(position.asFloat)); +// }); +// resolverLeft = nil; + },{ + ("Not updating position "++position++". Resolver is none.").postln; + ^false; + }); + } + + deleteSNP{//delete a SNP at a given position + arg snp, position; + var snpCounter = 0, deleted = false; + snpDict.at(position.asSymbol).do({ + arg snpItem, i; +// if(snpItem.notNil, { + snpCounter = snpCounter + 1; + if(snp.id == snpItem.id{ + snpItem.removeAt(position.asSymbol); + snpCounter = snpCounter - 1; + deleted = true; + }); +// }); + }); + if(snpCounter < 1,{//delete the complete position if there is no SNP left + snpDict.removeAt(position.asSymbol); + this.sortPositionLookup(-1, position); + this.sortUnknownLookup(-1, position); + }); + ^deleted; + } + + noneResolverAtPosition{//return a Dictionary of Chromosome -> SNP pairs that don't have a resolver or false if none found + arg position; + var noneResolver = Dictionary.new(25), noneResolverCount = 0; + if(snpDict.includesKey(position.asSymbol),{//if the position exists, check it + snpDict.at(position.asSymbol).keysValuesDo({ + arg chromosome, snp; + if(snp.hasResolver.not,{ + noneResolver.add(chromosome -> snp); + noneResolverCount = noneResolverCount + 1; + }); + }); + if(noneResolverCount>0,{ + ^noneResolver; + },{ + ^false; + }); + },{ + ^false; + }); + } + + snpAtPosition{//return a Dictionary of chromosome -> SNP pairs at a given position, or false if none available + arg position; + if(snpDict.includesKey(position.asSymbol),{//check if the key is there + ^snpDict.at(position.asSymbol);//return the Dictionary at position + },{ + ^false; + }); + } + + distanceToNextPosition{//calculate the distance from current position, to the one following + arg position; + var now; + if(position==0.0,{//if it's the beginning, return first position later on + now = -1; + },{ + now = snpArr.indexOf(position.asFloat); + }); + switch(now, + nil,{^position}, + snpArr.size-1,{^0}, + -1,{^snpArr[0]}, + {^snpArr[(snpArr.indexOf(position.asFloat)+1)]-position;} + ); + } + + queryStatistics{//query statistics about this SNPDict + //TODO: shortest/longest/mean distance between SNPs + //TODO: unknown/total SNPs + //TODO: number of SNPs per chromosome + //TODO: unknown/total SNPs per chromosome + //TODO: total positions + } +} diff --git a/SNPFile.sc b/SNPFile.sc new file mode 100644 index 0000000..a811d6f --- /dev/null +++ b/SNPFile.sc @@ -0,0 +1,167 @@ +SNPFile{ + var <snpDict; + var <file; + var <fileLength; + var <userID; + var <testSet = inf; + *new{ + arg aFile, aTestSet = inf; + ^super.new.init(aFile, aTestSet); + } + + init{ + arg aFile, aTestSet; + this.setFileAndUser(aFile); + testSet = aTestSet; + } + + + setFileAndUser{//set the file (again) to be parsed + arg aFile; + var allSlash, allDot; + file = aFile; + allSlash = file.findAll("/"); + allDot = file.findAll("."); + if(allSlash.notNil,{//if there is no slash in the filename + userID = file[allSlash[allSlash.size-1]+1..allDot[allDot.size-2]-1].asInt; + },{ + userID = file[0..allDot[allDot.size-2]-1].asInt; + }); + } + + readFile{//read a SNPDict from file + var snpFile = File(file, "r"), line = "", counter = 0, procCounter = Array.new(9), tmp, key, sizeOf, snp, res, keyCount = 0.0, unknown, unknownCount = 0.0; + fileLength = (("wc -l "++file.shellQuote).unixCmdGetStdOut).delimit({|ch| ch.isSpace}); + 9.do({|item,i| procCounter.add(round((fileLength[0].asFloat/100)*(i+1)*10, 1).asInt)});//calculating procentuals for a simple progression output + ("Now reading: "++file++" ("++fileLength[0]++" lines).").postln; + ("==========").postln; + if(snpFile.isOpen,{ + protect{ + while{(line = snpFile.getLine).notNil }{ + switch(line[0].asString, + "*",{//info line + sizeOf = line.delimit({|ch| ch.isSpace});//delimit by space and/or tab + snpDict = SNPDict.new(sizeOf[1].asFloat, sizeOf[3].asInt);//init new SNPDict + snpDict.initLookupFromFile(sizeOf[1].asFloat, sizeOf[2].asFloat); + }, + ";",{//key line + key = line.delimit({|ch| ch.isSpace});//delimit by space and/or tab + snpDict.addPositionLookupFromFile(keyCount, key[1].asFloat);//store to lookup table! + keyCount = keyCount + 1.0; + }, + "+",{//SNP line + tmp = line.delimit({|ch| ch.isSpace});//delimit by space and/or tab + if(tmp[6].isNil,{ + res = [tmp[5].asSymbol]; + },{ + res = [tmp[5].asSymbol, tmp[6].asSymbol]; + }); + snp = SNP.new(tmp[1], tmp[2], tmp[3], tmp[4], res); + snpDict.storeSNP(snp, key[1]); + }, + "|",{//unknown line + unknown = line.delimit({|ch| ch.isSpace});//delimit by space and/or tab + snpDict.addUnknownLookupFromFile(unknownCount, unknown[1].asFloat); + unknownCount = unknownCount + 1.0; + } + ); + counter = counter + 1; + switch(counter, + procCounter[0],{"=".post;}, + procCounter[1],{"=".post;}, + procCounter[2],{"=".post;}, + procCounter[3],{"=".post;}, + procCounter[4],{"=".post;}, + procCounter[5],{"=".post;}, + procCounter[6],{"=".post;}, + procCounter[7],{"=".post;}, + procCounter[8],{"=".post;} + ); + }; + "=".postln; + "Done reading file to RAM.".postln; + }{ + snpFile.close; + }; + },{ + ("Couldn't open file for reading: "++file).warn; + }); + ^snpDict; + } + + writeFile{//write a SNPDict to file + arg snpDict; + var snpFile = File(file, "w"), line = "", counter = 0, tmp, snp, newSameCounter = 0; + ("Attempting to write SNPDict to file: "++file).postln; + if(snpFile.isOpen, { + protect{ + snpFile.write("*\t"++snpDict.snpArr.size.asString++"\t"++snpDict.unknownArr.size.asString++"\t"++snpDict.userID++"\n");//write length of snpArr and unknownArr + snpDict.snpArr.do({ + arg item; + snpFile.write(";\t"++item.asString++"\n"); + snpDict.snpAtPosition(item).do({//write all SNPs + arg snp; + var res; + if(snp.resolver.size==2,{ + res = (snp.resolver[0]++"\t"++snp.resolver[1]).asString; + },{ + res = snp.resolver[0].asString; + }); + snpFile.write("+\t"++snp.chromosome.asString++"\t"++snp.position.asString++"\t"++snp.id.asString++"\t"++snp.base.asString++"\t"++res++"\n");//write SNP to file + }); + }); + snpDict.unknownArr.do({//write all unknown positions + arg item; + snpFile.write("|\t"++item.asString++"\n"); + }); + }{ + snpFile.close; + ("Done writing to file.").postln; + ^true; + }; + },{ + ("Couldn't open file for writing: "++file).postln; + ^false; + }); + } + + writeUnknownIDToFile{//write the rsids of all unknown SNPs to file + arg snpDict; + var snpFile = File("/media/Data/tmp/1/1.unknown.snp", "w"); + ("Attempting to write unknowns to file: "++file).postln; + if(snpFile.isOpen, { + protect{ + snpDict.unknownArr.do({ + arg item,i; + snpDict.noneResolverAtPosition(item).do({ + arg snp,i; + snpFile.write(snp.id++"\n"); + }); + }); + } + },{ + ("Couldn't open file for writing: "++file).postln; + ^false; + }); + } + + readUnknownIDFromFile{ + arg unknowns; + var snpFile = File(unknowns, "r"), fileLength, line ="", outArr, counter = 0; + fileLength = (("wc -l "++unknowns.shellQuote).unixCmdGetStdOut).delimit({|ch| ch.isSpace}); + fileLength.postln; + outArr = Array.new(fileLength[0].asInt); + if(snpFile.isOpen, { + protect{ + while{(line = snpFile.getLine).notNil }{ + outArr.add(line.asSymbol); + counter = counter + 1; + } + } + ^outArr; + },{ + ("Couldn't open file for writing: "++file).postln; + ^false; + }); + } +} diff --git a/SNPInfo.sc b/SNPInfo.sc new file mode 100644 index 0000000..26fc24a --- /dev/null +++ b/SNPInfo.sc @@ -0,0 +1,149 @@ +SNPInfo{//helper class for calculations and constants used in the other classes + const <baseSingle = #[\A, \C, \G, \T]; + const <basePair = #[\AA, \AC, \AG, \AT, \CC, \CG, \CT, \GG, \GT, \TT]; + const <unknownBasePair = #[\AA, \CC, \GG, \TT]; + const <emptyBasePair = #[\none, \none]; + const <emptyBase = #[\none]; +// const <chromosomesLength = #[249250621, 243199373, 198022430, 191154276, 180915260, 171115067, 159138663, 146364022, 141213431, 135534747, 135006516, 133851895, 115169878, 107349540, 102531392, 90354753, 81195210, 78077248, 59128983, 63025520, 48129895, 51304566, 155270560, 59373566, 16569]; + const <chromosomesLength = #[247199719, 242751149, 199446827, 191263063, 180837866, 170896993, 158821424, 146274826, 140442298, 135374737, 134452384, 132289534, 114127980, 106360585, 100338915, 88822254, 78654742, 76117153, 63806651, 62435965, 46944323, 49528953, 154913754, 57741652, 16569]; + const <chromosomesFactor = #[ 1, 1.0183256393155, 1.2394266818795, 1.2924592711349, 1.3669687907067, 1.4464837248482, 1.556463308124, 1.6899676161638, 1.7601514822835, 1.8260402529905, 1.8385670201281, 1.868626425126, 2.1659869823333, 2.324166597993, 2.4636475190109, 2.7830831561649, 3.142845716791, 3.2476217154365, 3.8741998698537, 3.9592519952242, 5.265806453317, 4.9910144274602, 1.5957247992325, 4.2811334701681, 14919.410887803 ]; +// const <chromosomesFactor = #[1, 1.0248818404643, 1.2586989312271, 1.3039238578163, 1.3777202708052, 1.4566257978907, 1.5662480524924, 1.7029500665129, 1.7650631334069, 1.8390163889117, 1.8462117858074, 1.8621374094106, 2.1641997484794, 2.3218601681945, 2.4309688587862, 2.7585778581012, 3.0697700147583, 3.1923592004677, 4.2153713518124, 3.9547570730079, 5.1787069346401, 4.85825415617, 1.6052664523139, 4.1980065842769, 15043.190355483]; + const <lengthAtOneMs = 69.236283611;//in hours + const <sCompany = #["ftdna-illumina", "23andme", "23andme-exome-vcf", "decodeme"]; + const <a = #[1,0,0,0]; + const <c = #[0,1,0,0]; + const <g = #[0,0,1,0]; + const <t = #[0,0,0,1]; + const <e = #[0,0,0,0]; + classvar <workingSCompany = #[1];//the companies working with this software (so far) + + *calcPosition{//normalize all positions on chromosomes to the longest (the first) by chromosomesFactor of chromosomesFactor + arg chromosome, position; + var factor = 0.0, outPos = 0.0; + switch(chromosome.asSymbol, + \1,{factor = chromosomesFactor[0]}, + \2,{factor = chromosomesFactor[1]}, + \3,{factor = chromosomesFactor[2]}, + \4,{factor = chromosomesFactor[3]}, + \5,{factor = chromosomesFactor[4]}, + \6,{factor = chromosomesFactor[5]}, + \7,{factor = chromosomesFactor[6]}, + \8,{factor = chromosomesFactor[7]}, + \9,{factor = chromosomesFactor[8]}, + \10,{factor = chromosomesFactor[9]}, + \11,{factor = chromosomesFactor[10]}, + \12,{factor = chromosomesFactor[11]}, + \13,{factor = chromosomesFactor[12]}, + \14,{factor = chromosomesFactor[13]}, + \15,{factor = chromosomesFactor[14]}, + \16,{factor = chromosomesFactor[15]}, + \17,{factor = chromosomesFactor[16]}, + \18,{factor = chromosomesFactor[17]}, + \19,{factor = chromosomesFactor[18]}, + \20,{factor = chromosomesFactor[19]}, + \21,{factor = chromosomesFactor[20]}, + \22,{factor = chromosomesFactor[21]}, + \X,{factor = chromosomesFactor[22]}, + \Y,{factor = chromosomesFactor[23]}, + \MT,{factor = chromosomesFactor[24]} + ); + ^outPos = round(position.asFloat*factor);//round to the next possible slot + } + + *convertChromosome{//convert funky names of chromosomes to valid numbers + arg chrom; + switch(chrom, + \X, {^23}, + \Y, {^24}, + \MT,{^25}, + "X", {^23}, + "Y", {^24}, + "MT",{^25}, + {^chrom.asInt} + ); + } + + *baseToVec{//calculate unique vectors on basis of the base given + arg base; + var out = [[],[]]; + if(base.isArray, {//unwrap items in Array + base.do({ + arg item, i; + if(item.notNil, { + out.put(i, this.baseToVec(item)); + }); + }); + ^out; + },{ + if(this.isBasePair(base),{//if it's a base pair calc + switch(base, + \AA, {^a}, + \AC, {^(a-c)}, + \AG, {^(a-g)}, + \AT, {^(a-t)}, + \CC, {^c}, + \CG, {^(c-g)}, + \CT, {^(c-t)}, + \GG, {^g}, + \GT, {^(g-t)}, + \TT, {^t}, + ); + },{ + if(this.isBase(base),{//if it's a single base, return + switch(base, + \A, {^a}, + \C, {^c}, + \G, {^g}, + \T, {^t} + ); + },{ + if(base == \none,{//if it's unknown, return empty + ^e; + }); + }); + }); + }); + } + + + *createResolverForPair{//for known base pairs, create a resolver + arg base; + base = base.asSymbol; + switch(base, + \AC,{^[\AA, \CC]}, + \AG,{^[\AA, \GG]}, + \AT,{^[\AA, \TT]}, + \CG,{^[\CC, \GG]}, + \CT,{^[\CC, \TT]}, + \GT,{^[\GG, \TT]}, + {^[\none, \none]} + ); + } + + *isUnknownBasePair{//check if it's an unknown pair + arg base; + if(unknownBasePair.includes(base.asSymbol),{//if the base is part of the unknown base pairs + ^true; + },{ + ^false; + }); + } + + *isBase{//check if it's a single base + arg base; + if(baseSingle.includes(base.asSymbol),{//if the base is part of the unknown base pairs + ^true; + },{ + ^false; + }); + } + + *isBasePair{//check if it's a (known) base pair + arg base; + if(basePair.includes(base.asSymbol),{//if the base is part of the unknown base pairs + ^true; + },{ + ^false; + }); + } +} diff --git a/SNPParser.sc b/SNPParser.sc new file mode 100644 index 0000000..d3e89e9 --- /dev/null +++ b/SNPParser.sc @@ -0,0 +1,107 @@ +SNPParser{ + var <placement; + var <comboDict; + var <file; + var <fileLength; + var <userID; + var <testSet; + + *new{ + arg aFile, aTestSet = inf; + ^super.new.init(aFile, aTestSet); + } + + init{ + arg aFile, aTestSet; + this.setFileAndUser(aFile); + testSet = aTestSet; + } + + setFileAndUser{//set the file (again) to be parsed + arg aFile; + var allSlash, allDot; + file = aFile; + allSlash = file.findAll("/"); + allDot = file.findAll("."); + if(allSlash.notNil,{//if there is no slash in the filename + userID = file[allSlash[allSlash.size-1]+1..allDot[allDot.size-2]-1].asInt; + },{ + userID = file[0..allDot[allDot.size-2]-1].asInt; + }); + userID.postln; + } + + readFile{//parse the file this parser currently is setup with + var found = false, company = ""; + "Guessing parser.".postln; + SNPInfo.sCompany.do({ + arg cCompany, i; + if(file.contains(cCompany.asString),{ + if(SNPInfo.workingSCompany.includes(i),{ + ("Attempting to parse a "++cCompany++" file now.").postln; + company = cCompany; + found = true; + },{ + ("Parsing files from "++cCompany++" is not supported (yet). Get me a coffee to make it work!").postln; + }); + }); + }); + if(found.not,{ + ("This file might not be supported at all: "++file).postln; + },{ + switch(company, + "23andme",{^this.parse23andme}, + {"No no no no no!".postln} + ); + }); + } + + parse23andme{//parsing a 23andme file: id, chromosome, position, base + var snpFile = File(file, "r"), line = "", counter = 0.0, tmp, snp, newSameCounter = 0.0; + fileLength = (("wc -l "++file.shellQuote).unixCmdGetStdOut).delimit({|ch| ch.isSpace}); + ("File "++file++" is "++fileLength[0]++" lines long.").postln; + "Parsing might require some minutes! So kick back an get a coffee, while it lasts.".postln; + comboDict = SNPDict.new(fileLength[0], userID); + if(snpFile.isOpen,{ + protect{ + while{(line = snpFile.getLine).notNil}{//FIXME: Reinsert testset + if(line[0].asString!="#",{//skip commented lines + tmp = line.delimit({|ch| ch.isSpace});//delimit the line by space and/or tab + if(tmp[3].asString!="--" && (SNPInfo.isBasePair(tmp[3]) || SNPInfo.isBase(tmp[3]) && (SNPInfo.chromosomesLength[SNPInfo.convertChromosome(tmp[1])-1]>=tmp[2].asFloat)),{//skip empty SNPs and make sure it's either a single base or a base pair and ignore out-of-range SNPs (yes, science is unclear!) + if(SNPInfo.isBasePair(tmp[3]),{//if it's a base pair, set it up + snp = SNP.new(tmp[1], tmp[2], tmp[0], tmp[3], SNPInfo.createResolverForPair(tmp[3])); + },{ + if(SNPInfo.isBase(tmp[3]), {//if it's a single base, set it up + snp = SNP.new(tmp[1], tmp[2], tmp[0], tmp[3], \none); + }); + }); + newSameCounter = newSameCounter + comboDict.storeSNP(snp, SNPInfo.calcPosition(snp.chromosome, snp.position)); + switch(newSameCounter, + 1.0,{"Storing SNPs now: \n==========".postln;}, + 100000.0,{"=".post;}, + 200000.0,{"=".post;}, + 300000.0,{"=".post;}, + 400000.0,{"=".post;}, + 500000.0,{"=".post;}, + 600000.0,{"=".post;}, + 700000.0,{"=".post;}, + 800000.0,{"=".post;}, + 900000.0,{"=".post;}, + ); + }); + }); + counter = counter + 1; + }; + }{ + snpFile.close; + }; + },{ + ("Couldn't open file for reading: "++file).warn; + }); + "=".postln; + "Sorting lookup tables. This will also take some time!".postln; + comboDict.orderLookup(2); + "Done sorting lookup tables.".postln; + ^comboDict; + } +} diff --git a/SNPResolver.sc b/SNPResolver.sc new file mode 100644 index 0000000..777349c --- /dev/null +++ b/SNPResolver.sc @@ -0,0 +1,196 @@ +SNPResolver{ + var <fileEnd = ".json"; + var <idList = ""; + var <userID; + var <thisUser; + var <users; + var <usersWithGenotypes = 0; + var <file; + var <path; + var <>resolver; + //initialize with a path to write json files to (/tmp for example), with a minimum free disk space of ~17-25GB! + *new{ + arg aPath = "/tmp/theSoundOfPeople/", aID = 1; + ^super.new.init(aPath, aID); + } + + init{ + arg aPath, aID; + path = aPath; + userID = aID.asInt; + if(File.exists(path++userID++"/").not, { + protect{ + File.mkdir(path++userID++"/"); + }{ + ("Oh noes. The directory '"++this.path++"' could not be created. Check if all parent dirs are there and you have write permission!").warn; + }; + }); +// this.downloadUserList; + } + + downloadUserList{//download a new users.json and parse it for users with genotypes + var usersFinished = Condition.new(false); + var phenoFinished = Condition.new(false); + fork{ + ("Downloading user list to: "++path++userID.asString++"/"++"users"++fileEnd).postln; + ("curl -s -o "++path++userID.asString++"/"++"users"++fileEnd++" http://opensnp.org/users"++fileEnd).unixCmd({ + arg res,pid; + if(res.asInt!=0,{ + "Server down. Try again later!".postln + },{//if the download finished, resume with parsing + usersFinished.unhang; + }); + }, false);//save to file with user id as parentdir + usersFinished.hang; + "Extracting users with genotypes...".postln; + users = (path++userID.asString++"/"++"users"++fileEnd).asString.parseYAMLFile; + users.do({//retrieve the user IDs of users that uploaded genotypes + arg item, i; + if(users[i].at("genotypes")[0].notNil,{ + if(users[i].at("id").asInt!=userID, {//exclude the user we're currently working on + idList = idList ++ users[i].at("id").asString++","; + usersWithGenotypes = usersWithGenotypes + 1; + }); + }); + }); + idList.removeAt(idList.size-1);//remove the last "," + "Done".postln; + ("Downloading user phenotype information to: "++path++userID.asString++"/"++userID.asString++".phenotypes"++fileEnd).postln; + ("curl -s -o "++path++userID.asString++"/"++userID.asString++".phenotypes"++fileEnd++" http://opensnp.org/phenotypes/json/"++userID.asString++fileEnd.asString).unixCmd({ + arg res,pid; + if(res.asInt!=0,{ + "Server down. Try again later!".postln + },{//if the download finished, resume with parsing + phenoFinished.unhang; + }); + }, false);//save to file with user id as parentdir + phenoFinished.hang; + thisUser = (path++userID.asString++"/"++userID.asString++".phenotypes"++fileEnd).asString.parseYAMLFile; + ("Finished downloading phenotypes of "++thisUser.at("user").at("name").asString++".").postln; + } + } + + downloadResolver{//get a resolver for an ID (of a base pair at a certain position) from opensnp.org + arg id; + var state = false; //, downloadFinished = Condition.new(false); + if(File.exists(path++userID.asString++"/"++id.asString++fileEnd).not,{ + ("Downloading JSON file for "++id).postln; + ("curl -s -o "++path++userID.asString++"/"++id.asString++fileEnd++" http://opensnp.org/snps/"++id.asString++fileEnd).unixCmd({ + arg res,pid; + if(res.asInt!=0,{ + "Failed! Retry later.".postln; + },{ + "Success!".postln; + }); + },false);//save to file with user id as parent directory + state = true; + },{ + state = true; + }); + ^state; + } + + retrieveResolver{//parse a downloaded JSON file and retrieve a resolver for a given base (pair) with an ID. Returns resolver base or \none + arg snp, id; + var baseCase = 0; + if(File.exists(path++userID.asString++"/"++id.asString++fileEnd),{//if the json file exists, parse it and return the resolver (if found) + var matchHunt = 0, huntOver = false, jsonReturn, resolver, jsonFile; + jsonFile = (path++userID.asString++"/"++id.asString++fileEnd); + try{ + jsonReturn = jsonFile.parseYAMLFile;//parse the JSON file + while({ matchHunt < size(jsonReturn) && huntOver.not },{//iterate the parsed JSON for a combo match + if(jsonReturn[matchHunt].includesKey("error").not,{//if error, skip this one + if(size(jsonReturn[matchHunt].at("user").at("genotypes"))!=0,{//if there is no data, skip this one + if(jsonReturn[matchHunt].at("user").at("genotypes")[0].at("local_genotype").asString != "--",{//if it's invalid (too), skip this one + if(SNPInfo.isBase(snp),{//check if single base or base pair + baseCase = 1; + }); + switch(baseCase, + 0,{//base pair lookup + if(jsonReturn[matchHunt].at("user").at("genotypes")[0].at("local_genotype").asString != snp.asString && SNPInfo.isBasePair(jsonReturn[matchHunt].at("user").at("genotypes")[0].at("local_genotype")),{//check if it's member of base pair and not the same base pair + ("SUCCESS: Different base found for "++id.asString++" in ("++(matchHunt+1).asString++"/"++jsonReturn.size.asString++")!").post; + (snp.asString++" -> "++jsonReturn[matchHunt].at("user").at("genotypes")[0].at("local_genotype").asString).postln; + resolver = this.calcResolverPair(snp, jsonReturn[matchHunt].at("user").at("genotypes")[0].at("local_genotype").asSymbol); + huntOver = true; + }); + }, + 1,{//single base lookup + if(jsonReturn[matchHunt].at("user").at("genotypes")[0].at("local_genotype").asString != snp.asString && SNPInfo.isBase(jsonReturn[matchHunt].at("user").at("genotypes")[0].at("local_genotype")),{//check if it's member of base pair and not the same base pair + ("SUCCESS: Different base found for "++id.asString++" in ("++(matchHunt+1).asString++"/"++jsonReturn.size.asString++")!").post; + (snp.asString++" -> "++jsonReturn[matchHunt].at("user").at("genotypes")[0].at("local_genotype").asString).postln; + resolver = [jsonReturn[matchHunt].at("user").at("genotypes")[0].at("local_genotype").asSymbol]; + huntOver = true; + }); + } + ); + }); + }); + }); + matchHunt = matchHunt + 1; + }); + jsonReturn = nil; + if(resolver.isNil,{//if no resolver was found return none + switch(baseCase, + 0,{^SNPInfo.emptyBasePair}, + 1,{^SNPInfo.emptyBase} + ); + },{ + ^resolver; + }); + }{ + ("FAILED: Parsing not possible, file corrupt: "++id.asString++fileEnd).postln; + ("Skipping for now and removing.").postln; + File.delete(path++userID.asString++"/"++id.asString++fileEnd); + } + },{//file is not there yet, download it? something like that... + ("FAILED: Resolver file for "++id.asString++" not available yet. Download it.").postln; +// this.downloadResolver(id); + }); + } + + calcResolverPair{//calculate upper/lower ends of resolver pairs + arg base, resolver; + switch(base, + \AA,{ + switch(resolver, + \AC,{^[resolver,\CC]}, + \CC,{^[\AC,resolver]}, + \AG,{^[resolver,\GG]}, + \GG,{^[\AG,resolver]}, + \AT,{^[resolver,\TT]}, + \TT,{^[\AT,resolver]} + ); + }, + \CC,{ + switch(resolver, + \AA,{^[resolver,\AC]}, + \AC,{^[\AA,resolver]}, + \CG,{^[resolver,\GG]}, + \GG,{^[\CG,resolver]}, + \CT,{^[resolver,\TT]}, + \TT,{^[\CT,resolver]} + ); + }, + \GG,{ + switch(resolver, + \AA,{^[resolver,\AG]}, + \AG,{^[\AA,resolver]}, + \CG,{^[\CC,resolver]}, + \CC,{^[resolver,\CG]}, + \GT,{^[resolver,\TT]}, + \TT,{^[\GT,resolver]} + ); + }, + \TT,{ + switch(resolver, + \AA,{^[resolver,\AT]}, + \AT,{^[\AA,resolver]}, + \CT,{^[\CC,resolver]}, + \CC,{^[resolver,\CT]}, + \GT,{^[\GG,resolver]}, + \GG,{^[resolver,\GT]} + ); + } + ); + } +} diff --git a/SNPSonificator.sc b/SNPSonificator.sc new file mode 100644 index 0000000..a14dde1 --- /dev/null +++ b/SNPSonificator.sc @@ -0,0 +1,379 @@ +SNPSonificator { + var <>numChannels;//number of channels to use + var <snpSynths; + var <>snpDict;//the SNPDict to use + var <>playTime;//calculated play time of a single SNP, defined by playTime setup on init + var <>includeUnknown;//wether to include SNPs with unknown resolvers (aka. SNPS with only bases set) + var <>ignoreSNPs = #[];//Array of Symbols of chromosomes to ignore (1-22,X,Y,MT) + var <keys;//Array storing normalized settings for each chromosome + var <keyToFrq;//Dictionary with normalized frequency ranges for 8 different chromosome lengths + var <play = false;//boolean indicating the state of playing + var <currentStep = 0.0;//storage for the position up to which sonification should take place + var <lastStep = 0.0;//storage for the last played step (SNP position) aka. current position playing + var <lengthOfSNP = 1.0;//distance between two SNPs in ms + var <speakerSetup;//Array holding the chromosome numbers for each speaker + var <synthGroup;//Group for the Synths + var <fxGroup;//Group for effects + var <compressionGroup;//Group for compression Synths + var <recordingGroup;//Group for recording Synths + var <sonBus;//Bus for Synth->compression + var <fxBus;//Bus for chromosome based effects + var <recBus;//Bus for compression->recording/out + var <record =false;//boolean indicating wether to record or not (default off) + var <>recPath;//String representing directory to use for recording + var <recordingBuffer;//the recording buffer to use, wehen recording + var <baseTone = #[110.00, 65.4, 98.00, 73.42];//the fundamentals from which 8 octaves will be calculated: A (A2), C (C2), G (G2), T (D2) + + *new{ + arg snpDict, numChannels = 2, playTime, includeUnknown = false, ignoreSNPs, setRecord = false, recordPath; + ^super.new.sonificatorInit(snpDict, numChannels, playTime, includeUnknown, ignoreSNPs, setRecord, recordPath); + } + + sonificatorInit{ + arg snpDict, numChannels, playTime, includeUnknown, ignoreSNPs, setRecord, recordPath; + this.snpDict = snpDict; + this.numChannels_(numChannels); + this.playTime = playTime; + lengthOfSNP = this.calcSNPTime(playTime); + this.includeUnknown = includeUnknown; + if(ignoreSNPs.notNil && ignoreSNPs.isArray,{ + this.ignoreSNPs = ignoreSNPs; + }); + this.initKeyRange.value; + this.setupSpeakers(numChannels, true); + this.setupGroups(numChannels); + this.setupBusses(numChannels); + this.addSNPSynths.value; + //TODO: add effects Synths based on chromosome areas + if(setRecord,{//if recording should be set up + if(recordPath.notNil && recordPath.isString,{//first check if recordingPath is set + if(recordPath[recordPath.size-1]!="/",{//compensate for missing trailing slash + recPath = (recordPath++"/"); + },{ + recPath = recordPath; + }); + if(File.exists(recPath),{//if path exists, init recording to file + recPath = recordPath; + record = setRecord; + this.setupRecording(numChannels); + },{ + ("Path does not exist: "++recPath.asString).postln; + ("Update recPath!") + }); + },{ + ("Recording path invalid: "++recPath.asString).postln; + }); + }); + } + + setupBusses{//setup the busses for compression and recording + arg channels; + sonBus = Bus.audio(Server.local, channels); + recBus = Bus.audio(Server.local, channels); + } + + setupGroups{//setup Groups (for Synths, compression and recording) + arg channels; + synthGroup = Group.new; +// fxGroup = Group.after(synthGroup); + compressionGroup = Group.after(synthGroup); + recordingGroup = Group.after(compressionGroup); + NodeWatcher.register(synthGroup); +// NodeWatcher.register(fxGroup); + NodeWatcher.register(compressionGroup); + NodeWatcher.register(recordingGroup); + } + + setupRecording{//create a buffer and a recording SynthDef, leaving file open for writing + arg channels; + ("Setting up recording to file.").postln; + recordingBuffer = Buffer.alloc(Server.local, 262144, channels); + recordingBuffer.write(recPath++"SNPSonification-"++(Date.seed).asString++"_("++channels.asString++"_chan).aiff".standardizePath, "aiff", "float32", 0, 0, true); + SynthDef(\recordSNP,{ + arg buffer; + DiskOut.ar(buffer, In.ar(recBus, channels)); + }).add; + } + + setupSpeakers{//setup speakers for stereo and circular multichannel + arg channels, printOuts = false; + switch(channels.asInt, + 2,{speakerSetup = [[8,16,24,6,14,22,4,12,20,2,10,18],[7,15,23,1,9,17,3,11,19,5,11,21]];}, + 4,{speakerSetup = [[3,7,11,15,19,23],[4,8,12,16,20,24],[2,6,10,14,18,22],[1,5,9,13,17,21]];}, + 6,{speakerSetup = [[5,11,17,23],[6,12,18,24],[4,10,16,12],[2,8,14,20],[1,7,13,19],[3,9,15,21]];}, + 8,{speakerSetup = [[7,15,23],[8,16,24],[6,14,22],[4,12,20],[2,10,18],[1,9,17],[3,11,19],[5,13,21]];}, + {"Invalid number of speakers set! Fix this by calling setupSpeakers(nSpeakers) on this object or this will break. Seriously!".postln;} + ); + if(printOuts,{//print the speaker setup + speakerSetup.do({ + arg item, i; + ("Speaker "++(i+1).asString++" has chromosomes: ").post; + item.do({ + arg chromosome; + (chromosome.asString++" ("++keys[chromosome-1].asString++") ").post; + }); + "".postln; + }); + }); + } + + initKeyRange{//init 8 octave spanning frequency set, map SNPs to frequencies + keys = round(SNPInfo.chromosomesFactor[0..SNPInfo.chromosomesFactor.size-2].normalizeSum*10, 0.125); + keys.add(0.0); + keyToFrq = Dictionary.new(8); + 8.do({//setup for 8 octaves + arg item, i; + switch(i, + 0, {keyToFrq.put(0.000, this.calcFrqCombination(baseTone));}, + 1, {keyToFrq.put(0.125, this.calcFrqCombination(baseTone*2.pow(i)));}, + 2, {keyToFrq.put(0.250, this.calcFrqCombination(baseTone*2.pow(i)));}, + 3, {keyToFrq.put(0.375, this.calcFrqCombination(baseTone*2.pow(i)));}, + 4, {keyToFrq.put(0.500, this.calcFrqCombination(baseTone*2.pow(i)));}, + 5, {keyToFrq.put(0.625, this.calcFrqCombination(baseTone*2.pow(i)));}, + 6, {keyToFrq.put(0.750, this.calcFrqCombination(baseTone*2.pow(i)));}, + 7, {keyToFrq.put(0.875, this.calcFrqCombination(baseTone*2.pow(i)));} + ); + }); + } + + calcFrqCombination{//calculate tones for bases and their combinations for a given set of notes from the same octave + arg baseTones; + ^Dictionary.newFrom(List[ + SNPInfo.a, baseTones[0], + SNPInfo.c, baseTones[1], + SNPInfo.g, baseTones[2], + SNPInfo.t, baseTones[3], + SNPInfo.a-SNPInfo.c, (baseTones[0]+baseTones[1])/2, + SNPInfo.a-SNPInfo.g, (baseTones[0]+baseTones[2])/2, + SNPInfo.a-SNPInfo.t, (baseTones[0]+baseTones[3])/2, + SNPInfo.c-SNPInfo.g, (baseTones[1]+baseTones[2])/2, + SNPInfo.c-SNPInfo.t, (baseTones[1]+baseTones[3])/2, + SNPInfo.g-SNPInfo.t, (baseTones[2]+baseTones[3])/2] + ); + } + + addSNPSynths{//Synthdefs for base pairs, single bases and the MT SNPs + "Adding SynthDef for base pairs -> \base_pair".postln; + SynthDef(\base_pair, {|out=0, freq=#[0.0,0.0,0.0] ,amplitude=0.6, attackTime = 0.01, releaseTime = 0.3, curve = -4| + var env = EnvGen.kr( + Env.perc(attackTime,releaseTime,amplitude,curve),doneAction:2//free the enclosing Synth when done + ); + Out.ar( + sonBus.index+out,//speaker to play on + SinOsc.ar(//sine tone for the base pair + freq[0],0,env, + SinOsc.ar(//sine tone for the first (lower) resolver + freq[1],0,env*0.5 + )+ + SinOsc.ar(//sine tone for the second (upper) resolver + freq[2],0,env * 0.5 + ) + ) + );}, variants: (//variants for 23 first chromosomes + 1: [amplitude: 0.10, attackTime:0.01, releaseTime:0.7, curve:-4],//0.125 + 2: [amplitude: 0.10, attackTime:0.01, releaseTime:0.7, curve:-4],//0.125 + 3: [amplitude: 0.08, attackTime:0.01, releaseTime:0.6, curve:-4],//0.250 + 4: [amplitude: 0.08, attackTime:0.01, releaseTime:0.6, curve:-4],//0.250 + 5: [amplitude: 0.08, attackTime:0.01, releaseTime:0.6, curve:-4],//0.250 + 6: [amplitude: 0.08, attackTime:0.01, releaseTime:0.6, curve:-4],//0.250 + 7: [amplitude: 0.08, attackTime:0.01, releaseTime:0.6, curve:-4],//0.250 + 8: [amplitude: 0.08, attackTime:0.01, releaseTime:0.6, curve:-4],//0.250 + 9: [amplitude: 0.08, attackTime:0.01, releaseTime:0.6, curve:-4],//0.250 + 10: [amplitude: 0.06, attackTime:0.01, releaseTime:0.5, curve:-2],//0.375 + 11: [amplitude: 0.06, attackTime:0.01, releaseTime:0.5, curve:-2],//0.375 + 12: [amplitude: 0.06, attackTime:0.01, releaseTime:0.5, curve:-2],//0.375 + 13: [amplitude: 0.06, attackTime:0.01, releaseTime:0.5, curve:-2],//0.375 + 14: [amplitude: 0.06, attackTime:0.01, releaseTime:0.5, curve:-2],//0.375 + 15: [amplitude: 0.06, attackTime:0.01, releaseTime:0.5, curve:-2],//0.375 + 16: [amplitude: 0.04, attackTime:0.01, releaseTime:0.5, curve:-2],//0.500 + 17: [amplitude: 0.04, attackTime:0.01, releaseTime:0.5, curve:-2],//0.500 + 18: [amplitude: 0.04, attackTime:0.01, releaseTime:0.5, curve:-2],//0.500 + 19: [amplitude: 0.04, attackTime:0.01, releaseTime:0.5, curve:-2],//0.750 + 20: [amplitude: 0.05, attackTime:0.01, releaseTime:0.5, curve:-2],//0.625 + 21: [amplitude: 0.05, attackTime:0.01, releaseTime:0.5, curve:-2],//0.875 + 22: [amplitude: 0.05, attackTime:0.01, releaseTime:0.5, curve:-2],//0.875 + 23: [amplitude: 0.05, attackTime:0.01, releaseTime:0.5, curve:-2]//0.875 + )).add; + "Adding SynthDef for single bases -> \base_single".postln; + SynthDef(\base_single, {|out=0, freq=#[0.0,0.0] ,amplitude=0.6, attackTime = 0.01, releaseTime = 0.3, curve = -4| + var env = EnvGen.kr( + Env.perc(attackTime,releaseTime,amplitude,curve),doneAction:2//free the enclosing Synth when done + ); + Out.ar( + sonBus.index+out, + Saw.ar(//sine tone for the base + freq[0],env,SinOsc.ar(//sine tone for the resolver + freq[1],0,env*0.5 + ) + ) + );},variants: (//variants for X and Y chromosomes + 23:[amplitude: 0.07, attackTime: 0.01, releaseTime: 0.5, curve: -4],//0.250 -> X + 24:[amplitude: 0.10, attackTime: 0.01, releaseTime: 0.7, curve: -4]//0.75 ->Y + )).add; + "Adding SynthDef for MT bases -> \base_mt".postln; + SynthDef(\base_mt, {|startPosition = 0, freq = #[0.0,0.0], attackTime = 0.01, releaseTime = 1.5, amplitude = 0.4, curve = -4 | + var mtSynth, panAround; + var env = EnvGen.kr( + Env.perc(attackTime,releaseTime,amplitude,curve),doneAction:2//free the enclosing Synth when done + ); + mtSynth = Saw.ar(//sine tone for the base + freq[0],env,SinOsc.ar(//sine tone for the resolver + freq[1],0,env*0.5 + ) + ); + panAround = PanAz.ar( + numChannels,//span the whole number of channels available + mtSynth,//use the MT Synth as input + LFSaw.kr(2,0),//pan around TODO: + startPosition + 0.5,//control level + 2,//width of sound + startPosition//random starting point + ); + Out.ar(sonBus, panAround); + }).add; + "Adding SynthDef for compression -> \compressor".postln; + SynthDef(\compressor, {|inBus| + var in,compressed; + in = In.ar(inBus,numChannels); + compressed = Compander.ar( + in,//signal in + in,//control signal + 0.5,//threshold + 1,//slopeBelow + 0.3,//slopeAbove + 0.002,//clampTime + 0.01//relaxTime + ); + ReplaceOut.ar(0, compressed); + Out.ar(recBus, compressed); + }).add; + } + + startDSPSynths{//start compression and possibly recording Synths + ("Starting compression Synth.").postln; + Synth(\compressor, [inBus:sonBus], compressionGroup);//play compression Synth + if(record,{//if recording, play recording Synth + ("Starting recording Synth for recording to file.").postln; + Synth(\recordSNP, [buffer: this.recordingBuffer], recordingGroup); + }); + } + + stopDSPSynths{//stop compression and possibly recording Synths + Routine{ + 2.wait; + if(record,{ + ("Stopping recording to file.").postln; + recordingBuffer.close; + recordingBuffer.free; + if(recordingGroup.isRunning,{ + recordingGroup.run(false); + }); + }); + 1.wait; + if(compressionGroup.isRunning,{ + ("Stopping compressor Synth.").postln; + compressionGroup.run(false); + }); + }.play; + } + + playFromTo{//start from a position (if none set, start from the beginning), play to a position (or end, if none set) + arg fromPosition = 0.0, toPosition = snpDict.positions;//start from position, play to position + var startTime=0.0;//time to first position (if not playing from the beginning, this stays the same) + this.startDSPSynths;//start Synths for compression and recording + play = true; //set play parameter + if((fromPosition == 0.0) || (snpDict.positions<fromPosition),{//if playing from start (or out of bounds), calculate start time for the first SNP + startTime = lengthOfSNP*snpDict.distanceToNextPosition(fromPosition.asFloat); + currentStep = fromPosition;//set start position + },{//else start at once + currentStep = fromPosition; + }); + if((toPosition>snpDict.positions) || (toPosition < fromPosition) || (toPosition.isNil),{//if to position is out of bounds or smaller then from, set to total length + lastStep = snpDict.positions; + },{//else play up to that position + lastStep = toPosition; + }); + ("Starting to play positions: "++currentStep++" - "++lastStep).postln; + SystemClock.sched(startTime,{//start a SystemClock immediately or with calculated startTime to read from the SNPDict + arg time; + var resched = 0.0; + if((play) && (snpDict.positions>currentStep) && (currentStep <= lastStep),{//if playing and positions are left, play the position and reschedule SystemClock to the next SNP +// ("Playing SNP #"++currentStep++" of "++snpDict.positions++".").postln; + resched = lengthOfSNP*this.playPosition(snpDict.snpAtPosition(snpDict.snpArr[currentStep]), snpDict.snpArr[currentStep]); + currentStep = currentStep+1.0; +// ("Rescheduling in "++resched++"s for step #"++currentStep++" of "++snpDict.positions).postln; + resched; + },{//end the SystemClock and print out last position + ("Done playing. Last position: "++(currentStep-1).asString).postln; + this.stopDSPSynths; + nil; + }); + }); + } + + pausePlay{//pause the sonification + play = false; + } + + resumePlay{//resume the sonification from last position played + this.playSon(currentStep, lastStep); + } + + playPosition{//play the SNP(s) at a position and return the distance to the next + arg posDict, position; + posDict.keysValuesDo{ + arg key, value; + if(ignoreSNPs.includes(key).not,{//ignore chromosomes if that is set up + if(includeUnknown,{//if SNPs without resolvers should be ignored, do that + this.playBase(value); + },{ + if(value.hasResolver,{ + this.playBase(value); + }); + }); + }); + }; + ^snpDict.distanceToNextPosition(position); + } + + playBase{//play the actual base/base pair + arg snp; + var baseKey = keyToFrq.at(keys[snp.vecChromosome-1]); + if(SNPInfo.isBasePair(snp.base),{//play base pairs + Synth('base_pair.'++snp.vecChromosome.asSymbol, [out: this.findSpeaker(snp.vecChromosome), freq: [baseKey.at(snp.vecBase), baseKey.at(snp.vecResolver[0]), baseKey.at(snp.vecResolver[1])]], synthGroup); + },{ + if(SNPInfo.isBase(snp.base), {//play single bases + if(snp.chromosome==\MT, {//play the MT chromosome + Synth('base_mt', [startPosition: rrand(numChannels,0), freq: [baseKey.at(snp.vecBase), baseKey.at(snp.vecResolver[0])]], synthGroup); + },{//play parts of the X and the Y chromosome + Synth('base_single.'++snp.vecChromosome.asSymbol, [out: this.findSpeaker(snp.vecChromosome), freq: [baseKey.at(snp.vecBase), baseKey.at(snp.vecResolver[0])]], synthGroup); + }); + }); + }); + baseKey.free; + } + + findSpeaker{//find the speaker for the chromosome (defined in speakerSetup) + arg chromosome; + var speaker = 0; + speakerSetup.do({ + arg item, i; + if(item.includes(chromosome),{ + speaker = i; + }); + }); + ^speaker; + } + + calcSNPTime{//calculate the clock time for a single SNP + arg playTime; + var snpTime; + snpTime = ((1/SNPInfo.lengthAtOneMs)*playTime)/1000; + ^snpTime; + } + + queryTimeAndPosition{//query current time and position + ("Positions: "++currentStep.asString++" - "++lastStep.asString++".").postln; + ("Play time: "++(currentStep*lengthOfSNP*1000).asString++"s - "++(lastStep*lengthOfSNP*1000).asString++"s.").postln; + } +} diff --git a/howto.scd b/howto.scd new file mode 100644 index 0000000..a1a17b5 --- /dev/null +++ b/howto.scd @@ -0,0 +1,41 @@ +//First off: All of this eats shitloads of RAM (still), so be aware! Don't use this with less than 8Gb of RAM available! You have been warned! Also make sure your /tmp has enough space if you're recording to file! + + +//Parsing a file, writing a new one to disk +( +//parse a new file from opensnp.org +~parser = SNPParser.new("/tmp/1.23andme.9"); +//get a SNPDict +~dictionary = ~parser.readFile; +//write the SNPDict to file +~file = SNPFile("/tmp/1.sonificate.snp"); +~file.writeFile(~dictionary); +) + +//You might want to recompile the Class library now. Your RAM will be full... +//boot server FIRST, then load file +( +s.makeWindow; +s.boot; +~file = SNPFile("/tmp/1.sonificate.snp");//read from own file (smaller) +~dictionary = ~file.readFile;//read in the file +~dictionary.positions;//the number of positions you'll be able to play +) + + +//Make some sound: These are examples on how to init the SNPSonificator class (only use one of them of course). Don't set the playTime below 1 hour. Your sound server won't like that... seriously! + +~sonification = SNPSonificator.new(~dictionary, 8, 2, false, [], true, "/tmp/");// 8 chan, 2hour playtime, recording to file +~sonification = SNPSonificator.new(~dictionary, 8, 6, true, [], false);//8 chan, 6hour playtime +~sonification = SNPSonificator.new(~dictionary, 2, 2, true, [], true, "/var/run/media/dave/whitey/");//2 chan, 2hour playtime, recording to file +~sonification = SNPSonificator.new(~dictionary, 2, 2, true, [], false);//2 chan, 2hour playtime +~sonification = SNPSonificator.new(~dictionary, 8, 2, true, [\1,\2,\3,\4,\5,\6,\7,\8,\9,\10,\11,\12,\13,\14,\15,\16,\17,\18,\19,\20,\21,\22], false);//8 chan, 2hour playtime, ignoring all chromosomes but X, Y and MT + + +~sonification.playFromTo(0, 200000);//play from position - to position (round about 950k in all, if you leave second argument blank, it'll try to play to the end) +~sonification.queryTimeAndPosition;//get current position and time +~sonification.pausePlay;//pause play at current position +~sonification.resumePlay;//resume it + + + |