# Vinyl Scratch Tutorial

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)))))

## Other Sounds Using Ring

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.

## Another Related Sound

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)))