// title: fraction2basimal // author: eli.rosenkim // description: // function that extracts repeating digits in "basimal" representations of ratios // code: //I wanted some interesting periodic sets of integers for just intonation music. //Rational fractions often yield periodic decimals in base 10 if they can't be formed using multiples of 2s and 5s. Prime base number systems have even greater potential for these periodic sequences. Choosing the base also acts to constrain the maximum magnitude of the numbers, useful to stay roughly within a certain "limit" in the Partch sense. //Conversion of decimal fraction to basimal algorithim from https://mnmathleague.org/wp-content/uploads/2013/06/Basimal-Fractions-Part-A-only.pdf //it doesn't work perfectly due to floating point errors, I can't find an arbitrary precision float for SC ~fraction2basimal.(1/3, 10); //without rounding in the equals comparision, this returns a bunch of threes (should only return two) and then after a few digits turns to rando errors ~fraction2basimal.(pi, 10); //pi and phi are both "terminating" after less than 50 digits with or without rounding in the equals comparison //function ( ~fraction2basimal = { arg x, base; var message = "max iterations computed", messageList = List.newClear(size: 0), a = 0, d=x, c, maxIterations=15, i=0, cList = List.newClear(size: 0), dList = List.newClear(size: 0); d = d - d.floor.asInteger; //removes integer component to avoid double digit numbers in the array when there shouldn't be while( ////conditions for stopping section//// {i < maxIterations} //limit on maximum numbers of iterations for safety && {d != 0}//if d=0 the basimal repetition is done and non-repeating &&{//if d at i is equal to any previous d, then the basimal repetition is the previous digits repeating var result = true; dList.do{ arg item, i; //dList.postln; d.postln; //for testing //************* if( ({i > 0}.value;) && //if i = 0 then i-1.max(0) is 0, and d will be compared with itself! ({dList[(i-1).max(0)].equalWithPrecision(d, 0.0000001)}.value;), //equalWithPrecision nessescarry to avoid floating point errors, I wish it wasn't! Comment out to test //************* //({dList[(i-1).max(0)] == d}.value;), //broken version without rounding to showcase issue, comment in for testing //************* {message = "repeating"; result = false;}, ); if(result == false, {messageList.add(cList[i])}); }; result; } , ////body of the algo section//// { i = i+1; a = d*base; c = a.floor.asInteger; //the integer part of a d = a - c; //the fractional part of a cList.add(c); dList.add(d); } ); //post and return section if(d == 0, {message = "terminating"}); if((message == "repeating"), {postf("%",messageList.asArray);(" repeating").postln;}, {message.postln;}); cList.asArray; }; ) //examples ~fraction2basimal.(1/3, 10); ~fraction2basimal.(4/5, 7); ~fraction2basimal.(6/7, 7); //results from the paper ~fraction2basimal.(3/8, 4); ~fraction2basimal.(3/8, 3); //supports bases over 10. Note that all digits in the output are represented in base 10 even if the computation is in a different base. ~fraction2basimal.(6/7, 3); //decimals to basimals also works ~fraction2basimal.((1+(5.sqrt))/2, 10);//golden ratio and other irrationals will yield infinite series that should always hit the max iterations. A precision error might at some point cause it to mistakenly terminate if you set the max iterations high enough though. ~fraction2basimal.(pi, 10);//floating point poops out at about 47 iterations ~fraction2basimal.(pi, 6); //note that one should always imagine a decimal before the first place, the function discards everything to the left of the decimal ~fraction2basimal.(1/3, 10); ~fraction2basimal.(4/3, 10); //same thing //fun formant sound, disperser taken from https://scsynth.org/t/phase-rotation-fos/5575 ( SynthDef(\Harrison3, { arg root = 100, oTone = 1, uTone = 1, fb1 = 0, formant = 1760, tube = 0, amp = 1, pan = 0, gate = 0.5, ratio = 1, k= 0.5; var finalFreq, sig1, sig, sigL, sigR, sigM, sigS, hornbostelwertheimerkonstante = 0.00063; finalFreq = (oTone/uTone) * root; sig1 = (0.2*Formant.ar(finalFreq, formfreq: formant)) + //osc1 (0.2*SinOscFB.ar(finalFreq, feedback: fb1)); //osc2 sig = sig1; sig = Limiter.ar(LeakDC.ar(NTube.ar(sig,`[0.97,1.0,1.0,1.0,0.97],`[0.5, tube.range(-1, 1),0.2],`([0.01,0.02,0.01,0.005]*0.01)))*3);//vocal tract filter sig = 0.1 * sig; sigL = DelayL.ar(sig, 1, LFNoise1.ar(1, finalFreq * hornbostelwertheimerkonstante * 0.1)); sigR = DelayL.ar(sig, 1, LFNoise1.ar(1.3, finalFreq * hornbostelwertheimerkonstante * 0.1)); sigM = sigL + sigR; sigS = sigL - sigR; 100.do {sigS = FOS.ar(sigS, k.neg, 1, k);}; //allpass disperser sigL = (sigM + sigS); sigR = (sigM - sigS); sig = [sigL, sigR]; sig = Pan2.ar(Limiter.ar(sig), pan, amp); sig = DFM1.ar(sig, 22000 - (500 * ((finalFreq/20 + 1).log2)), 0.3, 2, 0, 0);//filter sig = sig*EnvGen.kr(Env.adsr(0.04, 0.2, 0.6, 0.1), gate, doneAction: Done.freeSelf);//env sig = Limiter.ar(sig); //sig = Friction.ar(sig, friction: MouseX.kr(0.00001, 0.03, 1), spring: MouseY.kr(0.01, 0.1, 1), beltmass: MouseY.kr(2, 0.01, 1)); OffsetOut.ar(0, sig); }).add; ) //patern using 8/9 in base 11 for musical purposes ( ~long.stop; ~short1.stop; ~short2.stop; ~basimal = ~fraction2basimal.(8/9, 11); // 8/9 in base 11 is .986124 repeating ~basimal.pop; //this results in ~basimal = [9,8,6,1,2,4] //~basimal = ~basimal.rotate(0); ~antiTempo = 6/16; ~long = PmonoArtic( \Harrison2, \formant, Pseq(~basimal++~basimal, inf), \fb1, Pseq(~basimal++~basimal++[3], inf)*1/4, \tube, Pseq([0, 0, 0, 1/2, -1/2, 1/2], inf);, \root, 144* Pstep([9/10, 1, 1], ~antiTempo*4/5, inf), \uTone, Pstep(~basimal.rotate(-1), ~antiTempo*8/9, inf), \oTone, Pstep(~basimal, ~antiTempo, inf), \dur, Pseq(~basimal, inf)*(~antiTempo/10), \legato, Pseq(~basimal.mirror2, inf)*2/19 ).play; ~short = PmonoArtic( \Harrison2, \formant, Pseq(~basimal, inf), \fb1, Pseq([10, 1, 1], inf)*1/4, \tube, Pseq([-0.9, -0.8, 1, 1/2, -1/2, -0.9], inf), \root, 288* Pstep([9/10, 1, 1], ~antiTempo*3/5, inf), \uTone, Pstep(~basimal.rotate(1), ~antiTempo*8/9, inf), \oTone, Pstep(~basimal, ~antiTempo, inf), \dur, Pseq(~basimal, inf)*(~antiTempo/10), \legato, Pseq(~basimal, inf)*1/20 ).play; ) //another pattern ( ~long.stop; ~short.stop; ~short1.stop; ~short2.stop; ~basimal = ~fraction2basimal.(4/5, 13); ~basimal.pop; ~basimal = ~basimal.rotate(5); ~antiTempo = 6/32; ~root = 288; //~ratiolong = 25/24; ~ratiolong = 1; ~long = PmonoArtic( \Harrison2, \formant, Pseq(~basimal, inf), \fb1, Pseq(~basimal, inf)*0.1, \tube, Pseq([0, 0, 0, 1/2, -1/2, 1/2], inf);, \root, ~root *(~ratiolong)* Pstep([3/4, 1, 1], ~antiTempo*5/4, inf), \uTone, Pstep(~basimal, ~antiTempo*4/5, inf), \oTone, Pstep(~basimal, ~antiTempo, inf), \dur, Pseq(~basimal, inf)*(~antiTempo/10), \legato, Pseq(~basimal.mirror2, inf)*3/19 ).play; ~short1 = PmonoArtic( \Harrison2, \formant, Pseq(~basimal, inf), \fb1, Pseq([10, 1, 1], inf)*1/4, \tube, Pseq([-0.9, -0.8, 1, 1/2, -1/2, -0.9], inf), \root, ~root* Pstep([9/10, 1, 1], ~antiTempo*3/5, inf), \uTone, Pstep(~basimal.rotate(1), ~antiTempo*4/5, inf), \oTone, Pstep(~basimal, ~antiTempo, inf), \dur, Pseq(~basimal, inf)*(~antiTempo/10), \legato, Pseq(~basimal, inf)*1/20).play; ~short2 = PmonoArtic( \Harrison2, \formant, Pseq(~basimal, inf), \fb1, Pseq([10, 1, 1], inf)*1/4, \tube, Pseq([-0.9, -0.8, 1, 1/2, -1/2, -0.9], inf), \root, ~root* Pstep([9/10, 1, 1], ~antiTempo*3/5, inf), \uTone, Pstep(~ basimal.rotate(3), ~antiTempo*4/5, inf), \oTone, Pstep(~basimal, ~antiTempo, inf), \dur, Pseq(~basimal, inf)*(~antiTempo/10), \legato, Pseq(~basimal, inf)*1/20 ).play; )