# clm-ins.rb -- CLM instruments translated to Snd/Ruby

# Translator: Michael Scholz <mi-scholz@users.sourceforge.net>
# Created: 03/09/16 01:27:09
# Changed: 14/11/28 02:16:54

# Instruments work with
#   with_sound (CLM (sample2file gens) and Snd)
#   with_dac   (dac output, except at least for fullmix)
#
# Tested with Snd 15.x and Ruby 2.x.x

# pluck                  reson
# vox                    cellon
# fofins                 jl_reverb
# fm_trumpet             gran_synth
# pqw_vox                touch_tone
# stereo_flute           spectra
# fm_bell                two_tab
# fm_insect              lbj_piano
# fm_drum                resflt
# gong                   scratch
# attract                pins
# pqw                    zc
# tubebell               zn
# wurley                 za
# rhodey                 clm_expsrc     exp_snd
# hammondoid             expfil
# metal                  graph_eq
# drone                  anoi
# canter                 fullmix
# nrev                   grani
#
#  class Ssb_fm < Musgen
#   initialize(freq)
#   inspect
#   to_s
#   run_func(val1, val2)
#   ssb_fm(modsig)

# bes_fm(start, dur, freq, amp, ratio, index)
#
# make_ssb_fm(freq)
# ssb_fm?(obj)
# ssb_fm(gen, modsig)
#
# class Fm2 < Musgen
#  initialize(f1, f2, f3, f4, p1, p2, p3, p4)
#  inspect
#  to_s
#  run_func(val1, val2)
#  fm2(index)
#
# make_fm2(f1, f2, f3, f4, p1, p2, p3, p4)
# fm2?(obj)
# fm2(gen, index)

# comments from clm-ins.scm

$now = 0.0

require "ws"
require "spectr"
require "env"
include Math
with_silence do
  require "matrix"
end

def normalize_partials(partials)
  sum = 0.0
  parts = partials.dup
  len = parts.length
  1.step(len - 1, 2) do |i|
    sum += parts[i].abs
  end
  1.step(len - 1, 2) do |i|
    parts[i] /= sum
  end
  parts
end unless defined? normalize_partials

# violin is defined as an example in ws.rb
def violin_test(start = 0.0, dur = 1.0)
  violin(start, dur, 440, 0.5)
  $now = start + dur + 0.2
end

require "v"

# fm_violin is defined in v.rb
def fm_violin_test(start = 0.0, dur = 1.0)
  fm_violin(start, dur, 440, 0.5)
  $now = start + dur + 0.2
end

