{
   "labels" : [
      "amplitude",
      "graphic",
      "visualization",
      "loudness"
   ],
   "id" : "1-4Zn",
   "is_private" : null,
   "code" : "// See http://www.tcelectronic.com/media/1014018/skovenborg_nielsen_2007_loudness-meter_dafx.pdf for background.\r\n\r\n(\r\n// This is entirely interchangeable with a loudness algorithm of your\r\n// choice. Just make sure that the values returned are in [0..1].\r\nSynthDef(\\loudness, {\r\n\t|in = 0, shortRate = 6, longRate = 6|\r\n\tvar sig, chain, loudness;\r\n\tsig = In.ar(in, 1);\r\n\tchain = FFT(LocalBuf(1024), sig);\r\n\tloudness = Loudness.kr(chain) / 64.0;\r\n\t// The original paper uses an algorithm called \"TC LARM\" that has an\r\n\t// adjustable window size, using 0.5s for the short-term and 2.0s for\r\n\t// the long-term. Here we cheat and use the same loudness measurement\r\n\t// twice, with a lowpass on the long-term. With other algorithms you\r\n\t// may want to use their method. Keep in mind that having a nice\r\n\t// smooth long-term loudness is good for aesthetics.\r\n\tSendTrig.kr(Impulse.kr(shortRate), 0, loudness);\r\n\tSendTrig.kr(Impulse.kr(longRate), 1, LPF.kr(loudness, 0.3));\r\n}).add;\r\n)\r\n\r\n~loudbus = Bus.audio(s, 1);\r\n\r\n(\r\nvar size = 400;\r\nvar canv, synth, short, recv;\r\nvar longSize, long, longPos;\r\nw = Window(\"Ring\", Rect(100, 100, size, size));\r\nw.front;\r\n\r\n// Short-term loudness meter level\r\nshort = 0.0;\r\n// Size of the long-term loudness history. Increase for a smoother display.\r\nlongSize = 256;\r\n// Long-term loudness history\r\nlong = Signal.newClear(longSize);\r\n// Make the position match the second hand on the clock, just for fun\r\nlongPos = ((Date.getDate.second + 45) * longSize / 60).asInteger % longSize;\r\n\r\nsynth = Synth.tail(s, \\loudness, [\r\n\t\\in, ~loudbus,\r\n\t\\shortRate, 15,\r\n\t\\longRate, longSize / 60\r\n]);\r\n\r\ncanv = UserView(w, Rect(0, 0, size, size));\r\ncanv.background_(Color.black);\r\ncanv.drawFunc_ {\r\n\tvar center, wedge;\r\n\tcenter = size@size * 0.5;\r\n\r\n\twedge = { |l1, l2, color|\r\n\t\tPen.color = color;\r\n\t\tPen.addAnnularWedge(size@size * 0.5, size*0.4, size*0.45, (pi/2) + (1.5pi*l1), 1.5pi*(l2 - l1));\r\n\t\tPen.fill;\r\n\t};\r\n\r\n\t// These levels and choices of color are for the sake of example.\r\n\t// They are in no way the best options, and you will want to tune them.\r\n\tcase\r\n\t{ short < 0.3 } {\r\n\t\twedge.(0, short, Color.green);\r\n\t}\r\n\r\n\t{ short < 0.6 } {\r\n\t\twedge.(0.0, 0.3, Color.green);\r\n\t\twedge.(0.3, short, Color.yellow);\r\n\t}\r\n\r\n\t{ short >= 0.6 } {\r\n\t\twedge.(0.0, 0.3, Color.green);\r\n\t\twedge.(0.3, 0.6, Color.yellow);\r\n\t\twedge.(0.6, short, Color.red);\r\n\t};\r\n\r\n\tlongSize.do { |i|\r\n\t\tvar wedge;\r\n\t\tvar rhoInner, theta1, theta2;\r\n\t\ttheta1 = 2pi*i/longSize;\r\n\t\ttheta2 = 2pi*(i+1)/longSize;\r\n\t\trhoInner = size*0.1;\r\n\r\n\t\twedge = { |base, l1, l2, color|\r\n\t\t\tvar rhoB = rhoInner + (base*size*0.35);\r\n\t\t\tvar rho1 = rhoInner + (l1*size*0.35);\r\n\t\t\tvar rho2 = rhoInner + (l2*size*0.35);\r\n\t\t\tPen.color = color.blend(Color.black, 1 - ((i - longPos + 1) % longSize / longSize));\r\n\t\t\tPen.moveTo(center + Polar(rhoB, theta2).asPoint);\r\n\t\t\tPen.lineTo(center + Polar(rho2, theta2).asPoint);\r\n\t\t\tPen.lineTo(center + Polar(rho1, theta1).asPoint);\r\n\t\t\tPen.lineTo(center + Polar(rhoB, theta1).asPoint);\r\n\t\t\tPen.fill;\r\n\t\t};\r\n\r\n\t\t// These levels and choices of color are for the sake of example.\r\n\t\t// They are in no way the best options, and you will want to tune them.\r\n\r\n\t\t// BUG: Sometimes the quads look a little weird when two consecutive\r\n\t\t// samples bridge one of the color thresholds.\r\n\r\n\t\tcase\r\n\t\t{ long[i] < 0.3 } {\r\n\t\t\twedge.(0, long[i], long[(i+1)%longSize], Color.green);\r\n\t\t}\r\n\r\n\t\t{ long[i] < 0.6 } {\r\n\t\t\twedge.(0.0, 0.3, 0.3, Color.green);\r\n\t\t\twedge.(0.3, long[i], long[(i+1)%longSize], Color.yellow);\r\n\t\t}\r\n\r\n\t\t{ long[i] >= 0.6 } {\r\n\t\t\twedge.(0.0, 0.3, 0.3, Color.green);\r\n\t\t\twedge.(0.3, 0.6, 0.6, Color.yellow);\r\n\t\t\twedge.(0.6, long[i], long[(i+1)%longSize], Color.red);\r\n\t\t};\r\n\t};\r\n};\r\n\r\nrecv = OSCFunc({ arg msg;\r\n\tif (msg[1] == synth.nodeID) {\r\n\t\tif (msg[2] == 0) {\r\n\t\t\tshort = msg[3].clip(0, 1);\r\n\t\t\t{ canv.refresh }.defer;\r\n\t\t};\r\n\t\tif (msg[2] == 1) {\r\n\t\t\tlong[longPos] = msg[3].clip(0, 1);\r\n\t\t\tlongPos = (longPos + 1) % longSize;\r\n\t\t\t{ canv.refresh }.defer;\r\n\t\t};\r\n\t};\r\n}, '/tr', s.addr);\r\n\r\nw.onClose_ {\r\n\tsynth.free;\r\n\trecv.free;\r\n};\r\n)\r\n\r\n// Listen in from a microphone\r\nx = { SoundIn.ar(2) }.play(s, outbus: ~loudbus);\r\nx.free;",
   "name" : "Radial loudness meter",
   "author" : "snappizz",
   "ancestor_list" : [],
   "description" : "Short-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."
}
