From 839d8e3820c6097f89bdc096e264485d063c1943 Mon Sep 17 00:00:00 2001 From: David Runge Date: Tue, 14 Aug 2012 20:34:46 +0200 Subject: Classes and howto --- SNP.sc | 54 ++++++++ SNPDict.sc | 223 ++++++++++++++++++++++++++++++++ SNPFile.sc | 167 ++++++++++++++++++++++++ SNPInfo.sc | 149 +++++++++++++++++++++ SNPParser.sc | 107 +++++++++++++++ SNPResolver.sc | 196 ++++++++++++++++++++++++++++ SNPSonificator.sc | 379 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ howto.scd | 41 ++++++ 8 files changed, 1316 insertions(+) create mode 100644 SNP.sc create mode 100644 SNPDict.sc create mode 100644 SNPFile.sc create mode 100644 SNPInfo.sc create mode 100644 SNPParser.sc create mode 100644 SNPResolver.sc create mode 100644 SNPSonificator.sc create mode 100644 howto.scd 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 resolver;//the other (two) combination(s) of the position + var length; + var 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 =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 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 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 compression + var recording/out + var recPath;//String representing directory to use for recording + var \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.positionssnpDict.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 + + + -- cgit v1.2.3