aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Runge <david.runge@frqrec.com>2012-08-14 20:34:46 +0200
committerDavid Runge <david.runge@frqrec.com>2012-08-14 20:34:46 +0200
commit839d8e3820c6097f89bdc096e264485d063c1943 (patch)
tree77a0229565ee67b1f9cb8fa26529038f346fa8b8
parenta679c43512da6aac8ea5f8e4d68d9fdd5ea1b51d (diff)
downloadthesoundofpeople-839d8e3820c6097f89bdc096e264485d063c1943.tar.gz
thesoundofpeople-839d8e3820c6097f89bdc096e264485d063c1943.tar.bz2
thesoundofpeople-839d8e3820c6097f89bdc096e264485d063c1943.tar.xz
thesoundofpeople-839d8e3820c6097f89bdc096e264485d063c1943.zip
Classes and howto
-rw-r--r--SNP.sc54
-rw-r--r--SNPDict.sc223
-rw-r--r--SNPFile.sc167
-rw-r--r--SNPInfo.sc149
-rw-r--r--SNPParser.sc107
-rw-r--r--SNPResolver.sc196
-rw-r--r--SNPSonificator.sc379
-rw-r--r--howto.scd41
8 files changed, 1316 insertions, 0 deletions
diff --git a/SNP.sc b/SNP.sc
new file mode 100644
index 0000000..5249971
--- /dev/null
+++ b/SNP.sc
@@ -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
+
+
+