# PLUCK
#
# The Karplus-Strong algorithm as extended by David Jaffe and Julius
#  Smith -- see Jaffe and Smith, "Extensions of the Karplus-Strong
#  Plucked-String Algorithm" CMJ vol 7 no 2 Summer 1983, reprinted in
#  "The Music Machine".  translated from CLM's pluck.ins
add_help(:pluck,
         "pluck(start, dur, freq, amp, weighting, lossfact)  \
Implements the Jaffe-Smith plucked string physical model.
WEIGHTING is the ratio of the once-delayed to the twice-delayed samples.  \
It defaults to 0.5=shortest decay.
Anything other than 0.5 = longer decay.  \
Must be between 0 and less than 1.0.
LOSSFACT can be used to shorten decays.  \
Most useful values are between 0.8 and 1.0. pluck(0, 1, 330, 0.3, 0.95, 0.95)")
def pluck(start, dur, freq, amp, weighting = 0.5, lossfact = 0.9)
  get_optimum_c = lambda do |s, o, p|
    pa = (1.0 / o) * atan2(s * sin(o), (1.0 - s) + s * cos(o))
    tmp_int = (p - pa).floor
    pc = p - pa - tmp_int
    until pc >= 0.1
      tmp_int -= 1
      pc += 1.0
    end
    [tmp_int, (sin(o) - sin(o * pc)) / sin(o + o * pc)]
  end
  tune_it = lambda do |f, s1|
    p = @srate / f
    s = s1.zero? ? 0.5 : s1
    o = hz2radians(f)
    t1, c1 = get_optimum_c.call(s, o, p)
    t2, c2 = get_optimum_c.call(1.0 - s, o, p)
    if s != 0.5 and c1.abs < c2.abs
      [1.0 - s, c1, t1]
    else
      [s, c2, t2]
    end
  end
  wt0, c, dlen = tune_it.call(freq, weighting)
  lf = lossfact.zero? ? 1.0 : [1.0, lossfact].min
  wt = wt0.zero? ? 0.5 : [1.0, wt0].min
  tab = make_vct(dlen)
  # get initial waveform in "tab" -- here we can introduce 0's to
  # simulate different pick positions, and so on -- see the CMJ
  # article for numerous extensions.  The normal case is to load it
  # with white noise (between -1 and 1).
  allp = make_one_zero(lf * (1.0 - wt), lf * wt)
  feedb = make_one_zero(c, 1.0)     # or feedb = make_one_zero(1.0, c)
  dlen.times do |i|
    tab[i] = 1.0 - random(2.0)
  end
  run_instrument(start, dur) do
    val = tab.clm_cycle
    tab[tab.clm_cycle_index] = (1.0 - c) * one_zero(feedb, one_zero(allp, val))
    amp * val
  end
end

def pluck_test(start = 0.0, dur = 1.0)
  pluck(start, dur, 330, 0.3, 0.95, 0.95)
  $now = start + dur + 0.2
end

# formant center frequencies for a male speaker (vox and pqw_vox)
Formants = {
  :I  => [390, 1990, 2550], :E   => [530, 1840, 2480], :AE => [660, 1720, 2410],
  :UH => [520, 1190, 2390], :A   => [730, 1090, 2440], :OW => [570,  840, 2410],
  :U  => [440, 1020, 2240], :OO  => [300,  870, 2240], :ER => [490, 1350, 1690],
  :W  => [300,  610, 2200], :LL  => [380,  880, 2575], :R  => [420, 1300, 1600],
  :Y  => [300, 2200, 3065], :EE  => [260, 3500, 3800], :LH => [280, 1450, 1600],
  :L  => [300, 1300, 3000], :I2  => [350, 2300, 3340], :B  => [200,  800, 1750],
  :D  => [300, 1700, 2600], :G   => [250, 1350, 2000], :M  => [280,  900, 2200],
  :N  => [280, 1700, 2600], :NG  => [280, 2300, 2750], :P  => [300,  800, 1750],
  :T  => [200, 1700, 2600], :K   => [350, 1350, 2000], :F  => [175,  900, 4400],
  :TH => [200, 1400, 2200], :S   => [200, 1300, 2500], :SH => [200, 1800, 2000],
  :V  => [175, 1100, 2400], :THE => [200, 1600, 2200], :Z  => [200, 1300, 2500],
  :ZH => [175, 1800, 2000], :ZZ  => [900, 2400, 3800], :VV => [565, 1045, 2400]}

# MLBVOI
# 
# translation from MUS10 of Marc LeBrun's waveshaping voice instrument
# (using FM here) this version translated (and simplified slightly)
# from CLM's mlbvoi.ins
def vox(start, dur, freq, amp, ampfun, freqfun, freqscl, voxfun, index, vibscl)
  f1 = []
  f2 = []
  f3 = []
  (voxfun.length - 1).step(1, -2) do |i|
    phon = Formants[voxfun[i]]
    x = voxfun[i - 1]
    f1.unshift(phon[0])
    f1.unshift(x)
    f2.unshift(phon[1])
    f2.unshift(x)
    f3.unshift(phon[2])
    f3.unshift(x)
  end
  car_os = make_oscil(:frequency, 0)
  of0 = make_oscil(:frequency, 0)
  of1 = make_oscil(:frequency, 0)
  of2 = make_oscil(:frequency, 0)
  of3 = make_oscil(:frequency, 0)
  of4 = make_oscil(:frequency, 0)
  of5 = make_oscil(:frequency, 0)
  ampf = make_env(:envelope, ampfun, :scaler, amp, :duration, dur)
  frmf1 = make_env(:envelope, f1, :duration, dur)
  frmf2 = make_env(:envelope, f2, :duration, dur)
  frmf3 = make_env(:envelope, f3, :duration, dur)
  freqf = make_env(:envelope, freqfun, :duration, dur, :scaler, freqscl * freq, :offset, freq)
  per_vib = make_triangle_wave(:frequency, 6, :amplitude, freq * vibscl)
  ran_vib = make_rand_interp(:frequency, 20, :amplitude, freq * 0.01)
  run_instrument(start, dur) do
    frq = env(freqf) + triangle_wave(per_vib) + rand_interp(ran_vib)
    car = index * oscil(car_os, hz2radians(frq))
    frm = env(frmf1)
    frm0 = frm / frq.to_f
    frm_int = frm0.floor
    if frm_int.even?
      frq0 = hz2radians(frm_int * frq)
      frq1 = hz2radians((frm_int + 1) * frq)
      amp1 = frm0 - frm_int
      amp0 = 1.0 - amp1
    else
      frq1 = hz2radians(frm_int * frq)
      frq0 = hz2radians((frm_int + 1) * frq)
      amp0 = frm0 - frm_int
      amp1 = 1.0 - amp0
    end
    frm = env(frmf2)
    frm0 = frm / frq.to_f
    frm_int = frm0.floor
    if frm_int.even?
      frq2 = hz2radians(frm_int * frq)
      frq3 = hz2radians((frm_int + 1) * frq)
      amp3 = frm0 - frm_int
      amp2 = 1.0 - amp3
    else
      frq3 = hz2radians(frm_int * frq)
      frq2 = hz2radians((frm_int + 1) * frq)
      amp2 = frm0 - frm_int
      amp3 = 1.0 - amp2
    end
    frm = env(frmf3)
    frm0 = frm / frq.to_f
    frm_int = frm0.floor
    if frm_int.even?
      frq4 = hz2radians(frm_int * frq)
      frq5 = hz2radians((frm_int + 1) * frq)
      amp5 = frm0 - frm_int
      amp4 = 1.0 - amp5
    else
      frq5 = hz2radians(frm_int * frq)
      frq4 = hz2radians((frm_int + 1) * frq)
      amp4 = frm0 - frm_int
      amp5 = 1.0 - amp4
    end
    env(ampf) * (0.8 * (amp0 * oscil(of0, frq0 + 0.2 * car) +
                        amp1 * oscil(of1, frq1 + 0.2 * car)) +
                        0.15 * (amp2 * oscil(of2, frq2 + 0.5 * car) +
                                amp3 * oscil(of3, frq3 + 0.5 * car)) +
                                0.05 * (amp4 * oscil(of4, frq4 + car) +
                                        amp5 * oscil(of5, frq5 + car)))
  end
end

def vox_test(start = 0.0, dur = 1.0)
  amp_env = [0, 0, 25, 1, 75, 1, 100, 0]
  frq_env = [0, 0, 5, 0.5, 10, 0, 100, 1]
  examp1 = [0, :E, 25, :AE, 35, :ER, 65, :ER, 75, :I, 100, :UH]
  examp2 = [0, :I, 5, :OW, 10, :I, 50, :AE, 100, :OO]

  $now = start
  vox($now, dur, 170, 0.4, amp_env, frq_env, 0.1, examp1, 0.05, 0.1)
  $now += dur + 0.2
  vox($now, dur, 300, 0.4, amp_env, frq_env, 0.1, examp2, 0.02, 0.1)
  $now += dur + 0.2
  vox($now, 5, 600, 0.4, amp_env, frq_env, 0.1, examp2, 0.01, 0.1)
  $now += 5.0 + 0.2
end

# FOF example
add_help(:fofins,
         "fofins(beg, dur, frq, amp, vib, f0, a0, f1, a1, \
f2, a2, ae=[0, 0, 25, 1, 75, 1, 100, 0])  \
Produces FOF synthesis: \
fofins(0, 1, 270, 0.2, 0.001, 730, 0.6, 1090, 0.3, 2440, 0.1)")
def fofins(start, dur, frq, amp, vib, f0, a0, f1, a1, f2, a2,
           ae = [0, 0, 25, 1, 75, 1, 100,0])
  ampf = make_env(:envelope, ae, :scaler, amp, :duration, dur)
  frq0 = hz2radians(f0)
  frq1 = hz2radians(f1)
  frq2 = hz2radians(f2)
  foflen = @srate == 22050.0 ? 100 : 200
  vibr = make_oscil(:frequency, 6)
  win_freq = TWO_PI / foflen
  wt0 = make_wave_train(:size, foflen, :frequency, frq)
  foftab = mus_data(wt0)
  foflen.times do |i|
    foftab[i] = (a0 * sin(i * frq0) + a1 * sin(i * frq1) +
                     a2 * sin(i * frq2)) * 0.5 * (1.0 - cos(i * win_freq))
  end
  run_instrument(start, dur) do
    env(ampf) * wave_train(wt0, vib * oscil(vibr))
  end
end

def fofins_test(start = 0.0, dur = 1.0)
  fofins(start, dur, 270, 0.2, 0.001, 730, 0.6, 1090, 0.3, 2440, 0.1)
  $now = start + dur + 0.2
end

# FM TRUMPET
#
# Dexter Morrill's FM-trumpet: from CMJ feb 77 p51
def fm_trumpet(start, dur, *args)
  frq1, frq2, amp1, amp2, ampatt1, ampdec1, ampatt2, ampdec2 = nil
  modfrq1, modind11, modind12, modfrq2, modind21, modind22 = nil
  rvibamp, rvibfrq, vibamp, vibfrq, vibatt, vibdec = nil
  frqskw, frqatt, ampenv1, ampenv2 = nil
  indenv1, indenv2, degree, distance, reverb_amount = nil
  optkey(args, binding,
         [:frq1, 250.0],
         [:frq2, 1500.0],
         [:amp1, 0.5],
         [:amp2, 0.1],
         [:ampatt1, 0.03],
         [:ampdec1, 0.35],
         [:ampatt2, 0.03],
         [:ampdec2, 0.3],
         [:modfrq1, 250.0],
         [:modind11, 0.0],
         [:modind12, 2.66],
         [:modfrq2, 250.0],
         [:modind21, 0.0],
         [:modind22, 1.8],
         [:rvibamp, 0.007],
         [:rvibfrq, 125.0],
         [:vibamp, 0.007],
         [:vibfrq, 7.0],
         [:vibatt, 0.6],
         [:vibdec, 0.2],
         [:frqskw, 0.03],
         [:frqatt, 0.06],
         [:ampenv1, [0, 0, 25, 1, 75, 0.9, 100, 0]],
         [:ampenv2, [0, 0, 25, 1, 75, 0.9, 100, 0]],
         [:indenv1, [0, 0, 25, 1, 75, 0.9, 100, 0]],
         [:indenv2, [0, 0, 25, 1, 75, 0.9, 100, 0]],
         [:degree, 0.0],
         [:distance, 1.0],
         [:reverb_amount, 0.005])
  dur = dur.to_f
  per_vib_f = make_env(:envelope,
                       stretch_envelope([0, 1, 25, 0.1, 75, 0, 100, 0],
                                        25,
                                        [100 * (vibatt / dur), 45].min,
                                        75,
                                        [100 * (1.0 - vibdec / dur), 55].max),
                       :scaler, vibamp, :duration, dur)

  ran_vib = make_rand_interp(:frequency, rvibfrq, :amplitude, rvibamp)
  per_vib = make_oscil(:frequency, vibfrq)
  dec_01 = [75, 100 * (1.0 - 0.01 / dur)].max
  frq_f = make_env(:envelope,
                   stretch_envelope([0, 0, 25, 1, 75, 1, 100, 0],
                                    25,
                                    [25, 100 * (frqatt / dur)].min,
                                    75,
                                    dec_01),
                   :scaler, frqskw, :duration, dur)
  ampattpt1 = [25, 100 * (ampatt1 / dur)].min
  ampdecpt1 = [75, 100 * (1.0 - ampdec1 / dur)].max
  ampattpt2 = [25, 100 * (ampatt2 / dur)].min
  ampdecpt2 = [75, 100 * (1.0 - ampdec2 / dur)].max
  mod1_f = make_env(:envelope,
                    stretch_envelope(indenv1, 25, ampattpt1, 75, dec_01),
                    :scaler, modfrq1 * (modind12 - modind11), :duration, dur)
  mod1 = make_oscil(:frequency, 0.0)
  car1 = make_oscil(:frequency, 0.0)
  # set frequency to zero here because it is handled multiplicatively below
  car1_f = make_env(:envelope,
                    stretch_envelope(ampenv1, 25, ampattpt1, 75, ampdecpt1),
                    :scaler, amp1, :duration, dur)
  mod2_f = make_env(:envelope,
                    stretch_envelope(indenv2, 25, ampattpt2, 75, dec_01),
                    :scaler, modfrq2 * (modind22 - modind21), :duration, dur)
  mod2 = make_oscil(:frequency, 0.0)
  car2 = make_oscil(:frequency, 0.0)
  car2_f = make_env(:envelope,
                    stretch_envelope(ampenv2, 25, ampattpt2, 75, ampdecpt2),
                    :scaler, amp2, :duration, dur)
  run_instrument(start, dur,
                 :degree, degree,
                 :distance, distance,
                 :reverb_amount, reverb_amount) do
    frq_change = hz2radians((1.0 +
                             rand_interp(ran_vib)) *
                             (1.0 + env(per_vib_f) *
                              oscil(per_vib)) *
                              (1.0 + env(frq_f)))
    env(car1_f) *
      oscil(car1, frq_change *
            (frq1 + env(mod1_f) * oscil(mod1, modfrq1 * frq_change))) +
      env(car2_f) *
      oscil(car2, frq_change *
            (frq2 + env(mod2_f) * oscil(mod2, modfrq2 * frq_change)))
  end
end

def fm_trumpet_test(start = 0.0, dur = 1.0)
  fm_trumpet(start, dur)
  $now = start + dur + 0.2
end

# PQWVOX
#
# translation of CLM pqwvox.ins (itself translated from MUS10 of MLB's
# waveshaping voice instrument (using phase quadrature waveshaping))
add_help(:pqw_vox,
         "pqw_vox(start, dur, freq, spacing_freq, \
amp, ampfun, freqfun, freqscl, phonemes, formant_amps, formant_shapes)  \
Produces vocal sounds using phase quadrature waveshaping.")
def pqw_vox(start, dur, freq, spacing_freq, amp, ampfun, freqfun, freqscl,
            phonemes, formant_amps, formant_shapes)
  vox_fun = lambda do |phons, which, newenv|
    # make an envelope from which entry of phoneme data referred to by phons
    if phons.empty?
      newenv
    else
      vox_fun.call(phons[2..-1],
                   which, newenv + [phons[0], Formants[phons[1]][which]])
    end
  end
  car_sin = make_oscil(:frequency, 0.0)
  car_cos = make_oscil(:frequency, 0.0, :initial_phase, HALF_PI)
  frq_ratio = spacing_freq / freq.to_f
  fs = formant_amps.length
  sin_evens = Array.new(fs)
  cos_evens = Array.new(fs)
  sin_odds = Array.new(fs)
  cos_odds = Array.new(fs)
  amps = Array.new(fs)
  frmfs = Array.new(fs)
  sin_coeffs = Array.new(fs)
  cos_coeffs = Array.new(fs)
  ampf = make_env(:envelope, ampfun, :scaler, amp, :duration, dur)
  freqf = make_env(:envelope, freqfun, :scaler, freqscl * freq,
                   :duration, dur, :offset, freq)
  per_vib = make_triangle_wave(:frequency, 6.0, :amplitude, freq * 0.1)
  ran_vib = make_rand_interp(:frequency, 20.0, :amplitude, freq * 0.05)
  fs.times do |i|
    sin_evens[i] = make_oscil(:frequency, 0.0)
    sin_odds[i] = make_oscil(:frequency, 0.0)
    cos_evens[i] = make_oscil(:frequency, 0.0, :initial_phase, HALF_PI)
    cos_odds[i] = make_oscil(:frequency, 0.0, :initial_phase, HALF_PI)
    amps[i] = formant_amps[i]
    shape = normalize_partials(formant_shapes[i])
    cos_coeffs[i] = partials2polynomial(shape, 1)
    sin_coeffs[i] = partials2polynomial(shape, 0)
    frmfs[i] = make_env(:envelope, vox_fun.call(phonemes, i, []),
                        :duration, dur)
  end
  run_instrument(start, dur) do
    frq = env(freqf) + triangle_wave(per_vib) + rand_interp(ran_vib)
    frqscl = hz2radians(frq * frq_ratio)
    carsin = oscil(car_sin, frqscl)
    carcos = oscil(car_cos, frqscl)
    sum = 0.0
    fs.times do |j|
      frm = env(frmfs[j])
      frm0 = frm / frq
      frm_int = frm0.floor
      if frm_int.even?
        even_freq = hz2radians(frm_int * frq)
        odd_freq = hz2radians((frm_int + 1.0) * frq)
        odd_amp = frm0 - frm_int
        even_amp = 1.0 - odd_amp
      else
        odd_freq = hz2radians(frm_int * frq)
        even_freq = hz2radians((frm_int + 1.0) * frq)
        even_amp = frm0 - frm_int
        odd_amp = 1.0 - even_amp
      end
      fax = polynomial(cos_coeffs[j], carcos)
      yfax = carsin * polynomial(sin_coeffs[j], carcos)
      sum = sum + amps[j] *
        (even_amp * (yfax * oscil(sin_evens[j], even_freq) -
                     fax * oscil(cos_evens[j], even_freq)) +
                     odd_amp * (yfax * oscil(sin_odds[j], odd_freq) -
                                fax * oscil(cos_odds[j], odd_freq)))
    end
    env(ampf) * sum
  end
end

def pqw_vox_test(start = 0.0, dur = 1.0)
  ampfun = [0, 0, 50, 1, 100, 0]
  freqfun = [0, 0, 100, 0]
  freqramp = [0, 0, 100, 1]
  sh1 = [[1, 1, 2, 0.5],
    [1, 0.5, 2, 0.5, 3, 1],
    [1, 1, 4, 0.5]]
  sh2 = [[1, 1, 2, 0.5],
    [1, 1, 2, 0.5, 3, 0.2, 4, 0.1],
    [1, 1, 3, 0.1, 4, 0.5]]
  sh3 = [[1, 1, 2, 0.5],
    [1, 1, 4, 0.1],
    [1, 1, 2, 0.1, 4, 0.05]]
  sh4 = [[1, 1, 2, 0.5, 3, 0.1, 4, 0.01],
    [1, 1, 4, 0.1],
    [1, 1, 2, 0.1, 4, 0.05]]

  $now = start
  pqw_vox($now, dur, 300, 300, 0.5, ampfun, freqfun, 0.0,
          [0, :L, 100, :L], [0.33, 0.33, 0.33], sh1)
  $now += dur + 0.2
  pqw_vox($now, dur, 200, 200, 0.1, ampfun, freqramp, 0.1,
          [0, :UH, 100, :ER], [0.8, 0.15, 0.05], sh2)
  $now += dur + 0.2
  pqw_vox($now, dur, 100, 314, 0.1, ampfun, freqramp, 0.1,
          [0, :UH, 100, :ER], [0.8, 0.15, 0.05], sh2)
  $now += dur + 0.2
  pqw_vox($now, dur, 200, 314, 0.1, ampfun, freqramp, 0.01,
          [0, :UH, 100, :ER], [0.8, 0.15, 0.05], sh3)
  $now += dur + 0.2
  pqw_vox($now, dur, 100, 414, 0.2, ampfun, freqramp, 0.01,
          [0, :OW, 50, :E, 100, :ER], [0.8, 0.15, 0.05], sh4)
  $now += dur + 0.2
end

# STEREO-FLUTE
# slightly simplified [MS]
add_help(:stereo_flute,
        "stereo_flute(start, dur, freq, flow, *key_args) 
 :flow_envelope   = [0, 1, 100, 1]
 :decay           = 0.01
 :noise           = 0.0356
 :embouchure_size = 0.5
 :fbk_scl1        = 0.5
 :fbk_scl2        = 0.55
 :out_scl         = 1.0
 :a0              = 0.7
 :b1              = -0.3
 :vib_rate        = 5
 :vib_amount      = 0.03
 :ran_rate        = 5
 :ran_amount      = 0.03
is a physical model of a flute: \
stereo_flute(0, 1, 440, 0.55, :flow_envelope, [0, 0, 1, 1, 2, 1, 3, 0])")
def stereo_flute(start, dur, freq, flow, *args)
  flow_envelope, decay, noise, embouchure_size = nil
  fbk_scl1, fbk_scl2, out_scl = nil
  a0, b1, vib_rate, vib_amount, ran_rate, ran_amount = nil
  optkey(args, binding,
         [:flow_envelope, [0, 1, 100, 1]],
         [:decay, 0.01],   # additional time for instrument to decay
         [:noise, 0.0356],
         [:embouchure_size, 0.5],
         [:fbk_scl1, 0.5], # these two are crucial for good results
         [:fbk_scl2, 0.55],
         [:out_scl, 1.0],
         [:a0, 0.7],       # filter coefficients
         [:b1, -0.3],
         [:vib_rate, 5],
         [:vib_amount, 0.03],
         [:ran_rate, 5],
         [:ran_amount, 0.03])
  flowf = make_env(:envelope, flow_envelope, :scaler, flow,
                   :length, seconds2samples(dur - decay))
  periodic_vib = make_oscil(:frequency, vib_rate)
  ran_vib = make_rand_interp(:frequency, ran_rate)
  breath = make_rand(:frequency, @srate / 2.0, :amplitude, 1)
  period_samples = (@srate / freq).floor
  embouchure_samples = (embouchure_size * period_samples).floor
  embouchure = make_delay(embouchure_samples, :initial_element, 0.0)
  bore = make_delay(period_samples)
  reflection_lp_filter = make_one_pole(a0, b1)
  out_sig = current_diff = previous_out_sig = previous_dc_blocked_a = 0.0
  run_instrument(start, dur) do
    delay_sig = delay(bore, out_sig)
    emb_sig = delay(embouchure, current_diff)
    current_flow = vib_amount * oscil(periodic_vib) +
      ran_amount * rand_interp(ran_vib) + env(flowf)
    current_diff = (current_flow + noise * current_flow * rand(breath)) +
      fbk_scl1 * delay_sig
    current_exitation = emb_sig - emb_sig * emb_sig * emb_sig
    out_sig = one_pole(reflection_lp_filter,
                       current_exitation + fbk_scl2 * delay_sig)
    # NB the DC blocker is not in the cicuit. It is applied to the
    # out_sig but the result is not fed back into the system.
    dc_blocked_a = (out_sig - previous_out_sig) + 0.995 * previous_dc_blocked_a
    previous_out_sig = out_sig
    previous_dc_blocked_a = dc_blocked_a
    out_scl * dc_blocked_a
  end
end

def flute_test(start = 0.0, dur = 1.0)
  stereo_flute(start, dur, 440, 0.55, :flow_envelope, [0, 0, 1, 1, 2, 1, 3, 0])
  $now = start + dur + 0.2
end

# FM-BELL
add_help(:fm_bell,
         "fm_bell(startime, dur, frequency, amplitude, \
amp-env=[...], index_env=[...], index=1.0)  \
Mixes in one fm bell note." )
def fm_bell(start, dur, freq, amp,
            amp_env = [0, 0, 0.1, 1, 10, 0.6, 25, 0.3,
              50, 0.15, 90, 0.1, 100, 0],
            index_env = [0, 1, 2, 1.1, 25, 0.75, 75, 0.5, 100, 0.2],
            index = 1.0)
  fm_ind1 = hz2radians(32.0 * freq)
  fm_ind2 = hz2radians(4.0 * (8.0 - freq / 50.0))
  fm_ind3 = fm_ind2 * 0.705 * (1.4 - freq / 250.0)
  fm_ind4 = hz2radians(32.0 * (20.0 - freq / 20.0))
  mod1 = make_oscil(:frequency, freq * 2.0)
  mod2 = make_oscil(:frequency, freq * 1.41)
  mod3 = make_oscil(:frequency, freq * 2.82)
  mod4 = make_oscil(:frequency, freq * 2.4)
  car1 = make_oscil(:frequency, freq)
  car2 = make_oscil(:frequency, freq)
  car3 = make_oscil(:frequency, freq * 2.4)
  indf = make_env(:envelope, index_env, :scaler, index, :duration, dur)
  ampf = make_env(:envelope, amp_env, :scaler, amp, :duration, dur)
  run_instrument(start, dur) do
    fmenv = env(indf)
    env(ampf) *
      (oscil(car1, fmenv * fm_ind1 * oscil(mod1)) +
       0.15 *
       oscil(car2, fmenv * (fm_ind2 * oscil(mod2) + fm_ind3 * oscil(mod3))) +
       0.15 *
       oscil(car3, fmenv * fm_ind4 * oscil(mod4)))
  end
end

def fm_bell_test(start = 0.0, dur = 1.0)
  fm_bell(start, dur, 440, 0.5)
  $now = start + dur + 0.2
end

# FM-INSECT
def fm_insect(start, dur, freq, amp, amp_env,
              mod_freq, mod_skew, mod_freq_env, mod_index, mod_index_env,
              fm_index, fm_ratio, *args)
  degree, distance, reverb_amount = nil
  optkey(args, binding,
         [:degree, 0.0],
         [:distance, 1.0],
         [:reverb_amount, 0.005])
  carrier = make_oscil(:frequency, freq)
  fm1_osc = make_oscil(:frequency, mod_freq)
  fm2_osc = make_oscil(:frequency, fm_ratio * freq)
  ampf = make_env(:envelope, amp_env, :scaler, amp, :duration, dur)
  indf = make_env(:envelope, mod_index_env,
                  :scaler, hz2radians(mod_index),
                  :duration, dur)
  modfrqf = make_env(:envelope, mod_freq_env,
                     :scaler, hz2radians(mod_skew),
                     :duration, dur)
  fm2_amp = hz2radians(fm_index * fm_ratio * freq)
  run_instrument(start, dur,
                 :degree, degree,
                 :distance, distance,
                 :reverb_amount, reverb_amount) do
    garble_in = env(indf) * oscil(fm1_osc, env(modfrqf))
    garble_out = fm2_amp * oscil(fm2_osc, garble_in)
    env(ampf) * oscil(carrier, garble_out + garble_in)
  end
end

def fm_insect_test(start = 0.0, dur = 1.0)
  locust = [0, 0, 40, 1, 95, 1, 100, 0.5]
  bug_hi = [0, 1, 25, 0.7, 75, 0.78, 100, 1]
  amp = [0, 0, 25, 1, 75, 0.7, 100, 0]

  $now = start
  fm_insect($now + 0.000, 1.699, 4142.627, 0.015, amp, 60, -16.707,
            locust, 500.866, bug_hi, 0.346, 0.5)
  fm_insect($now + 0.195, 0.233, 4126.284, 0.030, amp, 60, -12.142,
            locust, 649.490, bug_hi, 0.407, 0.5)
  fm_insect($now + 0.217, 2.057, 3930.258, 0.045, amp, 60,  -3.011,
            locust, 562.087, bug_hi, 0.591, 0.5)
  fm_insect($now + 2.100, 1.500,  900.627, 0.060, amp, 40, -16.707,
            locust, 300.866, bug_hi, 0.346, 0.5)
  fm_insect($now + 3.000, 1.500,  900.627, 0.060, amp, 40, -16.707,
            locust, 300.866, bug_hi, 0.046, 0.5)
  fm_insect($now + 3.450, 1.500,  900.627, 0.090, amp, 40, -16.707,
            locust, 300.866, bug_hi, 0.006, 0.5)
  fm_insect($now + 3.950, 1.500,  900.627, 0.120, amp, 40, -10.707,
            locust, 300.866, bug_hi, 0.346, 0.5)
  fm_insect($now + 4.300, 1.500,  900.627, 0.090, amp, 40, -20.707,
            locust, 300.866, bug_hi, 0.246, 0.5)
  $now += 6.0
end

# FM-DRUM
#
# Jan Mattox's fm drum:
def fm_drum(start, dur, freq, amp, index, high = false,
            degree = 0.0, distance = 1.0, rev_amount = 0.01)
  casrat = high ? 8.525 : 3.515
  fmrat = high ? 3.414 : 1.414
  glsf = make_env(:envelope, [0, 0, 25, 0, 75, 1, 100, 1],
                  :scaler, high ? hz2radians(66) : 0.0, :duration, dur)
  ampfun = [0, 0, 3, 0.05, 5, 0.2, 7, 0.8, 8, 0.95,
    10, 1.0, 12, 0.95, 20, 0.3, 30, 0.1, 100, 0]
  atdrpt = 100 * (high ? 0.01 : 0.015) / dur
  ampf = make_env(:envelope,
                  stretch_envelope(ampfun, 10, atdrpt, 15,
                                   [atdrpt + 1,
                                     100 - 100 * ((dur - 0.2) / dur)].max),
                  :scaler, amp, :duration, dur)
  indxfun = [0, 0, 5, 0.014, 10, 0.033, 15, 0.061, 20, 0.099,
    25, 0.153, 30, 0.228, 35, 0.332, 40, 0.477,
    45, 0.681, 50, 0.964, 55, 0.681, 60, 0.478, 65, 0.332,
    70, 0.228, 75, 0.153, 80, 0.099, 85, 0.061,
    90, 0.033, 95, 0.0141, 100, 0]
  indxpt = 100 - 100 * ((dur - 0.1) / dur)
  divindxf = stretch_envelope(indxfun, 50, atdrpt, 65, indxpt)
  indxf = make_env(:envelope, divindxf, :duration, dur,
                   :scaler, [hz2radians(index * fmrat * freq), PI].min)
  mindxf = make_env(:envelope, divindxf, :duration, dur,
                    :scaler, [hz2radians(index * casrat * freq), PI].min)
  devf = make_env(:envelope,
                  stretch_envelope(ampfun, 10, atdrpt, 90,
                                   [atdrpt + 1,
                                     100 - 100 * ((dur - 0.05) / dur)].max),
                  :scaler, [hz2radians(7000), PI].min, :duration, dur)
  rn = make_rand(:frequency, 7000, :amplitude, 1)
  carrier = make_oscil(:frequency, freq)
  fmosc = make_oscil(:frequency, freq * fmrat)
  cascade = make_oscil(:frequency, freq * casrat)
  run_instrument(start, dur,
                 :degree, degree,
                 :distance, distance,
                 :reverb_amount, rev_amount) do
    gls = env(glsf)
    env(ampf) *
      oscil(carrier,
            gls +
            env(indxf) *
            oscil(fmosc,
                  gls * fmrat +
                  env(mindxf) * oscil(cascade,
                                      gls * casrat +
                                      env(devf) * rand(rn))))
  end
end

def fm_drum_test(start = 0.0, dur = 1.0)
  $now = start
  fm_drum($now, dur, 55, 0.3, 5, false)
  $now += dur + 0.2
  fm_drum($now, dur, 66, 0.3, 4, true)
  $now += dur + 0.2
end

# FM-GONG
#
# Paul Weineke's gong.
def gong(start, dur, freq, amp, *args)
  degree, distance, reverb_amount = nil
  optkey(args, binding,
         [:degree, 0.0],
         [:distance, 1.0],
         [:reverb_amount, 0.005])
  mfq1 = freq * 1.16
  mfq2 = freq * 3.14
  mfq3 = freq * 1.005
  indx01 = hz2radians(0.01 * mfq1)
  indx11 = hz2radians(0.30 * mfq1)
  indx02 = hz2radians(0.01 * mfq2)
  indx12 = hz2radians(0.38 * mfq2)
  indx03 = hz2radians(0.01 * mfq3)
  indx13 = hz2radians(0.50 * mfq3)
  atpt = 5
  atdur = 100 * (0.002 / dur)
  expf = [0, 0, 3, 1, 15, 0.5, 27, 0.25, 50, 0.1, 100, 0]
  rise = [0, 0, 15, 0.3, 30, 1.0, 75, 0.5, 100, 0]
  fmup = [0, 0, 75, 1.0, 98, 1.0, 100, 0]
  fmdwn = [0, 0, 2, 1.0, 100, 0]
  ampfun = make_env(:envelope, stretch_envelope(expf, atpt, atdur),
                    :scaler, amp, :duration, dur)
  indxfun1 = make_env(:envelope, fmup, :scaler, indx11 - indx01,
                      :duration, dur, :offset, indx01)
  indxfun2 = make_env(:envelope, fmdwn, :scaler, indx12 - indx02,
                      :duration, dur, :offset, indx02)
  indxfun3 = make_env(:envelope, rise, :scaler, indx13 - indx03,
                      :duration, dur, :offset, indx03)
  carrier = make_oscil(:frequency, freq)
  mod1 = make_oscil(:frequency, mfq1)
  mod2 = make_oscil(:frequency, mfq2)
  mod3 = make_oscil(:frequency, mfq3)
  run_instrument(start, dur,
                 :degree, degree,
                 :distance, distance,
                 :reverb_amount, reverb_amount) do
    env(ampfun) *
      oscil(carrier,
            env(indxfun1) * oscil(mod1) +
            env(indxfun2) * oscil(mod2) +
            env(indxfun3) * oscil(mod3))
  end
end

def gong_test(start = 0.0, dur = 1.0)
  gong(start, dur, 261.61, 0.6)
  $now = start + dur + 0.2
end

# ATTRACT
#
# by James McCartney, from CMJ vol 21 no 3 p 6
def attract(start, dur, amp, c)
  a = b = 0.2
  dt = 0.04
  scale = (0.5 * amp) / c
  x = -1.0
  y = z = 0.0
  run_instrument(start, dur) do
    x1 = x - dt * (y + z)
    y = y + dt * (x + a * y)
    z = z + dt * ((b + x * z) - c * z)
    x = x1
    scale * x
  end
end

def attract_test(start = 0.0, dur = 1.0)
  attract(start, dur, 0.5, 2.0)
  $now = start + dur + 0.2
end

# PQW
#
# phase-quadrature waveshaping used to create asymmetric (i.e. single
# side-band) spectra.  The basic idea here is a variant of sin x sin y
# - cos x cos y = cos (x + y)
def pqw(start, dur, spacing_freq, carrier_freq,
        amp, ampfun, indexfun, partials, *args)
  degree, distance, reverb_amount = nil
  optkey(args, binding,
         [:degree, 0.0],
         [:distance, 1.0],
         [:reverb_amount, 0.005])
  normalized_partials = normalize_partials(partials)
  spacing_cos = make_oscil(:frequency, spacing_freq, :initial_phase, HALF_PI)
  spacing_sin = make_oscil(:frequency, spacing_freq)
  carrier_cos = make_oscil(:frequency, carrier_freq, :initial_phase, HALF_PI)
  carrier_sin = make_oscil(:frequency, carrier_freq)
  sin_coeffs = partials2polynomial(normalized_partials, 0)
  cos_coeffs = partials2polynomial(normalized_partials, 1)
  amp_env = make_env(:envelope, ampfun, :scaler, amp, :duration, dur)
  ind_env = make_env(:envelope, indexfun, :duration, dur)
  r = carrier_freq / spacing_freq.to_f
  tr = make_triangle_wave(:frequency, 5,
                          :amplitude, hz2radians(0.005 * spacing_freq))
  rn = make_rand_interp(:frequency, 12,
                        :amplitude, hz2radians(0.005 * spacing_freq))
  run_instrument(start, dur,
                 :degree, degree,
                 :distance, distance,
                 :reverb_amount, reverb_amount) do
    vib = triangle_wave(tr) + rand_interp(rn)
    ax = [1.0, env(ind_env)].min * oscil(spacing_cos, vib)
    fax = polynomial(cos_coeffs, ax)
    yfax = oscil(spacing_sin, vib) * polynomial(sin_coeffs, ax)
    env(amp_env) *
      (oscil(carrier_sin, vib * r) * yfax -
       oscil(carrier_cos, vib * r) * fax)
  end
end

def pqw_test(start = 0.0, dur = 1.0)
  pqw(start, dur, 200, 1000, 0.2,
      [0, 0, 25, 1, 100, 0], [0, 1, 100, 0], [2, 0.1, 3, 0.3, 6, 0.5])
  $now = start + dur + 0.2
  # to see the asymmetric spectrum most clearly, set the index function
  # above to [0, 1, 100, 1]
end

# taken from Perry Cook's stkv1.tar.Z (Synthesis Toolkit), but I was
# in a bit of a hurry and may not have made slavishly accurate
# translations.  Please let me (bil@ccrma.stanford.edu) know of any
# serious (non-envelope) errors.
#
# from Perry Cook's TubeBell.cpp
def tubebell(start, dur, freq, amp, base = 32.0)
  osc0 = make_oscil(freq * 0.995)
  osc1 = make_oscil(freq * 0.995 * 1.414)
  osc2 = make_oscil(freq * 1.005)
  osc3 = make_oscil(freq * 1.414)
  ampenv1 = make_env(:envelope, [0, 0, 0.005, 1, dur, 0],
                     :base, base, :duration, dur)
  ampenv2 = make_env(:envelope, [0, 0, 0.001, 1, dur, 0],
                     :base, 2 * base, :duration, dur)
  ampmod = make_oscil(:frequency, 2.0)
  g0 = 0.5 * amp * 0.707
  g1 = 0.203
  g2 = 0.5 * amp
  g3 = 0.144
  run_instrument(start, dur) do
    (0.007 *
     oscil(ampmod) + 0.993) *
     (g0 * env(ampenv1) * oscil(osc0, g1 * oscil(osc1)) +
      g2 * env(ampenv2) * oscil(osc2, g3 * oscil(osc3)))
  end
end

def tubebell_test(start = 0.0, dur = 1.0)
  tubebell(start, dur, 440, 0.2, 32)
  $now = start + dur + 0.2
end

# from Perry Cook's Wurley.cpp
def wurley(start, dur, freq, amp)
  osc0 = make_oscil(freq)
  osc1 = make_oscil(freq * 4.0)
  osc2 = make_oscil(510.0)
  osc3 = make_oscil(510.0)
  ampmod = make_oscil(:frequency, 8.0)
  g0 = 0.5 * amp
  g1 = 0.307
  g2 = 0.5 * amp * 0.307
  g3 = 0.117
  dur = [dur, 0.3].max
  ampenv = make_env(:envelope, [0, 0, 1, 1, 9, 1, 10, 0], :duration, dur)
  indenv = make_env(:envelope, [0, 0, 0.001, 1, 0.15, 0, dur, 0],
                    :duration, dur)
  resenv = make_env(:envelope, [0, 0, 0.001, 1, 0.25, 0, dur, 0],
                    :duration, dur)
  run_instrument(start, dur) do
    env(ampenv) *
      (1.0 + 0.007 * oscil(ampmod)) *
      (g0 * oscil(osc0, g1 * oscil(osc1)) +
       env(resenv) * g2 * oscil(osc2, g3 * env(indenv) * oscil(osc3)))
  end
end

def wurley_test(start = 0.0, dur = 1.0)
  wurley(start, dur, 440, 0.2)
  $now = start + dur + 0.2
end

# from Perry Cook's Rhodey.cpp
def rhodey(start, dur, freq, amp, base = 0.5)
  osc0 = make_oscil(freq)
  osc1 = make_oscil(freq * 0.5)
  osc2 = make_oscil(freq)
  osc3 = make_oscil(freq * 15.0)
  dur = [dur, 0.3].max
  ampenv1 = make_env(:envelope, [0, 0, 0.005, 1, dur, 0],
                     :base, base, :duration, dur)
  ampenv2 = make_env(:envelope, [0, 0, 0.001, 1, dur, 0],
                     :base, base * 1.5, :duration, dur)
  ampenv3 = make_env(:envelope, [0, 0, 0.001, 1, 0.25, 0, dur, 0],
                     :base, base * 4, :duration, dur)
  g0 = 0.5 * amp
  g1 = 0.535
  g2 = 0.5 * amp
  g3 = 0.109
  run_instrument(start, dur) do
    g0 * env(ampenv1) * oscil(osc0, g1 * oscil(osc1)) +
    g2 * env(ampenv2) * oscil(osc2, env(ampenv3) * g3 * oscil(osc3))
  end
end

def rhodey_test(start = 0.0, dur = 1.0)
  rhodey(start, dur, 440, 0.2, 0.5)
  $now = start + dur + 0.2
end

# from Perry Cook's BeeThree.cpp
def hammondoid(start, dur, freq, amp)
  osc0 = make_oscil(freq * 0.999)
  osc1 = make_oscil(freq * 1.997)
  osc2 = make_oscil(freq * 3.006)
  osc3 = make_oscil(freq * 6.009)
  dur = [dur, 0.1].max
  ampenv1 = make_env(:envelope, [0, 0, 0.005, 1, dur - 0.008, 1, dur, 0],
                     :duration, dur)
  ampenv2 = make_env(:envelope, [0, 0, 0.005, 1, dur, 0], :duration, dur)
  g0 = 0.25 * 0.75 * amp
  g1 = 0.25 * 0.75 * amp
  g2 = 0.5 * amp
  g3 = 0.5 * 0.75 * amp
  run_instrument(start, dur) do
    env(ampenv1) *
      (g0 * oscil(osc0) +
       g1 * oscil(osc1) +
       g2 * oscil(osc2)) +
       env(ampenv2) * g3 * oscil(osc3)
  end
end

def hammondoid_test(start = 0.0, dur = 1.0)
  hammondoid(start, dur, 440, 0.2)
  $now = start + dur + 0.2
end

# from Perry Cook's HeavyMtl.cpp
def metal(start, dur, freq, amp)
  osc0 = make_oscil(freq)
  osc1 = make_oscil(freq * 4.0 * 0.999)
  osc2 = make_oscil(freq * 3.0 * 1.001)
  osc3 = make_oscil(freq * 0.5 * 1.002)
  dur = [dur, 0.1].max
  ampenv0 = make_env(:envelope, [0, 0, 0.001, 1, dur - 0.002, 1, dur, 0],
                     :duration, dur)
  ampenv1 = make_env(:envelope, [0, 0, 0.001, 1, dur - 0.011, 1, dur, 0],
                     :duration, dur)
  ampenv2 = make_env(:envelope, [0, 0, 0.01, 1, dur - 0.015, 1, dur, 0],
                     :duration, dur)
  ampenv3 = make_env(:envelope, [0, 0, 0.03, 1, dur - 0.04, 1, dur, 0],
                     :duration, dur)
  g0 = 0.615 * amp
  g1 = 0.202
  g2 = 0.574
  g3 = 0.116
  run_instrument(start, dur) do
    g0 *
      env(ampenv0) *
      oscil(osc0,
            g1 * env(ampenv1) * oscil(osc1, g2 * env(ampenv2) * oscil(osc2)) +
            g3 * env(ampenv3) * oscil(osc3))
  end
end

def metal_test(start = 0.0, dur = 1.0)
  metal(start, dur, 440, 0.2)
  $now = start + dur + 0.2
end

# DRONE
def drone(start, dur, freq, amp, ampfun, synth,
          ampat, ampdc, amtrev, deg, dis, rvibamt, rvibfreq)
  waveform = partials2wave(synth)
  amp *= 0.25
  s = make_table_lookup(:frequency, freq, :wave, waveform)
  amp_env = make_env(:envelope,
                     stretch_envelope(ampfun, 25, 100 * (ampat / dur.to_f), 75,
                                      100 - 100 * (ampdc / dur.to_f)),
                     :scaler, amp, :duration, dur)
  ran_vib = make_rand(:frequency, rvibfreq,
                      :amplitude, hz2radians(rvibamt * freq))
  run_instrument(start, dur,
                 :distance, dis,
                 :degree, deg,
                 :reverb_amount, amtrev) do
    env(amp_env) * table_lookup(s, rand(ran_vib).abs)
  end
end

# CANTER
def canter(start, dur, pitch, amp, deg, dis, pcrev,
           ampfun, ranfun, skewfun, skewpc,
           ranpc, ranfreq, indexfun, atdr, dcdr,
           ampfun1, indfun1, fmtfun1,
           ampfun2, indfun2, fmtfun2,
           ampfun3, indfun3, fmtfun3,
           ampfun4, indfun4, fmtfun4)
  amp *= 0.25
  dur = dur.to_f
  pitch = pitch.to_f
  rangetop = 910.0
  rangebot = 400.0
  k = (100 * (log(pitch / rangebot) / log(rangetop / rangebot))).floor
  mfq = pitch
  atpt = 100 * (atdr / dur)
  dcpt = 100 - 100 * (dcdr / dur)
  lfmt1 = envelope_interp(k, fmtfun1)
  harm1 = (0.5 + lfmt1 / pitch).floor
  dev11 = hz2radians(envelope_interp(k, indfun1) * mfq)
  dev01 = dev11 * 0.5
  lamp1 = envelope_interp(k, ampfun1) * amp * (1 - (harm1 - lfmt1 / pitch).abs)
  lfmt2 = envelope_interp(k, fmtfun2)
  harm2 = (0.5 + lfmt2 / pitch).floor
  dev12 = hz2radians(envelope_interp(k, indfun2) * mfq)
  dev02 = dev12 * 0.5
  lamp2 = envelope_interp(k, ampfun2) * amp * (1 - (harm2 - lfmt2 / pitch).abs)
  lfmt3 = envelope_interp(k, fmtfun3)
  harm3 = (0.5 + lfmt3 / pitch).floor
  dev13 = hz2radians(envelope_interp(k, indfun3) * mfq)
  dev03 = dev13 * 0.5
  lamp3 = envelope_interp(k, ampfun3) * amp * (1 - (harm3 - lfmt3 / pitch).abs)
  lfmt4 = envelope_interp(k, fmtfun4)
  harm4 = (0.5 + lfmt4 / pitch).floor
  dev14 = hz2radians(envelope_interp(k, indfun4) * mfq)
  dev04 = dev14 * 0.5
  lamp4 = envelope_interp(k, ampfun4) * amp * (1 - (harm4 - lfmt4 / pitch).abs)
  tampfun = make_env(:envelope, stretch_envelope(ampfun, 25, atpt, 75, dcpt),
                     :duration, dur)
  tskwfun = make_env(:envelope, stretch_envelope(skewfun, 25, atpt, 75, dcpt),
                     :scaler, hz2radians(pitch * skewpc.to_f), :duration, dur)
  tranfun = make_env(:envelope, stretch_envelope(ranfun, 25, atpt, 75, dcpt),
                     :duration, dur)
  tidxfun = make_env(:envelope, stretch_envelope(indexfun, 25, atpt, 75, dcpt),
                     :duration, dur)
  modgen = make_oscil(:frequency, pitch)
  gen1 = make_oscil(:frequency, pitch * harm1)
  gen2 = make_oscil(:frequency, pitch * harm2)
  gen3 = make_oscil(:frequency, pitch * harm3)
  gen4 = make_oscil(:frequency, pitch * harm4)
  ranvib = make_rand(:frequency, ranfreq, :amplitude, hz2radians(ranpc * pitch))
  run_instrument(start, dur,
                 :degree, deg,
                 :distance, dis,
                 :reverb_amount, pcrev) do
    frqval = env(tskwfun) + env(tranfun) * rand(ranvib)
    modval = oscil(modgen, frqval)
    ampval = env(tampfun)
    indval = env(tidxfun)
    lamp1 *
      ampval *
      oscil(gen1, ((dev01 + indval * dev11) * modval + frqval) * harm1) +
      lamp2 *
      ampval *
      oscil(gen2, ((dev02 + indval * dev12) * modval + frqval) * harm2) +
      lamp3 *
      ampval *
      oscil(gen3, ((dev03 + indval * dev13) * modval + frqval) * harm3) +
      lamp4 *
      ampval *
      oscil(gen4, ((dev04 + indval * dev14) * modval + frqval) * harm4)
  end
end

# NREV (the most popular Samson box reverb)
#
# reverb_factor controls the length of the decay -- it should not
#   exceed (/ 1.0 .823)
# lp_coeff controls the strength of the low pass filter inserted
#   in the feedback loop
# volume can be used to boost the reverb output
def nrev(*args)
  reverb_factor, lp_coeff, volume = nil
  optkey(args, binding,
         [:reverb_factor, 1.09],
         [:lp_coeff, 0.7],
         [:volume, 1.0])
  next_prime = lambda do |val|
    if val.prime?
      val
    else
      next_prime.call(val + 2)
    end
  end
  srscale = @srate / 25641
  dly_len = [1433, 1601, 1867, 2053, 2251, 2399,
    347, 113, 37, 59, 53, 43, 37, 29, 19]
  dly_len.map! do |x|
    val = (x * srscale).round
    val += 1 if val.even?
    next_prime.call(val)
  end
  comb1 = make_comb(0.822 * reverb_factor, dly_len[0])
  comb2 = make_comb(0.802 * reverb_factor, dly_len[1])
  comb3 = make_comb(0.773 * reverb_factor, dly_len[2])
  comb4 = make_comb(0.753 * reverb_factor, dly_len[3])
  comb5 = make_comb(0.753 * reverb_factor, dly_len[4])
  comb6 = make_comb(0.733 * reverb_factor, dly_len[5])
  low = make_one_pole(lp_coeff, lp_coeff - 1.0)
  chan2 = (@channels > 1)
  chan4 = (@channels == 4)
  allpass1 = make_all_pass(-0.7, 0.7, dly_len[6])
  allpass2 = make_all_pass(-0.7, 0.7, dly_len[7])
  allpass3 = make_all_pass(-0.7, 0.7, dly_len[8])
  allpass4 = make_all_pass(-0.7, 0.7, dly_len[9]) # 10 for quad
  allpass5 = make_all_pass(-0.7, 0.7, dly_len[11])
  allpass6 = (chan2 ? make_all_pass(-0.7, 0.7, dly_len[12]) : nil)
  allpass7 = (chan4 ? make_all_pass(-0.7, 0.7, dly_len[13]) : nil)
  allpass8 = (chan4 ? make_all_pass(-0.7, 0.7, dly_len[14]) : nil)
  out_frample = Vct.new(@channels, 0.0)
  run_reverb() do |val, i|
    rev = volume * val
    outrev = all_pass(allpass4,
                      one_pole(low,
                               all_pass(allpass3,
                                        all_pass(allpass2,
                                                 all_pass(allpass1,
                                                          comb(comb1, rev) +
                                                          comb(comb2, rev) +
                                                          comb(comb3, rev) +
                                                          comb(comb4, rev) +
                                                          comb(comb5, rev) +
                                                          comb(comb6, rev))))))
    out_frample[0] = all_pass(allpass5, outrev)
    if chan2
      out_frample[1] = all_pass(allpass6, outrev)
    end
    if chan4
      out_frample[2] = all_pass(allpass7, outrev)
      out_frample[3] = all_pass(allpass8, outrev)
    end
    out_frample
  end
end

def drone_canter_test(start = 0.0, dur = 1.0)
  fmt1 = [0, 1200, 100, 1000]
  fmt2 = [0, 2250, 100, 1800]
  fmt3 = [0, 4500, 100, 4500]
  fmt4 = [0, 6750, 100, 8100]
  amp1 = [0, 0.67, 100, 0.7]
  amp2 = [0, 0.95, 100, 0.95]
  amp3 = [0, 0.28, 100, 0.33]
  amp4 = [0, 0.14, 100, 0.15]
  ind1 = [0, 0.75, 100, 0.65]
  ind2 = [0, 0.75, 100, 0.75]
  ind3 = [0, 1, 100, 1]
  ind4 = [0, 1, 100, 1]
  skwf = [0, 0, 100, 0]
  ampf = [0, 0, 25, 1, 75, 1, 100, 0]
  ranf = [0, 0.5, 100, 0.5]
  index = [0, 1, 100, 1]
  solid = [0, 0, 5, 1, 95, 1, 100, 0]
  bassdr2 = [0.5, 0.06, 1, 0.62, 1.5, 0.07, 2, 0.6, 2.5, 0.08, 3, 0.56,
    4, 0.24, 5, 0.98, 6, 0.53, 7, 0.16, 8, 0.33, 9, 0.62, 10, 0.12,
    12, 0.14, 14, 0.86, 16, 0.12, 23, 0.14, 24, 0.17]
  tenordr = [0.3, 0.04, 1, 0.81, 2, 0.27, 3, 0.2, 4, 0.21, 5, 0.18,
    6, 0.35, 7, 0.03, 8, 0.07, 9, 0.02, 10, 0.025, 11, 0.035]

  $now = start
  drone($now, 4, 115, 0.125, solid, bassdr2, 0.1, 0.5, 0.03, 45, 1, 0.01, 10)
  drone($now, 4, 229, 0.125, solid, tenordr, 0.1, 0.5, 0.03, 45, 1, 0.01, 11)
  drone($now, 4, 229.5, 0.125, solid, tenordr, 0.1, 0.5, 0.03, 45, 1, 0.01, 9)
  canter($now, 2.100, 918.000, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 2.100, 0.300, 688.500, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 2.400, 0.040, 826.200, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 2.440, 0.560, 459.000, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.000, 0.040, 408.000, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.040, 0.040, 619.650, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.080, 0.040, 408.000, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.120, 0.040, 688.500, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.160, 0.290, 459.000, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.450, 0.150, 516.375, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.600, 0.040, 826.200, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.640, 0.040, 573.750, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.680, 0.040, 619.650, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.720, 0.180, 573.750, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.900, 0.040, 688.500, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  canter($now + 3.940, 0.260, 459.000, 0.175, 45.0, 1, 0.05,
         ampf, ranf, skwf, 0.050, 0.01, 10, index, 0.005, 0.005,
         amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
  $now += 4.4
end

# RESON
def reson(start, dur, pitch, amp, numformants,
          indxfun, skewfun, pcskew, skewat, skewdc,
          vibfreq, vibpc, ranvibfreq, ranvibpc,
          degree, distance, rev_amount, data)
  # data is a list of lists of form
  # [ampf, resonfrq, resonamp, ampat, ampdc, dev0, dev1, indxat, indxdc]
  dur = dur.to_f
  pitch = pitch.to_f
  modulator = make_oscil(:frequency, pitch)
  carriers = Array.new(numformants)
  ampfs = Array.new(numformants)
  indfs = Array.new(numformants)
  c_rats = Array.new(numformants)
  frqf = make_env(:envelope,
                  stretch_envelope(skewfun, 25, 100 * (skewat / dur), 75,
                                   100 - (100 * (skewdc / dur))),
                  :scaler, hz2radians(pcskew * pitch), :duration, dur)
  pervib = make_triangle_wave(:frequency, vibfreq,
                              :amplitude, hz2radians(vibpc * pitch))
  ranvib = make_rand_interp(:frequency, ranvibfreq,
                            :amplitude, hz2radians(ranvibpc * pitch))
  totalamp = 0.0
  numformants.times do |i|
    totalamp += data[i][2]
  end
  numformants.times do |i|
    frmdat = data[i]
    ampf = frmdat[0]
    freq = frmdat[1]
    rfamp = frmdat[2]
    ampat = 100 * (frmdat[3] / dur)
    ampdc = 100 - 100 * (frmdat[4] / dur)
    dev0 = hz2radians(frmdat[5] * freq)
    dev1 = hz2radians(frmdat[6] * freq)
    indxat = 100 * (frmdat[7] / dur)
    indxdc = 100 - 100 * (frmdat[8] / dur)
    harm = (freq / pitch).round
    rsamp = 1.0 - (harm - freq / pitch).abs
    cfq = pitch * harm
    ampat = 25 if ampat.zero?
    ampdc = 75 if ampdc.zero?
    indxat = 25 if indxat.zero?
    indxdc = 75 if indxdc.zero?
    indfs[i] = make_env(:envelope,
                        stretch_envelope(indxfun, 25, indxat, 75, indxdc),
                        :scaler, dev1 - dev0,
                        :offset, dev0,
                        :duration, dur)
    ampfs[i] = make_env(:envelope,
                        stretch_envelope(ampf, 25, ampat, 75, ampdc),
                        :scaler, rsamp * amp * (rfamp / totalamp),
                        :duration, dur)
    c_rats[i] = harm
    carriers[i] = make_oscil(:frequency, cfq)
  end
  run_instrument(start, dur,
                 :degree, degree,
                 :distance, distance,
                 :reverb_amount, rev_amount) do
    vib = triangle_wave(pervib) + rand_interp(ranvib) + env(frqf)
    modsig = oscil(modulator, vib)
    outsum = 0.0
    numformants.times do |j|
      outsum += env(ampfs[j]) *
        oscil(carriers[j],
              vib * c_rats[j] +
              env(indfs[j]) * modsig)
    end
    outsum
  end
end

def reson_test(start = 0.0, dur = 1.0)
  data = [[[0, 0, 100, 1], 1200, 0.5, 0.1, 0.1, 0, 1.0, 0.1, 0.1],
    [[0, 1, 100, 0], 2400, 0.5, 0.1, 0.1, 0, 1.0, 0.1, 0.1]]

  reson(start, dur, 440, 0.5, 2, [0, 0, 100, 1], [0, 0, 100, 1],
        0.1, 0.1, 0.1, 5, 0.01, 5, 0.01, 0, 1.0, 0.01, data)
  $now = start + dur + 0.2
end

# STK's feedback-fm instrument named CelloN in Sambox-land
def cellon(start, dur, pitch0, amp, ampfun, betafun,
           beta0, beta1, betaat, betadc, ampat, ampdc, dis, pcrev, deg, pitch1,
           glissfun = [0, 0, 100, 0], glissat = 0.0, glissdc = 0.0,
           pvibfreq = 0.0, pvibpc = 0.0,
           pvibfun = [0, 1, 100, 1], pvibat = 0.0, pvibdc = 0.0,
           rvibfreq = 0.0, rvibpc = 0.0, rvibfun = [0, 1, 100, 1])

  pit1 = pitch1.zero? ? pitch0 : pitch1
  car = make_oscil(:frequency, pitch0)
  low = make_one_zero(0.5, -0.5)
  fmosc = make_oscil(:frequency, pitch0)
  fm = 0.0
  dur = dur.to_f
  pitch0 = pitch0.to_f
  pvib = make_triangle_wave(:frequency, pvibfreq, :amplitude, 1.0)
  rvib = make_rand_interp(:frequency, rvibfreq, :amplitude, 1.0)
  ampap = (ampat > 0.0 ? (100 * (ampat / dur)) : 25)
  ampdp = (ampdc > 0.0 ? (100 * (1.0 - ampdc / dur)) : 75)
  glsap = (glissat > 0.0 ? (100 * (glissat / dur)) : 25)
  glsdp = (glissdc > 0.0 ? (100 * (1.0 - glissdc / dur)) : 75)
  betap = (betaat > 0.0 ? (100 * (betaat / dur)) : 25)
  betdp = (betadc > 0.0 ? (100 * (1.0 - betadc / dur)) : 75)
  pvbap = (pvibat > 0.0 ? (100 * (pvibat / dur)) : 25)
  pvbdp = (pvibdc > 0.0 ? (100 * (1.0 - pvibdc / dur)) : 75)
  pvibenv = make_env(:envelope,
                     stretch_envelope(pvibfun, 25, pvbap, 75, pvbdp),
                     :scaler, hz2radians(pvibpc * pitch0),
                     :duration, dur)
  rvibenv = make_env(:envelope, stretch_envelope(rvibfun),
                     :duration, dur,
                     :scaler, hz2radians(rvibpc * pitch0))
  glisenv = make_env(:envelope,
                     stretch_envelope(glissfun, 25, glsap, 75, glsdp),
                     :scaler, hz2radians(pit1 - pitch0),
                     :duration, dur)
  amplenv = make_env(:envelope, stretch_envelope(ampfun, 25, ampap, 75, ampdp),
                     :scaler, amp,
                     :duration, dur)
  betaenv = make_env(:envelope, stretch_envelope(betafun, 25, betap, 75, betdp),
                     :scaler, beta1 - beta0,
                     :offset, beta0,
                     :duration, dur)
  run_instrument(start, dur,
                 :degree, deg,
                 :distance, dis,
                 :reverb_amount, pcrev) do
    vib = env(pvibenv) * triangle_wave(pvib) +
      env(rvibenv) * rand_interp(rvib) +
      env(glisenv)
    fm = one_zero(low, env(betaenv) * oscil(fmosc, fm + vib))
    env(amplenv) * oscil(car, fm + vib)
  end
end

def cellon_test(start = 0.0, dur = 1.0)
  cellon(start, dur, 220, 0.5, 
         [0, 0, 25, 1, 75, 1, 100, 0],        # ampfun
         [0, 0, 25, 1, 75, 1, 100, 0],        # betafun
         0.75, 1.0, 0, 0, 0, 0, 1, 0, 0, 220, 
         [0, 0, 25, 1, 75, 1, 100, 0],        # glissfun
         0, 0, 0, 0,
         [0, 0, 100, 0],                      # pvibfun
         0, 0, 0, 0,
         [0, 0, 100, 0])                      # rvibfun
  $now = start + dur + 0.2
end

# JL-REVERB
def jl_reverb(*args)
  allpass1 = make_all_pass(-0.7, 0.7, 2111)
  allpass2 = make_all_pass(-0.7, 0.7,  673)
  allpass3 = make_all_pass(-0.7, 0.7,  223)
  comb1 = make_comb(0.742,  9601)
  comb2 = make_comb(0.733, 10007)
  comb3 = make_comb(0.715, 10799)
  comb4 = make_comb(0.697, 11597)
  outdel1 = make_delay((0.013 * @srate).round)
  outdel2 = (@channels > 1 ? make_delay((0.011 * @srate).round) : false)
  out_frample = Vct.new(@channels, 0.0)
  run_reverb() do |ho, i|
    allpass_sum = all_pass(allpass3, all_pass(allpass2, all_pass(allpass1, ho)))
    comb_sum = (comb(comb1, allpass_sum) + comb(comb2, allpass_sum) +
                comb(comb3, allpass_sum) + comb(comb4, allpass_sum))
    out_frample[0] = delay(outdel1, comb_sum)
    if outdel2
      out_frample[1] = delay(outdel2, comb_sum)
    end
    out_frample
  end
end

# GRAN-SYNTH
def gran_synth(start, dur, freq, grain_dur, interval, amp)
  grain_env = make_env(:envelope, [0, 0, 25, 1, 75, 1, 100, 0],
                       :duration, grain_dur)
  carrier = make_oscil(:frequency, freq)
  grain_size = ([grain_dur, interval].max * @srate).ceil
  grains = make_wave_train(:size, grain_size, :frequency, 1.0 / interval)
  grain = mus_data(grains)
  grain_size.times do |i|
    grain[i] = env(grain_env) * oscil(carrier)
  end
  run_instrument(start, dur) do
    amp * wave_train(grains)
  end
end

def gran_synth_test(start = 0.0, dur = 1.0)
  gran_synth(start, dur, 100, 0.0189, 0.02, 0.4)
  $now = start + dur + 0.2
end

# TOUCH-TONE
def touch_tone(start, number)
  touch_tab_1 = [0, 697, 697, 697, 770, 770, 770, 852, 852, 852, 941, 941, 941]
  touch_tab_2 = [0, 1209, 1336, 1477, 1209, 1336,
                 1477, 1209, 1336, 1477, 1209, 1336, 1477]
  number.length.times do |i|
    k = number[i]
    ii = if k.kind_of?(Numeric)
          k.zero? ? 11 : k
        else
          k == ?* ? 10 : 12
        end
    frq1 = make_oscil(:frequency, touch_tab_1[ii])
    frq2 = make_oscil(:frequency, touch_tab_2[ii])
    run_instrument(start + i * 0.3, 0.2) do
      0.25 * (oscil(frq1) + oscil(frq2))
    end
  end
end

def touch_tone_test(start = 0.0, dur = 1.0)
  touch_tone(start, [4, 8, 3, 4, 6, 2, 1])
  $now = start + dur * 7 + 0.2  # 7 digits
end

# SPECTRA
def spectra(start, dur, freq, amp,
            partials = [1, 1, 2, 0.5],
            amp_envelope = [0, 0, 50, 1, 100, 0],
            vibrato_amplitude = 0.005,
            vibrato_speed = 5.0,
            degree = 0.0,
            distance = 1.0,
            rev_amount = 0.005)
  waveform = partials2wave(partials)
  frq = hz2radians(freq)
  s = make_table_lookup(:frequency, freq, :wave, waveform)
  amp_env = make_env(:envelope, amp_envelope, :scaler, amp, :duration, dur)
  per_vib = make_triangle_wave(:frequency, vibrato_speed,
                               :amplitude, vibrato_amplitude * frq)
  ran_vib = make_rand_interp(:frequency, vibrato_speed + 1.0,
                             :amplitude, vibrato_amplitude * frq)
  run_instrument(start, dur,
                 :degree, degree,
                 :distance, distance,
                 :reverb_amount, rev_amount) do
    env(amp_env) *
      table_lookup(s, triangle_wave(per_vib) + rand_interp(ran_vib))
  end
end

def spectra_test(start = 0.0, dur = 1.0)
  spectra(start, dur, 440.0, 0.8, P_a4,
          [0, 0, 1, 1, 5, 0.9, 12, 0.5, 25, 0.25, 100, 0])
  $now = start + dur + 0.2
end

# TWO-TAB
#
# interpolate between two waveforms (this could be extended to
# implement all the various wavetable-based synthesis techniques).
def two_tab(start, dur, freq, amp,
            partial_1 = [1.0, 1.0, 2.0, 0.5],
            partial_2 = [1.0, 0.0, 3.0, 1.0],
            amp_envelope = [0, 0, 50, 1, 100, 0],
            interp_func = [0, 1, 100, 0],
            vibrato_amplitude = 0.005,
            vibrato_speed = 5.0,
            degree = 0.0,
            distance = 1.0,
            rev_amount = 0.005)
  waveform_1 = partials2wave(partial_1)
  waveform_2 = partials2wave(partial_2)
  frq = hz2radians(freq)
  s_1 = make_table_lookup(:frequency, freq, :wave, waveform_1)
  s_2 = make_table_lookup(:frequency, freq, :wave, waveform_2)
  amp_env = make_env(:envelope, amp_envelope, :scaler, amp, :duration, dur)
  interp_env = make_env(:envelope, interp_func, :duration, dur)
  per_vib = make_triangle_wave(:frequency, vibrato_speed,
                               :amplitude, vibrato_amplitude * frq)
  ran_vib = make_rand_interp(:frequency, vibrato_speed + 1.0,
                             :amplitude, vibrato_amplitude * frq)
  run_instrument(start, dur,
                 :degree, degree,
                 :distance, distance,
                 :reverb_amount, rev_amount) do
    vib = triangle_wave(per_vib) + rand_interp(ran_vib)
    intrp = env(interp_env)
    env(amp_env) *
      (intrp * table_lookup(s_1, vib) +
       (1.0 - intrp) * table_lookup(s_2, vib))
  end
end

def two_tab_test(start = 0.0, dur = 1.0)
  two_tab(start, dur, 440, 0.5)
  $now = start + dur + 0.2
end

# LBJ-PIANO
$clm_piano_attack_duration = 0.04
$clm_piano_release_duration = 0.2
$clm_db_drop_per_second = -10.0

Piano_Spectra = [[1.97, 0.0326, 2.99, 0.0086, 3.95, 0.0163, 4.97,
  0.0178, 5.98, 0.0177, 6.95, 0.0315, 8.02, 0.0001, 8.94, 0.0076,
  9.96, 0.0134, 10.99, 0.0284, 11.98, 0.0229, 13.02, 0.0229, 13.89,
  0.0010, 15.06, 0.0090, 16.00, 0.0003, 17.08, 0.0078, 18.16, 0.0064,
  19.18, 0.0129, 20.21, 0.0085, 21.27, 0.0225, 22.32, 0.0061, 23.41,
  0.0102, 24.48, 0.0005, 25.56, 0.0016, 26.64, 0.0018, 27.70, 0.0113,
  28.80, 0.0111, 29.91, 0.0158, 31.06, 0.0093, 32.17, 0.0017, 33.32,
  0.0002, 34.42, 0.0018, 35.59, 0.0027, 36.74, 0.0055, 37.90, 0.0037,
  39.06, 0.0064, 40.25, 0.0033, 41.47, 0.0014, 42.53, 0.0004, 43.89,
  0.0010, 45.12, 0.0039, 46.33, 0.0039, 47.64, 0.0009, 48.88, 0.0016,
  50.13, 0.0006, 51.37, 0.0010, 52.70, 0.0002, 54.00, 0.0004, 55.30,
  0.0008, 56.60, 0.0025, 57.96, 0.0010, 59.30, 0.0012, 60.67, 0.0011,
  61.99, 0.0003, 62.86, 0.0001, 64.36, 0.0005, 64.86, 0.0001, 66.26,
  0.0004, 67.70, 0.0006, 68.94, 0.0002, 70.10, 0.0001, 70.58, 0.0002,
  72.01, 0.0007, 73.53, 0.0006, 75.00, 0.0002, 77.03, 0.0005, 78.00,
  0.0002, 79.57, 0.0006, 81.16, 0.0005, 82.70, 0.0005, 84.22, 0.0003,
  85.41, 0.0002, 87.46, 0.0001, 90.30, 0.0001, 94.02, 0.0001, 95.26,
  0.0002, 109.39, 0.0003],
  [1.98, 0.0194, 2.99, 0.0210, 3.97, 0.0276, 4.96, 0.0297, 5.96, 0.0158,
    6.99, 0.0207, 8.01, 0.0009, 9.00, 0.0101, 10.00, 0.0297, 11.01,
    0.0289, 12.02, 0.0211, 13.04, 0.0127, 14.07, 0.0061, 15.08, 0.0174,
    16.13, 0.0009, 17.12, 0.0093, 18.16, 0.0117, 19.21, 0.0122, 20.29,
    0.0108, 21.30, 0.0077, 22.38, 0.0132, 23.46, 0.0073, 24.14, 0.0002,
    25.58, 0.0026, 26.69, 0.0035, 27.77, 0.0053, 28.88, 0.0024, 30.08,
    0.0027, 31.13, 0.0075, 32.24, 0.0027, 33.36, 0.0004, 34.42, 0.0004,
    35.64, 0.0019, 36.78, 0.0037, 38.10, 0.0009, 39.11, 0.0027, 40.32,
    0.0010, 41.51, 0.0013, 42.66, 0.0019, 43.87, 0.0007, 45.13, 0.0017,
    46.35, 0.0019, 47.65, 0.0021, 48.89, 0.0014, 50.18, 0.0023, 51.42,
    0.0015, 52.73, 0.0002, 54.00, 0.0005, 55.34, 0.0006, 56.60, 0.0010,
    57.96, 0.0016, 58.86, 0.0005, 59.30, 0.0004, 60.75, 0.0005, 62.22,
    0.0003, 63.55, 0.0005, 64.82, 0.0003, 66.24, 0.0003, 67.63, 0.0011,
    69.09, 0.0007, 70.52, 0.0004, 72.00, 0.0005, 73.50, 0.0008, 74.95,
    0.0003, 77.13, 0.0013, 78.02, 0.0002, 79.48, 0.0004, 82.59, 0.0004,
    84.10, 0.0003],
   [2.00, 0.0313, 2.99, 0.0109, 4.00, 0.0215, 5.00, 0.0242, 5.98, 0.0355,
     7.01, 0.0132, 8.01, 0.0009, 9.01, 0.0071, 10.00, 0.0258, 11.03,
     0.0221, 12.02, 0.0056, 13.06, 0.0196, 14.05, 0.0160, 15.11, 0.0107,
     16.11, 0.0003, 17.14, 0.0111, 18.21, 0.0085, 19.23, 0.0010, 20.28,
     0.0048, 21.31, 0.0128, 22.36, 0.0051, 23.41, 0.0041, 24.05, 0.0006,
     25.54, 0.0019, 26.62, 0.0028, 27.72, 0.0034, 28.82, 0.0062, 29.89,
     0.0039, 30.98, 0.0058, 32.08, 0.0011, 33.21, 0.0002, 34.37, 0.0008,
     35.46, 0.0018, 36.62, 0.0036, 37.77, 0.0018, 38.92, 0.0042, 40.07,
     0.0037, 41.23, 0.0011, 42.67, 0.0003, 43.65, 0.0018, 44.68, 0.0025,
     45.99, 0.0044, 47.21, 0.0051, 48.40, 0.0044, 49.67, 0.0005, 50.88,
     0.0019, 52.15, 0.0003, 53.42, 0.0008, 54.69, 0.0010, 55.98, 0.0005,
     57.26, 0.0013, 58.53, 0.0027, 59.83, 0.0011, 61.21, 0.0027, 62.54,
     0.0003, 63.78, 0.0003, 65.20, 0.0001, 66.60, 0.0006, 67.98, 0.0008,
     69.37, 0.0019, 70.73, 0.0007, 72.14, 0.0004, 73.62, 0.0002, 74.40,
     0.0003, 76.52, 0.0006, 77.97, 0.0002, 79.49, 0.0004, 80.77, 0.0003,
     81.00, 0.0001, 82.47, 0.0005, 83.97, 0.0001, 87.27, 0.0002],
   [2.00, 0.0257, 2.99, 0.0142, 3.97, 0.0202, 4.95, 0.0148, 5.95, 0.0420,
     6.95, 0.0037, 7.94, 0.0004, 8.94, 0.0172, 9.95, 0.0191, 10.96, 0.0115,
     11.97, 0.0059, 12.98, 0.0140, 14.00, 0.0178, 15.03, 0.0121, 16.09,
     0.0002, 17.07, 0.0066, 18.08, 0.0033, 19.15, 0.0022, 20.18, 0.0057,
     21.22, 0.0077, 22.29, 0.0037, 23.33, 0.0066, 24.97, 0.0002, 25.49,
     0.0019, 26.55, 0.0042, 27.61, 0.0043, 28.73, 0.0038, 29.81, 0.0084,
     30.91, 0.0040, 32.03, 0.0025, 33.14, 0.0005, 34.26, 0.0003, 35.38,
     0.0019, 36.56, 0.0037, 37.68, 0.0049, 38.86, 0.0036, 40.11, 0.0011,
     41.28, 0.0008, 42.50, 0.0004, 43.60, 0.0002, 44.74, 0.0022, 45.99,
     0.0050, 47.20, 0.0009, 48.40, 0.0036, 49.68, 0.0004, 50.92, 0.0009,
     52.17, 0.0005, 53.46, 0.0007, 54.76, 0.0006, 56.06, 0.0005, 57.34,
     0.0011, 58.67, 0.0005, 59.95, 0.0015, 61.37, 0.0008, 62.72, 0.0004,
     65.42, 0.0009, 66.96, 0.0003, 68.18, 0.0003, 69.78, 0.0003, 71.21,
     0.0004, 72.45, 0.0002, 74.22, 0.0003, 75.44, 0.0001, 76.53, 0.0003,
     78.31, 0.0004, 79.83, 0.0003, 80.16, 0.0001, 81.33, 0.0003, 82.44,
     0.0001, 83.17, 0.0002, 84.81, 0.0003, 85.97, 0.0003, 89.08, 0.0001,
     90.70, 0.0002, 92.30, 0.0002, 95.59, 0.0002, 97.22, 0.0003, 98.86,
     0.0001, 108.37, 0.0001, 125.54, 0.0001],
   [1.99, 0.0650, 3.03, 0.0040, 4.03, 0.0059, 5.02, 0.0090, 5.97, 0.0227,
     6.98, 0.0050, 8.04, 0.0020, 9.00, 0.0082, 9.96, 0.0078, 11.01, 0.0056,
     12.01, 0.0095, 13.02, 0.0050, 14.04, 0.0093, 15.08, 0.0064, 16.14,
     0.0017, 17.06, 0.0020, 18.10, 0.0025, 19.14, 0.0023, 20.18, 0.0015,
     21.24, 0.0032, 22.29, 0.0029, 23.32, 0.0014, 24.37, 0.0005, 25.43,
     0.0030, 26.50, 0.0022, 27.60, 0.0027, 28.64, 0.0024, 29.76, 0.0035,
     30.81, 0.0136, 31.96, 0.0025, 33.02, 0.0003, 34.13, 0.0005, 35.25,
     0.0007, 36.40, 0.0014, 37.51, 0.0020, 38.64, 0.0012, 39.80, 0.0019,
     40.97, 0.0004, 42.09, 0.0003, 43.24, 0.0003, 44.48, 0.0002, 45.65,
     0.0024, 46.86, 0.0005, 48.07, 0.0013, 49.27, 0.0008, 50.49, 0.0006,
     52.95, 0.0001, 54.23, 0.0005, 55.45, 0.0004, 56.73, 0.0001, 58.03,
     0.0003, 59.29, 0.0002, 60.59, 0.0003, 62.04, 0.0002, 65.89, 0.0002,
     67.23, 0.0002, 68.61, 0.0002, 69.97, 0.0004, 71.36, 0.0005, 85.42,
     0.0001],
   [1.98, 0.0256, 2.96, 0.0158, 3.95, 0.0310, 4.94, 0.0411, 5.95, 0.0238,
     6.94, 0.0152, 7.93, 0.0011, 8.95, 0.0185, 9.92, 0.0166, 10.93, 0.0306,
     11.94, 0.0258, 12.96, 0.0202, 13.97, 0.0403, 14.95, 0.0228, 15.93,
     0.0005, 17.01, 0.0072, 18.02, 0.0034, 19.06, 0.0028, 20.08, 0.0124,
     21.13, 0.0137, 22.16, 0.0102, 23.19, 0.0058, 23.90, 0.0013, 25.30,
     0.0039, 26.36, 0.0039, 27.41, 0.0025, 28.47, 0.0071, 29.64, 0.0031,
     30.60, 0.0027, 31.71, 0.0021, 32.84, 0.0003, 33.82, 0.0002, 35.07,
     0.0019, 36.09, 0.0054, 37.20, 0.0038, 38.33, 0.0024, 39.47, 0.0055,
     40.55, 0.0016, 41.77, 0.0006, 42.95, 0.0002, 43.27, 0.0018, 44.03,
     0.0006, 45.25, 0.0019, 46.36, 0.0033, 47.50, 0.0024, 48.87, 0.0012,
     50.03, 0.0016, 51.09, 0.0004, 53.52, 0.0017, 54.74, 0.0012, 56.17,
     0.0003, 57.40, 0.0011, 58.42, 0.0020, 59.70, 0.0007, 61.29, 0.0008,
     62.56, 0.0003, 63.48, 0.0002, 64.83, 0.0002, 66.12, 0.0012, 67.46,
     0.0017, 68.81, 0.0003, 69.13, 0.0003, 70.53, 0.0002, 71.84, 0.0001,
     73.28, 0.0002, 75.52, 0.0010, 76.96, 0.0005, 77.93, 0.0003, 78.32,
     0.0003, 79.73, 0.0003, 81.69, 0.0002, 82.52, 0.0001, 84.01, 0.0001,
     84.61, 0.0002, 86.88, 0.0001, 88.36, 0.0002, 89.85, 0.0002, 91.35,
     0.0003, 92.86, 0.0002, 93.40, 0.0001, 105.28, 0.0002, 106.22, 0.0002,
     107.45, 0.0001, 108.70, 0.0003, 122.08, 0.0002],
   [1.97, 0.0264, 2.97, 0.0211, 3.98, 0.0234, 4.98, 0.0307, 5.96, 0.0085,
     6.94, 0.0140, 7.93, 0.0005, 8.96, 0.0112, 9.96, 0.0209, 10.98, 0.0194,
     11.98, 0.0154, 12.99, 0.0274, 13.99, 0.0127, 15.01, 0.0101, 15.99,
     0.0002, 17.04, 0.0011, 18.08, 0.0032, 19.14, 0.0028, 20.12, 0.0054,
     21.20, 0.0053, 22.13, 0.0028, 23.22, 0.0030, 24.32, 0.0006, 25.24,
     0.0004, 26.43, 0.0028, 27.53, 0.0048, 28.52, 0.0039, 29.54, 0.0047,
     30.73, 0.0044, 31.82, 0.0007, 32.94, 0.0008, 34.04, 0.0012, 35.13,
     0.0018, 36.29, 0.0007, 37.35, 0.0075, 38.51, 0.0045, 39.66, 0.0014,
     40.90, 0.0004, 41.90, 0.0002, 43.08, 0.0002, 44.24, 0.0017, 45.36,
     0.0013, 46.68, 0.0020, 47.79, 0.0015, 48.98, 0.0010, 50.21, 0.0012,
     51.34, 0.0001, 53.82, 0.0003, 55.09, 0.0004, 56.23, 0.0005, 57.53,
     0.0004, 58.79, 0.0005, 59.30, 0.0002, 60.03, 0.0002, 61.40, 0.0003,
     62.84, 0.0001, 66.64, 0.0001, 67.97, 0.0001, 69.33, 0.0001, 70.68,
     0.0001, 73.57, 0.0002, 75.76, 0.0002, 76.45, 0.0001, 79.27, 0.0001,
     80.44, 0.0002, 81.87, 0.0002],
   [2.00, 0.0311, 2.99, 0.0086, 3.99, 0.0266, 4.97, 0.0123, 5.98, 0.0235,
     6.97, 0.0161, 7.97, 0.0008, 8.96, 0.0088, 9.96, 0.0621, 10.99, 0.0080,
     11.99, 0.0034, 12.99, 0.0300, 14.03, 0.0228, 15.04, 0.0105, 16.03,
     0.0004, 17.06, 0.0036, 18.09, 0.0094, 18.95, 0.0009, 20.17, 0.0071,
     21.21, 0.0161, 22.25, 0.0106, 23.28, 0.0104, 24.33, 0.0008, 25.38,
     0.0030, 26.46, 0.0035, 27.50, 0.0026, 28.59, 0.0028, 29.66, 0.0128,
     30.75, 0.0139, 31.81, 0.0038, 32.93, 0.0006, 34.04, 0.0004, 35.16,
     0.0005, 36.25, 0.0023, 37.35, 0.0012, 38.46, 0.0021, 39.59, 0.0035,
     40.71, 0.0006, 41.86, 0.0007, 42.42, 0.0001, 43.46, 0.0003, 44.17,
     0.0032, 45.29, 0.0013, 46.57, 0.0004, 47.72, 0.0011, 48.79, 0.0005,
     50.11, 0.0005, 51.29, 0.0003, 52.47, 0.0002, 53.68, 0.0004, 55.02,
     0.0005, 56.18, 0.0003, 57.41, 0.0003, 58.75, 0.0007, 59.33, 0.0009,
     60.00, 0.0004, 61.34, 0.0001, 64.97, 0.0003, 65.20, 0.0002, 66.48,
     0.0002, 67.83, 0.0002, 68.90, 0.0003, 70.25, 0.0003, 71.59, 0.0002,
     73.68, 0.0001, 75.92, 0.0001, 77.08, 0.0002, 78.45, 0.0002, 81.56,
     0.0002, 82.99, 0.0001, 88.39, 0.0001], 
   [0.97, 0.0059, 1.98, 0.0212, 2.99, 0.0153, 3.99, 0.0227, 4.96, 0.0215,
     5.97, 0.0153, 6.98, 0.0085, 7.98, 0.0007, 8.97, 0.0179, 9.98, 0.0512,
     10.98, 0.0322, 12.00, 0.0098, 13.02, 0.0186, 14.00, 0.0099, 15.05,
     0.0109, 15.88, 0.0011, 17.07, 0.0076, 18.11, 0.0071, 19.12, 0.0045,
     20.16, 0.0038, 21.23, 0.0213, 22.27, 0.0332, 23.34, 0.0082, 24.34,
     0.0014, 25.42, 0.0024, 26.47, 0.0012, 27.54, 0.0014, 28.60, 0.0024,
     29.72, 0.0026, 30.10, 0.0008, 31.91, 0.0021, 32.13, 0.0011, 33.02,
     0.0007, 34.09, 0.0014, 35.17, 0.0007, 36.27, 0.0024, 37.39, 0.0029,
     38.58, 0.0014, 39.65, 0.0017, 40.95, 0.0012, 41.97, 0.0004, 42.43,
     0.0002, 43.49, 0.0001, 44.31, 0.0012, 45.42, 0.0031, 46.62, 0.0017,
     47.82, 0.0013, 49.14, 0.0013, 50.18, 0.0010, 51.54, 0.0003, 53.90,
     0.0006, 55.06, 0.0010, 56.31, 0.0003, 57.63, 0.0001, 59.02, 0.0003,
     60.09, 0.0004, 60.35, 0.0004, 61.62, 0.0009, 63.97, 0.0001, 65.19,
     0.0001, 65.54, 0.0002, 66.92, 0.0002, 67.94, 0.0002, 69.17, 0.0003,
     69.60, 0.0004, 70.88, 0.0002, 72.24, 0.0002, 76.12, 0.0001, 78.94,
     0.0001, 81.75, 0.0001, 82.06, 0.0001, 83.53, 0.0001, 90.29, 0.0002,
     91.75, 0.0001, 92.09, 0.0002, 93.28, 0.0001, 97.07, 0.0001],
   [1.98, 0.0159, 2.98, 0.1008, 3.98, 0.0365, 4.98, 0.0133, 5.97, 0.0101,
     6.97, 0.0115, 7.97, 0.0007, 8.99, 0.0349, 10.01, 0.0342, 11.01,
     0.0236, 12.00, 0.0041, 13.02, 0.0114, 14.05, 0.0137, 15.06, 0.0100,
     16.05, 0.0007, 17.04, 0.0009, 18.12, 0.0077, 19.15, 0.0023, 20.12,
     0.0017, 21.24, 0.0113, 22.26, 0.0126, 23.30, 0.0093, 24.36, 0.0007,
     25.43, 0.0007, 26.47, 0.0009, 27.55, 0.0013, 28.59, 0.0025, 29.61,
     0.0010, 30.77, 0.0021, 31.86, 0.0023, 32.96, 0.0003, 34.03, 0.0007,
     35.06, 0.0005, 36.20, 0.0006, 37.34, 0.0006, 38.36, 0.0009, 39.60,
     0.0016, 40.69, 0.0005, 41.77, 0.0002, 42.92, 0.0002, 44.02, 0.0003,
     45.24, 0.0006, 46.33, 0.0004, 47.50, 0.0007, 48.71, 0.0007, 49.87,
     0.0002, 51.27, 0.0002, 53.42, 0.0003, 55.88, 0.0003, 57.10, 0.0004,
     58.34, 0.0002, 59.86, 0.0003, 61.13, 0.0003, 67.18, 0.0001, 68.50,
     0.0001, 71.17, 0.0001, 83.91, 0.0001, 90.55, 0.0001],
   [0.98, 0.0099, 2.00, 0.0181, 2.99, 0.0353, 3.98, 0.0285, 4.97, 0.0514,
     5.96, 0.0402, 6.96, 0.0015, 7.98, 0.0012, 8.98, 0.0175, 9.98, 0.0264,
     10.98, 0.0392, 11.98, 0.0236, 13.00, 0.0153, 14.04, 0.0049, 15.00,
     0.0089, 16.01, 0.0001, 17.03, 0.0106, 18.03, 0.0028, 19.05, 0.0024,
     20.08, 0.0040, 21.11, 0.0103, 22.12, 0.0104, 23.20, 0.0017, 24.19,
     0.0008, 25.20, 0.0007, 26.24, 0.0011, 27.36, 0.0009, 27.97, 0.0030,
     29.40, 0.0044, 30.37, 0.0019, 31.59, 0.0017, 32.65, 0.0008, 33.59,
     0.0005, 34.79, 0.0009, 35.75, 0.0027, 36.88, 0.0035, 37.93, 0.0039,
     39.00, 0.0031, 40.08, 0.0025, 41.16, 0.0010, 43.25, 0.0004, 44.52,
     0.0012, 45.62, 0.0023, 45.85, 0.0012, 47.00, 0.0006, 47.87, 0.0008,
     48.99, 0.0003, 50.48, 0.0003, 51.62, 0.0001, 52.43, 0.0001, 53.56,
     0.0002, 54.76, 0.0002, 56.04, 0.0002, 56.68, 0.0006, 57.10, 0.0003,
     58.28, 0.0005, 59.47, 0.0003, 59.96, 0.0002, 60.67, 0.0001, 63.08,
     0.0002, 64.29, 0.0002, 66.72, 0.0001, 67.97, 0.0001, 68.65, 0.0001,
     70.43, 0.0001, 79.38, 0.0001, 80.39, 0.0001, 82.39, 0.0001],
   [1.00, 0.0765, 1.99, 0.0151, 2.99, 0.0500, 3.99, 0.0197, 5.00, 0.0260,
     6.00, 0.0145, 6.98, 0.0128, 7.97, 0.0004, 8.98, 0.0158, 9.99, 0.0265,
     11.02, 0.0290, 12.02, 0.0053, 13.03, 0.0242, 14.03, 0.0103, 15.06,
     0.0054, 16.04, 0.0006, 17.08, 0.0008, 18.10, 0.0058, 19.16, 0.0011,
     20.16, 0.0055, 21.18, 0.0040, 22.20, 0.0019, 23.22, 0.0014, 24.05,
     0.0005, 25.31, 0.0019, 26.38, 0.0018, 27.44, 0.0022, 28.45, 0.0024,
     29.57, 0.0073, 30.58, 0.0032, 31.66, 0.0071, 32.73, 0.0015, 33.85,
     0.0005, 34.96, 0.0003, 36.00, 0.0020, 37.11, 0.0018, 38.18, 0.0055,
     39.23, 0.0006, 40.33, 0.0004, 41.52, 0.0003, 43.41, 0.0028, 45.05,
     0.0003, 45.99, 0.0002, 47.07, 0.0003, 48.52, 0.0002, 49.48, 0.0003,
     50.63, 0.0003, 51.81, 0.0002, 54.05, 0.0002, 55.24, 0.0001, 56.62,
     0.0001, 57.81, 0.0004, 59.16, 0.0013, 60.23, 0.0003, 66.44, 0.0001,
     68.99, 0.0004, 75.49, 0.0001, 87.56, 0.0004],
   [0.98, 0.0629, 1.99, 0.0232, 2.98, 0.0217, 4.00, 0.0396, 4.98, 0.0171,
     5.97, 0.0098, 6.99, 0.0167, 7.99, 0.0003, 8.98, 0.0192, 9.98, 0.0266,
     10.99, 0.0256, 12.01, 0.0061, 13.02, 0.0135, 14.02, 0.0062, 15.05,
     0.0158, 16.06, 0.0018, 17.08, 0.0101, 18.09, 0.0053, 19.11, 0.0074,
     20.13, 0.0020, 21.17, 0.0052, 22.22, 0.0077, 23.24, 0.0035, 24.00,
     0.0009, 25.32, 0.0016, 26.40, 0.0022, 27.43, 0.0005, 28.55, 0.0026,
     29.60, 0.0026, 30.65, 0.0010, 31.67, 0.0019, 32.77, 0.0008, 33.81,
     0.0003, 34.91, 0.0003, 36.01, 0.0005, 37.11, 0.0010, 38.20, 0.0014,
     39.29, 0.0039, 40.43, 0.0012, 41.50, 0.0006, 43.38, 0.0017, 43.75,
     0.0002, 44.94, 0.0005, 46.13, 0.0002, 47.11, 0.0003, 48.28, 0.0005,
     48.42, 0.0005, 49.44, 0.0003, 50.76, 0.0004, 51.93, 0.0002, 54.15,
     0.0003, 55.31, 0.0005, 55.50, 0.0003, 56.98, 0.0003, 57.90, 0.0004,
     60.33, 0.0002, 61.39, 0.0001, 61.59, 0.0001, 65.09, 0.0002, 66.34,
     0.0001, 68.85, 0.0001, 70.42, 0.0002, 71.72, 0.0001, 73.05, 0.0003,
     79.65, 0.0001, 85.28, 0.0002, 93.52, 0.0001],
   [1.02, 0.0185, 1.99, 0.0525, 2.98, 0.0613, 3.99, 0.0415, 4.98, 0.0109,
     5.97, 0.0248, 6.99, 0.0102, 7.98, 0.0005, 8.98, 0.0124, 9.99, 0.0103,
     10.99, 0.0124, 12.00, 0.0016, 13.01, 0.0029, 14.03, 0.0211, 15.04,
     0.0128, 16.07, 0.0021, 17.09, 0.0009, 18.09, 0.0043, 19.14, 0.0022,
     20.13, 0.0016, 21.20, 0.0045, 22.21, 0.0088, 23.26, 0.0046, 24.29,
     0.0013, 25.35, 0.0009, 26.39, 0.0028, 27.49, 0.0009, 28.51, 0.0006,
     29.58, 0.0012, 30.70, 0.0010, 31.74, 0.0019, 32.75, 0.0002, 33.85,
     0.0001, 34.95, 0.0005, 36.02, 0.0003, 37.16, 0.0009, 38.25, 0.0018,
     39.35, 0.0008, 40.54, 0.0004, 41.61, 0.0002, 43.40, 0.0004, 43.74,
     0.0003, 45.05, 0.0001, 46.11, 0.0003, 47.40, 0.0002, 48.36, 0.0004,
     49.55, 0.0004, 50.72, 0.0002, 52.00, 0.0001, 55.58, 0.0002, 57.02,
     0.0001, 57.98, 0.0002, 59.13, 0.0003, 61.56, 0.0001, 66.56, 0.0001,
     87.65, 0.0002],
   [1.00, 0.0473, 1.99, 0.0506, 2.99, 0.0982, 3.99, 0.0654, 5.00, 0.0196,
     5.99, 0.0094, 6.99, 0.0118, 7.93, 0.0001, 8.99, 0.0057, 10.01, 0.0285,
     11.01, 0.0142, 12.03, 0.0032, 13.03, 0.0056, 14.06, 0.0064, 15.06,
     0.0059, 16.11, 0.0005, 17.09, 0.0033, 18.14, 0.0027, 19.15, 0.0014,
     20.17, 0.0010, 21.21, 0.0059, 22.26, 0.0043, 23.31, 0.0031, 24.31,
     0.0018, 25.33, 0.0009, 26.41, 0.0005, 27.47, 0.0015, 28.53, 0.0015,
     29.58, 0.0041, 30.65, 0.0025, 31.73, 0.0011, 32.83, 0.0010, 34.98,
     0.0003, 36.07, 0.0009, 37.23, 0.0001, 38.26, 0.0020, 39.41, 0.0014,
     40.53, 0.0005, 41.40, 0.0003, 42.80, 0.0002, 43.48, 0.0028, 43.93,
     0.0001, 45.03, 0.0003, 46.18, 0.0007, 47.41, 0.0001, 48.57, 0.0002,
     49.67, 0.0001, 50.83, 0.0002, 54.39, 0.0001, 55.58, 0.0002, 57.97,
     0.0005, 58.11, 0.0002, 59.21, 0.0001, 60.42, 0.0002, 61.66, 0.0001], 
   [1.00, 0.0503, 2.00, 0.0963, 2.99, 0.1304, 3.99, 0.0218, 4.98, 0.0041,
     5.98, 0.0292, 6.98, 0.0482, 7.99, 0.0005, 8.99, 0.0280, 10.00, 0.0237,
     11.00, 0.0152, 12.02, 0.0036, 12.95, 0.0022, 14.06, 0.0111, 15.07,
     0.0196, 16.08, 0.0016, 17.11, 0.0044, 18.13, 0.0073, 19.17, 0.0055,
     20.19, 0.0028, 21.20, 0.0012, 22.27, 0.0068, 23.30, 0.0036, 24.35,
     0.0012, 25.35, 0.0002, 26.46, 0.0005, 27.47, 0.0005, 28.59, 0.0009,
     29.65, 0.0021, 30.70, 0.0020, 31.78, 0.0012, 32.89, 0.0010, 35.06,
     0.0005, 36.16, 0.0008, 37.27, 0.0010, 38.36, 0.0010, 39.47, 0.0014,
     40.58, 0.0004, 41.43, 0.0007, 41.82, 0.0003, 43.48, 0.0008, 44.53,
     0.0001, 45.25, 0.0003, 46.43, 0.0002, 47.46, 0.0002, 48.76, 0.0005,
     49.95, 0.0004, 50.96, 0.0002, 51.12, 0.0002, 52.33, 0.0001, 54.75,
     0.0001, 55.75, 0.0002, 56.90, 0.0002, 58.17, 0.0002, 59.40, 0.0004,
     60.62, 0.0002, 65.65, 0.0001, 66.91, 0.0002, 69.91, 0.0001, 71.25,
     0.0002],
   [1.00, 0.1243, 1.98, 0.1611, 3.00, 0.0698, 3.98, 0.0390, 5.00, 0.0138,
     5.99, 0.0154, 7.01, 0.0287, 8.01, 0.0014, 9.01, 0.0049, 10.00, 0.0144,
     11.01, 0.0055, 12.05, 0.0052, 13.01, 0.0011, 14.05, 0.0118, 15.07,
     0.0154, 16.12, 0.0028, 17.14, 0.0061, 18.25, 0.0007, 19.22, 0.0020,
     20.24, 0.0011, 21.27, 0.0029, 22.30, 0.0046, 23.34, 0.0049, 24.35,
     0.0004, 25.45, 0.0003, 26.47, 0.0007, 27.59, 0.0008, 28.16, 0.0009,
     29.12, 0.0002, 29.81, 0.0006, 30.81, 0.0009, 31.95, 0.0004, 33.00,
     0.0011, 34.12, 0.0005, 35.18, 0.0003, 36.30, 0.0008, 37.38, 0.0003,
     38.55, 0.0003, 39.64, 0.0006, 40.77, 0.0007, 41.52, 0.0006, 41.89,
     0.0006, 43.04, 0.0011, 43.60, 0.0009, 44.31, 0.0002, 45.68, 0.0002,
     46.56, 0.0003, 47.60, 0.0001, 48.83, 0.0006, 50.01, 0.0003, 51.27,
     0.0003, 56.04, 0.0005, 57.21, 0.0003, 58.56, 0.0004, 59.83, 0.0003,
     61.05, 0.0001, 62.20, 0.0001, 67.37, 0.0002, 76.53, 0.0001],
   [0.99, 0.0222, 1.99, 0.0678, 2.99, 0.0683, 4.00, 0.0191, 5.00, 0.0119,
     6.01, 0.0232, 6.98, 0.0336, 7.99, 0.0082, 9.01, 0.0201, 10.01, 0.0189,
     11.01, 0.0041, 12.01, 0.0053, 13.05, 0.0154, 14.04, 0.0159, 15.06,
     0.0092, 16.11, 0.0038, 17.12, 0.0014, 18.15, 0.0091, 19.16, 0.0006,
     20.30, 0.0012, 21.25, 0.0061, 22.28, 0.0099, 23.34, 0.0028, 24.38,
     0.0012, 25.43, 0.0016, 26.49, 0.0048, 27.55, 0.0025, 28.62, 0.0015,
     29.71, 0.0032, 30.78, 0.0077, 31.88, 0.0011, 32.97, 0.0007, 34.08,
     0.0006, 35.16, 0.0008, 36.28, 0.0004, 37.41, 0.0006, 38.54, 0.0005,
     39.62, 0.0002, 40.80, 0.0003, 41.93, 0.0001, 43.06, 0.0002, 44.21,
     0.0003, 45.38, 0.0002, 46.54, 0.0007, 47.78, 0.0003, 48.95, 0.0004,
     50.10, 0.0003, 51.37, 0.0002, 53.79, 0.0003, 56.20, 0.0001, 58.71,
     0.0002, 66.47, 0.0003],
   [1.01, 0.0241, 1.99, 0.1011, 2.98, 0.0938, 3.98, 0.0081, 4.99, 0.0062,
     5.99, 0.0291, 6.99, 0.0676, 7.59, 0.0004, 8.98, 0.0127, 9.99, 0.0112,
     10.99, 0.0142, 12.00, 0.0029, 13.02, 0.0071, 14.02, 0.0184, 15.03,
     0.0064, 16.07, 0.0010, 17.09, 0.0011, 18.11, 0.0010, 19.15, 0.0060,
     20.19, 0.0019, 21.24, 0.0025, 22.29, 0.0013, 23.31, 0.0050, 25.41,
     0.0030, 26.50, 0.0018, 27.53, 0.0006, 28.63, 0.0012, 29.66, 0.0013,
     30.77, 0.0020, 31.84, 0.0006, 34.04, 0.0001, 35.14, 0.0001, 36.32,
     0.0004, 37.41, 0.0007, 38.53, 0.0007, 39.67, 0.0009, 40.85, 0.0003,
     45.49, 0.0002, 46.65, 0.0001, 47.81, 0.0004, 49.01, 0.0002, 53.91,
     0.0002, 55.14, 0.0002, 57.69, 0.0002],
   [1.00, 0.0326, 2.00, 0.1066, 2.99, 0.1015, 4.00, 0.0210, 4.97, 0.0170,
     5.99, 0.0813, 6.98, 0.0820, 7.96, 0.0011, 8.99, 0.0248, 10.03, 0.0107,
     11.01, 0.0126, 12.01, 0.0027, 13.01, 0.0233, 14.04, 0.0151, 15.05,
     0.0071, 16.04, 0.0002, 17.10, 0.0061, 18.12, 0.0059, 19.15, 0.0087,
     20.23, 0.0005, 21.25, 0.0040, 22.30, 0.0032, 23.35, 0.0004, 24.40,
     0.0001, 25.45, 0.0030, 26.54, 0.0022, 27.60, 0.0003, 28.70, 0.0009,
     29.80, 0.0029, 30.85, 0.0006, 31.97, 0.0006, 34.19, 0.0004, 35.30,
     0.0003, 36.43, 0.0007, 37.56, 0.0005, 38.68, 0.0019, 39.88, 0.0013,
     41.00, 0.0003, 43.35, 0.0003, 44.51, 0.0002, 45.68, 0.0006, 46.93,
     0.0010, 48.11, 0.0006, 49.29, 0.0003, 55.58, 0.0002],
   [0.98, 0.0113, 1.99, 0.0967, 3.00, 0.0719, 3.98, 0.0345, 4.98, 0.0121,
     6.00, 0.0621, 7.00, 0.0137, 7.98, 0.0006, 9.01, 0.0314, 10.01, 0.0171,
     11.02, 0.0060, 12.03, 0.0024, 13.05, 0.0077, 14.07, 0.0040, 15.12,
     0.0032, 16.13, 0.0004, 17.15, 0.0011, 18.20, 0.0028, 19.18, 0.0003,
     20.26, 0.0003, 21.31, 0.0025, 22.35, 0.0021, 23.39, 0.0005, 25.55,
     0.0002, 26.62, 0.0014, 27.70, 0.0003, 28.78, 0.0005, 29.90, 0.0030,
     31.01, 0.0011, 32.12, 0.0005, 34.31, 0.0001, 35.50, 0.0002, 36.62,
     0.0002, 37.76, 0.0005, 38.85, 0.0002, 40.09, 0.0004, 43.60, 0.0001,
     44.73, 0.0002, 46.02, 0.0002, 47.25, 0.0004, 48.44, 0.0004],
   [0.99, 0.0156, 1.98, 0.0846, 2.98, 0.0178, 3.98, 0.0367, 4.98, 0.0448,
     5.98, 0.0113, 6.99, 0.0189, 8.00, 0.0011, 9.01, 0.0247, 10.02, 0.0089,
     11.01, 0.0184, 12.03, 0.0105, 13.00, 0.0039, 14.07, 0.0116, 15.09,
     0.0078, 16.13, 0.0008, 17.14, 0.0064, 18.19, 0.0029, 19.22, 0.0028,
     20.25, 0.0017, 21.32, 0.0043, 22.37, 0.0055, 23.42, 0.0034, 24.48,
     0.0004, 25.54, 0.0002, 26.61, 0.0017, 27.70, 0.0011, 28.80, 0.0002,
     29.89, 0.0019, 30.97, 0.0028, 32.09, 0.0007, 34.30, 0.0002, 35.44,
     0.0003, 36.55, 0.0001, 37.69, 0.0004, 38.93, 0.0002, 40.05, 0.0005,
     41.20, 0.0005, 42.37, 0.0002, 43.54, 0.0003, 44.73, 0.0001, 45.95,
     0.0002, 47.16, 0.0001, 48.43, 0.0005, 49.65, 0.0004, 55.90, 0.0002,
     59.81, 0.0004], 
   [1.01, 0.0280, 2.00, 0.0708, 2.99, 0.0182, 3.99, 0.0248, 4.98, 0.0245,
     5.98, 0.0279, 6.98, 0.0437, 7.99, 0.0065, 8.99, 0.0299, 10.00, 0.0073,
     10.99, 0.0011, 12.03, 0.0122, 13.03, 0.0028, 14.08, 0.0044, 15.11,
     0.0097, 16.15, 0.0010, 17.17, 0.0025, 18.19, 0.0017, 19.24, 0.0008,
     20.28, 0.0040, 21.32, 0.0024, 22.38, 0.0008, 23.46, 0.0032, 24.52,
     0.0010, 25.59, 0.0008, 26.68, 0.0009, 27.76, 0.0012, 28.88, 0.0003,
     29.95, 0.0005, 31.05, 0.0017, 32.14, 0.0002, 33.29, 0.0003, 37.88,
     0.0002, 39.03, 0.0002, 40.19, 0.0004, 41.37, 0.0003, 43.74, 0.0002,
     46.20, 0.0001, 48.68, 0.0001, 49.93, 0.0001, 51.19, 0.0002],
   [1.00, 0.0225, 1.99, 0.0921, 2.98, 0.0933, 3.99, 0.0365, 4.99, 0.0100,
     5.98, 0.0213, 6.98, 0.0049, 7.98, 0.0041, 8.98, 0.0090, 9.99, 0.0068,
     11.01, 0.0040, 12.03, 0.0086, 13.02, 0.0015, 14.04, 0.0071, 15.09,
     0.0082, 16.14, 0.0011, 17.15, 0.0014, 18.18, 0.0010, 19.26, 0.0013,
     20.26, 0.0005, 21.33, 0.0006, 22.36, 0.0011, 23.46, 0.0016, 24.52,
     0.0004, 25.59, 0.0002, 26.70, 0.0006, 27.78, 0.0007, 28.87, 0.0002,
     30.03, 0.0008, 31.14, 0.0010, 32.24, 0.0006, 33.37, 0.0002, 35.67,
     0.0003, 37.99, 0.0004, 39.17, 0.0004, 40.35, 0.0005, 41.53, 0.0001,
     46.42, 0.0001],
   [1.00, 0.0465, 1.99, 0.0976, 2.98, 0.0678, 4.00, 0.0727, 4.99, 0.0305,
     5.98, 0.0210, 6.98, 0.0227, 8.00, 0.0085, 9.01, 0.0183, 10.02, 0.0258,
     11.05, 0.0003, 12.06, 0.0061, 13.05, 0.0021, 14.10, 0.0089, 15.12,
     0.0077, 16.16, 0.0016, 17.21, 0.0061, 18.23, 0.0011, 19.29, 0.0031,
     20.36, 0.0031, 21.41, 0.0007, 22.48, 0.0013, 23.55, 0.0020, 24.64,
     0.0004, 25.74, 0.0005, 26.81, 0.0006, 27.95, 0.0006, 29.03, 0.0001,
     30.22, 0.0010, 31.30, 0.0004, 32.48, 0.0001, 33.60, 0.0002, 38.30,
     0.0003],
   [1.00, 0.0674, 1.99, 0.0841, 2.98, 0.0920, 3.99, 0.0328, 4.99, 0.0368,
     5.98, 0.0206, 6.99, 0.0246, 8.01, 0.0048, 9.01, 0.0218, 10.03, 0.0155,
     11.05, 0.0048, 12.06, 0.0077, 13.00, 0.0020, 14.10, 0.0083, 15.15,
     0.0084, 16.18, 0.0015, 17.22, 0.0039, 18.27, 0.0032, 19.34, 0.0026,
     20.40, 0.0012, 21.47, 0.0009, 22.54, 0.0008, 23.62, 0.0016, 24.71,
     0.0005, 25.82, 0.0004, 26.91, 0.0002, 28.03, 0.0008, 29.17, 0.0002,
     30.32, 0.0028, 31.45, 0.0004, 32.61, 0.0005, 33.77, 0.0001, 36.14,
     0.0003, 37.32, 0.0002, 38.54, 0.0005, 39.75, 0.0002, 42.23, 0.0002,
     48.65, 0.0001], 
   [1.01, 0.0423, 1.99, 0.0240, 2.98, 0.0517, 4.00, 0.0493, 5.00, 0.0324,
     6.00, 0.0094, 6.99, 0.0449, 7.99, 0.0050, 9.00, 0.0197, 10.03, 0.0132,
     11.03, 0.0009, 12.07, 0.0017, 13.08, 0.0023, 14.12, 0.0094, 15.16,
     0.0071, 16.21, 0.0020, 17.25, 0.0005, 18.30, 0.0027, 19.04, 0.0004,
     20.43, 0.0022, 21.51, 0.0002, 22.59, 0.0006, 23.72, 0.0018, 24.80,
     0.0002, 25.88, 0.0002, 27.03, 0.0002, 28.09, 0.0006, 29.31, 0.0002,
     30.46, 0.0004, 31.61, 0.0007, 32.78, 0.0005, 33.95, 0.0001, 36.34,
     0.0002, 37.56, 0.0001, 38.80, 0.0001, 40.02, 0.0001, 44.14, 0.0001],
   [1.00, 0.0669, 1.99, 0.0909, 2.99, 0.0410, 3.98, 0.0292, 4.98, 0.0259,
     5.98, 0.0148, 6.98, 0.0319, 7.99, 0.0076, 9.01, 0.0056, 10.02, 0.0206,
     11.04, 0.0032, 12.05, 0.0085, 13.08, 0.0040, 14.12, 0.0037, 15.16,
     0.0030, 16.20, 0.0013, 17.24, 0.0021, 18.30, 0.0010, 19.36, 0.0015,
     20.44, 0.0013, 21.50, 0.0009, 22.60, 0.0015, 23.69, 0.0014, 24.80,
     0.0006, 25.87, 0.0002, 27.02, 0.0006, 28.12, 0.0002, 29.28, 0.0003,
     30.43, 0.0002, 31.59, 0.0007, 32.79, 0.0001, 35.14, 0.0001, 37.57,
     0.0001, 40.03, 0.0002, 41.28, 0.0004, 44.10, 0.0001],
   [0.99, 0.0421, 1.99, 0.1541, 2.98, 0.0596, 3.98, 0.0309, 4.98, 0.0301,
     5.99, 0.0103, 7.00, 0.0240, 8.01, 0.0073, 9.01, 0.0222, 10.04, 0.0140,
     11.05, 0.0033, 12.08, 0.0045, 13.13, 0.0009, 14.13, 0.0015, 15.21,
     0.0026, 16.24, 0.0003, 17.30, 0.0004, 18.35, 0.0010, 19.39, 0.0003,
     20.50, 0.0015, 21.57, 0.0003, 22.68, 0.0011, 23.80, 0.0005, 24.90,
     0.0008, 26.02, 0.0002, 27.16, 0.0001, 28.30, 0.0006, 29.48, 0.0002,
     31.81, 0.0005, 33.00, 0.0003, 34.21, 0.0001, 37.89, 0.0001],
   [0.99, 0.0389, 2.00, 0.2095, 3.00, 0.0835, 3.99, 0.0289, 5.00, 0.0578,
     5.99, 0.0363, 7.01, 0.0387, 8.01, 0.0056, 9.04, 0.0173, 10.05, 0.0175,
     11.08, 0.0053, 12.10, 0.0056, 13.15, 0.0064, 14.19, 0.0036, 15.22,
     0.0019, 16.29, 0.0010, 17.36, 0.0017, 18.43, 0.0018, 19.51, 0.0004,
     20.60, 0.0011, 21.70, 0.0003, 22.82, 0.0003, 23.95, 0.0001, 25.05,
     0.0004, 26.17, 0.0001, 28.50, 0.0003, 29.68, 0.0001, 32.07, 0.0003,
     33.28, 0.0004, 34.52, 0.0001], 
   [1.00, 0.1238, 1.99, 0.2270, 3.00, 0.0102, 3.99, 0.0181, 4.98, 0.0415,
     6.00, 0.0165, 7.01, 0.0314, 8.02, 0.0148, 9.04, 0.0203, 10.05, 0.0088,
     11.07, 0.0062, 12.11, 0.0070, 13.14, 0.0054, 14.19, 0.0028, 15.24,
     0.0044, 16.30, 0.0029, 17.38, 0.0009, 18.45, 0.0026, 19.56, 0.0003,
     20.65, 0.0025, 21.74, 0.0014, 22.87, 0.0013, 23.99, 0.0007, 25.15,
     0.0002, 27.46, 0.0004, 28.39, 0.0006, 28.65, 0.0004, 29.85, 0.0001,
     31.05, 0.0002, 32.27, 0.0003, 33.52, 0.0002, 34.76, 0.0003],
   [1.00, 0.1054, 2.00, 0.2598, 2.99, 0.0369, 3.98, 0.0523, 4.99, 0.0020,
     5.99, 0.0051, 7.00, 0.0268, 8.01, 0.0027, 9.04, 0.0029, 10.05, 0.0081,
     11.08, 0.0047, 12.12, 0.0051, 13.16, 0.0091, 14.19, 0.0015, 15.27,
     0.0030, 16.34, 0.0017, 17.42, 0.0006, 18.51, 0.0003, 19.61, 0.0007,
     20.72, 0.0003, 21.84, 0.0001, 22.99, 0.0010, 24.13, 0.0001, 28.44,
     0.0001, 30.09, 0.0001],
   [0.99, 0.0919, 2.00, 0.0418, 2.99, 0.0498, 3.99, 0.0135, 4.99, 0.0026,
     6.00, 0.0155, 7.01, 0.0340, 8.02, 0.0033, 9.04, 0.0218, 10.08, 0.0084,
     11.11, 0.0057, 12.15, 0.0051, 13.21, 0.0043, 14.25, 0.0015, 15.31,
     0.0023, 16.40, 0.0008, 17.48, 0.0004, 18.59, 0.0016, 19.71, 0.0010,
     20.84, 0.0018, 21.98, 0.0002, 23.11, 0.0013, 24.26, 0.0003, 26.67,
     0.0002, 29.12, 0.0002, 30.37, 0.0002, 31.62, 0.0003, 32.92, 0.0001],
   [0.99, 0.1174, 1.99, 0.1126, 2.99, 0.0370, 3.99, 0.0159, 5.01, 0.0472,
     6.01, 0.0091, 7.03, 0.0211, 8.05, 0.0015, 9.07, 0.0098, 10.11, 0.0038,
     11.15, 0.0042, 12.20, 0.0018, 13.24, 0.0041, 14.32, 0.0033, 15.41,
     0.0052, 16.49, 0.0001, 17.61, 0.0004, 18.71, 0.0004, 19.84, 0.0004,
     20.99, 0.0002, 22.14, 0.0006, 23.31, 0.0006, 24.50, 0.0004, 25.70,
     0.0002, 28.09, 0.0002, 28.66, 0.0002, 32.00, 0.0001],
   [1.00, 0.1085, 2.00, 0.1400, 2.99, 0.0173, 3.99, 0.0229, 5.00, 0.0272,
     6.02, 0.0077, 7.03, 0.0069, 8.04, 0.0017, 9.08, 0.0045, 10.10, 0.0030,
     11.15, 0.0040, 12.20, 0.0007, 13.25, 0.0019, 14.32, 0.0008, 15.42,
     0.0024, 16.50, 0.0002, 17.59, 0.0005, 18.71, 0.0003, 19.83, 0.0002,
     20.98, 0.0005, 23.29, 0.0008],
   [1.00, 0.0985, 2.00, 0.1440, 2.99, 0.0364, 3.99, 0.0425, 5.00, 0.0190,
     6.01, 0.0089, 7.03, 0.0278, 8.04, 0.0006, 9.07, 0.0083, 10.10, 0.0021,
     11.14, 0.0050, 12.18, 0.0005, 13.26, 0.0036, 14.33, 0.0005, 15.41,
     0.0026, 17.62, 0.0004, 18.75, 0.0004, 19.89, 0.0003, 21.04, 0.0012,
     22.21, 0.0002, 23.38, 0.0004, 27.04, 0.0001],
   [0.99, 0.1273, 2.00, 0.1311, 2.99, 0.0120, 4.00, 0.0099, 5.00, 0.0235,
     6.02, 0.0068, 7.03, 0.0162, 8.06, 0.0009, 9.08, 0.0083, 10.12, 0.0014,
     11.17, 0.0050, 12.24, 0.0010, 13.29, 0.0013, 14.39, 0.0022, 15.48,
     0.0011, 16.59, 0.0002, 17.70, 0.0003, 18.84, 0.0010, 20.00, 0.0003,
     21.17, 0.0003, 23.56, 0.0004, 28.79, 0.0003], 
   [1.00, 0.1018, 2.00, 0.1486, 3.00, 0.0165, 4.00, 0.0186, 5.01, 0.0194,
     6.02, 0.0045, 7.04, 0.0083, 8.06, 0.0012, 9.10, 0.0066, 10.15, 0.0009,
     11.19, 0.0008, 12.26, 0.0011, 13.34, 0.0028, 14.45, 0.0006, 15.53,
     0.0009, 16.66, 0.0002, 17.79, 0.0006, 18.94, 0.0005, 20.11, 0.0003,
     21.29, 0.0005, 22.49, 0.0003, 23.73, 0.0005, 26.22, 0.0001, 27.52,
     0.0001, 28.88, 0.0002],
   [1.00, 0.1889, 1.99, 0.1822, 3.00, 0.0363, 4.00, 0.0047, 5.01, 0.0202,
     6.03, 0.0053, 7.05, 0.0114, 8.01, 0.0002, 9.13, 0.0048, 10.17, 0.0010,
     11.23, 0.0033, 12.30, 0.0010, 13.38, 0.0006, 14.50, 0.0002, 15.62,
     0.0010, 20.27, 0.0001, 21.47, 0.0001],
   [1.00, 0.0522, 1.99, 0.0763, 2.99, 0.0404, 4.00, 0.0139, 5.01, 0.0185,
     6.01, 0.0021, 7.06, 0.0045, 8.09, 0.0002, 9.11, 0.0003, 10.17, 0.0006,
     11.25, 0.0004, 12.32, 0.0005, 13.40, 0.0003, 14.53, 0.0003, 15.65,
     0.0007, 16.80, 0.0001, 17.95, 0.0002, 19.14, 0.0006, 20.34, 0.0002,
     21.56, 0.0003],
   [0.99, 0.1821, 1.99, 0.0773, 3.00, 0.0125, 4.01, 0.0065, 5.01, 0.0202,
     6.03, 0.0071, 7.05, 0.0090, 8.08, 0.0006, 9.13, 0.0008, 10.18, 0.0013,
     11.25, 0.0010, 12.33, 0.0012, 13.42, 0.0006, 14.54, 0.0005, 15.65,
     0.0004, 17.97, 0.0002, 19.15, 0.0001],
   [1.00, 0.1868, 2.00, 0.0951, 3.00, 0.0147, 4.01, 0.0134, 5.02, 0.0184,
     6.04, 0.0132, 7.06, 0.0011, 8.11, 0.0008, 9.15, 0.0010, 10.22, 0.0012,
     11.30, 0.0011, 12.40, 0.0003, 13.11, 0.0004, 13.49, 0.0002, 14.62,
     0.0003, 15.77, 0.0001],
   [1.00, 0.1933, 2.00, 0.0714, 3.00, 0.0373, 4.00, 0.0108, 5.02, 0.0094,
     6.02, 0.0010, 7.07, 0.0022, 8.11, 0.0002, 9.16, 0.0065, 10.23, 0.0015,
     11.31, 0.0023, 12.40, 0.0003, 13.53, 0.0014, 14.66, 0.0002, 15.81,
     0.0011, 18.20, 0.0002, 19.41, 0.0001],
   [0.99, 0.2113, 1.99, 0.0877, 3.00, 0.0492, 4.01, 0.0094, 5.02, 0.0144,
     6.04, 0.0103, 7.07, 0.0117, 8.12, 0.0006, 9.19, 0.0019, 10.25, 0.0007,
     11.35, 0.0017, 12.45, 0.0010, 13.58, 0.0003, 14.74, 0.0003, 15.91,
     0.0003, 19.57, 0.0002],
   [0.99, 0.2455, 1.99, 0.0161, 3.00, 0.0215, 4.01, 0.0036, 5.03, 0.0049,
     6.04, 0.0012, 7.09, 0.0036, 8.14, 0.0011, 9.21, 0.0009, 10.30, 0.0001,
     11.40, 0.0012, 12.50, 0.0001, 13.66, 0.0005, 14.84, 0.0001], 
   [1.00, 0.1132, 2.00, 0.0252, 3.00, 0.0292, 4.01, 0.0136, 5.03, 0.0045,
     6.06, 0.0022, 7.11, 0.0101, 8.17, 0.0004, 9.23, 0.0010, 10.33, 0.0012,
     11.44, 0.0013, 12.58, 0.0011, 13.75, 0.0002, 14.93, 0.0005, 16.14, 0.0002],
   [1.00, 0.1655, 2.00, 0.0445, 3.00, 0.0120, 4.00, 0.0038, 5.02, 0.0015,
     6.07, 0.0038, 7.11, 0.0003, 8.19, 0.0002, 9.25, 0.0010, 10.36, 0.0011,
     11.48, 0.0005, 12.63, 0.0002, 13.79, 0.0003, 16.24, 0.0002],
   [0.99, 0.3637, 1.99, 0.0259, 3.01, 0.0038, 4.01, 0.0057, 5.03, 0.0040,
     6.07, 0.0067, 7.12, 0.0014, 8.19, 0.0004, 9.27, 0.0003, 10.38, 0.0002,
     12.67, 0.0001],
   [1.00, 0.1193, 2.00, 0.0230, 3.00, 0.0104, 4.01, 0.0084, 5.04, 0.0047,
     6.08, 0.0035, 7.13, 0.0041, 8.20, 0.0002, 9.29, 0.0005, 10.40, 0.0005,
     11.53, 0.0003, 12.70, 0.0002, 13.91, 0.0002],
   [1.00, 0.0752, 2.00, 0.0497, 3.00, 0.0074, 4.02, 0.0076, 5.05, 0.0053,
     6.09, 0.0043, 7.15, 0.0024, 8.22, 0.0001, 9.32, 0.0006, 10.45, 0.0002,
     11.58, 0.0001, 12.78, 0.0001, 15.22, 0.0001],
   [1.00, 0.2388, 2.00, 0.0629, 3.01, 0.0159, 4.04, 0.0063, 5.07, 0.0051,
     6.12, 0.0045, 7.19, 0.0026, 8.29, 0.0015, 9.43, 0.0001, 11.75, 0.0002],
   [1.00, 0.1919, 2.01, 0.0116, 3.01, 0.0031, 4.03, 0.0090, 5.07, 0.0061,
     6.13, 0.0036, 7.19, 0.0013, 8.30, 0.0016, 9.13, 0.0001, 10.59, 0.0002,
     11.78, 0.0002],
   [1.00, 0.1296, 2.00, 0.0135, 3.01, 0.0041, 4.04, 0.0045, 5.09, 0.0028,
     6.14, 0.0046, 7.23, 0.0007, 8.32, 0.0007, 9.50, 0.0001],
   [1.00, 0.0692, 2.00, 0.0209, 3.02, 0.0025, 4.05, 0.0030, 5.09, 0.0047,
     6.17, 0.0022, 7.25, 0.0015, 8.36, 0.0015, 9.53, 0.0010, 10.69, 0.0001,
     13.40, 0.0001],
   [1.00, 0.1715, 2.00, 0.0142, 3.01, 0.0024, 4.03, 0.0015, 5.07, 0.0017,
     6.13, 0.0018, 7.22, 0.0009, 8.33, 0.0014, 9.51, 0.0007, 10.69, 0.0002], 
   [1.00, 0.1555, 2.01, 0.0148, 3.02, 0.0007, 4.06, 0.0006, 5.10, 0.0005,
     6.16, 0.0008, 7.26, 0.0009, 8.39, 0.0008, 9.58, 0.0002], 
   [1.00, 0.1357, 2.00, 0.0116, 3.02, 0.0026, 4.04, 0.0009, 5.09, 0.0004,
     6.17, 0.0005, 7.27, 0.0002, 8.40, 0.0001], 
   [1.00, 0.2185, 2.01, 0.0087, 3.03, 0.0018, 4.06, 0.0025, 5.11, 0.0020,
     6.20, 0.0012, 7.32, 0.0005, 8.46, 0.0001, 9.66, 0.0003], 
   [1.00, 0.2735, 2.00, 0.0038, 3.02, 0.0008, 4.06, 0.0012, 5.12, 0.0008,
     6.22, 0.0011, 7.35, 0.0003, 8.50, 0.0002], 
   [1.00, 0.1441, 1.99, 0.0062, 3.01, 0.0023, 4.05, 0.0011, 5.11, 0.0012,
     6.20, 0.0003, 7.33, 0.0004, 8.50, 0.0001], 
   [1.00, 0.0726, 2.01, 0.0293, 3.03, 0.0022, 5.14, 0.0005, 6.26, 0.0011,
     7.41, 0.0002, 8.63, 0.0002], 
   [1.00, 0.0516, 2.00, 0.0104, 3.02, 0.0029, 5.15, 0.0002, 6.27, 0.0001],
   [1.00, 0.0329, 2.00, 0.0033, 3.03, 0.0013, 4.10, 0.0005, 5.19,
     0.0004, 6.32, 0.0002],
   [1.00, 0.0179, 1.99, 0.0012, 3.04, 0.0005, 4.10, 0.0017, 5.20, 0.0005,
     6.35, 0.0001], 
   [1.00, 0.0334, 2.01, 0.0033, 3.04, 0.0011, 4.13, 0.0003, 5.22, 0.0003],
   [0.99, 0.0161, 2.01, 0.0100, 3.04, 0.0020, 4.13, 0.0003],
   [1.00, 0.0475, 1.99, 0.0045, 3.03, 0.0035, 4.12, 0.0011],
   [1.00, 0.0593, 2.00, 0.0014, 4.17, 0.0002],
   [1.00, 0.0249, 2.01, 0.0016],
   [1.00, 0.0242, 2.00, 0.0038, 4.19, 0.0002],
   [1.00, 0.0170, 2.02, 0.0030],
   [1.00, 0.0381, 2.00, 0.0017, 3.09, 0.0002],
   [1.00, 0.0141, 2.03, 0.0005, 3.11, 0.0003, 4.26, 0.0001],
   [1.00, 0.0122, 2.03, 0.0024],
   [1.00, 0.0107, 2.07, 0.0007, 3.12, 0.0004],
   [1.00, 0.0250, 2.02, 0.0026, 3.15, 0.0002],
   [1.01, 0.0092],
   [1.01, 0.0102, 2.09, 0.0005],
   [1.00, 0.0080, 2.00, 0.0005, 3.19, 0.0001],
   [1.01, 0.0298, 2.01, 0.0005]]

def lbj_piano(start, dur, freq, amp, *args)
  pfreq, degree, distance, reverb_amount = nil
  optkey(args, binding,
         [:pfreq, freq],
         [:degree, 45.0],
         [:distance, 1.0],
         [:reverb_amount, 0.0])
  get_piano_partials = lambda do |frq|
    Piano_Spectra[(12 * (log(frq / 32.703) / log(2))).round]
  end
  make_piano_ampfun = lambda do |dr|
    release_amp = db2linear($clm_db_drop_per_second * dr)
    attack_time = $clm_piano_attack_duration * 100.0 / dr
    [0, 0, attack_time / 4, 1.0, attack_time, 1.0, 100, release_amp]
  end
  # This thing sounds pretty good down low, below middle c or so.
  # Unfortunately, there are some tens of partials down there and
  # we're using exponential envelopes.  You're going to wait for a
  # long long time just to hear a single low note.  The high notes
  # sound pretty rotten--they just don't sparkle; I have a feeling
  # that this is due to the low amplitude of the original data, and
  # the lack of mechanical noise.
  #
  # The only thing you can do to alter the sound of a piano note is to
  # set the pfreq parameter.  Pfreq is used to look up the partials.
  # By default, it's set to the requested frequency.  Setting it to a
  # neighboring freq is useful when you're repeating notes.  Note that
  # there's no nyquist detection; a high freq with a low pfreq, will
  # give you fold over (hmmm...maybe I can get those high notes to
  # sparkle after all).
  partials = normalize_partials(get_piano_partials.call(pfreq))
  newdur = dur + $clm_piano_attack_duration + $clm_piano_release_duration
  env1dur = newdur - $clm_piano_release_duration
  env1samples = (env1dur * @srate).floor
  siz = (partials.length / 2).floor
  oscils = Array.new(siz)
  alist = make_vct(siz)
  ampfun1 = make_piano_ampfun.call(env1dur)
  ampenv1 = make_env(:envelope, ampfun1, :scaler, amp,
                     :duration, env1dur, :base, 10000.0)
  releaseamp = ampfun1.last
  ampenv2 = make_env(:envelope, [0, 1, 100, 0], :scaler, amp * releaseamp,
                     :duration, env1dur, :base, 1.0)
  sktr = j = 0
  0.step(partials.length - 1, 2) do |i|
    alist[j] = partials[i + 1]
    oscils[j] = make_oscil(:frequency, partials[i] * freq)
    j += 1
  end
  run_instrument(start, newdur,
                 :degree, degree,
                 :distance, distance,
                 :reverb_amount, reverb_amount) do
    sktr += 1
    let(0.0) do |sum|
      oscils.each_with_index do |osc, i|
        sum = sum + oscil(osc) * alist[i]
      end
      sum * env(((sktr > env1samples) ? ampenv2 : ampenv1))
    end
  end
end

def lbj_piano_test(start = 0.0, dur = 1.0)
  lbj_piano(start, dur, 440.0, 0.5)
  $now = start + dur + 0.24 + 0.2
end

# RESFLT
def resflt(start, dur, driver,
           ranfreq, noiamp, noifun, cosamp, cosfreq1, cosfreq0, cosnum,
           ampcosfun, freqcosfun,
           frq1, r1, g1, frq2, r2, g2, frq3, r3, g3, *args)
  degree, distance, reverb_amount = nil
  optkey(args, binding,
         [:degree, 0.0],
         [:distance, 1.0],
         [:reverb_amount, 0.005])
  # driver=0 -- use sum of cosines to drive the filter,
  # driver=1 -- use white noise
  # if noise used, ranfreq=frequency of random number generator,
  #                noiamp=amplitude thereof,
  #                noifun=amplitude envelope on white noise
  # if sum-of-cosines (i.e. a band-limited pulse train),
  #                cosamp=amplitude of pulse train,
  #                cosfreq1=top frequency (given freqcosfun)
  #                  (i.e. pulse frequency)
  #                cosfreq0=bottom frequency,
  #                cosnum=number of cosines in the pulse,
  #                ampcosfun=amplitude envelope on pulse train
  #                freqcosfun=frequency envelope on pulse train
  # There are then 3 resonators, centered at frq1, frq2, frq3,
  # with pole-radius r1, r2, and r3 respectively, and
  # with gains of g1, g2, and g3.
  f1 = make_two_pole(frq1, r1)
  f2 = make_two_pole(frq2, r2)
  f3 = make_two_pole(frq3, r3)
  with_noise = (driver == 1)
  frqf = if with_noise
           nil
         else
           make_env(:envelope, freqcosfun, :duration, dur,
                    :scaler, hz2radians(cosfreq1 - cosfreq0))
         end
  ampf = if with_noise
           make_env(:envelope, noifun, :scaler, noiamp, :duration, dur)
         else
           make_env(:envelope, ampcosfun, :scaler, cosamp, :duration, dur)
         end
  rn = if with_noise
         make_rand(:frequency, ranfreq)
       else
         nil
       end
  cn = if with_noise
         nil
       else
         make_sum_of_cosines(:frequency, cosfreq0, :cosines, cosnum)
       end
  if with_noise
    run_instrument(start, dur,
                   :distance, distance,
                   :degree, degree,
                   :reverb_amount, reverb_amount) do
      input1 = env(ampf) * rand(rn)
      two_pole(f1, input1 * g1) +
        two_pole(f2, input1 * g2) +
        two_pole(f3, input1 * g3)
    end
  else
    run_instrument(start, dur,
                   :distance, distance,
                   :degree, degree,
                   :reverb_amount, reverb_amount) do
      input1 = env(ampf) * sum_of_cosines(cn, env(frqf))
      two_pole(f1, input1 * g1) +
        two_pole(f2, input1 * g2) +
        two_pole(f3, input1 * g3)
    end
  end
end

def resflt_test(start = 0.0, dur = 1.0)
  $now = start
  resflt($now, dur, 0, 0, 0, nil,
         0.1, 200, 230, 10, [0, 0, 50, 1, 100, 0], [0, 0, 100, 1],
         500, 0.995, 0.1, 1000, 0.995, 0.1, 2000, 0.995, 0.1)
  $now += dur + 0.2
  resflt($now, dur, 1, 10000, 0.01, [0, 0, 50, 1, 100, 0],
         0, 0, 0, 0, nil, nil,
         500, 0.995, 0.1, 1000, 0.995, 0.1, 2000, 0.995, 0.1)
  $now += dur + 0.2
end

# SCRATCH
def scratch(start, file, src_ratio, turntable)
  assert_type(File.exist?(file), file, 1, "an existing file")
  f = make_file2sample(file)
  turn_i = 1
  turns = turntable.length
  cur_sample = seconds2samples(turntable[0])
  turn_sample = seconds2samples(turntable[turn_i])
  rd = make_src(:srate, src_ratio)
  forwards = (src_ratio > 0.0)
  set_mus_increment(rd, -src_ratio) if forwards and turn_sample < cur_sample
  turning = 0
  last_val = last_val2 = 0.0
  run_instrument(start, ws_duration(file)) do
    break if turn_i >= turns
    val = src(rd, 0.0, lambda do |dir|
                inval = file2sample(f, cur_sample)
                cur_sample += dir
                inval
              end)
    if turning.zero?
      # we past turn point going forwards
      if forwards and cur_sample >= turn_sample
        turning = 1
        # we past turn point going backwards
      elsif (not forwards) and cur_sample <= turn_sample
        turning = -1
      end
    else
      # wait for an inflection...
      if (last_val2 <= last_val and last_val >= val) or
          (last_val2 >= last_val and last_val <= val)
        turn_i += 1
        if turn_i < turns
          turn_sample = seconds2samples(turntable[turn_i])
          forwards = (not forwards)
          set_mus_increment(rd, -mus_increment(rd))
        end
        turning = 0
      end
    end
    last_val2, last_val = last_val, val
    val
  end
  mus_close(f)
end

def scratch_test(start = 0.0, dur = 1.0)
  scratch(start, "fyow.snd", [dur, 1.5].min, [0.0, 0.5, 0.25, 1.0])
  $now = start + mus_sound_duration("fyow.snd") + 0.2
end

# PINS
#
# spectral modeling (SMS)
def pins(start, dur, file, amp, *args)
  assert_type(File.exist?(file), file, 2, "an existing file")
  transposition, time_scaler, fftsize, highest_bin, max_peaks, attack = nil
  optkey(args, binding,
         [:transposition, 1.0], # this can be used to transpose the sound
         [:time_scaler, 1.0],   # this can make things happen faster
                                # (< 1.0)/slower (> 1.0) in the output
         [:fftsize, 256],       # should be a power of 2
                                # at 22050 srate, this is ok for
                                # sounds above 300Hz or so, below that
                                # you need 512 or 1024, at 44100,
                                # probably best to double these sizes
                                # -- it takes some searching
                                # sometimes.
         [:highest_bin, 128],   # how high in fft data should we search for pks
         [:max_peaks, 16],      # how many spectral peaks to track at the max
         :attack)               # whether to use original attack via time domain
                                # splice do the sliding fft shuffle,
                                # translate to polar coordinates, find
                                # spectral peaks, match with current,
                                # do some interesting transformation,
                                # resynthesize using oscils All the
                                # envelopes are created on the fly.
                                # max-peaks is how many of these peaks
                                # we are willing to track at any given
                                # time.
  fil = make_file2sample(file)
  file_duration = ws_duration(file)
  fdr = make_vct(fftsize)
  fdi = make_vct(fftsize)
  window = make_fft_window(Blackman2_window, fftsize)
  fftamps = make_vct(fftsize)
  max_oscils = 2 * max_peaks
  current_peak_freqs = make_vct(max_oscils)
  last_peak_freqs = make_vct(max_oscils)
  current_peak_amps = make_vct(max_oscils)
  last_peak_amps = make_vct(max_oscils)
  peak_amps = make_vct(max_peaks)
  peak_freqs = make_vct(max_peaks)
  resynth_oscils = make_array(max_oscils) do
    make_oscil(:frequency, 0)
  end
  # run-time generated amplitude and frequency envelopes
  amps = make_vct(max_oscils)
  rates = make_vct(max_oscils)
  freqs = make_vct(max_oscils)
  sweeps = make_vct(max_oscils)
  lowest_magnitude = 0.001
  hop = (fftsize / 4).floor
  outhop = (time_scaler * hop).floor
  ifreq = 1.0 / outhop
  ihifreq = hz2radians(ifreq)
  # integrate Blackman-Harris window = .42323*window
  fftscale = 1.0 / (fftsize * 0.42323)
  # width and shift by fftsize
  fft_mag = @srate / fftsize
  furthest_away_accepted = 0.1
  filptr = 0
  cur_oscils = max_oscils
  ramped = (attack or 0)
  splice_attack = attack.kind_of?(Numeric)
  attack_size = (attack or 1)
  ramp_ind = 0
  ramped_attack = make_vct(attack_size)
  if (dur / time_scaler) > file_duration
    error("%s is %1.3f seconds long, \
but we'll need %1.3f seconds of data for this note",
          file, file_duration, dur / time_scaler)
  end
  trigger = outhop
  vct_scale!(window, fftscale)
  run_instrument(start, dur) do
    if splice_attack
      ramp = 1.0 / attack_size
      # my experience in translating SMS, and rumor via Greg Sandell
      # leads me to believe that there is in fact no way to model some
      # attacks successfully in this manner, so this block simply
      # splices the original attack on to the rest of the note.
      # "attack" is the number of samples to include bodily.
      out_val = amp * file2sample(fil, filptr)
      filptr += 1
      if filptr > attack_size
        mult = 1.0
        attack_size.times do |j|
          ramped_attack[j] = mult * file2sample(fil, filptr + j)
          mult -= ramp
        end
        splice_attack = false
      end
      # if out_val
      out_val
    else
      if trigger >= outhop
        peaks = 0
        # get next block of data and apply window to it
        trigger = 0
        fftsize.times do |j|
          fdr[j] = window[j] * file2sample(fil, filptr)
          filptr += 1
        end
        vct_fill!(fdi, 0.0)
        filptr -= fftsize - hop
        # get the fft
        mus_fft(fdr, fdi, fftsize, 1)
        # change to polar coordinates (ignoring phases)
        highest_bin.times do |j|
          # no need to paw through the upper half
          # (so (<= highest-bin (floor fft-size 2)))
          x = fdr[j]
          y = fdi[j]
          fftamps[j] = 2.0 * sqrt(x * x + y * y)
        end
        max_oscils.times do |j|
          last_peak_freqs[j] = current_peak_freqs[j]
          last_peak_amps[j] = current_peak_amps[j]
          current_peak_amps[j] = 0.0
        end
        vct_fill!(peak_amps, 0.0)
        ra = fftamps[0]
        la = ca = 0.0
        # search for current peaks following Xavier Serra's recommendations in
        # "A System for Sound Analysis/Transformation/Synthesis 
        #      Based on a Deterministic Plus Stochastic Decomposition"
        highest_bin.times do |j|
          la, ca, ra = ca, ra, fftamps[j]
          if ca > lowest_magnitude and ca > ra and ca > la
            # found a local maximum above the current threshold
            # (its bin number is j-1)
            logla = log10(la)
            logca = log10(ca)
            logra = log10(ra)
            offset = (0.5 * (logla - logra)) / (logla + -2 * logca + logra)
            amp_1 = 10.0 ** (logca - (0.25 * (logla - logra) * offset))
            freq = fft_mag * (j + offset - 1)
            if peaks == max_peaks
              # gotta either flush this peak,
              # or find current lowest and flush him
              minp = 0
              minpeak = peak_amps[0]
              1.upto(max_peaks - 1) do |k|
                if peak_amps[k] < minpeak
                  minp = k
                  minpeak = peak_amps[k]
                end
              end
              if amp_1 > minpeak
                peak_freqs[minp] = freq
                peak_amps[minp] = amp_1
              end
            else
              peak_freqs[peaks] = freq
              peak_amps[peaks] = amp_1
              peaks += 1
            end
          end
        end
        # now we have the current peaks -- match them to the previous
        # set and do something interesting with the result the end
        # results are reflected in the updated values in the rates and
        # sweeps arrays.  search for fits between last and current,
        # set rates/sweeps for those found try to go by largest amp
        # first
        peaks.times do |j|
          maxp = 0
          maxpk = peak_amps[0]
          1.upto(max_peaks - 1) do |k|
            if peak_amps[k] > maxpk
              maxp = k
              maxpk = peak_amps[k]
            end
          end
          # now maxp points to next largest unmatched peak
          if maxpk > 0.0
            closestp = -1
            closestamp = 10.0
            current_freq = peak_freqs[maxp]
            icf = 1.0 / current_freq
            max_peaks.times do |k|
              if last_peak_amps[k] > 0.0
                closeness = icf * (last_peak_freqs[k] - current_freq).abs
                if closeness < closestamp
                  closestamp = closeness
                  closestp = k
                end
              end
            end
            if closestamp < furthest_away_accepted
              # peak_amp is transferred to appropriate current_amp and zeroed,
              current_peak_amps[closestp] = peak_amps[maxp]
              peak_amps[maxp] = 0.0
              current_peak_freqs[closestp] = current_freq
            end
          end
        end
        max_peaks.times do |j|
          if peak_amps[j] > 0.0
            # find a place for a new oscil and start it up
            new_place = -1
            max_oscils.times do |k|
              if last_peak_amps[k].zero? and current_peak_amps[k].zero?
                new_place = k
                break
              end
            end
            current_peak_amps[new_place] = peak_amps[j]
            peak_amps[j] = 0.0
            current_peak_freqs[new_place] = peak_freqs[j]
            last_peak_freqs[new_place] = peak_freqs[j]
            set_mus_frequency(resynth_oscils[new_place],
                              transposition * peak_freqs[j])
          end
        end
        cur_oscils = 0
        max_oscils.times do |j|
          rates[j] = ifreq * (current_peak_amps[j] - last_peak_amps[j])
          if current_peak_amps[j].nonzero? or last_peak_amps[j].nonzero?
            cur_oscils = j
          end
          sweeps[j] = ihifreq * transposition *
            (current_peak_freqs[j] - last_peak_freqs[j])
        end
        cur_oscils += 1
      end
      # run oscils, update envelopes
      trigger += 1
      if ramped.zero?
        sum = 0.0
      else
        sum = ramped_attack[ramp_ind]
        ramp_ind += 1
        ramped = 0 if ramp_ind == ramped
      end
      cur_oscils.times do |j|
        if amps[j].nonzero? or rates[j].nonzero?
          sum = sum + amps[j] * oscil(resynth_oscils[j], freqs[j])
          amps[j] += rates[j]
          freqs[j] += sweeps[j]
        end
      end
      # else out_val
      amp * sum
    end
  end
  mus_close(fil)
end

def pins_test(start = 0.0, dur = 1.0)
  pins(start, dur, "fyow.snd", 1, :time_scaler, 2)
  $now = start + dur + 0.2
end

# ZC
def zc(start, dur, freq, amp, length1, length2, feedback)
  s = make_pulse_train(:frequency, freq)
  d0 = make_comb(:size, length1,
                 :max_size, [length1, length2].max + 1,
                 :scaler, feedback)
  zenv = make_env(:envelope, [0, 0, 1, 1],
                  :scaler, length2 - length1,
                  :duration, dur)
  run_instrument(start, dur) do
    comb(d0, amp * pulse_train(s), env(zenv))
  end
end

def zc_test(start = 0.0, dur = 1.0)
  $now = start
  zc($now, dur, 100, 0.4, 20, 100, 0.95)
  $now += dur + 0.2
  zc($now, dur, 100, 0.4, 100, 20, 0.95)
  $now += dur + 0.2
end

# ZN
#
# notches are spaced at srate/len, feedforward sets depth thereof so
# sweep of len from 20 to 100 sweeps the notches down from 1000 Hz to
# ca 200 Hz so we hear our downward glissando beneath the pulses.
def zn(start, dur, freq, amp, length1, length2, feedforward)
  s = make_pulse_train(:frequency, freq)
  d0 = make_notch(:size, length1,
                  :max_size, [length1, length2].max + 1,
                  :scaler, feedforward)
  zenv = make_env(:envelope, [0, 0, 1, 1],
                  :scaler, length2 - length1,
                  :duration, dur)
  run_instrument(start, dur) do
    notch(d0, amp * pulse_train(s), env(zenv))
  end
end

def zn_test(start = 0.0, dur = 1.0)
  $now = start
  zn($now, dur, 100, 0.5, 20, 100, 0.95)
  $now += dur + 0.2
  zn($now, dur, 100, 0.5, 100, 20, 0.95)
  $now += dur + 0.2
end

# ZA
def za(start, dur, freq, amp, length1, length2, feedback, feedforward)
  s = make_pulse_train(:frequency, freq)
  d0 = make_all_pass(:feedback, feedback,
                     :feedforward, feedforward,
                     :size, length1,
                     :max_size, [length1, length2].max + 1)
  zenv = make_env(:envelope, [0, 0, 1, 1],
                  :scaler, length2 - length1,
                  :duration, dur)
  run_instrument(start, dur) do
    all_pass(d0, amp * pulse_train(s), env(zenv))
  end
end

def za_test(start = 0.0, dur = 1.0)
  $now = start
  za($now, dur, 100, 0.3, 20, 100, 0.95, 0.95)
  $now += dur + 0.2
  za($now, dur, 100, 0.3, 100, 20, 0.95, 0.95)
  $now += dur + 0.2
end

# CLM-EXPSRC
def clm_expsrc(start, dur, in_file, exp_ratio, src_ratio, amp,
               rev = false, start_in_file = 0)
  assert_type(File.exist?(in_file), in_file, 0, "an existing file")
  stf = (start_in_file * srate(in_file)).floor
  fda = make_readin(in_file, :channel, 0, :start, stf)
  exa = make_granulate(lambda do |dir|
                         readin(fda)
                       end, :expansion, exp_ratio)
  srca = make_src(lambda do |dir|
                    granulate(exa)
                  end, :srate, src_ratio)
  two_chans = (channels(in_file) == 2) and (channels(@ws_output) == 2)
  revit = @reverb and rev
  beg = seconds2samples(start)
  fin = seconds2samples(dur) + beg
  # to satisfy with_sound-option :info and :notehook
  with_sound_info(get_func_name, start, dur)
  if two_chans
    fdb = make_readin(in_file, :channel, 1, :srate, stf)
    exb = make_granulate(lambda do |dir|
                           readin(fdb)
                         end, :expansion, exp_ratio)
    srcb = make_src(lambda do |dir|
                      granulate(exb)
                    end, :srate, src_ratio)
    if revit
      rev_amp = rev * 0.5
      (beg..fin).each do |i|
        vala = src(srca) * amp
        valb = src(srcb) * amp
        outa(i, vala, @ws_output)
        outb(i, valb, @ws_output)
        outa(i, (vala + valb) * rev_amp, @reverb)
      end
    else                        # !revit
      (beg..fin).each do |i|
        outa(i, src(srca) * amp, @ws_output)
        outb(i, src(srcb) * amp, @ws_output)
      end
    end                         # revit
  else                          # !two_chans
    if revit
      rev_amp = rev
      (beg..fin).each do |i|
        vala = src(srca) * amp
        outa(i, vala, @ws_output)
        outa(i, vala * rev_amp, @ws_reverb)
      end
    else                        # !revit
      (beg..fin).each do |i|
        outa(i, src(srca) * amp, @ws_output)
      end
    end                         # revit
  end                           # two_chans
end

def clm_expsrc_test(start = 0.0, dur = 1.0)
  clm_expsrc(start, dur, "oboe.snd", 2.0, 1.0, 1.0, 0.05)
  $now = start + dur + 0.2
end

# EXP-SND
#
# granulate with envelopes on the expansion amount, segment envelope
# shape, segment length, hop length, and input file resampling rate
def exp_snd(file, start, dur, amp,
            exp_amt = 1.0, ramp = 0.4, seglen = 0.15,
            sr = 1.0, hop = 0.05, ampenv = nil)
  assert_type(File.exist?(file), file, 0, "an existing file")
  f0 = make_ws_reader(file, :start, 0)
  expenv = make_env(:envelope,
                    (exp_amt.kind_of?(Array) ?
                     exp_amt : [0, exp_amt, 1, exp_amt]),
                    :duration, dur)
  lenenv = make_env(:envelope,
                    (seglen.kind_of?(Array) ?
                     seglen : [0, seglen, 1, seglen]),
                    :duration, dur)
  max_seg_len, initial_seg_len = if seglen
                                   if seglen.kind_of?(Array)
                                     [max_envelope(seglen), seglen[1]]
                                   else
                                     [seglen, seglen]
                                   end
                                 else
                                   [0.15, 0.15]
                                 end
  scaler_amp = ((max_seg_len > 0.15) ? ((0.6 * 0.15) / max_seg_len) : 0.6)
  srenv = make_env(:envelope, (sr.kind_of?(Array) ? sr : [0, sr, 1, sr]),
                   :duration, dur)
  rampdata = (ramp.kind_of?(Array) ? ramp : [0, ramp, 1, ramp])
  rampenv = make_env(:envelope, rampdata, :duration, dur)
  initial_ramp_time = if ramp
                        if ramp.kind_of?(Array)
                          ramp[1]
                        else
                          ramp
                        end
                      else
                        0.4
                      end
  hopenv = make_env(:envelope, (hop.kind_of?(Array) ? hop : [0, hop, 1, hop]),
                    :duration, dur)
  max_out_hop, initial_out_hop = if hop
                                   if hop.kind_of?(Array)
                                     [max_envelope(hop), hop[1]]
                                   else
                                     [hop, hop]
                                   end
                                 else
                                   [0.05, 0.05]
                                 end
  min_exp_amt, initial_exp_amt = if exp_amt
                                   if exp_amt.kind_of?(Array)
                                     [min_envelope(exp_amt), exp_amt[1]]
                                   else
                                     [exp_amt, exp_amt]
                                   end
                                 else
                                   [1.0, 1.0]
                                 end
  max_in_hop = max_out_hop / min_exp_amt.to_f
  max_len = (@srate * ([max_out_hop, max_in_hop].max + max_seg_len)).ceil
  ampe = make_env(:envelope, (ampenv or [0, 0, 0.5, 1, 1, 0]),
                  :scaler, amp,
                  :duration, dur)
  ex_a = make_granulate(:input, lambda do |dir| ws_readin(f0) end,
                        :expansion, initial_exp_amt,
                        :max_size, max_len,
                        :ramp, initial_ramp_time,
                        :hop, initial_out_hop,
                        :length, initial_seg_len,
                        :scaler, scaler_amp)
  ex_samp = next_samp = 0.0
  vol = env(ampe)
  val_a0 = vol * granulate(ex_a)
  val_a1 = vol * granulate(ex_a)
  if min_envelope(rampdata) <= 0.0 or max_envelope(rampdata) >= 0.5
    error("ramp argument to expand must always be between 0.0 and 0.5: %1.3f",
          ramp)
  else
    run_instrument(start, dur) do
      expa = env(expenv)  # current expansion amount
      segl = env(lenenv)  # current segment length
      resa = env(srenv)   # current resampling increment
      rmpl = env(rampenv) # current ramp length (0 to 0.5)
      hp = env(hopenv)    # current hop size
      # now we set the granulate generator internal state to reflect all
      # these envelopes
      sl = (segl * @srate).floor
      rl = (rmpl * sl).floor
      vol = env(ampe)
      set_mus_length(ex_a, sl)
      set_mus_ramp(ex_a, rl)
      set_mus_frequency(ex_a, hp)
      set_mus_increment(ex_a, expa)
      next_samp += resa
      if next_samp > (ex_samp + 1)
        (next_samp - ex_samp).floor.times do
          val_a0, val_a1 = val_a1, vol * granulate(ex_a)
          ex_samp += 1
        end
      end
      if next_samp == ex_samp
        val_a0
      else
        val_a0 + (next_samp - ex_samp) * (val_a1 - val_a0)
      end
    end
    close_ws_reader(f0)
  end
end

def exp_snd_test(start = 0.0, dur = 1.0)
  $now = start
  exp_snd("fyow.snd", $now, dur, 1, [0, 1, 1, 3], 0.4, 0.15,
          [0, 2, 1, 0.5], 0.05)
  $now += dur + 0.2
  exp_snd("oboe.snd", $now, dur, 1, [0, 1, 1, 3], 0.4, 0.15,
          [0, 2, 1, 0.5], 0.2)
  $now += dur + 0.2
end

# EXPFIL
Grn = Struct.new("Grn",
                 :rampval, :rampinc,
                 :loc, :segctr, :whichseg, :ramplen, :steadylen, :trigger)

def expfil(start, dur, hopsecs, rampsecs, steadysecs, file1, file2)
  assert_type(File.exist?(file1), file1, 5, "an existing file")
  assert_type(File.exist?(file2), file2, 6, "an existing file")
  fil1 = make_file2sample(file1)
  fil2 = make_file2sample(file2)
  hop = seconds2samples(hopsecs)
  ramplen = seconds2samples(rampsecs)
  steadylen = seconds2samples(steadysecs)
  grn1 = Grn.new(0.0, 1.0 / ramplen, 0, 0, 0, ramplen, steadylen, 0)
  grn2 = Grn.new(0.0, 1.0 / ramplen, 0, 0, 0, ramplen, steadylen, 0)
  beg = seconds2samples(start)
  out1 = beg
  out2 = hop + beg
  run_instrument(start, dur) do |i|
    val = 0.0
    if i == out1
      inval = ina(grn1.loc, fil1)
      grn1.loc += 1
      if grn1.whichseg.zero? # ramp-up
        inval *= grn1.rampval
        grn1.rampval += grn1.rampinc
        grn1.segctr += 1
        if grn1.segctr == grn1.ramplen
          grn1.segctr = 0
          grn1.whichseg += 1
        end
      else
        if grn1.whichseg == 1 # steady-state
          grn1.segctr += 1
          if grn1.segctr == grn1.steadylen
            grn1.segctr = 0
            grn1.whichseg += 1
          end
        else # ramp-down
          inval *= grn1.rampval
          grn1.segctr += 1
          grn1.rampval -= grn1.rampinc
          if grn1.segctr == grn1.ramplen
            grn1.segctr = 0
            grn1.trigger = 1
            grn1.whichseg = 0
            grn1.rampval = 0.0
          end
        end
      end
      val += inval
      out1 += 1
      if grn1.trigger == 1
        grn1.trigger = 0
        out1 += hop
      end
    end
    if i == out2
      inval = ina(grn2.loc, fil2)
      grn2.loc += 1
      if grn2.whichseg.zero? # ramp-up
        inval *= grn2.rampval
        grn2.rampval += grn2.rampinc
        grn2.segctr += 1
        if grn2.segctr == grn2.ramplen
          grn2.segctr = 0
          grn2.whichseg += 1
        end
      else
        if grn2.whichseg == 1 # steady-state
          grn2.segctr += 1
          if grn2.segctr == grn2.steadylen
            grn2.segctr = 0
            grn2.whichseg += 1
          end
        else # ramp-down
          inval *= grn2.rampval
          grn2.segctr += 1
          grn2.rampval -= grn2.rampinc
          if grn2.segctr == grn2.ramplen
            grn2.segctr = 0
            grn2.trigger = 1
            grn2.whichseg = 0
            grn2.rampval = 0.0
          end
        end
      end
      val += inval
      out2 += 1
      if grn2.trigger == 1
        grn2.trigger = 0
        out2 += hop
      end
    end
    val
  end
end

def expfil_test(start = 0.0, dur = 1.0)
  expfil(start, dur, 0.2, 0.01, 0.1, "oboe.snd", "fyow.snd")
  $now = start + dur + 0.2
end

# GRAPH-EQ
#
=begin
From: Marco Trevisani <marco@ccrma.Stanford.EDU>

This should work like a Graphic Equalizer....
Very easy to use. Just some note:

"amp" & "amp-env" apply an enveloppe to the final result of the
filtering.  

"dur" as ""standard"" in my instruments, when dur = 0 it will take the length
of the sndfile input, otherwise the duration in seconds.

"gain-freq-list" is a list of gains and frequencies to
filter --in this order gain and frequencies--. There is no limit to
the size of the list. Gain can be a number or an
envelope. Unfortunatelly in this version they cant alternate, one
should chose, all envelopes or all numbers i.e.: 
case 1 -> '( .1 440.0 .3 1500.0 .2 330.0 ...etc) or 
case 2 -> '((0 .1 1 .5) 440.0 (0 1 1 .01) 1500 (0 .3 1 .5) 330.0 ...etc) 
'( .1 440.0 (0 1 1 .01) 1500 ..etc) <<< again, this is not allowed ..

"offset-gain" This apply to all the gains if case 1. It adds or
subtracts an offset to all the gains in the list. This number can be positive
or negative. In case the result is a negative number --let's say offset =
-.4 and, like in case 1, the first gain is .1, the result would be
-.3 -- the instrument will pass a gain equal to 0.  

"filt-gain-scale" & "filt-gain-base" will apply to the elements of the
envelopes if we are in case 2, gains are envelopes.

"stats" if #t --default-- prints the number of seconds processed, if
nil doesnt print anything, which will speed up a bit the process.
=end
#
def graph_eq(file, start, dur, *args)
  assert_type(File.exist?(file), file, 0, "an existing file")
  or_beg, amplitude, amp_env, amp_base, offset_gain = nil
  gain_freq_list, filt_gain_scale, filt_gain_base, a1 = nil
  optkey(args, binding,
         [:or_beg, 0],
         [:amplitude, 1],
         [:amp_env, [0, 1, 0.8, 1, 1, 0]],
         [:amp_base, 1],
         [:offset_gain, 0],
         [:gain_freq_list, [[0, 1, 1, 0], 440, [0, 0, 1, 1], 660]],
         [:filt_gain_scale, 1],
         [:filt_gain_base, 1],
         [:a1, 0.99])
  durata = (dur.zero? ? ws_duration(file) : dur)
  len = seconds2samples(durata)
  or_start = (or_beg * ws_srate(file)).round
  rd_a = make_ws_reader(file, :start, or_start)
  half_list = gain_freq_list.length / 2
  ampenv = make_env(:envelope, amp_env,
                    :scaler, amplitude,
                    :duration, durata,
                    :base, amp_base)
  gain_list = []
  0.step(gain_freq_list.length - 1, 2) do |i|
    gain_list << gain_freq_list[i]
  end
  freq_list = []
  1.step(gain_freq_list.length - 1, 2) do |i|
    freq_list << gain_freq_list[i]
  end
  if_list_in_gain = gain_list[0].kind_of?(Array)
  env_size = (if_list_in_gain ? Array.new(freq_list.length) : nil)
  frm_size = Array.new(freq_list.length)
  gains = Vct.new(len, 1.0)
  half_list.times do |i|
    gval = gain_list[i]
    fval = freq_list[i]
    if gval.kind_of?(Array)
      env_size[i] = make_env(:envelope, gval,
                             :scaler, filt_gain_scale,
                             :duration, durata,
                             :base, filt_gain_base)
      frm_size[i] = make_formant(fval, a1)
    else
      gains[i] = (offset_gain + gval < 0) ? 0 : (offset_gain + gain)
      frm_size[i] = make_formant(fval, a1)
    end
  end
  run_instrument(start, durata) do
    outval = 0.0
    inval = ws_readin(rd_a)
    half_list.times do |j|
      if if_list_in_gain
        gains[j] = env(env_size[j]) * (1.0 - a1)
      end
      outval = outval + gains[j] * formant(frm_size[j], inval)
    end
    env(ampenv) * outval
  end
  close_ws_reader(rd_a)
end

def graph_eq_test(start = 0.0, dur = 1.0)
  graph_eq("oboe.snd", start, dur, :amplitude, 50.0)
  $now = start + dur + 0.2
end

# ANOI
#
# a kind of noise reduction -- on-going average spectrum is squelched
# to some extent obviously aimed at intermittent signal in background
# noise
# this is based on Perry Cook's Scrubber.m
def anoi(infile, start, dur, fftsize = 128, amp_scaler = 1.0, r = TWO_PI)
  assert_type(File.exist?(infile), infile, 0, "an existing file")
  freq_inc = (fftsize / 2).floor
  fdi = make_vct(fftsize)
  fdr = make_vct(fftsize)
  spectr = make_vct(freq_inc, 1.0)
  scales = make_vct(freq_inc, 1.0)
  diffs = make_vct(freq_inc)
  win = make_fft_window(Blackman2_window, fftsize)
  k = 0
  amp = 0.0
  incr = amp_scaler * 4.0 / @srate
  file = make_file2sample(infile)
  radius = 1.0 - r / fftsize.to_f
  bin = @srate / fftsize
  fs = make_array(freq_inc) do |i|
    make_formant(i * bin, radius)
  end
  samp = 0
  run_instrument(start, dur) do
    inval = file2sample(file, samp)
    samp += 1
    fdr[k] = inval
    k += 1
    amp += incr if amp < amp_scaler
    if k >= fftsize
      k = 0
      spectrum(fdr, fdi, win, 1)
      freq_inc.times do |j|
        spectr[j] = 0.9 * spectr[j] + 0.1 * fdr[j]
        if spectr[j] >= fdr[j]
          diffs[j] = scales[j] / -fftsize
        else
          diffs[j] = ((fdr[j] - spectr[j]) / fdr[j] - scales[j]) / fftsize
        end
      end
    end
    outval = 0.0
    1.upto(freq_inc - 1) do |j|
      cur_scale = scales[j]
      outval = outval + cur_scale * formant(fs[j], inval)
      scales[j] += diffs[j]
    end
    amp * outval
  end
end

def anoi_test(start = 0.0, dur = 1.0)
  anoi("fyow.snd", start, dur, 128, 2.0)
  $now = start + dur + 0.2
end

=begin
Date: Fri, 25 Sep 1998 09:56:41 +0300
From: Matti Koskinen <mjkoskin@sci.fi>
To: linux-audio-dev@ginette.musique.umontreal.ca
Subject: [linux-audio-dev] Announce: alpha version of denoising
[...]
  I wrote a simple denoiser called anoi after it's parent
  clm-instrument anoi.ins.

  anoi tries to remove white noise like tape hiss from wav-
  files. Removing of noise succeeds ok, but depending of the
  original sound, some distortion can be audible.

  If someone is interested, http://www.sci.fi/~mjkoskin
  contains tarred and gzipped file.

  Now only monophonic wav-files can be denoised, but adding
  others isn't too difficult. 

-matti
mjkoskin@sci.fi
=end

# FULLMIX
#
# ;; "matrix" can be a simple amplitude or a list of lists each inner
# ;;     list represents one input channel's amps into one output channel
# ;;     each element of the list can be a number, a list (turned into an
# ;;     env) or an env
# ;;
# ;; "srate" can be a negative number (read in reverse), or an envelope.
def fullmix(in_file,
            start = 0.0,
            outdur = false,
            inbeg = 0.0,
            matrix = false,
            srate = false,
            reverb_amount = false)
  unless File.exist?(in_file)
    Snd.raise(:no_such_file, in_file, "no such file")
  end
  unless start
    start = 0.0
  end
  unless inbeg
    inbeg = 0.0
  end
  if number?(outdur)
    dur = outdur
  else
    sr = number?(srate) ? srate.abs : 1.0
    dur = (mus_sound_duration(in_file) - inbeg) / sr
  end
  in_chans = channels(in_file)
  reversed = ((number?(srate) and srate.negative?) or
              (array?(srate) and srate.cadr.negative?))
  inloc = (Float(inbeg) * mus_sound_srate(in_file)).round
  ochans = [in_chans, @channels].max
  if @ws_reverb and number?(reverb_amount) and reverb_amount.positive?
    rev_mx = Vct.new(in_chans * in_chans, reverb_amount)
  else
    rev_mx = false
  end
  dir = (reversed ? -1 : 1)
  if (not srate) or (number?(srate) and srate == 1.0)
    file = make_file2frample(in_file)
  else
    file = make_array(in_chans) do |i|
      make_readin(in_file, i, inloc, :direction, dir)
    end
  end
  envs = false
  if array?(srate)
    srcenv = make_env(srate, :duration, dur, :scaler, Float(dir))
  else
    srcenv = false
  end
  case matrix
  when Array
    mx = Vct.new(ochans * ochans, 0.0)
    matrix.each_with_index do |inlist, inp|
      break if inp >= in_chans
      inlist.each_with_index do |outn, outp|
        break if outp >= @channels
        case outn
        when Numeric
          # mx[inp][outp] = outn
          mx[inp * ochans + outp] = outn
        when Array, Mus
          unless envs
            envs = Array.new(in_chans * @channels, false)
          end
          if env?(outn)
            envs[inp * @channels + outp] = outn
          else
            envs[inp * @channels + outp] = make_env(outn, :duration, dur)
          end
        else
          Snd.warning("unknown element in matrix: %p", outn)
        end
      end
    end
  when Numeric
    # ; matrix is a number in this case (a global scaler)
    mx = Vct.new(ochans * ochans, matrix)
  else
    mx = Vct.new(ochans * ochans, 1.0)
  end
  # to satisfy with_sound-option :info and :notehook
  with_sound_info(get_func_name, start, dur)
  beg = seconds2samples(start)
  samps = seconds2samples(dur)
  if (not array?(file))
    mxe = envs
    if envs
      mxe = Array.new(in_chans) do |i|
        Array.new(@channels) do |j|
          envs[i * @channels + j]
        end
      end
    end
    if sound?(@ws_output)
      output = file_name(@ws_output)
      if sound?(@ws_reverb)
        revout = file_name(@ws_reverb)
      end
    else
      output = @ws_output
      revout = @ws_reverb
    end
    mus_file_mix(output, file, beg, samps, inloc, mx, mxe)
    if rev_mx
      mus_file_mix(revout, file, beg, samps, inloc, rev_mx)
    end
  else
    if sound?(@ws_output)
      Snd.raise(:wrong_type_arg, "don't use :to_snd true")
    end
    sr = (number?(srate) ? srate.abs : 0.0)
    srcs = Array.new(in_chans) do |i|
      make_src(:input, file[i], :srate, sr)
    end
    mus_file_mix_with_envs(file, beg, samps, mx, rev_mx, envs, srcs, srcenv,
                           @ws_output, @ws_reverb)
  end
end

def fullmix_test(start = 0.0, dur = 1.0)
  $now = start
  fullmix("pistol.snd", $now, dur)
  $now += dur + 0.2
  fullmix("oboe.snd", $now, dur, 0,
          [[0.1, make_env([0, 0, 1, 1], :duration, dur, :scaler, 0.5)]])
  $now += dur + 0.2
end

# Original header:

# ;;; grani: a granular synthesis instrument
# ;;;   by Fernando Lopez-Lezcano
# ;;;   http://ccrma.stanford.edu/~nando/clm/grani/
# ;;;
# ;;;   Original grani.ins instrument written for the 220a Course by
# ;;;   Fernando Lopez-Lezcano & Juan Pampin, November 6 1996
# ;;;
# ;;; Mar 21 1997: working with hop and grain-dur envelopes
# ;;; Mar 22 1997: working with src envelope (grain wise) & src spread
# ;;; Jan 26 1998: started work on new version
# ;;; Nov  7 1998: input soundfile duration calculation wrong
# ;;; Nov 10 1998: bug in in-samples (thanks to Kristopher D. Giesing for this one)
# ;;; Dec 20 1998: added standard locsig code
# ;;; Feb 19 1999: added "nil" as default value of where to avoid warning (by bill)
# ;;; Jan 10 2000: added input-channel to select which channel of the input file 
# ;;;              to process.
# ;;;              added grain-start-in-seconds to be able to specify input file
# ;;;              locations in seconds for the grain-start envelope
# ;;; May 06 2002: fixed array passing of where-bins in clisp (reported by Charles
# ;;;              Nichols and jennifer l doering
# ;;; Mar 27 2003: added option for sending grains to all channels (requested by
# ;;;              Oded Ben-Tal)

# ;;; calculate a random spread around a center of 0
def random_spread(spread)
  spread.nonzero? ? (random(spread) - spread / 2.0) : 0.0
end

# ;;; create a constant envelope if argument is a number
def envelope_or_number(val)
  assert_type((number?(val) or array?(val) or vct?(val)),
              val, 0, "a number, an array or a vct")
  case val
  when Numeric
    [0, Float(val), 1, Float(val)]
  when Vct
    val.to_a
  when Array
    val
  end
end

# ;;; create a vct from an envelope
def make_gr_env(env, length = 512)
  length_1 = (length - 1).to_f
  make_vct!(length) do |i|
    envelope_interp(i / length_1, env)
  end
end

# ;;; Grain envelopes
def raised_cosine(*args)
  duty_cycle, length = nil
  optkey(args, binding,
         [:duty_cycle, 100],
         [:length, 128])
  active = length * duty_cycle.to_f * 0.01
  incr = PI / (active - 1.0)
  start = (length - active) / 2.0
  fin = (length + active) / 2.0
  s = -1
  make_vct!(length) do |i|
    sine = if i >= start and i < fin
             s += 1
             sin(s * incr)
           else
             0.0
           end
    sine * sine
  end
end

# ;;;=============================================================================
# ;;; Granular synthesis instrument
# ;;;=============================================================================
# 
# ;;; input-channel:
# ;;;   from which channel in the input file are samples read
# ;;; amp-envelope:
# ;;;   amplitude envelope for the note
# ;;; grain-envelope:
# ;;; grain-envelope-end:
# ;;;   envelopes for each individual grain. The envelope applied in the result
# ;;;   of interpolating both envelopes. The interpolation is controlled by
# ;;;   grain-envelope-trasition. If "grain-envelope-end" is nil interpolation
# ;;;   is turned off and only grain-envelope is applied to the grains. 
# ;;; grain-envelope-trasition:
# ;;;   an enveloper that controls the interpolation between the two grain envelopes
# ;;;   0 -> selects "grain-envelope"
# ;;;   1 -> selects "grain-envelope-end"
# ;;; grain-envelope-array-size
# ;;;   size of the array passed to make-table-lookup
# ;;; grain-duration:
# ;;;   envelope that controls grain duration in seconds
# ;;; srate-linear:
# ;;;   t -> sample rate envelope is linear
# ;;;   nil -> sample rate envelope is exponential
# ;;; srate:
# ;;;   envelope that controls sample rate conversion. The envelope is an
# ;;;   exponential envelope, the base and error bound of the conversion
# ;;;   are controlled by "srate-base" and "srate-error".
# ;;; srate-spread:
# ;;;   random spread of sample rate conversion around "srate"
# ;;; srate-base:
# ;;;   base for the exponential conversion
# ;;;   for example: base = (expt 2 (/ 12)) creates a semitone envelope
# ;;; srate-error:
# ;;;   error bound for the exponential conversion. 
# ;;; grain-start:
# ;;;   envelope that determines the starting point of the current grain in
# ;;;   the input file. "y"->0 starts the grain at the beginning of the input
# ;;;   file. "y"->1 starts the grain at the end of the input file.
# ;;; grain-start-spread:
# ;;;   random spread around the value of "grain-start"
# ;;; grain-start-in-seconds:
# ;;;   nil -> grain-start y envelope expressed in percent of the duration of the input file
# ;;;   t   -> grain-start y envelope expressed in seconds
# ;;; grain-density:
# ;;;   envelope that controls the number of grains per second generated in the output file

# ;;; grain-density-spread:

Grani_to_locsig            = 0
Grani_to_grain_duration    = 1
Grani_to_grain_start       = 2
Grani_to_grain_sample_rate = 3
Grani_to_grain_random      = 4
Grani_to_grain_allchans    = 5

def grani(start, dur, amp, file, *args)
  assert_type(File.exist?(file), file, 3, "an existing file")
  input_channel = nil
  grains, amp_envelope, grain_envelope, grain_envelope_end = nil
  grain_envelope_transition, grain_envelope_array_size, grain_duration = nil
  grain_duration_spread, grain_duration_limit, srate, srate_spread = nil
  srate_linear, srate_base, srate_error, grain_start, grain_start_spread = nil
  grain_start_in_seconds, grain_density, grain_density_spread = nil
  reverb_amount, reverse, where_to, where_bins, grain_distance = nil
  grain_distance_spread, grain_degree, grain_degree_spread = nil
  optkey(args, binding,
         [:input_channel, 0],
         [:grains, 0],
         [:amp_envelope, [0, 0, 0.3, 1, 0.7, 1, 1, 0]],
         [:grain_envelope, [0, 0, 0.3, 1, 0.7, 1, 1, 0]],
         [:grain_envelope_end, false],
         [:grain_envelope_transition, [0, 0, 1, 1]],
         [:grain_envelope_array_size, 512],
         [:grain_duration, 0.1],
         [:grain_duration_spread, 0.0],
         [:grain_duration_limit, 0.002],
         [:srate, 0.0],
         [:srate_spread, 0.0],
         [:srate_linear, false],
         [:srate_base, 2.0 ** (1.0 / 12)],
         [:srate_error, 0.01],
         [:grain_start, [0, 0, 1, 1]],
         [:grain_start_spread, 0.0],
         [:grain_start_in_seconds, false],
         [:grain_density, 10.0],
         [:grain_density_spread, 0.0],
         [:reverb_amount, 0.01],
         [:reverse, false],
         [:where_to, 0],
         [:where_bins, []],
         [:grain_distance, 1.0],
         [:grain_distance_spread, 0.0],
         [:grain_degree, 45.0],
         [:grain_degree_spread, 0.0])
  beg, fin = times2samples(start, dur)
  in_file_channels = ws_channels(file)
  in_file_sr       = ws_srate(file)
  in_file_dur      = ws_duration(file)
  rd = make_ws_reader(file,
                      :channel, [input_channel, in_file_channels - 1].min,
                      :vct?, true)
  in_file_reader   = make_src(:input, lambda do |dir| ws_readin(rd) end,
                              :srate, 1.0)
  set_mus_increment(in_file_reader, -1) if reverse
  last_in_sample   = (in_file_dur * in_file_sr).round
  srate_ratio      = in_file_sr / mus_srate()
  sr_env = make_env(:envelope, if srate_linear
                                 envelope_or_number(srate)
                               else
                                 exp_envelope(envelope_or_number(srate),
                                              :base, srate_base,
                                              :error, srate_error)
                               end,
                    :scaler, srate_ratio,
                    :duration, dur)
  sr_spread_env = make_env(:envelope, envelope_or_number(srate_spread),
                           :duration, dur)
  amp_env = make_env(:envelope, amp_envelope, :scaler, amp, :duration, dur)
  gr_dur = make_env(:envelope, envelope_or_number(grain_duration),
                    :duration, dur)
  gr_dur_spread = make_env(:envelope, envelope_or_number(grain_duration_spread),
                           :duration, dur)
  gr_start_scaler = (grain_start_in_seconds ? 1.0 : in_file_dur)
  gr_start = make_env(:envelope, envelope_or_number(grain_start),
                      :duration, dur)
  gr_start_spread = make_env(:envelope, envelope_or_number(grain_start_spread),
                             :duration, dur)
  gr_dens_env = make_env(:envelope, envelope_or_number(grain_density),
                         :duration, dur)
  gr_dens_spread_env = make_env(:envelope,
                                envelope_or_number(grain_density_spread),
                                :duration, dur)
  if vct?(grain_envelope)
    ge = grain_envelope
  else
    ge = make_gr_env(grain_envelope, grain_envelope_array_size)
  end
  gr_env = make_table_lookup(:frequency, 1.0, :initial_phase, 0, :wave, ge)
  if grain_envelope_end
    if vct?(grain_envelope_end)
      ge = grain_envelope_end
    else
      ge = make_gr_env(grain_envelope_end, grain_envelope_array_size)
    end
  else
    ge = make_vct(512)
  end
  gr_env_end = make_table_lookup(:frequency, 1.0, :initial_phase, 0, :wave, ge)
  gr_int_env = make_env(:envelope,
                        envelope_or_number(grain_envelope_transition),
                        :duration, dur)
  gr_dist = make_env(:envelope, envelope_or_number(grain_distance),
                     :duration, dur)
  gr_dist_spread = make_env(:envelope,
                            envelope_or_number(grain_distance_spread),
                            :duration, dur)
  gr_degree = make_env(:envelope, envelope_or_number(grain_degree),
                       :duration, dur)
  gr_degree_spread = make_env(:envelope,
                              envelope_or_number(grain_degree_spread),
                              :duration, dur)
  gr_start_sample = beg
  gr_samples = 0
  gr_offset = 1
  gr_dens = 0.0
  gr_dens_spread = 0.0
  grain_counter = 0
  samples = 0
  first_grain = true
  case grain_duration
  when Numeric
    dur += grain_duration
  when Array
    dur += grain_duration.last
  end
  run_instrument(start, dur, :degree, 45.0) do
    if gr_offset < gr_samples
      gr_offset += 1
      (if grain_envelope_end
         gr_where = env(gr_int_env)
         (1 - gr_where) * table_lookup(gr_env) +
           gr_where * table_lookup(gr_env_end)
       else
         table_lookup(gr_env)
       end) * env(amp_env) * src(in_file_reader)
    else
      if first_grain
        first_grain = false
        gr_start_sample = beg
      else
        gr_start_sample += seconds2samples(1.0 / (gr_dens + gr_dens_spread))
        if (gr_start_sample > fin) or
          (grains.nonzero? and (grain_counter >= grains))
          break
        end
      end
      gr_offset = 0
      gr_from_beg = gr_start_sample - beg
      set_mus_location(amp_env, gr_from_beg)
      set_mus_location(gr_dur, gr_from_beg)
      set_mus_location(gr_dur_spread, gr_from_beg)
      set_mus_location(sr_env, gr_from_beg)
      set_mus_location(sr_spread_env, gr_from_beg)
      set_mus_location(gr_start, gr_from_beg)
      set_mus_location(gr_start_spread, gr_from_beg)
      set_mus_location(gr_dens_env, gr_from_beg)
      set_mus_location(gr_dens_spread_env, gr_from_beg)
      in_start_value = env(gr_start) * gr_start_scaler +
        random_spread(env(gr_start_spread) * gr_start_scaler)
      in_start = (in_start_value * in_file_sr).round
      gr_duration = [grain_duration_limit,
        env(gr_dur) + random_spread(env(gr_dur_spread))].max
      gr_samples = seconds2samples(gr_duration)
      gr_srate = if srate_linear
                   env(sr_env) + random_spread(env(sr_spread_env))
                 else
                   env(sr_env) * srate_base ** random_spread(env(sr_spread_env))
                 end
      set_mus_increment(in_file_reader, gr_srate)
      in_samples = (gr_samples / (1.0 / srate_ratio)).round
      set_mus_phase(gr_env, 0.0)
      set_mus_phase(gr_env_end, 0.0)
      set_mus_frequency(gr_env, 1.0 / gr_duration)
      set_mus_frequency(gr_env_end, 1.0 / gr_duration)
      gr_dens = env(gr_dens_env)
      gr_dens_spread = random_spread(env(gr_dens_spread_env))
      samples += gr_samples
      grain_counter += 1
      where = case where_to
              when Grani_to_grain_duration
                gr_duration
              when Grani_to_grain_start
                in_start_value
              when Grani_to_grain_sample_rate
                gr_srate
              when Grani_to_grain_random
                random(1.0)
              else
                Grani_to_locsig
              end
      if where.nonzero? and where_bins.length > 0
        (where_bins.length - 1).times do |chn|
          locsig_set!(@locsig, chn,
                      (where.between?(where_bins[chn], where_bins[chn + 1]) ?
                       1.0 :
                       0.0))
        end
      else
        if where_to == Grani_to_grain_allchans
          @channels.times do |chn|
            locsig_set!(@locsig, chn, 1.0)
          end
        else
          set_mus_location(gr_dist, gr_from_beg)
          set_mus_location(gr_dist_spread, gr_from_beg)
          set_mus_location(gr_degree, gr_from_beg)
          set_mus_location(gr_degree_spread, gr_from_beg)
          deg = env(gr_degree) + random_spread(env(gr_degree_spread))
          dist = env(gr_dist) + random_spread(env(gr_dist_spread))
          dist_scl = 1.0 / [dist, 1.0].max
          if sample2file?(@ws_reverb)
            locsig_reverb_set!(@locsig, 0,
                               reverb_amount * (1.0 / sqrt([dist, 1.0].max)))
          end
          if @channels == 1
            locsig_set!(@locsig, 0, dist_scl)
          else
            if @channels == 2
              frac = [90.0, [0.0, deg].max].min / 90.0
              locsig_set!(@locsig, 0, dist_scl * (1.0 - frac))
              locsig_set!(@locsig, 1, dist_scl * frac)
            else
              if @channels > 2
                locsig_set!(@locsig, 0, if deg.between?(0, 90)
                                          dist_scl * ((90.0 - deg) / 90.0)
                                        else
                                          if deg.between?(270, 360)
                                            dist_scl * ((deg - 270.0) / 90.0)
                                          else
                                            0.0
                                          end
                                        end)
                locsig_set!(@locsig, 1, if deg.between?(90, 180)
                                          dist_scl * (180.0 - deg) / 90.0
                                        else
                                          if deg.between?(0, 90)
                                            dist_scl * (deg / 90.0)
                                          else
                                            0.0
                                          end
                                        end)
                locsig_set!(@locsig, 2, if deg.between?(180, 270)
                                          dist_scl * (270.0 - deg) / 90.0
                                        else
                                          if deg.between?(90, 180)
                                            dist_scl * (deg - 90.0) / 90.0
                                          else
                                            0.0
                                          end
                                        end)
                if @channels > 3
                  locsig_set!(@locsig, 3, if deg.between?(270, 360)
                                            dist_scl * (360.0 - deg) / 90.0
                                          else
                                            if deg.between?(180, 270)
                                              dist_scl * (deg - 180.0) / 90.0
                                            else
                                              0.0
                                            end
                                          end)
                end
              end
            end
          end
        end
      end
      in_start = if (in_start + in_samples) > last_in_sample
                   last_in_sample - in_samples
                 else
                   [in_start, 0].max
                 end
      set_ws_location(rd, in_start)
      0.0
    end
  end
  close_ws_reader(rd)
end

def grani_test(start = 0.0, dur = 1.0)
  grani(start, dur, 5.0, "oboe.snd", :grain_envelope, raised_cosine())
  $now = start + dur + 0.2
end

# BES-FM
def bes_fm(start, dur, freq, amp, ratio, index)
  car_ph = mod_ph = 0.0
  car_incr = hz2radians(freq)
  mod_incr = ratio.to_f * car_incr
  ampenv = make_env(:envelope, [0, 0, 25, 1, 75, 1, 100, 0],
                    :scaler, amp,
                    :duration, dur)
  run_instrument(start, dur) do
    out_val = env(ampenv) * bes_j1(car_ph)
    car_ph = car_ph + car_incr + index.to_f * bes_j1(mod_ph)
    mod_ph += mod_incr
    out_val
  end
end

def bes_fm_test(start = 0.0, dur = 1.0)
  bes_fm(start, dur, 440, 10, 1, 4)
  $now = start + dur + 0.2
end

# SSB_FM
class Ssb_fm < Musgen
  def initialize(freq)
    super()
    @frequency = freq
    @osc1 = make_oscil(freq, 0)
    @osc2 = make_oscil(freq, HALF_PI)
    @osc3 = make_oscil(0, 0)
    @osc4 = make_oscil(0, HALF_PI)
    @hilbert = make_hilbert_transform(40)
    @delay = make_delay(40)
  end

  def inspect
    format("%s.new(%s)", self.class, @frequency)
  end

  def to_s
    format("#<%s freq: %s>", self.class, @frequency)
  end

  def run_func(val1 = 0.0, val2 = 0.0)
    ssb_fm(val1)
  end
  
  def ssb_fm(modsig)
    am0 = oscil(@osc1)
    am1 = oscil(@osc2)
    car0 = oscil(@osc3, hilbert_transform(@hilbert, modsig))
    car1 = oscil(@osc4, delay(@delay, modsig))
    am0 * car0 + am1 * car1
  end
end

def make_ssb_fm(freq = 440.0)
  Ssb_fm.new(freq)
end

def ssb_fm?(obj)
  obj.kind_of?(Ssb_fm)
end

def ssb_fm(gen, modsig = 0.0)
  gen.ssb_fm(modsig)
end

# FM2
class Fm2 < Musgen
  def initialize(f1, f2, f3, f4, p1, p2, p3, p4)
    super()
    @osc1 = make_oscil(f1, p1)
    @osc2 = make_oscil(f2, p2)
    @osc3 = make_oscil(f3, p3)
    @osc4 = make_oscil(f4, p4)
  end

  def inspect
    format("%s.new(%s, %s, %s, %s, %s, %s, %s, %s)",
           self.class, @f1, @f2, @f3, @f4, @p1, @p2, @p3, @p4)
  end

  def to_s
    format("#<%s %1.3f, %1.3f, %1.3f, %1.3f, %1.3f, %1.3f, %1.3f, %1.3f>",
           self.class, @f1, @f2, @f3, @f4, @p1, @p2, @p3, @p4)
  end

  def run_func(val1 = 0.0, val2 = 0.0)
    fm2(val1)
  end
  
  def fm2(index)
    (oscil(@osc1, index * oscil(@osc2)) +
     oscil(@osc3, index * oscil(@osc4))) * 0.25
  end
end

# make_fm2(1000, 100, 1000, 100, 0, 0, HALF_PI, HALF_PI)
# make_fm2(1000, 100, 1000, 100, 0, 0, 0, HALF_PI)
def make_fm2(f1, f2, f3, f4, p1, p2, p3, p4)
  Fm2.new(f1, f2, f3, f4, p1, p2, p3, p4)
end

def fm2?(obj)
  obj.kind_of?(Fm2)
end

def fm2(gen, index = 0.0)
  gen.fm2(index)
end

def clm_ins_test(start = 0.0, dur = 1.0)
  $now = start
  violin_test($now, dur)
  fm_violin_test($now, dur)
  pluck_test($now, dur)
  vox_test($now, dur)
  fofins_test($now, dur)
  fm_trumpet_test($now, dur)
  pqw_vox_test($now, dur)
  flute_test($now, dur)
  fm_bell_test($now, dur)
  fm_insect_test($now, dur)
  fm_drum_test($now, dur)
  gong_test($now, dur)
  attract_test($now, dur)
  pqw_test($now, dur)
  tubebell_test($now, dur)
  wurley_test($now, dur)
  rhodey_test($now, dur)
  hammondoid_test($now, dur)
  metal_test($now, dur)
  drone_canter_test($now, dur)
  reson_test($now, dur)
  cellon_test($now, dur)
  gran_synth_test($now, dur)
  touch_tone_test($now, dur)
  spectra_test($now, dur)
  two_tab_test($now, dur)
  lbj_piano_test($now, dur)
  resflt_test($now, dur)
  scratch_test($now, dur)
  pins_test($now, dur)
  zc_test($now, dur)
  zn_test($now, dur)
  za_test($now, dur)
  clm_expsrc_test($now, dur)
  exp_snd_test($now, dur)
  expfil_test($now, dur)
  graph_eq_test($now, dur)
  anoi_test($now, dur)
  fullmix_test($now, dur)
  grani_test($now, dur)
  bes_fm_test($now, dur)
end

# clm-ins.rb ends here
