This sound reminds me of the effect of dragging a needle across a
vinyl record. The function is defined with SAL syntax, followed by
Lisp syntax in a smaller font size.
function ring(dur, pch, scl)
begin
with modstep1 = hz-to-step(step-to-hz(pch) * sqrt(2.0)), modstep2 = hz-to-step(step-to-hz(pch) * sqrt(11.0)) return env(0.05, 0.1, 0.2, 1, 0.6, 0.24, 1) * fmosc(pch, pwl(0.07, 1800, 0.15, 1000, 0.4, 680,
0.8, 240, 1, 100, 1) * scl * osc(modstep1) * osc(modstep2)) ~ dur end
(defun ring (dur pch scl) (let ((modstep1 (hz-to-step (* (step-to-hz pch) (sqrt 2.0)))) (modstep2 (hz-to-step (* (step-to-hz pch) (sqrt 11.0))))) (stretch dur (mult (env 0.05 0.1 0.2 1 0.6 0.24 1) (fmosc pch (mult (pwl 0.07 1800 0.15 1000 0.4 680 0.8 240 1 100 1) scl (osc modstep1) (osc modstep2) ))) )))
The following plays an example sound from this function:
play ring(7.1, hz-to-step(1), 1.2)
(play (ring 7.1 (hz-to-step 1) 1.2))
Here is a brief description of how this function works: The sound is created by an FM
oscillator. The modulation comes from two sinusoids operating at low frequencies
multiplied together. The sinusoids are not harmonically related, so an irregular pulse is
generated by their product. This is scaled further by a piece-wise linear envelope that
adds more variation. To make the sinusoids inharmonically related, their frequencies are
scaled by the square root of 2 and the square root of 11. The variables modstep1
and modstep2
are initialized to these computed frequencies.
The following example combines several instances of ring
with different
parameters:
play sim(0.15 * ring(7.1, hz-to-step(1), 1.2) @ 2.9, 0.175 * ring(5.1, hz-to-step(2), 1.414) @ 4.9, 0.2 * ring(3.1, hz-to-step(4), 1.8) @ 6.9) (play (sim (scale 0.15 (at 2.9 (ring 7.1 (hz-to-step 1) 1.2))) (scale 0.175 (at 4.9 (ring 5.1 (hz-to-step 2) 1.414))) (scale 0.2 (at 6.9 (ring 3.1 (hz-to-step 4) 1.8)))))
The same ring
function can be used to achieve other sounds. Listen to
these examples:
play sim(0.35 * ring(4, 1, 1) @ 1.5, 0.325 * ring(4, 4, 2) @ 4, 0.3 * ring(4, 10, 4) @ 7.5)
(play (sim (scale 0.35 (at 1.5 (ring 4 1 1))) (scale 0.325 (at 4 (ring 4 4 2))) (scale 0.3 (at 7.5 (ring 4 10 4))) ))
These instances use a higher pitch parameter than the previous ones.
The following creates a sound using FM and a wave table derived from a vocal sound.
function vocrap(pch: 16, dur: 1)
begin
if ! boundp(quote(*voc-table1*)) then exec mk-voc1-table() return fmosc(pch,
pwl(0, 3, 0.1, -20, 0.2, 20, 0.3, 30, 0.4, -10,
0.5, 15, 0.6, 0, 0.8, -30, 1, 60, 1),
*voc-table1*) ~ dur
end
(defun vocrap (&optional (pch 16) (dur 1)) (if (not (boundp '*voc-table1*)) (mk-voc1-table)) (fmosc pch (stretch dur (pwl 0 3 0.1 -20 0.2 20 0.3 30 0.4 -10 0.5 15 0.6 0 0.8 -30 1 60 1)) *voc-table1*))
This function uses a special test to make sure that *voc-table1*
is
initialized. If it is not, it would be an error to read or test it, but you can query to
find out if the variable is bound or unbound. (Global variables become bound when you
assign a value to them.) The boundp
function takes an atom (note the use of
the quote (or in Lisp, the single quote prefix character) to denote a symbol, the name of the variable, rather then the variable's
value) and returns true if the variable is bound.
Here is the definition for mk-voc1-table
. You might have to replace the
filename depending upon how your system is configured.:
function mk-voc1-table()
begin
if ! boundp(quote(voc-snd1)) then set voc-snd1 = s-read("./test/voc1.snd")
set *voc-table1* = list(voc-snd1, 16, T)
(defun mk-voc1-table () (if (not (boundp 'voc-snd1)) (setf voc-snd1 (s-read "./test/voc1.snd"))) (setf *voc-table1* (list voc-snd1 16 T)))
The following shows one way to invoke vocrap
:
play seqrep(i, 4, vocrap())
(play (seqrep (i 4) (vocrap)))