Frequency Shifter
-
Nobody? :/
-
@clumsybear this snippet make Hise crash for me, which build is it exported with?
-
Sorry, I didn't implement a Frequency Shifter myself so my expertise is pretty limited on this subject.
However if it crashes, it's right up my alley :)
-
@ulrik Jep, I figured out that it crashes, sry about that. I updated it. It should work now with the latest HISE-scriptnode build. At least I hope so
@Christoph-Hart I have a build with custom nodes, does that affect the snippets I share?HiseSnippet 2608.3oc6c0saaibElzxiskhSZ61fhz6DVTTjEHgP7OIKzKhi+aqQiy5Z4jcuyflZjLgo3vRRYGuEE859Hr8QnO.En2sOIE8QXeCZmeHEGRQIQqZSI6PADDO+Q9cNy46LGNC4LG6gLg99HOAwpmdiKTPbSPmabBtX2KLrbDNbOAwmCNvC9mFBcLuoyEV8BfdmoJryMtF99vtBhhU9ZRMEqtp.82O8lcLrMbLgwYIH7QjkI7cVCrBhy83s+CV11GXzEdp0.tZqs8glHmcQ1ngXTUAzPv0v7Ri9v2aPp1J.Aw01uqU.xqSfQ.zWPb0cPcwXCcsCq9ezx25baHIgrPG7Ehk8AH6tDDSxUX2Krr6dbjz6KHHBNNVWTgoKdN3HqtVixOVm7yoETOtE75CwUlF7j4gWibCOANzsJCcXPf5Nz1HHIxHcGgEXgbRfrCcBfN9VA2v2csPg6u.brUf4EYi2Ux.u3Np6a7F14+Tv985AMChA6pfC9t4smNaCwFYYHJNttqFniomka.99S.xlgIqev2oj.PrrED+mf566z2xAJY5AwXaOe22CCtF4c4K+xQrY+P1r7W9U+tdCcLIp+5tdPWCO3onisMt4k9FCbsgmfuBup941HyK6X88vup1etV0Z+kZ04ZDEv6PpwKMuvvwAZ6Od0HrZm.Oj8KcFN3bn2qpekg8v3q2XD6J4SelQ24IngAVN8OxHvy5SXt86GNnC1ehIb2PzgySbEARSooaPRSrt5.c5RS7ew+BKTljVLrP4nBEHBCzI.mentk14E923+JkyyQpaRkPcwRwFGXXhk1aN1H3BBU.61K.aqA8jLyx6azEPQ.KMwxQEfhv6vJ9TY1PfbS3citKZv.BdItPC+6u0pK4VC.pMZDZHcBtKm3P9Hh8LPAqKVgbgHxVEFrqQv05.FFmgHTnHMlIPo9X6liM7vCZfUZg2HlHv5mnxRUhr7L.5JbUn15ZeJoH8BNQJQstOkLFYl2MgXEr4sKzKvhfdgTRw5LWoW6Y3R9muqsUfr7jEjdDshxYTFcRAgQJFSTX5sHAgwTlhnH9JQAN6lHyclqqXDNYiGZ4EfJN1dXEN6gWPw3u2xF6iJ3rCBceIKuDarGKHqxIHOEf7MwAYQFyUYbz6AkhKewh93..N0vqOLHbH4QV7zAEIovgBTgHZUAevGRBBS3ijwPXWmiGdtskY3fBbbeB8IJEQ03fIxAQ27irb3tDGY7oQoTwpfqgdgwsRJ8CttboUE5D.cICIFNhzkvqYJYVZBNWiD0CjGjB4AJTkTLVVGmD+KAdnUgGPQUJFT.PCojvpFNGEk1sanSQ2ShGcIBhUW8u+C3e+sdaKjOclLGNWEH2LkRSNAFoUX1ZsMo3p9IDKBAt6j.mw85f9XRWJR4SXl08W3zwYZPuRJC5MAm.8gALgch1zIayOiPBxSy3oBqjmt00.uVtQR1PRquvJjjPLcaup.cIMEEkVgDC5yMFd4AfW2NmTijrz0.xoIFoIpgUY5PqgjZC4FpgrhNCPnfKvAPJvyGE3FG6ofAHGjhOFqPTRavufYCxWdwFvV1Feb1.BiELAVnIR0uDL1ndKlAo4i8II0B6X.98vtL37qAlC8CPCjRC6npcXWR+wXkN139zXZA.+krHZmrZHVB1fSBpBLrsIUKsTzyxlz0KEU7BRJjymeRd2UfauOgrFsLsSg6lAK2f3RSVUOedtH2AUNbB.skZm.krZviSVclIJap0RdKJJq.9iIFwLe.603VmXX7sRgKRERMP9VSEVwA.w6mO2A.kNVL4YDKlLGXVCClF2RG7gs4dGazAIyYbhi6jlRxqEQxk+Liku3BJdCflrjL1hujmWxyKbdtRIOund3W7im1RRUucIQujnW7Dc0RhdgQza1RVRskRIQujnW7DcsRhdgQzUZ1VSRsYIQujnW7Dc8RhdwE5tb6VMkJCcujnWHD8prpNXncpQyqAFf+eIbAKgKAIOiVjSAWaJqDndhUBD.zaL1ZApmZs.YUJO1jqCRr1kUAZ5xRsaqKjF9jWHvr5G1XT+f7xQ+vsTyOEeouVdJ11zhuUDuInxoWnjZ1YsXgUYKWlIx+Aw6Dy5YMzbyO6FZVVelCMiqx8wrmKukTqsJCAubj4EPH3sJCAuvBAeKUI8FMKWN7Rh9BfnuUIQuvH5pp5RxpZkD8RhdwSzaWRzKtIUSUSSRStjnWRzKLh9SF8Bt0njoWXLccLCSZqVkL8Rl9Bfo+Y+6xpdwwz0j00kkJY54koyDvRt9cvRkoVtTY2gKUlbqlR33yywRkM4EzYdJgyC9F.5mAlxh4yDa7uwe4b8MSw+0dy9t4i+PnUWZ+BvFa2KXclWgAntTjkpS34bHOpJ2SfOFY.tEkcCfoMzvK0xiuIiySK5g4BydG6jeRz6vc7l+yalnKU9uue4xuu+GPee+4Xnh4J7uHXk4G1uaO7ue6NK6eX+0.9V8UvdrR4O6YLGGgE9.JfgErijoFU15.WnwkJYtKJPJYoSMKJNdzNaRt8jO06vWGqQ5dRdQZiuwczdoUz9kv9NFmaGsUiE0Eguqi5fB8Au8e8Gi6lFk217cVw0juKKttwcbgavF+v+5MIhzez9w19ex0C56S+L0wZu78t3DNTq9xxPsOt3KyeLxzNmuHcDlJO.BwbkLCwTcIHDy0FODylkgXdWNxvFoCtTaoJ3xxvIKCmL+gSRiIP4Qv7NsveQgI1G5SapfxZVHVhdkfkSLu9wCnTgyqG.3Zk5QoqxLVbsJGJYZCknHLk2N+tVWkMEDWviC0pxLTqJ2op0piTqpKGp0hIfesYnj0lSkr17DV+n4oPsbdJJn4oP8wz7Tn94x7TLK1D+Sx0p7I4Jx4rHgLrV5P8m4CXLqmcXrmUPTbWDtuhtKLRmkg3jbzijqqQBVBO7vskpyouOAG1E2wRNp.xf+rHVv1zziYJnZORDz4ZlylbhIFRY+w194WXa9uka2u2xs620wWNs46cpYQsg+B.85Ejzd6o.CGC6a7gRjhVp2ieSTxGNbOi.Chy+vivBtpJJtG7JKSHy0RUvdP+KCPtINfTdFqPrVC+T8zSKHpV7WQSW+aMtBV+qg3m2m49dBGUP0+w+8+HuGUPt49nB5aLCv29S8Lb7cQ9ItvcfCrNE4Pjw3LwDJhaV1DHwk+tHCuLK5mdyAXgLy1zwHXnGsG4sCPCcBRbn0T4+ySJlLOxhVs7HKZVmSLMl54DShyZmpQXriE4L9Xer6HaLuPfsDN6A6YLzNHJ2j10GgbPtWfbHtfi6zOAF3Y0uOLQXyYJPuMHvv7x3bd91m.wg7xa.+a19cXCOCuiXOSz7nKjyutXZ8Wu.vfacBYr9iiiZpJOhNpotEPoHNljJh6w.CSOzYlrSWJ5lhNMGrb6POz3pBNhjttrvUwSkcCgAXOhmYZFsEXjcaTli1nNGsQaNZi9bzllyQaZMGsYqo1FxfQucX.Z.iJhy338oQkHJFOaGUD9ejlVodC
this one should work now
-
Yes of course, if your snippet contains nodes that you've exported to C++, it won't load them if they aren't compiled.
Just press
U
before you export the snippet to switch to the non-hardcoded version of the node. -
the node was opened while exporting the snippet, did it work on your side?
If I was wrong, this is the snippet with the expanded node
HiseSnippet 2615.3oc6cssaaibFdnkGaKEmrs6FTjdmvhdQVfDBwSRVnWXm3CaMZbVUKmr6cFzTijILEIWRJ63snnW2GgsW0KK5CPA5c6s8knnOB6aP6bfTbH0IZAaJYaJff34D42++7+8O+bFxYZ44Xf78c7.BkO4ZWDPXSX6qsCNe2y0MsAGtGP34vC7Pe+.jsw0sO2ra.x6TEvau1U22G0AHHT5qI0Tn7p.5ued62paoaafhyB.9nioA5cl8MChys0N+dSKqCz6fNwrOWsU24PCG6ccrbFfQUIXMfqtwE58PuWmTsUf.g01uiYfiW6.8.jOPX025zAiMmqrY0+il9lmYgHIj.swWHV1G3X0gfXRtfcO2zpSqHo2G.Dfsh0EkX5hmCOxri4v7i0I+BZAUiaAu9PXkoAOId3UKyvCvgtUYnCCBmNCrzCRhLR2QXAlN1IP1g1AHaeyfq46tVnv8WBaYFXb93w6JiAu3Np6Z7F14+T39c6hLBhA6pvC9t69dZAgQ0cUfsM7LcCv2eBP1LLY0C9N4D.hkMP3eBqtucOSajngGBis87ceOJ3JGuKd4WNjM6Gxlk9xu521cfsAQ8W00C4p6gNwokk90uzWuuqE5X7U3UUOyxw3h1l+.5qp7GqTtxepRUtFQA7aI03kFmqaair7GsZDVscfmi0KsGz+Lj2qpdot0f3q2HD6RSVexSrGS24wNCBLs6cjdfm4mvb62Onear+DCztgnCmmvJ.RSooqQRSrtZir6PS7+v+BKThjVHrPonBADgAYGfyOT2R67B+a7ekx44P0MoRNcvRwFGnafk1qaoGbNgJfc6Efs0PdhFiy6azEPFfklX4nDTF7NrhOUl0.jaBuazcc52mfWhKzv+9aM6Pt0PnRsZgFRGi6xINjOhXOCkw5hUHWHhrUhA6JDbsNjgwYHB4JRiYBT9F1toktGdPCrRK7FwDAV+DUVJSjkmActDWEpst5mRJRufSjRTq6RIiQlicSP.N171E4EXRPOHkTrNyU5Ud5tj+46ZYFHIMYAoKQqHeJkQmTPXjhQDEldKRPXLkoHJBuR.vY2DYtybcEivIa7PKOGTww1CqvYO7BJF+clVXeTAmdPn6KIokXi8XAYUNA4oPGeCbPVjwbkGE8dHw3xWrnON.fSz85gBBGRdnEOcPQRJbn.kHhVY3G7Qjfv.ejLFB65zZvYVlFgCJvw8IzmnTDUiMlHGDcyOxzl6Rbj9mFlRAqBtB4EF2JozO35xkVAzN.4RFRLbDoKPWwTxrzDbtFIpGDOHAYAJTkTLVVGmD+KAdnUgGPQUJFTPXMwjvpBNGY4lMqoQQ2ShGcIBhkW8u9i3e+kt6.xlNShCmqBkpmRoIk.izJLas1lTbU8XhEAf6NA3LtWG1CS5RQJeByrt2BmNNSC5URYPuI7XjOJfIrSzlNYa9LBIHKMimJrRV5VWC9ZoZIYCIs9BqPRBwzs8JC0DUkkkaDRLnO2X3kGBecyLRMRxRWCJklXjlnFVkoCsZhJ0jpoDxJZ22wI3bb.j.d9HfabrmB66X6H6iwJxIoM3myrA4KOeCXa7Feb1.fQBl.KzDo5KfiLp2hYP5nXevOBPBhE1s.5GPcXf4WCMF3G3zWLMnip1gcH8FiT5Hi5SinEB8WxhmkO.vIMb+FbRPYntkEoZokhtlVjNdwnhWPRgT17Rx6rBdy8HLtwJS6R31YnxMHNzjTzxleKxcPgCmPXSwlIPIqF73jUmYhx5pMj1hhxRv+PhwKyFvdMt0IFDeqT3hTgTCiu0TgUb3O7d4yb3OoiDSZFQhIwAl0vfo1Mz8dXatywFcHxLFk3ntnoj7JQjboGYr7EWHwa.UkDkvV7E77BddtyykK3440i9he3zFhJZMKH5ED87mnqTPzyMhd8FRhJMjKH5ED87mnqVPzyMhtb8lphJ0KH5ED87mnqUPzyuP2kZ1ntXQn6ED8bgnWlU09CrRMZdEXe7+KhKXIbAH4YzBbJ3JSYc.0RrNfPnVsQVIPsTqDHqRYwlbcXhUtrLTUSRrYSMPZ3Sdc.GW+vFC6GjVN5GtgZ9o3K80RSw1lV7Mh3MAUN8BkTyNqkJrLa4xLb7uW7Fwr93FZt9itglkzl4Py3pbWL64RaI1XqhPvKFYdADBdihPvysPv2RQTqV8hkCufnu.H5aUPzyMhthhlnjhZAQufnm+D8lED87aR0TTUEUkJH5ED8bin+jgufa0JX54FSWCyvD2pQASufou.X5O5eWV0xOltpjlljXASOqLcl.Vv0uEVpLkhkJ6VboxjZTWDGedFVprIufNySIbdv2.R+HvjWLejXi9E9KkouYJ9u0a1WMe7mAsxR6W.1H6cAqy7Jz2oCEYo5DdNGxipxcD3iQFjaQY2.ZXgz8Rs73ax37zhtetvr2xN4mD8Nb+t4+t8Dcox+08KU708eO5q6OCCULWg+EAqw9Y8C96juq++81K4eV+Uf9l8jwdrR4O6YLGGgEdOJfgErijoFU15PWj9ExicOTfTxRmZVPXznc1jb6Iep2guNVC08j7hzFei6vcRqncKg8s0OyJZiFKpKBeWG1AE5Cdm+7OE2MMLuc36rhqIeWVbci63B2dM9w+01Ihze3tw19ex0C46S+L0wZur8t3DNTq1xxPsOr3KyeLxzNmOOcDlx2CBwbkwFhoxRPHlqMZHl0KBw71bjgMRGbo5RUvkEgSVDNY1CmjFSf7Cf4cZg+hBSrOzl1TAMtYgXI5UBVJw75GOfRINudPnqYpGktLyXw0rXnjoMThLXJuc9cLub7TPbAOLTqxyPsJeqpVKOTsprbnVym.9UmgRVcNUxpySX8CmmBkh4oHmlmBkGRySgxik4oXVrI9mjqQwSxkmyYQBYXszg5OyGvXVO6Pro3ei8rBBB65f6qn6BizYYHNIG8H45ZjfkvCObao5b56SvgcvcrjCJfwveVDKXaZ5wLET0GHB5bMyYSNwDCor2Ha97Krs92hM62a3l8653Km578N0rn1tegvtcCRZu8Tntst009HQRQK06vuIJ4CGtmdfNw4e3AXAWUED1CcooAh4ZoLbOj+EANtINdTdFqPrVC+T8zyJHpV7WQSW8a0uDU8qQ3m2m49dBGTPU+o+y+HqGTPtY9fB5aLBv29S7zs8cc7Sbgai5adhiMQFiyDSnHtYYSfDW965n6M1h94sO.KjisMs0CF3Q6QdSemA1AINxZJk4yIlavAVzpEGXQy5Tho1TOkXRbR6TNBisMImvG6icGYg4E.1R3rGpq9.qfnbSZWejisi64N1DWvwc5GiB7L60CkHr4wJPuIHP23h3bd9NGivg7xa.+a14cXCOcuiXOSz7nKjxttXZ8Wu.xfaUBYr5CiCZpROJOnoxkCIo73dzW2vy4TC1YKEcSQmlCVtsoGYbkgGQRWUBbY7TYWCzG6Q7TCins.iw2F44nMJyQaTmi1nMGso9bzlFyQa1ZpsgLXzaFD3zmQEwYzZeZTIBBwy1QIv+GfPOo5L
-
The only thing left would be to separate the sidebands. But I’m not sure how to substract signals from each other in scriptnode
-
Multiply with -1 and add.
-
I get the
math.mul
with-1
, butadd
like inmath.add
with value0
? Or do you mean to just sum the two signal chains after flipping the phase? @Christoph-Hart -
You don't need the
add
node, just use a split container and flip the phase of one signal:HiseSnippet 1402.3oc6X87abSDEdb1LMIaaQkRPhiqBbHUpDEuMPQhC61jMaTDYSVhS+wsnI1SxNJ1yXrGmzsENAG3eA3.bE3.24D8O.hTkx+.vQtEtvAtTdiG6X6MaRVVozTUgur67Mu23u22al2LiaGHroggh.jwDaz0mhLtF1pKW1YgNDFGsbCjwafaQBkzfJZn465SBCoNHCiRKo.LlXTT7yQ0lm3R31zLHD5ABlMcElGSlg1t9mvbcaRbnav7xY8b0W1VvWP3Jh.9TBOKxmXuKYG5pDkYifQFWYQGlTDXIIRZHxXz4ENcs5H1mqs+ArP1VtTUCSjELPZ3lBWGEiU+GsPGlqS6z3NDgLvsyTgRZUXRbKlC6X7L03FwcTIyi75gwHmE8LySuY6K8L6C8P4X2nZ1AjP3D4RjEYlJcjzASTLSsLWR4gLY27oqKU59l31Locm9y2Q5CegD0EMeSR9WGu31aSskYjcTbyGMrY5+CSDMNo1UFaYGv7kv6WQjqkzrRyGYVfPZXjwOhWjuCiSmwNfBTqQn+pT49hfcmdJmP+otUkO95k2NhaqT7J9ATeR.cCQaWR2oCId9tz0AutcksbE16ZwdB8VkeZ4IJ+EkqjyoXNNuxhos6P3bpa3IMSsPlKCDtSyi71hFb6J6QbixFuSrVdrASBE7k4L4Z9zDyJDCovPkibrLC8XRkA0mYDqKhjL9NsHx.1igxCqF4YAkjroKjDs.lwHHkqwsmU0VMA0hxcha7B3IoSSUaijNMS6Do3AkKA7j7Sb9O4+v+TIaLFRYJTgCHCi2jXCxU21DYG0xGnToDleRClw934oWQ4gIBHbuTcEPGJ.aTRMroEa07eAgmmhTpRsI++gLG0qCabavB8Dt0g4IpB2sByW51XD03oihX9NlhPigC8cgZ+mJ20ceQQXsOJRMRhHpIkVwNGA8RQESILpMI.10C12MoFkOMPxh6IWjLtNRLekNTJn8kwBNcSeArvt.muIdalqJZm439ubHc1FRaPB1gJ6MW.mVHsE33x78f7htDbKF+ApRb4GuVjGWD6qOr9Jh8oA8bjn4peee+dQAasjTeUYXE129Mpmeol0tz80ZmB8fO+Ou6W5cPMk3dUby.5mEQ41cQG+ZicK7mpiFDdem3mCqkm2G7apm2sGdmZYQdmZ64y6+469g+dp+54w7tD9SQ8HcGUCM35b0mcBcFUsu5b0m0GcFr874aJszkYiO4aOuw7STNWJiPmjxGUqeTFg5GkOpVdJ+zI+4k9im7U8P4Wz86a+VMNLYpgkmPH6.6qkw6T2tz38wXmgT2RU7nOR8oTRDi8hbKVaoL1C9cFE9qn0TLF7DvQ+5E+Dm7Ifwv5W1InQwLvouc0.1y8WtAQRTZQxQixYpgQC5dv8H0GIdBbCZ3tRgegytOotyqheHYOZ7UXiOQzaG2daQfWkknvFhpKgXdFWh84C5kX8G3KwtlsDnvFADdnuHjZlej6oup46qAUFwKZtFpfUpvqIDd4ra75ofUyC1lvKLXP6BiTK0gcSa960VjSfieaQgnwYsPaHXI8L8ncjaHMdYfYdGyfqlG1nzPckohWpMm8++E4O0KxWZvn64bCzIR4nEScwvEgxRtvRxXNdSXM31jHWYJZw0TsDbgeGAmYmeB15T3BU6rCsXMl9EP2SJI16lgLY80otTRXt0YuW8UfC3RBh2dX3zByAWKNq706f0zshpPPkWO9.LkdM5CvbQrBEpU9JPl9kwGp3kw6viXGH1zV+oYTK8FOFAhad7W9cBbKU6Jln8zmBAimclYQdPA7MssUyNdePe5uOUGBetyP3ybCgOevP3yGND9b2gvmO5L8Qs248hjBO87e.n8hwmeyvPetg3kBn+E.xMaFB
-
ok, got you! So if I want create a duplicate signal with -90° phase shift I multiply by -0.5, right?
-
well, I'll just keep it as it is for now and call it a ring modulator ;)
-
ok, got you! So if I want create a duplicate signal with -90° phase shift I multiply by -0.5, right?
I am not a DSP expert, but the only trivial phase change that can be achieved by simple multiplication is 180° because it's just a flip of the signal. Multiplying with
-0.5
will just result in a 180° phase shift plus a gain adjustment of -6dB.Any other phase change can't be applied on a signal because there is no frequency dependant phase value, so you'll need some kind of all pass filter which does that for you.
To be honest, I've still haven't heard one frequency shifter that does not use FFT processing which doesn't sound like the last breaths of a dying robot (even the one in the STK toolkit sounds rather, well "experimental"), but if your "ring modulator" sounds good, then don't touch it :)
-
@Christoph-Hart said in Frequency Shifter:
Multiplying with -0.5 will just result in a 180° phase shift plus a gain adjustment of -6dB.
makes sense, I already figured out that even if I manage to flip the phase by 90° it will only change the phase of one frequency. Like you said, I'd need a Allpass filter network of some kind. That's why I rendered out a Hilbert transfer function as an IR in Matlab. It's an IR of an Allpass filter network that results in a phase flip of 90° over a 500-15000hz range with minimal error.
@Christoph-Hart said in Frequency Shifter:
To be honest, I've still haven't heard one frequency shifter that does not use FFT processing which doesn't sound like the last breaths of a dying robot (even the one in the STK toolkit sounds rather, well "experimental"), but if your "ring modulator" sounds good, then don't touch it
It only sounds ok if I put the whole node graph in a 2 frame block container and oversample the frame block container . if not there are too many artefacts.
So the issue right now is that Im not able to run an IR within the frame block container. If they are in separate frame blocks, the result is terrible.
-
How long is that impulse response?
You might get away with brute-force convolution (just a normal FIR filter) if the tap size is below 64.
-
It's 1024 samples long and generated at 44100 SR. I'm not sure about the tap size, is it the same as the length in samples?
-
Yes, well that may be too long and if you oversample this to run per sample, it will definitely burn your CPU (since you can't do FFT based convolution with one sample and have to resort to brute-force time-domain convolution).
I don't know too much about FIR filter design, but if you take a look at the 1024 sample IR, is there any substantial (non-zero) value after ~64 samples? Because if not you can truncate it to 64 samples (which is the break-even point where the FFT based convolution will yield better performance) and check how it sounds.
-
I had a read on that topic, and it seems to work with exactly 64 samples as well. I'll try to render out a smaller version or truncate it.
Can you elaborate on the brute force convolution technique wit FIR filter in HISE? I'm not entirely sure what you're referring to.
-
The current convolution module is doing the convolution on the frequency domain, but for smaller impulse responses (as I said, up to 64 samples), directly convoluting each sample is more efficient (and in our case, also possible on per-sample level). The pseudo code algorithm for this looks like something like this (it's not the actual formula, but the complexity is similar:
for(int i = 0; i < blockSize; i++) { for(int j = 0; j < irSize; j++) { output[i] += output[i - j] * ir[j] ; } }
As you can see, it has to iterate over the IR size for each sample so for bigger impulse responses the CPU power goes up exponentially.
I think I will add a
filter.fir
module that you can use instead of the convolution module, which will do this and work in a frame container. -
Actually I just need to wrap this JUCE class, which does it already :)