«Radial loudness meter» by snappizz
on 08 Jun'15 00:25 inShort-time loudness is represented by an outer arc, and long-term loudness is represented with a circular graph showing the loudness over the last 60s of audio.
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
// See http://www.tcelectronic.com/media/1014018/skovenborg_nielsen_2007_loudness-meter_dafx.pdf for background. ( // This is entirely interchangeable with a loudness algorithm of your // choice. Just make sure that the values returned are in [0..1]. SynthDef(\loudness, { |in = 0, shortRate = 6, longRate = 6| var sig, chain, loudness; sig = In.ar(in, 1); chain = FFT(LocalBuf(1024), sig); loudness = Loudness.kr(chain) / 64.0; // The original paper uses an algorithm called "TC LARM" that has an // adjustable window size, using 0.5s for the short-term and 2.0s for // the long-term. Here we cheat and use the same loudness measurement // twice, with a lowpass on the long-term. With other algorithms you // may want to use their method. Keep in mind that having a nice // smooth long-term loudness is good for aesthetics. SendTrig.kr(Impulse.kr(shortRate), 0, loudness); SendTrig.kr(Impulse.kr(longRate), 1, LPF.kr(loudness, 0.3)); }).add; ) ~loudbus = Bus.audio(s, 1); ( var size = 400; var canv, synth, short, recv; var longSize, long, longPos; w = Window("Ring", Rect(100, 100, size, size)); w.front; // Short-term loudness meter level short = 0.0; // Size of the long-term loudness history. Increase for a smoother display. longSize = 256; // Long-term loudness history long = Signal.newClear(longSize); // Make the position match the second hand on the clock, just for fun longPos = ((Date.getDate.second + 45) * longSize / 60).asInteger % longSize; synth = Synth.tail(s, \loudness, [ \in, ~loudbus, \shortRate, 15, \longRate, longSize / 60 ]); canv = UserView(w, Rect(0, 0, size, size)); canv.background_(Color.black); canv.drawFunc_ { var center, wedge; center = size@size * 0.5; wedge = { |l1, l2, color| Pen.color = color; Pen.addAnnularWedge(size@size * 0.5, size*0.4, size*0.45, (pi/2) + (1.5pi*l1), 1.5pi*(l2 - l1)); Pen.fill; }; // These levels and choices of color are for the sake of example. // They are in no way the best options, and you will want to tune them. case { short < 0.3 } { wedge.(0, short, Color.green); } { short < 0.6 } { wedge.(0.0, 0.3, Color.green); wedge.(0.3, short, Color.yellow); } { short >= 0.6 } { wedge.(0.0, 0.3, Color.green); wedge.(0.3, 0.6, Color.yellow); wedge.(0.6, short, Color.red); }; longSize.do { |i| var wedge; var rhoInner, theta1, theta2; theta1 = 2pi*i/longSize; theta2 = 2pi*(i+1)/longSize; rhoInner = size*0.1; wedge = { |base, l1, l2, color| var rhoB = rhoInner + (base*size*0.35); var rho1 = rhoInner + (l1*size*0.35); var rho2 = rhoInner + (l2*size*0.35); Pen.color = color.blend(Color.black, 1 - ((i - longPos + 1) % longSize / longSize)); Pen.moveTo(center + Polar(rhoB, theta2).asPoint); Pen.lineTo(center + Polar(rho2, theta2).asPoint); Pen.lineTo(center + Polar(rho1, theta1).asPoint); Pen.lineTo(center + Polar(rhoB, theta1).asPoint); Pen.fill; }; // These levels and choices of color are for the sake of example. // They are in no way the best options, and you will want to tune them. // BUG: Sometimes the quads look a little weird when two consecutive // samples bridge one of the color thresholds. case { long[i] < 0.3 } { wedge.(0, long[i], long[(i+1)%longSize], Color.green); } { long[i] < 0.6 } { wedge.(0.0, 0.3, 0.3, Color.green); wedge.(0.3, long[i], long[(i+1)%longSize], Color.yellow); } { long[i] >= 0.6 } { wedge.(0.0, 0.3, 0.3, Color.green); wedge.(0.3, 0.6, 0.6, Color.yellow); wedge.(0.6, long[i], long[(i+1)%longSize], Color.red); }; }; }; recv = OSCFunc({ arg msg; if (msg[1] == synth.nodeID) { if (msg[2] == 0) { short = msg[3].clip(0, 1); { canv.refresh }.defer; }; if (msg[2] == 1) { long[longPos] = msg[3].clip(0, 1); longPos = (longPos + 1) % longSize; { canv.refresh }.defer; }; }; }, '/tr', s.addr); w.onClose_ { synth.free; recv.free; }; ) // Listen in from a microphone x = { SoundIn.ar(2) }.play(s, outbus: ~loudbus); x.free;
reception
Nice meter! I get this error however:
ERROR: Qt: You can not use this Qt functionality in the current thread. Try scheduling on AppClock instead. ERROR: Primitive 'QWidgetRefresh' failed.
Calling canv.refresh inside a Task scheduled on the AppClock fixes this:
Task( { loop{ canv.refresh; (1/60).wait } } ).play(AppClock);
The error come from the OSCFunc at the end of the code, you should wrap the two "canv.refresh" in "{ canv.refresh }.defer"
Fixed! Thanks for the correction.