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; } addCompressionSynths{ "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*0.1); Out.ar(recBus, compressed*0.1); }).add; } startDSPSynths{//start compression and possibly recording Synths ("Starting compression Synth.").postln; Synth(\compressor, [inBus:sonBus], compressionGroup);//play compression Synth compressionGroup.run(true);//explicitely turn on compressionGroup (needed for pause/play) if(record,{//if recording, play recording Synth ("Starting recording Synth for recording to file.").postln; Synth(\recordSNP, [buffer: this.recordingBuffer], recordingGroup); },{//FIXME: Do we have to explicitely deactivate recordingGroup when not used? recordingGroup.run(false); }); } 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 isPlaying = 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((isPlaying) && (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 isPlaying = false; } resumePlay{//resume the sonification from last position played isPlaying = true; ("current: "++currentStep++" - last: "++lastStep).postln; this.playFromTo(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); }); }); }); //update the currently playing base of the chromosome in gui, if it has been spawned if(gui.notNil,{ gui.setIntermediateText(snp.vecChromosome, snp.base); }); 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; } // create a new SNPGUI instance spawnView{ gui = SNPGUI.new(speakerSetup); } }