«Granular display» by tommaisey
on 15 Oct'12 04:13 inMy first post to sccode! A display which visualises a granularised sound file. Each grain is represented by a dot which moves across the waveform of the sound file at its real speed (i.e. pitch) The dots fade in and out to show the effects of each grain's envelope, and amplitude is shown by position on the y-axis.
Partly inspired by the Borderlands granular synth (https://ccrma.stanford.edu/~carlsonc/256b/final/).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
// Granular display by T.Maisey. // Position in the sound file is (obviously) on the x-axis, amplitude on the y-axis. // The grain's transparency shows its position in its envelope (window). // The speed of the grain shows pitch. // ---------------------------------------------- // Requires use of Qt gui kit.: GUI.qt; ( var soundFile, buffer, window, fileView, grainView, granary, displayGrain; soundFile = SoundFile.openRead(Platform.resourceDir +/+ "sounds/a11wlk01.wav"); buffer = Buffer.readChannel(s, soundFile.path, channels: [0]); { SynthDef(\grain, { |bufNum = 0, pos, dur, env = 0.05, pitch = 1, pan = 0, amp = 0.5| var envl, sig; envl = EnvGen.kr(Env.linen(env, dur, env, amp, -3), doneAction: 2); sig = PlayBuf.ar(1, bufNum, pitch, 0, pos.linlin(0,1,0,BufFrames.kr(bufNum))) * envl; Out.ar(0, Pan2.ar(sig, pan)); }).add; s.sync; }.fork; ~pos = { rrand(0.2, 0.6) }; ~dur = { rrand(0.1, 0.5) }; ~env = { rrand(0.3, 0.5) }; ~amp = { rrand(0.1, 0.7) }; ~pitch = { rrand(0.5, 2) }; // pitch is a ratio ~pan = { rrand(-0.8, 0.8) }; ~wait = { 0.1 }; // time between grains ~playTask = Task({ loop { var pos, pitch, amp, dur, env; Synth(\grain, [ \bufNum, buffer, \pos, pos = ~pos.value, \pitch, pitch = ~pitch.value, \amp, amp = ~amp.value, \dur, dur = ~dur.value, \env, env = ~env.value, \pan, ~pan.value, ]); displayGrain.value(pos, pitch, amp, dur, env); ~wait.value.wait; }}); // Grain display window = Window.new("Grain display", Rect(200, 300, 740, 300)); fileView = SoundFileView().readFile(soundFile).gridOn_(false); grainView = UserView().resize_(5); window.layout = StackLayout(fileView, grainView).mode_(\stackAll).index_(1); window.onClose = {p.stop}; window.front; // Where information about currently playing grains is stored for drawFunc to access: granary = (); // Add a grain to the granary: displayGrain = {|pos, pitchRatio, amp, dur, env| var time, routine; // I've limited it to displaying 30 grains, but I don't see why it couldn't do more. if (granary.size < 30, { time = Date.getDate().bootSeconds; routine = Routine { granary.put(time.asSymbol, [pos, pitchRatio, amp, dur, env]); (dur + (env * 2)).wait; granary.removeAt(time.asSymbol); }.play }); }; grainView.animate = true; grainView.drawFunc = { var width, height, now, fileDur; width = grainView.bounds.width; height = grainView.bounds.height; now = Date.getDate().bootSeconds; fileDur = soundFile.duration; Pen.fillColor = Color.white; Pen.strokeColor = Color.black; granary.keysValuesDo {|k, v| var xP, yP, delta, alpha; delta = now - k.asFloat; xP = (width * v[0]) + (width * ((delta * v[1]) / fileDur)); yP = (height * (1 - v[2])); if( delta < (v[3] + v[4]), { alpha = (delta/v[4]).clip(0,1) }, { alpha = 1 - ((delta - (v[3] + v[4])) / v[4]) } ); Pen.translate(xP, yP); // moveTo doesn't seem to work in this context, so translate... Pen.addOval(Rect(0,0,10,10)); Pen.alpha = alpha; Pen.fillStroke; Pen.translate(xP.neg, yP.neg); // ...and translate back. }; }; // Start the grains: p = ~playTask.play; ) // Try some different granular parameters: ( ~pos = { rrand(0.3, 0.8) }; ~dur = { rrand(0.01, 0.05) }; ~env = { 0.02 }; ~amp = { rrand(0.2, 0.6) }; ~pitch = { rrand(2, 4.0) }; ~pan = { rrand(-0.8, 0.8) }; ~wait = { 0.01 }; // time between grains ) // Or how about these: ( ~pos = { rrand(0, 0.5) }; ~dur = { rrand(0.1, 0.2) }; ~env = { rrand(0.3, 0.5) }; ~amp = { rrand(0.2, 0.8) }; ~pitch = { rrand(0.2, 0.7) }; ~pan = { rrand(-0.8, 0.8) }; ~wait = { 0.1 }; ) // To stop the grains, close the grain display window or evaluate: p.stop;
descendants
full graph
«Re: Granular display» by julian.rohrhuber (private)
reception
I'm really having fun with this, seeing the grains makes these things so much cooler
Yeah, it makes the process much more intuitive. I'm working on a version that allows you to 'paint' onto the waveform, it's really fun.