;; code_4.sal -- Frequency Modulation ;; vibrato play fmosc(e5, 20 * lfo(6), *saw-table*) ;; vibrato depth changes with pitch: play fmosc(e3, 20 * lfo(6), *saw-table*) ;; making vibrato a percentage of the fundamental frequency: define function vibtone(pitch, vibamp) return fmosc(pitch, step-to-hz(pitch) * vibamp * 0.01 * lfo(6), *saw-table*) play vibtone(e5, 5) play vibtone(e3, 5) ;; changing the rate of vibrato: nested FM ;; NOTE: LFO does not allow a frequency control, so use HZOSC ;; but HZOSC computes at the sample rate, so use SOUND-SRATE-ABS ;; to change the rate define function fm-lfo(amp, freq) return sound-srate-abs(*default-control-srate*, amp * hzosc(freq)) play fmosc(g5, fm-lfo(20, pwlv(1, 1, 10)), *saw-table*) ~ 2 ;; ================ ALTERNATIVES TO FM SYNTHESIS =============== ;; ;; (1) Addition of individual sinusoids ;; Example: sum 20 sinusoids where the higher frequencies decay ;; faster than lower frequencies play simrep(i, 20, hzosc(step-to-hz(c3) * (i + 1)) * pwev(1, 1 - i / 30.0, 0.002)) ~ 6 ;; (2) Use a time-varying filter to modify a fixed spectrum ;; Example: buzz output a fixed spectrum of n harmonics. Use ;; a low-pass filter with some resonance to shape the spectrum. function cutoff() return pwlv(5000, 1, 10) function envelope() return env(0.01, 0.2, 0.8, 1, 0.9, 0.8) play (reson(buzz(20, c3, const(0)), cutoff(), 1000, 1) * ;; cutoff, bandwidth, normalization envelope()) ~ 6 ;; ;; ============================================================== ;; just for fun, let's use a high vibrato rate on a low tone play fmosc(c4, fm-lfo(50, pwlv(3, 1, 100))) ~ 4 ;; FM Synthesis ;; first, increase depth of vibrato at a slow (normal) rate: play fmosc(g5, fm-lfo(pwl(1, 500), 6)) ~ 2 ;; now, try even more at a higher vibrato rate: play fmosc(g5, fm-lfo(pwl(1, 1000, 1), 500)) ~ 2 ;; typically, you use integer-related modulation at a ;; fixed frequency; notice richness increases with DEPTH ;; of modulation play fmosc(c4, pwl(1, 4000, 1) * osc(c4)) ~ 10 ;; If the Modulator frequency is twice the Carrier frequency ;; we get odd harmonics: play fmosc(c4, pwl(1, 4000, 1) * osc(c5)) ~ 10 ;; Small integer C:M ratios give rise to harmonic spectra. ;; Try 5:3 set modstep = hz-to-step((3.0 / 5.0) * step-to-hz(c4)) play fmosc(c4, pwl(1, 4000, 1) * osc(modstep)) ~ 10 ;; What's the (theoretical) fundamental? set fundstep = hz-to-step(step-to-hz(c4) / 5.0) play pluck(fundstep) ~ 3 ;; a non-integer ratio gives non-harmonic partials play fmosc(c4, pwl(1, 1000, 1) * osc(d4)) ~ 3 play fmosc(c4, pwl(1, 3000, 1) * osc(e2 + 0.5)) ~ 6 ; multiple oscillators can be used play (fmosc(c4, pwl(1, 1000, 1) * osc(e2 + 0.5)) + fmosc(fs4, pwl(1, 2000, 1) * osc(e2 + 0.5))) ~ 6 ;; Formants are simulated using a carrier near the resonance ;; and FM to create nearby harmonics. play fmosc(c5, 500 * osc(c3)) ~ 3 ;; Formant 1 for "ah" is 700, Formant 2 is 1150Hz. ;; Fundamental = C3 (130.8HZ), set c3hz = step-to-hz(c3) ;; Formant 1 is about 5th harmonic (700 / 131) set carrier1 = c3hz * 5 ;; Formant 2 is about 9th harmonic (1150 / 131) set carrier2 = c3hz * 9 ;; The following uses one FM oscillator for the fundamental ;; and a few low harmonics, one FM oscillator for the first ;; formant, and one FM oscillator for the second formant play ( 0.2 * fmosc(c3, 50 * osc(c3)) + 0.4 * fmosc(hz-to-step(carrier1), 100 * hzosc(c3hz)) + 0.5 * fmosc(hz-to-step(carrier2), 80 * hzosc(c3hz)) ) ~ 4 ;; The following is a wavetable-based oscillator that creates ;; a similar spectrum. set ahtab = build-harmonic(1, 1024) * 0.4 + build-harmonic(2, 1024) * 0.28 + build-harmonic(3, 1024) * 0.13 + build-harmonic(4, 1024) * 0.16 + build-harmonic(5, 1024) * 0.45 + build-harmonic(6, 1024) * 0.18 + build-harmonic(7, 1024) * 0.11 + build-harmonic(8, 1024) * 0.28 + build-harmonic(9, 1024) * 0.72 + build-harmonic(10, 1024) * 0.22 + build-harmonic(11, 1024) * 0.03 + build-harmonic(12, 1024) * 0.02 play osc(c3, 4, list(ahtab, hz-to-step(1.0), t)) ;; Why would you use 3 FM oscillators when one table-lookup ;; oscillator can do the same thing? ;; One answer is that with FM, you have parameters you can ;; vary in time. Here is the same "ah" sound with three ;; FM oscillators, only each oscillator has a randomly varying ;; modulation index and amplitude. ; ;; Compute a slowly varying random signal by making 15 samples ;; per second of random numbers, then interpolating the sample ;; rate up to *default-control-srate* function rand-ctrl() return force-srate(*default-control-srate*, sound-srate-abs(15, noise(1))) ;; ;; make an FM module with randomly varying amplitude and ;; depth of modulation. The modulation amount varies +/- 20% ;; The amplitude varies +/- 30% function fmrnd(pitch, mod-index, mod-freq) begin with mod-det = mod-index * step-to-hz(pitch), mod-rnd = mod-det * 0.2 * rand-ctrl(), mod-depth = mod-det + mod-rnd, fm-det = fmosc(pitch, mod-depth * hzosc(mod-freq)), amp-mod = rand-ctrl() * 0.3 + 1 return fm-det * amp-mod end ;; ;; Now, reimplement the "ah" sound with fmrnd ;; The mod-index parameter (2nd parameter) is ;; depth / carrier frequency ;; play ( 0.2 * fmrnd(c3, 0.4, c3hz) + 0.4 * fmrnd(hz-to-step(carrier1), 0.15, c3hz) + 0.5 * fmrnd(hz-to-step(carrier2), 0.07, c3hz) ) ~ 4 ;; the classic FM brass sound (refinements are of course possible) play fmosc(c5, pwl(0.05, 2000, 1, 1000, 1) * osc(c5)) ;; here is an fm instrument whose controls are amplitude, pitch, ;; C:M and depth of modulation as a ratio ;; ;; some details: ;; The C:M ratio is based on frequencies, so we convert ;; pitch p to Hz using step-to-hz. Since this yields the ;; carrier frequency, we divide by C:M to get the modulator ;; frequency, but there is no divide operation for signals ;; (signals that contain a zero would raise a divide-by-zero ;; error, so division is discouraged). Nevertheless, there ;; is a RECIP function for taking reciprocals, so we use ;; that to convert C:M ratio to M:C ratio and then multiply. ;; We could have simply used the divide function (/ ...), but ;; by using MULT and RECIP, we can pass in a time-varying ;; control (a SOUND) and the code will still work. define function fm(p, a, c-m, mod-ratio) return a * fmosc(p, mod-ratio * step-to-hz(p) * hzosc(step-to-hz(p) * recip(c-m))) play fm(gs2, pwl(0.3, 1, 0.7, 1, 1), 0.5, pwlv(0.5, 0.5, 10, 1, 2)) ~ 2