RE: Buffer.detectPitch()
-
Re: Buffer detect pitch returns 0
@d-healey Did you end up getting this working? I have a similar setup and despite trying a bunch of different settings, it never consistently detects the pitch of a sine wave:
Content.makeFrontInterface(600, 600); const var props = { "BufferLength": 2048, "WindowType": "Blackman Harris", "DecibelRange": [ -100.0, 0.0 ], "UsePeakDecay": false, "UseDecibelScale": true, "YGamma": 1.5, "Decay": 0.7, "UseLogarithmicFreqAxis": true }; const var dp0 = Synth.getDisplayBufferSource("Parametriq EQ1"); const var dp = dp0.getDisplayBuffer(0); dp.setRingBufferProperties(props); const buffer = dp.getReadBuffer(); const timer = Engine.createTimerObject(); timer.setTimerCallback(function() { var pitch = buffer.detectPitch(44100.0); if (pitch > 0.0) Console.print(pitch); }); timer.startTimer(20);
HiseSnippet 1260.3oc2W02aaSDF2toFQxXHFZBweZUwejhZihyxZJff01zjsJ5KgjtMPHzzE6mjbzy24d9b2hl52Q9nruAvycNY1llUJQPEiH01bOuc+tmW9cW6IE9PbrPZYW4zoQfk8G4LXJWMo8DBkacv9V120QkvA4lJHV0vZuoQj3XHvx1tzi0lXWdUKym27n8HLB2GxDYY8LA0GNjFRUYR6sy2SYrtj.3TZXNqatyA9BdaASjfvojScqHh+YjwvwDsYq3X8DR7DK6uzwOXXiGDzzuQS3qd.Lp41s7ZzZjWiG1pYCxVMah5fVaQZXY+AcBnJgbfhfv2xd08DASGLQ7Rd5F7LZLcHCzK7rFf6bp3tBVf9HpkZ0dBkEzaddJ1xx1oWVVqTZV69NGQCnuUdV16SLJby7HeBzdkhvqTA34kGd0yAuE.I6bPZ0THcOmA9RZjJSiFO2w4.tBjiHXcJOTRs0ZkMK4zVfVvU0BImAck3h25Q0spWeCW7Wq+MUpf0pXk6EDoajTDE69stuthq6Z6kLZDHOD3iUSV6qcaTu41ank+bJOP7RMzPoqsGCKsgDt6SHRIMdMiI6C9zg.qOgOVazOixbc2zqd8Z02v7c7K3e+EiwOMF5AjyPeHSQiGQXwvbEyBz.eBSGHkLIU0O8XRXHAk3U6gy2Qi20q0ZtuGJFSjT0jPpeWIb9tuBQWZHpbYgScPTc7LaFVpMFT6SiiXjooG+AXKLltVqGQh8tJI8b2N+f2ZXZKu+n6XPthyU0o2fnZwfpOkONUHVEi.ohBwUMo6rJvPidSrzgpOPBlEmLaT3fl1jN7wTNTyWBXCmd5SdxveE7UFSMFo2Uih1DFaHVjpNJg6qnBd00q75JkM0apxeBFszctV.nvPzSKrZyllxEFNW2x5enibqlZ+2oKeqqkUF6vhELnVjjxUopQOtLGHTDYJLp1.C1bH3J3GKTvIyvRkKq39mUMZzB0o6okBFSmUVf5zs5ZbrJOIbHH2.qbrD3sFhCaEmfcd2Sv4IX7SGwxYnfe.mpNIB3uKZGqYyk32d5A6STD8X+LYYcGnr8gKPR2TRfxN6CwmoDQHs6UXHPtIQPBinJRXoo0mo.yAEXIzLA7XpZZdZ++wXwtoP7dNllsEiwUV.FwL0+FXbF2+cc5fyA9pL.tpS2e7Vfn2YdMtch7Bny4ypeEIcJfiiSB6RYHattU0VmX518zNbBt2lbmcoaLN8t9zTeQhBIuNhf33U3lg6bJoHlk3bfY.vJ5t5z00mCnA.Ovr32wOyT5oWaOSo2bk4KEebZp3NNCP9MCmrIY7Yl0tOmbA39X.eEitQx6ZdDyucSeDSzM9QLm3qvs+TIgGGIhKD3APH8TAWmoyDh2Aou3outyNu71Bhbgpdyi5hGxE5y.hJQZFQ1MTjvUEFIJccuMxaIeazp+m6sQuGP7UXpt7bLNfFFwfN7K.Fxtav3mhz4iHIL0boE6kORvEQSDbpe9BceMWv3wfLO1W3AZWkBuwOSx82oOv.R9l1uXmCwlMhDySvRlK79a+T1EVu9bmT35pG.ce+8BqR+u9BqakKBtM1iPhuT7B+zmCp6S+PiD7byM+6okcNRu10yx7Dw70xPjk6E99EC0Ubrwx53CVVGatrN9vk0wsVVGasrNt8esi5Kg1MQIBSGMsrNpWGy6qssydZTIq+.fsjp6Q
-
@iamlamprey I don't remember if I found a solution
-
2048 samples might be too few as this might be less than the minimal frequency that the algorithm can detect. Can you try 4096 or 8192 samples?
-
@d-healey @Christoph-Hart I got it working with this snippet from Christoph as a starting point, and help from Claude for the HPS:
// setting up vars const NOTES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; var referenceTuning = 440; // will be a slider at some point var tuningMode = 'cents'; // 'cents' or 'hz' const MIN_FREQ = 30.0; // Minimum frequency to consider, we go LOW TUNE const MAX_FREQ = 2000.0; // Maximum frequency for fundamental detection const HARMONIC_THRESHOLD = 0.3; // Minimum relative amplitude for harmonic detection inline function analyzePitch(freq) { if (!freq || freq < 0) { lblTuner.set("text", "TUNE"); // reset the panel here return; } local midiNote = 69 + 12 * Math.log(freq / referenceTuning) / Math.log(2); local closestNote = Math.round(midiNote); local distanceCents = (midiNote - closestNote) * 100; // If too far off, switch to adjacent note if (Math.abs(distanceCents) > 50) { closestNote += distanceCents > 0 ? 1 : -1; distanceCents = (midiNote - closestNote) * 100; } // Get note name local noteIndex = ((closestNote % 12) + 12) % 12; local noteName = NOTES[noteIndex]; // Calculate target frequency local targetFreq = referenceTuning * Math.pow(2, (closestNote - 69) / 12); // Calculate deviation in Hz if needed local distanceHz = freq - targetFreq; local note = noteName; local deviation = tuningMode == "cents" ? Math.round(distanceCents) : Math.round(distanceHz); local unit = tuningMode == "cents" ? "C" : "Hz"; local inTune = Math.abs(distanceCents) < 5; // use cents either way since it's just a bool // Debug /* Console.print("---------------"); Console.print("Note: " + note); Console.print("Deviation: " + deviation); Console.print("Unit: " + unit); Console.print("Frequency: " + freq); Console.print("TargetFreq: " + targetFreq); Console.print("---------------"); */ local direction = deviation > 0 ? "+" : "-"; // need a panel to set paint routine here lblTuner.set("text", note + " " + direction + Math.round(Math.abs(deviation)) + unit); return [note, deviation, unit, freq, targetFreq]; } // Setup display buffer & Instantiate FFT const var dp0 = Synth.getDisplayBufferSource("tuner"); const var dp = dp0.getDisplayBuffer(0); const var FFT_SIZE = 16384; const var MAX_LENGTH = 65536; const var fft = Engine.createFFT(); fft.setWindowType(fft.BlackmanHarris); fft.setOverlap(0.0); var pitch = []; // make sure the ring buffer is as big as possible dp.setRingBufferProperties({ "BufferLength": MAX_LENGTH, "NumChannels": 2 }); // Convert binIndex to its center frequency inline function binIndexToFreq(idx) { return (idx / FFT_SIZE) * Engine.getSampleRate(); } // Harmonic Product Spectrum // Multiplies downsampled versions of the spectrum to enhance fundamental / avoid stupid overtones inline function findPitchHPS(magBuffer, numHarmonics) { if (!numHarmonics) numHarmonics = 4; local spectrum = magBuffer[0]; local spectrumLength = spectrum.length; local hpsSpectrum = Buffer.create(Math.floor(spectrumLength / numHarmonics)); // Initialize HPS spectrum with original spectrum for (i = 0; i < hpsSpectrum.length; i++) { hpsSpectrum[i] = spectrum[i]; } for (h = 2; h <= numHarmonics; h++) { local maxIndex = Math.min(Math.floor(spectrumLength / h), hpsSpectrum.length); for (i = 0; i < maxIndex; i++) { local harmonicIndex = i * h; if (harmonicIndex < spectrumLength) hpsSpectrum[i] *= spectrum[harmonicIndex]; } } // Find peak in valid frequency range local maxIdx = 0; local maxVal = 0; local minBin = Math.floor((MIN_FREQ / Engine.getSampleRate()) * FFT_SIZE); local maxBin = Math.floor((MAX_FREQ / Engine.getSampleRate()) * FFT_SIZE); for (i = minBin; i < Math.min(maxBin, hpsSpectrum.length); i++) { if (hpsSpectrum[i] > maxVal) { maxVal = hpsSpectrum[i]; maxIdx = i; } } // grab index return binIndexToFreq(maxIdx); } // Executed per slice of the fft // the false bool is {normalized | decibels} fft.setMagnitudeFunction(function(magBuffer, offset) { // pass the bins through the HPS var f = findPitchHPS(magBuffer, 4); pitch = analyzePitch(f); }, false); // prepare FFT & setup record buffer to pass to it fft.prepare(FFT_SIZE, 2); const recordBuffer = [Buffer.create(MAX_LENGTH), Buffer.create(MAX_LENGTH)]; inline function analyse() { // Copy the buffer using the thread safe copy method // might need this in the HPS function as well... dp.copyReadBuffer(recordBuffer); // and process fft.process(recordBuffer); } // timer object will run our HPS on the signal, and update our Label text in realtime const tunerTimer = Engine.createTimerObject(); tunerTimer.setTimerCallback(function() { analyse(); }); // definitely dont need this to be too fast tunerTimer.startTimer(100);
Works pretty good, not sure if the manual buffer copying in the HPS is ideal but CPU is < 1%, still working on the accuracy for it, super low notes are a bit off still
-
@iamlamprey Combining the snippet and this code seems complex.
Can you share a snippet? -
@resonant here you go:
HiseSnippet 3990.3oc6as0aabbEdojWmXVm1j1ffh9zTl1ZJaYIRJYI6PqXYIQZIDcKhz1A0v0cH2gjSzxc1t6PIQmHfBz9Pdq8w9V+qje.o.AHuWfh9dg6u.2yYl8J0JYIk3f.TQCPs6Lm4b9lys4LyXtsmnMy2W3YjKeygtLibW0rwPGYuk6Q4NFqshQt2xTNvg4cSIyWdKikF5R88YVF4xM9CPRxckKYn97h6sD0l5zlE2jgwiD71r0484x3V2dwOhaaWmZwZx6mf5YWbs1BmkE1hA.bF2rjgKs8tztrMoHYiYZrJ0umQtqa11pUkYrlsckYY2YFVmYu87kqLemxUt07yVgN2ryB8wleNZEibWtlEWJ7ZHo.7MxcokDVCazSruiV.Oh6yaYyvWJaz.jrt45BaKbJhsZrbOts01g5IeCiblaGq0FWq0dWyM3V7n1i0duspCR7HRp.yMVZ3MdJ3U93fWFPJWBHcIMjdGyFs83tx3dP77iLWyQx75PA6TRnno0Xr+y6Ztr.nvQNUe5tr5dvKQin3rkJMIo7bklnZ97SOMwmIkbmtjAtj8nd94AymujX2xtI5wPVfDxptL4xh9tBG3khEBIn.vF8P1bql0Z.z+jBKWXRRgkee76UTeodrF9Uc0Wp2ef5K0i2W8k5wkJ7zp4AfP7XcXdLvUDDChuEHyNaopD.w6CddjVLBk3ays.LRkDeQeFwUvcjD0nkpAsgvhAC7ZsAH6eM0fCdlH7HWq2yuV.12XsMeV8cp8w.wUJMEHFBR6FbGd+A8Ic7X+gA.TFRjBBN.TpSR1mQ5JHqu0iIMe3l0B4z8+jXNURwKjSzCFgSc..zYfiEDV3Ho1DKlj0VxENA7Y06uyFas4ZK+rlqtSsFqt05q.brzTyTMIv7X1TIeOPUz20lKG.yVju8nd8EN71IXZdtiM2gghT0.g5PsG9b11bY6dEQXMQ9OKOLsI7Njh+RrAxm+4J7RtKozDptzDfeBs9SAdOEKHYGHQaGpF.+ADgdLnGhrGXTnNLaROvVFMZOlbfmSU06GlW8GaQaPIzGBy1THQa1b2gbCR4JjqC5NYuorEcUnjL8nNFSPlNljJnScL+ZaK7gLdArTQkm.T5ECEzDUSPsE2Wh49VF8P.5inhbyjbZB.SkKUJPPvbcsNfegfzAb6Dc5LIweeToh9JTqOkh9aDGXbQZWENns7KlRfSP9PxsJMAYDMcxovMVXDL9gjRj6QJS9.xMKWMZHm04QBy.Lad.SCWhC3ZlP6fssliE6.jiESBqeMXmlPYslP8b0QFEl5GFjJ+vShXySiUfKSsaO.7jYDI0CRyDGljfS5tpi9.KbjjCAdIth8KVYRRJzcSvUB8QJG4ajRhVr83TUDA2gr5yQCjCiAqbjgiAz8B5Phal.No73bztZgS6TtWQRZgTomVfTPkSp.XJS3hNhywGjUeq97TNv.Skm.yg7x.aJr5yKjbPbGLRNL7HC2x6RtkJjdfOin3Egwg.aOx9zgDeNPHgKulO4SG.osnjVBgsh8g55UXsFzM3sqq9CrhhuvlMkqGGWJ4lo+THXNMBUnwDPO3m4DG3NBMqDph0DFowyl5GBpKMgnhKaZpG5IpITknLSBaF4OnoL1+Ha5OlI80mNeJGOOc9av7D69ni5KbCk07lEh8pQGWvBny3BoevTvtTbMQvsQho+iRCmYBbky6M.3qTdQx9FIc8h8RhTtSDqAyGmdmnB0mLF2SpHZRkNbxD5GHQvgpJQZ.iyEC1bsAOqVC5.w3jeCYMGzcTxwn050aFr9nkaIPonJyEqLYE8nVRMnFPwmPgNEjoKPwxEUitkNB8EKEQCHfm0XseaMbEnacqYlKxy2uO01FvCjeXnX.wR3bMoVg6OvEZ2VruZlknLf0qs4CZtZDmB5oSGLFslSWvdLUaOFLs.gVDPvzSC8gFjGycrD6iU.VDaYIanD59TmUoddbefvrIaUpiSbmasGyyl5VDp.YBcAUtpUkf5ydptvOrzP.7dL0xzdXZz.cN2mP8Is3cw+3J7UEvl2xE46N.cZsFTSJLwkbleQb4pB5VWm4zU1qvGjPCLI16lC5CET6.dl9PmUxeXP4mPXAfTIHMG8pKfeKGxwfYZ.nDuRvnEvDNflBzIpH25fvJXB7+vlf79gVTb4t.sNX+afULw1AT9nlW6+sZXYSvLyZPaIogKDA3MnO14FCrkbnHKlOX622wWMdKBfce.M9vJ+J0nevPvoAyoGlFMUYdSSn6I3fSC3pC+QfycnhZ+iL85.FWUwYqtcih8oA5bHFcP+Pb5mpjsTcjhLvle6pwoj0oVh.5BjHt+jROsZFjnMo.ggMLkspkjz1y0uQLG0rKv6VmxnisP3UbDVNc5oyDUIY7I0hIqAoP3Ta9yYDPwDOKfRt5A0zyA6aBjqFEVQbQNV8bUBGVMKATCmHD9Mtwnk3lfrmveZhYO7VXMSQHSICTEUoJoG4tKjZdAMcT1GTvK8fvZpTZo9bmSTc0ahIy.+SDW62nS1PAjXJlFGILgA3MDPbHfoW0TDhNZoI6ti3kLQ9QMeinGudBEYJV8zXQc3npWvvWGhGHtL5tXcZ6Ad.VI1LkG0oaxZUwYs0AJsP5VeD7GktIcv.n1Wh6DZDz59hQ6Ib5iIuAlQIJ6xHBJC1Etwvy.6R6+pQo1tF4snkU1dEY4VqLgoMIeXfl43bOhzaoGW0QIRqx4mrcrqGsEXBACdxb0ijLWyrnDy0Nf0dfjg1eObO+PN0fzsvhcHApGo1vxzX0m35WelivquJOgE4ygZPZyaAq7bX3piaP65n1ub8f7sECS7lLWKraNf1vrrfbviZQIL.u3CPIQc6oZ.RFoHBWnsCtKgiI88rAV1vEiSuMbvjk+vI0SEb8Qbt45wbodpRefhg7U0HAUlI7rBWtFVpQCLbkS0LLXLECcmljTIpBG8X03AqFXjb0QqZCYZN1tvJHx7.E7A+3X00xB2gZ0kVZC7whLvF.UGiBqBR6.6o.opOS1SXENv97t8BptR1CrlPvTfRNg77I6yrsmZpoTiBpNAYzN.aCJpK4DMwV+nXdD8wooCuT5K06iNjC0V.IuO.dQqOEb80G9j2.GBTioBPBM174cAEvjJ1Ov0BKVEoXcZKrVbn9ZbR.SZajaAlBU8oMUbejJBUMtkRhX8I4ioD8dUO.ag0tETXXrmanlOxRTMpJKKF3PxkL6gXkqIUsfSSKVv4W3KSIHnDcsnJVoDTDYjlW3f6EaKs.uBnkHi1UmNY1GdRhdBrL5L6VKqSXfEgUUagQQP9+ArHBMxcozGA6kOcGAaa8AalfPgCVewVtrf2O4yP1H3jQgmd3ZqPkT7LbCZKt3XnMbaosY5Sz8Jlqv72UJbMxMVzwoZjyTp58sBNuWkaiAGDcdyvspYbPzwr+GoKNL4Yti9Wvz1DO.NDVQr8pxfS1Vy151BJdXuM4f1.49OwLYSkSHB3SBQzbw84Vxd.Ny8WyYXzigAn3a0iUC54WdyOhMrkf5YYzpa38.bkKaYY8RjQfOX+3Ve4Ke4uHcqUBZVQLNshINDVJM8X+tblpcc.R6wHzfMUTd1oJo1qQvl61psjtGaSkOCzsJqpp+0E6CCCZZl4TuuJW+Z4Jyqde4A9RQ+G3Qc6AkukZnqv5PgcBbeWnTDOr5dnWX8PcmpMpArZGb+tPGklZt6n9b6aWZ96TZ9xJpZJ510lgGOSJNiWwPvNjPnnZ6g9rGAo.DdIvRjzfNqaSkMjCsGgSaWKzFLZ6MvX5XoTIr8ZNVIj8b4Ozv3nWFwaaBfFO0rz2MBdCRAc.QqotPB7RGb74xgIugoyvElT5DuvjSKDeGS0xqYiwwx.iPb7qCLFbMSukYMX8k1xX.dIy5ex48NkJc5uSIyvHzfjAehR3ug9pASIeHlI0Aj3ig5PbuQvMLk6KLiSOGTrQSw1.8E8ipncRRKnb3ca.0fczb5Aq3tDRQw1AGLv4I0+k+N512L1QcDYcgBq83PZPSPGnOGovis.0Aigo6zuWBeG8UZvbrTufosB5rbjBC5rbXmIVvXSlbeg2tJSTvyF4dyj1i6aaK1GSkyCbZA6hpssE1Cc6g6aBaRSQHHueew.0xIJjF6wuTnkHDzqR8aR41X.PiAvFxbr1xoAvG0U+la7GoOVCDMklB9GDPrIjvBT20oX9ngaSwUCdGSbMTvMFqRLxcNSOJ00+F1CxqvD6Z7.Si9pUrFyDrPIV8T6BiuAAnigr+plq4+HrWXqVFOBcGvYghmZ0oFnZj3i2KlwwBZc2YATcOmEfFgfwRg.sd4Un1xBA5ddMppL1FVBCJ7l4EhdPr3vdSsrK+5B0X7YBYiI79VYxMSnvMM82qSZf+Vlc31njlB65nfVOjyjoFi5rYGDVzWcM+ieDwMte1vH+ZPxKKnTR0.ByFjT8m6xQuA5+M3NAyx3Z71PsK7Ds8EeyhMjLWcTc7RWM1ksud5Gz5h+o9+i6o+uMPzsYXzDJspoPwvfSfEEbRJRKrE+6KFT5yHs+WVz3DP9hyLy2buQP9W9Ue06e5Ptw6+e+ZExG27iOVD+PG0la.CZjP93+Mm83+1+7dYhXnBDiSVWW4KOht1nxoDwH6QDeYUYQmEPGUdalfNYzZlfFx1eDP+h6cp.8u+msx2D3fznuP.aKzo6YD4K9ud9eNK0sp8yAxe6LT2vr4DT2X8z+.TceRf9Jl0bnPwIVmcb+hi22NQtvKiI1pbQxvKRFdQxvKRFdQxv85T9hjgWjL7hjgWjL7+ySF9iME9s411v2PtqzIEeOyfKPZpTzbzriivjuMoIG4zFGskjIMQFmNoIv8rNJivyewlQ8ROCupYe75y08j4gXn54ULgFK0DJUJ6bu1hHibQdCSMCds3X6ehurjPraep5vKOWW7UxyD+Gq6Dxu.XU8+lO0QH9dp2IOltGi7.lCyCOQ+xmvObku9z9CWw8T+CWQe8QM8nN9tB+zlEVedS7+1VIaDuOFXMR0k+jr8kETuL65E2qNLIybLMnxAdpStM7DcSb2DieR+dXN1i7N80zjf9Lt0fK855GGykNcWjwq3dW+g+USk5dWtRHFavwjd0fnTaLgIhwepYPzXXqocx2P3HBOq+XOfcXROd2trzIExZBceoj1d23Vd2E2gA41R5M+qVbcvKj5oVl77oKN6+tlxzd8yM0vkfQljuM1suWcyNwqWb7S00K9cNdO+W032MP46iKS66CYzm11S7r156fD8keSUKv71QUpvUL2.emT1XuQWME+Q27r1sSypiLvJm2ANy4cfyddG3sNuCbty6.m+7Nva+pGHtn08GHE80grPIYaWSWNYtvZugnCi+GTk9oS
This is specifically for low guitar tunings (46hz) so it probably won't work well for the Sine Synth
-
@iamlamprey said in RE: Buffer.detectPitch():
This is specifically for low guitar tunings
This might help me a lot, when I'm tuning instruments like harps some of those low strings are too low for my tuner plugin.
-
@d-healey I just tested it with my guitar in Ableton and got it to double drop C (around 30hz), it's not perfect but my guitar is also not setup properly for those tunings so the pitch wobbles (the Ableton tuner has a very similar value, it just updates way faster so it feels smoother)
-
@iamlamprey This will be very helpful, thank you.
I tried it with guitar samples and it works well.