Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I generate this tick sound in JavaScript?

On a.bestmetronome.com, they have a metronome that generates ticks based on noise.

However, I couldn't figure out how the "ticks" are generated. Although I was able to work out the "tick" generator out of Audacity:

;; Metronome tick by Steve Daulton.
(defun metronome-tick (hz peak)
    (let* ((ln 300)
        (sig-array (make-array ln))
        (x 1))
    ;; generate some 'predictable' white noise
    (dotimes (i ln)
        (setf x (rem (* 479 x) 997))
        (setf (aref sig-array i) (- (/ x 500.0) 1)))
        (setf sig (sim (s-rest-abs 0.2)
            (snd-from-array 0 44100 sig-array)))
    (setf sig
        (mult (abs-env (pwev 10 (/ ln 44100.0) 2 1 0))
            (highpass8  (lowpass2 sig (* 2 hz) 6)
                        hz)))
    (let ((gain (/ (peak sig 300))))
    ; The '1.11' factor makes up for gain reduction in 'resample'
    (mult (abs-env (pwlv 1.11 0.02 1.11 0.05 0 ))
        (jcrev (mult peak gain sig) 0.01 0.1)))))

;; Single tick generator:
(defun get-metronome-tick (hz gain)
    (resample
        (sound-srate-abs 44100 (metronome-tick hz gain))
        *sound-srate*))

And here is the tick I currently have, inside the function that generates it:

function scheduleNote(beatNumber, time)
{
    // push the note on the queue, even if we're not playing.
    notesInQueue.push({
        note: beatNumber, 
        time: time
    });

    if (beatNumber % 4 === 0)          // beat 0 == high pitche
    {
        var fader = actx.createGain();
        fader.gain.value = 2;
        fader.connect(distor);

        var oscil0 = actx.createOscillator();
        oscil0.frequency.value = noteFreq[0];
        oscil0.connect(fader);

        var oscil1 = actx.createOscillator();
        oscil1.frequency.value = noteFreq[0] * 2;
        oscil1.connect(fader);

        oscil0.start(time);
        oscil1.start(time);
        oscil0.frequency.exponentialRampToValueAtTime(noteFreq[1], time + noteLength);
        oscil1.frequency.exponentialRampToValueAtTime(noteFreq[1] * 2, time + noteLength);
        fader.gain.linearRampToValueAtTime(0, time + noteLength);
        oscil0.stop(time + noteLength);
        oscil1.stop(time + noteLength);
    }
}

However, the script to generate the ticks themselves is in the Nyquist language, which is based on Lisp. How can I write something in JavaScript that does what the Lisp/Nyquist generator does, but using AudioContext and Web Audio functions?

I tried numerous translation tools online, none seemed to get where I needed. Also, how can I make sure that the empty buffer that I start with (Which is filled with the noise sample generated by the code above) starts exactly according to the time variable in the JS?

The "highpass8" could be done with createBiquadFilter() and the noise could be created starting with random numbers. I've worked with the Web Audio API before, and made a noise and tone generator out of it. However, on this task I'm stuck.


Alternatively, I tried looking in a.bestmetronome.com's source but apparently they use Flash to generate their sounds, and I couldn't find the ActiveX Object that actually generated the ticks. How could I replicate the way they generated the ticks? (Also using the Web Audio API)

like image 714
El Ectric Avatar asked Nov 07 '22 16:11

El Ectric


1 Answers

The Nyquist code as written in the question will not run because the s-rest-abs function has not been defined. This version may be run in Audacity's "Nyquist Prompt" effect (see: https://manual.audacityteam.org/man/nyquist_prompt.html)

;version 4
;type generate

;; Metronome tick by Steve Daulton.
(defun metronome-tick (hz peak)
    (let* ((ln 300)
        (sig-array (make-array ln))
        (x 1))
    ;; generate some 'predictable' white noise
(dotimes (i ln)
    (setf x (rem (* 479 x) 997))
    (setf (aref sig-array i) (- (/ x 500.0) 1)))
        (setf sig (sim (abs-env (s-rest 0.2))
            (snd-from-array 0 44100 sig-array)))
    (setf sig
        (mult (abs-env (pwev 10 (/ ln 44100.0) 2 1 0))
            (highpass8  (lowpass2 sig (* 2 hz) 6)
                        hz)))
    (let ((gain (/ (peak sig 300))))
    ; The '1.11' factor makes up for gain reduction in 'resample'
    (mult (abs-env (pwlv 1.11 0.02 1.11 0.05 0 ))
        (jcrev (mult peak gain sig) 0.01 0.1)))))

;; Single tick generator:
(defun get-metronome-tick (hz gain)
    (resample
        (sound-srate-abs 44100 (metronome-tick hz gain))
        *sound-srate*))

(get-metronome-tick 440 0.8)

The final line calls the function "get-metronome-tick".

(As the author of the Audacity Rhythm Track generator, I'd be happy to provide more details about what exactly this code is doing, though I don't think that is necessary in the context of the question.)

As you can see from running this code in Audacity's Nyquist Prompt, it generates a sequence of 2200 sample values (50ms at 44100 Hz sample rate). It was written this way so that the entire plug-in could be distributed in a single small text file.

I'm not a JS expert, but I'd expect that in JS / HTML5 it would be easier to simply create a small audio file for the sample data. As very high frequencies are not required, this could be a very small low sample rate file (say 550 samples at a sample rate of 11025 Hz) or in a compressed format such as OGG or MP3. The file could be created in Audacity by generating the tick with the above code and then exporting.

The sound can then be loaded with something like:

<audio src="tickSound.ogg" type="audio/ogg"></audio>

See also: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API

and

https://www.w3.org/TR/webaudio/

like image 55
Steve Daulton Avatar answered Nov 15 '22 11:11

Steve Daulton