Phreaking (2008)
I wrote this piece for BrumCon 07. The con was sponsored by our local 2600 group, so I decided to use telephone in-line signaling codes as source materials. I spent a lot of time readin up on phone phreaking, which was just so completely cool. I never did it as a kid because the threats my dad made against me were so dire. But man, it was awesome!
The piece, though, is slightly silly. Well, maybe more than slightly. I doubt I’ll play it again, but I think the logic I used around the drum beats will definitely be refined and reused.
In the spirit of the con, the fugly, un-clean code is below the cut, along with some explanation of what the heck is going on. When I say ugly and unclean, I really, really mean it.
Code
The SuperCollider code requires BBBufferTool.sc (which is a very close relative of BufferTool.sc, so you can use either one). It handles some tricks with Buffers, like granulation.
Some explanation of the code at the bottom . . .
( var joystick; // drums a = BBBufferTool.open(s, "sounds/SHARP_SN.wav"); b = BBBufferTool.open(s, "sounds/kick2.wav"); c = BBBufferTool.open(s, "sounds/HIHATCLO.wav"); d = BBBufferTool.open(s, "sounds/click.au"); e = BBBufferTool.open(s, "sounds/TOM_HI.wav"); a = [a, b, c, d, e]; // operator b = BBBufferTool.open(s, "sounds/pundits/telephone/hangup.aiff"); c = BBBufferTool.open(s,"sounds/pundits/telephone/not_completed.aiff"); d = BBBufferTool.open(s,"sounds/pundits/telephone/not_in_service.aiff"); b = [b, c, d]; SynthDef("teltone", {arg out = 0, freq, dur = 0.2, amp = 0.2, pan = -1; var sin, env, panner; sin = SinOsc.ar(freq, mul: amp); env = EnvGen.kr(Env.linen(0.01, dur - 0.02, 0.01, 1, 'linear'), doneAction: 2); panner = Pan2.ar(sin, pan, env); Out.ar(out, panner); }).store; SynthDef("gamey", { arg out, freq, gate = 1, amp, pan; var osc, env, filter, panner; osc = Pulse.ar(freq, mul:amp); env = amp * EnvGen.kr(Env.asr(0.09, 1, 0.1, 1, 'linear'), gate, doneAction: 2); osc = osc * env; filter = MantissaMask.ar(osc, 8/*, env*/); panner = Pan2.ar(filter, pan); Out.ar(out, panner); }).store; SynthDef("funky", { arg out, freq, dur, gate = 1, amp, pan; var osc, env, filter, panner, widthline, pitchline; // a half step down is 50/53 // and a quarter step is 34/35 // eigth is 68/ 67 pitchline = Line.kr((68 / 67), (67 / 68), dur * 3 / 5); widthline = Line.kr(0.25, 0.75, dur); osc = Pulse.ar(freq * pitchline, widthline); env = amp * EnvGen.kr(Env.asr(0.09, 1, 0.1, 1, 'linear'), gate, doneAction: 2); osc = osc * env; filter = MantissaMask.ar(osc, 8/*, env*/); panner = Pan2.ar(filter, pan); Out.ar(out, panner); }).store; SynthDef("plainBuf", {arg out = 0, bufnum = 0, gate =1, pan = 0, amp = 0; // plays a buffer through forwards and at the normal rate var dur, env, buf, outputAudio; dur = BufDur.kr(bufnum) + 1; // envelope required or the ugens stay around forver env = EnvGen.kr(Env.linen(0.0001, (dur - 0.0002), 0.0001, amp), gate, doneAction:2); buf = PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum)); // this Pan2 stuff is a hack because Pan4 is glitchy outputAudio = Pan2.ar(buf, pan); //outputAudio = Pan2.ar(outputAudio.at(0), yPan) ++ Pan2.ar(outputAudio.at(1), yPan); Out.ar( out, outputAudio * env); }).store; SynthDef( "grainBuf", { arg out = 0, bufnum = 0, pan = 0, amp =1,startFrame = 0, grainDur = 1, rate = 1; var env, speed, player, panner; env = EnvGen.kr(Env.linen(0.001, (grainDur - 0.002), 0.001, amp), doneAction:2); //env = EnvGen.kr(Env.linen(0.0001, (grainDur - 0.0002), 0.0001, 1), doneAction:2); speed = rate * BufRateScale.kr(bufnum); player = PlayBuf.ar(1, bufnum, speed, startPos: startFrame); panner = Pan2.ar(player, pan, env); OffsetOut.ar(out, panner); }).store; //HIDDeviceServiceExt.buildDeviceList; //HIDDeviceServiceExt.queueDeviceByName(' USB Joystick '); // Look for the devices that are attached: GeneralHID.buildDeviceList; // Get the list of devices: //g = GeneralHID.deviceList; // Check which devices have been found: GeneralHID.postDevices; // Pick the 6th device and open it and create an instance of it: //j = GeneralHID.open( g[6] ); //j.info; //j.info.name GeneralHID.deviceList.do({ arg dev, num; var name; name = dev.info.name; (name.contains("RumblePad")).if ({ j = GeneralHID.open( dev); }) }); j.caps; joystick = j; // get joystick stuff setup while that goes; joystick.spec = IdentityDictionary[ // buttons \b1->joystick.slots[1].at(1), \b2->joystick.slots[1].at(2), \b3->joystick.slots[1].at(3), \b4->joystick.slots[1].at(4), \b5->joystick.slots[1].at(5), \b6->joystick.slots[1].at(6), \b7->joystick.slots[1].at(7), \b8->joystick.slots[1].at(8), \b9->joystick.slots[1].at(9), \b10->joystick.slots[1].at(10), \hat->joystick.slots[3].at(57), // sticks //left \lx->joystick.slots[3].at(48), \ly->joystick.slots[3].at(49), //right \rx-> joystick.slots[3].at(50), \ry->joystick.slots[3].at(53), ]; j = joystick; ) ( var coin10p, coin50p, trunk2600, dtmfkey, operator, npa_codes, intl_codes, busy_signal, dial_tone, pay_tone, confirmation_tone, ringback_tone, joystick, steps, dur, flag, drum_accents, cond; joystick = j; GeneralHID.startEventLoop(0.005); //http://en.wikipedia.org/wiki/Red_box_%28phreaking%29 // In the UK, a 1000 Hz tone for 200 ms represents a 10p coin, // and 1000 Hz for 350 ms represents a 50p coin. coin10p = [1000, 0.2]; coin50p = [1000, 0.35]; trunk2600 = [2600, 0.4]; pay_tone = [400, 0.125]; // divided by 10 for artistic reasons //confirmation_tone = [400, ] // The minimum duration of the tone should be at least 70 msec, dtmfkey = IdentityDictionary[ \1-> [697, 1209], \2-> [697, 1336], \3-> [697, 1477], \4-> [770, 1209], \5-> [770, 1336], \6-> [770, 1477], \7-> [852, 1209], \8-> [852, 1336], \9-> [852, 1477], \0-> [941, 1336], \star-> [941, 1209], \pound-> [941, 1477], \a-> [697, 1633], \b-> [770, 1633], \c-> [852, 1633], \d-> [941, 1633] ]; //Normally, the tone durations are on for 60ms, with 60ms of silence between digits. //The 'KP' and 'KP2' tones are sent for 100ms. // KP2 (ST2 in the R1 standard) was used for dialing internal Bell System telephone numbers. /* Once the far end sends the supervision flash, the user would use the blue box to dial a "Key Pulse" or "KP", the tone that starts a routing digit sequence, followed by either a telephone number or one of the numerous special codes that were used internally by the telephone company, then finished up with a "Start" or "ST" tone. At this point, the far end of the connection would route the call the way you told it, while the users end would think you were still ringing at the original number. KP1 is generally used for domestic dialing where KP2 would be for international calls. */ operator = IdentityDictionary[ \1-> [700, 900], \2-> [700, 1100], \3-> [900, 1100], \4-> [700, 1300], \5-> [900, 1300], \6-> [1100, 1300], \7-> [700, 1500], \8-> [900, 1500], \9-> [1100, 1500], \0-> [1300, 1500], \11-> [700, 1700], \12-> [900, 1700], \kp-> [1100, 1700], \kp2-> [1300, 1700], \st-> [1500, 1700] ]; npa_codes = [ // a three digit are code (aka a NPA) comes before these codes [\1, \0, \0], [\1, \0, \1], [\1, \0, \2], [\1, \0, \3], [\1, \0, \4], [\1, \0, \5], [\1, \0, \6], [\1, \0, \7], [\1, \0, \8], [\1, \0, \9], [\1, \2, \1], [\1, \3, \1], [\1, \4, \1], [\1, \6, \1], [\1, \8, \1] // coin refund! ]; intl_codes = [ [\9, \1, \4, \1, \5, \1], //incoming [\2, \1, \2, \1, \5, \1], //incoming // senders [\9, \1, \4, \1, \8, \2], [\2, \1, \2, \1, \8, \3], [\4, \1, \2, \1, \8, \4], [\4, \0, \7, \1, \8, \5], [\5, \1, \0, \1, \8, \6], //oakland! [\3, \0, \3, \1, \8, \7], [\2, \1, \2, \1, \8, \8] ]; // The frequencies were initially designed with a ratio of 21/19, busy_signal = [ 400, 0.375 ]; dial_tone = [ 350, 440]; ringback_tone = [ [ [400, 450], 0.4 ], [ \rest, 0.2], [ [400, 450], 0.4 ], [ \rest, 2.0] ]; //In Australia and the UK, the standard ring cadence is 400 ms on, 200 ms off, 400 ms on, 2000 ms off. // prepare some buffers, figure out beat sizes steps = 30.rrand(38); dur = 4 / steps; { b.do({arg bbuf; bbuf.beatlength = steps; bbuf.calc_grains_dur(dur/4); }); }.fork; flag = true; // This piece is five minuts long, so let's pull the plug after 5 Routine.new({ (5 * 60).wait; flag = false; "time's up!".postln; }).play; // ok let's make some sound Pseq([ // dial tone Pbind ( \instrument, \teltone, \freq, dial_tone, \dur, Pseq([2 + 4.0.rand], 1) ), // coins Pbind ( \instrument, \teltone, [\freq, \dur], Prout({ var dur; (2 + 3.rand).do ({ dur = 0.2.rrand(0.5); [coin10p, coin50p].choose.yield; [\rest, 0.0125].yield; pay_tone.yield; [\rest, 0.0125].yield; //[400, 0.125].yield; // pay tone? dur = 0.18.rrand(0.3); [\rest, dur].yield; }); dur = 0.2.rrand(1.1); [dial_tone, dur].yield; }) // Prand([coin10p, coin50p, // [\rest, 0.7], // [\rest, 0.5], // [\rest, 0.6]], 7) ), // pause //Pbind ( // // \freq, \rest, // \dur, Pwhite([0.2, 1], 1) //), // dial Pbind ( \instrument, \teltone, //\dur, Pfunc({0.1.rrand(0.3)}), /*\key, Pseq([\0, Prand([\0, \1, \2, \3, \4, \5, \6, \7, \8, \9], 7) ], 7), \freq, Pfunc({ arg evt; dtmfkey.at(evt[\key]) }),*/ [\freq, \dur], Prout ({ arg evt; var keys, dial, button, dur; keys = [\0, \1, \2, \3, \4, \5, \6, \7, \8, \9]; evt.dump; evt.array.postln; button = \0; dial = dtmfkey.at(button); dur = 0.11.rrand(0.2); button.post; dur.post; dial.postln; [dial, dur].yield; dur = 0.05.rrand(0.1); [\rest, dur].yield; 7.do({ button = keys.choose; dial = dtmfkey.at(button); dur = 0.07.rrand(0.2); button.post; dur.post; dial.postln; [dial, dur].yield; dur = 0.05.rrand(0.1); [\rest, dur].yield; }); [\rest, 0.2].yield; }) ), // ring back + 2600 Pbind ( \instrument, \teltone, [\freq, \dur], Pseq([ Pseq(ringback_tone, (2 + 2.rand)), trunk2600, [\rest, 0.2] ], 1) ), // and now the piece Ptpar([ //ringing 0, Pbind ( \instrument, \teltone, [\freq, \dur], Pseq(ringback_tone, 95) ), // operator dialing 1, Pbind ( \instrument, \teltone, [\freq, \dur], Prout ({ var button; /*82.do*/ {flag}.while ({ [operator.at(\kp2), 0.1].yield; intl_codes.choose.do({ arg key; [\rest, 0.06].yield; [operator.at(key), 0.06].yield; }); 10.do({ button = [\0, \1, \2, \3, \4, \5, \6, \7, \8, \9].choose; [\rest, 0.06].yield; [operator.at(button), 0.06].yield; }); [\rest, 0.06].yield; [operator.at(\st), 0.1].yield; [\rest, 1.82].yield; }) }) ), // drum patterns 8, Pseq([ // stick something after the drums Pbind( \instrument, \plainBuf, //\amp, 0.19, //\dur, Pseq([ // Pseq([ // Pseq([0.2485], 3), // 0.249], 3), // Pseq([ // Pseq([0.2485], 3), // 0.271], 1) // ], inf), // * 16 adds up to 4 [\bufnum, \amp, \dur, \freq], Prout({ var kick, hat, snare, click, tom, drumpats, currentpat, drums, result, repeats, rest, //steps, dur, //have moved outside this Prout accents, amp; kick = a[1].bufnum; hat = a[2].bufnum; snare = a[0].bufnum; click = a[3].bufnum; tom = a[4].bufnum; repeats = 8.rrand(10); // number of times to repeat a pattern before swapping // what we need is the emergency addition of beats in case a loop is too sparse joystick.spec.at(\b6).action_({ |val| var value; value = val.value; (value == 1).if ({ "adding beats".postln; drums = [kick, hat, snare, click, tom].scramble.copyRange(0, 1.rrand(4)); drums.do({ arg drum; 4.rrand(steps).do({ result = steps.rand; currentpat = currentpat.add([result, drum]); }) }); }); }); // too many! // drop a beat! joystick.spec.at(\b5).action_({ |val| var value; value = val.value; (value == 1).if ({ "dropping ".post; currentpat.pop.postln; }); }); // let's keep the accents around 4.rrand(steps /2 ).do ({ drum_accents = drum_accents.add(steps.rand); }); // do the looping thing /*(80 / repeats).do*/ {flag}.while ({ //currentpat = drumpats.choose; drums = [kick, hat, snare, click, tom].scramble.copyRange(0, 1.rrand(4)); currentpat = []; drums.do({ arg drum; 4.rrand(steps).do({ result = steps.rand; currentpat = currentpat.add([result, drum]); }) }); drum_accents.do({ arg ac; currentpat = [[ac, drums.last]] ++ currentpat; }); "new drum pat".postln; repeats.do({ //currentpat.postln; steps.do ({ arg index; amp = 0.15; result = []; rest = \rest; drums = []; currentpat.do ({ arg val, num; //val.first if (val.first == index, { //val.last.postln; ((drums.includes(val.last)).not). if ({ drums = drums ++ val.last; rest = 440; }, { // kill the redundancy currentpat.removeAt(num); }) }); }); if ( drum_accents.includes(index), { amp = amp + 0.09; }); [drums, amp, rest].postln; [drums, amp, dur, rest].yield; }); }); }); // we're done, so set the flag flag = false; }) ), // still in the drum Pseq Pbind( \instrument, \grainBuf, \amp, 0.4, \rate, 1, [\bufnum, \dur, \grainDur, \startFrame, \freq], Prout({ var activebuf; activebuf = b.choose; [activebuf.bufnum, activebuf.dur, activebuf.dur, 0, 440].yield; }) )], 1), //end of drum pseq // busy signal 16, Pbind ( \instrument, \teltone, [\freq, \dur], Pseq ([ Pseq([busy_signal, [\rest, busy_signal.last]],6.rrand(8)), Prand([[\rest, 8], [\rest, 16], [\rest, 24]], 1)], 8 ) ), // telephone operator voice 56, Pbind ( \instrument, \grainBuf, \amp, 0.4, \rate, 1, [\bufnum, \dur, \grainDur, \startFrame, \freq], Prout({ var tick, repeats, accents, substeps, subdur, activebuf, stuttered, stutter_probability, overlap, stut, init_func, stutter_steps; init_func = { arg buf; // number of times to repeat a pattern before swapping repeats = 2.rrand(4); // let's keep the accents around 4.rrand(steps /4 ).do ({ accents = accents.add(steps.rand); }); // ok, now lets' keep the accents off of substeps accents = accents * 4; //accents = accents ++ drum_accents; stuttered = 0; activebuf = b.wrapAt(buf); }; // because there are 4 substeps to a step substeps = steps * 4; subdur = dur /4; // init stuttered = 0; tick = 0; stutter_probability = 0.5; // let's try 50% overlap = 1.02; // multiply the duration by // this will become a joystick function: activebuf = b.choose; init_func.value(3.rand); joystick.spec.at(\b1).action_({ |val| var value; value = val.value; (value == 1).if ({ init_func.value(0) }); }); joystick.spec.at(\b2).action_({ |val| var value; value = val.value; (value == 1).if ({ init_func.value(1) }); }); joystick.spec.at(\b3).action_({ |val| var value; value = val.value; (value == 1).if ({ init_func.value(2) }); }); joystick.spec.at(\lx).action_({ |val| var value; value = val.value; stutter_probability = value; value.postln; }); stutter_steps = CV.new.sp(2, 0, 5, 1, 'linear'); joystick.spec.at(\rx).action_({ |val| var value; value = val.value; stutter_steps.input = value; stutter_steps.value.postln; }); {flag}.while({ (activebuf.notNil). if ({ repeats.do({ activebuf.grains.do({arg grain, index; (accents.includes(index)).if ({ // don't stutter stuttered = index; [grain.bufnum, subdur, subdur * overlap, grain.startFrame, 440].yield; } , { // can stutter stutter_probability.coin .if ({ // will stutter stut = stuttered + stutter_steps.value.rand2; [activebuf.grains.wrapAt(stut).bufnum, subdur, subdur * overlap, activebuf.grains.wrapAt(stut).startFrame, 440].yield; } , { // won't stutter stuttered = index; [grain.bufnum, subdur, subdur * overlap, grain.startFrame, 440].yield; }); }); tick = tick + 1; }); {/*tick < substeps*/ (tick % substeps) != 0}.while({ [0, subdur, subdur, 0, \rest].yield; tick = tick +1 }); tick = 0; }); activebuf = nil; }, { stuttered = 0; [0, dur, dur, 0, \rest].yield; }); }); activebuf = b.choose; //[activebuf.bufnum, activebuf.dur, activebuf.dur, 0, 440].yield; }) ), // bassline 32, Pbind( \instrument, \teltone, \amp, 0.18, \freq, Prout({ var num, repeats; num = [\0, \0, \1, \4, \0, \8, \7, \2, \5, \0, \6, \4, \4]; repeats = 8.rrand(10); {flag}.while({ num = [ [\0, \0, \1, \4, \0, \8, \7, \2, \5, \0, \6, \4, \4], // my childhood // other numbers redacted because they're still current ].choose; repeats.do({ num.do({ arg button; if (flag, { (dtmfkey.at(button) / 8).yield; }); }); }); }); }), \dur, 0.5 ) ], 1 ) ], 1).play; )
The very top part loads drum beat sounds and operator sounds. Below that are a bunch of SynthDefs. Many of them are not actually used. (I told you it was ugly.) Then some code related to my joystick. All of that code runs as one block. The variables whose names are single letters, a, b, c, etc are persistent across blocks, but the rest have a local scope.
The next block contains a whole lot of crap about telephone codes. Then we make some decisions about how many beats are going to be in a loop. The Buffers are divided into grains the size of 16th notes. (Which is to say, 4 grains to a beat.) Then comes the timing logic. Pbinds, Pseqs, etc, handle timing really well. So I've got some of them to do the straight telephony-sounding intro. Then, the drums come in. I haphazardly pick some beats which are accented. Those always have drum sounds on them and are hit harder. Then I randomly pick other beats and stick some drum sounds on them also. The rule of loopy dance music is that if you repeat something enough times, it starts to work by the sheer force of the repetition. That's the logic here. So play that a few times. Then switch it up with new drum sounds, but the same accents.
The next interesting thing is the operator voice. It comes in of it's own accord, but future instances of it are triggered with the joystick. The grains of the buffer can be shuffled. The parameters for this are controlled by the sticks on my gamepad. Accented beats are always played at the correct time. This effect would be more striking if the voice accents were actually in synch with the drum accents, but they're not. So the pattern is the same, but will probably be offset in time.
Finally the bassline (which is low sine tones. You won't hear it on your laptop's built-in speakers) is just phone numbers played at slow speed and shifted down by 3 octaves. I took some numbers out of the code above because I don't want folks calling up my friends and family.
The other numbers inside the piece were totally random. This turned out not to be the best idea. To me, it sounded fine, but I'm not a phreaker, so phone number tones are not as meaningful to me. To some folks in the audience, they could totally tell that numbers were not meaningful. Which is really awesome, actually, even though it's not great for this piece.
Podcast: Play in new window | Download (5.9MB)
I like that music.