# snd-test.rb -- Snd Ruby code and tests

# Translator/Author: Michael Scholz <mi-scholz@users.sourceforge.net>
# Created: Sat Feb 18 10:18:34 CET 2005
# Changed: Sun May 29 12:24:34 CEST 2011

# Commentary:
#
# Tested with:
#   Snd version 12.2 of 30-May-11
#   ruby 1.8.0 (2003-08-04)
#   ruby 1.8.7 (2010-08-16 patchlevel 302)
#   ruby 1.9.2p136 (2010-12-25 revision 30365)
#   ruby 1.9.3dev (2011-05-29 trunk 31779)

#
# Reads init file ./.sndtest.rb or ~/.sndtest.rb for global variables,
# hooks, etc.
#
# Example:
#
=begin
% cat ./.sndtest.rb
# $VERBOSE = true
# $DEBUG   = true

# (ENV["RUBYLIB"] or $HOME + "/share/snd").split(/:/).each do |f| $LOAD_PATH.unshift(f) end

$original_save_dir       = set_save_dir(ENV["TMPDIR"])
$original_temp_dir       = set_temp_dir(save_dir)
$info_array_print_length = 4

$sf_dir                  = "/usr/opt/sound/sf1/"
$bigger_snd              = "/usr/opt/sound/SFiles/bigger.snd"
$with_big_file           = true
# $all_args              = true
# $bigtest_08            = true
# $tests                 = 2

def bye(n = 0)
  exit(n)
end
=end

#
# Start tests:
#
# snd -noinit -load snd-test.rb         # all tests
# snd -noinit -load snd-test.rb 3 7 20  # only tests 3, 7, 20
# snd -noinit -load snd-test.rb -23     # all tests except 23
#
#  test 00: constants
#  test 01: defaults
#  test 02: headers
#  test 03: variables
#  test 04: sndlib
#  test 05: simple overall checks
#  test 06: vcts
#  test 07: colors
#  test 08: clm
#  test 09: mix
#  test 10: marks
#  test 11: dialogs
#  test 12: extensions
#  test 13: menus, edit lists, hooks, etc
#  test 14: all together now
#  test 15: chan-local vars
#  test 16: regularized funcs
#  test 17: dialogs and graphics
#  test 18: enved
#  test 19: save and restore
#  test 20: transforms
#  test 21: new stuff
#  test 23: with-sound
#  test 28: errors
#  test all done

$VERBOSE = false
$DEBUG   = false

$my_snd_error_hook = false
$my_mus_error_hook = false

# run snd-test.rb $tests times
$tests = 1

$HOME                           = ENV["HOME"]
$original_save_dir              = (save_dir or $HOME + "/zap/snd")
$original_temp_dir              = (temp_dir or $HOME + "/zap/tmp")
$original_sound_file_extensions = sound_file_extensions
$original_prompt                = listener_prompt
$default_file_buffer_size       = 65536
$info_array_print_length        = 24

$sf_dir         = "/home/bil/sf1/"
$bigger_snd     = "/home/bil/zap/sounds/bigger.snd"
$with_big_file  = false
# $bigtest_08 not included in $all_args
$all_args       = false
$bigtest_08     = false

# Global variables may be overridden in `pwd`/.sndtest.rb or ~/.sndtest.rb.
lambda do |file|
  if File.file?(file)
    load(file)
  elsif File.file?(f = $HOME + "/" + file)
    load(f)
  end
end.call(".sndtest.rb")

unless $my_snd_error_hook
  $my_snd_error_hook = Proc.new do |msg| true end
end

require "clm"

# default 1, can be reset in .sndtest.rb
$tests = ((integer?($tests) and $tests > 0) ? $tests : 1)
$clmtest = 0

$with_test_nogui  = provided? :snd_nogui
$with_test_gui    = (not $with_test_nogui)
$with_test_motif  = provided? :snd_motif
$with_test_gtk    = provided? :snd_gtk
$with_test_ladspa = provided? :snd_ladspa
$with_test_gl     = provided? :gl
$with_test_gl2ps  = provided? :gl2ps
$with_test_gsl    = provided? :gsl
$with_test_alsa   = provided? :alsa

if $with_test_nogui
  undef x_bounds
  undef set_x_bounds
  undef y_bounds
  undef set_y_bounds
  undef enved_filter
  undef graph_cursor
  undef set_graph_cursor
  undef enved_envelope
  undef set_enved_envelope
  undef colormap
  undef set_colormap

  # FIXME (INFO)
  # snd-nogui.c defines "in"
  alias call_in in

  x_bounds_value = [0.0, 0.1]
  make_proc_with_setter(:x_bounds,
                        Proc.new do |*args| x_bounds_value end,
                        Proc.new do |bounds, *args| x_bounds_value = bounds end)

  y_bounds_value = [-1.0, 1.0]
  make_proc_with_setter(:y_bounds,
                        Proc.new do |*args| y_bounds_value end,
                        Proc.new do |bounds, *args| y_bounds_value = bounds end)

  #
  # FIXME (INFO)
  #
  # For ruby18 it's important to define Procs with arity 0 in this way:
  #
  #     Proc.new do | | enved_filter_value end
  # or  Proc.new do enved_filter_value end
  # or  lambda do enved_filter_value end
  #
  # but not
  #
  #     lambda do | | enved_filter_value end
  #
  # Otherwise we can't correctly determine the arity in test 28.
  #
  enved_filter_value = true
  make_proc_with_setter(:enved_filter,
                        Proc.new do | | enved_filter_value end,
                        Proc.new do |val| enved_filter_value = val end)

  graph_cursor_value = 34
  make_proc_with_setter(:graph_cursor,
                        Proc.new do | | graph_cursor_value end,
                        Proc.new do |val| graph_cursor_value = val end)

  enved_envelope_value = nil
  make_proc_with_setter(:enved_envelope,
                        Proc.new do | | enved_envelope_value end,
                        Proc.new do |val| enved_envelope_value = val end)

  colormap_value = $hot_colormap
  make_proc_with_setter(:colormap,
                        Proc.new do | | colormap_value end,
                        Proc.new do |val|
                          if val.kind_of?(Numeric) and val.between?(0, 20)
                            colormap_value = val
                          end
                        end)

  # These global variables are already defined and set to false in snd-nogui.c.
  $mouse_enter_graph_hook    = Hook.new("$mouse_enter_graph_hook", 2)
  $mouse_enter_label_hook    = Hook.new("$mouse_enter_label_hook", 3)
  $mouse_enter_listener_hook = Hook.new("$mouse_enter_listener_hook", 1)
  $mouse_enter_text_hook     = Hook.new("$mouse_enter_text_hook", 1)
  $mouse_leave_graph_hook    = Hook.new("$mouse_leave_graph_hook", 2)
  $mouse_leave_label_hook    = Hook.new("$mouse_leave_label_hook", 3)
  $mouse_leave_listener_hook = Hook.new("$mouse_leave_listener_hook", 1)
  $mouse_leave_text_hook     = Hook.new("$mouse_leave_text_hook", 1)
end

require "examp"
require "ws"
require "hooks"
require "mix"
require "marks"
require "pvoc"
require "bird"
require "v"
require "poly"
require "dsp"
require "analog-filter"
require "rgb"
require "effects"
require "draw"
require "musglyphs"

if $with_test_motif
  RXSetErrorHandler(lambda do |dpy, e|
                      val, err = RXGetErrorText(dpy, Rerror_code(e), nil, 1024)
                      $stderr.printf("Xlib error_code[%s]: %s\n", val, err)
                      val, err = RXGetErrorText(dpy, Rrequest_code(e), nil, 1024)
                      $stderr.printf("Xlib request_code[%s]: %s\n", val, err)
                      val, err = RXGetErrorText(dpy, Rminor_code(e), nil, 1024)
                      $stderr.printf("Xlib minor_code[%s]: %s\n", val, err)
                      $stderr.printf("Ruby $!: %s\n", $!.inspect)
                      $stderr.printf("Ruby $@: %s\n", $@.inspect)
                    end)
  RXSetIOErrorHandler(lambda do |dpy|
                        $stderr.printf("Xlib IO Error dpy: %s", dpy.inspect)
                        $stderr.printf("Ruby $!: %s\n", $!.inspect)
                        $stderr.printf("Ruby $@: %s\n", $@.inspect)
                      end)
end

# Returns Ascii value of KEY as a Fixnum.
#
# Since Ruby 1.9.0 (July/August 2006) ?x returns string "x" instead of
# fixnum 120, so we need a new function.
#
# key_to_int(?x) => 120
if ?x.kind_of?(String)
  def key_to_int(key)
    key.sum
  end
else
  def key_to_int(key)
    key
  end
end

if $with_test_nogui
  set_with_mix_tags(true)
end

unbind_key(key_to_int(?c), 4, true) # C-c for interrupt key
trap("SIGINT") do |sig|
  puts
  snd_info("Interrupt received.  Finish snd-test.rb.")
  snd_info("")
  $timings.last.last.stop
  finish_snd_test
  clear_test_files
  exit(2)
end

# let procs $snd|mus_error_hook("sndtestrc") untouched
def reset_almost_all_hooks
  reset_all_hooks
  if proc?($my_snd_error_hook)
    $snd_error_hook.add_hook!("sndtest", &$my_snd_error_hook)
  end
  if proc?($my_mus_error_hook)
    $mus_error_hook.add_hook!("sndtest", &$my_mus_error_hook)
  end
end

reset_almost_all_hooks

# to prepare view_files_infos in snd-xfile.c
# (save_state calls save_view_files_dialogs() in snd-xfile.c)
Snd.add_sound_path(Dir.pwd)

$test_functions = Array.new

def main_test
  start_snd_test()
  if false
    # FIXME (INFO)
    # Instead of rand() one can use different command lines:
    # snd -noinit snd-test.rb 3 2 1
    # snd -noinit snd-test.rb 1 3 2
    # etc.
    $test_functions.rand!
  end
  $test_functions.each do |func|
    before_test(func)
    snd_func(func)
    after_test(func)
  end
  finish_snd_test()
  clear_test_files
  exit(0)
end

if $with_test_nogui
  def snd_info(fmt, *args)
    clm_print("# %s\n", format(fmt, *args))
    nil
  end
else
  def snd_info(fmt, *args)
    str = format(fmt, *args)
    clm_print("\n# %s", str)
    $stderr.printf("# %s\n", str)
    nil
  end
end

if RUBY_VERSION >= "1.9"
  def snd_display_prev_caller(fmt, *args)
    if line = caller(2)[0].scan(/:(.*):in /).first
      snd_info("[%s] %s", line.first, format(fmt, *args))
    else
      snd_info(fmt, *args)
    end
  end
else
  def snd_display_prev_caller(fmt, *args)
    # Because ruby < 1.9 has a line number bug we use function name.
    if line = caller(2)[0].scan(/^.*:in `(.*)'/).first
      snd_info("[%s] %s", line.first, format(fmt, *args))
    else
      snd_info(fmt, *args)
    end
  end
end

def snd_display(*args)
  if args.empty?
    snd_display_prev_caller("")
  else
    fmt = args.shift
    snd_display_prev_caller(fmt, *args)
  end
end

def snd_debug(*args)
  if args.empty?
    snd_display_prev_caller("#<SND-DEBUG>")
  else
    fmt = args.shift
    snd_display_prev_caller("#<SND-DEBUG: %s>", format(fmt, *args))
  end
end

def snd_test_format(sndfmt, res, req, fmt = "", *args)
  str = format(sndfmt, res, req)
  unless fmt.empty?
    str = format(fmt, *args) + ": " + str
  end
  str
end

def snd_format(res, req, op = "!=", fmt = "", *args)
  case req
  when Float
    snd_test_format("res %1.4f #{op} req %1.4f?", res, req, fmt, *args)
  when Vct, Array, Vec, Poly
    old_alen = mus_array_print_length()
    old_vlen = print_length()
    set_mus_array_print_length($info_array_print_length)
    set_print_length($info_array_print_length)
    if res.nil?
      res = "nil"
    end
    str = snd_test_format("res #{op} req?\n# => res %s\n# => req %s", res, req, fmt, *args)
    set_mus_array_print_length(old_alen)
    set_print_length(old_vlen)
    str
  else
    snd_test_format("res %s #{op} req %s?", res.inspect, req.inspect, fmt, *args)
  end
end

def snd_format_neq(res, req, fmt = "", *args)
  snd_format(res, req, "!=", fmt, *args)
end

def snd_test_equal?(res, req)
  case req
  when Float
    res.kind_of?(Numeric) and fequal?(res, req)
  when Vct, Vec, Poly
    vequal?(res, req)
  else
    res == req
  end
end

def snd_test_neq(res, req, fmt = "", *args)
  if snd_test_equal?(res, req)
    false
  else
    snd_display_prev_caller(snd_format(res, req, "!=", fmt, *args))
    true
  end
end

def snd_test_eq(res, req, fmt = "", *args)
  if snd_test_equal?(res, req)
    snd_display_prev_caller(snd_format(res, req, "==", fmt, *args))
    true
  else
    false
  end
end

# snd_test_any_neq(res, req, :ffequal?, "more info")
def snd_test_any_neq(res, req, func, fmt = "", *args)
  if method(func).call(res, req)
    false
  else
    snd_display_prev_caller(snd_format(res, req, "!=", fmt, *args))
    true
  end
end

# snd_test_any_eq(res, req, :ffequal?, "more info")
def snd_test_any_eq(res, req, func, fmt = "", *args)
  if method(func).call(res, req)
    snd_display_prev_caller(snd_format(res, req, "==", fmt, *args))
    true
  else
    false
  end
end

# command line args: last arg(s) may be zero or many test numbers
# snd -noinit -load snd-test.rb 3 7 20  # only tests 3, 7, 20
# snd -noinit -load snd-test.rb -23     # all tests except 23

lambda do
  # non existent tests, non wanted tests (negative arguments like -23) added here
  nargs = [22, 24, 25, 26, 27, 29]
  targs = []
  if script_arg.positive?
    script_args[script_arg..-1].each do |arg|
 if integer?(n = Snd.catch(:all, nil) do Integer(arg) end.first)
        if n < 0
          nargs << n.abs
        elsif n <= 30             # test_30 for short tests
          targs << n
        end
      end
    end
  end
  if targs.empty?
    29.times do |n| targs << n end
  end
  (targs - nargs).each do |n|
    $test_functions << format("test_%02d", n).intern
  end
end.call

def fneq_err(f1, f2, err = 0.001)
  (f1 - f2).abs > err
end

def fneq(a, b)
  fneq_err(a, b, 0.001)
end

def ffneq(a, b)
  fneq_err(a, b, 0.01)
end

def fffneq(a, b)
  fneq_err(a, b, 0.1)
end

def fequal_err(f1, f2, err = 0.001)
  (f1 - f2).abs <= err
end

def fequal?(a, b)
  fequal_err(a, b, 0.001)
end

def ffequal?(a, b)
  fequal_err(a, b, 0.01)
end

def fffequal?(a, b)
  fequal_err(a, b, 0.1)
end

# returns a vct or false
# obj: Vct, Array, Vec, Poly
def any2vct(obj)
  obj.respond_to?(:to_vct) and obj.to_vct
end

# compares Arrays and Vcts
def vequal_err(val0, val1, err = 0.001)
  (v0 = any2vct(val0)) and (v1 = any2vct(val1)) and v0.subtract(v1).peak <= err
end

def vequal?(v0, v1)
  vequal_err(v0, v1, 0.001)
end

def vvequal?(v0, v1)
  vequal_err(v0, v1, 0.00002)
end

def vfequal?(v0, v1)
  vequal_err(v0, v1, 0.01)
end

def vffequal?(v0, v1)
  vequal_err(v0, v1, 0.1)
end

def vfffequal?(v0, v1)
  vequal_err(v0, v1, 0.5)
end

alias vequal    vequal?
alias vvequal   vvequal?
alias vfequal   vfequal?
alias vffequal  vffequal?
alias vfffequal vfffequal?

def cneq(a, b)
  if number?(a) and number?(b)
    fneq(a.real, b.real) or fneq(a.imag, b.imag)
  else
    true
  end
end

def vcneql(a, b)
  if a.length != b.length
    true
  else
    a.each_with_index do |x, i|
      if cneq(x, b[i])
        return true
      end
    end
    false
  end
end

def vmaxdiff(v0, v1)
  v0.dup.subtract(v1).peak
end

def list_p(obj)
  array?(obj) and (not obj.empty?)
end

def any_arity(obj)
  case obj
  when Proc, Method
    obj.arity
  when String, Symbol
    method(obj).arity
  else
    0
  end
end

def arity_ok(func, args)
  if integer?(rargs = Snd.catch do any_arity(func) end.first)
    if rargs >= 0
      args == rargs
    else
      args >= (rargs.abs - 1)   # We have no idea how much optional args FUNC has.
    end
  else
    false
  end
end

def set_arity_ok(func, args)
  arity_ok("set_#{func}", args)
end

if $with_test_nogui
  def dismiss_all_dialogs
  end
else
  def dismiss_all_dialogs
    dialog_widgets.each do |dialog|
      if array?(dialog)
        if symbol?(dialog.car)
          if is_managed?(dialog)
            hide_widget(dialog)
          end
        else
          dialog.each do |d|
            if symbol?(d.car)
              if is_managed?(dialog)
                hide_widget(dialog)
              end
            end
          end
        end
      end
    end
  end
end

def safe_display_edits(snd = false, chn = false, edpos = false, with_source = true)
  Snd.catch(:all, lambda do |*args| snd_display_prev_caller("display_edits: %s", args) end) do
    display_edits(snd, chn, edpos, with_source)
  end.first
end

def safe_divide(a, b)
  b.zero? ? a : (a / b)
end

set_mus_file_buffer_size($default_file_buffer_size)
set_with_background_processes(false)

def make_color_with_catch(c1, c2, c3)
  make_color(c1, c2, c3)
rescue
  make_color(1, 0, 0)
end

def file_copy(f1, f2)
  if File.exists?(f1)
    fin = File.open(f1, "r")
    fout = File.open(f2, "w")
    fout.write(fin.read) until fin.eof?
    fin.close
    fout.close
  end
end

def delete_file(file)
  File.owned?(file) and File.unlink(file)
end

def delete_files(*files)
  files.each do |f| delete_file(f) end
end

def with_file(file, verbose = $DEBUG, &body)
  if File.exists?(full_name = $sf_dir + file)
    body.call(full_name)
  else
    if verbose
      snd_info("%s missing?", full_name)
    end
  end
end

def with_gc_disabled
  GC.disable
  ret = yield
  GC.enable
  GC.start
  ret
end

Snd_error_tags.each do |tag|
  if (res = Snd.catch(tag) do Snd.throw(tag, "snd-test") end).first != tag
    snd_display("Snd.catch (throwing 1): %s -> %s", tag.inspect, res.inspect)
  end
  if (res = Snd.catch(tag) do Snd.raise(tag, "snd-test") end).first != tag
    snd_display("Snd.catch (raising 1): %s -> %s", tag.inspect, res.inspect)
  end
  if (res = Snd.catch(tag, :okay) do Snd.throw(tag, "snd-test") end).first != :okay
    snd_display("Snd.catch (throwing 2): %s -> %s", tag.inspect, res.inspect)
  end
  if (res = Snd.catch(tag, :okay) do Snd.raise(tag, "snd-test") end).first != :okay
    snd_display("Snd.catch (raising 2): %s -> %s", tag.inspect, res.inspect)
  end
end

def cwd
  Dir.pwd + "/"
end

class Snd_test_time
  def initialize
    @real_time     = Time.now
    @process_time  = process_times
    @real = @utime = @stime = 0.0
  end
  attr_reader :real, :utime, :stime

  def to_s
    format("real: %1.3f, user: %1.3f, system: %1.3f", @real, @utime, @stime)
  end

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

  def start
    @real_time    = Time.now
    @process_time = process_times
  end

  def stop
    @real = Time.now - @real_time
    cur_time = process_times
    @utime = cur_time.utime - @process_time.utime
    @stime = cur_time.stime - @process_time.stime
  end
end

def with_time(msg = nil, &body)
  stt = Snd_test_time.new
  stt.start
  body.call
  stt.stop
  if msg and $VERBOSE
    snd_info("%s (%s)", stt.to_s, msg)
  end
  [stt.real, stt.utime, stt.stime]
end

def start_snd_test()
  # map_chan* procedure
  $init_channel  = lambda do |y| 1.0 end
  $default_srate = 22050.0
  $timings       = Array.new(0)
  set_show_listener(true)
  set_window_x(600)
  set_window_y(10)
  kind = if $with_test_motif
           "motif"
         elsif $with_test_gtk
           "gtk"
         elsif $with_test_nogui
           "nogui"
         else
           "unknown"
         end
  snd_info("===  Snd version: %s (snd_%s)", snd_version, kind)
  snd_info("=== Ruby version: %s (%s) [%s]", RUBY_VERSION, RUBY_RELEASE_DATE, RUBY_PLATFORM)
  snd_info("")
  snd_info("%s", Time.now.localtime.strftime("%a %d-%b-%Y %I:%M %p %Z"))
  snd_info("")
  $overall_start_time = Snd_test_time.new
end

def finish_snd_test()
  $overall_start_time.stop
  Snd.regions.apply(:forget_region)
  set_view_files_sort(0)
  clear_sincs
  stop_playing
  reset_almost_all_hooks
  set_ask_about_unsaved_edits(false)
  set_remember_sound_state(false)
  snd_info("all done!")
  snd_info("")
  unless $timings.empty?
    $timings.each do |tst| snd_info("%s %s", tst.first, tst.last.inspect) end
  end
  snd_info("total   %s\n", $overall_start_time.inspect)
  set_show_listener(true)
  save_listener("test-ruby.output")
  clear_listener
  set_show_listener(true)
  set_listener_prompt($original_prompt)
end

def clear_test_files
  fs = 0
  [$original_save_dir, $original_temp_dir, "/tmp"].each do |path|
    if File.exists?(path)
      fs += Dir[path + "/snd_*"].length
      Dir[path + "/snd_*"].each do |f| delete_file(f) end
    end
  end
  snd_info("%s temporary file%s deleted", fs.zero? ? "no" : fs, fs.between?(0, 1) ? "" : "s")
  mus_sound_prune
  $snd_opened_sound = false
  ["1",
   "aaa.eps",
   "accelmap",
   "envs.save",
   "fmv.snd",
   "fmv.wav",
   "fmv0.snd",
   "fmv1.snd",
   "fmv2.snd",
   "fmv3.snd",
   "fmv4.reverb",
   "fmv4.snd",
   "gtk-errors",
   "hiho.marks",
   "hiho.rb",
   "hiho.snd",
   "hiho.snd",
   "hiho.tmp",
   "hiho.wave",
   "ho",
   "new.snd",
   "oboe.marks",
   "obtest.snd.stereo",
   "remembered-oboe.snd.rb",
   "saved-snd.rb",
   "snd.eps",
   "test-1.snd",
   "test-2.snd",
   "test-macros.scm",
   "test.aifc",
   "test.aiff",
   "test.data",
   "test.rev",
   "test.reverb",
   "test.rf64",
   "test.snd",
   "test.snd.snd",
   "test.wav",
   "test.wave",
   "test.xpm",
   "test1.snd",
   "test1.reverb",
   "test2.snd",
   "test3.snd",
   "tmp.snd",
   "with-mix.rbm",
   "with-mix.snd"].each do |file| delete_file(file) end
  ["bad_data_format.snd.snd",
   "ce-c3.w02.snd",
   "hcom-16.snd.snd",
   "ieee-text-16.snd.snd",
   "mus10.snd.snd",
   "nasahal.avi.snd",
   "nist-shortpack.wav.snd",
   "o2_dvi.wave.snd",
   "oboe.g721.snd",
   "oboe.g723_24.snd",
   "oboe.g723_40.snd",
   "oki.wav.snd",
   "trumps22.adp.snd",
   "wood.sds.snd"].each do |file|
    with_file(file, false) do delete_file(file) end
  end
end

def before_test(func)
  set_mus_srate($clm_srate = $default_srate.to_i)
  set_clipping(false)
  set_mus_clipping(false)
  $timings.push([func, Snd_test_time.new])
  snd_info("%s", func)
end

def after_test(func)
  $timings.last.last.stop
  if sounds
    snd_info("%s: open sounds: %s", func, short_file_name(true))
    Snd.sounds.apply(:close_sound)
  end
  Snd.regions.apply(:forget_region)
  set_ask_about_unsaved_edits(false)
  set_remember_sound_state(false)
  dismiss_all_dialogs
  snd_info("%s done%s\n#", func, $VERBOSE ? format(" (%s)", $timings.last.last) : "")
end

# returns body's return value or error symbol (eg. :no_such_sound)
def without_errors(&body)
  Snd.catch(:all) do
    body.call
  end.first
end

if $with_test_motif
  module Test_event
    # see event.scm
    def key_event(widget, key, state)
      e = RXEvent(RKeyPress)
      dpy = RXtDisplay(widget)
      window = RXtWindow(widget)
      Rset_type(e, RKeyPress)
      Rset_window(e, window)
      Rset_display(e, dpy)
      Rset_root(e, RRootWindow(dpy, RDefaultScreen(dpy)))
      Rset_x(e, 0)
      Rset_y(e, 0)
      Rset_x_root(e, 0)
      Rset_y_root(e, 0)
      Rset_keycode(e, RXKeysymToKeycode(dpy, [:KeySym, key_to_int(key)]))
      Rset_state(e, state)
      Rset_time(e, [:Time, RCurrentTime])
      Rset_same_screen(e, true)
      Rset_subwindow(e, [:Window, RNone])
      err = RXSendEvent(dpy, window, false, RKeyPressMask, e)
      if err.nonzero?
        Rset_type(e, RKeyRelease)
        Rset_time(e, [:Time, RCurrentTime])
        err = RXSendEvent(dpy, window, false, RKeyReleaseMask, e)
      end
      if err.zero?
        snd_display("[key-event error] ", err)
      end
      err
    end

    def key_event_with_mouse(widget, key, state, x, y)
      e = RXEvent(RKeyPress)
      dpy = RXtDisplay(widget)
      window = RXtWindow(widget)
      Rset_type(e, RKeyPress)
      Rset_window(e, window)
      Rset_display(e, dpy)
      Rset_root(e, RRootWindow(dpy, RDefaultScreen(dpy)))
      Rset_x(e, x)
      Rset_y(e, y)
      Rset_x_root(e, x)
      Rset_y_root(e, y)
      Rset_keycode(e, RXKeysymToKeycode(dpy, [:KeySym, key_to_int(key)]))
      Rset_state(e, state)
      Rset_time(e, [:Time, RCurrentTime])
      Rset_same_screen(e, true)
      Rset_subwindow(e, [:Window, RNone])
      err = RXSendEvent(dpy, window, false, RKeyPressMask, e)
      if err.nonzero?
        Rset_type(e, RKeyRelease)
        Rset_time(e, [:Time, RCurrentTime])
        err = RXSendEvent(dpy, window, false, RKeyReleaseMask, e)
      end
      if err.zero?
        snd_display("[key-event error] ", err)
      end
      err
    end

    def resize_event(widget, width, height)
      e = RXEvent(RResizeRequest)
      dpy = RXtDisplay(widget)
      window = RXtWindow(widget)
      Rset_window(e, window)
      Rset_display(e, dpy)
      Rset_width(e, width)
      Rset_height(e, height)
      RXSendEvent(dpy, window, false, RResizeRedirectMask, e)
    end

    def enter_event(widget)
      e = RXEvent(REnterNotify)
      dpy = RXtDisplay(widget)
      window = RXtWindow(widget)
      Rset_window(e, window)
      Rset_display(e, dpy)
      RXSendEvent(dpy, window, false, REnterWindowMask, e)
    end

    def leave_event(widget)
      e = RXEvent(RLeaveNotify)
      dpy = RXtDisplay(widget)
      window = RXtWindow(widget)
      Rset_window(e, window)
      Rset_display(e, dpy)
      RXSendEvent(dpy, window, false, RLeaveWindowMask, e)
    end

    def expose_event(widget, x, y, width, height)
      e = RXEvent(RExpose)
      dpy = RXtDisplay(widget)
      window = RXtWindow(widget)
      Rset_window(e, window)
      Rset_display(e, dpy)
      Rset_x(e, x)
      Rset_y(e, y)
      Rset_width(e, width)
      Rset_height(e, height)
      Rset_count(e, 0)
      RXSendEvent(dpy, window, false, RExposureMask, e)
    end

    def click_event(widget, button, state, x, y)
      e = RXEvent(RButtonPress)
      dpy = RXtDisplay(widget)
      window = RXtWindow(widget)
      Rset_type(e, RButtonPress)
      Rset_window(e, window)
      Rset_display(e, dpy)
      Rset_root(e, RRootWindow(dpy, RDefaultScreen(dpy)))
      Rset_x(e, x)
      Rset_y(e, y)
      Rset_x_root(e, 0)
      Rset_y_root(e, 0)
      Rset_state(e, state)
      Rset_button(e, button)
      Rset_time(e, [:Time, RCurrentTime])
      Rset_same_screen(e, true)
      Rset_subwindow(e, [:Window, RNone])
      err = RXSendEvent(dpy, window, false, RButtonPressMask, e)
      if err.nonzero?
        Rset_type(e, RButtonRelease)
        Rset_time(e, [:Time, RCurrentTime])
        err = RXSendEvent(dpy, window, false, RButtonReleaseMask, e)
      end
      if err.zero?
        snd_display("[click-event error] ", err)
      end
      err
    end

    def drag_event(widget, button, state, x0, y0, x1, y1)
      e = RXEvent(RButtonPress)
      e1 = RXEvent(RMotionNotify)
      dpy = RXtDisplay(widget)
      window = RXtWindow(widget)
      Rset_type(e, RButtonPress)
      Rset_window(e, window)
      Rset_display(e, dpy)
      Rset_root(e, RRootWindow(dpy, RDefaultScreen(dpy)))
      Rset_x(e, x0)
      Rset_y(e, y0)
      Rset_x_root(e, 0)
      Rset_y_root(e, 0)
      Rset_state(e, state)
      Rset_button(e, button)
      Rset_time(e, [:Time, RCurrentTime])
      Rset_same_screen(e, true)
      Rset_subwindow(e, [:Window, RNone])
      err = RXSendEvent(dpy, window, false, RButtonPressMask, e)
      if err.nonzero?
        Rset_window(e1, window)
        Rset_display(e1, dpy)
        Rset_root(e1, RRootWindow(dpy, RDefaultScreen(dpy)))
        # Rset_x(e1, x1)
        # Rset_y(e1, y1)
        Rset_x_root(e1, x0)
        Rset_y_root(e1, y0)
        Rset_state(e1, state)
        Rset_time(e1, [:Time, RCurrentTime + 300])
        Rset_same_screen(e1, true)
        Rset_subwindow(e1, [:Window, RNone])
        Rset_is_hint(e1, RNotifyNormal)
        den = if (x1 - x0).abs > 10 or (y1 - y0).abs > 10
                10
              else
                2
              end
        xdiff = ((x1 - x0) / den.to_f).floor
        ydiff = ((y1 - y0) / den.to_f).floor
        xn = x0 + xdiff
        yn = y0 + ydiff
        den.times do
          Rset_x(e1, xn)
          Rset_y(e1, yn)
          RXSendEvent(dpy, window, false, RButtonMotionMask, e1)
          xn += xdiff
          yn += ydiff
        end
        Rset_type(e, RButtonRelease)
        Rset_time(e, [:Time, RCurrentTime + 500])
        Rset_x(e, x1)
        Rset_y(e, y1)
        RXSendEvent(dpy, window, false, RButtonReleaseMask, e)
      end
    end

    def select_item(wid, pos)
      if RXmIsList(wid)
        RXmListSelectPos(wid, pos + 1, true)
      else
        snd_display("is not a list!", RXtName(wid))
      end
    end

    def click_button(button, value = false, bits = false)
      if RWidget?(button)
        if RXtIsSensitive(button)
          if RXmIsPushButton(button) or RXmIsPushButtonGadget(button)
            if RXtHasCallbacks(button, RXmNactivateCallback) == RXtCallbackHasSome
              but = RXmPushButtonCallbackStruct()
              Rset_click_count(but, 0)
              e = RXEvent(RButtonPress)
              Rset_state(e, (bits or 0))
              Rset_event(but, e)
              RXtCallCallbacks(button, RXmNactivateCallback, but)
            else
              snd_display("pushbutton %s has no active callbacks", RXtName(button))
            end
          else
            if RXmIsToggleButton(button) or RXmIsToggleButtonGadget(button)
              if RXtHasCallbacks(button, RXmNvalueChangedCallback) == RXtCallbackHasSome
                tgl = RXmToggleButtonCallbackStruct()
                Rset_set(tgl, value)
                e = RXEvent(RButtonPress)
                Rset_state(e, (bits or 0))
                Rset_event(tgl, e)
                RXtCallCallbacks(button, RXmNvalueChangedCallback, tgl)
              else
                snd_display("togglebutton %s has no valueChanged callbacks", RXtName(button))
              end
            else
              if RXmIsArrowButton(button)
                if RXtHasCallbacks(button, RXmNactivateCallback) == RXtCallbackHasSome
                  arr = RXmArrowButtonCallbackStruct()
                  Rset_click_count(arr, 0)
                  e = RXEvent(RButtonPress)
                  Rset_state(e, (bits or 0))
                  Rset_event(arr, e)
                  RXtCallCallbacks(button, RXmNactivateCallback, arr)
                else
                  snd_display("arrowbutton %s has no active callbacks", RXtName(button))
                end
              else
                snd_display("%s (%s) is not a push or toggle button",
                            RXtName(button),
                            RXtName(RXtParent(button)))
              end
            end
          end
        else
          snd_display("%s is not sensitive", RXtName(button))
        end
      else
        snd_display("%s is not a widget", button)
      end
    end

    def resize_pane(wid, height)
      RXtUnmanageChild(wid)
      RXtVaSetValues(wid,
                     [RXmNpaneMinimum, (height > 5 ? (height - 5) : 0),
                      RXmNpaneMaximum, height + 5])
      RXtManageChild(wid)
      RXtVaSetValues(wid, [RXmNpaneMinimum, 5, RXmNpaneMaximum, 1000])
    end

    def force_event
      app = main_widgets.car
      msk = RXtIMXEvent | RXtIMAlternateInput
      until (RXtAppPending(app) & msk).zero?
        RXtDispatchEvent(RXtAppNextEvent(app))
      end
    end

    def take_keyboard_focus(wid)
      if RXmIsTraversable(wid) and (RXmGetVisibility(wid) != RXmVISIBILITY_FULLY_OBSCURED)
        RXmProcessTraversal(wid, RXmTRAVERSE_CURRENT)
      end
    end

    def move_scale(scl, val)
      RXmScaleSetValue(scl, val)
      cb = RXmScaleCallbackStruct()
      Rset_value(cb, val)
      Rset_event(cb, RXEvent())
      RXtCallCallbacks(scl, RXmNvalueChangedCallback, cb)
    end

    def change_prop(winat, name, command)
      find_window = lambda do |dpy, top, natom|
        res = RXGetWindowProperty(dpy, top, natom, 0, 1024, false, RXA_STRING)
        if res[0] == RSuccess and res[1] != [:Atom, RNone]
          top
        else
          vals = RXQueryTree(dpy, top)
          if vals[0].zero?
            false
          else
            vals[3].each do |win|
              if RWindow?(val = find_window.call(dpy, win, natom))
                return val
              end
            end
            false
          end
        end
      end
      dpy = RXtDisplay(main_widgets[1])
      natom = RXInternAtom(dpy, winat, false)
      if RWindow?(window = find_window.call(dpy, RDefaultRootWindow(dpy), natom))
        RXChangeProperty(dpy, window, RXInternAtom(dpy, name, false), RXA_STRING, 8,
                         RPropModeReplace, command)
        RXFlush(dpy)
        command
      else
        false
      end
    end

    make_proc_with_setter(:beep_state,
                          # returns amp pitch duration
                          lambda {
                            vals = RXGetKeyboardControl(RXtDisplay(main_widgets.cadr))[1, 3]
                          },
                          # amp pitch dur
                          # set_beep_state([100, 200, 100])
                          lambda { |lst|
                            RXChangeKeyboardControl(RXtDisplay(main_widgets.cadr),
                                                    RKBBellPercent |
                                                    RKBBellPitch |
                                                    RKBBellDuration,
                                                    [0] + lst)
                          })

    def beep
      RXBell(RXtDisplay(main_widgets.cadr), 100)
    end
  end
  include Test_event
end

# snd-test.scm translations
# ---------------- test 00: constants ----------------

Tiny_font_string = $with_test_motif ? "6x12" : $with_test_gtk ? "Sans 8" : "9x15"
Tiny_font_set_string = $with_test_motif ? "9x15" : $with_test_gtk ? "Monospace 10" : "6x12"

# FIXME (INFO)
#
# temp_dir
# save_dir
# ladspa_dir
# peak_env_dir
#
# These variables default to NULL (snd.c/snd-0.h).
# snd-test.scm checks for #f
# snd-test.rb  checks for ""

def test_00
  if sounds or mixes or marks or regions
    snd_display("start up sounds: %s, mixes: %s, marks: %s, regions: %s",
                sounds.inspect,
                mixes.inspect,
                marks.inspect,
                regions.inspect)
  end
  [[:Enved_amplitude, 0],
   [:Bartlett_window, 4],
   [:Bartlett_hann_window, 21],
   [:Blackman2_window, 6],
   [:Blackman3_window, 7],
   [:Blackman4_window, 8],
   [:Blackman5_window, 24],
   [:Blackman6_window, 25],
   [:Blackman7_window, 26],
   [:Blackman8_window, 27],
   [:Blackman9_window, 28],
   [:Blackman10_window, 29],
   [:Bohman_window, 22],
   [:Cauchy_window, 12],
   [:Mlt_sine_window, 33],
   [:Papoulis_window, 34],
   [:Dpss_window, 35],
   [:Sinc_window, 36],
   [:Channels_combined, 1],
   [:Channels_separate, 0],
   [:Channels_superimposed, 2],
   [:Connes_window, 18],
   [:Cursor_in_middle, 3],
   [:Cursor_in_view, 0],
   [:Cursor_on_left, 1],
   [:Cursor_on_right, 2],
   [:Dolph_chebyshev_window, 16],
   [:Exponential_window, 9],
   [:Flat_top_window, 23],
   [:Sync_none, 0],
   [:Sync_all, 1],
   [:Sync_by_sound, 2],
   [:Zoom_focus_active, 2],
   [:Zoom_focus_left, 0],
   [:Zoom_focus_middle, 3],
   [:Zoom_focus_right, 1],
   [:Gaussian_window, 14],
   [:Graph_dots, 1],
   [:Graph_dots_and_lines, 3],
   [:Graph_filled, 2],
   [:Graph_lines, 0],
   [:Graph_lollipops, 4],
   [:Hamming_window, 5],
   [:Hann_window, 1],
   [:Hann_poisson_window, 17],
   [:Kaiser_window, 11],
   [:Keyboard_no_action, 4],
   [:Parzen_window, 3],
   [:Poisson_window, 13],
   [:Rectangular_window, 0],
   [:Riemann_window, 10],
   [:Rv2_window, 30],
   [:Rv3_window, 31],
   [:Rv4_window, 32],
   [:Samaraki_window, 19],
   [:Ultraspherical_window, 20],
   [:Graph_as_sonogram, 1],
   [:Graph_as_spectrogram, 2],
   [:Graph_once, 0],
   [:Graph_as_wavogram, 3],
   [:Enved_spectrum, 1],
   [:Speed_control_as_float, 0],
   [:Speed_control_as_ratio, 1],
   [:Speed_control_as_semitone, 2],
   [:Enved_srate, 2],
   [:Tukey_window, 15],
   [:Welch_window, 2],
   [:Cursor_cross, 0],
   [:Cursor_line, 1],
   [:Dont_normalize, 0],
   [:Envelope_linear, 0],
   [:Envelope_exponential, 1],
   [:Normalize_by_channel, 1],
   [:Normalize_by_sound, 2],
   [:Normalize_globally, 3],
   [:X_axis_in_samples, 1],
   [:X_axis_in_beats, 3],
   [:X_axis_in_measures, 4],
   [:X_axis_in_seconds, 0],
   [:X_axis_as_clock, 5],
   [:X_axis_as_percentage, 2],
   [:Enved_add_point, 0],
   [:Enved_delete_point, 1],
   [:Enved_move_point, 2],
   [:Time_graph, 0],
   [:Transform_graph, 1],
   [:Lisp_graph, 2],
   [:Copy_context, 0],
   [:Cursor_context, 3],
   [:Selection_context, 2],
   [:Mark_context, 4],
   [:Show_no_axes, 0],
   [:Show_all_axes, 1],
   [:Show_x_axis, 2],
   [:Show_all_axes_unlabelled, 3],
   [:Show_x_axis_unlabelled, 4],
   [:Show_bare_x_axis, 5],
   # sndlib constants
   [:Mus_unsupported, 0],
   [:Mus_next, 1],
   [:Mus_aifc, 2],
   [:Mus_riff, 3],
   [:Mus_nist, 6],
   [:Mus_raw, 12],
   [:Mus_ircam, 15],
   [:Mus_aiff, 49],
   [:Mus_bicsf, 5],
   [:Mus_voc, 10],
   [:Mus_svx, 9],
   [:Mus_soundfont, 26],
   [:Mus_rf64, 4],
   [:Mus_caff, 60],
   #
   [:Mus_interp_none, 0],
   [:Mus_interp_linear, 1],
   [:Mus_interp_sinusoidal, 2],
   [:Mus_interp_all_pass, 3],
   [:Mus_interp_lagrange, 4],
   [:Mus_interp_bezier, 5],
   [:Mus_interp_hermite, 6],
   #
   [:Mus_chebyshev_first_kind, 1],
   [:Mus_chebyshev_second_kind, 2],
   #
   [:Mus_unknown, 0],
   [:Mus_bshort, 1],
   [:Mus_lshort, 10],
   [:Mus_mulaw, 2],
   [:Mus_alaw, 6],
   [:Mus_byte, 3],
   [:Mus_ubyte, 7],
   [:Mus_bfloat, 4],
   [:Mus_lfloat, 12],
   [:Mus_bint, 5],
   [:Mus_lint, 11],
   [:Mus_bintn, 17],
   [:Mus_lintn, 18],
   [:Mus_b24int, 8],
   [:Mus_l24int, 16],
   [:Mus_bdouble, 9],
   [:Mus_ldouble, 13],
   [:Mus_ubshort, 14],
   [:Mus_ulshort, 15],
   [:Mus_bfloat_unscaled, 19],
   [:Mus_lfloat_unscaled, 20],
   [:Mus_bdouble_unscaled, 21],
   [:Mus_ldouble_unscaled, 22]].each do |sym, req|
    snd_test_neq(Module.const_get(sym), req, "%s", sym)
  end
  #
  old_dir = temp_dir
  set_temp_dir(false)
  [[:region_graph_style, Graph_lines],
   [:ask_about_unsaved_edits, false],
   [:show_full_duration, false],
   [:show_full_range, false],
   [:initial_beg, 0.0],
   [:initial_dur, 0.1],
   [:ask_before_overwrite, false],
   [:audio_output_device, 0],
   [:auto_resize, true],
   [:auto_update, false],
   [:channel_style, 1],
   [:color_cutoff, 0.003],
   [:color_inverted, true],
   [:color_scale, 1.0],
   [:auto_update_interval, 60.0],
   [:cursor_update_interval, 0.05],
   [:cursor_location_offset, 0],
   [:dac_combines_channels, true],
   [:dac_size, 256],
   [:minibuffer_history_length, 8],
   [:clipping, false],
   [:default_output_chans, 1],
   [:default_output_data_format, Mus_lfloat],
   [:default_output_srate, 44100],
   [:default_output_header_type, Mus_next],
   [:dot_size, 1],
   [:cursor_size, 15],
   [:cursor_style, Cursor_cross],
   [:tracking_cursor_style, Cursor_line],
   [:enved_base, 1.0],
   [:enved_clip?, true],
   [:enved_filter, true],
   [:enved_filter_order, 40],
   [:enved_in_dB, false],
   [:enved_style, Envelope_linear],
   [:enved_power, 3.0],
   [:enved_target, 0],
   [:enved_wave?, false],
   [:enved_envelope, nil],
   [:eps_file, "snd.eps"],
   [:eps_bottom_margin, 0.0],
   [:eps_left_margin, 0.0],
   [:eps_size, 1.0],
   [:fft_window_alpha, 0.0],
   [:fft_window_beta, 0.0],
   [:fft_log_frequency, false],
   [:fft_log_magnitude, false],
   [:fft_with_phases, false],
   [:transform_size, 512],
   [:transform_graph_type, Graph_once],
   [:fft_window, 6],
   [:graph_cursor, 34],
   [:graph_style, Graph_lines],
   [:graphs_horizontal, true],
   [:html_dir, "."],
   [:html_program, "firefox"],
   [:just_sounds, true],
   [:listener_prompt, ">"],
   [:max_transform_peaks, 100],
   [:max_regions, 16],
   [:max_virtual_ptrees, 32],
   [:min_dB, -60.0],
   [:log_freq_start, 32.0],
   [:selection_creates_region, true],
   [:transform_normalization, Normalize_by_channel],
   [:view_files_sort, 0],
   [:print_length, 12],
   [:play_arrow_size, 10],
   [:save_state_file, "saved-snd.rb"],
   [:show_axes, 1],
   [:show_transform_peaks, false],
   [:show_indices, false],
   [:show_marks, true],
   [:show_mix_waveforms, true],
   [:show_selection_transform, false],
   [:show_y_zero, false],
   [:show_grid, false],
   [:grid_density, 1.0],
   [:show_sonogram_cursor, false],
   [:sinc_width, 10],
   [:spectrum_end, 1.0],
   [:spectro_hop, 4],
   [:spectrum_start, 0.0],
   [:spectro_x_angle, ($with_test_gl ? 300.0 : 90.0)],
   [:spectro_x_scale, ($with_test_gl ? 1.5 : 1.0)],
   [:spectro_y_angle, ($with_test_gl ? 320.0 : 0.0)],
   [:spectro_y_scale, 1.0],
   [:spectro_z_angle, ($with_test_gl ? 0.0 : 358.0)],
   [:spectro_z_scale, ($with_test_gl ? 1.0 : 0.1)],
   [:temp_dir, ""],
   [:ladspa_dir, ""],
   [:peak_env_dir, ""],
   [:tiny_font, Tiny_font_string],
   [:transform_type, $fourier_transform],
   [:trap_segfault, true],
   [:with_file_monitor, true],
   # FIXME (INFO)
   # Ruby doesn't optimize
   [:optimization, 0],
   [:clm_table_size, 512],
   [:clm_default_frequency, 0.0],
   [:with_verbose_cursor, false],
   [:with_inset_graph, false],
   [:with_interrupts, true],
   [:remember_sound_state, false],
   [:with_smpte_label, false],
   [:with_toolbar, ($with_test_gtk ? true : false)],
   [:with_tooltips, true],
   [:with_menu_icons, false],
   [:save_as_dialog_src, false],
   [:save_as_dialog_auto_comment, false],
   [:with_pointer_focus, false],
   [:wavelet_type, 0],
   [:time_graph_type, Graph_once],
   [:wavo_hop, 3],
   [:wavo_trace, 64],
   [:x_axis_style, 0],
   [:beats_per_minute, 60.0],
   [:beats_per_measure, 4],
   [:zero_pad, 0],
   [:zoom_focus_style, 2],
   [:sync_style, Sync_by_sound],
   [:mix_waveform_height, 20],
   [:mix_tag_width, 6],
   [:mix_tag_height, 14],
   [:mark_tag_width, 10],
   [:mark_tag_height, 4]].each do |sym, req|
    snd_test_neq(set_snd_func(sym, snd_func(sym)), req, "set_%s", sym)
  end
  set_temp_dir(old_dir)
  #
  set_max_transform_peaks(-123)
  set_zero_pad(-123)
  [[:max_transform_peaks, 100],
   [:zero_pad, 0]].each do |sym, req|
    snd_test_neq(set_snd_func(sym, snd_func(sym)), req, "set_%s", sym)
  end
  snd_test_neq(zero_pad(true, true), nil, "zero_pad(true, true)")
  if $with_test_motif
    [:axis_label_font,
     :axis_numbers_font,
     :tiny_font,
     :peaks_font,
     :bold_peaks_font].each do |sym|
      req = snd_func(sym)
      snd_test_neq(set_snd_func(sym, "8x123"), req, "set_%s to bogus value", sym)
    end
  end
  set_ask_about_unsaved_edits(false)
  set_remember_sound_state(false)
end

# ---------------- test 01: defaults ----------------

$good_colormap   = $hot_colormap
$better_colormap = $black_and_white_colormap

def test_01
  if $with_test_gui
    unless colormap?($good_colormap)
      $good_colormap = false
      (1..20).each do |i|
        col = integer2colormap(i)
        if colormap?(col)
          $good_colormap = col
          break
        end
      end
    end
    unless colormap?($better_colormap)
      $better_colormap = false
      (colormap2integer($good_colormap)..20).each do |i|
        col = integer2colormap(i)
        if colormap?(col)
          $better_colormap = col
          break
        end
      end
    end
  end
  #
  old_dir = temp_dir
  set_temp_dir(false)
  [["amp_control", without_errors do amp_control() end, :no_such_sound],
   ["amp_control_bounds", amp_control_bounds()[1], 8.0],
   ["ask_about_unsaved_edits", ask_about_unsaved_edits(), false],
   ["ask_before_overwrite", ask_before_overwrite(), false],
   ["audio_output_device", audio_output_device(), 0],
   ["auto_resize", auto_resize(), true],
   ["auto_update", auto_update(), false],
   ["auto_update_interval", auto_update_interval(), 60.0],
   ["beats_per_measure", beats_per_measure(), 4],
   ["beats_per_minute", beats_per_minute(), 60.0],
   ["channel_style", channel_style(), 1],
   ["clipping", clipping(), false],
   ["clm_table_size", clm_table_size(), 512],
   ["clm_default_frequency", clm_default_frequency(), 0.0],
   ["color_cutoff", color_cutoff(), 0.003],
   ["color_inverted", color_inverted(), true],
   ["color_scale", color_scale(), 1.0],
   ["colormap", colormap(), $good_colormap],
   ["contrast_control", without_errors do contrast_control() end, :no_such_sound],
   ["contrast_control_amp", contrast_control_amp(), 1.0],
   ["contrast_control_bounds", contrast_control_bounds()[1], 10.0],
   ["contrast_control?", without_errors do contrast_control?() end, :no_such_sound],
   ["with_tracking_cursor", with_tracking_cursor(), false],
   ["cursor_location_offset", cursor_location_offset(), 0],
   ["cursor_size", cursor_size(), 15],
   ["cursor_style", cursor_style(), Cursor_cross],
   ["cursor_update_interval", cursor_update_interval(), 0.05],
   ["dac_combines_channels", dac_combines_channels(), true],
   ["dac_size", dac_size(), 256],
   ["default_output_chans", default_output_chans(), 1],
   ["default_output_data_format", default_output_data_format(), Mus_lfloat],
   ["default_output_header_type", default_output_header_type(), Mus_next],
   ["default_output_srate", default_output_srate(), 44100],
   ["dot_size", dot_size(), 1],
   ["enved_base", enved_base(), 1.0],
   ["enved_clip?", enved_clip?(), true],
   ["enved_envelope", enved_envelope(), nil],
   ["enved_filter", enved_filter(), true],
   ["enved_filter_order", enved_filter_order(), 40],
   ["enved_in_dB", enved_in_dB(), false],
   ["enved_power", enved_power(), 3.0],
   ["enved_style", enved_style(), Envelope_linear],
   ["enved_target", enved_target(), 0],
   ["enved_wave?", enved_wave?(), false],
   ["eps_bottom_margin", eps_bottom_margin(), 0.0],
   ["eps_file", eps_file(), "snd.eps"],
   ["eps_left_margin", eps_left_margin(), 0.0],
   ["eps_size", eps_size(), 1.0],
   ["expand_control", without_errors do expand_control() end, :no_such_sound],
   ["expand_control_bounds", expand_control_bounds()[1], 20.0],
   ["expand_control_hop", expand_control_hop(), 0.05],
   ["expand_control_jitter", expand_control_jitter(), 0.1],
   ["expand_control_length", expand_control_length(), 0.15],
   ["expand_control_ramp", expand_control_ramp(), 0.4],
   ["expand_control?", without_errors do expand_control?() end, :no_such_sound],
   ["fft_log_frequency", fft_log_frequency(), false],
   ["fft_log_magnitude", fft_log_magnitude(), false],
   ["fft_with_phases", fft_with_phases(), false],
   ["fft_window", fft_window(), 6],
   ["fft_window_alpha", fft_window_alpha(), 0.0],
   ["fft_window_beta", fft_window_beta(), 0.0],
   ["filter_control_coeffs", without_errors do filter_control_coeffs() end, :no_such_sound],
   ["filter_control_envelope", without_errors do filter_control_envelope() end, :no_such_sound],
   ["filter_control_in_dB", filter_control_in_dB(), false],
   ["filter_control_in_hz", filter_control_in_hz(), false],
   ["filter_control_order", filter_control_order(), 20],
   ["filter_control?", without_errors do filter_control?() end, :no_such_sound],
   ["graph_cursor", graph_cursor(), 34],
   ["graph_style", graph_style(), Graph_lines],
   ["graphs_horizontal", graphs_horizontal(), true],
   ["grid_density", grid_density(), 1.0],
   ["html_dir", html_dir(), "."],
   ["html_program", html_program(), "firefox"],
   ["initial_beg", initial_beg(), 0.0],
   ["initial_dur", initial_dur(), 0.1],
   ["just_sounds", just_sounds(), true],
   ["ladspa_dir", ladspa_dir(), ""],
   ["peak_env_dir", peak_env_dir(), ""],
   ["lisp_graph?", without_errors do lisp_graph?() end, :no_such_sound],
   ["listener_prompt", listener_prompt(), ">"],
   ["log_freq_start", log_freq_start(), 32.0],
   ["mark_tag_height", mark_tag_height(), 4],
   ["mark_tag_width", mark_tag_width(), 10],
   ["max_regions", max_regions(), 16],
   ["max_virtual_ptrees", max_virtual_ptrees(), 32],
   ["max_transform_peaks", max_transform_peaks(), 100],
   ["min_dB", min_dB(), -60.0],
   ["minibuffer_history_length", minibuffer_history_length(), 8],
   ["mix_tag_height", mix_tag_height(), 14],
   ["mix_tag_width", mix_tag_width(), 6],
   ["mix_waveform_height", mix_waveform_height(), 20],
   ["mus_array_print_length", mus_array_print_length(), 8],
   ["mus_clipping", mus_clipping(), false],
   ["mus_float_equal_fudge_factor", mus_float_equal_fudge_factor(), 0.0000001],
   ["mus_prescaler", mus_prescaler(), 1.0],
   ["optimization", optimization(), 0], # Ruby doesn't optimize
   ["play_arrow_size", play_arrow_size(), 10],
   ["print_length", print_length(), 12],
   ["read_only", without_errors do read_only() end, :no_such_sound],
   ["region_graph_style", region_graph_style(), Graph_lines],
   ["remember_sound_state", remember_sound_state(), false],
   ["reverb_control_feedback", reverb_control_feedback(), 1.09],
   ["reverb_control_length", without_errors do reverb_control_length() end, :no_such_sound],
   ["reverb_control_length_bounds", reverb_control_length_bounds()[1], 5.0],
   ["reverb_control_lowpass", reverb_control_lowpass(), 0.7],
   ["reverb_control_scale", without_errors do reverb_control_scale() end, :no_such_sound],
   ["reverb_control_scale_bounds", reverb_control_scale_bounds()[1], 4.0],
   ["reverb_control?", without_errors do reverb_control?() end, :no_such_sound],
   ["save_as_dialog_auto_comment", save_as_dialog_auto_comment, false],
   ["save_as_dialog_src", save_as_dialog_src, false],
   ["save_state_file", save_state_file(), "saved-snd.rb"],
   ["selection_creates_region", selection_creates_region(), true],
   ["show_axes", show_axes(), 1],
   ["show_controls", show_controls(), false],
   ["show_full_duration", show_full_duration(), false],
   ["show_full_range", show_full_range(), false],
   ["show_grid", show_grid(), false],
   ["show_indices", show_indices(), false],
   ["show_marks", show_marks(), true],
   ["show_mix_waveforms", show_mix_waveforms(), true],
   ["show_selection_transform", show_selection_transform(), false],
   ["show_sonogram_cursor", show_sonogram_cursor(), false],
   ["show_transform_peaks", show_transform_peaks(), false],
   ["show_y_zero", show_y_zero(), false],
   ["sinc_width", sinc_width(), 10],
   ["spectrum_end", spectrum_end(), 1.0],
   ["spectro_hop", spectro_hop(), 4],
   ["spectrum_start", spectrum_start(), 0.0],
   ["spectro_x_angle", spectro_x_angle(), ($with_test_gl ? 300.0 : 90.0)],
   ["spectro_x_scale", spectro_x_scale(), ($with_test_gl ? 1.5 : 1.0)],
   ["spectro_y_angle", spectro_y_angle(), ($with_test_gl ? 320.0 : 0.0)],
   ["spectro_y_scale", spectro_y_scale(), 1.0],
   ["spectro_z_angle", spectro_z_angle(), ($with_test_gl ? 0.0 : 358.0)],
   ["spectro_z_scale", spectro_z_scale(), ($with_test_gl ? 1.0 : 0.1)],
   ["speed_control", without_errors do speed_control() end, :no_such_sound],
   ["speed_control_bounds", speed_control_bounds()[1], 20.0],
   ["sync", without_errors do sync() end, :no_such_sound],
   ["sync_style", sync_style(), Sync_by_sound],
   ["temp_dir", temp_dir(), ""],
   ["time_graph_type", time_graph_type(), Graph_once],
   ["time_graph?", without_errors do time_graph?() end, :no_such_sound],
   ["tiny_font", tiny_font(), Tiny_font_string],
   ["tracking_cursor_style", tracking_cursor_style(), Cursor_line],
   ["transform_graph_type", transform_graph_type(), Graph_once],
   ["transform_graph?", without_errors do transform_graph?() end, :no_such_sound],
   ["transform_normalization", transform_normalization(), Normalize_by_channel],
   ["transform_size", transform_size(), 512],
   ["transform_type", transform_type(), $fourier_transform],
   ["view_files_sort", view_files_sort(), 0],
   ["wavelet_type", wavelet_type(), 0],
   ["wavo_hop", wavo_hop(), 3],
   ["wavo_trace", wavo_trace(), 64],
   ["with_mix_tags", with_mix_tags(), true],
   ["with_relative_panes", with_relative_panes(), true],
   ["with_tracking_cursor", with_tracking_cursor(), false],
   ["with_verbose_cursor", with_verbose_cursor(), false],
   ["with_inset_graph", with_inset_graph(), false],
   ["with_interrupts", with_interrupts(), true],
   ["with_smpte_label", with_smpte_label, false],
   ["with_toolbar", with_toolbar,  ($with_test_gtk ? true : false)],
   ["with_tooltips", with_tooltips, true],
   ["with_menu_icons", with_menu_icons, false],
   ["with_pointer_focus", with_pointer_focus(), false],
   ["x_axis_style", x_axis_style(), 0],
   ["zero_pad", zero_pad(), 0],
   ["zoom_focus_style", zoom_focus_style(), 2]].each do |name, res, req|
    snd_test_neq(res, req, name)
  end
  set_temp_dir(old_dir)
  snd_test_neq($snd_opened_sound, false, "$snd_opened_sound")
  set_ask_about_unsaved_edits(false)
  set_remember_sound_state(false)
end

# ---------------- test 02: headers ----------------

def test_headers(name, chns, sr, dur, typ, frm, loop_start = false, loop_end = false)
  if File.exists?(name)
    file = name
  else
    file = $sf_dir + name
  end
  if File.exists?(file)
    fchns = mus_sound_chans(file)
    fsr = mus_sound_srate(file)
    fdur = mus_sound_duration(file)
    if fchns != chns
      snd_display_prev_caller(snd_format_neq(fchns, chns, "%s chans", name))
    end
    if fsr != sr
      snd_display_prev_caller(snd_format_neq(fsr, sr, "%s srate", name))
    end
    if fneq(fdur, dur)
      snd_display_prev_caller(snd_format_neq(fdur, dur, "%s duration", name))
    end
    ffrm = mus_sound_data_format(file)
    ftyp = mus_sound_header_type(file)
    req = mus_sound_length(file)
    res = mus_sound_datum_size(file) * fdur * fsr * fchns
    if (ffrm != Mus_unknown) and
        (ftyp != 27) and
        (req + 1) < res
      snd_display_prev_caller(snd_format_neq(res, req, "%s length", name))
    end
    fframes = mus_sound_frames(file)
    res = fframes.to_f / fsr
    if fneq(res, fdur)
      snd_display_prev_caller(snd_format_neq(res, fdur, "%s frames", name))
    end
    fsamps = mus_sound_samples(file)
    res = fframes - fsamps / fchns
    if res.abs > 1
      snd_display_prev_caller(snd_format_neq(res, fsamps, "%s samples", name))
    end
    res = mus_header_type_name(ftyp)
    if res != typ
      snd_display_prev_caller(snd_format_neq(res, typ, "%s type", name))
    end
    res = mus_data_format_name(ffrm)
    if res != frm
      snd_display_prev_caller(snd_format_neq(res, frm, "%s format", name))
    end
    lst = mus_sound_loop_info(file)
    if loop_start and loop_end
      if (not lst.nil?) and lst.length > 1
        if lst[0] != loop_start
          snd_display_prev_caller(snd_format_neq(lst[0], loop_start, "%s loop start", name))
        end
        if lst[1] != loop_end
          snd_display_prev_caller(snd_format_neq(lst[1], loop_end, "%s loop end", name))
        end
      else
        snd_display_prev_caller("%s loop info empty: %s?", name, lst.inspect)
      end
    else
      unless lst.nil?
        snd_display_prev_caller("%s thinks it has loop info: %s?", name, lst)
      end
    end
    mus_sound_forget(file)
  else
    if $DEBUG
      snd_display_prev_caller("%s missing?", file)
    end
  end
end

def test_02
  test_headers("5_secs.aiff", 1, 44100, 5.303107, "AIFF", "big endian short (16 bits)")
  test_headers("8svx-8.snd", 1, 22050, 1.88766443729401, "SVX8", "signed byte (8 bits)")
  test_headers("Fnonull.aif", 1, 8000, 0.00112499995157123, "AIFC", "mulaw (8 bits)")
  test_headers("Pmiscck.aif", 1, 8000, 0.00112499995157123, "AIFC", "mulaw (8 bits)")
  test_headers("Pmiscck.wav", 1, 8000, 0.00112499995157123, "RIFF", "mulaw (8 bits)")
  test_headers("Poffset.aif", 1, 8000, 0.00112499995157123, "AIFC", "mulaw (8 bits)")
  test_headers("Porder.aif", 1, 8000, 0.00112499995157123, "AIFC", "mulaw (8 bits)")
  test_headers("Ptjunk.aif", 1, 8000, 0.00112499995157123, "AIFC", "mulaw (8 bits)")
  test_headers("Ptjunk.wav", 1, 8000, 0.00112499995157123, "RIFF", "mulaw (8 bits)")
  test_headers("SINE24-S.WAV", 2, 44100, 2.0, "RIFF", "little endian int (24 bits)")
  test_headers("a1.asf", 1, 16000, 3.736562, "asf", "unknown")
  test_headers("a2.asf", 1, 8000, 4.630625, "asf", "unknown")
  test_headers("addf8.afsp", 1, 8000, 2.9760000705719, "Sun/Next", "big endian short (16 bits)")
  test_headers("addf8.d", 1, 8000, 2.9760000705719, "SPPACK", "big endian short (16 bits)")
  test_headers("addf8.dwd", 1, 8000, 2.976000071, "DiamondWare", "little endian short (16 bits)")
  test_headers("addf8.nh", 2, 44100, 0.269931972, "raw (no header)", "big endian short (16 bits)")
  test_headers("addf8.sd", 1, 8000, 2.9760000705719, "ESPS", "big endian short (16 bits)")
  test_headers("addf8.sf_mipseb", 1, 8000, 2.9760000705719, "IRCAM", "big endian short (16 bits)")
  test_headers("addf8.sf_sun", 1, 8000, 2.9760000705719, "IRCAM", "big endian short (16 bits)")
  test_headers("addf8.sf_vax_b", 1, 8000, 2.9760000705719, "IRCAM", "big endian short (16 bits)")
  test_headers("addf8.wav", 1, 8000, 2.9760000705719, "RIFF", "little endian short (16 bits)")
  test_headers("aebass.krz", 1, 44100, 3.0, "Kurzweil 2000", "big endian short (16 bits)")
  test_headers("aiff-16.snd", 2, 44100, 0.746666669845581, "AIFF", "big endian short (16 bits)")
  test_headers("aiff-8.snd", 2, 44100, 0.746666669845581, "AIFF", "signed byte (8 bits)")
  test_headers("alaw.aifc", 1, 44100, 0.0367800444364548, "AIFC", "alaw (8 bits)")
  test_headers("alaw.wav", 1, 11025, 8.70666694641113, "RIFF", "alaw (8 bits)")
  test_headers("astor_basia.mp2", 2, 44100, 1.022,"raw (no header)", "big endian short (16 bits)")
  test_headers("c.asf", 1, 8000, 21.368126, "asf", "unknown")
  test_headers("ce-c3.w02", 1, 33000, 3.88848495483398, "TX-16W", "unknown")
  test_headers("ce-c4.w03", 1, 33000, 2.91618180274963, "TX-16W", "unknown")
  test_headers("ce-d2.w01", 1, 33000, 3.46439385414124, "TX-16W", "unknown")
  test_headers("clbonef.wav", 1, 22050, 2.57832193374634, "RIFF", "little endian float (32 bits)")
  test_headers("cranker.krz", 1, 44100, 3.48267579, "Kurzweil 2000", "big endian short (16 bits)")
  test_headers("d40130.aif", 1, 10000, 0.100000001490116, "AIFF", "big endian short (16 bits)")
  test_headers("d40130.au", 1, 10000, 0.100000001490116, "Sun/Next", "big endian short (16 bits)")
  test_headers("d40130.dsf", 1, 8000, 0.125, "Delusion", "little endian short (16 bits)")
  test_headers("d40130.fsm", 1, 8000, 0.12524999678, "Farandole", "little endian short (16 bits)")
  test_headers("d40130.iff", 1, 10000, 0.100000001490116, "SVX8", "signed byte (8 bits)")
  test_headers("d40130.pat", 1, 10000, 0.100000001490116, "Gravis Ultrasound patch", "little endian short (16 bits)")
  test_headers("d40130.sds", 1, 10000, 0.100000001490116, "MIDI sample dump", "unknown")
  test_headers("d40130.sdx", 1, 10000, 0.100000001490116, "Sample dump", "unsigned little endian short (16 bits)")
  test_headers("d40130.sf", 1, 10000, 0.100000001490116, "IRCAM", "little endian short (16 bits)")
  test_headers("d40130.smp", 1, 8000, 0.125, "SMP", "little endian short (16 bits)")
  test_headers("d40130.sou", 1, 8000, 0.125, "SBStudioII", "little endian short (16 bits)")
  test_headers("d40130.st3", 1, 8000, 0.125, "Digiplayer ST3", "unsigned little endian short (16 bits)")
  test_headers("d40130.uwf", 1, 8000, 0.1252499, "Ultratracker", "little endian short (16 bits)")
  test_headers("d40130.voc", 1, 10000, 0.100100003182888, "VOC", "unsigned byte (8 bits)")
  test_headers("d40130.w00", 1, 16000, 0.0625, "TX-16W", "unknown")
  test_headers("d40130.wav", 1, 10000, 0.100000001490116, "RIFF", "little endian short (16 bits)")
  test_headers("d43.wav", 1, 10000, 0.100000001490116, "RIFF", "little endian short (16 bits)")
  test_headers("digit0v0.aiff", 1, 8000, 0.560000002384186, "AIFC", "big endian short (16 bits)")
  test_headers("esps-16.snd", 1, 8000, 3.09737491607666, "ESPS", "big endian short (16 bits)")
  test_headers("forest.aiff", 2, 44100, 3.907143, "AIFF", "big endian short (16 bits)", 24981, 144332)
  test_headers("g721.au", 1, 11025, 4.35328817367554, "Sun/Next", "unknown")
  test_headers("g722.aifc", 1, 44100, 0.0184353739023209, "AIFC", "unknown")
  test_headers("gong.wve", 1, 8000, 3.96799993515015, "PSION", "alaw (8 bits)")
  test_headers("gsm610.wav", 1, 11025, 1.7687075138092, "RIFF", "unknown")
  test_headers("inrs-16.snd", 1, 8000, 2.46399998664856, "INRS", "little endian short (16 bits)")
  test_headers("kirk.wve", 1, 8000, 1.40799999237061, "PSION", "alaw (8 bits)")
  test_headers("loop.aiff", 1, 44100, 0.0367120169103146, "AIFC", "big endian short (16 bits)", 12, 23)
  test_headers("m.asf", 1, 8000, 64.964622, "asf", "unknown")
  test_headers("mary-sun4.sig", 1, 8000, 4.47612476348877, "Comdisco SPW signal", "big endian double (64 bits)")
  test_headers("mocksong.wav", 1, 11025, 7.869569301605, "RIFF", "little endian short (16 bits)")
  test_headers("mono24.wav", 1, 22050, 1.98997735977173, "RIFF", "little endian int (24 bits)")
  test_headers("msadpcm.wav", 1, 11025, 4.43501138687134, "RIFF", "unknown")
  test_headers("n8.snd", 1, 44100, 0.0367800444364548, "Sun/Next", "signed byte (8 bits)")
  test_headers("nasahal.aif", 1, 11025, 9.89841270446777, "AIFF", "signed byte (8 bits)")
  test_headers("nasahal.avi", 1, 11025, 10.432744, "AVI", "little endian short (16 bits)")
  test_headers("nasahal.dig", 1, 11025, 9.8984, "Sound Designer 1", "big endian short (16 bits)")
  test_headers("nasahal.ivc", 2, 44100, 0.449, "raw (no header)", "big endian short (16 bits)")
  test_headers("nasahal.pat", 1, 11025, 3.95410442352295, "Gravis Ultrasound patch", "unsigned byte (8 bits)")
  test_headers("nasahal.snd", 1, 11025, 9.89841270446777, "SNDT", "unsigned byte (8 bits)")
  test_headers("nasahal.svx", 1, 11025, 9.89841270446777, "SVX8", "signed byte (8 bits)")
  test_headers("nasahal.v8", 1, 8000, 13.6412496566772, "Covox V8", "unsigned byte (8 bits)")
  test_headers("nasahal.voc", 1, 11025, 9.89941024780273, "VOC", "unsigned byte (8 bits)")
  test_headers("nasahal.vox", 2, 44100, 0.22444, "raw (no header)", "big endian short (16 bits)")
  test_headers("nasahal8.wav", 1, 11025, 9.89841270446777, "RIFF", "unsigned byte (8 bits)")
  test_headers("nasahalad.smp", 1, 11025, 4.94920635223389, "Goldwave sample", "little endian short (16 bits)")
  test_headers("next-16.snd", 1, 22050, 1.00004529953003, "Sun/Next", "big endian short (16 bits)")
  test_headers("next-8.snd", 1, 22050, 0.226757362484932, "Sun/Next", "signed byte (8 bits)")
  test_headers("next-dbl.snd", 1, 22050, 0.226757362484932, "Sun/Next", "big endian double (64 bits)")
  test_headers("oboe.ldbl", 1, 22050, 2.30512475967407, "RIFF", "little endian double (64 bits)")
  test_headers("next-flt.snd", 1, 22050, 0.226757362484932, "Sun/Next", "big endian float (32 bits)")
  test_headers("aifc-float.snd", 1, 22050, 0.2267573624849, "AIFC", "big endian float (32 bits)")
  test_headers("next-mulaw.snd", 1, 8012, 2.03295063972473, "Sun/Next", "mulaw (8 bits)")
  test_headers("next24.snd", 1, 44100, 0.0367800444364548, "Sun/Next", "big endian int (24 bits)")
  test_headers("nist-01.wav", 1, 16000, 2.26912498474121, "NIST", "little endian short (16 bits)")
  test_headers("nist-10.wav", 1, 16000, 2.26912498474121, "NIST", "big endian short (16 bits)")
  test_headers("nist-16.snd", 1, 16000, 1.02400004863739, "NIST", "big endian short (16 bits)")
  test_headers("nist-shortpack.wav", 1, 16000, 4.53824996948242, "NIST", "unknown")
  test_headers("none.aifc", 1, 44100, 0.0367800444364548, "AIFC", "big endian short (16 bits)")
  test_headers("nylon2.wav", 2, 22050, 1.14376413822174, "RIFF", "unknown")
  test_headers("o2.adf", 1, 44100, 0.036780, "CSRE adf", "little endian short (16 bits)")
  test_headers("o2.avr", 1, 44100, 0.0183900222182274, "AVR", "big endian short (16 bits)")
  test_headers("o2.bicsf", 1, 44100, 0.0367800444364548, "IRCAM", "big endian short (16 bits)")
  test_headers("o2.mpeg1", 2, 44100, 0.0070975, "raw (no header)", "big endian short (16 bits)")
  test_headers("o2.sd2", 2, 44100, 0.0183900222, "raw (no header)", "big endian short (16 bits)")
  test_headers("o2.sf2", 1, 44100, 0.036780044436, "SoundFont", "little endian short (16 bits)")
  test_headers("o2.smp", 1, 8000, 0.202749997377396, "SMP", "little endian short (16 bits)")
  test_headers("o2.voc", 1, 44100, 0.0368934236466885, "VOC", "little endian short (16 bits)")
  test_headers("o2.wave", 1, 44100, 0.0367800444364548, "RIFF", "little endian short (16 bits)")
  test_headers("o2_12bit.aiff", 1, 44100, 0.036780044436, "AIFF", "big endian short (16 bits)")
  test_headers("o2_18bit.aiff", 1, 44100, 0.0367800444364548, "AIFF", "big endian int (24 bits)")
  test_headers("o2_711u.wave", 1, 44100, 0.0367800444364548, "RIFF", "mulaw (8 bits)")
  test_headers("o2_722.snd", 1, 44100, 0.0183900222182274, "Sun/Next", "unknown")
  test_headers("o2_726.aiff", 1, 8000, 0.0367499999701977, "AIFC", "unknown")
  test_headers("o2_726.snd", 1, 44100, 0.0230158735066652, "Sun/Next", "unknown")
  test_headers("o2_728.aiff", 1, 8000, 0.0367499999701977, "AIFC", "unknown")
  test_headers("o2_8.iff", 1, 44100, 0.0367800444364548, "SVX8", "signed byte (8 bits)")
  test_headers("o2_8.voc", 1, 44100, 0.0370294786989689, "VOC", "unsigned byte (8 bits)")
  test_headers("o2_dvi.wave", 1, 44100, 0.0232199542224407, "RIFF", "unknown")
  test_headers("o2_float.bicsf", 1, 44100, 0.0367800444, "IRCAM", "big endian float (32 bits)")
  test_headers("o2_gsm.aiff", 1, 8000, 0.0367499999701977, "AIFC", "unknown")
  test_headers("o2_u8.avr", 1, 44100, 0.0367800444364548, "AVR", "unsigned byte (8 bits)")
  test_headers("o2_u8.wave", 1, 44100, 0.0367800444364548, "RIFF", "unsigned byte (8 bits)")
  test_headers("o28.mpc", 1, 44100, 0.036780, "AKAI 4", "little endian short (16 bits)")
  test_headers("oboe.g721", 1, 22050, 1.15287983417511, "Sun/Next", "unknown")
  test_headers("oboe.g723_24", 1, 22050, 0.864761888980865, "Sun/Next", "unknown")
  test_headers("oboe.g723_40", 1, 22050, 1.44126987457275, "Sun/Next", "unknown")
  test_headers("oboe.kts", 1, 22050, 2.305125, "Korg", "big endian short (16 bits)")
  test_headers("oboe.its", 1, 22050, 2.305125, "Impulse Tracker", "little endian short (16 bits)")
  test_headers("oboe.sf2", 1, 22050, 2.305124759674, "SoundFont", "little endian short (16 bits)")
  test_headers("oboe.paf", 1, 22050, 2.305125, "Ensoniq Paris", "big endian short (16 bits)")
  test_headers("oboe.pf1", 1, 22050, 2.305125, "Ensoniq Paris", "little endian short (16 bits)")
  test_headers("oboe.smp", 1, 22050, 2.305125, "snack SMP", "little endian short (16 bits)")
  test_headers("oboe.rf64", 1, 22050, 2.305125, "rf64", "little endian short (16 bits)")
  test_headers("oboe-be32.caf", 1, 22050, 2.305125, "caff", "normalized big endian int (32 bits)")
  test_headers("oboe-bf64.caf", 1, 22050, 2.305125, "caff", "big endian double (64 bits)")
  test_headers("oboe-lf32.caf", 1, 22050, 2.305125, "caff", "little endian float (32 bits)")
  test_headers("oboe-ulaw.caf", 1, 22050, 2.305125, "caff", "mulaw (8 bits)")
  test_headers("oboe.nsp", 1, 22050, 2.305125, "CSL", "little endian short (16 bits)")
  test_headers("oboe.nvf", 1, 8000, 6.353500, "Creative NVF", "unknown")
  test_headers("oboe-ulaw.voc", 1, 22050, 2.305669, "VOC", "mulaw (8 bits)")
  test_headers("oboe-lf32.sf", 1, 22050, 2.305669, "IRCAM", "little endian float (32 bits)")
  test_headers("oboe.wfp", 1, 22050, 2.305125, "Turtle Beach", "little endian short (16 bits)")
  test_headers("oboe.sox", 1, 22050, 2.305125, "Sox", "normalized little endian int (32 bits)")
  test_headers("oki.snd", 2, 44100, 0.004195011, "raw (no header)", "big endian short (16 bits)")
  test_headers("oki.wav", 1, 44100, 0.016780, "RIFF", "unknown")
  test_headers("orv-dvi-adpcm.wav", 1, 44100, 1.92725622653961, "RIFF", "unknown")
  test_headers("riff-16.snd", 1, 22050, 1.88766443729401, "RIFF", "little endian short (16 bits)")
  test_headers("riff-8-u.snd", 1, 11025, 0.506848096847534, "RIFF", "unsigned byte (8 bits)")
  test_headers("rooster.wve", 1, 8000, 2.04800009727478, "PSION", "alaw (8 bits)")
  test_headers("sd1-16.snd", 1, 44100, 0.40054, "Sound Designer 1", "big endian short (16 bits)")
  test_headers("sf-16.snd", 1, 22050, 1.88766443729401, "IRCAM", "big endian short (16 bits)")
  test_headers("si654.adc", 1, 16000, 6.71362495422363, "ADC/OGI", "big endian short (16 bits)")
  test_headers("smp-16.snd", 1, 8000, 5.2028751373291, "SMP", "little endian short (16 bits)")
  test_headers("sound.pat", 1, 8000, 1.95050001144409, "Gravis Ultrasound patch", "unsigned little endian short (16 bits)")
  test_headers("sound.sap", 1, 8000, 1.95050001144409, "Goldwave sample", "little endian short (16 bits)")
  test_headers("sound.sds", 1, 8000, 1.95050001144409, "MIDI sample dump", "unknown")
  test_headers("sound.sfr", 1, 8000, 1.95050001144409, "SRFS", "little endian short (16 bits)")
  test_headers("sound.v8", 1, 8000, 1.95050001144409, "Covox V8", "unsigned byte (8 bits)")
  test_headers("sound.vox", 2, 44100, 0.0442177, "raw (no header)", "big endian short (16 bits)")
  test_headers("step.omf", 1, 11025, 8.70666694641113, "OMF", "signed byte (8 bits)")
  test_headers("step.qt", 1, 11025, 8.70630359649658, "Quicktime", "unsigned byte (8 bits)")
  test_headers("sun-16-afsp.snd", 1, 8000, 2.9760000705719, "Sun/Next", "big endian short (16 bits)")
  test_headers("sun-mulaw.snd", 1, 8000, 4.61950016021729, "Sun/Next", "mulaw (8 bits)")
  test_headers("sw1038t_short.wav", 2, 8000, 6.0, "NIST", "mulaw (8 bits)")
  test_headers("swirl.pat", 1, 22050, 1.0619500875473, "Gravis Ultrasound patch", "unsigned little endian short (16 bits)")
  test_headers("sy85.snd", 1, 8000, 5.05600023269653, "Sy-85", "big endian short (16 bits)")
  test_headers("sy99.snd", 1, 8000, 4.54400014877319, "Sy-99", "big endian short (16 bits)")
  test_headers("telephone.wav", 1, 16000, 2.2788124084, "NIST", "little endian short (16 bits)")
  test_headers("trumps22.adp", 1, 22050, 3.092880, "RIFF", "unknown")
  test_headers("truspech.wav", 1, 8000, 1.1599999666214, "RIFF", "unknown")
  test_headers("ulaw.aifc", 1, 44100, 0.0367800444364548, "AIFC", "mulaw (8 bits)")
  test_headers("voc-8-u.snd", 1, 8000, 1.49937498569489, "VOC", "unsigned byte (8 bits)")
  test_headers("o28.voc", 1, 44100, 0.036893, "VOC", "little endian short (16 bits)")
  test_headers("voxware.wav", 1, 8000, 0.324000000953674, "RIFF", "unknown")
  test_headers("wd.w00", 1, 8000, 0.202749997377396, "Sy-99", "big endian short (16 bits)")
  test_headers("wd1.smp", 1, 8000, 0.202749997377396, "SMP", "little endian short (16 bits)")
  test_headers("wd1.wav", 1, 44100, 0.0367800444364548, "RIFF", "little endian short (16 bits)")
  test_headers("wheel.mat", 2, 44100, 0.14564626, "raw (no header)", "big endian short (16 bits)")
  test_headers("b8.pvf", 1, 44100, 0.036803, "Portable Voice Format", "signed byte (8 bits)")
  test_headers("b16.pvf", 1, 44100, 0.0368, "Portable Voice Format", "big endian short (16 bits)")
  test_headers("b32.pvf", 1, 44100, 0.036803, "Portable Voice Format", "big endian int (32 bits)")
  test_headers("water.voc", 2, 32000, 42.3463897705078, "VOC", "little endian short (16 bits)")
  test_headers("wood.dsf", 1, 8000, 0.202749997377, "Delusion", "little endian short (16 bits)")
  test_headers("wood.dvi", 1, 22100, 0.0278733037412167, "RIFF", "unknown")
  test_headers("wood.dwd", 1, 22100, 0.0733936652541161, "DiamondWare", "signed byte (8 bits)")
  test_headers("wood.fsm", 1, 8000, 0.2029999942, "Farandole", "little endian short (16 bits)")
  test_headers("wood.mad", 1, 22100, 0.0372398197650909, "RIFF", "unknown")
  test_headers("wood.maud", 1, 44100, 0.0183900222182274, "MAUD", "big endian short (16 bits)")
  test_headers("wood.pat", 1, 22100, 0.0733936652541161, "Gravis Ultrasound patch", "little endian short (16 bits)")
  test_headers("wood.riff", 1, 44100, 0.0367800444364548, "RIFF", "little endian short (16 bits)")
  test_headers("wood.rifx", 1, 44100, 0.0367800444364548, "RIFF", "big endian short (16 bits)")
  test_headers("wood.sds", 1, 22100, 0.0733936652541161, "MIDI sample dump", "unknown")
  test_headers("wood.sdx", 1, 22100, 0.0733936652541161, "Sample dump", "unsigned little endian short (16 bits)")
  test_headers("wood.sf", 1, 44100, 0.0367800444364548, "IRCAM", "big endian short (16 bits)")
  test_headers("wood.sndr", 2, 44100, 0.009229, "raw (no header)", "big endian short (16 bits)")
  test_headers("wood.sndt", 1, 44100, 0.0367800444364548, "SNDT", "unsigned byte (8 bits)")
  test_headers("wood.st3", 1, 8000, 0.202749997377396, "Digiplayer ST3", "unsigned little endian short (16 bits)")
  test_headers("wood.uwf", 1, 8000, 0.202999994, "Ultratracker", "little endian short (16 bits)")
  test_headers("wood.w00", 1, 16000, 0.101374998688698, "TX-16W", "unknown")
  test_headers("wood12.aiff", 1, 44100, 0.0367800444364548, "AIFF", "big endian short (16 bits)")
  test_headers("wood16.dwd", 2, 44100, 0.03678004, "DiamondWare", "little endian short (16 bits)")
  test_headers("wood16.wav", 2, 44100, 0.03678004, "RIFF", "little endian short (16 bits)")
  test_headers("wood16.nsp", 2, 44100, 0.03678004, "CSL", "little endian short (16 bits)")
  test_headers("wood16.smp", 2, 44100, 0.03678004, "snack SMP", "little endian short (16 bits)")
  test_headers("wood24.aiff", 1, 44100, 0.0367800444364548, "AIFF", "big endian int (24 bits)")
  test_headers("woodblock.aiff", 1, 44100, 0.03678, "AIFF", "big endian short (16 bits)")
  test_headers("woodflt.snd", 1, 44100, 0.0367800444364548, "Sun/Next", "big endian float (32 bits)")
  test_headers("RealDrums.sf2", 1, 44100, 6.397256, "SoundFont", "little endian short (16 bits)")
  test_headers("32bit.sf", 1, 44100, 4.6, "IRCAM", "little endian float (32 bits, unscaled)")
  test_headers("PCM_48_8bit_m.w64", 1, 48000, 0.375, "SoundForge", "unsigned byte (8 bits)")
  test_headers("oboe.sf6", 1, 22050, 2.305125, "SoundForge", "little endian short (16 bits)")
  test_headers("addf8.24we", 1, 8000, 2.976000, "RIFF", "little endian int (24 bits)")
  test_headers("hybrid.snd", 1, 44100, 4.600000, "BICSF", "big endian float (32 bits)")
  test_headers("litmanna.sf", 1, 44100, 0.533, "IRCAM", "little endian short (16 bits)")
  test_headers("M1F1-float64C-AFsp.aif", 2, 8000, 2.9366, "AIFC", "big endian double (64 bits)")
  test_headers("MacBoing.wav", 1, 11127, 0.696, "RIFF", "unsigned byte (8 bits)")
  test_headers("t15.aiff", 2, 44100, 135.00, "AIFC", "little endian short (16 bits)")
  test_headers("tomf8.aud", 1, 8000, 2.016000, "INRS", "little endian short (16 bits)")
  test_headers("Xhs001x.nsp", 1, 10000, 6.017400, "CSL", "little endian short (16 bits)")
  test_headers("zulu_a4.w11", 1, 33000, 1.21987879276276, "TX-16W", "unknown", 23342, 40042)
  #
  name = "forest.aiff"
  with_file(name) do |file|
    snd_test_neq(mus_sound_mark_info(file),
                 [[4, 0], [3, 0], [2, 144332], [1, 24981]],
                 "mus_sound_mark_info %s", name)
  end
  name = "traffic.aiff"
  with_file(name) do |file|
    snd_test_neq(mus_sound_mark_info(file),
                 [[4, 1], [3, 0], [2, 171931], [1, 99461]],
                 "mus_sound_mark_info %s", name)
  end
end

# ---------------- test 03: variables ----------------

def test_03
  ind = open_sound("oboe.snd")
  test_dir = $HOME + "/test"
  if File.exists?(test_dir)
    old_val = temp_dir
    snd_test_neq(set_temp_dir(test_dir), test_dir, "set_temp_dir")
    set_temp_dir(old_val)
  end
  snd_test_neq(sample(1000), 0.0328, "sample 1000")
  #
  [$output_name_hook,
   $output_comment_hook,
   $peak_env_hook,
   $help_hook,
   $mark_drag_hook,
   $mix_drag_hook,
   $mouse_drag_hook,
   $mouse_click_hook,
   $mouse_press_hook,
   $start_playing_hook,
   $start_playing_selection_hook,
   $stop_playing_hook,
   $key_press_hook,
   $snd_error_hook,
   $snd_warning_hook,
   $name_click_hook,
   $after_apply_controls_hook,
   $enved_hook,
   $mouse_enter_label_hook,
   $mouse_enter_graph_hook,
   $mouse_enter_listener_hook,
   $mouse_leave_label_hook,
   $mouse_leave_graph_hook,
   $mouse_leave_listener_hook,
   $initial_graph_hook,
   $after_graph_hook,
   $graph_hook].each_with_index do |h, i|
    if (not hook?(h)) or (not h.empty?)
      snd_display("%d: %s?", i, h.inspect)
    end
  end
  #
  if $with_test_gui
    old_ctrl = show_controls
    set_show_controls(true)
    req = enved_dialog
    snd_test_neq(dialog_widgets[2], req, "enved_dialog")
    req = [0.0, 0.0, 1.0, 1.0, 2.0, 0.0]
    set_enved_envelope(req)
    snd_test_neq(enved_envelope, req, "set_enved_envelope")
    set_enved_envelope(enved_envelope())
    snd_test_neq(enved_envelope, req, "set_enved_envelope to self")
    set_show_controls(old_ctrl)
  end
  #
  gui_lst = [[:color_cutoff, 0.003, 0.01],
             [:color_inverted, true, false],
             [:color_scale, 1.0, 0.5],
             [:contrast_control?, false, true],
             [:enved_base, 1.0, 1.5],
             [:enved_in_dB, false, true],
             [:enved_target, 0, 1],
             [:enved_wave?, false, true],
             [:expand_control?, false, true],
             [:fft_log_frequency, false, true],
             [:fft_log_magnitude, false, true],
             [:fft_with_phases, false, true],
             [:enved_filter_order, 40, 20],
             [:filter_control?, false, true],
             [:transform_normalization, Normalize_by_channel, Dont_normalize],
             [:reverb_control?, false, true],
             [:show_transform_peaks, false, true],
             [:show_selection_transform, false, true],
             [:spectrum_end, 1.0, 0.7],
             [:spectro_hop, 4, 10],
             [:spectrum_start, 0.0, 0.1],
             [:spectro_x_angle, ($with_test_gl ? 300.0 : 90.0), 60.0],
             [:spectro_x_scale, ($with_test_gl ? 1.5 : 1.0), 2.0],
             [:spectro_y_angle, ($with_test_gl ? 320.0 : 0.0), 60.0],
             [:spectro_y_scale, 1.0, 2.0],
             [:spectro_z_angle, ($with_test_gl ? 0.0 : 358.0), 60.0],
             [:spectro_z_scale, ($with_test_gl ? 1.0 : 0.1), 0.2]]
  lst = [[:amp_control, 1.0, 0.5],
         [:amp_control_bounds, [0.0, 8.0], [1.0, 5.0]],
         [:ask_about_unsaved_edits, false, true],
         [:ask_before_overwrite, false, true],
         [:audio_input_device, 0, 1],
         [:audio_output_device, 0, 1],
         [:auto_resize, true, false],
         [:auto_update, false, true],
         [:channel_style, 0, 1],
         [:colormap, $good_colormap, $better_colormap],
         [:contrast_control, 0.0, 0.5],
         [:contrast_control_bounds, [0.0, 10.0], [1.0, 5.0]],
         [:contrast_control_amp, 1.0, 0.5],
         [:auto_update_interval, 60.0, 120.0],
         [:cursor_update_interval, 0.05, 0.1],
         [:cursor_location_offset, 0, 32768],
         [:with_tracking_cursor, false, true],
         [:cursor_size, 15, 30],
         [:cursor_style, Cursor_cross, Cursor_line],
         [:tracking_cursor_style, Cursor_line, Cursor_cross],
         [:dac_combines_channels, true, false],
         [:dac_size, 256, 512],
         [:minibuffer_history_length, 8, 16],
         [:clipping, false, true],
         [:default_output_chans, 1, 2],
         [:default_output_data_format, 1, 1],
         [:default_output_srate, 22050, 44100],
         [:default_output_header_type, Mus_next, Mus_aifc],
         [:dot_size, 1, 4],
         [:enved_clip?, false, true],
         [:enved_style, Envelope_linear, Envelope_exponential],
         [:enved_power, 3.0, 3.5],
         [:eps_file, "snd.eps", "snd-1.eps"],
         [:eps_left_margin, 0.0, 72.0],
         [:eps_size, 1.0, 2.0],
         [:eps_bottom_margin, 0.0, 36.0],
         [:expand_control, 1.0, 2.0],
         [:expand_control_bounds, [0.001, 20.0], [1.0, 2.0]],
         [:expand_control_hop, 0.05, 0.1],
         [:expand_control_jitter, 0.1, 0.2],
         [:expand_control_length, 0.15, 0.2],
         [:expand_control_ramp, 0.4, 0.2],
         [:fft_window_alpha, 0.0, 1.0],
         [:fft_window_beta, 0.0, 0.5],
         [:transform_size, 512, 1024],
         [:transform_graph_type, Graph_once, Graph_as_sonogram],
         [:fft_window, 6, 5],
         [:transform_graph?, false, true],
         [:filter_control_in_dB, false, true],
         [:filter_control_envelope, [0.0, 1.0, 1.0, 1.0], [0.0, 1.0, 1.0, 0.0]],
         [:enved_filter, true, false],
         [:filter_control_in_hz, false, true],
         [:filter_control_order, 20, 40],
         [:graph_cursor, 34, 32],
         [:graph_style, 0, 1],
         [:initial_beg, 0.0, 1.0],
         [:initial_dur, 0.1, 1.0],
         [:just_sounds, false, true],
         [:listener_prompt, ">", ":"],
         [:max_transform_peaks, 100, 10],
         [:max_regions, 16, 6],
         [:min_dB, -60.0, -90.0],
         [:log_freq_start, 32.0, 10.0],
         [:mix_waveform_height, 20, 40],
         [:mix_tag_height, 14, 20],
         [:mix_tag_width, 6, 20],
         [:mark_tag_height, 4, 20],
         [:mark_tag_width, 10, 20],
         [:mus_prescaler, 1.0, 100.0],
         [:mus_clipping, false, true],
         [:selection_creates_region, true, false],
         [:view_files_sort, 0, 1],
         [:play_arrow_size, 10, 16],
         [:print_length, 12, 16],
         [:region_graph_style, Graph_lines, Graph_lollipops],
         [:reverb_control_decay, 1.0, 2.0],
         [:reverb_control_feedback, 1.09, 1.6],
         [:reverb_control_length, 1.0, 2.0],
         [:reverb_control_length_bounds, [0.0, 0.5], [1.0, 2.0]],
         [:reverb_control_lowpass, 0.7, 0.9],
         [:reverb_control_scale, 0.0, 0.2],
         [:reverb_control_scale_bounds, [0.0, 4.0], [0.0, 0.2]],
         [:show_axes, 1, 0],
         [:show_full_duration, false, true],
         [:show_full_range, false, true],
         [:show_indices, false, true],
         [:show_marks, true, false],
         [:show_mix_waveforms, true, false],
         [:show_y_zero, false, true],
         [:show_grid, false, true],
         [:grid_density, 1.0, 0.5],
         [:show_sonogram_cursor, false, true],
         [:sinc_width, 10, 40],
         [:speed_control, 1.0, 0.5],
         [:speed_control_bounds, [0.05, 20.0], [1.0, 5.0]],
         [:speed_control_style, 0, 1],
         [:speed_control_tones, 12, 18],
         [:sync, 0, 1],
         [:sync_style, Sync_by_sound, Sync_all],
         [:tiny_font, Tiny_font_string, Tiny_font_set_string],
         [:transform_type, $fourier_transform, $autocorrelation],
         [:with_verbose_cursor, false, true],
         [:wavelet_type, 0, 1],
         [:time_graph?, false, true],
         # FIXME (segfault, see below)
         # [:time_graph_type, Graph_once, Graph_as_wavogram],
         [:wavo_hop, 3, 6],
         [:wavo_trace, 64, 128],
         [:with_mix_tags, true, false],
         [:with_relative_panes, true, false],
         [:with_gl, $with_test_gl, false],
         [:x_axis_style, 0, 1],
         [:beats_per_minute, 30.0, 120.0],
         [:beats_per_measure, 1, 120],
         [:zero_pad, 0, 1],
         [:zoom_focus_style, 2, 1]]
  if $with_test_gui
    lst += gui_lst
  end
  if $with_test_motif
    # FIXME (GTK segfault)
    # doesn't work with GTK
    # set_time_graph_type(Graph_as_wavogram) ==> segfault
    lst += [[:time_graph_type, Graph_once, Graph_as_wavogram]]
  end
  lst.each do |sym, initval, newval|
    next unless symbol?(sym)
    2.times do |i|
      set_snd_func(sym, newval)
      snd_test_neq(snd_func(sym), newval, "set_%s[%d]", sym, i)
      set_snd_func(sym, initval)
    end
  end
  #
  [if $with_test_gui
     [:amp_control, 1.0, [-1.0, 123.123]]
   end,
   [:amp_control_bounds, [0.0, 8.0], [false, [0.0], [1.0, 0.0], 2.0]],
   [:channel_style, 0, [32, -1, 1.0]],
   [:colormap, $good_colormap, [321, -123]],
   [:color_cutoff, 0.003, [-1.0, 123.123]],
   [:color_scale, 1.0, [-32.0, 2000.0]],
   if $with_test_motif
     # FIXME (GTK segfault)
     # doesn't work with GTK and ruby 1.9.2
     # set_contrast_control(xxx) ==> segfault
     [:contrast_control, 0.0, [-123.123, 123.123]]
   end,
   [:contrast_control_bounds, [0.0, 10.0], [false, [0.0], [1.0, 0.0], 2.0]],
   [:cursor_size, 15, [1.123, -2.5]],
   [:dac_size, 256, [-1, 0, -123]],
   [:dot_size, 1, [0, -1, -123]],
   [:enved_target, 0, [123, -321]],
   [:expand_control, 1.0, [-1.0, 0.0]],
   [:expand_control_bounds, [0.001, 20.0], [false, [0.0], [1.0, 0.0], 2.0]],
   [:expand_control_hop, 0.05, [-1.0]],
   [:expand_control_length, 0.15, [-1.0, 0.0]],
   [:expand_control_ramp, 0.4, [-1.0, 1.0, 123.123]],
   [:fft_window_alpha, 0.0, [-1.0, 123.123]],
   [:fft_window_beta, 0.0, [-1.0, 123.123]],
   [:transform_size, 512, [-1, 0]],
   [:zero_pad, 0, [-1, -123]],
   [:cursor_style, Cursor_cross, [-1]],
   [:cursor_style, Cursor_line, [2, 123]],
   [:tracking_cursor_style, Cursor_line, [-1]],
   [:tracking_cursor_style, Cursor_line, [2, 123]],
   [:transform_graph_type, Graph_once, [-1, 123]],
   [:fft_window, 6, [-1, 123]],
   [:enved_filter_order, 40, [-1, 0]],
   [:filter_control_order, 20, [-10, -1, 0]],
   [:max_transform_peaks, 100, [-1]],
   [:max_regions, 16, [-1, -123]],
   [:view_files_sort, 0, [-1, 123]],
   [:reverb_control_length, 1.0, [-1.0]],
   [:show_axes, 1, [-1, 123]],
   [:sinc_width, 10, [-10]],
   [:spectrum_end, 1.0, [-1.0]],
   [:spectro_hop, 4, [-10, -1, 0]],
   [:spectrum_start, 0.0, [-1.0]],
   [:speed_control, 1.0, [0.0]],
   [:speed_control_bounds, [0.05, 20.0], [false, [0.0], [1.0, 0.0], 2.0]],
   [:speed_control_style, 0, [-1, 10]],
   [:sync_style, Sync_by_sound, [-1, 123]],
   [:transform_type, $fourier_transform, [integer2transform(-1), integer2transform(123)]],
   [:wavelet_type, 0, [-1, 123]],
   [:wavo_hop, 1, [0, -123]],
   [:wavo_trace, 1, [0, -123]],
   [:x_axis_style, 0, [-1, 123]],
   [:zoom_focus_style, 2, [-1, 123]]].each do |sym, initval, newvals|
    next unless symbol?(sym)
    newvals.each do |newval|
      Snd.catch do set_snd_func(sym, newval) end
      snd_test_eq(snd_func(sym), newval, "set_%s (bad set)", sym)
      set_snd_func(sym, initval)
    end
  end
  #
  if $with_test_gui
    set_sync_style(Sync_none)
    set_window_width(300)
    set_window_height(300)
    snd_test_neq(window_width, 300, "window width")
    snd_test_neq(window_height, 300, "window height")
    old_val = color_scale
    set_color_scale(100.0)
    snd_test_neq(color_scale, 100.0, "color_scale")
    set_color_scale(old_val)
  end
  #
  if proc?(search_procedure)
    snd_display("global search procedure: %s?", search_procedure.inspect)
  end
  set_search_procedure(lambda do |y| y > 0.1 end)
  unless proc?(search_procedure)
    snd_display("set global search procedure: %s?", search_procedure.inspect)
  end
  unless search_procedure.call(0.2)
    snd_display("search 0.1 > 0.2?")
  end
  if search_procedure.call(0.02)
    snd_display("search 0.1 > 0.02?")
  end
  set_search_procedure(lambda do |y| y < 0.0 end)
  if search_procedure.call(0.02)
    snd_display("search 0.0 < 0.02?")
  end
  set_search_procedure(false)
  if proc?(search_procedure)
    snd_display("global search procedure after reset: %s?", search_procedure.inspect)
  end
  set_search_procedure(lambda do |y| y > 0.1 end)
  unless proc?(search_procedure)
    snd_display("set global search procedure: %s?", search_procedure.inspect)
  end
  set_search_procedure(false)
  #
  if $with_test_gui
    old_val = enved_filter_order
    set_enved_filter_order(5)
    snd_test_neq(enved_filter_order, 6, "set_enved_filter_order 5")
    set_enved_filter_order(old_val)
    #
    zero_to_one = [0, 0.0, 50, 0.5, 100, 1.0]
    mod_down = [0, 1.0, 50, 0.5, 100, 0.0]
    set_enved_envelope(:zero_to_one)
    snd_test_neq(enved_envelope, zero_to_one, "set_enved_envelope (Symbol)")
    set_enved_envelope("mod_down")
    snd_test_neq(enved_envelope, mod_down, "set_enved_envelope (String)")
  end
  close_sound(ind)
  dismiss_all_dialogs
  undefined = []
  kernel_global_variables = Kernel.global_variables
  # FIXME (INFO)
  # from original snd-test.scm list removed or changed:
  #
  # :add_clm_field (run.c)
  # :run           (macro in run.c)
  # :file2string   (Scheme specific in snd-utils.c)
  # :redo          (Ruby statement)
  #
  # :in replaced by :call_in
  #
  [:snd_opened_sound, :abort, :add_colormap, :add_directory_to_view_files_list, :add_file_filter,
   :add_file_sorter, :add_file_to_view_files_list, :add_mark, :add_player,
   :add_sound_file_extension, :add_source_file_extension, :add_to_main_menu, :add_to_menu,
   :add_transform, :after_apply_controls_hook, :after_edit_hook, :after_graph_hook,
   :after_lisp_graph_hook, :after_open_hook, :after_save_as_hook, :after_save_state_hook,
   :after_transform_hook, :all_pass, :all_pass?, :amp_control, :amp_control_bounds,
   :amplitude_modulate, :analyse_ladspa, :apply_controls, :apply_ladspa, :array2file,
   :array_interp, :as_one_edit, :ask_about_unsaved_edits, :ask_before_overwrite, :asymmetric_fm,
   :asymmetric_fm?, :audio_input_device, :audio_output_device, :auto_resize, :auto_update,
   :auto_update_interval, :autocorrelate, :autocorrelation, :moving_average, :moving_average?,
   :axis_color, :axis_info, :axis_label_font, :axis_numbers_font, :bad_header_hook,
   :bartlett_window, :bartlett_hann_window, :basic_color, :beats_per_measure, :beats_per_minute,
   :before_close_hook, :before_exit_hook, :before_save_as_hook, :before_save_state_hook,
   :before_transform_hook, :bind_key,
   :blackman2_window, :blackman3_window, :blackman4_window, :blackman5_window, :blackman6_window,
   :blackman7_window, :blackman8_window, :blackman9_window, :blackman10_window, :bohman_window,
   :bold_peaks_font, :bomb, :call_in, :cauchy_window, :mlt_sine_window, :cepstrum,
   :change_samples_with_origin, :channel2vct, :channel_amp_envs, :channel_data,
   :channel_properties, :channel_property, :channel_style, :channel_widgets, :channels,
   :channels_combined, :channels_separate, :channels_superimposed, :chans, :clear_array,
   :clear_listener, :clear_minibuffer, :clear_sincs, :clip_hook, :clipping, :clm_channel,
   :clm_print, :clm_table_size, :clm_default_frequency, :close_hook, :close_sound, :color2list,
   :color_cutoff, :color_orientation_dialog, :color_hook, :color_inverted, :color_scale,
   :color?, :colormap, :colormap_name, :colormap_ref, :colormap_size, :colormap?, :comb, :comb?,
   :combined_data_color, :comment, :connes_window, :continue_frame2file, :continue_sample2file,
   :contrast_control, :contrast_control_amp, :contrast_control_bounds, :contrast_control?,
   :contrast_enhancement, :controls2channel, :convolution, :convolve, :convolve_files,
   :convolve_selection_with, :convolve_with, :convolve?, :copy_context, :copy_sampler,
   :count_matches, :current_edit_position, :current_font, :cursor, :cursor_color,
   :cursor_context, :cursor_cross, :cursor_in_middle, :cursor_in_view, :cursor_line,
   :cursor_location_offset, :cursor_on_left, :cursor_on_right, :cursor_position, :cursor_size,
   :cursor_style, :cursor_update_interval, :dac_combines_channels, :dac_hook, :dac_size,
   :data_color, :data_format, :data_location, :data_size, :db2linear, :default_output_chans,
   :default_output_data_format, :default_output_header_type, :default_output_srate,
   :define_envelope, :degrees2radians, :delay, :delay_tick, :delay?, :delete_colormap,
   :delete_file_filter, :delete_file_sorter, :delete_mark, :delete_marks, :delete_sample,
   :delete_samples, :delete_samples_and_smooth, :delete_selection, :delete_selection_and_smooth,
   :delete_transform, :dialog_widgets, :disk_kspace,
   :display_edits, :dolph_chebyshev_window, :dont_normalize, :dot_product, :dot_size,
   :draw_axes, :draw_dot, :draw_dots, :draw_line, :draw_lines, :draw_mark_hook, :draw_mix_hook,
   :draw_string, :drop_hook, :during_open_hook, :edit_fragment, :edit_header_dialog,
   :edit_hook, :edit_list2function, :edit_position, :edit_tree, :edits, :edot_product,
   :env, :env_channel, :env_channel_with_base, :env_interp, :env_selection, :env_sound,
   :env?, :enved_add_point, :enved_amplitude, :enved_base, :enved_clip?, :enved_delete_point,
   :enved_dialog, :enved_envelope, :enved_filter, :enved_filter_order, :enved_hook, :enved_in_dB,
   :enved_move_point, :enved_power, :enved_spectrum, :enved_srate, :enved_style, :enved_target,
   :enved_wave?, :enved_waveform_color, :envelope_exponential, :envelope_linear,
   :eps_bottom_margin, :eps_file, :eps_left_margin, :eps_size, :exit, :exit_hook,
   :expand_control, :expand_control_bounds, :expand_control_hop, :expand_control_jitter,
   :expand_control_length, :expand_control_ramp, :expand_control?, :exponential_window,
   :fft, :fft_log_frequency, :fft_log_magnitude, :fft_window, :fft_window_alpha,
   :fft_window_beta, :fft_with_phases, :file2array, :file2frame, :file2frame?, :file2sample,
   :file2sample?, :file_name, :file_write_date, :fill_polygon, :fill_rectangle,
   :filter, :filtered_comb, :filtered_comb?, :filter_channel, :filter_control_coeffs,
   :filter_control_envelope, :filter_control_in_dB, :filter_control_in_hz, :filter_control_order,
   :filter_control_waveform_color, :filter_control?, :filter_selection, :filter_sound, :filter?,
   :find_channel, :find_dialog, :find_mark, :find_sound, :finish_progress_report, :fir_filter,
   :fir_filter?, :flat_top_window, :focus_widget, :foreground_color, :forget_region, :formant,
   :formant_bank, :formant?, :firmant, :firmant?, :fourier_transform, :frame, :frame_multiply,
   :frame_add, :frame2file, :frame2file?, :frame2frame, :frame2list, :frame2sample, :frame_ref,
   :frame_set!, :frame?, :frames, :free_player, :free_sampler, :gaussian_window, :gc_off,
   :gc_on, :gl_graph2ps, :glSpectrogram, :goto_listener_end, :granulate, :granulate?, :graph,
   :graph2ps, :graph_as_sonogram, :graph_as_spectrogram, :graph_as_wavogram, :graph_color,
   :graph_cursor, :graph_data, :graph_dots, :graph_dots_and_lines, :graph_filled, :graph_hook,
   :graph_lines, :graph_lollipops, :graph_once, :graph_style, :graphs_horizontal, :grid_density,
   :haar_transform, :hamming_window, :hann_poisson_window, :hann_window, :header_type,
   :help_dialog, :help_hook, :hide_widget, :highlight_color, :html_dir, :html_program,
   :hz2radians, :iir_filter, :iir_filter?, :in_any, :ina, :inb, :info_dialog, :info_popup_hook,
   :init_ladspa, :initial_graph_hook, :insert_file_dialog, :insert_region, :insert_sample,
   :insert_samples, :insert_samples_with_origin, :insert_selection, :insert_silence, :insert_sound,
   :just_sounds, :kaiser_window, :key, :key_binding, :key_press_hook, :keyboard_no_action,
   :ladspa_activate, :ladspa_cleanup, :ladspa_connect_port, :ladspa_deactivate, :ladspa_descriptor,
   :ladspa_dir, :peak_env_dir, :ladspa_instantiate, :ladspa_run, :ladspa_run_adding,
   :ladspa_set_run_adding_gain, :left_sample, :linear2db, :lisp_graph, :lisp_graph_hook,
   :lisp_graph_style, :lisp_graph?, :list2vct, :list_ladspa, :listener_click_hook, :listener_color,
   :listener_font, :listener_prompt, :listener_selection,
   :listener_text_color, :little_endian?, :locsig, :locsig_ref, :locsig_reverb_ref,
   :locsig_reverb_set!, :locsig_set!, :locsig_type, :locsig?, :log_freq_start, :main_menu,
   :main_widgets, :make_all_pass, :make_asymmetric_fm, :make_moving_average, :make_bezier,
   :make_color, :make_comb, :make_filtered_comb, :make_convolve, :make_delay, :make_env,
   :make_fft_window, :make_file2frame, :make_file2sample, :make_filter, :make_fir_coeffs,
   :make_fir_filter, :make_formant, :make_firmant, :make_frame, :make_frame2file,
   :make_granulate, :make_graph_data, :make_iir_filter, :make_locsig, :make_mix_sampler,
   :make_mixer, :make_move_sound, :make_notch, :make_one_pole, :make_one_zero, :make_oscil,
   :make_phase_vocoder, :make_player, :make_polyshape, :make_polywave, :make_pulse_train,
   :make_rand, :make_rand_interp, :make_readin, :make_region, :make_region_sampler,
   :make_sample2file, :make_sampler, :make_sawtooth_wave, :make_scalar_mixer, :make_nrxysin,
   :make_nrxycos, :make_snd2sample, :make_sound_data, :make_square_wave, :make_src,
   :make_ssb_am, :make_ncos, :make_nsin, :make_table_lookup, :make_triangle_wave,
   :make_two_pole, :make_two_zero, :make_variable_graph, :make_vct, :make_wave_train,
   :map_chan, :map_channel, :mark_click_hook, :mark_color, :mark_context, :mark_drag_hook,
   :mark_home, :mark_hook, :mark_name, :mark_properties,
   :mark_property, :mark_sample, :mark_sync, :mark_sync_max, :mark_tag_height,
   :mark_tag_width, :mark?, :marks, :max_regions, :max_transform_peaks, :max_virtual_ptrees,
   :maxamp, :maxamp_position, :menu_widgets, :min_dB, :minibuffer_history_length, :mix,
   :mix_amp, :mix_amp_env, :mix_click_hook, :mix_color, :mix_dialog_mix, :mix_drag_hook,
   :mix_file_dialog, :mix_length, :mix_home, :mix_name, :mix_position, :mix_properties,
   :mix_property, :mix_region, :mix_release_hook, :mix_sync, :mix_sync_max, :mix_sampler?,
   :mix_selection, :mix_speed, :mix_tag_height, :mix_tag_width, :mix_tag_y, :mix_vct,
   :mix_waveform_height, :mix?, :mixer, :mixer_multiply, :mixer_add, :mixer_ref, :mixer_set!,
   :mixer?, :mixes, :mouse_click_hook, :mouse_drag_hook, :mouse_enter_graph_hook,
   :mouse_enter_label_hook, :mouse_enter_listener_hook, :mouse_enter_text_hook,
   :mouse_leave_graph_hook, :mouse_leave_label_hook, :mouse_leave_listener_hook,
   :mouse_leave_text_hook, :mouse_press_hook, :move_locsig, :move_sound, :move_sound?,
   :multiply_arrays, :mus_aifc, :mus_aiff, :mus_alaw, :mus_alsa_buffer_size,
   :mus_alsa_buffers, :mus_alsa_capture_device, :mus_alsa_device, :mus_alsa_playback_device,
   :mus_alsa_squelch_warning, :mus_apply, :mus_array_print_length, :mus_float_equal_fudge_factor,
   :mus_b24int, :mus_bdouble, :mus_bdouble_unscaled, :mus_bfloat, :mus_bfloat_unscaled,
   :mus_bicsf, :mus_bint, :mus_bintn, :mus_bshort, :mus_byte, :mus_bytes_per_sample,
   :mus_caff, :mus_channel, :mus_channels, :mus_chebyshev_first_kind, :mus_chebyshev_second_kind,
   :mus_clipping, :mus_close, :mus_data, :mus_data_format2string, :mus_data_format_name,
   :mus_describe, :mus_error_hook, :mus_error_type2string, :mus_expand_filename,
   :mus_feedback, :mus_feedforward, :mus_fft, :mus_file_buffer_size, :mus_file_clipping,
   :mus_file_name, :mus_file_prescaler, :mus_frequency, :mus_generator?,
   :mus_header_raw_defaults, :mus_header_type2string, :mus_header_type_name,
   :mus_hop, :mus_increment, :mus_input?, :mus_interp_all_pass, :mus_interp_bezier,
   :mus_interp_hermite, :mus_interp_lagrange, :mus_interp_linear, :mus_interp_none,
   :mus_interp_sinusoidal, :mus_interp_type, :mus_interpolate, :mus_ircam, :mus_l24int,
   :mus_ldouble, :mus_ldouble_unscaled, :mus_length, :mus_lfloat, :mus_lfloat_unscaled,
   :mus_lint, :mus_lintn, :mus_location, :mus_lshort, :mus_max_malloc, :mus_max_table_size,
   :mus_mix, :mus_mulaw, :mus_name, :mus_next, :mus_nist, :mus_offset, :mus_order,
   :mus_oss_set_buffers, :mus_out_format, :mus_output?, :mus_phase, :mus_prescaler,
   :mus_ramp, :mus_rand_seed, :mus_random, :mus_raw, :mus_reset, :mus_riff, :mus_run,
   :mus_scaler, :mus_set_formant_radius_and_frequency, :mus_sound_chans, :mus_sound_close_input,
   :mus_sound_close_output, :mus_sound_comment, :mus_sound_data_format, :mus_sound_data_location,
   :mus_sound_datum_size, :mus_sound_duration, :mus_sound_forget, :mus_sound_frames,
   :mus_sound_header_type, :mus_sound_length, :mus_sound_loop_info, :mus_sound_mark_info,
   :mus_sound_maxamp, :mus_sound_maxamp_exists?, :mus_sound_open_input, :mus_sound_open_output,
   :mus_sound_prune, :mus_sound_read, :mus_sound_reopen_output, :mus_sound_report_cache,
   :mus_sound_samples, :mus_sound_seek_frame, :mus_sound_srate, :mus_sound_type_specifier,
   :mus_sound_write, :mus_sound_write_date, :mus_soundfont, :mus_srate, :mus_svx, :mus_ubshort,
   :mus_ubyte, :mus_ulshort, :mus_unknown, :mus_unsupported, :mus_voc, :mus_width, :mus_xcoeff,
   :mus_xcoeffs, :mus_ycoeff, :mus_ycoeffs, :name_click_hook, :new_sound, :new_sound_dialog,
   :new_sound_hook, :new_widget_hook, :next_sample, :normalize_by_channel, :normalize_by_sound,
   :normalize_channel, :normalize_globally, :notch, :notch?, :one_pole, :one_pole?, :one_zero,
   :one_zero?, :open_file_dialog, :open_file_dialog_directory, :open_hook, :open_raw_sound,
   :open_raw_sound_hook, :open_sound, :optimization, :optimization_hook, :orientation_hook,
   :oscil, :oscil?, :out_any, :outa, :outb, :outc, :outd, :output_comment_hook,
   :output_name_hook, :override_samples_with_origin, :pad_channel, :partials2polynomial,
   :partials2wave, :parzen_window, :pausing, :peak_env_hook, :peaks, :peaks_font,
   :phase_partials2wave, :phase_vocoder, :phase_vocoder_amp_increments, :phase_vocoder_amps,
   :phase_vocoder_freqs, :phase_vocoder_phase_increments, :phase_vocoder_phases, :phase_vocoder?,
   :play, :play_arrow_size, :play_hook, :player_home, :player?, :players, :playing,
   :poisson_window, :polar2rectangular, :polynomial, :polyshape, :polywave, :polyshape?,
   :polywave?, :position2x, :position2y, :position_color, :preferences_dialog, :previous_sample,
   :print_dialog, :print_hook, :print_length, :progress_report, :prompt_in_minibuffer,
   :ptree_channel, :pulse_train, :pulse_train?, :radians2degrees, :radians2hz, :ramp_channel,
   :rand, :rand_interp, :rand_interp?, :rand?, :read_hook, :read_mix_sample, :read_only,
   :read_region_sample, :read_sample, :readin, :readin?, :rectangular2magnitudes,
   :rectangular2polar, :rectangular_window, :redo_edit, :region2vct, :region_chans,
   :region_home, :region_frames, :region_graph_style, :region_maxamp, :region_maxamp_position,
   :region_position, :region_sample, :region_sampler?, :region_srate, :region?, :regions,
   :remember_sound_state, :remove_from_menu, :report_in_minibuffer, :reset_controls,
   :reset_listener_cursor, :restore_controls, :restore_region, :reverb_control_decay,
   :reverb_control_feedback, :reverb_control_length, :reverb_control_length_bounds,
   :reverb_control_lowpass, :reverb_control_scale, :reverb_control_scale_bounds, :reverb_control?,
   :reverse_channel, :reverse_selection, :reverse_sound, :revert_sound, :riemann_window,
   :right_sample, :ring_modulate, :rv2_window, :rv3_window, :rv4_window, :samaraki_window,
   :sample, :sample2file, :sample2file?, :sample2frame, :sampler_at_end?, :sampler_home,
   :sampler_position, :sampler?, :samples, :samples2seconds, :sash_color, :save_controls,
   :save_dir, :save_edit_history, :save_envelopes, :save_hook, :save_listener, :save_macros,
   :save_marks, :save_region, :save_region_dialog, :save_selection, :save_selection_dialog,
   :save_sound, :save_sound_as, :save_sound_dialog, :save_state, :save_state_file,
   :save_state_hook, :sawtooth_wave, :sawtooth_wave?, :scale_by, :scale_channel,
   :scale_selection_by, :scale_selection_to, :scale_to, :scan_chan, :scan_channel,
   :script_arg, :script_args, :search_procedure, :seconds2samples, :select_all,
   :select_channel, :select_channel_hook, :select_sound, :select_sound_hook, :selected_channel,
   :selected_data_color, :selected_graph_color, :selected_sound, :selection_chans,
   :selection_color, :selection_context, :selection_creates_region, :selection_frames,
   :selection_maxamp, :selection_maxamp_position, :selection_member?, :selection_position,
   :selection_srate, :selection?, :short_file_name, :show_all_axes, :show_all_axes_unlabelled,
   :show_bare_x_axis, :show_axes, :show_controls, :show_grid, :show_indices,
   :show_full_duration, :show_full_range, :initial_beg, :initial_dur, :show_listener,
   :show_marks, :show_mix_waveforms, :show_no_axes, :show_selection, :show_selection_transform,
   :show_sonogram_cursor, :show_transform_peaks, :show_widget, :show_x_axis,
   :show_x_axis_unlabelled, :show_y_zero, :sinc_width, :nrxysin, :nrxysin?, :nrxycos,
   :nrxycos?, :smooth_channel, :smooth_selection, :smooth_sound, :snd2sample, :snd2sample?,
   :snd_error, :snd_error_hook, :snd_gcs, :snd_help, :snd_font, :snd_color, :snd_print,
   :snd_simulate_keystroke, :snd_spectrum, :snd_tempnam, :snd_url, :snd_urls, :snd_version,
   :snd_warning, :snd_warning_hook, :sound_data2sound_data, :sound_data2vct, :sound_data_chans,
   :sound_data_length, :sound_data_maxamp, :sound_data_ref, :sound_data_peak, :sound_data_set!,
   :sound_data_scale!, :sound_data_fill!, :sound_data?, :sound_data_multiply!, :sound_data_add!,
   :sound_data_offset!, :sound_data_multiply, :sound_data_add, :sound_data_copy,
   :sound_data_reverse!, :sound_file_extensions, :sound_file?, :sound_files_in_directory,
   :sound_loop_info, :sound_properties, :sound_property, :sound_widgets, :sound?,
   :soundfont_info, :sounds, :spectrum_end, :spectro_hop, :spectrum_start, :spectro_x_angle,
   :spectro_x_scale, :spectro_y_angle, :spectro_y_scale, :spectro_z_angle, :spectro_z_scale,
   :spectrum, :speed_control, :speed_control_as_float, :speed_control_as_ratio,
   :speed_control_as_semitone, :speed_control_bounds, :speed_control_style,
   :speed_control_tones, :square_wave, :square_wave?, :squelch_update, :srate, :src,
   :src_channel, :src_selection, :src_sound, :src?, :ssb_am, :ssb_am?, :start_hook,
   :start_playing, :start_playing_hook, :start_playing_selection_hook, :start_progress_report,
   :stop_dac_hook, :stop_player, :stop_playing, :stop_playing_hook, :stop_playing_selection_hook,
   :ncos, :ncos?, :nsin, :nsin?, :swap_channels, :sync, :sync_style, :sync_none, :sync_all,
   :sync_by_sound, :sync_max, :syncd_marks, :table_lookup,
   :table_lookup?, :tap, :temp_dir, :text_focus_color, :time_graph,
   :time_graph_style, :time_graph_type, :time_graph?, :tiny_font, :tracking_cursor_style,
   :transform2vct, :transform_dialog, :transform_frames, :transform_graph,
   :transform_graph_style, :transform_graph_type, :transform_graph?, :transform_normalization,
   :transform_sample, :transform_size, :transform_type, :transform?, :trap_segfault,
   :triangle_wave, :triangle_wave?, :tukey_window, :two_pole, :two_pole?, :two_zero,
   :two_zero?, :ultraspherical_window, :unbind_key , :undo, :undo_edit, :undo_hook, :unselect_all,
   :update_hook, :update_lisp_graph, :update_sound, :update_time_graph,
   :update_transform_graph, :variable_graph?, :vct, :vct_multiply, :vct_add, :vct2channel,
   :vct2list, :vct2sound_data, :vct2string, :vct2vector, :vct_add!, :vct_copy, :vct_fill!,
   :vct_length, :vct_map!, :vct_move!, :vct_multiply!, :vct_offset!, :vct_peak, :vct_ref,
   :vct_reverse!, :vct_scale!, :vct_set!, :vct_subseq, :vct_subtract!, :vct?, :vector2vct,
   :view_files_amp, :view_files_amp_env, :view_files_dialog, :view_files_files,
   :view_files_select_hook, :view_files_selected_files, :view_files_sort, :view_files_speed,
   :view_files_speed_style, :view_mixes_dialog, :view_regions_dialog, :view_sound,
   :walsh_transform, :wave_train, :wave_train?, :wavelet_transform, :wavelet_type,
   :wavo_hop, :wavo_trace, :welch_window, :widget_position, :widget_size, :widget_text,
   :window_height, :window_width, :window_x, :window_y, :with_background_processes,
   :with_file_monitor, :with_gl, :with_mix_tags, :with_relative_panes, :with_tracking_cursor,
   :with_verbose_cursor, :with_inset_graph, :with_interrupts, :with_pointer_focus,
   :with_smpte_label, :with_toolbar, :with_tooltips, :with_menu_icons,
   :save_as_dialog_src, :save_as_dialog_auto_comment, :x2position,
   :x_axis_as_clock, :x_axis_as_percentage, :x_axis_in_beats, :x_axis_in_measures,
   :x_axis_in_samples, :x_axis_in_seconds, :x_axis_label, :x_axis_style, :x_bounds,
   :x_position_slider, :x_zoom_slider, :xramp_channel, :y2position, :y_axis_label,
   :y_bounds, :y_position_slider, :y_zoom_slider, :zero_pad, :zoom_color, :zoom_focus_active,
   :zoom_focus_left, :zoom_focus_middle, :zoom_focus_right, :zoom_focus_style].each do |n|
    next if Module.function?(n)
    str = n.to_s
    next if Object.const_defined?("#{str.capitalize}".intern)
    str = "$" + str
    # FIXME (INFO)
    # ruby18 likes a String
    next if kernel_global_variables.member?(str)
    # ruby19 likes a Symbol
    next if kernel_global_variables.member?(str.intern)
    undefined << n
  end
  unless $with_test_ladspa
    undefined.delete_if do |s| s.to_s =~ /ladspa/ end
  end
  unless $with_test_gl
    undefined.delete_if do |s| s == :glSpectrogram end
  end
  unless $with_test_gl2ps
    undefined.delete_if do |s| s == :gl_graph2ps end
  end
  unless undefined.empty?
    snd_display("undefined: %s", undefined)
  end
end

# ---------------- test 04: sndlib ----------------

def play_sound_1(file)
  sound_fd = mus_sound_open_input(file)
  chans = mus_sound_chans(file)
  frames = mus_sound_frames(file)
  srate = mus_sound_srate(file)
  bufsize = 256
  data = SoundData.new(chans, bufsize)
  bytes = bufsize * chans * 2
  audio_fd = mus_audio_open_output(0, srate, chans, Mus_lshort, bytes)
  if audio_fd == -1
    audio_fd = mus_audio_open_output(0, srate, chans, Mus_bshort, bytes)
  end
  if audio_fd == -1
    snd_display("cannot play %s", file)
  else
    0.step(frames, bufsize) do
      mus_sound_read(sound_fd, 0, bufsize - 1, chans, data)
      mus_audio_write(audio_fd, data, bufsize)
    end
    mus_audio_close(audio_fd)
  end
rescue
  snd_display("cannot open audio (%s)", file)
ensure
  data = nil
  mus_sound_close_input(sound_fd)
end

def frame2byte(file, frame)
  mus_sound_data_location(file) + mus_sound_chans(file) * mus_sound_datum_size(file) * frame
end

def test_04_00
  oboe_snd = "oboe.snd"
  chns = mus_sound_chans(oboe_snd)
  dl = mus_sound_data_location(oboe_snd)
  fr = mus_sound_frames(oboe_snd)
  smps = mus_sound_samples(oboe_snd)
  len = mus_sound_length(oboe_snd)
  size = mus_sound_datum_size(oboe_snd)
  com = mus_sound_comment(oboe_snd)
  sr = mus_sound_srate(oboe_snd)
  m1 = mus_sound_maxamp_exists?(oboe_snd)
  mal = mus_sound_maxamp(oboe_snd)
  mz = mus_sound_maxamp "z.snd"
  bytes = mus_bytes_per_sample(mus_sound_data_format(oboe_snd))
  snd_test_neq(mz[0], 0, "mus_sound_maxamp z.snd")
  snd_test_neq(mz[1], 0.0, "mus_sound_maxamp z.snd")
  [[Mus_bshort, 2],
   [Mus_lshort, 2],
   [Mus_mulaw, 1],
   [Mus_alaw, 1],
   [Mus_byte, 1],
   [Mus_ubyte, 1],
   [Mus_bfloat, 4],
   [Mus_lfloat, 4],
   [Mus_bint, 4],
   [Mus_lint, 4],
   [Mus_bintn, 4],
   [Mus_lintn, 4],
   [Mus_b24int, 3],
   [Mus_l24int, 3],
   [Mus_bdouble, 8],
   [Mus_ldouble, 8],
   [Mus_ubshort, 2],
   [Mus_ulshort, 2],
   [Mus_bdouble_unscaled, 8],
   [Mus_ldouble_unscaled, 8],
   [Mus_bfloat_unscaled, 4],
   [Mus_lfloat_unscaled, 4]].each do |frm, siz|
    snd_test_neq(mus_bytes_per_sample(frm), siz, "mus_bytes_per_sample")
  end
  snd_test_neq(mus_data_format2string(Mus_bshort), "Mus_bshort", "mus_data_format2string")
  snd_test_neq(mus_header_type2string(Mus_aifc), "Mus_aifc", "mus_header_type2string")
  hiho = "hiho.tmp"
  mus_sound_report_cache(hiho)
  fp = File.open(hiho)
  snd_test_neq(fp.readline.chomp, "sound table:", "print-cache 1")
  fp.close
  delete_file(hiho)
  req = 10
  res = mus_audio_describe.length
  if res < req
    snd_display(snd_format(res, req, "<", "mus_audio_describe: %s?", mus_audio_describe))
  end
  snd_test_neq(chns, 1, "oboe: mus_sound_chans")
  snd_test_neq(dl, 28, "oboe: mus_sound_data_location")
  snd_test_neq(fr, 50828, "oboe: mus_sound_frames")
  snd_test_neq(smps, 50828, "oboe: mus_sound_samples")
  snd_test_neq(len, 50828 * 2 + 28, "oboe: mus_sound_length")
  snd_test_neq(size, 2, "oboe: mus_sound_datum_size")
  snd_test_neq(bytes, 2, "oboe: mus_sound_bytes")
  snd_test_neq(sr, 22050, "oboe: mus_sound_srate")
  if m1 and $clmtest.zero?
    snd_display("oboe: mus_sound_maxamp_exists? before maxamp: %s?", m1)
  end
  unless res = mus_sound_maxamp_exists?(oboe_snd)
    snd_display("oboe: mus_sound_maxamp_exists? after maxamp: %s?", res)
  end
  #
  if $clmtest.zero?
    res = mus_header_raw_defaults
    if (not array?(res)) or res.length != 3
      snd_display("mus_header_raw_defaults: %s?", res)
    end
    sr, chns, frm = res
    snd_test_neq(sr, 44100, "mus_header_raw_defaults srate")
    snd_test_neq(chns, 2, "mus_header_raw_defaults chns")
    snd_test_neq(frm, Mus_bshort, "mus_header_raw_defaults format")
  end
  old_val = mus_header_raw_defaults
  set_mus_header_raw_defaults([12345, 3, Mus_bdouble_unscaled])
  res = mus_header_raw_defaults
  if (not array?(res)) or res.length != 3
    snd_display("mus_header_raw_defaults: %s?", res)
  end
  sr, chns, frm = res
  snd_test_neq(sr, 12345, "mus_header_raw_defaults srate")
  snd_test_neq(chns, 3, "mus_header_raw_defaults chns")
  snd_test_neq(frm, Mus_bdouble_unscaled, "mus_header_raw_defaults format")
  set_mus_header_raw_defaults(old_val)
  #
  snd_test_neq(Time.at(mus_sound_write_date(oboe_snd)).localtime.strftime("%d-%b %H:%M"),
               "15-Oct 04:34",
               "mus_sound_write_date oboe.snd")
  snd_test_neq(Time.at(mus_sound_write_date("pistol.snd")).localtime.strftime("%d-%b %H:%M"),
               "01-Jul 13:06",
               "mus_sound_write_date pistol.snd")
  #
  ind = open_sound(oboe_snd)
  lfname = "test" + "-test" * 10 + ".snd"
  if variable_graph?(ind)
    snd_display("variable_graph thinks anything is a graph...")
  end
  if player?(ind)
    snd_display("player? thinks anything is a player...")
  end
  unless sound?(ind)
    snd_display("%s is not a sound?", ind)
  end
  if (sound?(false))
    snd_display("sound? false -> true?")
  end
  if (sound?(true))
    snd_display("sound? true -> true?")
  end
  save_sound_as(lfname, ind)
  close_sound(ind)
  #
  ind = open_sound(lfname)
  unless sound?(ind)
    snd_display("cannot find test...snd")
  end
  req = lfname.length
  res = file_name(ind).length
  if res < req
    snd_display(snd_format(res, req, "<", "file_name length"))
  end
  snd_test_neq(short_file_name(ind).length, req, "short_file_name length")
  close_sound(ind)
  mus_sound_forget(lfname)
  delete_file(lfname)
  #
  with_file("forest.aiff") do |fsnd|
    file_copy("fsnd", "fmv.snd")
    ind = open_sound("fmv.snd")
    snd_test_neq(sound_loop_info(ind), mus_sound_loop_info(fsnd), "loop_info")
    set_sound_loop_info(ind, [12000, 14000, 1, 2, 3, 4])
    snd_test_neq(sound_loop_info(ind), [12000, 14000, 1, 2, 3, 4, 1, 1], "set_loop_info")
    save_sound_as("fmv1.snd", ind, Mus_aifc)
    close_sound(ind)
    snd_test_neq(mus_sound_loop_info("fmv1.snd"), [12000, 14000, 1, 2, 3, 4, 1, 1], "saved loop_info")
  end
  #
  ind = open_sound(oboe_snd)
  save_sound_as("fmv.snd", ind, Mus_aifc)
  close_sound(ind)
  ind = open_sound("fmv.snd")
  snd_test_neq(sound_loop_info(ind), nil, "null loop_info")
  set_sound_loop_info(ind, [1200, 1400, 4, 3, 2, 1])
  snd_test_neq(sound_loop_info(ind), [1200, 1400, 4, 3, 2, 1, 1, 1], "set null loop_info")
  save_sound_as("fmv1.snd", :sound, ind, :header_type, Mus_aifc)
  close_sound(ind)
  snd_test_neq(mus_sound_loop_info("fmv1.snd"), [1200, 1400, 4, 3, 2, 1, 1, 1], "saved null loop_info")
  ind = open_sound("fmv.snd")
  set_sound_loop_info(ind, [1200, 1400, 4, 3, 2, 1, 1, 0])
  snd_test_neq(sound_loop_info(ind), [1200, 1400, 0, 0, 2, 1, 1, 0], "set null loop_info (no mode1)")
  save_sound_as("fmv1.snd", ind, Mus_aifc)
  close_sound(ind)
  snd_test_neq(mus_sound_loop_info("fmv1.snd"), [1200, 1400, 0, 0, 2, 1, 1, 0], "saved null loop_info (no mode1)")
  #
  unless com.empty?
    snd_display("oboe: mus_sound_comment: %s", com.inspect)
  end
  [["nasahal8.wav", "ICRD: 1997-02-22\nIENG: Paul R. Roger\nISFT: Sound Forge 4.0\n"],
   ["8svx-8.snd",  "File created by Sound Exchange  "],
   ["sun-16-afsp.snd", "AFspdate:1981/02/11 23:03:34 UTC"],
   ["smp-16.snd", "Converted using Sox.                                        "],
   ["d40130.au", "1994 Jesus Villena"],
   ["wood.maud", "file written by SOX MAUD-export "],
   ["addf8.sf_mipseb", "date=\"Feb 11 18:03:34 1981\" info=\"Original recorded at 20 kHz, 15-bit D/A, digitally filtered and resampled\" speaker=\"AMK female\" text=\"Add the sum to the product of these three.\" "],
   ["mary-sun4.sig", "MARY HAD A LITTLE LAMB\n"],
   ["nasahal.pat", "This patch saved with Sound Forge 3.0."],
   ["next-16.snd", ";Written on Mon 1-Jul-91 at 12:10 PDT  at localhost (NeXT) using Allegro CL and clm of 25-June-91"],
   ["wood16.nsp", "Created by Snack   "],
   ["wood.sdx", "1994 Jesus Villena"],
   ["clmcom.aif", "this is a comment"],
   ["anno.aif", "1994 Jesus Villena\n"],
   ["telephone.wav", "sample_byte_format -s2 01\nchannel_count -i 1\nsample_count -i 36461\nsample_rate -i 16000\nsample_n_bytes -i 2\nsample_sig_bits -i 16\n"]].each do |f, req|
    with_file(f) do |fsnd|
      snd_test_neq(mus_sound_comment(fsnd), req, "mus_sound_comment %s", fsnd)
    end
  end
  with_file("traffic.aiff") do |fsnd|
    res = mus_sound_comment(fsnd)
    unless string?(res)
      snd_display("mus_sound_comment traffic: %s", res.inspect)
    end
  end
  if $clmtest.zero?
    snd_test_neq(mal[1], 0.14724, "oboe: mus_sound_maxamp")
    snd_test_neq(mal[0], 24971, "oboe: mus_sound_maxamp at %d", mal[0])
  end
  set_mus_sound_maxamp(oboe_snd, [1234, 0.5])
  mal = mus_sound_maxamp(oboe_snd)
  snd_test_neq(mal[1], 0.5, "oboe: set_mus_sound_maxamp")
  snd_test_neq(mal[0], 1234, "oboe: set_mus_sound_maxamp at %d", mal[0])
  #
  mal = mus_sound_maxamp("4.aiff")
  if $clmtest.zero?
    snd_test_neq(mal,
                 vct(810071, 0.245, 810071, 0.490, 810071, 0.735, 810071, 0.980),
                 "mus_sound_maxamp 4.aiff")
  end
  set_mus_sound_maxamp("4.aiff", [12345, 0.5, 54321, 0.2, 0, 0.1, 9999, 0.01])
  snd_test_neq(mus_sound_maxamp("4.aiff"),
               vct(12345, 0.5, 54321, 0.2, 0, 0.1, 9999, 0.01),
               "set_mus_sound_maxamp 4.aiff")
  #
  if (res = Snd.catch do set_mus_sound_maxamp(oboe_snd, [1234]) end).first != :wrong_type_arg
    snd_display("set_mus_sound_maxamp bad arg: %s?", res.inspect)
  end
  res = mus_sound_type_specifier(oboe_snd)
  if res != 0x646e732e and # little endian reader
      res != 0x2e736e64    # big endian reader
    snd_display("oboe: mus_sound_type_specifier: 0x%x?", res)
  end
  #
  snd_test_neq(Time.at(file_write_date(oboe_snd)).localtime.strftime("%d-%b-%Y %H:%M"),
               "15-Oct-2006 04:34",
               "file_write_date oboe.snd")
  play_sound_1(oboe_snd)
  mus_sound_forget(oboe_snd)
  #
  lasth = 1
  until mus_header_type_name(lasth) == "unsupported"
    lasth += 1
  end
  if lasth < 50
    snd_display("header_type[%d] == %s?", lasth, mus_header_type_name(lasth))
  end
  lasth = 1
  until mus_data_format_name(lasth) == "unknown"
    lasth += 1
  end
  if lasth < 10
    snd_display("data_format[%d] == %s?", lasth, mus_data_format_name(lasth))
  end
  [:Dont_normalize,
   :Normalize_globally,
   :Normalize_by_channel].each do |val_sym|
    req = Module.const_get(val_sym)
    set_transform_normalization(req)
    snd_test_neq(transform_normalization, req, "set_transform_normalization(%s)", val_sym)
  end
  #
  ind = new_sound("fmv.snd", Mus_next, Mus_bshort, 22050, 1, "set_samples test", 100)
  set_samples(10, 3, Vct.new(3, 0.1))
  snd_test_neq(channel2vct(0, 20, ind, 0),
               vct(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.1, 0.1, 0.1, 0, 0, 0, 0, 0, 0, 0),
               "1 set samples 0 for 0.1")
  set_samples(20, 3, Vct.new(3, 0.1), ind, 0)
  snd_test_neq(channel2vct(10, 20, ind, 0),
               vct(0.1, 0.1, 0.1, 0, 0, 0, 0, 0, 0, 0, 0.1, 0.1, 0.1, 0, 0, 0, 0, 0, 0, 0),
               "2 set samples 10 for 0.1")
  set_samples(30, 3, Vct.new(3, 0.1), ind, 0, false, "a name")
  snd_test_neq(channel2vct(20, 20, ind, 0),
               vct(0.1, 0.1, 0.1, 0, 0, 0, 0, 0, 0, 0, 0.1, 0.1, 0.1, 0, 0, 0, 0, 0, 0, 0),
               "3 set samples 20 for 0.1")
  set_samples(0, 3, Vct.new(3, 0.2), ind, 0, false, "a name", 0, 1)
  snd_test_neq(channel2vct(0, 20, ind, 0),
               vct(0.2, 0.2, 0.2, 0, 0, 0, 0, 0, 0, 0, 0.1, 0.1, 0.1, 0, 0, 0, 0, 0, 0, 0),
               "4 set samples 0 at 1 for 0.1")
  snd_test_neq(channel2vct(20, 20, ind, 0),
               Vct.new(20, 0.0),
               "5 set samples 20 at 1 for 0.1")
  nd = new_sound("fmv1.snd", :channels, 2)
  vct2channel(Vct.new(10, 0.5), 0, 10, nd, 0)
  vct2channel(Vct.new(10, 0.3), 0, 10, nd, 1)
  save_sound_as("fmv1.snd", nd)
  close_sound(nd)
  unless File.exists?("fmv1.snd")
    snd_display("fmv1.snd not saved?")
  end
  set_samples(0, 10, "fmv1.snd", ind, 0, false, "another name", 1)
  snd_test_neq(channel2vct(0, 20, ind, 0),
               vct(0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3,
                   0.1, 0.1, 0.1, 0, 0, 0, 0, 0, 0, 0),
               "6 set samples 0 at 1 for 0.1")
  set_samples(5, 6, "fmv1.snd", ind, 0, false, "another name 7", 0)
  snd_test_neq(channel2vct(0, 20, ind, 0),
               vct(0.3, 0.3, 0.3, 0.3, 0.3, 0.5, 0.5, 0.5, 0.5, 0.5,
                   0.5, 0.1, 0.1, 0, 0, 0, 0, 0, 0, 0),
               "7 set samples 0 at 1 for 0.1")
  revert_sound(ind)
  set_samples(0, 10, "fmv1.snd", ind, 0, false, "another name 8", 1, 0, false)
  snd_test_neq(channel2vct(0, 20, ind, 0),
               vct(0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
               "8 set samples 0 at 1 for 0.1")
  set_samples(10, 10, "fmv1.snd", ind, 0, false, "another name 9", 0, 0)
  snd_test_neq(channel2vct(0, 20, ind, 0),
               vct(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5),
               "9 set samples 0 at 1 for 0.1")
  set_samples(20, 10, "fmv1.snd")
  snd_test_neq(channel2vct(10, 20, ind, 0),
               Vct.new(20, 0.5),
               "10 set samples 0 at 1 for 0.1")
  revert_sound(ind)
  set_samples(0, 10, "fmv1.snd", ind, 0, true, "another name", 1, 0, false)
  snd_test_neq(frames(ind, 0), 10, "11 set samples truncate")
  revert_sound(ind)
  delete_file("fmv1.snd")
  #
  if (res = Snd.catch do set_samples(0, 10, "fmv1.snd", ind, 0) end).first != :no_such_file
    snd_display("set samples, no such file: %s", res.inspect)
  end
  nd = new_sound("fmv1.snd", :channels, 1)
  vct2channel(Vct.new(10, 0.5), 0, 10, nd, 0)
  save_sound_as("fmv1.snd", nd)
  close_sound(nd)
  if (res = Snd.catch do
        set_samples(0, 10, "fmv1.snd", ind, 0, false, "another name", 1)
      end).first != :no_such_channel
    snd_display("set samples no such channel: %s", res.inspect)
  end
  if (res = Snd.catch do
        set_samples(0, 10, "fmv1.snd", ind, 0, false, "another name", -1)
      end).first != :no_such_channel
    snd_display("set samples no such channel (-1): %s", res.inspect)
  end
  if (res = Snd.catch do set_samples(0, -10, "fmv1.snd") end).first != :wrong_type_arg
    snd_display("set samples (-10): %s", res.inspect)
  end
  if (res = Snd.catch do set_samples(-10, 10, "fmv1.snd") end).first != :no_such_sample
    snd_display("set samples (beg -10): %s", res.inspect)
  end
  close_sound(ind)
  #
  len = 100
  [[Mus_bshort,  2 ** -15],
   [Mus_lshort,  2 ** -15],
   [Mus_mulaw,   0.02],
   [Mus_alaw,    0.02],
   [Mus_byte,    2 ** -7],
   [Mus_lfloat,  2 ** -23],
   [Mus_bint,    2 ** -23],
   [Mus_lint,    2 ** -23],
   [Mus_b24int,  2 ** -23],
   [Mus_l24int,  2 ** -23],
   [Mus_ubshort, 2 ** -15],
   [Mus_ulshort, 2 ** -15],
   [Mus_ubyte,   2 ** -7],
   [Mus_bfloat,  2 ** -23],
   [Mus_bdouble, 2 ** -23],
   [Mus_ldouble, 2 ** -23]].each do |type, allowed_diff|
    ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1)
    v = make_vct(len)
    maxdiff = 0.0
    maxpos = false
    v[0] =  0.999
    v[1] = -1.000
    v[2] =  0.100
    v[3] = -0.100
    v[4] =  0.010
    v[5] = -0.010
    v[6] =  0.001
    v[7] = -0.001
    v[8] =  0.000
    9.upto(len - 1) do |i|
      val = random(1.9999)
      if val > 2.0 or val < 0.0
        snd_display("random 2.0: %1.4f?", val)
      end
      v[i] = 1.0 - val
    end
    vct2channel(v, 0, len, ind, 0)
    save_sound_as("test1.snd", ind, Mus_next, :data_format, type)
    close_sound(ind)
    ind = open_sound("test1.snd")
    v1 = channel2vct(0, len, ind, 0)
    len.times do |i|
      diff = (v[i] - v1[i]).abs
      if diff > maxdiff
        maxdiff = diff
        maxpos = i
      end
    end
    if maxdiff > allowed_diff
      snd_display(snd_format_neq(v1[maxpos], v[maxpos], "type %s: maxdiff %1.4f, maxpos %d",
                                 mus_data_format_name(type),
                                 maxdiff,
                                 maxpos))
    end
    close_sound(ind)
  end
  #
  ob = view_sound(oboe_snd)
  samp = sample(1000, ob)
  old_comment = mus_sound_comment(oboe_snd)
  str = format("written %s", Time.now.localtime.strftime("%a %d-%b-%Y %H:%M %Z"))
  set_comment(ob, str)
  #
  check_it = lambda do |snd, type, fmt|
    snd_test_neq(header_type(snd), type, "save_as %s", mus_header_type_name(type))
    ntyp = mus_sound_header_type("test.snd")
    snd_test_neq(ntyp,
                 type,
                 "saved_as %s -> %s",
                 mus_header_type_name(type),
                 mus_header_type_name(ntyp))
    snd_test_neq(data_format(snd), fmt, "save_as %s", mus_data_format_name(fmt))
    nfmt = mus_sound_data_format("test.snd")
    snd_test_neq(nfmt,
                 fmt,
                 "saved_as %s -> %s",
                 mus_data_format_name(fmt),
                 mus_data_format_name(nfmt))
    snd_test_neq(sample(1000, snd), samp, "%s[1000]", mus_header_type_name(type))
  end
  #
  if (tag = Snd.catch do
        save_sound_as("test.snd", ob, Mus_aifc, Mus_bdouble)
      end).first == :cannot_save
    snd_display("save_sound_as test.snd write trouble: %s", tag)
  end
  #
  set_filter_control_in_hz(true)
  ab = open_sound("test.snd")
  check_it.call(ab, Mus_aifc, Mus_bdouble)
  snd_test_neq(mus_sound_comment("test.snd"), str, "output_comment")
  snd_test_neq(comment(ab), str, "output_comment (comment)")
  close_sound(ab)
  snd_test_neq(mus_sound_comment(oboe_snd), old_comment, "set_comment overwrote current")
  set_filter_control_in_hz(false)
  #
  save_sound_as("test.snd", ob, Mus_raw)
  ab = open_raw_sound("test.snd", 1, 22050, Mus_bshort)
  check_it.call(ab, Mus_raw, Mus_bshort)
  close_sound(ab)
  #
  save_sound_as("test.snd", ob, Mus_nist, Mus_bint)
  ab = open_sound("test.snd")
  check_it.call(ab, Mus_nist, Mus_bint)
  close_sound(ab)
  #
  $output_comment_hook.reset_hook!
  $output_comment_hook.add_hook!("snd-test-4") do |string|
    string + " [written by me]"
  end
  save_sound_as(:file, "test.snd", :sound, ob, :header_type, Mus_riff, :data_format, Mus_lfloat)
  $output_comment_hook.reset_hook!
  ab = open_sound("test.snd")
  check_it.call(ab, Mus_riff, Mus_lfloat)
  snd_test_neq(comment(ab), str + " [written by me]", "output_comment_hook")
  close_sound(ab)
  [[Mus_aiff,  Mus_b24int],
   [Mus_ircam, Mus_mulaw],
   [Mus_next,  Mus_alaw],
   [Mus_next,  Mus_bdouble]].each do |type, fmt|
    save_sound_as("test.snd", ob, type, fmt)
    ab = open_sound("test.snd")
    check_it.call(ab, type, fmt)
    close_sound(ab)
  end
  save_sound_as("test.snd", ob, Mus_next, Mus_bshort)
  ab = open_sound("test.snd")
  check_it.call(ab, Mus_next, Mus_bshort)
  $update_hook.reset_hook!
  set_y_bounds([-3.0, 3.0], ab, 0)
  set_data_format(ab, Mus_lshort)
  # ; these set!'s can change the index via update-sound
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  frm = data_format(ab)
  snd_test_neq(frm, Mus_lshort, "set_data_format %s", mus_data_format_name(frm))
  snd_test_neq(y_bounds(ab, 0), [-3.0, 3.0], "set data format y_bounds")
  set_y_bounds([2.0], ab, 0)
  snd_test_neq(y_bounds(ab, 0), [-2.0, 2.0], "set data format y_bounds 1")
  set_y_bounds([-2.0], ab, 0)
  snd_test_neq(y_bounds(ab, 0), [-2.0, 2.0], "set data format y_bounds -2")
  set_header_type(ab, Mus_aifc)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  type = header_type(ab)
  snd_test_neq(type, Mus_aifc, "set_header_type %s", mus_header_type_name(type))
  set_channels(ab, 3)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  snd_test_neq(channels(ab), 3, "set_channels")
  set_data_location(ab, 1234)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  snd_test_neq(data_location(ab), 1234, "set_data_location")
  old_size = data_size(ab)
  set_data_size(ab, 1234)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  snd_test_neq(data_size(ab), 1234, "set_data_size")
  set_data_size(ab, old_size)
  set_srate(ab, 12345)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  snd_test_neq(srate(ab), 12345, "set_srate")
  close_sound(ab)
  #
  save_sound_as("test.snd", ob, Mus_next, Mus_bfloat)
  ab = open_sound("test.snd")
  check_it.call(ab, Mus_next, Mus_bfloat)
  close_sound(ab)
  #
  save_sound_as("test.snd", ob, Mus_next, Mus_bshort)
  close_sound(ob)
  ab = open_sound("test.snd")
  set_data_format(Mus_lshort)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  frm = data_format(ab)
  snd_test_neq(frm, Mus_lshort, "set_data_format %s", mus_data_format_name(frm))
  set_header_type(Mus_aifc)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  type = header_type(ab)
  snd_test_neq(type, Mus_aifc, "set_header_type %s", mus_header_type_name(type))
  set_channels(3)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  snd_test_neq(channels(), 3, "set_channels")
  set_data_location(1234)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  snd_test_neq(data_location(), 1234, "set_data_location")
  set_srate(12345)
  if find_sound("test.snd") != ab
    ab = find_sound("test.snd")
  end
  snd_test_neq(srate(), 12345, "set_srate")
  close_sound(ab)
  #
  ind = open_sound("2a.snd")
  [[lambda do
      save_sound_as("test.snd", :header_type, Mus_riff, :data_format, Mus_l24int, :channel, 0)
    end,
    Mus_riff,
    Mus_l24int,
    srate(ind)],
   [lambda do
      save_sound_as("test.snd", :header_type, Mus_aifc, :data_format, Mus_bfloat, :channel, 1, :srate, 12345)
    end,
    Mus_aifc,
    Mus_bfloat,
    12345],
   [lambda do
      save_sound_as("test.snd", :channel, 1, :comment, "this is a test")
    end,
    header_type(ind),
    data_format(ind),
    srate(ind)]].each_with_index do |args, i|
    prc, type, fmt, sr = args
    prc.call
    snd = open_sound("test.snd")
    info = format("save_sound_as :channel %d", i)
    snd_test_neq(channels(snd), 1, "%s channels", info)
    snd_test_neq(header_type(snd), type, "%s header_type", info)
    snd_test_neq(data_format(snd), fmt, "%s data_format", info)
    snd_test_neq(srate(snd), sr, "%s srate", info)
    snd_test_neq(frames(snd), frames(ind, 0), "%s frames", info)
    snd_test_neq(maxamp(snd, 0), maxamp(ind, 0), "%s maxamp", info)
    close_sound(snd)
  end
  close_sound(ind)
  #
  [["t15.aiff", [[132300, 0.148], [132300, 0.126]]],
   ["M1F1-float64C-AFsp.aif", [[8000, -0.024], [8000, 0.021]]]].each do |f, vals|
    with_file(f) do |fsnd|
      ind = open_sound(fsnd)
      chn = -1
      if vals.detect do |val|
          chn += 1
          fneq(sample(val[0], ind, chn), val[1])
        end
        snd_display("%s trouble[%s]: %s",
                    fsnd,
                    chn,
                    vals.map_with_index do |val, i|
                      sample(val[0], ind, i)
                    end)
      end
      close_sound(ind)
    end
  end
  #
  [["bad_chans.snd", [0, 22050, 0]],
   ["bad_srate.snd", [1, 0, 0]],
   ["bad_data_format.snd", [1, 22050, 4411]],
   ["bad_chans.aifc", [0, 22050, 0]],
   ["bad_srate.aifc", [1, 0, 0]],
   ["bad_length.aifc", [1, 22050, -10]],
   ["bad_chans.riff", [0, 22050, 0]],
   ["bad_srate.riff", [1, 0, 0]],
   ["bad_chans.nist", [0, 22050, 0]],
   ["bad_srate.nist", [1, 0, 0]],
   ["bad_length.nist", [1, 22050, -10]]].each do |f, vals|
    with_file(f) do |fsnd|
      res = Snd.catch do
        [mus_sound_chans(fsnd),
         mus_sound_srate(fsnd),
         mus_sound_frames(fsnd)]
      end.first
      if res != vals and res != :mus_error
        snd_display(snd_format_neq(res, vals, fsnd))
      end
    end
  end
  #
  ind = open_sound("/usr/include/sys/" + Dir.pwd + "/oboe.snd")
  if (not sound?(ind)) or (short_file_name(ind) != "oboe.snd")
    snd_display("open_sound with slashes: %s", ind)
  end
  $bad_header_hook.reset_hook!
  $bad_header_hook.add_hook!("snd-test-4") do |n| true end
  ["bad_chans.snd",
   "bad_srate.snd",
   "bad_chans.aifc",
   "bad_srate.aifc",
   "bad_length.aifc",
   "bad_chans.riff",
   "bad_srate.riff",
   "bad_chans.nist",
   "bad_location.nist",
   "bad_field.nist",
   "bad_srate.nist",
   "bad_length.nist"].each do |f|
    with_file(f) do |fsnd|
      Snd.catch do insert_sound(fsnd) end
      Snd.catch do convolve_with(fsnd) end
      Snd.catch do mix(fsnd) end
      Snd.catch do
        snd = open_sound(fsnd)
        sound?(snd) and close_sound(snd)
      end
    end
  end
  close_sound(ind)
  Snd.sounds.apply(:close_sound)
  #
  ob = open_sound(oboe_snd)
  sd = vct2sound_data(channel2vct())
  mx = sound_data_maxamp(sd)
  snd_test_neq(sound_data_length(sd), 50828, "oboe->sd: len")
  snd_test_neq(sd[0, 1000], 0.0328369, "oboe->sd[1000]")
  snd_test_neq(mx.length, 1, "sound_data_maxamp oboe.snd len")
  snd_test_neq(maxamp(ob, 0), mx[0], "sound_data_maxamp oboe.snd")
  snd_test_neq(sound_data_peak(sd), mx[0], "sound_data_peak oboe.snd")
  if (res = Snd.catch do set_selected_channel(1) end).first != :no_such_channel
    snd_display("set_selected_channel bad chan: %s?", res)
  end
  if (res = Snd.catch do set_selected_channel(123456, 1) end).first != :no_such_sound
    snd_display("set_selected_channel bad snd: %s?", res)
  end
  [[2, 1000],
   [-1, 1000],
   [0, -1],
   [0, 10000000]].each do |chn, frm|
    if (res = Snd.catch do sd[chn, frm] end).first != :out_of_range
      snd_display("sound_data_ref bad chan or frame: %s %s %s?", chn, frm, res)
    end
  end
  [[2, 1000],
   [0, 10000000]].each do |chn, frm|
    if (res = Snd.catch do sd[chn, frm] = 1 end).first != :out_of_range
      snd_display("sound_data_set! bad chan or frame: %s %s %s?", chn, frm, res)
    end
  end
  v = make_vct(3)
  if (res = Snd.catch do vct2sound_data(v, sd, 2) end).first != :out_of_range
    snd_display("vct2sound_data set bad chan: %s?", res.inspect)
  end
  close_sound(ob)
  #
  if selected_sound
    snd_display("selected_sound %s %s?", selected_sound, sounds.inspect)
  end
  #
  with_file("a.sf2") do |fsnd|
    fil = open_sound(fsnd)
    loops = soundfont_info(fil)
    if loops.nil? or loops[0][2] != 65390 or loops[1][1] != 65490
      snd_display("soundfont_info: %s", loops.inspect)
    end
    close_sound(fil)
  end
end

def test_04_01
  fmv5_snd = "fmv5.snd"
  delete_file(fmv5_snd)
  fd = mus_sound_open_output(fmv5_snd, 22050, 1, Mus_bshort, Mus_aiff, "no comment")
  sdata = SoundData.new(1, 100)
  100.times do |i| sdata[0, i] = i * 0.01 end
  snd_test_neq(sdata.to_s,
               "#<sound-data[chans=1, length=100]:
    (0.000 0.010 0.020 0.030 0.040 0.050 0.060 0.070 0.080 0.090 0.100 0.110 ...)>",
               "print sound_data")
  edat = sdata
  edat1 = SoundData.new(1, 100)
  edat2 = SoundData.new(2, 100)
  snd_test_neq(edat, sdata, "sound_data")
  snd_test_eq(edat1, sdata, "sound_data 1")
  snd_test_eq(edat1, edat2, "sound_data 2")
  100.times do |i| edat1[0, i] = sdata[0, i] end
  snd_test_neq(edat1, sdata, "sound_data 3")
  v0 = make_vct(100)
  v1 = make_vct(3)
  sound_data2vct(sdata, 0, v0)
  snd_test_neq(v0[10], 0.1, "sound_data2vct")
  sound_data2vct(sdata, 0, v1)
  snd_test_neq(v1[1], 0.01, "sound_data2(small)vct")
  vct2sound_data(v0, sdata, 0)
  snd_test_neq(sound_data_ref(sdata, 0, 10), 0.1, "vct2sound_data")
  snd_test_neq(sdata[0, 10], 0.1, "vct2sound_data applied")
  if (res = Snd.catch do sound_data2vct(sdata, 2, v0) end).first != :out_of_range
    snd_display("sound_data2vct bad chan: %s?", res.inspect)
  end
  if (res = Snd.catch do mus_audio_write(1, make_sound_data(3, 3), 123) end).first != :out_of_range
    snd_display("mus_audio_write bad frames: %s?", res.inspect)
  end
  #
  v0 = make_vct(10)
  vx = make_vct(3)
  sdata2 = SoundData.new(2, 10)
  10.times do |i|
    sdata2[0, i] = 0.1
    sdata2[1, i] = 0.2
  end
  sound_data2vct(sdata2, 0, v0)
  sound_data2vct(sdata2, 0, vx)
  snd_test_neq(v0[1], 0.1, "sound_data2vct[1]")
  sound_data2vct(sdata2, 1, v0)
  snd_test_neq(v0[1], 0.2, "sound_data2vct[2]")
  vct2sound_data(v0, sdata2, 0)
  snd_test_neq(sdata2[0, 1], 0.2, "vct2sound_data[2]")
  vct_fill!(v0, 0.3)
  vct2sound_data(v0, sdata2, 1)
  snd_test_neq(sdata2[1, 1], 0.3, "vct2sound_data[3]")
  vct2sound_data(vx, sdata2, 0)
  mus_sound_write(fd, 0, 99, 1, sdata)
  mus_sound_close_output(fd, 100 * mus_bytes_per_sample(Mus_bshort))
  fd = mus_sound_reopen_output(fmv5_snd, 1, Mus_bshort, Mus_aiff, mus_sound_data_location(fmv5_snd))
  mus_sound_close_output(fd, 100 * mus_bytes_per_sample(Mus_bshort))
  fd = mus_sound_open_input(fmv5_snd)
  mus_sound_read(fd, 0, 99, 1, sdata)
  snd_test_neq(sdata[0, 10], 0.1, "mus_sound_write")
  pos = mus_sound_seek_frame(fd, 20)
  # FIXME (INFO)
  # IO.open(fd).pos doesn't work
  # snd_test_neq(IO.open(fd).pos, pos, "1 mus_sound_seek_frame")
  snd_test_neq(frame2byte(fmv5_snd, 20), pos, "2 mus_sound_seek_frame")
  mus_sound_read(fd, 0, 10, 1, sdata)
  snd_test_neq(sdata[0, 0], 0.2, "2 mus_sound_seek")
  mus_sound_close_input(fd)
  #
  sd = make_sound_data(2, 10)
  vct2sound_data(Vct.new(10, 0.25), sd, 0)
  vct2sound_data(Vct.new(10, 0.50), sd, 1)
  sound_data_scale!(sd, 2.0)
  snd_test_neq(sound_data2vct(sd, 0), Vct.new(10, 0.5), "sound_data_scale! chan 0")
  snd_test_neq(sound_data2vct(sd, 1), Vct.new(10, 1.0), "sound_data_scale! chan 1")
  sd = make_sound_data(2, 10)
  sound_data_fill!(sd, 2.0)
  snd_test_neq(sound_data2vct(sd, 0), Vct.new(10, 2.0), "sound_data_fill! chan 0")
  snd_test_neq(sound_data2vct(sd, 1), Vct.new(10, 2.0), "sound_data_fill! chan 1")
  #
  if (res = Snd.catch do
        mus_sound_open_output("fmv.snd", 22050, -1, Mus_bshort, Mus_aiff, "no comment")
      end).first != :out_of_range
    snd_display("mus_sound_open_output bad chans: %s?", res)
  end
  if (res = Snd.catch do
        mus_sound_open_output("fmv.snd", 22050, 1, -1, Mus_aiff, "no comment")
      end).first != :out_of_range
    snd_display("mus_sound_open_output bad format: %s?", res)
  end
  if (res = Snd.catch do
        mus_sound_open_output("fmv.snd", 22050, 1, Mus_bshort, -1, "no comment")
      end).first != :out_of_range
    snd_display("mus_sound_open_output bad type: %s?", res)
  end
  if (res = Snd.catch do
        mus_sound_reopen_output("fmv.snd", -1, Mus_bshort, Mus_aiff, false)
      end).first != :out_of_range
    snd_display("mus_sound_reopen_output bad chans: %s?", res)
  end
  if (res = Snd.catch do
        mus_sound_reopen_output("fmv.snd", 1, -1, Mus_aiff, false)
      end).first != :out_of_range
    snd_display("mus_sound_reopen_output bad format: %s?", res)
  end
  if (res = Snd.catch do
        mus_sound_reopen_output("fmv.snd", 1, Mus_bshort, -1, false)
      end).first != :out_of_range
    snd_display("mus_sound_reopen_output bad type: %s?", res)
  end
  #
  sd = SoundData.new(2, 10)
  sd.fill!(1.0)
  snd_test_neq(sound_data2vct(sd, 0), Vct.new(10, 1.0), "sd.fill! chan 0")
  snd_test_neq(sound_data2vct(sd, 1), Vct.new(10, 1.0), "sd.fill! chan 1")
  sd1 = sd.dup
  snd_test_neq(sd1, sd, "sd.dup (copy)")
  #
  ["trunc.snd",
   "trunc.aiff",
   "trunc.wav",
   "trunc.sf",
   "trunc.voc",
   "trunc.nist",
   "bad.wav",
   "trunc1.aiff",
   "badform.aiff"].each do |file|
    with_file(file) do |fsnd|
      res = Snd.catch do open_sound(fsnd) end
      snd_test_neq(res.first, :mus_error, "open_sound %s", file)
    end
  end
  $open_raw_sound_hook.add_hook!("snd-test-044") do |file, choice| [1, 22050, Mus_bshort] end
  with_file("empty.snd") do |fsnd|
    ind = open_sound(fsnd)
    if data_format(ind) != Mus_bshort or
        channels(ind) != 1 or
        srate(ind) != 22050 or
        data_location(ind) != 0 or
        frames(ind) != 0
      snd_display("open raw: %s %s %s %s %s?",
                  data_format(ind),
                  channels(ind),
                  srate(ind),
                  data_location(ind),
                  frames(ind))
    end
    close_sound(ind)
  end
  $open_raw_sound_hook.reset_hook!
  #
  sd1 = SoundData.new(1, 32)
  sd2 = SoundData.new(2, 64)
  32.times do |i|
    sd1[0, i] = i * 0.01
  end
  64.times do |i|
    sd2[0, i] = i * 0.1
    sd2[1, i] = i * 0.2
  end
  sound_data2sound_data(sd2, sd1, 3, 6, 32)
  [[0, 0.00],
   [2, 0.02],
   [3, 0.00],
   [6, 0.30],
   [10, 0.1]].each do |idx, val|
    snd_test_neq(sd1[0, idx], val, "sound_data2sound_data %d", idx)
  end
  sound_data2sound_data(sd1, sd2, 0, 10, 32)
  snd_test_neq(sd2[0, 5], 0.2, "sound_data2sound_data 2 5")
  #
  sdi = SoundData.new(1, 32)
  sdo = SoundData.new(1, 32)
  snd_test_neq(sound_data2sound_data(sdi, sdo, 10, 32, 10),  2, "sound_data2sound_data wrap around")
  snd_test_neq(sound_data2sound_data(sdi, sdo, 10, 32, 32), 10, "sound_data2sound_data wrap around")
  if (res = Snd.catch do
        sound_data2sound_data(sdi, sdo, -1, 10, 10)
      end).first != :out_of_range
    snd_display("sound_data2sound_data start: %s", res)
  end
  if (res = Snd.catch do
        sound_data2sound_data(sdi, sdo, 0, -1, 10)
      end).first != :out_of_range
    snd_display("sound_data2sound_data frames: %s", res)
  end
  if (res = Snd.catch do
        sound_data2sound_data(sdi, sdo, 0, 128, 10)
      end).first != :out_of_range
    snd_display("sound_data2sound_data frames: %s", res)
  end
  #
  sd = SoundData.new(1, 1)
  snd_test_neq(sd[0, 0], 0.0, "sound_data_ref")
  sd[0, 0] = 1.0
  snd_test_neq(sd[0, 0], 1.0, "sound_data_set")
  sd1 = make_sound_data(1, 1)
  sound_data_set!(sd1, 0, 0, 1.0)
  snd_test_neq(sd, sd1, "sound_data_set")
  #
  sd = SoundData.new(2, 3)
  snd_test_neq(sd[0, 0], 0.0, "sound_data_ref (1)")
  sd[1, 0] = 1.0
  snd_test_neq(sd[1, 0], 1.0, "sound_data_set (1 0)")
  sd[1, 2] = 2.0
  snd_test_neq(sd[1, 2], 2.0, "sound_data_set (1 2)")
  sd1 = make_sound_data(2, 3)
  sound_data_set!(sd1, 1, 0, 1.0)
  sound_data_set!(sd1, 1, 2, 2.0)
  snd_test_neq(sd, sd1, "sound_data_set (3)")
  #
end

def test_04_02
  [1, 2, 4, 8].each do |chans|
    [[Mus_bshort, Mus_next],
     [Mus_bfloat, Mus_aifc],
     [Mus_lshort, Mus_aifc],
     [Mus_lfloat, Mus_riff],
     [Mus_lshort, Mus_nist],
     [Mus_bint, Mus_aiff],
     [Mus_lint, Mus_next],
     [Mus_bintn, Mus_next],
     [Mus_lintn, Mus_next],
     [Mus_b24int, Mus_aifc],
     [Mus_l24int, Mus_riff],
     [Mus_bfloat, Mus_ircam],
     [Mus_bfloat_unscaled, Mus_next],
     [Mus_lfloat_unscaled, Mus_next],
     [Mus_bdouble_unscaled, Mus_next],
     [Mus_ldouble_unscaled, Mus_next],
     [Mus_bdouble, Mus_next],
     [Mus_ldouble, Mus_next],
     [Mus_ulshort, Mus_next],
     [Mus_ubshort, Mus_next]].each do |df, ht|
      samps = case chans
              when 1
                100000
              when 2
                50000
              else
                1000
              end
      delete_file("fmv5.snd")
      fd = mus_sound_open_output("fmv5.snd", 22050, chans, df, ht, "no, comment")
      sdata = SoundData.new(chans, samps)
      ndata = SoundData.new(chans, samps)
      chans.times do |chn|
        samps.times do |i|
          # FIXME (INFO)
          # This leads to v0 = 1.0 != v1 = -1.0 at some places:
          # sdata[chn, i] = random(2.0) - 1.0
          sdata[chn, i] = random(1.9999) - 1.0
        end
      end
      mus_sound_write(fd, 0, samps - 1, chans, sdata)
      mus_sound_close_output(fd, samps * chans * mus_bytes_per_sample(df))
      fd = mus_sound_open_input("fmv5.snd")
      mus_sound_read(fd, 0, samps - 1, chans, ndata)
      pos = mus_sound_seek_frame(fd, 100)
      # FIXME (INFO)
      # IO.open(fd).pos doesn't work
      # snd_test_neq(IO.open(fd).pos,
      #              pos,
      #              "mus_sound_seek_frame(%d) chans %d, (%s %s)",
      #              pos,
      #              chans,
      #              mus_header_type_name(ht),
      #              mus_data_format_name(df))
      snd_test_neq(frame2byte("fmv5.snd", 100),
                   pos,
                   "mus_sound_seek_frame(100) chans %d, (%s %s)",
                   chans,
                   mus_header_type_name(ht),
                   mus_data_format_name(df))
      mus_sound_close_input(fd)
      Snd.catch(:read_write_error, lambda do |*args|
                  snd_display("read_write trouble: %s %s (%s != %s at [%s, %s])?",
                              *args.first[2..-1])
                end) do
        chans.times do |chn|
          samps.times do |i|
            v0 = sdata[chn, i]
            v1 = ndata[chn, i]
            if fneq(v0, v1)
              Snd.throw(:read_write_error,
                        mus_data_format_name(df), mus_header_type_name(ht), v0, v1, chn, i)
            end
          end
        end
      end
    end
  end
end

def test_04_03
  ind = open_sound("oboe.snd")
  our_short = little_endian? ? Mus_lshort : Mus_bshort
  our_srate = 22050
  our_dac_buffer_size_in_bytes = 512
  our_dac_buffer_size_in_shorts = 256
  our_chans = 1
  our_chan = 0
  inport = Snd.catch(:mus_error, -1) do
    mus_audio_open_input(0, our_srate, our_chans, our_short, our_dac_buffer_size_in_bytes)
  end.first
  data = make_sound_data(our_chans, our_dac_buffer_size_in_shorts)
  vobj = make_vct(our_dac_buffer_size_in_shorts)
  if inport == -1
    snd_display("cannot open audio input port!")
  else
    10.times do
      mus_audio_read(inport, data, our_dac_buffer_size_in_shorts)
      graph(sound_data2vct(data, our_chan, vobj))
    end
    mus_audio_close(inport)
  end
  close_sound(ind)
  #
  fmv = "fmv.snd"
  fd = mus_sound_open_output(fmv, 22050, 1, Mus_bshort, Mus_next, "no comment")
  sdata = SoundData.new(1, 10)
  sdata[0, 1] = 0.1
  mus_sound_write(fd, 0, 9, 1, sdata)
  mus_sound_close_output(fd, 20)
  fd = mus_sound_open_input(fmv)
  mus_sound_read(fd, 0, 9, 1, sdata)
  if fneq(sdata[0, 0], 0.0) or
      fneq(sdata[0, 1], 0.1) or
      fneq(sdata[0, 2], 0.0) or
      fneq(sdata[0, 6], 0.0)
    snd_display("read/write: %s?", sdata.to_a)
  end
  mus_sound_close_input(fd)
  fd = mus_sound_reopen_output(fmv, 1, Mus_bshort, Mus_next, mus_sound_data_location(fmv))
  mus_sound_seek_frame(fd, 0)
  sdata[0, 2] = 0.1
  sdata[0, 3] = 0.1
  mus_sound_write(fd, 0, 9, 1, sdata)
  mus_sound_close_output(fd, 20)
  fd = mus_sound_open_input(fmv)
  sdata1 = SoundData.new(1, 10)
  mus_sound_read(fd, 0, 9, 1, sdata1)
  if fneq(sdata1[0, 0], 0.0) or
      fneq(sdata1[0, 1], 0.1) or
      fneq(sdata1[0, 2], 0.1) or
      fneq(sdata1[0, 3], 0.1) or
      fneq(sdata1[0, 6], 0.0)
    snd_display(snd_format_neq(sdata1.to_a, sdata.to_a, "re-read/write"))
  end
  mus_sound_close_input(fd)
  #
  # check clipping choices
  #
  ind = view_sound("oboe.snd")
  set_clipping(false)
  map_channel(lambda do |y| y * 10.0 end, 0, frames(), ind, 0)
  save_sound_as("test.snd", ind, Mus_next, Mus_bfloat)
  undo_edit(1, ind, 0)
  ind1 = open_sound("test.snd")
  snd_test_neq(maxamp(ind1, 0), 10.0 * maxamp(ind, 0), "clipping 0")
  close_sound(ind1)
  delete_file("test.snd")
  #
  set_clipping(true)
  map_channel(lambda do |y| y * 10.0 end, 0, frames(), ind, 0)
  save_sound_as("test.snd", ind, Mus_next, Mus_bfloat)
  undo_edit(1, ind, 0)
  ind1 = open_sound("test.snd")
  snd_test_neq(maxamp(ind1, 0), 1.0, "clipping 1")
  close_sound(ind1)
  delete_file("test.snd")
  #
  set_clipping(false)
  mx = maxamp(ind)
  map_channel(lambda do |y| y + (1.001 - mx) end, 0, frames(), ind, 0)
  save_sound_as("test.snd", ind, Mus_next, Mus_bshort)
  ind1 = open_sound("test.snd")
  unless res = scan_channel(lambda do |y| y < 0.0 end)
    snd_display("clipping 2: %s?", res)
  end
  close_sound(ind1)
  delete_file("test.snd")
  #
  set_clipping(true)
  save_sound_as("test.snd", ind, Mus_next, Mus_bshort)
  ind1 = open_sound("test.snd")
  if res = scan_channel(lambda do |y| y < 0.0 end)
    snd_display("clipping 3: %s?", res)
  end
  close_sound(ind1)
  delete_file("test.snd")
  #
  set_clipping(false)
  close_sound(ind)
  delete_file(fmv)
  #
  set_clipping(false)
  snd = new_sound("test.snd", :data_format, Mus_lshort)
  pad_channel(0, 10)
  set_sample(1,  1.0)
  set_sample(2, -1.0)
  set_sample(3,  0.9999)
  set_sample(4,  2.0)
  set_sample(5, -2.0)
  set_sample(6,  1.3)
  set_sample(7, -1.3)
  set_sample(8,  1.8)
  set_sample(9, -1.8)
  save_sound(snd)
  close_sound(snd)
  snd = open_sound("test.snd")
  snd_test_neq(channel2vct(0, 10),
               vct(0.0, 1.0, -1.0, 1.0, 0.0, 0.0, -0.7, 0.7, -0.2, 0.2),
               "unclipped 1")
  close_sound(snd)
  mus_sound_forget("test.snd")
  #
  set_clipping(true)
  snd = new_sound("test.snd", :data_format, Mus_lshort)
  pad_channel(0, 10)
  set_sample(1,  1.0)
  set_sample(2, -1.0)
  set_sample(3,  0.9999)
  set_sample(4,  2.0)
  set_sample(5, -2.0)
  set_sample(6,  1.3)
  set_sample(7, -1.3)
  set_sample(8,  1.8)
  set_sample(9, -1.8)
  save_sound(snd)
  close_sound(snd)
  snd = open_sound("test.snd")
  snd_test_neq(channel2vct(0, 10),
               vct(0.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0),
               "clipped")
  close_sound(snd)
  #
  data = vct(0.0, 1.0, -1.0, 0.9999, 2.0, -2.0, 1.3, -1.3, 1.8, -1.8)
  sdata = vct2sound_data(data)
  snd = mus_sound_open_output("test.snd", 22050, 1, Mus_lshort, Mus_riff, "a comment")
  old_clip = mus_file_clipping(snd)
  old_snd = snd
  set_mus_file_clipping(snd, false)
  mus_sound_write(snd, 0, 9, 1, sdata)
  mus_sound_close_output(snd, 40)
  snd = open_sound("test.snd")
  snd_test_neq(channel2vct(0, 10),
               vct(0.0, -1.0, -1.0, 1.0, 0.0, 0.0, -0.7, 0.7, -0.2, 0.2),
               "unclipped 2")
  close_sound(snd)
  mus_sound_forget("test.snd")
  #
  data = vct(0.0, 1.0, -1.0, 0.9999, 2.0, -2.0, 1.3, -1.3, 1.8, -1.8)
  sdata = vct2sound_data(data)
  snd = mus_sound_open_output("test.snd", 22050, 1, Mus_lshort, Mus_riff, "a comment")
  set_mus_file_clipping(snd, true)
  mus_sound_write(snd, 0, 9, 1, sdata)
  set_mus_file_clipping(snd, false)
  mus_sound_close_output(snd, 40)
  if snd == old_snd
    set_mus_file_clipping(snd, old_clip)
  end
  snd = open_sound("test.snd")
  snd_test_neq(channel2vct(0, 10),
               vct(0.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0),
               "clipped 1")
  close_sound(snd)
  #
  set_mus_clipping(false)
  data = vct(0.0, 1.0, -1.0, 0.9999, 2.0, -2.0, 1.3, -1.3, 1.8, -1.8)
  sdata = vct2sound_data(data)
  snd = mus_sound_open_output("test.snd", 22050, 1, Mus_lshort, Mus_riff, "a comment")
  mus_sound_write(snd, 0, 9, 1, sdata)
  mus_sound_close_output(snd, 40)
  snd = open_sound("test.snd")
  snd_test_neq(channel2vct(0, 10),
               vct(0.0, -1.0, -1.0, 1.0, 0.0, 0.0, -0.7, 0.7, -0.2, 0.2),
               "unclipped 3")
  close_sound(snd)
  mus_sound_forget("test.snd")
  #
  set_mus_clipping(true)
  data = vct(0.0, 1.0, -1.0, 0.9999, 2.0, -2.0, 1.3, -1.3, 1.8, -1.8)
  sdata = vct2sound_data(data)
  snd = mus_sound_open_output("test.snd", 22050, 1, Mus_lshort, Mus_riff, "a comment")
  mus_sound_write(snd, 0, 9, 1, sdata)
  mus_sound_close_output(snd, 40)
  snd = open_sound("test.snd")
  snd_test_neq(channel2vct(0, 10),
               vct(0.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0),
               "clipped 2")
  close_sound(snd)
  #
  set_mus_clipping(true)
  data = vct(0.0, 1.0, -1.0, 0.9999, 2.0, -2.0, 1.3, -1.3, 1.8, -1.8)
  sdata = vct2sound_data(data)
  snd = mus_sound_open_output("test.snd", 22050, 1, Mus_lshort, Mus_riff, "a comment")
  if (res = Snd.catch do mus_sound_write(snd, 0, 10, 1, sdata) end).first != :out_of_range
    snd_display("mus_sound_write too many bytes: %s", res.inspect)
  end
  if (res = Snd.catch do mus_sound_read(snd, 0, 10, 1, sdata) end).first != :out_of_range
    snd_display("mus_sound_read too many bytes: %s", res.inspect)
  end
  mus_sound_close_output(snd, 0)
  delete_file("test.snd")
  mus_sound_forget("test.snd")
  set_mus_clipping(false)       # default
  set_clipping(false)
  #
  com = "this is a comment which we'll repeat enough times to trigger an internal loop" * 3
  fd = mus_sound_open_output(fmv, 22050, 4, Mus_lshort, Mus_riff, com)
  sdata = SoundData.new(4, 10)
  4.times do |i| sdata[i, 1] = 0.1 end
  mus_sound_write(fd, 0, 9, 4, sdata)
  mus_sound_close_output(fd, 80)
  fd = mus_sound_open_input(fmv)
  mus_sound_read(fd, 0, 9, 4, sdata)
  4.times do |i|
    if fneq(sdata[i, 0], 0.0) or
        fneq(sdata[i, 1], 0.1) or
        fneq(sdata[i, 2], 0.0) or
        fneq(sdata[i, 6], 0.0)
      snd_display("1 read/write[%d]: %s?", i, sdata.to_vct(i).to_a)
    end
  end
  mus_sound_close_input(fd)
  fd = mus_sound_reopen_output(fmv, 4, Mus_lshort, Mus_riff, mus_sound_data_location(fmv))
  mus_sound_seek_frame(fd, 0)
  4.times do |i|
    sdata[i, 2] = 0.1
    sdata[i, 3] = 0.1
  end
  mus_sound_write(fd, 0, 9, 4, sdata)
  mus_sound_close_output(fd, 80)
  fd = mus_sound_open_input(fmv)
  sdata1 = SoundData.new(4, 10)
  mus_sound_read(fd, 0, 9, 4, sdata1)
  4.times do |i|
    if fneq(sdata1[i, 0], 0.0) or
        fneq(sdata1[i, 1], 0.1) or
        fneq(sdata1[i, 2], 0.1) or
        fneq(sdata1[i, 3], 0.1) or
        fneq(sdata1[i, 6], 0.0)
      snd_display(snd_format_neq(sdata1.to_vct(i).to_a, sdata.to_vct(i).to_a,
                                 "2 re-read/write[%d]", i))
    end
  end
  mus_sound_close_input(fd)
  #
  with_file("32bit.sf") do |fsnd|
    ind = open_sound(fsnd)
    snd_test_neq(maxamp(ind, 0), 0.228, "32bit max")
    close_sound(ind)
  end
  [["next-dbl.snd", 10, 10,
    vct(0.475, 0.491, 0.499, 0.499, 0.492, 0.476, 0.453, 0.423, 0.387, 0.344)],
   ["oboe.ldbl", 1000, 10,
    vct(0.033, 0.035, 0.034, 0.031, 0.026, 0.020, 0.013, 0.009, 0.005, 0.004)],
   ["next-flt.snd", 10, 10,
    vct(0.475, 0.491, 0.499, 0.499, 0.492, 0.476, 0.453, 0.423, 0.387, 0.344)],
   ["clbonef.wav", 1000, 10,
    vct(0.111, 0.101, 0.070, 0.032, -0.014, -0.060, -0.085, -0.108, -0.129, -0.152)],
   ["next-8.snd", 10, 10,
    vct(0.898, 0.945, 0.977, 0.992, 0.992, 0.977, 0.945, 0.906, 0.844, 0.773)],
   ["o2_u8.wave", 1000, 10,
    vct(-0.164, -0.219, -0.258, -0.242, -0.180, -0.102, -0.047, 0.000, 0.039, 0.055)],
   ["next-16.snd", 1000, 10,
    vct(-0.026, -0.022, -0.024, -0.030, -0.041, -0.048, -0.050, -0.055, -0.048, -0.033)],
   ["o2.wave", 1000, 10,
    vct(-0.160, -0.216, -0.254, -0.239, -0.175, -0.102, -0.042, 0.005, 0.041, 0.059)],
   ["o2_18bit.aiff", 1000, 10,
    vct(-0.160, -0.216, -0.254, -0.239, -0.175, -0.102, -0.042, 0.005, 0.041, 0.059)],
   ["o2_12bit.aiff", 1000, 10,
    vct(-0.160, -0.216, -0.254, -0.239, -0.175, -0.102, -0.042, 0.005, 0.041, 0.059)],
   ["next24.snd", 1000, 10,
    vct(-0.160, -0.216, -0.254, -0.239, -0.175, -0.102, -0.042, 0.005, 0.041, 0.059)],
   ["mono24.wav", 1000, 10,
    vct(0.005, 0.010, 0.016, 0.008, -0.007, -0.018, -0.025, -0.021, -0.005, 0.001)],
   ["o2_711u.wave", 1000, 10,
    vct(-0.164, -0.219, -0.254, -0.242, -0.172, -0.103, -0.042, 0.005, 0.042, 0.060)],
   ["alaw.wav", 1000, 10,
    vct(-0.024, -0.048, -0.024, 0.000, 0.008, 0.008, 0.000, -0.040, -0.064, -0.024)],
   ["b32.pvf", 1000, 10,
    vct(-0.160, -0.216, -0.254, -0.239, -0.175, -0.102, -0.042, 0.005, 0.041, 0.059)],
   ["b32.wave", 1000, 10,
    vct(-0.160, -0.216, -0.254, -0.239, -0.175, -0.102, -0.042, 0.005, 0.041, 0.059)],
   ["b32.snd", 1000, 10,
    vct(-0.160, -0.216, -0.254, -0.239, -0.175, -0.102, -0.042, 0.005, 0.041, 0.059)],
   ["32bit.sf", 1000, 10,
    vct(0.016, 0.014, 0.013, 0.011, 0.010, 0.010, 0.010, 0.010, 0.012, 0.014)],
   ["nist-shortpack.wav", 10000, 10,
    vct(0.021, 0.018, 0.014, 0.009, 0.004, -0.001, -0.004, -0.006, -0.007, -0.008)],
   ["wood.sds", 1000, 10,
    vct(-0.160, -0.216, -0.254, -0.239, -0.175, -0.102, -0.042, 0.005, 0.041, 0.059)],
   ["oboe.g721", 1000, 10,
    vct(-0.037, -0.040, -0.040, -0.041, -0.042, -0.038, -0.028, -0.015, -0.005, 0.002)],
   ["oboe.g723_40", 1000, 10,
    vct(-0.037, -0.040, -0.041, -0.041, -0.041, -0.038, -0.028, -0.015, -0.005, 0.003)],
   ["mus10.snd", 10000, 10,
    vct(0.004, 0.001, 0.005, 0.009, 0.017, 0.015, 0.008, 0.011, 0.009, 0.012)],
   ["ieee-text-16.snd", 1000, 10,
    vct(-0.052, -0.056, -0.069, -0.077, -0.065, -0.049, -0.054, -0.062, -0.066, -0.074)],
   ["hcom-16.snd", 10000, 10,
    vct(0.000, 0.000, 0.000, 0.008, 0.000, -0.016, -0.016, -0.016, -0.008, 0.000)],
   ["ce-c3.w02", 1000, 10,
    vct(0.581, 0.598, 0.596, 0.577, 0.552, 0.530, 0.508, 0.479, 0.449, 0.425)],
   ["nasahal.avi", 20000, 10,
    vct(0.390, 0.120, -0.399, -0.131, 0.464, 0.189, -0.458, -0.150, 0.593, 0.439)],
   ["oki.wav", 100, 10,
    vct(0.396, 0.564, 0.677, 0.779, 0.761, 0.540, 0.209, -0.100, -0.301, -0.265)],
   ["trumps22.adp", 5000, 10,
    vct(0.267, 0.278, 0.309, 0.360, 0.383, 0.414, 0.464, 0.475, 0.486, 0.495)]
  ].each do |file, beg, dur, data|
    with_file(file) do |fsnd|
      Snd.catch do
        ind = open_sound(fsnd)
        snd_test_neq(channel2vct(beg, dur, ind, 0), data, "%s", file)
        close_sound(ind)
      end
    end
  end
end

def test_04_04
  ["no error",
   "no frequency method",
   "no phase method",
   "null gen arg to method",
   "no length method",
   "no free method",
   "no describe method",
   "no data method",
   "no scaler method",
   "memory allocation failed",
   "unstable two pole error",
   "can't open file",
   "no sample input",
   "no sample output",
   "no such channel",
   "no file name provided",
   "no location method",
   "no channel method",
   "no such fft window",
   "unsupported data format",
   "header read failed",
   "unsupported header type",
   "file descriptors not initialized",
   "not a sound file",
   "file closed",
   "write error",
   "header write failed",
   "can't open temp file",
   "interrupted",
   "bad envelope",
   "audio channels not available",
   "audio srate not available",
   "audio format not available",
   "no audio input available",
   "audio configuration not available",
   "audio write error",
   "audio size not available",
   "audio device not available",
   "can't close audio",
   "can't open audio",
   "audio read error",
   "can't write audio",
   "can't read audio",
   "no audio read permission",
   "can't close file",
   "arg out of range",
   "wrong type arg",
   "no channels method",
   "no hop method",
   "no width method",
   "no file-name method",
   "no ramp method",
   "no run method",
   "no increment method",
   "no offset method",
   "no xcoeff method",
   "no ycoeff method",
   "no xcoeffs method",
   "no ycoeffs method",
   "no reset",
   "bad size",
   "can't convert",
   "read error",
   "no safety method",
   "can't translate"].each_with_index do |err, i|
    snd_test_neq(err, mus_error_type2string(i), "mus_error_type2string %d", i)
  end
  oboe_snd = "oboe.snd"
  cur_srate = mus_sound_srate(oboe_snd)
  cur_chans = mus_sound_chans(oboe_snd)
  cur_format = mus_sound_data_format(oboe_snd)
  cur_type = mus_sound_header_type(oboe_snd)
  cur_loc = mus_sound_data_location(oboe_snd)
  cur_samps = mus_sound_samples(oboe_snd)
  set_mus_sound_srate(oboe_snd, cur_srate * 2)
  snd_test_neq(mus_sound_srate(oboe_snd), cur_srate * 2, "set_mus_sound_srate")
  set_mus_sound_samples(oboe_snd, cur_samps * 2)
  snd_test_neq(mus_sound_samples(oboe_snd), cur_samps * 2, "set_mus_sound_samples")
  set_mus_sound_chans(oboe_snd, cur_chans * 2)
  snd_test_neq(mus_sound_chans(oboe_snd), cur_chans * 2, "set_mus_sound_chans")
  set_mus_sound_data_location(oboe_snd, cur_loc * 2)
  snd_test_neq(mus_sound_data_location(oboe_snd), cur_loc * 2, "set_mus_sound_data_location")
  set_mus_sound_header_type(oboe_snd, Mus_nist)
  snd_test_neq(mus_sound_header_type(oboe_snd), Mus_nist, "set_mus_sound_header_type")
  set_mus_sound_data_format(oboe_snd, Mus_lintn)
  snd_test_neq(mus_sound_data_format(oboe_snd), Mus_lintn, "set_mus_sound_data_format")
  set_mus_sound_srate(oboe_snd, cur_srate)
  set_mus_sound_samples(oboe_snd, cur_samps)
  set_mus_sound_chans(oboe_snd, cur_chans)
  set_mus_sound_data_location(oboe_snd, cur_loc)
  set_mus_sound_header_type(oboe_snd, cur_type)
  set_mus_sound_data_format(oboe_snd, cur_format)
  #
  ind = open_sound("oboe.snd")
  save_sound_as("test.wave", ind, Mus_riff)
  save_sound_as("test.rf64", ind, Mus_rf64)
  save_sound_as("test.aifc", ind, Mus_aifc)
  close_sound(ind)
  ["test.wave",
   "test.rf64",
   "test.aifc"].each do |file|
    cur_srate = mus_sound_srate(file)
    cur_chans = mus_sound_chans(file)
    cur_format = mus_sound_data_format(file)
    cur_type = mus_sound_header_type(file)
    cur_loc = mus_sound_data_location(file)
    cur_samps = mus_sound_samples(file)
    set_mus_sound_srate(file, cur_srate * 2)
    snd_test_neq(mus_sound_srate(file), cur_srate * 2, "%s set_mus_sound_srate", file)
    set_mus_sound_samples(file, cur_samps * 2)
    snd_test_neq(mus_sound_samples(file), cur_samps * 2, "%s set_mus_sound_samples", file)
    set_mus_sound_chans(file, cur_chans * 2)
    snd_test_neq(mus_sound_chans(file), cur_chans * 2, "%s set_mus_sound_chans", file)
    set_mus_sound_data_location(file, cur_loc * 2)
    snd_test_neq(mus_sound_data_location(file), cur_loc * 2, "%s set_mus_sound_data_location", file)
    set_mus_sound_header_type(file, Mus_nist)
    snd_test_neq(mus_sound_header_type(file), Mus_nist, "%s set_mus_sound_header_type", file)
    set_mus_sound_data_format(file, Mus_lintn)
    snd_test_neq(mus_sound_data_format(file), Mus_lintn, "%s set_mus_sound_data_format", file)
    set_mus_sound_srate(file, cur_srate)
    set_mus_sound_samples(file, cur_samps)
    set_mus_sound_chans(file, cur_chans)
    set_mus_sound_data_location(file, cur_loc)
    set_mus_sound_header_type(file, cur_type)
    set_mus_sound_data_format(file, cur_format)
  end
  ["test.wave",
   "test.rf64",
   "test.aifc"].each do |file|
    ind = open_sound(file)
    cur_srate = srate(ind)
    cur_chans = chans(ind)
    cur_format = data_format(ind)
    cur_type = header_type(ind)
    cur_loc = data_location(ind)
    cur_samps = frames(ind)
    set_srate(ind, cur_srate * 2)
    snd_test_neq(srate(ind), cur_srate * 2, "%s set_srate", file)
    set_frames(cur_samps * 2, ind)
    snd_test_neq(frames(ind), cur_samps * 2, "%s set_frames", file)
    set_chans(ind, cur_chans * 2) # this can change the index
    xind = find_sound(file)
    if ind != xind
      ind = xind
    end
    snd_test_neq(chans(ind), cur_chans * 2, "%s set_chans", file)
    set_data_location(ind, cur_loc * 2)
    snd_test_neq(data_location(ind), cur_loc * 2, "%s set_location", file)
    set_header_type(ind, Mus_nist)
    snd_test_neq(header_type(ind), Mus_nist, "%s set_header_type", file)
    set_data_format(ind, Mus_lintn)
    snd_test_neq(data_format(ind), Mus_lintn, "%s set_data_format", file)
    set_srate(ind, cur_srate)
    set_frames(cur_samps, ind)
    set_channels(ind, cur_chans)
    set_data_location(ind, cur_loc)
    set_header_type(ind, cur_type)
    set_data_format(ind, cur_format)
    close_sound(ind)
    delete_file(file)
  end
end

# FIXME (INFO)
# with big file
#
# with_sound(:output, $bigger_snd, :srate, 44100, :play, false) do
#   72000.times do |i|
#     fm_violin_1(i, 0.1, 440, i / 72000.0 * 0.9 + 0.01)
#   end
# end

$big_file_frames = 0

def test_04_05
  if File.exists?($bigger_snd)
    # ; silence as last .9 secs, so it probably wasn't written
    probable_frames = (44100 * 71999.1).floor
    snd_test_neq(mus_sound_samples($bigger_snd), 3175160310, "bigger samples")
    snd_test_neq(mus_sound_frames($bigger_snd), 3175160310, "bigger frames")
    snd_test_neq(mus_sound_frames($bigger_snd), probable_frames, "bigger frames (probable)")
    snd_test_neq(mus_sound_length($bigger_snd), 6350320648, "bigger length")
    snd_test_neq(mus_sound_duration($bigger_snd), 71999.1015, "bigger dur")
    ind = open_sound($bigger_snd)
    snd_test_neq(frames(ind), 3175160310, "bigger frames")
    $big_file_frames = frames(ind)
    snd_test_neq(frames(ind), probable_frames, "bigger frames (probable)")
    snd_test_neq(frames(ind, 0, 0), $big_file_frames, "bigger edpos-frames")
    m1 = add_mark(44100 * 50000, ind)
    snd_test_neq(mark_sample(m1), 44100 * 50000, "bigger mark at")
    set_mark_sample(m1, 44100 * 66000)
    snd_test_neq(mark_sample(m1), 44100 * 66000, "bigger mark to")
    if mix?(mx = mix_sound("oboe.snd", 44100 * 60000).car)
      snd_test_neq(mix_position(mx), 44100 * 60000, "bigger mix at")
      set_mix_position(mx, 44100 * 61000)
      snd_test_neq(mix_position(mx), 44100 * 61000, "bigger mix to")
      undo_edit(2)
    else
      snd_display("no mix tag from mix_sound: %s?", mx)
    end
    res = find_channel(lambda do |y| y != 0.0 end)
    if res.kind_of?(FalseClass) or res > 100
      snd_display("bigger find not 0.0: %s", res)
    end
    old_select = selection_creates_region
    set_selection_creates_region(false)
    select_all(ind)
    snd_test_neq(selection_frames(), frames(ind), "bigger select all")
    set_selection_position(44100 * 50000)
    snd_test_neq(selection_position(), 44100 * 50000, "bigger select pos")
    set_selection_position(0)
    set_selection_frames(44100 * 65000)
    snd_test_neq(selection_frames(), 44100 * 65000, "bigger select len")
    set_selection_creates_region(old_select)
    set_cursor(44100 * 50000, ind)
    snd_test_neq(cursor(ind), 44100 * 50000, "bigger cursor")
    m1 = add_mark(44123 * 51234, ind)
    snd_test_neq(mark_sample(m1), 44123 * 51234, "bigger mark at")
    mid = find_mark(44123 * 51234)
    snd_test_neq(mid, m1, "bigger find_mark")
    mx = mix_sound("oboe.snd", 44123 * 51234).car
    mxd = find_mix(44123 * 51234)
    snd_test_neq(mxd, mx, "bigger find_mix")
    set_cursor(44123 * 51234, ind)
    snd_test_neq(cursor(ind), 44123 * 51234, "bigger cursor 123")
    close_sound(ind)
  end
end

def test_04_06
  ind = new_sound("tmp.snd", Mus_riff, Mus_l24int, 22050, 1, :size, 100000)
  old_selection_creates_region = selection_creates_region()
  set_selection_creates_region(true)
  x = -0.5
  incr = 1.0 / frames()
  map_channel(lambda do |n|
                val = x
                x += incr
                val
              end)
  save_sound(ind)
  close_sound(ind)
  ind = open_sound("tmp.snd")
  reg = select_all
  [[:Mus_next, :Mus_l24int],
   [:Mus_aifc, :Mus_l24int]].each do |ht, df|
    save_selection("tmp1.snd", Module.const_get(ht), Module.const_get(df))
    ind1 = open_sound("tmp1.snd")
    x = -0.5
    incr = 1.0 / frames()
    err = scan_channel(lambda do |n|
                         val = x
                         x += incr
                         fneq(val, n)
                       end, 0, 100000, ind1)
    if err
      snd_display("%s (%s) selection not saved correctly? %s", df, ht, err)
    end
    close_sound(ind1)
  end
  save_region(reg, "tmp1.snd", Mus_next, Mus_l24int)
  ind1 = open_sound("tmp1.snd")
  x = -0.5
  incr = 1.0 / frames()
  err = scan_channel(lambda do |n|
                       val = x
                       x += incr
                       fneq(val, n)
                     end, 0, 100000, ind1)
  if err
    snd_display("Mus_l24int (Mus_next) region not saved correctly? %s", err)
  end
  close_sound(ind1)
  delete_file("tmp1.snd")
  close_sound(ind)
  delete_file("tmp.snd")
  set_selection_creates_region(old_selection_creates_region)
  #
  ind = new_sound("tmp.snd", Mus_next, Mus_bfloat, 22050, 1, :size, 10, :comment, false)
  map_channel($init_channel)
  env_channel([0.0, 0.0, 0.1, 0.1, 0.2, 0.2, 0.3, 0.3, 0.4, 0.4,
               0.5, 0.5, 0.6, 0.6, 0.7, 0.7, 0.8, 0.8, 0.9, 0.9])
  snd_test_neq(channel2vct(),
               vct(0.000, 0.100, 0.200, 0.300, 0.400, 0.500, 0.600, 0.700, 0.800, 0.900),
               "ramp env by 0.1")
  close_sound(ind)
end

def test_04_07
  $open_raw_sound_hook.reset_hook!
  $open_raw_sound_hook.add_hook!(get_func_name) do |a, b| true end
  $bad_header_hook.reset_hook!
  $bad_header_hook.add_hook!(get_func_name) do |n| true end
  if $open_raw_sound_hook.empty?
    snd_display("$open_raw_sound_hook.add_hook! failed??")
  end
  if $bad_header_hook.empty?
    snd_display("$bad_header_hook.add_hook! failed??")
  end
  magic_words = [".snd", "FORM", "AIFF", "AIFC", "COMM", "COMT", "INFO", "INST", "inst", "MARK",
                 "SSND", "FVER", "NONE", "ULAW", "ulaw", "ima4", "raw ", "sowt", "in32", "in24",
                 "ni23", "fl32", "FL32", "fl64", "twos", "ALAW", "alaw", "APPL", "CLM ", "RIFF",
                 "RIFX", "WAVE", "fmt ", "data", "fact", "clm ", "NIST", "8SVX", "16SV", "Crea",
                 "tive", "SOUN", "D SA", "MPLE", "BODY", "VHDR", "CHAN", "ANNO", "NAME", "2BIT",
                 "HCOM", "FSSD", "%//\n", "%---", "ALaw", "Soun", "MAUD", "MHDR", "MDAT", "mdat",
                 "MThd", "sfbk", "sdta", "shdr", "pdta", "LIST", "GF1P", "ATCH", "$SIG", "NAL_",
                 "GOLD", " SAM", "SRFS", "Diam", "ondW", "CSRE", "SND ", "SNIN", "SNDT", "DDSF",
                 "FSMu", "UWFD", "LM89", "SY80", "SY85", "SCRS", "DSPL", "AVI ", "strf", "movi",
                 "PRAM", " paf", "fap ", "DS16", "HEDR", "HDR8", "SDA_", "SDAB", "SD_B", "NOTE",
                 "file", "=sam", "SU7M", "SU7R", "PVF1", "PVF2", "AUTH", "riff", "TWIN", "IMPS",
                 "SMP1", "Maui", "SDIF", "NVF "]
  len = magic_words.length
  magic_words.each_with_index do |magic, ctr|
    if $open_raw_sound_hook.empty?
      snd_display("$open_raw_sound_hook.add_hook! cleared??")
    end
    if $bad_header_hook.empty?
      snd_display("$bad_header_hook.add_hook! cleared??")
    end
    delete_file("test.snd")
    mus_sound_forget("test.snd")
    File.open("test.snd", "w") do |fp|
      fp.write(magic)
      128.times do
        fp.write(mus_random(1.0))
      end
    end
    res = Snd.catch do open_sound("test.snd") end.first
    if number?(res) and sound?(res)
      snd_display("open_sound garbage %s: %s?", magic, res)
      if sound?(res)
        close_sound(res)
      end
    end
    delete_file("test.snd")
    mus_sound_forget("test.snd")
    File.open("test.snd", "w") do |fp|
      fp.write(magic)
      128.times do
        fp.write(mus_random(128))
      end
    end
    res = Snd.catch do open_sound("test.snd") end.first
    if number?(res) and sound?(res)
      snd_display("open_sound plausible garbage %s: %s?", magic, res)
      if sound?(res)
        close_sound(res)
      end
    end
    delete_file("test.snd")
    mus_sound_forget("test.snd")
    File.open("test.snd", "w") do |fp|
      fp.write(magic)
      (1...12).each do |i|
        if (ctr + i) < len
          fp.write(magic_words[ctr + i])
        else
          fp.write(magic_words[i])
        end
      end
    end
    res = Snd.catch do open_sound("test.snd") end.first
    if number?(res) and sound?(res)
      snd_display("open_sound very plausible garbage %s: %s?", magic, res)
      if sound?(res)
        close_sound(res)
      end
    end
  end
  delete_file("test.snd")
  mus_sound_forget("test.snd")
end

def make_aifc_file(frames, auth_lo, bits)
  File.open("test.aif", "w") do |fp|
    fp.write "FORM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0146); # len
    fp.write "AIFCFVER"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # version chunk size
    fp.putc(0242); fp.putc(0200); fp.putc(0121); fp.putc(0100); # version
    fp.write "COMM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0046); # COMM chunk size
    fp.putc(0000); fp.putc(0001);                 # 1 chan
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(frames); # frames
    fp.putc(0000); fp.putc(bits);                 # bits
    fp.putc(0100); fp.putc(0016); fp.putc(0254); fp.putc(0104); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    # srate as 80-bit float (sheesh)
    fp.write "NONE"                               # compression
    fp.putc(0016);                                # pascal string len
    fp.write "not compressed"
    fp.putc(0000);
    fp.write "AUTH"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(auth_lo); # AUTH chunk size
    fp.write "bil"
    fp.putc(0000);
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014); # SSND chunk size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # SSND data loc
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # block size?
    fp.putc(0000); fp.putc(0101); fp.putc(0000); fp.putc(0100); # two samples
  end
end

undef read_ascii
def read_ascii(in_filename,
               out_filename = "test.snd",
               out_type = Mus_next,
               out_format = Mus_bshort,
               out_srate = 44100)
  in_buffer = IO.readlines(in_filename)         # array of strings
  out_snd = new_sound(out_filename, out_type, out_format, out_srate, 1,
                      format("created by %s: %s", get_func_name, in_filename))
  bufsize = 512
  data = make_vct(bufsize)
  loc = 0
  frame = 0
  short2float = 1.0 / 32768.0
  as_one_edit_rb do | |
    in_buffer.each do |line|
      line.split.each do |str_val|
        val = eval(str_val)
        data[loc] = val * short2float
        loc += 1
        if loc == bufsize
          vct2channel(data, frame, bufsize, out_snd, 0)
          frame += bufsize
          loc = 0
        end
      end
    end
    if loc > 0
      vct2channel(data, frame, loc, out_snd, 0)
    end
  end
  out_snd
end

def test_04_08
  File.open("test.snd", "w") do |fp|
    fp.write ".snd"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0034); # location
    fp.putc(0000); fp.putc(0001); fp.putc(0215); fp.putc(0030); # nominal size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0022); # format
    fp.putc(0000); fp.putc(0000); fp.putc(0126); fp.putc(0042); # srate
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0001); # chans
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # comment
    fp.putc(0000); fp.putc(0001); # samp 1
  end
  snd_test_neq(mus_sound_data_format("test.snd"), Mus_bshort, "next 18")
  delete_file("test.snd")
  mus_sound_forget("test.snd")
  File.open("test.snd", "w") do |fp|
    fp.write ".snd"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # location
    fp.putc(0000); fp.putc(0001); fp.putc(0215); fp.putc(0030); # nominal size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0022); # format
    fp.putc(0000); fp.putc(0000); fp.putc(0126); fp.putc(0042); # srate
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0001); # chans
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # comment
    fp.putc(0000); fp.putc(0001); # samp 1
  end
  res = Snd.catch do open_sound("test.snd") end.first
  if number?(res) and sound?(res)
    snd_display("open_sound next bad location %s: %s?", data_location(res), res)
    close_sound(res)
  end
  delete_file("test.snd")
  mus_sound_forget("test.snd")
  File.open("test.snd", "w") do |fp|
    fp.write ".snd"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0034); # location
    fp.putc(0000); fp.putc(0001); fp.putc(0215); fp.putc(0030); # nominal size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0122); # format
    fp.putc(0000); fp.putc(0000); fp.putc(0126); fp.putc(0042); # srate
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0001); # chans
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # comment
    fp.putc(0000); fp.putc(0001); # samp 1
  end
  res = Snd.catch do open_sound("test.snd") end.first
  if number?(res) and sound?(res)
    snd_display("open_sound next bad format %s: %s?", data_format(res), res)
    close_sound(res)
  end
  delete_file("test.snd")
  mus_sound_forget("test.snd")
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  #
  # ;;correct (make-aifc-file #o002 #o004 #o020)
  #
  make_aifc_file(0102, 004, 020)
  Snd.catch do
    ind = open_sound("test.aif")
    snd_test_neq(frames(ind), 2, "bad frames in header")
    close_sound(ind)
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  make_aifc_file(002, 150, 020)
  res = Snd.catch do open_sound("test.aif") end.first
  if number?(res) and sound?(res)
    snd_display("open_sound aifc no ssnd chunk %s: %s?", data_location(res), res)
    close_sound(res)
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  make_aifc_file(002, 000, 020)
  res = Snd.catch do open_sound("test.aif") end.first
  if number?(res) and sound?(res)
    snd_display("open_sound aifc 0-len auth chunk %s: %s?", data_location(res), res)
    close_sound(res)
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  make_aifc_file(002, 150, 120)
  res = Snd.catch do open_sound("test.aif") end.first
  if number?(res) and sound?(res)
    snd_display("open_sound aifc bits 80 %s: %s?", data_format(res), res)
    close_sound(res)
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  File.open("test.aif", "w") do |fp|
    fp.write "FORM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0176); # len
    fp.write "AIFCFVER"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # version chunk size
    fp.putc(0242); fp.putc(0200); fp.putc(0121); fp.putc(0100); # version
    fp.write "COMM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0046); # COMM chunk size
    fp.putc(0000); fp.putc(0001);                 # 1 chan
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0002); # frames
    fp.putc(0000); fp.putc(0020);                 # bits
    fp.putc(0100); fp.putc(0016); fp.putc(0254); fp.putc(0104); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    # srate as 80-bit float (sheesh)
    fp.write "NONE"                               # compression
    fp.putc(0016);                                # pascal string len
    fp.write "not compressed"
    fp.putc(0000);
    fp.write "AUTH"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # AUTH chunk size
    fp.write "bil"
    fp.putc(0000);
    fp.write "ANNO"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # AUTH chunk size
    fp.write "cat"
    fp.putc(0000);
    fp.write "NAME"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # AUTH chunk size
    fp.write "dog"
    fp.putc(0000);
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # AUTH chunk size

    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014); # SSND chunk size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # SSND data loc
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # block size?
    fp.putc(0000); fp.putc(0101); fp.putc(0000); fp.putc(0100); # two samples
  end
  Snd.catch do
    snd_test_neq(mus_sound_comment("test.aif").length, 15, "aifc 3 aux comments")
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  File.open("test.aif", "w") do |fp|
    fp.write "FORM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0142); # len
    fp.write "AIFC"
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014); # SSND chunk size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # SSND data location
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # block size?
    fp.putc(0000); fp.putc(0101); fp.putc(0000); fp.putc(0100); # two samples
    fp.write "COMM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0046); # COMM chunk size
    fp.putc(0000); fp.putc(0001);                 # 1 chan
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0002); # frames
    fp.putc(0000); fp.putc(0020);                 # bits
    fp.putc(0100); fp.putc(0016); fp.putc(0254); fp.putc(0104); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    # srate as 80-bit float (sheesh)
    fp.write "NONE"                               # compression
    fp.putc(0016);                                # pascal string len
    fp.write "not compressed"
    fp.putc(0000);
    fp.write "COMT"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    fp.write "bil"
    fp.putc(0000);
  end
  Snd.catch do
    snd_test_neq(mus_sound_comment("test.aif")[0..2], "bil", "aifc trailing comt comments")
  end
  snd_test_neq(mus_sound_frames("test.aif"), 2, "aifc trailing comt frames")
  Snd.catch do
    ind = open_sound("test.aif")
    if fneq(sample(0), 0.00198) or
        fneq(sample(1), 0.00195) or
        fneq(sample(2), 0.0) or
        fneq(sample(3), 0.0)
      snd_display("aifc trailing comt samps: %s %s %s %s",
                  sample(0), sample(1), sample(2), sample(3))
    end
    close_sound(ind)
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  File.open("test.aif", "w") do |fp|
    fp.write "FORM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0142); # len
    fp.write "AIFC"
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014); # SSND chunk size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # SSND data location
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # block size?
    fp.putc(0000); fp.putc(0101); fp.putc(0000); fp.putc(0100); # two samples
    fp.write "COMM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0046); # COMM chunk size
    fp.putc(0000); fp.putc(0001);                 # 1 chan
    fp.putc(0000); fp.putc(0000); fp.putc(0100); fp.putc(0102); # frames
    fp.putc(0000); fp.putc(0020);                 # bits
    fp.putc(0100); fp.putc(0016); fp.putc(0254); fp.putc(0104); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    # srate as 80-bit float (sheesh)
    fp.write "NONE"                               # compression
    fp.putc(0016);                                # pascal string len
    fp.write "not compressed"
    fp.putc(0000);
    fp.write "COMT"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    fp.putc(0000); fp.putc(0101); fp.putc(0000); fp.putc(0000);
    fp.write "bil"
    fp.putc(0000);
  end
  snd_test_neq(mus_sound_comment("test.aif")[0..2], "bil", "aifc trailing comt comment")
  snd_test_neq(mus_sound_frames("test.aif"), 2, "aifc trailing comt (bogus) frames")
  Snd.catch do
    ind = open_sound("test.aif")
    if fneq(sample(0), 0.00198) or
        fneq(sample(1), 0.00195) or
        fneq(sample(2), 0.0) or
        fneq(sample(3), 0.0)
      snd_display("aifc trailing comt samps (bogus frame setting): %s %s %s %s",
                  sample(0), sample(1), sample(2), sample(3))
    end
    close_sound(ind)
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  File.open("test.aif", "w") do |fp|
    fp.write "FORM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0142); # len
    fp.write "AIFC"
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014); # SSND chunk size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # SSND data location
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # block size?
    fp.putc(0000); fp.putc(0101); fp.putc(0000); fp.putc(0100); # two samples
    fp.write "COMM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0046); # COMM chunk size
    fp.putc(0000); fp.putc(0001);                 # 1 chan
    fp.putc(0000); fp.putc(0000); fp.putc(0100); fp.putc(0102); # frames
    fp.putc(0000); fp.putc(0020);                 # bits
    fp.putc(0100); fp.putc(0016); fp.putc(0254); fp.putc(0104); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    # srate as 80-bit float (sheesh)
    fp.write "NONE"                               # compression
    fp.putc(0016);                                # pascal string len
    fp.write "not compressed"
    fp.putc(0000);
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    fp.putc(0000); fp.putc(0101); fp.putc(0000); fp.putc(0100);
  end
  res = Snd.catch do open_sound("test.aif") end.first
  if number?(res) and sound?(res)
    snd_display("open_sound aifc 2 ssnd chunks %s: %s?", data_location(res), res)
    close_sound(res)
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  File.open("test.aif", "w") do |fp|
    fp.write "FORM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0040); # len
    fp.write "AIFC"
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014); # SSND chunk size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # SSND data location
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # block size?
    fp.putc(0000); fp.putc(0101); fp.putc(0000); fp.putc(0100); # two samples
  end
  res = Snd.catch do open_sound("test.aif") end.first
  if res != :mus_error
    snd_display("open_sound aifc no comm chunk: %s?", res)
    if number?(res) and sound?(res)
      close_sound(res)
    end
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  #
  File.open("test.aif", "w") do |fp|
    # write AIFC with trailing chunks to try to confuse file->sample
    fp.write "FORM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0176); # len
    fp.write "AIFCFVER"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # version chunk size
    fp.putc(0242); fp.putc(0200); fp.putc(0121); fp.putc(0100); # version
    fp.write "COMM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0046); # COMM chunk size
    fp.putc(0000); fp.putc(0001);                 # 1 chan
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0002); # frames
    fp.putc(0000); fp.putc(0020);                 # bits
    fp.putc(0100); fp.putc(0016); fp.putc(0254); fp.putc(0104); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    # srate as 80-bit float (sheesh)
    fp.write "NONE"                               # compression
    fp.putc(0016);                                # pascal string len
    fp.write "not compressed"
    fp.putc(0000);
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    fp.putc(0170); fp.putc(0101); fp.putc(0100); fp.putc(0100); # two samples
    fp.write "AUTH"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # AUTH chunk size
    fp.write "bil"
    fp.putc(0000);
    fp.write "ANNO"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004);
    fp.write "cat"
    fp.putc(0000);
    fp.write "NAME"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004);
    fp.write "dog"
    fp.putc(0000);
  end
  Snd.catch do
    gen = make_file2sample("test.aif")
    snd_test_neq(gen.call(0), 0.93948, "file2sample chunked 0")
    snd_test_neq(gen.call(1), 0.50195, "file2sample chunked 1")
    snd_test_neq(gen.call(2), 0.00000, "file2sample chunked eof")
    snd_test_neq(gen.call(3), 0.00000, "file2sample chunked eof+1")
    res = open_sound("test.aif")
    snd_test_neq(frames(res), 2, "chunked frames")
    snd_test_neq(sample(0), 0.93948, "file chunked 0")
    snd_test_neq(sample(1), 0.50195, "file chunked 1")
    snd_test_neq(sample(2), 0.00000, "file chunked eof")
    snd_test_neq(sample(3), 0.00000, "file chunked eof+1")
    close_sound(res)
  end
  Snd.catch do
    snd_test_neq(mus_sound_frames("test.aif"), 2, "chunked mus_sound_frames")
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  #
  File.open("test.aif", "w") do |fp|
    # write AIFC with trailing chunks to try to confuse file->sample
    fp.write "FORM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0176); # len
    fp.write "AIFCFVER"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # version chunk size
    fp.putc(0242); fp.putc(0200); fp.putc(0121); fp.putc(0100); # version
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014); # SSND chunk size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # SSND data location
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # block size?
    fp.putc(0170); fp.putc(0101); fp.putc(0100); fp.putc(0100); # two samples
    fp.write "COMM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0046); # COMM chunk size
    fp.putc(0000); fp.putc(0001);                 # 1 chan
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0002); # frames
    fp.putc(0000); fp.putc(0020);                 # bits
    fp.putc(0100); fp.putc(0016); fp.putc(0254); fp.putc(0104); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    # srate as 80-bit float (sheesh)
    fp.write "NONE"                               # compression
    fp.putc(0016);                                # pascal string len
    fp.write "not compressed"
    fp.putc(0000);
    fp.write "APPL"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(?h);
    fp.write "CLM ;Written Mon 02-Nov-98 01:44 CST by root at ockeghem (Linux/X86) using Allegro CL, clm of 20-Oct-98"
    fp.putc(0000);
  end
  Snd.catch do
    gen = make_file2sample("test.aif")
    snd_test_neq(gen.call(0), 0.93948, "file2sample chunked 0")
    snd_test_neq(gen.call(1), 0.50195, "file2sample chunked 1")
    snd_test_neq(gen.call(2), 0.00000, "file2sample chunked eof")
    snd_test_neq(gen.call(3), 0.00000, "file2sample chunked eof+1")
    res = open_sound("test.aif")
    snd_test_neq(frames(res), 2, "chunked frames")
    snd_test_neq(sample(0), 0.93948, "file chunked 0")
    snd_test_neq(sample(1), 0.50195, "file chunked 1")
    snd_test_neq(sample(2), 0.00000, "file chunked eof")
    snd_test_neq(sample(3), 0.00000, "file chunked eof+1")
    snd_test_neq(comment(),
                 ";Written Mon 02-Nov-98 01:44 CST by root at ockeghem (Linux/X86) using Allegro CL, clm of 20-Oct-98",
                 "chunked appl comment")
    close_sound(res)
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  #
  File.open("test.aif", "w") do |fp|
    # write AIFC with trailing chunks to try to confuse file->sample
    fp.write "FORM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0176); # len
    fp.write "AIFCFVER"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0004); # version chunk size
    fp.putc(0242); fp.putc(0200); fp.putc(0121); fp.putc(0100); # version
    fp.write "SSND"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0014); # SSND chunk size
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # SSND data location
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); # block size?
    fp.putc(0170); fp.putc(0101); fp.putc(0100); fp.putc(0100); # two samples (onr frame)
    fp.write "COMM"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0046); # COMM chunk size
    fp.putc(0000); fp.putc(0002);                 # 2 chan
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0001); # frame
    fp.putc(0000); fp.putc(0020);                 # bits
    fp.putc(0100); fp.putc(0016); fp.putc(0254); fp.putc(0104); fp.putc(0000);
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(0000);
    # srate as 80-bit float (sheesh)
    fp.write "NONE"                               # compression
    fp.putc(0016);                                # pascal string len
    fp.write "not compressed"
    fp.putc(0000);
    fp.write "APPL"
    fp.putc(0000); fp.putc(0000); fp.putc(0000); fp.putc(?h);
    fp.write "CLM ;Written Mon 02-Nov-98 01:44 CST by root at ockeghem (Linux/X86) using Allegro CL, clm of 20-Oct-98"
    fp.putc(0000);
  end
  Snd.catch do
    gen = make_file2sample("test.aif")
    snd_test_neq(gen.call(0, 0), 0.93948, "file2sample chunked 0 0")
    snd_test_neq(gen.call(0, 1), 0.50195, "file2sample chunked 0 1")
    snd_test_neq(gen.call(1, 0), 0.00000, "file2sample chunked eof (stereo)")
    snd_test_neq(gen.call(1, 1), 0.00000, "file2sample chunked eof+1 (stereo)")
    res = open_sound("test.aif")
    snd_test_neq(frames(res), 1, "chunked frames (1)")
    snd_test_neq(sample(0, res, 0), 0.93948, "file chunked 0 0")
    snd_test_neq(sample(0, res, 1), 0.50195, "file chunked 0 1")
    snd_test_neq(sample(1, res, 0), 0.00000, "file chunked eof (stereo)")
    snd_test_neq(sample(1, res, 1), 0.00000, "file chunked eof+1 (stereo)")
    snd_test_neq(comment(),
                 ";Written Mon 02-Nov-98 01:44 CST by root at ockeghem (Linux/X86) using Allegro CL, clm of 20-Oct-98",
                 "chunked appl comment (stereo)")
    close_sound(res)
  end
  delete_file("test.aif")
  mus_sound_forget("test.aif")
  #
  files = sound_files_in_directory(Dir.pwd)
  if files.empty?
    snd_display("no sound files in %s?", Dir.pwd)
  end
  files1 = sound_files_in_directory()
  snd_test_neq(files1, files, "different sound files in %s and default?", Dir.pwd)
  files2 = sound_files_in_directory(".")
  if files1 != files2 or files != files2
    snd_display("sound_files_in_directory dot: %s but %s?:", files2, files)
  end
  $open_raw_sound_hook.reset_hook!
  $bad_header_hook.reset_hook!
  Snd.sounds.apply(:close_sound)
  #
  ind = new_sound(:size, 0)
  snd_test_neq(frames(ind), 0, "new_sound :size 0")
  snd_test_neq(sample(0), 0.0, "new_sound :size 0 sample 0")
  new_file_name = file_name(ind)
  close_sound(ind)
  delete_file(new_file_name)
  ind = new_sound(:size, 1)
  snd_test_neq(frames(ind), 1, "new_sound :size 1")
  snd_test_neq(sample(0), 0.0, "new_sound :size 1 sample 0")
  new_file_name = file_name(ind)
  close_sound(ind)
  delete_file(new_file_name)
  if (res = Snd.catch do new_sound(:size, -1) end).first != :out_of_range
    snd_display("new_sound :size -1: %s", res.inspect)
  end
  #
  with_file("caruso.asc") do |file|
    ind = read_ascii(file)
    unless sound?(ind)
      snd_display("read_ascii cannot find %s (%s)?", file, ind.inspect)
    end
    snd_test_neq(maxamp(ind, 0), 0.723, "read_ascii maxamp")
    snd_test_neq(frames(ind, 0), 50000, "read_ascii frames")
    snd_test_neq(srate(ind), 44100, "read_ascii srate")
    set_srate(ind, 8000)
    snd_test_neq(frames(ind, 0), 50000, "set srate clobbered new sound frames")
    snd_test_neq(maxamp(ind, 0), 0.723, "set srate clobbered new sound maxamp")
    close_sound(ind)
  end
  #
  ind = open_sound("oboe.snd")
  save_sound_as("test space.snd")
  close_sound(ind)
  ind = open_sound("test space.snd")
  snd_test_neq(short_file_name(ind), "test space.snd", "file name with space")
  snd_test_neq(mus_sound_frames("test space.snd"), frames(ind), "spaced filename frames")
  add_mark(1234, ind, 0)
  save_marks(ind)
  close_sound(ind)
  ind = open_sound("test space.snd")
  load("test space.marks")
  unless find_mark(1234, ind)
    snd_display("space file name save marks: %s", marks(ind).inspect)
  end
  rd = make_readin(:file, "test space.snd")
  snd_test_neq(mus_file_name(rd), "test space.snd", "file name with space readin")
  close_sound(ind)
  delete_files("test space.snd", "test space.marks")
  #
  # FIXME (INFO)
  # S7 specific tests skipped
  #
end

def test_04
  $tests.times do |i|
    $clmtest = i
    if $VERBOSE and $tests > 1
      snd_info("clmtest %d of %d", $clmtest + 1, $tests)
    end
    clear_listener()
    test_04_00
    test_04_01
    test_04_02
    test_04_03
    test_04_04
    test_04_05 if $with_big_file
    test_04_06
  end
  test_04_07
  test_04_08
end

# ---------------- test 05: simple overall checks ----------------

def test_edpos(ind1, func_sym, func_body = nil, &change_thunk)
  if proc?(func_body)
    fr1 = func_body.call(ind1, 0, false)
    fr2 = func_body.call(ind1, 0, 0)
    fr3 = func_body.call(ind1, 0, Current_edit_position)
    fr4 = func_body.call(ind1, 0, lambda do |snd, chn| 0 end)
    unless fr1 == fr2 and fr1 == fr3 and fr1 == fr4
      snd_display_prev_caller("initial %s: %s %s %s %s?", func_sym, fr1, fr2, fr3, fr4)
    end
    change_thunk.call
    fr5 = func_body.call(ind1, 0, false)
    fr6 = func_body.call(ind1, 0, 1)
    fr7 = func_body.call(ind1, 0, Current_edit_position)
    fr8 = func_body.call(ind1, 0, lambda do |snd, chn| edit_position(snd, chn) end)
    unless fr5 == fr6 and fr5 == fr7 and fr5 == fr8
      snd_display_prev_caller("%s (edpos 1): %s %s %s %s?", func_sym, fr5, fr6, fr7, fr8)
    end
    fr5 = func_body.call(ind1, 0, 0)
    fr6 = func_body.call(ind1, 0, lambda do |snd, chn| 0 end)
    unless fr1 == fr5 and fr1 == fr6
      snd_display_prev_caller("%s (edpos -1): %s %s %s?", func_sym, fr1, fr5, fr6)
    end
  else
    fr1 = snd_func(func_sym, ind1, 0, false)
    fr2 = snd_func(func_sym, ind1, 0, 0)
    fr3 = snd_func(func_sym, ind1, 0, Current_edit_position)
    fr4 = snd_func(func_sym, ind1, 0, lambda do |snd, chn| 0 end)
    unless fr1 == fr2 and fr1 == fr3 and fr1 == fr4
      snd_display_prev_caller("initial %s: %s %s %s %s?", func_sym, fr1, fr2, fr3, fr4)
    end
    change_thunk.call
    fr5 = snd_func(func_sym, ind1, 0, false)
    fr6 = snd_func(func_sym, ind1, 0, 1)
    fr7 = snd_func(func_sym, ind1, 0, Current_edit_position)
    fr8 = snd_func(func_sym, ind1, 0, lambda do |snd, chn| edit_position(snd, chn) end)
    unless fr5 == fr6 and fr5 == fr7 and fr5 == fr8
      snd_display_prev_caller("%s (edpos 1): %s %s %s %s?", func_sym, fr5, fr6, fr7, fr8)
    end
    fr5 = snd_func(func_sym, ind1, 0, 0)
    fr6 = snd_func(func_sym, ind1, 0, lambda do |snd, chn| 0 end)
    unless fr1 == fr5 and fr1 == fr6
      snd_display_prev_caller("%s (edpos -1): %s %s %s?", func_sym, fr1, fr5, fr6)
    end
  end
  revert_sound(ind1)
end

def test_edpos_1(func_sym, ind1, &body)
  v0 = channel2vct(12000, 10, ind1, 0)
  body.call(ind1, 0)
  v1 = channel2vct(12000, 10, ind1, 0)
  if vequal(v0, v1)
    snd_display_prev_caller(snd_format(v1, v0, "==", "%s (0) no change!", func_sym))
  end
  body.call(ind1, 0)
  v2 = channel2vct(12000, 10, ind1, 0)
  unless vequal(v1, v2)
    snd_display_prev_caller(snd_format(v2, v1, "!=", "%s (1)", func_sym))
  end
  body.call(ind1, lambda do |snd, chn| 0 end)
  v2 = channel2vct(12000, 10, ind1, 0)
  unless vequal(v1, v2)
    snd_display_prev_caller(snd_format(v2, v1, "!=", "%s (2)", func_sym))
  end
  revert_sound(ind1)
end

def test_orig(func0, func1, name, ind1)
  v0 = channel2vct(12000, 10, ind1, 0)
  func0.call(ind1)
  v1 = channel2vct(12000, 10, ind1, 0)
  if vfequal(v0, v1)            # okay
    snd_display_prev_caller(snd_format(v1, v0, "==", "%s (orig 0) no change!", name))
  end
  func1.call(ind1)
  v2 = channel2vct(12000, 10, ind1, 0)
  unless vfequal(v0, v2)        # okay
    snd_display_prev_caller(snd_format(v2, v0, "!=", "%s (orig 1)", name))
  end
  revert_sound(ind1)
end

def make_bandpass_2(flo1, fhi1, flo2, fhi2, len = 30)
  f1 = make_bandpass(flo1, fhi1, len)
  f2 = make_bandpass(flo2, fhi2, len)
  vct_add!(mus_xcoeffs(f1), mus_xcoeffs(f2))
  f1
end

def bandpass_2(f, input)
  fir_filter(f, input)
end

def check_maxamp(ind, val, name)
  if fneq(maxamp(ind, 0), val)
    snd_display_prev_caller("maxamp amp_env %s: %s should be %s", name, maxamp(ind), val)
  end
  unless pos = find_channel(lambda do |y| y.abs >= (val - 0.0001) end)
    snd_display_prev_caller("actual maxamp %s vals not right", name)
  end
  maxpos = maxamp_position(ind, 0)
  if maxpos != pos
    snd_display_prev_caller("%s: find and maxamp_position disagree: %s (%s) %s (%s)?",
                            name, pos, sample(pos, ind, 0), maxpos, sample(maxpos, ind, 0))
  end
  mx = 0.0
  ctr = 0
  mpos = 0
  scan_chan(lambda do |y|
              if fneq(y.abs, mx) and y.abs > mx
                mpos = ctr
                mx = y.abs
              end
              ctr += 1
              false
            end)
  if mpos != maxpos
    snd_display_prev_caller("%s: scan_chan and maxamp_position disagree: %s %s?",
                            name, mpos, maxpos)
  end
  if fneq(mx, val)
    snd_display_prev_caller("actual %s max: %s (correct: %s)", name, mx, val)
  end
end

def check_env_vals(name, gen)
  ctr = -1
  scan_chan(lambda do |y|
              ctr += 1
              if fneq(val = env(gen), y)
                snd_display_prev_caller(snd_format_neq(val, y,
                                                       "%s %s at %d",
                                                       get_func_name, name, ctr))
                true
              else
                false
              end
            end)
end

def our_x2position(ind, x)
  ax = axis_info(ind, 0)
  [ax[10].to_f + ((x - ax[2]) * (ax[12].to_f - ax[10])) / (ax[4] - ax[2]), x2position(x, ind)]
end

def region2vct_1(reg, chn, len)
  region2vct(reg, 0, len, chn)
end

def region_to_vct(reg, chn, len)
  rs = make_region_sampler(reg, 0, chn)
  make_vct!(len) do next_sample(rs) end
end

# basic edit tree cases
def test_05_00
  if playing
    snd_display("dac is running?")
  end
  ind = open_sound("oboe.snd")
  set_transform_graph?(true, ind, 0)
  set_transform_graph_type(Graph_as_sonogram, ind, 0)
  set_y_axis_label("hiho", ind, 0)
  set_fft_log_frequency(true, ind, 0)
  update_transform_graph(ind, 0)
  close_sound(ind)
  #
  ind = new_sound("test.snd")
  if redo_edit.nonzero?
    snd_display("redo_edit with no ops: %s?", redo_edit)
  end
  if undo_edit.nonzero?
    snd_display("undo_edit with no ops: %s?", undo_edit)
  end
  str = format("
EDITS: 0

 (begin) [0:2]:
   (at 0, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 1, end_mark)
", Dir.pwd)
  if (res = safe_display_edits) != str
    snd_display("new 0: %s %s?", str, res)
  end
  insert_samples(10, 10, make_vct(10))
  str = format("
EDITS: 1

 (begin) [0:2]:
   (at 0, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 1, end_mark)

 (insert 1 20) ; insert_samples [1:4]:
   (at 0, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 1, cp->sounds[-1][0:8, 0.000])
   (at 10, cp->sounds[1][0:9, 1.000]) [buf: 10] 
   (at 20, end_mark)
", Dir.pwd, Dir.pwd)
  if (res = safe_display_edits) != str
    snd_display("new 1: %s %s?", str, res)
  end
  undo_edit
  insert_samples(0, 10, make_vct(10))
  str = format("
EDITS: 1

 (begin) [0:2]:
   (at 0, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 1, end_mark)

 (insert 0 10) ; insert_samples [1:3]:
   (at 0, cp->sounds[1][0:9, 1.000]) [buf: 10] 
   (at 10, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 11, end_mark)
", Dir.pwd, Dir.pwd)
  if (res = safe_display_edits) != str
    snd_display("new 2: %s %s?", str, res)
  end
  undo_edit(2)
  insert_samples(0, 10, make_vct(10))
  str = format("
EDITS: 1

 (begin) [0:2]:
   (at 0, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 1, end_mark)

 (insert 0 10) ; insert_samples [1:3]:
   (at 0, cp->sounds[1][0:9, 1.000]) [buf: 10] 
   (at 10, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 11, end_mark)
", Dir.pwd, Dir.pwd)
  if (res = safe_display_edits) != str
    snd_display("new 3: %s %s?", str, res)
  end
  undo_edit
  set_sample(0, 0.5)
  str = format("
EDITS: 1

 (begin) [0:2]:
   (at 0, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 1, end_mark)

 (set 0 1) ; set_sample(0, 0.5000 [1:2]:
   (at 0, cp->sounds[1][0:0, 1.000]) [buf: 1] 
   (at 1, end_mark)
", Dir.pwd)
  if (res = safe_display_edits) != str
    snd_display("new 4: %s %s?", str, res)
  end
  undo_edit
  set_samples(0, 10, make_vct(10))
  str = format("
EDITS: 1

 (begin) [0:2]:
   (at 0, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 1, end_mark)

 (set 0 10) ; set-samples [1:2]:
   (at 0, cp->sounds[1][0:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
", Dir.pwd)
  if (res = safe_display_edits) != str
    snd_display("new 5: %s %s?", str, res)
  end
  idx = 1
  test_idx = 5
  test_output = lambda do |str|
    idx += 1
    test_idx += 1
    if (res = safe_display_edits(ind, 0, idx)) != str
      snd_display("new %s: %s %s?", test_idx, str, res)
    end
  end
  delete_samples(3, 4)
  test_output.call("
 (delete 3 4) ; delete_samples(3, 4 [2:3]:
   (at 0, cp->sounds[1][0:2, 1.000]) [buf: 10] 
   (at 3, cp->sounds[1][7:9, 1.000]) [buf: 10] 
   (at 6, end_mark)
")
  set_samples(1, 4, make_vct(4))
  test_output.call("
 (set 1 4) ; set-samples [3:4]:
   (at 0, cp->sounds[1][0:0, 1.000]) [buf: 10] 
   (at 1, cp->sounds[2][0:3, 1.000]) [buf: 4] 
   (at 5, cp->sounds[1][9:9, 1.000]) [buf: 10] 
   (at 6, end_mark)
")
  undo_edit(2)
  insert_samples(2, 3, make_vct(3))
  insert_samples(2, 1, make_vct(1))
  insert_samples(4, 1, make_vct(1))
  insert_samples(15, 1, make_vct(1))
  str = format("
EDITS: 5

 (begin) [0:2]:
   (at 0, cp->sounds[0][0:0, 0.000]) [file: %s/test.snd[0]]
   (at 1, end_mark)

 (set 0 10) ; set-samples [1:2]:
   (at 0, cp->sounds[1][0:9, 1.000]) [buf: 10] 
   (at 10, end_mark)

 (insert 2 3) ; insert_samples [2:4]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[2][0:2, 1.000]) [buf: 3] 
   (at 5, cp->sounds[1][2:9, 1.000]) [buf: 10] 
   (at 13, end_mark)

 (insert 2 1) ; insert_samples [3:5]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[3][0:0, 1.000]) [buf: 1] 
   (at 3, cp->sounds[2][0:2, 1.000]) [buf: 3] 
   (at 6, cp->sounds[1][2:9, 1.000]) [buf: 10] 
   (at 14, end_mark)

 (insert 4 1) ; insert_samples [4:7]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[3][0:0, 1.000]) [buf: 1] 
   (at 3, cp->sounds[2][0:0, 1.000]) [buf: 3] 
   (at 4, cp->sounds[4][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[2][1:2, 1.000]) [buf: 3] 
   (at 7, cp->sounds[1][2:9, 1.000]) [buf: 10] 
   (at 15, end_mark)

 (insert 15 1) ; insert_samples [5:8]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[3][0:0, 1.000]) [buf: 1] 
   (at 3, cp->sounds[2][0:0, 1.000]) [buf: 3] 
   (at 4, cp->sounds[4][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[2][1:2, 1.000]) [buf: 3] 
   (at 7, cp->sounds[1][2:9, 1.000]) [buf: 10] 
   (at 15, cp->sounds[5][0:0, 1.000]) [buf: 1] 
   (at 16, end_mark)
", Dir.pwd)
  test_idx += 1
  idx += 2
  if (res = safe_display_edits) != str
    snd_display("new 8: %s?", res)
  end
  delete_samples(2, 1)
  test_output.call("
 (delete 2 1) ; delete_samples(2, 1 [6:7]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[2][0:0, 1.000]) [buf: 3] 
   (at 3, cp->sounds[4][0:0, 1.000]) [buf: 1] 
   (at 4, cp->sounds[2][1:2, 1.000]) [buf: 3] 
   (at 6, cp->sounds[1][2:9, 1.000]) [buf: 10] 
   (at 14, cp->sounds[5][0:0, 1.000]) [buf: 1] 
   (at 15, end_mark)
")
  delete_samples(0, 5)
  test_output.call("
 (delete 0 5) ; delete_samples(0, 5 [7:4]:
   (at 0, cp->sounds[2][2:2, 1.000]) [buf: 3] 
   (at 1, cp->sounds[1][2:9, 1.000]) [buf: 10] 
   (at 9, cp->sounds[5][0:0, 1.000]) [buf: 1] 
   (at 10, end_mark)
")
  delete_samples(6, 4)
  test_output.call("
 (delete 6 4) ; delete_samples(6, 4 [8:3]:
   (at 0, cp->sounds[2][2:2, 1.000]) [buf: 3] 
   (at 1, cp->sounds[1][2:6, 1.000]) [buf: 10] 
   (at 6, end_mark)
")
  delete_samples(0, 1)
  test_output.call("
 (delete 0 1) ; delete_samples(0, 1 [9:2]:
   (at 0, cp->sounds[1][2:6, 1.000]) [buf: 10] 
   (at 5, end_mark)
")
  delete_samples(0, 5)
  test_output.call("
 (delete 0 5) ; delete_samples(0, 5 [10:1]:
   (at 0, end_mark)
")
  delete_samples(0, 10)
  unless edit_position == 10
    snd_display("no-op delete deleted something! %s", safe_display_edits)
  end
  insert_samples(0, 3, make_vct(3))
  test_output.call("
 (insert 0 3) ; insert_samples [11:2]:
   (at 0, cp->sounds[6][0:2, 1.000]) [buf: 3] 
   (at 3, end_mark)
")
  delete_samples(2, 1)
  test_output.call("
 (delete 2 1) ; delete_samples(2, 1 [12:2]:
   (at 0, cp->sounds[6][0:1, 1.000]) [buf: 3] 
   (at 2, end_mark)
")
  set_sample(0, 0.5)
  test_output.call("
 (set 0 1) ; set_sample(0, 0.5000 [13:3]:
   (at 0, cp->sounds[7][0:0, 1.000]) [buf: 1] 
   (at 1, cp->sounds[6][1:1, 1.000]) [buf: 3] 
   (at 2, end_mark)
")
  set_sample(1, 0.5)
  test_output.call("
 (set 1 1) ; set_sample(1, 0.5000 [14:3]:
   (at 0, cp->sounds[7][0:0, 1.000]) [buf: 1] 
   (at 1, cp->sounds[8][0:0, 1.000]) [buf: 1] 
   (at 2, end_mark)
")
  map_channel($init_channel, 0, 10)
  test_output.call("
 (set 0 10) ; map-channel [15:2]:
   (at 0, cp->sounds[9][0:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  insert_samples(0, 10, make_vct(10))
  test_output.call("
 (insert 0 10) ; insert_samples [16:3]:
   (at 0, cp->sounds[10][0:9, 1.000]) [buf: 10] 
   (at 10, cp->sounds[9][0:9, 1.000]) [buf: 10] 
   (at 20, end_mark)
")
  set_samples(2, 3, make_vct(3))
  test_output.call("
 (set 2 3) ; set-samples [17:5]:
   (at 0, cp->sounds[10][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[11][0:2, 1.000]) [buf: 3] 
   (at 5, cp->sounds[10][5:9, 1.000]) [buf: 10] 
   (at 10, cp->sounds[9][0:9, 1.000]) [buf: 10] 
   (at 20, end_mark)
")
  set_samples(0, 12, make_vct(12))
  test_output.call("
 (set 0 12) ; set-samples [18:3]:
   (at 0, cp->sounds[12][0:11, 1.000]) [buf: 12] 
   (at 12, cp->sounds[9][2:9, 1.000]) [buf: 10] 
   (at 20, end_mark)
")
  set_samples(30, 10, make_vct(10))
  test_output.call("
 (set 20 21) ; set-samples [19:5]:
   (at 0, cp->sounds[12][0:11, 1.000]) [buf: 12] 
   (at 12, cp->sounds[9][2:9, 1.000]) [buf: 10] 
   (at 20, cp->sounds[-1][0:9, 0.000])
   (at 30, cp->sounds[13][0:9, 1.000]) [buf: 10] 
   (at 40, end_mark)
")
  close_sound(ind)
end

# scale/ramp
def test_05_01
  ind = new_sound("test.snd")
  idx = -1
  test_name = "scl"
  test_output = lambda do |ed, str|
    idx += 1
    if (res = safe_display_edits(ind, 0, ed)) != str
      snd_display("%s %s: %s %s?", test_name, idx, str, res)
    end
  end
  map_channel($init_channel, 0, 10)
  scale_channel(0.5)
  test_output.call(2, "
 (scale 0 10) ; scale_channel(0.500, 0, false [2:2]:
   (at 0, cp->sounds[1][0:9, 0.500]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 0, 3)
  test_output.call(2, "
 (scale 0 3) ; scale_channel(0.500, 0, 3 [2:3]:
   (at 0, cp->sounds[1][0:2, 0.500]) [buf: 10] 
   (at 3, cp->sounds[1][3:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 5, 5)
  test_output.call(2, "
 (scale 5 5) ; scale_channel(0.500, 5, 5 [2:3]:
   (at 0, cp->sounds[1][0:4, 1.000]) [buf: 10] 
   (at 5, cp->sounds[1][5:9, 0.500]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 2, 4)
  test_output.call(2, "
 (scale 2 4) ; scale_channel(0.500, 2, 4 [2:4]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:5, 0.500]) [buf: 10] 
   (at 6, cp->sounds[1][6:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 10, 10)
  unless edit_position == 1
    snd_display("scale beyond end edited? %s", safe_display_edits)
  end
  scale_channel(0.5, 100, 10)
  unless edit_position == 1
    snd_display("scale way beyond end edited? %s", safe_display_edits)
  end
  scale_channel(0.5, 5, 10)
  test_output.call(2, "
 (scale 5 5) ; scale_channel(0.500, 5, 5 [2:3]:
   (at 0, cp->sounds[1][0:4, 1.000]) [buf: 10] 
   (at 5, cp->sounds[1][5:9, 0.500]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  set_sample(4, 0.5)
  test_output.call(2, "
 (set 4 1) ; set_sample(4, 0.5000 [2:4]:
   (at 0, cp->sounds[1][0:3, 1.000]) [buf: 10] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  scale_channel(0.5, 0, 4)
  test_output.call(3, "
 (scale 0 4) ; scale_channel(0.500, 0, 4 [3:4]:
   (at 0, cp->sounds[1][0:3, 0.500]) [buf: 10] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  scale_channel(0.5, 4, 1)
  test_output.call(4, "
 (scale 4 1) ; scale_channel(0.500, 4, 1 [4:4]:
   (at 0, cp->sounds[1][0:3, 0.500]) [buf: 10] 
   (at 4, cp->sounds[2][0:0, 0.500]) [buf: 1] 
   (at 5, cp->sounds[1][5:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  scale_channel(0.5, 0, 7)
  test_output.call(5, "
 (scale 0 7) ; scale_channel(0.500, 0, 7 [5:5]:
   (at 0, cp->sounds[1][0:3, 0.250]) [buf: 10] 
   (at 4, cp->sounds[2][0:0, 0.250]) [buf: 1] 
   (at 5, cp->sounds[1][5:6, 0.500]) [buf: 10] 
   (at 7, cp->sounds[1][7:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  scale_channel(0.5, 1, 4)
  test_output.call(6, "
 (scale 1 4) ; scale_channel(0.500, 1, 4 [6:6]:
   (at 0, cp->sounds[1][0:0, 0.250]) [buf: 10] 
   (at 1, cp->sounds[1][1:3, 0.125]) [buf: 10] 
   (at 4, cp->sounds[2][0:0, 0.125]) [buf: 1] 
   (at 5, cp->sounds[1][5:6, 0.500]) [buf: 10] 
   (at 7, cp->sounds[1][7:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit(4)
  scale_channel(0.5, 1, 8)
  test_output.call(3, "
 (scale 1 8) ; scale_channel(0.500, 1, 8 [3:6]:
   (at 0, cp->sounds[1][0:0, 1.000]) [buf: 10] 
   (at 1, cp->sounds[1][1:3, 0.500]) [buf: 10] 
   (at 4, cp->sounds[2][0:0, 0.500]) [buf: 1] 
   (at 5, cp->sounds[1][5:8, 0.500]) [buf: 10] 
   (at 9, cp->sounds[1][9:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit(2)
  idx = -1
  test_name = "ramp"
  ramp_channel(0.0, 1.0)
  test_output.call(2, "
 (ramp 0 10) ; ramp_channel(0.000, 1.000, 0, false [2:2]:
   (at 0, cp->sounds[1][0:9, 1.000, [1]0.000 -> 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  scale_channel(0.5)
  test_output.call(3, "
 (scale 0 10) ; scale_channel(0.500, 0, false [3:2]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 0, 5)
  test_output.call(3, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [3:3]:
   (at 0, cp->sounds[1][0:4, 0.500, [1]0.000 -> 0.444]) [buf: 10] 
   (at 5, cp->sounds[1][5:9, 1.000, [1]0.556 -> 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 2, 4)
  test_output.call(3, "
 (scale 2 4) ; scale_channel(0.500, 2, 4 [3:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.111]) [buf: 10] 
   (at 2, cp->sounds[1][2:5, 0.500, [1]0.222 -> 0.556]) [buf: 10] 
   (at 6, cp->sounds[1][6:9, 1.000, [1]0.667 -> 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 5, 5)
  test_output.call(3, "
 (scale 5 5) ; scale_channel(0.500, 5, 5 [3:3]:
   (at 0, cp->sounds[1][0:4, 1.000, [1]0.000 -> 0.444]) [buf: 10] 
   (at 5, cp->sounds[1][5:9, 0.500, [1]0.556 -> 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit(2)
  ramp_channel(0.2, 0.6, 2, 6)
  test_output.call(2, "
 (ramp 2 6) ; ramp_channel(0.200, 0.600, 2, 6 [2:4]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:7, 1.000, [1]0.200 -> 0.600]) [buf: 10] 
   (at 8, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  scale_channel(0.5, 0, 5)
  test_output.call(3, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [3:5]:
   (at 0, cp->sounds[1][0:1, 0.500]) [buf: 10] 
   (at 2, cp->sounds[1][2:4, 0.500, [1]0.200 -> 0.360]) [buf: 10] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.440 -> 0.600]) [buf: 10] 
   (at 8, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 2, 6)
  test_output.call(3, "
 (scale 2 6) ; scale_channel(0.500, 2, 6 [3:4]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:7, 0.500, [1]0.200 -> 0.600]) [buf: 10] 
   (at 8, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 5, 4)
  test_output.call(3, "
 (scale 5 4) ; scale_channel(0.500, 5, 4 [3:6]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:4, 1.000, [1]0.200 -> 0.360]) [buf: 10] 
   (at 5, cp->sounds[1][5:7, 0.500, [1]0.440 -> 0.600]) [buf: 10] 
   (at 8, cp->sounds[1][8:8, 0.500]) [buf: 10] 
   (at 9, cp->sounds[1][9:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  set_sample(4, 0.5)
  test_output.call(3, "
 (set 4 1) ; set_sample(4, 0.5000 [3:6]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280]) [buf: 10] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.440 -> 0.600]) [buf: 10] 
   (at 8, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5, 4, 1)
  test_output.call(3, "
 (scale 4 1) ; scale_channel(0.500, 4, 1 [3:6]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280]) [buf: 10] 
   (at 4, cp->sounds[1][4:4, 0.500, [1]0.360 -> 0.360]) [buf: 10] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.440 -> 0.600]) [buf: 10] 
   (at 8, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  delete_sample(4)
  test_output.call(3, "
 (delete 4 1) ; delete_samples(4, 1 [3:5]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280]) [buf: 10] 
   (at 4, cp->sounds[1][5:7, 1.000, [1]0.440 -> 0.600]) [buf: 10] 
   (at 7, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 9, end_mark)
")
  undo_edit
  delete_samples(4, 2)
  test_output.call(3, "
 (delete 4 2) ; delete_samples(4, 2 [3:5]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280]) [buf: 10] 
   (at 4, cp->sounds[1][6:7, 1.000, [1]0.520 -> 0.600]) [buf: 10] 
   (at 6, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 8, end_mark)
")
  undo_edit
  delete_samples(4, 3)
  test_output.call(3, "
 (delete 4 3) ; delete_samples(4, 3 [3:5]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280]) [buf: 10] 
   (at 4, cp->sounds[1][7:7, 1.000, [1]0.600 -> 0.600]) [buf: 10] 
   (at 5, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 7, end_mark)
")
  undo_edit
  delete_samples(4, 4)
  test_output.call(3, "
 (delete 4 4) ; delete_samples(4, 4 [3:4]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280]) [buf: 10] 
   (at 4, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 6, end_mark)
")
  undo_edit
  delete_samples(4, 5)
  test_output.call(3, "
 (delete 4 5) ; delete_samples(4, 5 [3:4]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280]) [buf: 10] 
   (at 4, cp->sounds[1][9:9, 1.000]) [buf: 10] 
   (at 5, end_mark)
")
  undo_edit
  scale_channel(0.5, 4, 2)
  test_output.call(3, "
 (scale 4 2) ; scale_channel(0.500, 4, 2 [3:6]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280]) [buf: 10] 
   (at 4, cp->sounds[1][4:5, 0.500, [1]0.360 -> 0.440]) [buf: 10] 
   (at 6, cp->sounds[1][6:7, 1.000, [1]0.520 -> 0.600]) [buf: 10] 
   (at 8, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  pad_channel(4, 1)
  test_output.call(3, "
 (silence 4 1) ; pad-channel [3:6]:
   (at 0, cp->sounds[1][0:1, 1.000]) [buf: 10] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280]) [buf: 10] 
   (at 4, cp->sounds[-1][0:0, 0.000])
   (at 5, cp->sounds[1][4:7, 1.000, [1]0.360 -> 0.600]) [buf: 10] 
   (at 9, cp->sounds[1][8:9, 1.000]) [buf: 10] 
   (at 11, end_mark)
")
  close_sound(ind)
end

# xramp
def test_05_02
  ind = new_sound("test.snd")
  idx = -1
  test_name = "xramp"
  test_output = lambda do |ed, str|
    idx += 1
    if (res = safe_display_edits(ind, 0, ed)) != str
      snd_display("%s %s: %s %s?", test_name, idx, str, res)
    end
  end
  map_channel($init_channel, 0, 10)
  xramp_channel(0.0, 1.0, 32.0)
  test_output.call(2, "
 (ramp 0 10) ; xramp_channel(0.000, 1.000, 32.000, 0, false [2:2]:
   (at 0, cp->sounds[1][0:9, 1.000, [1]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  xramp_channel(0.0, 1.0, 0.325)
  test_output.call(2, "
 (ramp 0 10) ; xramp_channel(0.000, 1.000, 0.325, 0, false [2:2]:
   (at 0, cp->sounds[1][0:9, 1.000, [1]0.000 -> 1.000, off: 1.481, scl: -1.481]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  xramp_channel(0.0, 1.0, 0.0)
  test_output.call(2, format("
 (scale 0 10) ; scale_channel(0.000, 0, false [2:2]:
   (at 0, cp->sounds[0][0:9, 0.000]) [file: %s/test.snd[0]]
   (at 10, end_mark)
", Dir.pwd))
  undo_edit
  xramp_channel(0.0, 1.0, 1.0)
  test_output.call(2, "
 (ramp 0 10) ; ramp_channel(0.000, 1.000, 0, false [2:2]:
   (at 0, cp->sounds[1][0:9, 1.000, [1]0.000 -> 1.000]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  xramp_channel(0.5, 1.5, 32.0)
  test_output.call(2, "
 (ramp 0 10) ; xramp_channel(0.500, 1.500, 32.000, 0, false [2:2]:
   (at 0, cp->sounds[1][0:9, 1.000, [1]0.500 -> 1.500, off: 0.468, scl: 0.032]) [buf: 10] 
   (at 10, end_mark)
")
  if fneq(maxamp, 1.5) or fneq(sample(0), 0.5)
    snd_display("xramp 5 vals: %s %s", maxamp, sample(0))
  end
  undo_edit
  xramp_channel(-0.5, 1.5, 32.0)
  test_output.call(2, "
 (ramp 0 10) ; xramp_channel(-0.500, 1.500, 32.000, 0, false [2:2]:
   (at 0, cp->sounds[1][0:9, 1.000, [1]-0.500 -> 1.500, off: -0.565, scl: 0.065]) [buf: 10] 
   (at 10, end_mark)
")
  if fneq(maxamp, 1.5) or fneq(sample(0), -0.5)
    snd_display("xramp 6 vals: %s %s", maxamp, sample(0))
  end
  undo_edit
  xramp_channel(0.0, 1.0, 32.0)
  vals = channel2vct
  scale_channel(0.5)
  test_output.call(3, "
 (scale 0 10) ; scale_channel(0.500, 0, false [3:2]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 10, end_mark)
")
  ctr = 0
  if res = scan_chan(lambda do |y|
                       if fneq(y, 0.5 * vals[ctr])
                         true
                       else
                         ctr += 1
                         false
                       end
               end)
    snd_display("trouble in xramp 7: %s", res)
  end
  undo_edit
  delete_sample(0)
  test_output.call(3, "
 (delete 0 1) ; delete_samples(0, 1 [3:2]:
   (at 0, cp->sounds[1][1:9, 1.000, [1]0.015 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 9, end_mark)
")
  ctr = 1
  if res = scan_chan(lambda do |y|
                       if fneq(y, vals[ctr])
                         true
                       else
                         ctr += 1
                         false
                       end
               end)
    snd_display("trouble in xramp 8: %s", res)
  end
  undo_edit
  delete_samples(0, 2)
  test_output.call(3, "
 (delete 0 2) ; delete_samples(0, 2 [3:2]:
   (at 0, cp->sounds[1][2:9, 1.000, [1]0.037 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 8, end_mark)
")
  ctr = 2
  if res = scan_chan(lambda do |y|
                       if fneq(y, vals[ctr])
                         true
                       else
                         ctr += 1
                         false
                       end
               end)
    snd_display("trouble in xramp 9: %s", res)
  end
  undo_edit
  delete_sample(0)
  delete_sample(0)
  test_output.call(4, "
 (delete 0 1) ; delete_samples(0, 1 [4:2]:
   (at 0, cp->sounds[1][2:9, 1.000, [1]0.037 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 8, end_mark)
")
  undo_edit(2)
  delete_sample(4)
  test_output.call(3, "
 (delete 4 1) ; delete_samples(4, 1 [3:3]:
   (at 0, cp->sounds[1][0:3, 1.000, [1]0.000 -> 0.070, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 4, cp->sounds[1][5:9, 1.000, [1]0.189 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 9, end_mark)
")
  undo_edit
  delete_samples(4, 2)
  test_output.call(3, "
 (delete 4 2) ; delete_samples(4, 2 [3:3]:
   (at 0, cp->sounds[1][0:3, 1.000, [1]0.000 -> 0.070, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 4, cp->sounds[1][6:9, 1.000, [1]0.293 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 8, end_mark)
")
  undo_edit
  scale_channel(0.5, 4, 2)
  test_output.call(3, "
 (scale 4 2) ; scale_channel(0.500, 4, 2 [3:4]:
   (at 0, cp->sounds[1][0:3, 1.000, [1]0.000 -> 0.070, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 4, cp->sounds[1][4:5, 0.500, [1]0.118 -> 0.189, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 6, cp->sounds[1][6:9, 1.000, [1]0.293 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 10, end_mark)
")
  ctr = 0
  if res = scan_chan(lambda do |y|
                       if (ctr > 5 and fneq(y, vals[ctr])) or
                           (ctr < 4 and fneq(y, vals[ctr])) or
                           ((ctr == 4 or ctr == 5) and fneq(y, 0.5 * vals[ctr]))
                         true
                       else
                         ctr += 1
                         false
                       end
               end)
    snd_display("trouble in xramp 13: %s", res)
  end
  undo_edit
  scale_channel(0.5, 0, 2)
  test_output.call(3, "
 (scale 0 2) ; scale_channel(0.500, 0, 2 [3:3]:
   (at 0, cp->sounds[1][0:1, 0.500, [1]0.000 -> 0.015, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 2, cp->sounds[1][2:9, 1.000, [1]0.037 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  pad_channel(4, 2)
  test_output.call(3, "
 (silence 4 2) ; pad-channel [3:4]:
   (at 0, cp->sounds[1][0:3, 1.000, [1]0.000 -> 0.070, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 4, cp->sounds[-1][0:1, 0.000])
   (at 6, cp->sounds[1][4:9, 1.000, [1]0.118 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 12, end_mark)
")
  undo_edit
  set_sample(4, 1.0)
  test_output.call(3, "
 (set 4 1) ; set_sample(4, 1.0000 [3:4]:
   (at 0, cp->sounds[1][0:3, 1.000, [1]0.000 -> 0.070, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:9, 1.000, [1]0.189 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  set_samples(4, 2, make_vct(2))
  test_output.call(3, "
 (set 4 2) ; set-samples [3:4]:
   (at 0, cp->sounds[1][0:3, 1.000, [1]0.000 -> 0.070, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 4, cp->sounds[2][0:1, 1.000]) [buf: 2] 
   (at 6, cp->sounds[1][6:9, 1.000, [1]0.293 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 10, end_mark)
")
  undo_edit
  scale_channel(0.5)
  set_samples(4, 2, make_vct(2))
  test_output.call(4, "
 (set 4 2) ; set-samples [4:4]:
   (at 0, cp->sounds[1][0:3, 0.500, [1]0.000 -> 0.070, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 4, cp->sounds[2][0:1, 1.000]) [buf: 2] 
   (at 6, cp->sounds[1][6:9, 0.500, [1]0.293 -> 1.000, off: -0.032, scl: 0.032]) [buf: 10] 
   (at 10, end_mark)
")
  close_sound(ind)
  ind = new_sound("test.snd")
  test_name = "multi-ramp"
  idx = 0
  map_channel($init_channel, 0, 100)
  10.times do |i| scale_channel(0.5, i * 10, 10) end
  ramp_channel(0.0, 1.0)
  test_output.call(12, "
 (ramp 0 100) ; ramp_channel(0.000, 1.000, 0, false [12:11]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 0.091]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.101 -> 0.192]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.202 -> 0.293]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.303 -> 0.394]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.404 -> 0.495]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.505 -> 0.596]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.606 -> 0.697]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.707 -> 0.798]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.808 -> 0.899]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500, [1]0.909 -> 1.000]) [buf: 100] 
   (at 100, end_mark)
")
  if fneq(maxamp, 0.5)
    snd_display("multi-ramp 1 maxamp: %s", maxamp)
  end
  undo_edit
  ramp_channel(0.1, 1.0, 10, 90)
  test_output.call(12, "
 (ramp 10 90) ; ramp_channel(0.100, 1.000, 10, 90 [12:11]:
   (at 0, cp->sounds[1][0:9, 0.500]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.100 -> 0.191]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.201 -> 0.292]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.302 -> 0.393]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.403 -> 0.494]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.504 -> 0.596]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.606 -> 0.697]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.707 -> 0.798]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.808 -> 0.899]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500, [1]0.909 -> 1.000]) [buf: 100] 
   (at 100, end_mark)
")
  if fneq(maxamp, 0.5)
    snd_display("multi-ramp 2 maxamp: %s", maxamp)
  end
  undo_edit
  ramp_channel(0.0, 0.9, 0, 90)
  test_output.call(12, "
 (ramp 0 90) ; ramp_channel(0.000, 0.900, 0, 90 [12:11]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 0.091]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.101 -> 0.192]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.202 -> 0.293]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.303 -> 0.394]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.404 -> 0.496]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.506 -> 0.597]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.607 -> 0.698]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.708 -> 0.799]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.809 -> 0.900]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500]) [buf: 100] 
   (at 100, end_mark)
")
  if fneq(maxamp, 0.5)
    snd_display("multi-ramp 3 maxamp: %s", maxamp)
  end
  if fneq(sample(89), 0.45)
    snd_display("multi-ramp 3 sample 89: %s", sample(89))
  end
  if fneq(sample(90), 0.5)
    snd_display("multi-ramp 3 sample 90: %s", sample(90))
  end
  undo_edit
  ramp_channel(0.1, 0.9, 10, 80)
  test_output.call(12, "
 (ramp 10 80) ; ramp_channel(0.100, 0.900, 10, 80 [12:11]:
   (at 0, cp->sounds[1][0:9, 0.500]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.100 -> 0.191]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.201 -> 0.292]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.303 -> 0.394]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.404 -> 0.495]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.505 -> 0.596]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.606 -> 0.697]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.708 -> 0.799]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.809 -> 0.900]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500]) [buf: 100] 
   (at 100, end_mark)
")
  revert_sound
  map_channel($init_channel, 0, 100)
  ramp_channel(0.0, 1.0)
  10.times do |i| scale_channel(0.5, i * 10, 10) end
  test_output.call(12, "
 (scale 90 10) ; scale_channel(0.500, 90, 10 [12:11]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 0.091]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.101 -> 0.192]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.202 -> 0.293]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.303 -> 0.394]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.404 -> 0.495]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.505 -> 0.596]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.606 -> 0.697]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.707 -> 0.798]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.808 -> 0.899]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500, [1]0.909 -> 1.000]) [buf: 100] 
   (at 100, end_mark)
")
  close_sound(ind)
end

# ramp2
def test_05_03
  ind = new_sound("test.snd")
  idx = -1
  test_name = "ramp2"
  test_output = lambda do |ed, str|
    idx += 1
    if (res = safe_display_edits(ind, 0, ed)) != str
      snd_display("%s %s: %s %s?", test_name, idx, str, res)
    end
  end
  map_chan($init_channel, 0, 10)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  test_output.call(3, "
 (ramp 0 11) ; ramp_channel(0.000, 1.000, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(res = channel2vct,
                vct(0.000, 0.010, 0.040, 0.090, 0.160, 0.250, 0.360, 0.490, 0.640, 0.810, 1.000))
    snd_display("ramp2 (0): %s", res)
  end
  scale_channel(0.5)
  test_output.call(4, "
 (scale 0 11) ; scale_channel(0.500, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 0.500, [1]0.000 -> 1.000, [2]0.000 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  scale_channel(0.5, 0, 5)
  test_output.call(4, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [4:3]:
   (at 0, cp->sounds[1][0:4, 0.500, [1]0.000 -> 0.400, [2]0.000 -> 0.400]) [buf: 11] 
   (at 5, cp->sounds[1][5:10, 1.000, [1]0.500 -> 1.000, [2]0.500 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(res = channel2vct,
                vct(0.000, 0.005, 0.020, 0.045, 0.080, 0.250, 0.360, 0.490, 0.640, 0.810, 1.000))
    snd_display("ramp2 (2): %s", res)
  end
  undo_edit
  scale_channel(0.5, 2, 4)
  test_output.call(4, "
 (scale 2 4) ; scale_channel(0.500, 2, 4 [4:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100, [2]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:5, 0.500, [1]0.200 -> 0.500, [2]0.200 -> 0.500]) [buf: 11] 
   (at 6, cp->sounds[1][6:10, 1.000, [1]0.600 -> 1.000, [2]0.600 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit(2)
  ramp_channel(0.75, 0.25)
  test_output.call(3, "
 (ramp 0 11) ; ramp_channel(0.750, 0.250, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.750 -> 0.250]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  ramp_channel(0.2, 0.6, 2, 6)
  test_output.call(3, "
 (ramp 2 6) ; ramp_channel(0.200, 0.600, 2, 6 [3:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:7, 1.000, [1]0.200 -> 0.700, [2]0.200 -> 0.600]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  scale_channel(0.5, 0, 5)
  test_output.call(4, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [4:5]:
   (at 0, cp->sounds[1][0:1, 0.500, [1]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:4, 0.500, [1]0.200 -> 0.400, [2]0.200 -> 0.360]) [buf: 11] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.500 -> 0.700, [2]0.440 -> 0.600]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  set_sample(4, 0.5)
  test_output.call(4, "
 (set 4 1) ; set_sample(4, 0.5000 [4:6]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.300, [2]0.200 -> 0.280]) [buf: 11] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.500 -> 0.700, [2]0.440 -> 0.600]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit(3)
  close_sound(ind)
  ind = new_sound("test.snd")
  map_channel($init_channel, 0, 100)
  test_name = "multi-ramp2"
  10.times do |i| scale_channel(0.5, i * 10, 10) end
  ramp_channel(0.0, 1.0)
  ramp_channel(1.0, 0.0)
  test_output.call(13, "
 (ramp 0 100) ; ramp_channel(1.000, 0.000, 0, false [13:11]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 0.091, [2]1.000 -> 0.909]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.101 -> 0.192, [2]0.899 -> 0.808]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.202 -> 0.293, [2]0.798 -> 0.707]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.303 -> 0.394, [2]0.697 -> 0.606]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.404 -> 0.495, [2]0.596 -> 0.505]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.505 -> 0.596, [2]0.495 -> 0.404]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.606 -> 0.697, [2]0.394 -> 0.303]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.707 -> 0.798, [2]0.293 -> 0.202]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.808 -> 0.899, [2]0.192 -> 0.101]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500, [1]0.909 -> 1.000, [2]0.091 -> 0.000]) [buf: 100] 
   (at 100, end_mark)
")
  undo_edit(12)
  ramp_channel(0.0, 1.0, 10, 20)
  ramp_channel(0.0, 1.0, 50, 10)
  ramp_channel(0.0, 1.0, 25, 10)
  test_output.call(4, "
 (ramp 25 10) ; ramp_channel(0.000, 1.000, 25, 10 [4:8]:
   (at 0, cp->sounds[1][0:9, 1.000]) [buf: 100] 
   (at 10, cp->sounds[1][10:24, 1.000, [1]0.000 -> 0.737]) [buf: 100] 
   (at 25, cp->sounds[1][25:29, 1.000, [1]0.789 -> 1.000, [2]0.000 -> 0.444]) [buf: 100] 
   (at 30, cp->sounds[1][30:34, 1.000, [1]0.556 -> 1.000]) [buf: 100] 
   (at 35, cp->sounds[1][35:49, 1.000]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 1.000, [1]0.000 -> 1.000]) [buf: 100] 
   (at 60, cp->sounds[1][60:99, 1.000]) [buf: 100] 
   (at 100, end_mark)
")
  close_sound(ind)
end

# ramp-xramp, xramp-ramp
def test_05_04
  ind = new_sound("test.snd")
  idx = -1
  test_name = "ramp-xramp"
  test_output = lambda do |ed, str|
    idx += 1
    if (res = safe_display_edits(ind, 0, ed)) != str
      snd_display("%s %s: %s %s?", test_name, idx, str, res)
    end
  end
  map_chan($init_channel, 0, 10)
  ramp_channel(0.0, 1.0)
  xramp_channel(0.0, 1.0, 32.0)
  test_output.call(3, "
 (ramp 0 11) ; xramp_channel(0.000, 1.000, 32.000, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(case1 = channel2vct,
                vct(0.000, 0.001, 0.006, 0.018, 0.039, 0.075, 0.135, 0.233, 0.387, 0.628, 1.000))
    snd_display("ramp-xramp (1): %s", case1)
  end
  scale_channel(0.5)
  test_output.call(4, "
 (scale 0 11) ; scale_channel(0.500, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 0.500, [1]0.000 -> 1.000, [2]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  scale_channel(0.5, 0, 5)
  test_output.call(4, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [4:3]:
   (at 0, cp->sounds[1][0:4, 0.500, [1]0.000 -> 0.400, [2]0.000 -> 0.097, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 5, cp->sounds[1][5:10, 1.000, [1]0.500 -> 1.000, [2]0.150 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(case2 = channel2vct,
                vct(0.000, 0.001, 0.003, 0.009, 0.019, 0.075, 0.135, 0.233, 0.387, 0.628, 1.000))
    snd_display("ramp-xramp (2): %s", case2)
  end
  undo_edit
  scale_channel(0.5, 2, 4)
  test_output.call(4, "
 (scale 2 4) ; scale_channel(0.500, 2, 4 [4:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100, [2]0.000 -> 0.013, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 2, cp->sounds[1][2:5, 0.500, [1]0.200 -> 0.500, [2]0.032 -> 0.150, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 6, cp->sounds[1][6:10, 1.000, [1]0.600 -> 1.000, [2]0.226 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit(2)
  xramp_channel(0.75, 0.25, 32.0)
  test_output.call(3, "
 (ramp 0 11) ; xramp_channel(0.750, 0.250, 32.000, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.750 -> 0.250, off: 0.234, scl: 0.016]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  xramp_channel(0.2, 0.6, 3.0, 2, 6)
  test_output.call(3, "
 (ramp 2 6) ; xramp_channel(0.200, 0.600, 3.000, 2, 6 [3:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:7, 1.000, [1]0.200 -> 0.700, [2]0.200 -> 0.600, off: 0.000, scl: 0.200]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  scale_channel(0.5, 0, 5)
  test_output.call(4, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [4:5]:
   (at 0, cp->sounds[1][0:1, 0.500, [1]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:4, 0.500, [1]0.200 -> 0.400, [2]0.200 -> 0.310, off: 0.000, scl: 0.200]) [buf: 11] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.500 -> 0.700, [2]0.387 -> 0.600, off: 0.000, scl: 0.200]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  set_sample(4, 0.5)
  test_output.call(4, "
 (set 4 1) ; set_sample(4, 0.5000 [4:6]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.300, [2]0.200 -> 0.249, off: 0.000, scl: 0.200]) [buf: 11] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.500 -> 0.700, [2]0.387 -> 0.600, off: 0.000, scl: 0.200]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  revert_sound
  test_name = "xramp-ramp"
  map_chan($init_channel, 0, 10)
  xramp_channel(0.0, 1.0, 32.0)
  ramp_channel(0.0, 1.0)
  test_output.call(3, "
 (ramp 0 11) ; ramp_channel(0.000, 1.000, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(case1, (res = channel2vct))
    snd_display("xramp-ramp (1): %s", res)
  end
  scale_channel(0.5)
  test_output.call(4, "
 (scale 0 11) ; scale_channel(0.500, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 0.500, [1]0.000 -> 1.000, [2]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  scale_channel(0.5, 0, 5)
  test_output.call(4, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [4:3]:
   (at 0, cp->sounds[1][0:4, 0.500, [1]0.000 -> 0.400, [2]0.000 -> 0.097, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 5, cp->sounds[1][5:10, 1.000, [1]0.500 -> 1.000, [2]0.150 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(case2, (res = channel2vct))
    snd_display("xramp-ramp (2): %s", res)
  end
  undo_edit
  scale_channel(0.5, 2, 4)
  test_output.call(4, "
 (scale 2 4) ; scale_channel(0.500, 2, 4 [4:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100, [2]0.000 -> 0.013, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 2, cp->sounds[1][2:5, 0.500, [1]0.200 -> 0.500, [2]0.032 -> 0.150, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 6, cp->sounds[1][6:10, 1.000, [1]0.600 -> 1.000, [2]0.226 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit(2)
  ramp_channel(0.75, 0.25)
  test_output.call(3, "
 (ramp 0 11) ; ramp_channel(0.750, 0.250, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.750 -> 0.250, [2]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  ramp_channel(0.2, 0.6, 2, 6)
  test_output.call(3, "
 (ramp 2 6) ; ramp_channel(0.200, 0.600, 2, 6 [3:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.013, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 2, cp->sounds[1][2:7, 1.000, [1]0.200 -> 0.600, [2]0.032 -> 0.333, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.484 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  scale_channel(0.5, 0, 5)
  test_output.call(4, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [4:5]:
   (at 0, cp->sounds[1][0:1, 0.500, [1]0.000 -> 0.013, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 2, cp->sounds[1][2:4, 0.500, [1]0.200 -> 0.360, [2]0.032 -> 0.097, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.440 -> 0.600, [2]0.150 -> 0.333, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.484 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  set_sample(4, 0.5)
  test_output.call(4, "
 (set 4 1) ; set_sample(4, 0.5000 [4:6]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.013, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.280, [2]0.032 -> 0.059, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.440 -> 0.600, [2]0.150 -> 0.333, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.484 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  close_sound(ind)
end

# ramp2+xramp
def test_05_05
  ind = new_sound("test.snd")
  idx = -1
  test_name = "ramp2+xramp"
  test_output = lambda do |ed, str|
    idx += 1
    if (res = safe_display_edits(ind, 0, ed)) != str
      snd_display("%s %s: %s %s?", test_name, idx, str, res)
    end
  end
  map_chan($init_channel, 0, 10)
  xramp_channel(0.0, 1.0, 32.0)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  test_output.call(4, "
 (ramp 0 11) ; ramp_channel(0.000, 1.000, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, [3]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(case1 = channel2vct,
                vct(0.000, 0.000, 0.001, 0.005, 0.015, 0.038, 0.081, 0.163, 0.310, 0.565, 1.000))
    snd_display("ramp2+xramp (1): %s", case1)
  end
  scale_channel(0.5)
  test_output.call(5, "
 (scale 0 11) ; scale_channel(0.500, 0, false [5:2]:
   (at 0, cp->sounds[1][0:10, 0.500, [1]0.000 -> 1.000, [2]0.000 -> 1.000, [3]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  scale_channel(0.5, 0, 5)
  test_output.call(5, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [5:3]:
   (at 0, cp->sounds[1][0:4, 0.500, [1]0.000 -> 0.400, [2]0.000 -> 0.400, [3]0.000 -> 0.097, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 5, cp->sounds[1][5:10, 1.000, [1]0.500 -> 1.000, [2]0.500 -> 1.000, [3]0.150 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(case2 = channel2vct,
                vct(0.000, 0.000, 0.001, 0.003, 0.008, 0.038, 0.081, 0.163, 0.310, 0.565, 1.000))
    snd_display("ramp2+xramp (2): %s", case2)
  end
  undo_edit
  scale_channel(0.5, 2, 4)
  test_output.call(5, "
 (scale 2 4) ; scale_channel(0.500, 2, 4 [5:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100, [2]0.000 -> 0.100, [3]0.000 -> 0.013, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 2, cp->sounds[1][2:5, 0.500, [1]0.200 -> 0.500, [2]0.200 -> 0.500, [3]0.032 -> 0.150, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 6, cp->sounds[1][6:10, 1.000, [1]0.600 -> 1.000, [2]0.600 -> 1.000, [3]0.226 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit(2)
  ramp_channel(0.75, 0.25)
  test_output.call(4, "
 (ramp 0 11) ; ramp_channel(0.750, 0.250, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.750 -> 0.250, [3]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  ramp_channel(0.2, 0.6, 2, 6)
  test_output.call(4, "
 (ramp 2 6) ; ramp_channel(0.200, 0.600, 2, 6 [4:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100, [2]0.000 -> 0.013, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 2, cp->sounds[1][2:7, 1.000, [1]0.200 -> 0.700, [2]0.200 -> 0.600, [3]0.032 -> 0.333, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000, [2]0.484 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  scale_channel(0.5, 0, 5)
  test_output.call(5, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [5:5]:
   (at 0, cp->sounds[1][0:1, 0.500, [1]0.000 -> 0.100, [2]0.000 -> 0.013, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 2, cp->sounds[1][2:4, 0.500, [1]0.200 -> 0.400, [2]0.200 -> 0.360, [3]0.032 -> 0.097, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.500 -> 0.700, [2]0.440 -> 0.600, [3]0.150 -> 0.333, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000, [2]0.484 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  set_sample(4, 0.5)
  test_output.call(5, "
 (set 4 1) ; set_sample(4, 0.5000 [5:6]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100, [2]0.000 -> 0.013, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.300, [2]0.200 -> 0.280, [3]0.032 -> 0.059, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.500 -> 0.700, [2]0.440 -> 0.600, [3]0.150 -> 0.333, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000, [2]0.484 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  revert_sound
  idx = -1
  test_name = "xramp+ramp2"
  map_chan($init_channel, 0, 10)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  xramp_channel(0.0, 1.0, 32.0)
  test_output.call(4, "
 (ramp 0 11) ; xramp_channel(0.000, 1.000, 32.000, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, [3]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(case1, (res = channel2vct))
    snd_display("xramp+ramp2 (1): %s", res)
  end
  revert_sound(ind)
  close_sound(ind)
end

# multi-ramp2+xramp
# multi-ramp-xramp
# xramp2
# multi-xramp2
def test_05_06
  ind = new_sound("test.snd")
  idx = -1
  test_name = "multi-ramp2+xramp"
  test_output = lambda do |ed, str|
    idx += 1
    if (res = safe_display_edits(ind, 0, ed)) != str
      snd_display("%s %s: %s %s?", test_name, idx, str, res)
    end
  end
  map_channel($init_channel, 0, 100)
  scale_channel(0.5)
  xramp_channel(1.0, 0.0, 32.0)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  case3 = channel2vct
  undo_edit(4)
  10.times do |i| scale_channel(0.5, i * 10, 10) end
  xramp_channel(1.0, 0.0, 32.0)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  test_output.call(14, "
 (ramp 0 100) ; ramp_channel(0.000, 1.000, 0, false [14:11]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 0.091, [2]0.000 -> 0.091, [3]1.000 -> 0.721, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.101 -> 0.192, [2]0.101 -> 0.192, [3]0.695 -> 0.499, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.202 -> 0.293, [2]0.202 -> 0.293, [3]0.480 -> 0.342, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.303 -> 0.394, [2]0.303 -> 0.394, [3]0.329 -> 0.231, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.404 -> 0.495, [2]0.404 -> 0.495, [3]0.222 -> 0.153, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.505 -> 0.596, [2]0.505 -> 0.596, [3]0.147 -> 0.099, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.606 -> 0.697, [2]0.606 -> 0.697, [3]0.094 -> 0.060, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.707 -> 0.798, [2]0.707 -> 0.798, [3]0.057 -> 0.033, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.808 -> 0.899, [2]0.808 -> 0.899, [3]0.030 -> 0.014, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500, [1]0.909 -> 1.000, [2]0.909 -> 1.000, [3]0.012 -> -0.000, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 100, end_mark)
")
  unless vequal(case3, res = channel2vct)
    snd_display("multi-ramp2+xramp: %s", res)
  end
  revert_sound
  map_channel($init_channel, 0, 100)
  xramp_channel(1.0, 0.0, 32.0)
  ramp_channel(0.0, 1.0, 10, 20)
  ramp_channel(0.0, 1.0, 50, 10)
  ramp_channel(0.0, 1.0, 25, 10)
  test_output.call(5, "
 (ramp 25 10) ; ramp_channel(0.000, 1.000, 25, 10 [5:8]:
   (at 0, cp->sounds[1][0:9, 1.000, [1]1.000 -> 0.721, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 10, cp->sounds[1][10:24, 1.000, [1]0.000 -> 0.737, [2]0.695 -> 0.413, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 25, cp->sounds[1][25:29, 1.000, [1]0.789 -> 1.000, [2]0.000 -> 0.444, [3]0.398 -> 0.342, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 30, cp->sounds[1][30:34, 1.000, [1]0.556 -> 1.000, [2]0.329 -> 0.282, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 35, cp->sounds[1][35:49, 1.000, [1]0.271 -> 0.153, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 1.000, [1]0.000 -> 1.000, [2]0.147 -> 0.099, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 60, cp->sounds[1][60:99, 1.000, [1]0.094 -> -0.000, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 100, end_mark)
")
  close_sound(ind)
  ind = new_sound("test.snd")
  idx = -1
  test_name = "multi-ramp-xramp"
  map_channel($init_channel, 0, 100)
  scale_channel(0.5)
  ramp_channel(0.0, 1.0)
  xramp_channel(1.0, 0.0, 32.0)
  case3 = channel2vct
  undo_edit(3)
  10.times do |i| scale_channel(0.5, i * 10, 10) end
  ramp_channel(0.0, 1.0)
  xramp_channel(1.0, 0.0, 32.0)
  test_output.call(13, "
 (ramp 0 100) ; xramp_channel(1.000, 0.000, 32.000, 0, false [13:11]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 0.091, [2]1.000 -> 0.721, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.101 -> 0.192, [2]0.695 -> 0.499, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.202 -> 0.293, [2]0.480 -> 0.342, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.303 -> 0.394, [2]0.329 -> 0.231, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.404 -> 0.495, [2]0.222 -> 0.153, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.505 -> 0.596, [2]0.147 -> 0.099, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.606 -> 0.697, [2]0.094 -> 0.060, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.707 -> 0.798, [2]0.057 -> 0.033, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.808 -> 0.899, [2]0.030 -> 0.014, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500, [1]0.909 -> 1.000, [2]0.012 -> -0.000, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 100, end_mark)
")
  unless vequal(case3, res = channel2vct)
    snd_display("multi-ramp-xramp: %s", res)
  end
  undo_edit(12)
  xramp_channel(0.0, 1.0, 3.0, 10, 20)
  xramp_channel(0.0, 1.0, 3.0, 50, 10)
  xramp_channel(0.0, 1.0, 3.0, 25, 10)
  test_output.call(4, "
 (ramp 25 10) ; xramp_channel(0.000, 1.000, 3.000, 25, 10 [4:8]:
   (at 0, cp->sounds[1][0:9, 1.000]) [buf: 100] 
   (at 10, cp->sounds[1][10:24, 1.000, [1]0.000 -> 0.623, off: -0.500, scl: 0.500]) [buf: 100] 
   (at 25, cp->sounds[1][25:29, 1.000, [1]0.690 -> 1.000, off: -0.500, scl: 0.500, [2]0.000 -> 0.315, off: -0.500, scl: 0.500]) [buf: 100] 
   (at 30, cp->sounds[1][30:34, 1.000, [1]0.421 -> 1.000, off: -0.500, scl: 0.500]) [buf: 100] 
   (at 35, cp->sounds[1][35:49, 1.000]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 1.000, [1]0.000 -> 1.000, off: -0.500, scl: 0.500]) [buf: 100] 
   (at 60, cp->sounds[1][60:99, 1.000]) [buf: 100] 
   (at 100, end_mark)
")
  revert_sound
  test_name = "multi-xramp-ramp 3"
  map_channel($init_channel, 0, 100)
  10.times do |i| scale_channel(0.5, i * 10, 10) end
  xramp_channel(1.0, 0.0, 32.0)
  ramp_channel(0.0, 1.0)
  test_output.call(13, "
 (ramp 0 100) ; ramp_channel(0.000, 1.000, 0, false [13:11]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 0.091, [2]1.000 -> 0.721, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.101 -> 0.192, [2]0.695 -> 0.499, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.202 -> 0.293, [2]0.480 -> 0.342, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.303 -> 0.394, [2]0.329 -> 0.231, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.404 -> 0.495, [2]0.222 -> 0.153, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.505 -> 0.596, [2]0.147 -> 0.099, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.606 -> 0.697, [2]0.094 -> 0.060, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.707 -> 0.798, [2]0.057 -> 0.033, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.808 -> 0.899, [2]0.030 -> 0.014, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500, [1]0.909 -> 1.000, [2]0.012 -> -0.000, off: -0.032, scl: 0.032]) [buf: 100] 
   (at 100, end_mark)
")
  undo_edit(12)
  ramp_channel(0.0, 1.0, 10, 20)
  ramp_channel(0.0, 1.0, 50, 10)
  ramp_channel(0.0, 1.0, 25, 10)
  test_output.call(4, "
 (ramp 25 10) ; ramp_channel(0.000, 1.000, 25, 10 [4:8]:
   (at 0, cp->sounds[1][0:9, 1.000]) [buf: 100] 
   (at 10, cp->sounds[1][10:24, 1.000, [1]0.000 -> 0.737]) [buf: 100] 
   (at 25, cp->sounds[1][25:29, 1.000, [1]0.789 -> 1.000, [2]0.000 -> 0.444]) [buf: 100] 
   (at 30, cp->sounds[1][30:34, 1.000, [1]0.556 -> 1.000]) [buf: 100] 
   (at 35, cp->sounds[1][35:49, 1.000]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 1.000, [1]0.000 -> 1.000]) [buf: 100] 
   (at 60, cp->sounds[1][60:99, 1.000]) [buf: 100] 
   (at 100, end_mark)
")
  close_sound(ind)
  ind = new_sound("test.snd")
  idx = -1
  test_name = "xramp2"
  map_chan($init_channel, 0, 10)
  xramp_channel(0.0, 1.0, 2.0)
  xramp_channel(0.0, 1.0, 2.0)
  test_output.call(3, "
 (ramp 0 11) ; xramp_channel(0.000, 1.000, 2.000, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, off: -1.000, scl: 1.000, [2]0.000 -> 1.000, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(res = channel2vct,
                vct(0.000, 0.005, 0.022, 0.053, 0.102, 0.172, 0.266, 0.390, 0.549, 0.750, 1.000))
    snd_display("xramp2 (1): %s", res)
  end
  scale_channel(0.5)
  test_output.call(4, "
 (scale 0 11) ; scale_channel(0.500, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 0.500, [1]0.000 -> 1.000, off: -1.000, scl: 1.000, [2]0.000 -> 1.000, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  scale_channel(0.5, 0, 5)
  test_output.call(4, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [4:3]:
   (at 0, cp->sounds[1][0:4, 0.500, [1]0.000 -> 0.320, off: -1.000, scl: 1.000, [2]0.000 -> 0.320, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 5, cp->sounds[1][5:10, 1.000, [1]0.414 -> 1.000, off: -1.000, scl: 1.000, [2]0.414 -> 1.000, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(res = channel2vct,
                vct(0.000, 0.003, 0.011, 0.027, 0.051, 0.172, 0.266, 0.390, 0.549, 0.750, 1.000))
    snd_display("xramp2 (2): %s", res)
  end
  undo_edit
  scale_channel(0.5, 2, 4)
  test_output.call(4, "
 (scale 2 4) ; scale_channel(0.500, 2, 4 [4:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.072, off: -1.000, scl: 1.000, [2]0.000 -> 0.072, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 2, cp->sounds[1][2:5, 0.500, [1]0.149 -> 0.414, off: -1.000, scl: 1.000, [2]0.149 -> 0.414, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 6, cp->sounds[1][6:10, 1.000, [1]0.516 -> 1.000, off: -1.000, scl: 1.000, [2]0.516 -> 1.000, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit(2)
  xramp_channel(0.75, 0.25, 0.3)
  test_output.call(3, "
 (ramp 0 11) ; xramp_channel(0.750, 0.250, 0.300, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, off: -1.000, scl: 1.000, [2]0.750 -> 0.250, off: 0.964, scl: -0.714]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  xramp_channel(0.2, 0.6, 32.0, 2, 6)
  test_output.call(3, "
 (ramp 2 6) ; xramp_channel(0.200, 0.600, 32.000, 2, 6 [3:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.072, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 2, cp->sounds[1][2:7, 1.000, [1]0.149 -> 0.625, off: -1.000, scl: 1.000, [2]0.200 -> 0.600, off: 0.187, scl: 0.013]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.741 -> 1.000, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  scale_channel(0.5, 0, 5)
  test_output.call(4, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [4:5]:
   (at 0, cp->sounds[1][0:1, 0.500, [1]0.000 -> 0.072, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 2, cp->sounds[1][2:4, 0.500, [1]0.149 -> 0.320, off: -1.000, scl: 1.000, [2]0.200 -> 0.239, off: 0.187, scl: 0.013]) [buf: 11] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.414 -> 0.625, off: -1.000, scl: 1.000, [2]0.290 -> 0.600, off: 0.187, scl: 0.013]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.741 -> 1.000, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  set_sample(4, 0.5)
  test_output.call(4, "
 (set 4 1) ; set_sample(4, 0.5000 [4:6]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.072, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.149 -> 0.231, off: -1.000, scl: 1.000, [2]0.200 -> 0.213, off: 0.187, scl: 0.013]) [buf: 11] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.414 -> 0.625, off: -1.000, scl: 1.000, [2]0.290 -> 0.600, off: 0.187, scl: 0.013]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.741 -> 1.000, off: -1.000, scl: 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit(3)
  close_sound(ind)
  ind = new_sound("test.snd")
  idx = -1
  test_name = "multi-xramp2"
  map_channel($init_channel, 0, 100)
  10.times do |i| scale_channel(0.5, i * 10, 10) end
  xramp_channel(0.0, 1.0, 3.0)
  xramp_channel(1.0, 0.0, 0.3)
  test_output.call(13, "
 (ramp 0 100) ; xramp_channel(1.000, 0.000, 0.300, 0, false [13:11]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 0.053, off: -0.500, scl: 0.500, [2]1.000 -> 0.950, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.059 -> 0.117, off: -0.500, scl: 0.500, [2]0.945 -> 0.889, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.124 -> 0.190, off: -0.500, scl: 0.500, [2]0.882 -> 0.819, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.198 -> 0.271, off: -0.500, scl: 0.500, [2]0.811 -> 0.740, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.279 -> 0.361, off: -0.500, scl: 0.500, [2]0.731 -> 0.651, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.371 -> 0.462, off: -0.500, scl: 0.500, [2]0.641 -> 0.550, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.473 -> 0.575, off: -0.500, scl: 0.500, [2]0.540 -> 0.437, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.587 -> 0.701, off: -0.500, scl: 0.500, [2]0.425 -> 0.308, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.715 -> 0.842, off: -0.500, scl: 0.500, [2]0.295 -> 0.164, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500, [1]0.857 -> 1.000, off: -0.500, scl: 0.500, [2]0.148 -> 0.000, off: 1.429, scl: -1.429]) [buf: 100] 
   (at 100, end_mark)
")
  close_sound(ind)
end

# ramp3
# multi-ramp3
def test_05_07
  ind = new_sound("test.snd")
  idx = -1
  test_name = "ramp3"
  test_output = lambda do |ed, str|
    idx += 1
    if (res = safe_display_edits(ind, 0, ed)) != str
      snd_display("%s %s: %s %s?", test_name, idx, str, res)
    end
  end
  map_chan($init_channel, 0, 10)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  test_output.call(4, "
 (ramp 0 11) ; ramp_channel(0.000, 1.000, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, [3]0.000 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  unless vequal(res = channel2vct,
                vct(0.000, 0.001, 0.008, 0.027, 0.064, 0.125, 0.216, 0.343, 0.512, 0.729, 1.000))
    snd_display("ramp3 (1): %s", res)
  end
  scale_channel(0.5)
  test_output.call(5, "
 (scale 0 11) ; scale_channel(0.500, 0, false [5:2]:
   (at 0, cp->sounds[1][0:10, 0.500, [1]0.000 -> 1.000, [2]0.000 -> 1.000, [3]0.000 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  scale_channel(0.5, 0, 5)
  test_output.call(5, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [5:3]:
   (at 0, cp->sounds[1][0:4, 0.500, [1]0.000 -> 0.400, [2]0.000 -> 0.400, [3]0.000 -> 0.400]) [buf: 11] 
   (at 5, cp->sounds[1][5:10, 1.000, [1]0.500 -> 1.000, [2]0.500 -> 1.000, [3]0.500 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  scale_channel(0.5, 2, 4)
  test_output.call(5, "
 (scale 2 4) ; scale_channel(0.500, 2, 4 [5:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100, [2]0.000 -> 0.100, [3]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:5, 0.500, [1]0.200 -> 0.500, [2]0.200 -> 0.500, [3]0.200 -> 0.500]) [buf: 11] 
   (at 6, cp->sounds[1][6:10, 1.000, [1]0.600 -> 1.000, [2]0.600 -> 1.000, [3]0.600 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit(2)
  ramp_channel(0.75, 0.25)
  test_output.call(4, "
 (ramp 0 11) ; ramp_channel(0.750, 0.250, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, [3]0.750 -> 0.250]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  ramp_channel(0.2, 0.6, 2, 6)
  test_output.call(4, "
 (ramp 2 6) ; ramp_channel(0.200, 0.600, 2, 6 [4:4]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100, [2]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:7, 1.000, [1]0.200 -> 0.700, [2]0.200 -> 0.700, [3]0.200 -> 0.600]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000, [2]0.800 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  scale_channel(0.5, 0, 5)
  test_output.call(5, "
 (scale 0 5) ; scale_channel(0.500, 0, 5 [5:5]:
   (at 0, cp->sounds[1][0:1, 0.500, [1]0.000 -> 0.100, [2]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:4, 0.500, [1]0.200 -> 0.400, [2]0.200 -> 0.400, [3]0.200 -> 0.360]) [buf: 11] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.500 -> 0.700, [2]0.500 -> 0.700, [3]0.440 -> 0.600]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000, [2]0.800 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit
  set_sample(4, 0.5)
  test_output.call(5, "
 (set 4 1) ; set_sample(4, 0.5000 [5:6]:
   (at 0, cp->sounds[1][0:1, 1.000, [1]0.000 -> 0.100, [2]0.000 -> 0.100]) [buf: 11] 
   (at 2, cp->sounds[1][2:3, 1.000, [1]0.200 -> 0.300, [2]0.200 -> 0.300, [3]0.200 -> 0.280]) [buf: 11] 
   (at 4, cp->sounds[2][0:0, 1.000]) [buf: 1] 
   (at 5, cp->sounds[1][5:7, 1.000, [1]0.500 -> 0.700, [2]0.500 -> 0.700, [3]0.440 -> 0.600]) [buf: 11] 
   (at 8, cp->sounds[1][8:10, 1.000, [1]0.800 -> 1.000, [2]0.800 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  undo_edit(3)
  close_sound(ind)
  ind = new_sound("test.snd")
  idx = -1
  test_name = "multi-ramp3"
  map_channel($init_channel, 0, 100)
  test_name = ""
  10.times do |i| scale_channel(0.5, i * 10, 10) end
  ramp_channel(0.0, 1.0)
  ramp_channel(1.0, -0.5)
  ramp_channel(-0.5, 1.5)
  test_output.call(14, "
 (ramp 0 100) ; ramp_channel(-0.500, 1.500, 0, false [14:11]:
   (at 0, cp->sounds[1][0:9, 0.500, [1]0.000 -> 0.091, [2]1.000 -> 0.864, [3]-0.500 -> -0.318]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 0.500, [1]0.101 -> 0.192, [2]0.848 -> 0.712, [3]-0.298 -> -0.116]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 0.500, [1]0.202 -> 0.293, [2]0.697 -> 0.561, [3]-0.096 -> 0.086]) [buf: 100] 
   (at 30, cp->sounds[1][30:39, 0.500, [1]0.303 -> 0.394, [2]0.545 -> 0.409, [3]0.106 -> 0.288]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 0.500, [1]0.404 -> 0.495, [2]0.394 -> 0.258, [3]0.308 -> 0.490]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 0.500, [1]0.505 -> 0.596, [2]0.242 -> 0.106, [3]0.510 -> 0.692]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 0.500, [1]0.606 -> 0.697, [2]0.091 -> -0.045, [3]0.712 -> 0.894]) [buf: 100] 
   (at 70, cp->sounds[1][70:79, 0.500, [1]0.707 -> 0.798, [2]-0.061 -> -0.197, [3]0.914 -> 1.096]) [buf: 100] 
   (at 80, cp->sounds[1][80:89, 0.500, [1]0.808 -> 0.899, [2]-0.212 -> -0.348, [3]1.116 -> 1.298]) [buf: 100] 
   (at 90, cp->sounds[1][90:99, 0.500, [1]0.909 -> 1.000, [2]-0.364 -> -0.500, [3]1.318 -> 1.500]) [buf: 100] 
   (at 100, end_mark)
")
  undo_edit(13)
  ramp_channel(0.0, 1.0, 10, 30)
  ramp_channel(0.0, 1.0, 50, 20)
  ramp_channel(0.0, 1.0, 20, 15)
  ramp_channel(0.0, 1.0, 30, 30)
  test_output.call(5, "
 (ramp 30 30) ; ramp_channel(0.000, 1.000, 30, 30 [5:10]:
   (at 0, cp->sounds[1][0:9, 1.000]) [buf: 100] 
   (at 10, cp->sounds[1][10:19, 1.000, [1]0.000 -> 0.310]) [buf: 100] 
   (at 20, cp->sounds[1][20:29, 1.000, [1]0.345 -> 0.655, [2]0.000 -> 0.643]) [buf: 100] 
   (at 30, cp->sounds[1][30:34, 1.000, [1]0.690 -> 0.828, [2]0.714 -> 1.000, [3]0.000 -> 0.138]) [buf: 100] 
   (at 35, cp->sounds[1][35:39, 1.000, [1]0.862 -> 1.000, [2]0.172 -> 0.310]) [buf: 100] 
   (at 40, cp->sounds[1][40:49, 1.000, [1]0.345 -> 0.655]) [buf: 100] 
   (at 50, cp->sounds[1][50:59, 1.000, [1]0.000 -> 0.474, [2]0.690 -> 1.000]) [buf: 100] 
   (at 60, cp->sounds[1][60:69, 1.000, [1]0.526 -> 1.000]) [buf: 100] 
   (at 70, cp->sounds[1][70:99, 1.000]) [buf: 100] 
   (at 100, end_mark)
")
  close_sound(ind)
  ind = new_sound("test.snd")
  map_chan($init_channel, 0, 10)
  idx = -1
  test_name = "ramp+xramp"
  ramp_channel(0.0, 1.0)
  xramp_channel(0.0, 1.0, 32.0)
  test_output.call(3, "
 (ramp 0 11) ; xramp_channel(0.000, 1.000, 32.000, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  idx = -1
  test_name = "xramp+xramp"
  undo_edit(2)
  xramp_channel(0.0, 1.0, 0.32)
  xramp_channel(0.0, 1.0, 32.0)
  test_output.call(3, "
 (ramp 0 11) ; xramp_channel(0.000, 1.000, 32.000, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, off: 1.471, scl: -1.471, [2]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  idx = -1
  test_name = "xramp+xramp+xramp"
  undo_edit(2)
  xramp_channel(0.0, 1.0, 0.32)
  xramp_channel(0.0, 1.0, 32.0)
  xramp_channel(0.0, 1.0, 32.0)
  test_output.call(4, "
 (ramp 0 11) ; xramp_channel(0.000, 1.000, 32.000, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, off: 1.471, scl: -1.471, [2]0.000 -> 1.000, off: -0.032, scl: 0.032, [3]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  idx = -1
  test_name = "xramp+xramp+ramp"
  undo_edit(3)
  xramp_channel(0.0, 1.0, 0.32)
  xramp_channel(0.0, 1.0, 32.0)
  ramp_channel(0.0, 1.0)
  test_output.call(4, "
 (ramp 0 11) ; ramp_channel(0.000, 1.000, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, off: 1.471, scl: -1.471, [3]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  idx = -1
  test_name = "xramp+ramp"
  undo_edit(3)
  xramp_channel(0.0, 1.0, 32.0)
  ramp_channel(0.0, 1.0)
  test_output.call(3, "
 (ramp 0 11) ; ramp_channel(0.000, 1.000, 0, false [3:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  idx = -1
  test_name = "ramp+ramp+xramp"
  undo_edit(2)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  xramp_channel(0.0, 1.0, 32.0)
  test_output.call(4, "
 (ramp 0 11) ; xramp_channel(0.000, 1.000, 32.000, 0, false [4:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, [3]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  idx = -1
  test_name = "ramp+ramp+ramp+ramp"
  undo_edit(3)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  test_output.call(5, "
 (ramp 0 11) ; ramp_channel(0.000, 1.000, 0, false [5:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, [3]0.000 -> 1.000, [4]0.000 -> 1.000]) [buf: 11] 
   (at 11, end_mark)
")
  idx = -1
  test_name = "ramp+ramp+ramp+xramp"
  undo_edit(4)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  xramp_channel(0.0, 1.0, 32.0)
  test_output.call(5, "
 (ramp 0 11) ; xramp_channel(0.000, 1.000, 32.000, 0, false [5:2]:
   (at 0, cp->sounds[1][0:10, 1.000, [1]0.000 -> 1.000, [2]0.000 -> 1.000, [3]0.000 -> 1.000, [4]0.000 -> 1.000, off: -0.032, scl: 0.032]) [buf: 11] 
   (at 11, end_mark)
")
  close_sound(ind)
end

def test_05_08
  data = make_vct(101, 1.0)
  rto1_data = make_vct(101)
  xto1_data = make_vct(101)
  cos_data = make_vct(101)
  xe = make_env(:envelope, [0, 0, 1, 1], :length, 101, :base, 32.0)
  incr = PI / 101.0
  ang = -0.5 * PI
  101.times do |i|
    rto1_data[i] = i * 0.01
    xto1_data[i] = env(xe)
    cos_data[i] = cos(ang)
    ang += incr
  end
  ind = new_sound("test.snd")
  set_to_1 = lambda do map_chan($init_channel, 0, 100) end
  cset_to_1 = lambda do |dat| 101.times do |i| dat[i] = 1.0 end end
  ramp_to_1 = lambda do ramp_channel(0.0, 1.0) end
  cramp_to_1 = lambda do |dat| vct_multiply!(dat, rto1_data) end
  scale_by_half = lambda do scale_channel(0.5) end
  cscale_by_half = lambda do |dat| vct_scale!(dat, 0.5) end
  scale_by_two = lambda do scale_channel(2.0, 30, 40) end
  cscale_by_two = lambda do |dat| (30...70).each do |i| dat[i] *= 2.0 end end
  xramp_to_1 = lambda do xramp_channel(0.0, 1.0, 32.0) end
  cxramp_to_1 = lambda do |dat| vct_multiply!(dat, xto1_data) end
  scale_mid = lambda do scale_channel(0.125, 30, 30) end
  cscale_mid = lambda do |dat| (30...60).each do |i| dat[i] *= 0.125 end end
  on_air = lambda do scale_channel(0.0, 10, 30) end
  con_air = lambda do |dat| (10...40).each do |i| dat[i] = 0.0 end end
  rev_channel2vct = lambda do
    len = data.length
    rd = make_sampler(len - 1, ind, 0, -1)
    dat = make_vct(len)
    (len - 1).downto(0) do |i| dat[i] = rd.call end
    free_sampler(rd)
    dat
  end
  if $with_test_motif
    edhist = channel_widgets(ind, 0)[7]
    edp = RXtParent(edhist)
    pmax = RXtVaGetValues(edp, [RXmNpaneMaximum, 0]).cadr
    RXtUnmanageChild(edp)
    RXtVaSetValues(edp, [RXmNpaneMinimum, 100])
    RXtManageChild(edp)
  end
  set_squelch_update(true, ind)
  # 0 case
  set_to_1.call
  snd_test_neq(channel2vct, data, "0 case")
  snd_test_neq(rev_channel2vct.call, data, "0 rev case")
  # 1 case
  [[scale_by_two, cscale_by_two, :scale_by_two],
    [ramp_to_1, cramp_to_1, :ramp_to_1],
    [xramp_to_1, cxramp_to_1, :xramp_to_1],
    [scale_by_half, cscale_by_half, :scale_by_half],
    [scale_mid, cscale_mid, :scale_mid],
    [on_air, con_air, :on_air]].each do |func, check, name|
    revert_sound
    set_to_1.call
    cset_to_1.call(data)
    func.call
    check.call(data)
    snd_test_neq(channel2vct, data, "1 case %s", name)
    snd_test_neq(rev_channel2vct.call, data, "1 rev case %s", name)
  end
  # 2 case
  [[scale_by_two, cscale_by_two, :scale_by_two],
    [ramp_to_1, cramp_to_1, :ramp_to_1],
    [xramp_to_1, cxramp_to_1, :xramp_to_1],
    [scale_by_half, cscale_by_half, :scale_by_half],
    [scale_mid, cscale_mid, :scale_mid],
    [on_air, con_air, :on_air]].each do |func, check, name|
    [[scale_by_two, cscale_by_two, :scale_by_two],
      [ramp_to_1, cramp_to_1, :ramp_to_1],
      [xramp_to_1, cxramp_to_1, :xramp_to_1],
      [scale_by_half, cscale_by_half, :scale_by_half],
      [scale_mid, cscale_mid, :scale_mid],
      [on_air, con_air, :on_air]].each do |func1, check1, name1|
      revert_sound
      set_to_1.call
      cset_to_1.call(data)
      func.call
      check.call(data)
      func1.call
      check1.call(data)
      snd_test_neq(channel2vct, data, "2 case %s (%s)", name1, name)
      snd_test_neq(rev_channel2vct.call, data, "2 rev case %s (%s)", name1, name)
    end
  end
  # 3 case
  [[scale_by_two, cscale_by_two, :scale_by_two],
    [ramp_to_1, cramp_to_1, :ramp_to_1],
    [xramp_to_1, cxramp_to_1, :xramp_to_1],
    [scale_by_half, cscale_by_half, :scale_by_half],
    [scale_mid, cscale_mid, :scale_mid],
    [on_air, con_air, :on_air]].each do |func, check, name|
    [[scale_by_two, cscale_by_two, :scale_by_two],
      [ramp_to_1, cramp_to_1, :ramp_to_1],
      [xramp_to_1, cxramp_to_1, :xramp_to_1],
      [scale_by_half, cscale_by_half, :scale_by_half],
      [scale_mid, cscale_mid, :scale_mid],
      [on_air, con_air, :on_air]].each do |func1, check1, name1|
      [[scale_by_two, cscale_by_two, :scale_by_two],
        [ramp_to_1, cramp_to_1, :ramp_to_1],
        [xramp_to_1, cxramp_to_1, :xramp_to_1],
        [scale_by_half, cscale_by_half, :scale_by_half],
        [scale_mid, cscale_mid, :scale_mid],
        [on_air, con_air, :on_air]].each do |func2, check2, name2|
        revert_sound
        set_to_1.call
        cset_to_1.call(data)
        func.call
        check.call(data)
        func1.call
        check1.call(data)
        func2.call
        check2.call(data)
        snd_test_neq(channel2vct, data, "3 case %s (%s (%s))", name2, name1, name)
        snd_test_neq(rev_channel2vct.call, data, "3 rev case %s (%s (%s))", name2, name1, name)
      end
    end
  end
  if $all_args
    # 4 case
    [[scale_by_two, cscale_by_two, :scale_by_two],
     [ramp_to_1, cramp_to_1, :ramp_to_1],
     [xramp_to_1, cxramp_to_1, :xramp_to_1],
     [scale_by_half, cscale_by_half, :scale_by_half],
     [scale_mid, cscale_mid, :scale_mid],
     [on_air, con_air, :on_air]].each do |func, check, name|
      [[scale_by_two, cscale_by_two, :scale_by_two],
       [ramp_to_1, cramp_to_1, :ramp_to_1],
       [xramp_to_1, cxramp_to_1, :xramp_to_1],
       [scale_by_half, cscale_by_half, :scale_by_half],
       [scale_mid, cscale_mid, :scale_mid],
       [on_air, con_air, :on_air]].each do |func1, check1, name1|
        [[scale_by_two, cscale_by_two, :scale_by_two],
         [ramp_to_1, cramp_to_1, :ramp_to_1],
         [xramp_to_1, cxramp_to_1, :xramp_to_1],
         [scale_by_half, cscale_by_half, :scale_by_half],
         [scale_mid, cscale_mid, :scale_mid],
         [on_air, con_air, :on_air]].each do |func2, check2, name2|
          [[scale_by_two, cscale_by_two, :scale_by_two],
           [ramp_to_1, cramp_to_1, :ramp_to_1],
           [xramp_to_1, cxramp_to_1, :xramp_to_1],
           [scale_by_half, cscale_by_half, :scale_by_half],
           [scale_mid, cscale_mid, :scale_mid],
           [on_air, con_air, :on_air]].each do |func3, check3, name3|
            revert_sound
            set_to_1.call
            cset_to_1.call(data)
            func.call
            check.call(data)
            func1.call
            check1.call(data)
            func2.call
            check2.call(data)
            func3.call
            check3.call(data)
            snd_test_neq(channel2vct,
                         data,
                         "4 case %s (%s (%s (%s)))",
                         name3, name2, name1, name)
            snd_test_neq(rev_channel2vct.call,
                         data,
                         "4 rev case %s (%s (%s (%s)))",
                         name3, name2, name1, name)
          end
        end
      end
    end
    # 5 case
    [[scale_by_two, cscale_by_two, :scale_by_two],
     [ramp_to_1, cramp_to_1, :ramp_to_1],
     [xramp_to_1, cxramp_to_1, :xramp_to_1],
     [scale_by_half, cscale_by_half, :scale_by_half],
     [scale_mid, cscale_mid, :scale_mid],
     [on_air, con_air, :on_air]].each do |func, check, name|
      [[scale_by_two, cscale_by_two, :scale_by_two],
       [ramp_to_1, cramp_to_1, :ramp_to_1],
       [xramp_to_1, cxramp_to_1, :xramp_to_1],
       [scale_by_half, cscale_by_half, :scale_by_half],
       [scale_mid, cscale_mid, :scale_mid],
       [on_air, con_air, :on_air]].each do |func1, check1, name1|
        [[scale_by_two, cscale_by_two, :scale_by_two],
         [ramp_to_1, cramp_to_1, :ramp_to_1],
         [xramp_to_1, cxramp_to_1, :xramp_to_1],
         [scale_by_half, cscale_by_half, :scale_by_half],
         [scale_mid, cscale_mid, :scale_mid],
         [on_air, con_air, :on_air]].each do |func2, check2, name2|
          [[scale_by_two, cscale_by_two, :scale_by_two],
           [ramp_to_1, cramp_to_1, :ramp_to_1],
           [xramp_to_1, cxramp_to_1, :xramp_to_1],
           [scale_by_half, cscale_by_half, :scale_by_half],
           [scale_mid, cscale_mid, :scale_mid],
           [on_air, con_air, :on_air]].each do |func3, check3, name3|
            [[scale_by_two, cscale_by_two, :scale_by_two],
             [ramp_to_1, cramp_to_1, :ramp_to_1],
             [xramp_to_1, cxramp_to_1, :xramp_to_1],
             [scale_by_half, cscale_by_half, :scale_by_half],
             [scale_mid, cscale_mid, :scale_mid],
             [on_air, con_air, :on_air]].each do |func4, check4, name4|
              revert_sound
              set_to_1.call
              cset_to_1.call(data)
              func.call
              check.call(data)
              func1.call
              check1.call(data)
              func2.call
              check2.call(data)
              func3.call
              check3.call(data)
              func4.call
              check4.call(data)
              snd_test_neq(channel2vct,
                           data,
                           "5 case %s (%s (%s (%s (%s))))",
                           name4, name3, name2, name1, name)
              snd_test_neq(rev_channel2vct.call,
                           data,
                           "5 rev case %s (%s (%s (%s (%s))))",
                           name4, name3, name2, name1, name)
            end
          end
        end
      end
    end
    # 6 case
    [[scale_by_two, cscale_by_two, :scale_by_two],
     [ramp_to_1, cramp_to_1, :ramp_to_1],
     [xramp_to_1, cxramp_to_1, :xramp_to_1],
     [scale_by_half, cscale_by_half, :scale_by_half],
     [scale_mid, cscale_mid, :scale_mid],
     [on_air, con_air, :on_air]].each do |func, check, name|
      [[scale_by_two, cscale_by_two, :scale_by_two],
       [ramp_to_1, cramp_to_1, :ramp_to_1],
       [xramp_to_1, cxramp_to_1, :xramp_to_1],
       [scale_by_half, cscale_by_half, :scale_by_half],
       [scale_mid, cscale_mid, :scale_mid],
       [on_air, con_air, :on_air]].each do |func1, check1, name1|
        [[scale_by_two, cscale_by_two, :scale_by_two],
         [ramp_to_1, cramp_to_1, :ramp_to_1],
         [xramp_to_1, cxramp_to_1, :xramp_to_1],
         [scale_by_half, cscale_by_half, :scale_by_half],
         [scale_mid, cscale_mid, :scale_mid],
         [on_air, con_air, :on_air]].each do |func2, check2, name2|
          [[scale_by_two, cscale_by_two, :scale_by_two],
           [ramp_to_1, cramp_to_1, :ramp_to_1],
           [xramp_to_1, cxramp_to_1, :xramp_to_1],
           [scale_by_half, cscale_by_half, :scale_by_half],
           [scale_mid, cscale_mid, :scale_mid],
           [on_air, con_air, :on_air]].each do |func3, check3, name3|
            [[scale_by_two, cscale_by_two, :scale_by_two],
             [ramp_to_1, cramp_to_1, :ramp_to_1],
             [xramp_to_1, cxramp_to_1, :xramp_to_1],
             [scale_by_half, cscale_by_half, :scale_by_half],
             [scale_mid, cscale_mid, :scale_mid],
             [on_air, con_air, :on_air]].each do |func4, check4, name4|
              [[scale_by_two, cscale_by_two, :scale_by_two],
               [ramp_to_1, cramp_to_1, :ramp_to_1],
               [xramp_to_1, cxramp_to_1, :xramp_to_1],
               [scale_by_half, cscale_by_half, :scale_by_half],
               [scale_mid, cscale_mid, :scale_mid],
               [on_air, con_air, :on_air]].each do |func5, check5, name5|
                revert_sound
                set_to_1.call
                cset_to_1.call(data)
                func.call
                check.call(data)
                func1.call
                check1.call(data)
                func2.call
                check2.call(data)
                func3.call
                check3.call(data)
                func4.call
                check4.call(data)
                func5.call
                check5.call(data)
                snd_test_neq(channel2vct,
                             data,
                             "6 case %s (%s (%s (%s (%s (%s)))))",
                             name5, name4, name3, name2, name1, name)
                snd_test_neq(rev_channel2vct.call,
                             data,
                             "6 rev case %s (%s (%s (%s (%s (%s)))))",
                             name5, name4, name3, name2, name1, name)
              end
            end
          end
        end
      end
    end
  end
  close_sound(ind)
end

def test_05_09
  ind = open_sound("oboe.snd")
  snd_test_neq(redo_edit(1, ind, 0), 0, "open redo_edit with no ops")
  snd_test_neq(undo_edit(1, ind, 0), 0, "open undo_edit with no ops")
  set_cursor(1000)
  delete_sample(321)
  snd_test_neq(cursor(), 999, "delete_sample before cursor")
  snd_test_neq(cursor(ind, 0, 0), 1000, "delete_sample before cursor (0)")
  undo_edit
  snd_test_neq(cursor(), 1000, "delete_sample after cursor undo")
  undo_edit(-1)
  snd_test_neq(cursor(), 999, "delete_sample before cursor redo")
  redo_edit(-1)
  delete_sample(1321)
  snd_test_neq(cursor(), 1000, "delete_sample after cursor")
  undo_edit
  delete_samples(0, 100)
  snd_test_neq(cursor(), 900, "delete_samples before cursor")
  undo_edit
  delete_samples(1100, 100)
  snd_test_neq(cursor(), 1000, "delete_samples after cursor")
  undo_edit
  insert_samples(100, 100, make_vct(100))
  snd_test_neq(cursor(), 1100, "insert_samples before cursor")
  undo_edit
  insert_samples(1100, 100, make_vct(100))
  snd_test_neq(cursor(), 1000, "insert_samples after cursor")
  undo_edit
  set_samples(0, 100, make_vct(100))
  snd_test_neq(cursor(), 1000, "set_samples cursor")
  set_show_axes(Show_x_axis_unlabelled, ind, 0)
  update_time_graph
  set_show_axes(Show_all_axes_unlabelled, ind, 0)
  update_time_graph
  close_sound(ind)
  #
  ind = new_sound("test.snd", :size, 100)
  vct2channel(Vct.new(3, 1.0), 10, 8)
  snd_test_neq(maxamp(ind, 0), 1.0, "vct2channel size mismatch maxamp")
  snd_test_neq(channel2vct(0, 20, ind, 0),
               vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                   1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
               "vct2channel size mismatch")
  revert_sound(ind)
  set_samples(10, 5, Vct.new(3, 1.0))
  snd_test_neq(maxamp(ind, 0), 1.0, "set_samples size mismatch maxamp")
  snd_test_neq(channel2vct(0, 20, ind, 0),
               vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                   1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
               "set_samples size mismatch")
  revert_sound(ind)
  insert_samples(10, 8, Vct.new(3, 1.0), ind, 0)
  snd_test_neq(maxamp(ind, 0), 1.0, "insert samples size mismatch maxamp")
  snd_test_neq(channel2vct(0, 20, ind, 0),
               vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                   1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
               "insert samples size mismatch")
  close_sound(ind)
end

def test_05_10
  ind = open_sound("oboe.snd")
  bnds = x_bounds(ind)
  xp = x_position_slider
  yp = y_position_slider
  xz = x_zoom_slider
  yz = y_zoom_slider
  snd_test_neq(snd_completion(" open-so"), " open-sound", "completion (1)")
  # FIXME (INFO)
  # Zoom_focus_right (constant) replaced by zoom_focus_style
  snd_test_neq(snd_completion(" zoom_focus_s"), " zoom_focus_style", "completion (2)")
  play("oboe.snd", :wait, true)
  play("oboe.snd", :start, 12000, :wait, true)
  play("oboe.snd", :start, 12000, :end, 15000, :wait, true)
  play(ind, :edit_position, edit_position - 1, :wait, true)
  old_speed = speed_control(ind)
  old_style = speed_control_style
  old_open = show_controls(ind)
  set_show_controls(true, ind)
  set_speed_control(-2.0, ind)
  play(ind, :start, 12345, :wait, true)
  set_speed_control_style(Speed_control_as_semitone)
  set_speed_control(0.5, ind)
  set_speed_control_style(Speed_control_as_ratio)
  set_speed_control(0.25, ind)
  set_speed_control(old_speed, ind)
  set_speed_control_style(old_style)
  set_show_controls(old_open, ind)
  bomb(ind, true)
  k = disk_kspace("oboe.snd")
  if (not number?(k)) or k <= 0
    snd_display("disk_kspace = %s", k)
  end
  k = disk_kspace("/baddy/hiho")
  # FIXME (INFO)
  # #if !USE_STATFS in snd-file.c
  # disk_kspace returns 1234567
  if k != 1234567 and k != -1
    snd_display("disk_kspace of bogus file = %s", k)
  end
  snd_test_neq(transform_frames, 0, "transform_frames")
  set_transform_size(512)
  set_transform_graph?(true)
  unless (res = fft_peak(ind, 0, 1.0))
    snd_display("fft_peak %s?", res.inspect)
  end
  set_time_graph?(true)
  # 
  Snd.catch(:all, lambda do |*args| snd_display("axis label error: %s", args) end) do
    snd_test_neq(x_axis_label(), "time", "def time x_axis_label")
    set_x_axis_label("no time", ind, 0, Time_graph)
    snd_test_neq(x_axis_label(), "no time", "set time x_axis_label")
    update_transform_graph
    snd_test_neq(x_axis_label(ind, 0, Transform_graph), "frequency", "get fft x_axis_label")
    set_x_axis_label("hiho", ind, 0, Transform_graph)
    update_transform_graph
    snd_test_neq(x_axis_label(ind, 0, Transform_graph), "hiho", "set fft x_axis_label")
    set_x_axis_label("frequency", ind, 0, Transform_graph) # for later test
    # 
    graph([0, 0, 1, 1, 2, 0], "lisp")
    update_lisp_graph
    snd_test_neq(x_axis_label(ind, 0, Lisp_graph), "lisp", "def lisp x_axis_label")
    set_x_axis_label("no lisp", ind, 0, Lisp_graph)
    snd_test_neq(x_axis_label(ind, 0, Lisp_graph), "no lisp", "lisp x_axis_label")
    # 
    set_y_axis_label("no amp", ind, 0, Time_graph)
    snd_test_neq(y_axis_label(), "no amp", "time y_axis_label")
    set_y_axis_label("no lamp", ind, 0, Lisp_graph)
    snd_test_neq(y_axis_label(ind, 0, Lisp_graph), "no lamp", "lisp y_axis_label")
    set_y_axis_label(false)
    set_y_axis_label("no amp", ind, 0)
    snd_test_neq(y_axis_label(), "no amp", "time y_axis_label")
    set_y_axis_label(false, ind)
  end
  #
  cr = make_cairo(channel_widgets(ind, 0)[0])
  graph_data(make_vct(4), ind, 0, Copy_context, false, false, Graph_lines, cr)
  free_cairo(cr)
  update_lisp_graph
  graph(vct(0, 0, 1, 1, 2, 0))
  32.times do
    graph(vct(0, 1, 2))
    graph([vct(0, 1, 2), vct(3, 2, 1), vct(1, 2, 3)])
    graph([vct(0, 1, 2), vct(3, 2, 1)])
  end
  set_x_bounds([0.0, 0.01])
  data = make_graph_data
  if vct?(data)
    snd_test_neq(right_sample - left_sample + 1,
                 data.length,
                 "make_graph_data bounds: %d %d",
                 left_sample,
                 right_sample)
    mid = (0.5 * data.length).round
    snd_test_neq(sample(left_sample + mid), data[mid], "make_graph_data[%d]", mid)
  end
  data = make_graph_data(ind, 0, 0, 100, 199)
  if vct?(data)
    snd_test_neq(data.length, 100, "make_graph_data 100:199")
    snd_test_neq(sample(50), data[50], "make_graph_data 50")
  end
  set_x_bounds([0.0, 0.1])
  update_transform_graph
  Snd.catch(:no_such_axis, lambda do |*args| snd_display("transform axis not displayed?") end) do
    snd_test_neq(x_axis_label(ind, 0, Transform_graph), "frequency", "def fft x_axis_label")
    set_x_axis_label("fourier", ind, 0, Transform_graph)
    snd_test_neq(x_axis_label(ind, 0, Transform_graph), "fourier", "fft x_axis_label")
    set_x_axis_label("hiho")
    # 
    set_y_axis_label("spectra", ind, 0, Transform_graph)
    snd_test_neq(y_axis_label(ind, 0, Transform_graph), "spectra", "fft y_axis_label")
    set_y_axis_label("hiho")
  end
  # 
  if number?(transform_frames) and transform_frames.zero?
    snd_display("transform_graph? transform-frames: %s?", trandform_frames)
  end
  update_transform_graph
  if (tag = Snd.catch do peaks("/baddy/hiho") end).first != :cant_open_file
    snd_display("peaks bad file: %s", tag.inspect)
  end
  peaks("tmp.peaks")
  pks_data = IO.readlines("tmp.peaks")
  if /Snd: fft peaks/ !~ pks_data[0]
    snd_display("peaks 1: %s", pks_data[0].inspect)
  end
  if /fft 512 points beginning at sample 0/ !~ pks_data[2]
    snd_display("peaks 2: %s", pks_data[2].inspect)
  end
  delete_file("tmp.peaks")
  peaks()
  if $with_test_motif and (!dialog_widgets[20] or !RXtIsManaged(dialog_widgets[20]))
    snd_display("peaks but no help?")
  end
  dismiss_all_dialogs
  num_transforms = 6
  num_transform_graph_types = 3
  set_transform_graph?(true, ind, 0)
  set_transform_size(64, ind, 0)
  num_transforms.times do |i|
    set_transform_type(integer2transform(i))
    unless transform?(integer2transform(i))
      snd_display("transform? %d?", i)
    end
    num_transform_graph_types.times do |j|
      set_transform_graph_type(j, ind, 0)
      update_transform_graph(ind, 0)
    end
  end
  set_transform_type($fourier_transform)
  unless (res = transform?(transform_type))
    snd_display("transform? %s %s?", res, $fourier_transform)
  end
  unless transform?($autocorrelation)
    snd_display("transform? $autocorrelation")
  end
  if read_only(ind)
    snd_display("read_only open_sound: %s?", read_only(ind))
  end
  set_read_only(true, ind)
  unless read_only(ind)
    snd_display("set_read_only: %s?", read_only(ind))
  end
  a_ctr = 0
  bind_key(key_to_int(?a), 0, lambda do a_ctr = 3 end)
  key(key_to_int(?a), 0)
  snd_test_neq(a_ctr, 3, "bind_key")
  unbind_key(key_to_int(?a), 0)
  a_ctr = 0
  key(key_to_int(?a), 0)
  old_gstyle = graph_style
  5.times do |i|
    psf = eps_file
    if string?(psf)
      delete_file(psf)
      set_graph_style(i)
      graph2ps
      unless File.exists?(psf)
        snd_display("graph2ps: %s?", psf)
      end
      delete_file(psf)
    end
  end
  set_graph_style(old_gstyle)
  if (err = Snd.catch(:cannot_print, 12345) do graph2ps("/bad/bad.eps") end).first != 12345
    snd_display("graph2ps err: %s?", err.inspect)
  end
  n2 = open_sound("2.snd") or open_sound("4.aiff")
  set_transform_graph?(true, n2)
  [Channels_superimposed, Channels_combined, Channels_separate].each do |style|
    set_channel_style(style, n2)
    snd_test_neq(channel_style(n2), style, "channel_style")
    graph2ps("aaa.eps")
  end
  close_sound(n2)
  if channels(ind) == 1
    set_channel_style(Channels_superimposed, ind)
    snd_test_neq(channel_style(ind), Channels_separate, "channel_style[0]")
  end
  set_sync(32, ind)
  snd_test_neq(sync(ind), 32, "sunc->32")
  if (res = sync_max()) < 32
    snd_display("sync_max 32: %s?", res)
  end
  set_sync(0, ind)
  set_channel_sync(12, ind, 0)
  snd_test_neq(channel_sync(ind, 0), 12, "channel_sunc->12")
  set_channel_sync(0, ind, 0)
  snd_test_neq(a_ctr, 0, "unbind_key")
  snd_test_neq(xp, 0.0, "x_position_slider")
  snd_test_neq(yp, 0.0, "y_position_slider")
  if fneq(xz, 0.04338) and fneq(xz, 1.0)
    snd_display("x_zoom_slider: %s?", xz)
  end
  snd_test_neq(yz, 1.0, "y_zoom_slider")
  if (fneq(bnds[0], 0.0) or fneq(bnds[1], 0.1)) and
      (fneq(bnds[0], 0.0) or fneq(bnds[1], 2.305))
    snd_display("x_bounds: %s?", bnds)
  end
  snd_test_neq(find_sound("oboe.snd"), ind, "oboe: index")
  [[:sound?, true],
    [:chans, 1],
    [:channels, 1],
    [:frames, 50828],
    [:srate, 22050],
    [:data_location, 28],
    [:data_size, 50828 * 2],
    [:data_format, Mus_bshort],
    [:maxamp, 0.14724],
    [:maxamp_position, 24971],
    [:comment, ""]].each do |func, val|
    snd_test_neq(snd_func(func, ind), val, "oboe")
  end
  snd_test_neq(short_file_name(ind), "oboe.snd", "oboe: short name")
  snd_test_neq(count_matches(lambda do |y| y > 0.125 end), 1314, "oboe: count_matches")
  snd_test_neq(count_matches(lambda do |y| y > 0.1 end), 2852, "oboe: unopt count_matches")
  spot = find_channel(lambda do |y| y > 0.13 end)
  if spot.kind_of?(FalseClass) or spot != 8862
    snd_display("find: %s?", spot)
  end
  set_right_sample(3000)
  if ((res = right_sample) - 3000).abs > 1
    snd_display("right_sample: %s?", res)
  end
  set_left_sample(1000)
  if ((res = left_sample) - 1000).abs > 1
    snd_display("left_sample: %s?", res)
  end
  eds = edits
  if eds[0].nonzero? or eds[1].nonzero?
    snd_display("edits: %s?", eds)
  end
  if edit_position != eds[0]
    snd_display("edit_position: %s %s?", edit_position, eds)
  end
  play(ind, :channel, 0, :wait, true)
  bomb(ind, false)
  select_all(ind, 0)
  r0 = regions.first
  unless selection?
    snd_display("selection?")
  end
  unless region?(r0)
    snd_display("region?")
  end
  snd_test_neq(selection_chans, 1, "selection_chans (1)")
  snd_test_neq(selection_srate, srate(ind), "selection_srate")
  snd_test_neq(region_maxamp(r0), maxamp(ind), "region_maxamp (1)")
  snd_test_neq(region_maxamp_position(r0), maxamp_position(ind), "region_maxamp_position (1)")
  snd_test_neq(selection_maxamp(ind, 0), maxamp(ind), "selection_maxamp (1)")
  snd_test_neq(selection_maxamp_position(ind, 0), maxamp_position(ind), "selection_maxamp_position (1)")
  save_region(r0, "temp.dat")
  if File.exists?("temp.dat")
    File.unlink("temp.dat")
  else
    snd_display("save_region file disappeared?")
  end
  play(r0, :wait, true)   # needs to be true here or it never gets run
  if Snd.regions.length != 1
    snd_display("regions: %s", regions)
  end
  unless (res = selection_member?(ind))
    snd_display("selection_member?: %s?", res)
  end
  [[:region_srate, 22050],
   [:region_chans, 1],
   [:region_frames, 50828],
   [:region_home, ["oboe.snd", 0, 50827]]].each do |func, req|
    snd_test_neq(snd_func(func, r0), req, "%s", func)
  end
  snd_test_neq(selection_frames, 50828, "selection_frames")
  snd_test_neq(selection_position, 0, "selection_position")
  snd_test_neq(region_position(r0, 0), 0, "region_position")
  snd_test_neq(region_maxamp(r0), maxamp(ind), "region_maxamp (2)")
  snd_test_neq(selection_maxamp(ind, 0), maxamp(ind), "selection_maxamp (2)")
  [[:region_srate, 22050],
   [:region_chans, 1],
   [:region_frames, 50828],
   [:region_maxamp, maxamp(ind)]].each do |func, req|
    snd_test_neq(snd_func(func, r0), req, "%s", func)
  end
  samps1 = channel2vct(0, 50827, ind, 0)
  samps2 = region2vct(r0, 0, 50828, 0)
  rd = make_sampler(0, ind, 0, 1)
  unless sampler?(rd)
    snd_display("%s not sampler?", rd)
  end
  snd_test_neq(sampler_position(rd), 0, "initial sampler_position")
  snd_test_neq(sampler_home(rd), [ind, 0], "sampler_home")
  if sampler_at_end?(rd)
    snd_display("%s init at end?", rd)
  end
  if (res = Snd.catch do region2vct(r0, -1, 1233) end).first != :no_such_sample
    snd_display("region2vct -1: %s", res.inspect)
  end
  if res = Snd.catch do region2vct(r0, 12345678, 1) end.first
    snd_display("region2vct 12345678: %s", res.inspect)
  end
  snd_test_neq(rd.to_s,
               "#<sampler: oboe.snd[0: 0] from 0, at 0, forward>",
               "sampler actually got %s", rd.to_s)
  erd = rd
  snd_test_neq(erd, rd, "sampler equal?")
  50827.times do |i|
    val = (i % 2).nonzero? ? next_sample(rd) : read_sample(rd)
    if val != samps1[i] or val != samps2[i]
      snd_display("readers disagree at %s (%s %s %s)", i, val, samps1[i], samps2[i])
      break
    end
  end
  free_sampler(rd)
  if (res = Snd.catch do make_sampler(0, ind, -1) end).first != :no_such_channel
    snd_display("make_sampler bad chan -1: %s?", res.inspect)
  end
  if (res = Snd.catch do make_sampler(0, ind, 1) end).first != :no_such_channel
    snd_display("make_sampler bad chan 1: %s?", res.inspect)
  end
  fd = make_sampler(0)
  if mix_sampler?(fd)
    snd_display("sampler: mix %s?", fd)
  end
  if region_sampler?(fd)
    snd_display("sampler: region %s?", fd)
  end
  unless sampler?(fd)
    snd_display("sampler: normal %s?", fd)
  end
  snd_test_neq(sampler_position(fd), 0, "sampler: position")
  free_sampler(fd)
  snd_test_neq(fd.to_s[-16, 16], "at eof or freed>", "freed sampler")
  reg = regions.first
  chns = region_chans(reg)
  if (res = Snd.catch do
        make_region_sampler(reg, 0, chans + 1)
      end).first != :no_such_channel
    snd_display("make_region_sampler bad chan (2): %s %s", res.inspect, regions)
  end
  if (res = Snd.catch do
        make_region_sampler(reg, 0, 0, -2)
      end).first != :no_such_direction
    snd_display("make_region_sampler bad dir (-2): %s", res.inspect)
  end
  revert_sound(ind)
  insert_sample(100, 0.5, ind)
  if (res = Snd.catch do insert_sound("oboe.snd", 0, 1) end).first != :no_such_channel
    snd_display("insert_sound bad chan (1): %s", res.inspect)
  end
  if (res = Snd.catch do insert_sample(-12, 1.0) end).first != :no_such_sample
    snd_display("insert_sample bad pos: %s", res.inspect)
  end
  set_show_axes(Show_no_axes, ind, 0)
  update_transform_graph(ind)
  update_time_graph(ind)
  snd_test_neq(sample(100), 0.5, "insert_sample (100)")
  snd_test_neq(frames(ind), 50829, "insert_sample (frames)")
  v0 = Array.new(3, 0.25)
  v1 = make_vct(3, 0.75)
  insert_samples(200, 3, v0, ind)
  insert_samples(300, 3, v1, ind)
  snd_test_neq(sample(201), 0.25, "insert_samples (201)")
  snd_test_neq(sample(301), 0.75, "insert_samples (301)")
  snd_test_neq(frames(ind), 50835, "insert_samples (frames)")
  save_sound_as("hiho.snd", ind, Mus_next, Mus_bshort, :srate, 22050)
  nind = view_sound("hiho.snd")
  snd_test_neq(sample(101, nind), sample(101, ind), "save_sound_as")
  unless read_only(nind)
    snd_display("read_only view_sound: %s?", read_only(nind))
  end
  set_speed_control_style(Speed_control_as_semitone, nind)
  snd_test_neq(speed_control_style(nind), Speed_control_as_semitone, "speed_control_style set semi")
  set_speed_control_tones(-8, nind)
  snd_test_neq(speed_control_tones(nind), 12, "speed_control_tones -8")
  set_speed_control_tones(18, nind)
  snd_test_neq(speed_control_tones(nind), 18, "speed_control_tones 18")
  graph2ps("aaa.eps")
  close_sound(nind)
  revert_sound(ind)
  # 
  set_sample(50, 0.5, ind)
  snd_test_neq(sample(50), 0.5, "set_sample")
  set_samples(60, 3, Array.new(3, 0.25), ind)
  snd_test_neq(sample(60), 0.25, "set_samples (60)")
  snd_test_neq(sample(61), 0.25, "set_samples (61)")
  set_samples(10, 3, [0.1, 0.2, 0.3], ind)
  snd_test_neq(channel2vct(10, 3, ind), vct(0.1, 0.2, 0.3), "set_samples via list")
  revert_sound(ind)
  save_sound_as("temporary.snd", ind)
  set_samples(100000, 20000, "temporary.snd", ind)
  snd_test_neq(channel2vct(110000, 10), channel2vct(10000, 10), "set_samples to self")
  revert_sound(ind)
  delete_file("temporary.snd")
  delete_sample(100, ind)
  snd_test_neq(frames(ind), 50827, "delete_sample")
  delete_samples(0, 100, ind)
  snd_test_neq(frames(ind), 50727, "delete_samples")
  revert_sound(ind)
  maxa = maxamp(ind)
  scale_to(0.5, ind)
  newmaxa = maxamp(ind)
  snd_test_neq(newmaxa, 0.5, "scale_to")
  undo_edit(1, ind)
  scale_by(2.0, ind)
  newmaxa = maxamp(ind)
  snd_test_neq(newmaxa, 2.0 * maxa, "scale_by")
  revert_sound(ind)
  scale_by(-1, ind)
  mix("oboe.snd")
  snd_test_neq(maxamp(ind, 0), 0.0, "invert+mix")
  revert_sound(ind)
  select_all(ind)
  if regions.length != 2
    snd_display("regions (2): %s", regions)
  end
  scale_selection_to(0.5)
  newmaxa = maxamp(ind)
  snd_test_neq(newmaxa, 0.5, "scale_selection_to")
  revert_sound(ind)
  select_all(ind)
  scale_selection_by(2.0)
  newmaxa = maxamp(ind)
  snd_test_neq(newmaxa, 2.0 * maxa, "scale_selection_by")
  revert_sound(ind)
  select_all(ind)
  rread = make_region_sampler(regions.first, 0)
  sread = make_sampler(0, ind)
  rvect = region2vct(regions.first, 0, 100)
  svect = samples(0, 100, ind)
  snd_test_neq(region_sample(regions.first, 1), rvect[1], "region_sample")
  100.times do |i|
    rval = next_sample(rread)
    sval = next_sample(sread)
    snd_test_neq(rval, sval, "sample_read")
    snd_test_neq(rval, rvect[i], "region_samples")
    snd_test_neq(sval, svect[i], "samples")
  end
  free_sampler(rread)
  val0 = next_sample(sread)
  if sampler_at_end?(sread)
    snd_display("premature end?")
  end
  previous_sample(sread)
  val1 = previous_sample(sread)
  snd_test_neq(val0, val1, "previous_sample")
  free_sampler(rread)
  revert_sound(ind)
  s100 = sample(100)
  s40 = sample(40)
  len = frames
  addlen = mus_sound_frames("fyow.snd")
  old_csize = cursor_size
  old_cstyle = cursor_style
  set_cursor_style(Cursor_line)
  set_cursor_size(25)
  set_cursor(50, ind)
  snd_test_neq(cursor_style, Cursor_line, "cursor_style")
  snd_test_neq(cursor_size, 25, "cursor_size")
  set_cursor_style(Cursor_cross)
  set_cursor_size(15)
  set_cursor(30, ind, 0)
  set_cursor_style(Cursor_line)
  set_cursor_size(20)
  set_cursor(20, ind, 0)
  if $with_test_gui
    set_cursor_style(lambda do |snd, chn, ax|
                       x, y = cursor_position
                       size = (cursor_size / 2.0).round
                       cr = make_cairo(channel_widgets(snd, chn)[0])
                       draw_line(x - size, y - size, x + size, y + size, snd, chn, Cursor_context, cr)
                       draw_line(x - size, y + size, x + size, y - size, snd, chn, Cursor_context, cr)
                       free_cairo(cr)
                     end,
                     ind, 0)
    unless proc?(res = cursor_style(ind, 0))
      snd_display("set_cursor_style to Proc: %s", res)
    end
  end
  set_cursor_size(old_csize)
  set_cursor_style(old_cstyle)
  set_cursor(50, ind)
  insert_sound("fyow.snd", cursor, 0, ind, 0)
  ss100 = sample(100)
  snd_test_neq(sample(40), s40, "insert_sound s40")
  snd_test_eq(ss100, s100, "insert_sound s100")
  snd_test_neq(ss100, 0.001831, "insert_sound")
  snd_test_neq(frames, addlen + len, "insert_sound len")
  save_sound_as("not-temporary.snd")
  insert_samples(0, 100, "not-temporary.snd")
  set_cursor(frames(ind, 0, 0) - 2, ind, 0, 0)
  revert_sound
  snd_test_neq(cursor(ind, 0), frames(ind, 0, 0) - 2, "set edpos cursor %s", cursor)
  delete_file("not-temporary.snd")
  id = make_region(0, 99)
  insert_region(id, 60, ind)
  snd_test_neq(frames, len + 100, "insert_region len")
  snd_test_neq(sample(100), s40, "insert_region")
  if (res = Snd.catch do
        regmax = if regions
                   regions.map do |r| region2integer(r) end.max + 1000
                 else
                   1000
                 end
        insert_region(integer2region(regmax), 0)
      end).first != :no_such_region
    snd_display("insert_region bad id: %s", res.inspect)
  end
  save_region(id, "fmv.snd")
  region_srate(id)
  region_chans(id)
  region_frames(id)
  [[:mus_sound_header_type, Mus_next],
   [:mus_sound_data_format, Mus_out_format],
   [:mus_sound_srate, region_srate(id)],
   [:mus_sound_chans, region_chans(id)],
   [:mus_sound_frames, region_frames(id)]].each do |func, req|
    snd_test_neq(snd_func(func, "fmv.snd"), req, "save_region (1) %s", func)
  end
  if region_position(id, 0).nonzero?
    snd_display("save_region position: %s", region_position(id, 0))
  end
  delete_file("fmv.snd")
  save_region(id, "fmv.snd", Mus_riff, Mus_lshort, "this is a comment")
  [[:mus_sound_header_type, Mus_riff],
   [:mus_sound_data_format, Mus_lshort],
   [:mus_sound_comment, "this is a comment"],
   [:mus_sound_frames, region_frames(id)]].each do |func, req|
    snd_test_neq(snd_func(func, "fmv.snd"), req, "save_region (2) %s", func)
  end
  delete_file("fmv.snd")
  save_region(id,
              :file, "fmv.snd",
              :header_type, Mus_riff,
              :data_format, Mus_lshort,
              :comment, "this is a comment")
  [[:mus_sound_header_type, Mus_riff],
   [:mus_sound_data_format, Mus_lshort],
   [:mus_sound_comment, "this is a comment"],
   [:mus_sound_frames, region_frames(id)]].each do |func, req|
    snd_test_neq(snd_func(func, "fmv.snd"), req, "save_region opt (3) %s", func)
  end
  delete_file("fmv.snd")
  save_region(id,
              :comment, "this is a comment",
              :file, "fmv.snd",
              :data_format, Mus_lshort,
              :header_type, Mus_riff)
  [[:mus_sound_header_type, Mus_riff],
   [:mus_sound_data_format, Mus_lshort],
   [:mus_sound_comment, "this is a comment"],
   [:mus_sound_frames, region_frames(id)]].each do |func, req|
    snd_test_neq(snd_func(func, "fmv.snd"), req, "save_region opt1 (4) %s", func)
  end
  delete_file("fmv.snd")
  save_region(id, "fmv.snd", :data_format, Mus_bshort)
  [[:mus_sound_header_type, Mus_next],
   [:mus_sound_data_format, Mus_bshort],
   [:mus_sound_frames, region_frames(id)]].each do |func, req|
    snd_test_neq(snd_func(func, "fmv.snd"), req, "save_region opt2 (5) %s", func)
  end
  delete_files("fmv.snd", "aaa.eps")
  close_sound(ind)
end

def test_05_11
  if (res = Snd.catch do new_sound("hi.snd", 0, 1, 100, 0) end).first != :out_of_range
    snd_display("new_sound bad chan: %s?", res)
  end
  # 
  ind = new_sound("fmv.snd", Mus_next, Mus_bshort, 22050, 2, "unequal lens")
  insert_silence(0, 1000, ind, 1)
  if (res1 = frames(ind, 0)) != 1 or (res2 = frames(ind, 1)) != 1001
    snd_display("silence 1: %s %s?", res1, res2)
  end
  save_sound(ind)
  if (res1 = frames(ind, 0)) != 1001 or (res2 = frames(ind, 1)) != 1001
    snd_display("saved silence 1: %s %s?", res1, res2)
  end
  if (res = mus_sound_frames("fmv.snd")) != 1001
    snd_display("saved frames silence 1: %s?", res)
  end
  v0 = channel2vct(0, 1000, ind, 0)
  v1 = channel2vct(0, 1000, ind, 1)
  if fneq(vct_peak(v0), 0.0)
    snd_display("auto-pad 0: %s?", vct_peak(v0))
  end
  if fneq(vct_peak(v1), 0.0)
    snd_display("silence 0: %s?", vct_peak(v1))
  end
  close_sound(ind)
  delete_file("fmv.snd")
  # 
  ind = new_sound("fmv.snd", Mus_next, Mus_bshort, 22050, 2, "unequal lens")
  pad_channel(0, 1000, ind, 1)
  if (res1 = frames(ind, 0)) != 1 or (res2 = frames(ind, 1)) != 1001
    snd_display("silence: %s %s?", res1, res2)
  end
  v0 = channel2vct(0, 1000, ind, 0)
  v1 = channel2vct(0, 1000, ind, 1)
  if fneq(vct_peak(v0), 0.0)
    snd_display("pad 0: %s?", vct_peak(v0))
  end
  if fneq(vct_peak(v1), 0.0)
    snd_display("pad 1: %s?", vct_peak(v1))
  end
  map_channel($init_channel, 0, 2, ind, 0)
  map_channel($init_channel, 0, 1002, ind, 1)
  pad_channel(0, 1000, ind, 0, 1)
  if (res = frames(ind, 1)) != 1002
    snd_display("pad_channel ed 1: %s?", res)
  end
  close_sound(ind)
  delete_file("fmv.snd")
  # 
  ind = new_sound("fmv.snd", Mus_ircam, Mus_bshort, 22050, 1, "this is a comment")
  v0 = make_vct(128)
  v0[64] = 0.5
  v0[127] = 0.5
  vct2samples(0, 128, v0, ind, 0)
  make_selection(0, 126)
  smooth_selection
  v0 = channel2vct(0, 128, ind, 0)
  if fneq(sample(127), 0.5) or fneq(sample(120), 0.4962) or
      fneq(sample(32), 0.07431) or fneq(sample(64), 0.25308)
    snd_display("smooth_selection: %s?", v0)
  end
  revert_sound(ind)
  vct_fill!(v0, 0.0)
  v0[10] = 0.5
  vct2channel(v0)
  select_all
  old_sw = sinc_width
  set_sinc_width(40)
  src_selection(0.5)
  v0 = channel2vct(0, 128, ind, 0)
  if fneq(sample(20), 0.5) or fneq(sample(30), 0.0) or fneq(sample(17), -0.1057)
    snd_display("src_selection: %s?", v0)
  end
  unselect_all
  if selection_member?
    snd_display("unselect_all but still a selection?")
  end
  unselect_all
  revert_sound(ind)
  set_sinc_width(old_sw)
  vct_fill!(v0, 0.0)
  v0[10] = 0.5
  vct2channel(v0, 0)
  select_all
  filter_selection([0, 0, 0.1, 1, 1, 0], 40)
  v0 = channel2vct(0, 128, ind, 0)
  if fneq(sample(29), 0.1945) or fneq(sample(39), -0.0137) or fneq(sample(24), -0.01986)
    snd_display("filter_selection: %s?", v0)
  end
  revert_sound(ind)
  vct_fill!(v0, 1.0)
  vct2channel(v0)
  select_all
  filter_selection(make_one_zero(:a0, 0.5, :a1, 0.0))
  v0 = channel2vct(0, 128, ind, 0)
  if fneq(sample(29), 0.5) or fneq(sample(39), 0.5) or fneq(sample(24), 0.5)
    snd_display("filter_selection one_zero: %s?", v0)
  end
  revert_sound(ind)
  vct_fill!(v0, 1.0)
  vct2channel(v0)
  delete_file("fmv5.snd")
  select_all
  env_selection([0, 0, 1, 1, 2, 0], 1.0)
  v0 = channel2vct(0, 128, ind, 0)
  if fneq(sample(64), 1.0) or fneq(sample(20), 0.3125) or fneq(sample(119), 0.127)
    snd_display("env_selection: %s?", v0)
  end
  save_selection("fmv5.snd", Mus_next, Mus_bint, 22050, "")
  revert_sound(ind)
  # 
  if (res = Snd.catch do file2array("/baddy/hiho", 0, 0, 128, v0) end).first != :no_such_file
    snd_display("file2array w/o file: %s", res.inspect)
  end
  if (res = Snd.catch do file2array("fmv5.snd", 123, 0, 128, v0) end).first != :no_such_channel
    snd_display("file2array w/o channel: %s", res.inspect)
  end
  file2array("fmv5.snd", 0, 0, 128, v0)
  if fneq(v0[64], 1.0) or fneq(v0[20], 0.3125) or fneq(v0[119], 0.127)
    snd_display("save_selection: %s %s %s %s?", v0[64], v0[20], v0[119], v0)
  end
  if (res = mus_sound_data_format("fmv5.snd")) != Mus_bint
    snd_display("save_selection type: %s?", mus_data_format_name(res))
  end
  if (res = mus_sound_header_type("fmv5.snd")) != Mus_next
    snd_display("save_selection format: %s?", mus_header_type_name(res))
  end
  if (res = mus_sound_srate("fmv5.snd")) != 22050
    snd_display("save_selection srate: %s?", res)
  end
  vct_fill!(v0, 0.0)
  v0[100] = 0.5
  v0[2] = -0.5
  vct2channel(v0)
  select_all
  Snd.catch do reverse_selection end
  save_selection("fmv4.snd", Mus_riff, Mus_lfloat, 44100, "this is a comment")
  v0 = channel2vct(0, 128, ind, 0)
  if fneq(sample(27), 0.5) or fneq(sample(125), -0.5)
    snd_display("reverse_selection: %s?", v0)
  end
  file2array("fmv4.snd", 0, 0, 128, v0)
  if fneq(sample(27), 0.5) or fneq(sample(125), -0.5)
    snd_display("save reverse_selection: %s?", v0)
  end
  if (res = mus_sound_header_type("fmv4.snd")) != Mus_riff
    snd_display("save_selection type 1: %s", mus_header_type_name(res))
  end
  if (res = mus_sound_data_format("fmv4.snd")) != Mus_lfloat
    snd_display("save_selection format 1: %s", mus_data_format_name(res))
  end
  if (res = mus_sound_srate("fmv4.snd")) != 44100
    snd_display("save_selection srate 1: %s", res)
  end
  if (res = mus_sound_comment("fmv4.snd")) != "this is a comment"
    snd_display("save_selection comment: %s", res)
  end
  delete_file("fmv4.snd")
  # 
  save_selection(:file, "fmv4.snd",
                 :header_type, Mus_riff,
                 :data_format, Mus_lfloat,
                 :srate, 44100,
                 :comment, "this is a comment")
  if (res = mus_sound_header_type("fmv4.snd")) != Mus_riff
    snd_display("save_selection opt type 1: %s", mus_header_type_name(res))
  end
  if (res = mus_sound_data_format("fmv4.snd")) != Mus_lfloat
    snd_display("save_selection opt format 1: %s", mus_data_format_name(res))
  end
  if (res = mus_sound_srate("fmv4.snd")) != 44100
    snd_display("save_selection opt srate 1: %s", res)
  end
  if (res = mus_sound_comment("fmv4.snd")) != "this is a comment"
    snd_display("save_selection opt comment: %s", res)
  end
  delete_file("fmv4.snd")
  # 
  save_selection(:file, "fmv4.snd", :data_format, Mus_bfloat, :channel, 0)
  res = mus_sound_header_type("fmv4.snd")
  if res != Mus_next and res != Mus_ircam
    snd_display("save_selection opt1 type 1: %s", mus_header_type_name(res))
  end
  if (res = mus_sound_data_format("fmv4.snd")) != Mus_bfloat
    snd_display("save_selection opt1 format 1: %s", mus_data_format_name(res))
  end
  if (res = mus_sound_chans("fmv4.snd")) != 1
    snd_display("save_selection opt1 chans: %s", res)
  end
  delete_file("fmv4.snd")
  revert_sound(ind)
  vct_fill!(v0, 0.0)
  v0[2] = 1.0
  v1 = make_vct(256)
  128.times do |i| v1[i] = v0[i] end
  vct2channel(v1)
  select_all
  if mus_clipping
    set_mus_clipping(false)
  end
  if clipping
    set_clipping(false)
  end
  convolve_selection_with("fmv5.snd", 0.5)
  v0 = channel2vct(0, 128, ind, 0)
  if fneq(sample(66), -0.5)
    snd_display("convolve_selection_with: %s %s %s?", v0[66], sample(66), v0)
  end
  close_sound(ind)
  delete_file("fmv.snd")
end

def test_05_12
  obind = open_sound("oboe.snd")
  vol = maxamp(obind)
  dur = frames
  set_amp_control(2.0, obind)
  snd_test_neq(amp_control(obind), 2.0, "set_amp_control")
  reset_controls(obind)
  snd_test_neq(amp_control(obind), 1.0, "set_amp_control")
  set_amp_control_bounds([0.0, 4.0], obind)
  snd_test_neq(amp_control_bounds(obind), [0.0, 4.0], "amp_control_bounds")
  set_amp_control(2.0, obind)
  if (res = Snd.catch do apply_controls(obind) end).first == :no_such_sound
    snd_display("apply_controls: cannot find oboe.snd? %s", res.inspect)
  end
  newamp = maxamp(obind)
  if fneq_err(2.0 * vol, newamp, 0.05)
    snd_display("apply amp: %s -> %s?", vol, newamp)
  end
  set_amp_control_bounds([0.0, 8.0], obind)
  set_speed_control_bounds([1.0, 5.0], obind)
  snd_test_neq(speed_control_bounds(obind), [1.0, 5.0], "speed_control_bounds")
  set_speed_control(0.5, obind)
  set_speed_control_bounds([0.05, 20.0], obind)
  add_mark(1234)
  apply_controls(obind)
  newdur = frames(obind)
  set_speed_control(1.0, obind)
  unless newdur - 2.0 * dur < 256
    snd_display("apply speed: %s -> %s?", dur, newdur)
  end
  set_contrast_control?(true, obind)
  set_contrast_control_bounds([0.5, 2.5], obind)
  snd_test_neq(contrast_control_bounds(obind), [0.5, 2.5], "contrast_control_bounds")
  set_contrast_control(1.0, obind)
  apply_controls(obind)
  set_contrast_control_bounds([0.0, 10.0], obind)
  snd_test_neq(contrast_control_bounds(obind), [0.0, 10.0], "contrast_control_bounds (2)")
  secamp = maxamp(obind)
  secdur = frames(obind)
  snd_test_neq(secamp, 0.989, "apply contrast")
  snd_test_neq(secdur, newdur, "apply contrast length")
  undo_edit(3, obind)
  set_reverb_control?(true, obind)
  set_reverb_control_scale_bounds([0.0, 1.0], obind)
  snd_test_neq(reverb_control_scale_bounds(obind), [0.0, 1.0], "reverb_control_scale_bounds")
  set_reverb_control_length_bounds([0.0, 2.0], obind)
  snd_test_neq(reverb_control_length_bounds(obind), [0.0, 2.0], "reverb_control_length_bounds")
  set_reverb_control_scale(0.2, obind)
  apply_controls(obind)
  revamp = maxamp(obind)
  revdur = frames(obind)
  snd_test_any_neq(revamp, 0.214, :ffequal?, "apply reverb scale") # okay
  unless revdur - ((reverb_control_decay * 22050.0).round + 50828) < 256
    snd_display("apply reverb length: %s?", revdur)
  end
  undo_edit(1, obind)
  set_expand_control?(true, obind)
  set_expand_control_bounds([1.0, 3.0], obind)
  snd_test_neq(expand_control_bounds(obind), [1.0, 3.0], "expand_control_bounds")
  set_expand_control(1.5, obind)
  apply_controls(obind)
  expamp = maxamp(obind)
  expdur = frames(obind)
  if fneq_err(expamp, 0.152, 0.05)
    snd_display("apply expand_control scale: %s?", expamp)
  end
  if expdur <= 1.25 * 50828
    snd_display("apply expand_control length: %s?", expdur)
  end
  set_expand_control_bounds([0.001, 20.0], obind)
  undo_edit(1, obind)
  set_filter_control?(true, obind)
  set_filter_control_order(40, obind)
  set_filter_control_envelope([0.0, 0.0, 1.0, 0.5, 2.0, 0.0], obind)
  apply_controls(obind)
  fltamp = maxamp(obind)
  fltdur = frames(obind)
  # FIXME (INFO)
  # from command line: => 0.054348257295914 (snd-s7|ruby|forth)
  #     from listener: => less than 0.025
  if (fltamp - 0.02).abs > 0.005
    snd_display(snd_format(fltamp, 0.025, ">", "apply filter scale"))
  end
  req = 40 + 50828 + 256
  if fltdur - (40 + 50828) > 256
    snd_display(snd_format(fltdur, req, ">", "apply filter length"))
  end
  undo_edit(1, obind)
  # 
  revert_sound(obind)
  make_selection(1000, 1000)
  scale_selection_to(0.1)
  scale_selection_by(2.0)
  make_selection(2000, 2001)
  scale_selection_by(2.0)
  scale_selection_to(0.5)
  make_selection(1000, 2001)
  scale_selection_to(0.5)
  scale_selection_by(0.5)
  make_selection(2000, 2000)
  scale_selection_by(2.0)
  scale_selection_to(0.5)
  make_selection(1000, 1001)
  scale_selection_to(0.1)
  scale_selection_by(2.0)
  make_selection(999, 2002)
  scale_selection_to(1.0)
  scale_selection_by(0.5)
  tree = edit_tree
  tr_tree = [[0, 0, 0, 998, 1.0, 0.0, 0.0, 0],
             [999, 0, 999, 999, 0.999969720840454, 0.0, 0.0, 0],
             [1000, 0, 1000, 1000, 6.09052181243896, 0.0, 0.0, 0],
             [1001, 0, 1001, 1001, 0.999969720840454, 0.0, 0.0, 0],
             [1002, 0, 1002, 1999, 0.499984979629517, 0.0, 0.0, 0],
             [2000, 0, 2000, 2000, 7.54652404785156, 0.0, 0.0, 0],
             [2001, 0, 2001, 2001, 3.7732629776001, 0.0, 0.0, 0],
             [2002, 0, 2002, 2002, 0.999969720840454, 0.0, 0.0, 0],
             [2003, 0, 2003, 50827, 1.0, 0.0, 0.0, 0],
             [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]]
  if tree.length != tr_tree.length
    snd_display(snd_format_neq(tree.length, tr_tree.length, "edit trees are not same length"))
  else
    tree.each_with_index do |branch, i|
      tr_branch = tr_tree[i]
      5.times do |j|
        snd_test_neq(branch[j], tr_branch[j], "edit trees disagree at [%d][%d]", i, j)
      end
    end
  end
  insert_silence(1001, 8)
  insert_silence(900, 50)
  insert_silence(2005, 1)
  insert_silence(999, 2)
  tree = edit_tree
  tr_tree = [[0, 0, 0, 899, 1.0, 0.0, 0.0, 0],
             [900, -1, 0, 49, 0.0, 0.0, 0.0, 0],
             [950, 0, 900, 948, 1.0, 0.0, 0.0, 0],
             [999, -1, 0, 1, 0.0, 0.0, 0.0, 0],
             [1001, 0, 949, 998, 1.0, 0.0, 0.0, 0],
             [1051, 0, 999, 999, 0.999969720840454, 0.0, 0.0, 0],
             [1052, 0, 1000, 1000, 6.09052181243896, 0.0, 0.0, 0],
             [1053, -1, 0, 7, 0.0, 0.0, 0.0, 0],
             [1061, 0, 1001, 1001, 0.999969720840454, 0.0, 0.0, 0],
             [1062, 0, 1002, 1946, 0.499984979629517, 0.0, 0.0, 0],
             [2007, -1, 0, 0, 0.0, 0.0, 0.0, 0],
             [2008, 0, 1947, 1999, 0.499984979629517, 0.0, 0.0, 0],
             [2061, 0, 2000, 2000, 7.54652404785156, 0.0, 0.0, 0],
             [2062, 0, 2001, 2001, 3.7732629776001, 0.0, 0.0, 0],
             [2063, 0, 2002, 2002, 0.999969720840454, 0.0, 0.0, 0],
             [2064, 0, 2003, 50827, 1.0, 0.0, 0.0, 0],
             [50889, -2, 0, 0, 0.0, 0.0, 0.0, 0]]
  if tree.length != tr_tree.length
    snd_display(snd_format_neq(tree.length, tr_tree.length,
                               "silenced edit trees are not same length"))
  else
    tree.each_with_index do |branch, i|
      tr_branch = tr_tree[i]
      5.times do |j|
        snd_test_neq(branch[j], tr_branch[j], "silenced edit trees disagree at [%d][%d]", i, j)
      end
    end
  end
  snd_test_neq(vct(sample(998), sample(999), sample(1000), sample(1001)),
               vct(-0.03, 0.0, 0.0, -0.03),
               "insert_silence [999 for 2]")
  snd_test_neq(vct(sample(2006), sample(2007), sample(2008)),
               vct(-0.033, 0.0, -0.033),
               "insert_silence [2007 for 1]")
  revert_sound(obind)
  add_mark(1200, obind, 0)
  mark_num = marks(obind, 0).length
  scale_by(2.0, obind, 0)
  mark_now = marks(obind, 0).length
  snd_test_neq(mark_num, mark_now, "mark lost after scaling")
  set_selection_position(0)
  set_selection_frames(100)
  scale_selection_to(0.5)
  mark_now = marks(obind, 0).length
  snd_test_neq(mark_num, mark_now, "mark lost after scaling scaling")
  m1 = add_mark(1000)
  set_cursor(100, obind, 0)
  key(key_to_int(?u), 4, obind)
  key(key_to_int(?1), 0, obind)
  key(key_to_int(?0), 0, obind)
  key(key_to_int(?0), 0, obind)
  key(key_to_int(?o), 4, obind)
  snd_test_neq(mark_sample(m1), 1100, "mark after zeros")
  set_cursor(0, obind)
  key(key_to_int(?j), 4, obind)
  snd_test_neq(cursor(obind), 1100, "C-j")
  add_mark(100)
  set_cursor(0, obind)
  key(key_to_int(?u), 4, obind)
  key(key_to_int(?2), 0, obind)
  key(key_to_int(?j), 4, obind)
  snd_test_neq(cursor(obind), 1100, "C-u 2 C-j")
  key(key_to_int(?-), 4, obind)
  key(key_to_int(?j), 4, obind)
  snd_test_neq(cursor(obind), 100, "C-- C-j")
  revert_sound(obind)
  frs = frames(obind)
  make_region(0, 999, obind, 0)
  unless selection?
    snd_display("make_region but no selection? %s", selection?)
  end
  delete_selection
  snd_test_neq(frames(obind), frs - 1000, "delete_selection")
  val = sample(0, obind, 0)
  undo_edit
  snd_test_neq(sample(1000), val, "delete_selection val")
  insert_selection
  if (res = Snd.catch do insert_selection(0, obind, 123) end).first != :no_such_channel
    snd_display("insert_selection bad chan: %s?", res.inspect)
  end
  if (res = Snd.catch do mix_selection(0, obind, 123) end).first != :no_such_channel
    snd_display("mix_selection bad chan: %s?", res.inspect)
  end
  snd_test_neq(frames(obind), frs + 1000, "insert_selection")
  snd_test_neq(sample(2000), val, "insert_selection val")
  val = sample(900)
  mix_selection
  snd_test_neq(sample(900), val * 2.0, "mix_selection val")
  snd_test_neq(frames(obind), frs + 1000, "mix_selection len")
  close_sound(obind)
end

Apply_to_sound, Apply_to_channel, Apply_to_selection = [0, 1, 2]

def test_05_13
  ind = open_sound("2.snd")
  len = frames(ind)
  set_sync(1, ind)
  set_speed_control(0.5, ind)
  apply_controls(ind, Apply_to_sound)             # temp 1
  if (frames - 2 * len).abs > 256
    snd_display("apply srate 0.5: %s %s?", frames, len * 2)
  end
  make_selection(0, frames)
  set_speed_control(0.5, ind)
  apply_controls(ind, Apply_to_selection)         # temp 2
  if (frames - 4 * len).abs > 256
    snd_display("apply srate 0.5 to selection: %s %s?", frames, len * 4)
  end
  env_sound([0, 0, 1, 1], 0, frames, 32.0)        # temp 3
  reg = select_all                                # make multi_channel region
  insert_region(reg, 0)                           # temp 4
  insert_selection(0)                             # temp 5
  revert_sound(ind)
  set_speed_control(0.5)
  set_sync(0, ind)
  set_selected_channel(ind, 1)
  apply_controls(ind, Apply_to_channel)
  if (frames(ind, 1) - 2 * len).abs > 256
    snd_display("apply srate 0.5 to chan 1: %s %s?", frames(ind, 1), len * 2)
  end
  if frames(ind, 0) != len
    snd_display("apply srate 0.5 but chan 0: %s %s?", frames(ind, 0), len)
  end
  set_speed_control(0.5, ind)
  apply_controls(ind, Apply_to_sound, 1000)
  make_selection(2000, 4000)
  set_speed_control(0.5, ind)
  apply_controls(ind, Apply_to_selection)
  set_selected_channel(ind, false)
  if selected_channel(ind)
    snd_display("selected_channel false: %s?", selected_channel(ind))
  end
  close_sound(ind)
  #
  ind1 = open_sound("oboe.snd")
  mx1 = maxamp(ind1, 0)
  ind2 = open_sound("2.snd")
  mx20 = maxamp(ind2, 0)
  mx21 = maxamp(ind2, 1)
  select_sound(ind1)
  scale_sound_by(2.0)
  if fneq(res = maxamp(ind1, 0), 2.0 * mx1)
    snd_display("scale_sound_by 2.0: %s %s?", mx1, res)
  end
  if (res1 = edit_fragment(1, ind1, 0)) !=
      (res2 = ["scale_channel(2.000, 0, false", "scale", 0, 50828])
    snd_display("scale_sound_by:\n# %s\n# %s", res1, res2)
  end
  scale_sound_to(0.5)
  if fneq(res = maxamp(ind1, 0), 0.5)
    snd_display("scale_sound_to 0.5: %s?", res)
  end
  if (res1 = edit_fragment(2, ind1, 0)) !=
      (res2 = ["scale_channel(1.698, 0, false", "scale", 0, 50828])
    snd_display("scale_sound_to:\n# %s\n# %s", res1, res2)
  end
  scale_sound_by(0.0, 0, 1000, ind1, 0)
  if fneq(res = maxamp(ind1, 0), 0.5)
    snd_display("scale_sound_by 0.0: %s?", res)
  end
  if (res1 = edit_fragment(3, ind1, 0)) !=
      (res2 = ["scale_channel(0.000, 0, 1000", "scale", 0, 1000])
    snd_display("scale_sound_by 0.0:\n# %s\n# %s", res1, res2)
  end
  v = channel2vct(0, 1000, ind1, 0)
  if fneq(res = vct_peak(v), 0.0)
    snd_display("scale_sound_by 0.0 [0:1000]: %s", res)
  end
  revert_sound(ind1)
  oldv = channel2vct(12000, 10, ind1, 0)
  scale_sound_by(2.0, 12000, 10, ind1, 0)
  newv = channel2vct(12000, 10, ind1, 0)
  10.times do |i|
    if fneq(res1 = oldv[i] * 2.0, res2 = newv[i])
      snd_display("scale %s: %s %s?", i, res1, res2)
    end
  end
  if (res1 = edit_fragment(1, ind1, 0)) !=
      (res2 = ["scale_channel(2.000, 12000, 10", "scale", 12000, 10])
    snd_display("scale_sound_by 2.0 [12000:10]:\n# %s\n# %s", res1, res2)
  end
  revert_sound(ind1)
  # 
  select_sound(ind2)
  scale_sound_by(2.0)
  if fneq(res = maxamp(ind2, 0), 2.0 * mx20)
    snd_display("2:0 scale_sound_by 2.0: %s %s?", mx20, res)
  end
  if fneq(res = maxamp(ind2, 1), 2.0 * mx21)
    snd_display("2:1 scale_sound_by 2.0: %s %s?", mx21, res)
  end
  if (res1 = edit_fragment(1, ind2, 0)) !=
      (res2 = ["scale_channel(2.000, 0, false", "scale", 0, 50828])
    snd_display("2 scale_sound_by:\n# %s\n# %s", res1, res2)
  end
  scale_sound_to(0.5)
  if fneq(res = [maxamp(ind2, 0), maxamp(ind2, 1)].max, 0.5)
    snd_display("2 scale_sound_to 0.5: %s %s?", res, maxamp(ind2))
  end
  scale_sound_by(0.0, 0, 1000, ind2, 1)
  if fneq(res = maxamp(ind2, 0), 0.5)
    snd_display("2 scale_sound_by 0.0: %s?", res)
  end
  if (res1 = edit_fragment(3, ind2, 1)) !=
      (res2 = ["scale_channel(0.000, 0, 1000", "scale", 0, 1000])
    snd_display("2:1 scale_sound_by 0.0:\n# %s\n# %s", res1, res2)
  end
  v = channel2vct(0, 1000, ind2, 1)
  if fneq(res = vct_peak(v), 0.0)
    snd_display("2:1 scale_sound_by 0.0 [0:1000]: %s", res)
  end
  revert_sound(ind2)
  oldv = channel2vct(12000, 10, ind2, 0)
  scale_sound_by(2.0, 12000, 10, ind2, 0)
  newv = channel2vct(12000, 10, ind2, 0)
  10.times do |i|
    if fneq(res1 = oldv[i] * 2.0, res2 = newv[i])
      snd_display("2 scale %s: %s %s?", i, res1, res2)
    end
  end
  revert_sound(ind2)
  #
  set_sync(3, ind2)
  set_sync(3, ind1)
  scale_sound_by(2.0)
  if fneq(res = maxamp(ind1, 0), mx1)
    snd_display("sync scale_sound_by 2.0: %s %s?", mx1, res)
  end
  if fneq(res = maxamp(ind2, 0), 2.0 * mx20)
    snd_display("2:0 sync scale_sound_by 2.0: %s %s?", mx20, res)
  end
  if fneq(res = maxamp(ind2, 1), 2.0 * mx21)
    snd_display("2:1 sync scale_sound_by 2.0: %s %s?", mx21, res)
  end
  scale_sound_to(1.0, 20000, 40000, ind2, 1)
  if fneq(res = maxamp(ind1, 0), mx1)
    snd_display("sync scale_sound_to 1.0: %s %s?", mx1, res)
  end
  if fneq(res = maxamp(ind2, 0), 2.0 * mx20)
    snd_display("2:0 sync scale_sound_to 1.0: %s %s?", mx20, res)
  end
  if fneq(res = maxamp(ind2, 1), 1.0)
    snd_display("2:1 sync scale_sound_to 1.0: %s?", res)
  end
  close_sound(ind1)
  close_sound(ind2)
end

def test_05_14
  ind = open_sound("now.snd")
  unless $snd_opened_sound.eql?(ind)
    snd_display("$snd_opened_sound: %s %s?", $snd_opened_sound, ind)
  end
  set_amp_control(0.5, ind)
  if fneq(res = amp_control(ind), 0.5)
    snd_display("amp_control (0.5): %s?", res)
  end
  set_amp_control(0.25, ind, 0)
  if fneq(res = amp_control(ind), 0.5)
    snd_display("amp_control after local set (0.5): %s?", res)
  end
  if fneq(res = amp_control(ind, 0), 0.25)
    snd_display("amp_control 0 (0.25): %s?", res)
  end
  set_amp_control(1.0, ind)
  if fneq(res = amp_control(ind), 1.0)
    snd_display("amp_control after local set (1.0): %s?", res)
  end
  if fneq(res = amp_control(ind, 0), 0.25)
    snd_display("amp_control 0 after set (0.25): %s?", res)
  end
  #
  set_transform_graph?(true, ind, 0)
  set_transform_graph_type(Graph_as_sonogram, ind, 0)
  update_transform_graph(ind, 0)
  res = transform_frames(ind, 0)
  if (not list?(res)) or fneq(res.car, 1.0) or res.caddr != 256
    snd_display("transform_frames: %s (%s)?", res.inspect, transform_size(ind, 0))
  end
  close_sound(ind)
  #
  ind = open_sound("4.aiff")
  if fneq(res = amp_control(ind), 1.0)
    snd_display("amp_control upon open (1.0): %s?", res)
  end
  if fneq(res = amp_control(ind, 2), 1.0)
    snd_display("amp_control 2 upon open (1.0): %s?", res)
  end
  set_amp_control(0.5, ind)
  if fneq(res = amp_control(ind, 2), 0.5)
    snd_display("amp_control 2 after global set (0.5): %s?", res)
  end
  set_amp_control(0.25, ind, 2)
  if fneq(res = amp_control(ind, 2), 0.25)
    snd_display("amp_control 2 (0.25): %s?", res)
  end
  after_ran = false
  $after_apply_controls_hook.reset_hook!
  $after_apply_controls_hook.add_hook!("snd-test") do |snd| after_ran = snd end
  apply_controls(ind)
  unless ind.eql?(after_ran)
    snd_display("$after_apply_controls_hook: %s?", after_ran)
  end
  $after_apply_controls_hook.reset_hook!
  revert_sound(ind)
  set_sync(1, ind)
  scale_to(vct(0.1, 0.2))
  mx = maxamp(ind, true)
  if fneq(mx[0], 0.1) or fneq(mx[1], 0.2) or fneq(mx[2], 0.2) or fneq(mx[3], 0.2)
    snd_display("scale_to with vector: %s?", mx)
  end
  set_filter_control_envelope([0, 0, 1, 1], ind)
  if [0.0, 0.0, 1.0, 1.0] != filter_control_envelope(ind)
    snd_display("set_filter_control_envelope: %s?", filter_control_envelope(ind))
  end
  set_filter_control_order(20, ind)
  unless vequal(res = filter_control_coeffs(ind),
                vct(-0.007, 0.010, -0.025, 0.029, -0.050, 0.055, -0.096, 0.109, -0.268, 0.241,
                    0.241, -0.268, 0.109, -0.096, 0.055, -0.050, 0.029, -0.025, 0.010, -0.007))
    snd_display("highpass coeffs: %s?", res)
  end
  set_filter_control_envelope(filter_control_envelope(ind), ind)
  if [0.0, 0.0, 1.0, 1.0] != filter_control_envelope(ind)
    snd_display("set_filter_control_envelope to self: %s?", filter_control_envelope(ind))
  end
  set_filter_control_envelope([0, 1, 1, 0], ind)
  unless vequal(res = filter_control_coeffs(ind),
                vct(0.003, 0.002, 0.004, 0.002, 0.007, 0.003, 0.014, 0.012, 0.059, 0.394,
                    0.394, 0.059, 0.012, 0.014, 0.003, 0.007, 0.002, 0.004, 0.002, 0.003))
    snd_display("lowpass coeffs: %s?", res)
  end
  close_sound(ind)
end

def test_05_15
  obind = open_sound("4.aiff")
  amps = maxamp(obind, true)
  snd_test_neq(maxamp_position(obind, true), [810071, 810071, 810071, 810071], "4.aiff times")
  if window_width < 600
    set_window_width(600)
  end
  if window_height < 600
    set_window_height(600)
  end
  set_x_bounds([0.0, 0.1], obind, 0)
  set_show_axes(Show_x_axis, obind, 0)
  update_time_graph
  set_amp_control(0.1, obind)
  select_channel(2)
  if (res = Snd.catch do apply_controls(obind, 1) end).first == :no_such_sound
    snd_display("apply_controls cannot find 4.aiff: %s?", res.inspect)
  end
  newamps = maxamp(obind, true)
  if fneq(amps[0], newamps[0]) or
      fneq(amps[1], newamps[1]) or
      fneq_err(0.1 * amps[2], newamps[2], 0.05) or
      fneq(amps[3], newamps[3])
    snd_display(snd_format_neq(newamps, amps, "apply amps"))
  end
  undo_edit(1, obind, 2)
  set_amp_control(0.1, obind)
  make_region(0, frames(obind), obind, 1)
  Snd.catch do apply_controls(obind, 2) end
  newamps = maxamp(obind, true)
  if fneq(amps[0], newamps[0]) or
      fneq_err(0.1 * amps[1], newamps[1], 0.05) or
      fneq(amps[2], newamps[2]) or
      fneq(amps[3], newamps[3])
    snd_display(snd_format_neq(newamps, amps, "apply selection amps"))
  end
  # 
  if $with_test_gui
    axinfo = axis_info(obind, 0, Time_graph)
    losamp, hisamp, x0, y0, x1, y1 = axinfo[0, 6]
    xpos = x0 + 0.5 * (x1 - x0.to_f)
    ypos = y0 + 0.75 * (y1 - y0.to_f)
    select_channel(0)
    set_cursor(100, obind)
    xy = cursor_position(obind)
    snd_test_neq(position2x(xy[0]), cursor(obind).to_f / srate(obind), "cursor_position %s", xy[0])
    snd_test_neq(position2x(x2position(xpos)), xpos, "x<->position")
    if ((res = position2y(y2position(ypos))) - ypos).abs > 0.5
      snd_display(snd_format(res, ypos, "y<->position"))
    end
    snd_test_neq(left_sample(obind, 0), losamp, "axis_info[0 losamp]")
    snd_test_neq(right_sample(obind, 0), hisamp, "axis_info[1 hisamp]")
    snd_test_neq(axinfo[6], 0.0, "axis_info[6 xmin]")
    snd_test_neq(axinfo[7], -1.0, "axis_info[7 ymin]")
    snd_test_neq(axinfo[9], 1.0, "axis_info[9 ymax]")
    res = our_x2position(obind, x0)
    if (res[0] - res[1]).abs > 1
      snd_display(snd_format_neq(res[0], res[1], "x0->position"))
    end
    res = our_x2position(obind, x1)
    if (res[0] - res[1]).abs > 1
      snd_display(snd_format_neq(res[0], res[1], "x1->position"))
    end
    res = our_x2position(obind, 0.5 * (x0 + x1))
    if (res[0] - res[1]).abs > 1
      snd_display(snd_format_neq(res[0], res[1], "xmid->position"))
    end
    if $clmtest.zero?
      cp_x = lambda do |x|
        (axinfo[10] + ((x - x0.to_f) * ((axinfo[12] - axinfo[10].to_f) / (x1 - x0.to_f)))).floor
      end
      cp_y = lambda do |y|
        (axinfo[13] + ((y1.to_f - y) * ((axinfo[11] - axinfo[13].to_f) / (y1 - y0.to_f)))).floor
      end
      if ((res1 = x2position(xpos)) - (res2 = cp_x.call(xpos))).abs > 1
        snd_display(snd_format_neq(res2, res1, "cp_x 0.5"))
      end
      if ((res1 = y2position(ypos)) - (res2 = cp_y.call(ypos))).abs > 1
        snd_display(snd_format_neq(res2, res1, "cp_y 0.75"))
      end
      10.times do |i|
        xxpos = x0 + random(x1 - x0)
        yypos = y0 + random(y1 - y0)
        if ((res1 = x2position(xxpos)) - (res2 = cp_x.call(xxpos))).abs > 1
          snd_display(snd_format_neq(res2, res1, "cp_x[%d] %1.4f", i, xxpos))
        end
        if ((res1 = y2position(yypos)) - (res2 = cp_y.call(yypos))).abs > 1
          snd_display(snd_format_neq(res2, res1, "cp_y[%d] %1.4f", i, yypos))
        end
        snd_test_neq(position2x(cp_x.call(xxpos)), xxpos, "x2position cp_x[%d]", i)
        # okay
        snd_test_any_neq(position2y(cp_y.call(yypos)), yypos, :fffequal?, "y2position cp_y[%d]", i)
      end
    end
    old_samp = left_sample(obind, 0)
    set_left_sample(1234, obind, 0)
    snd_test_neq(axis_info(obind, 0)[0], 1234, "axis_info[0 losamp at 1234]")
    set_left_sample(old_samp, obind, 0)
    axinfo = axis_info(obind, 0)
    x0 = axinfo[2]
    x1 = axinfo[4]
    res = our_x2position(obind, x0)
    if (res[0] - res[1]).abs > 1
      snd_display("x0a->position: %s?", res)
    end
    res = our_x2position(obind, x1)
    if (res[0] - res[1]).abs > 1
      snd_display("x1a->position: %s?", res)
    end
    res = our_x2position(obind, 0.5 * (x0 + x1))
    if (res[0] - res[1]).abs > 1
      snd_display("xmida->position: %s?", res)
    end
    set_y_bounds([-2.0, 3.0], obind, 0)
    snd_test_neq(axis_info(obind, 0)[7], -2.0, "axis_info[7 ymin]")
    snd_test_neq(axis_info(obind, 0)[9], 3.0, "axis_info[9 ymax]")
  end
  # 
  close_sound(obind)
end

def test_05_16
  ind1 = open_sound("oboe.snd")
  test_orig(lambda { |snd| src_sound(2.0, 1.0, snd) },
            lambda { |snd| src_sound(0.5, 1.0, snd) },
            "src_sound(2.0, 1.0, snd)",
            ind1)
  test_orig(lambda { |snd| src_channel(2.0) },
            lambda { |snd| src_channel(0.5) },
            "src_channel(2.5)",
            ind1)
  test_orig(lambda { |snd| scale_by(2.0, snd) },
            lambda { |snd| scale_by(0.5, snd) },
            "scale_by(2.0, snd)",
            ind1)
  test_orig(lambda { |snd| scale_channel(2.0) },
            lambda { |snd| scale_channel(0.5) },
            "scale_channel(2.0)",
            ind1)
  test_orig(lambda { |snd| reverse_sound(snd) },
            lambda { |snd| reverse_sound(snd) },
            "reverse_sound(snd)",
            ind1)
  test_orig(lambda { |snd| reverse_channel() },
            lambda { |snd| reverse_channel() },
            "reverse_channel()",
            ind1)
  test_orig(lambda { |snd| env_sound([0, 1.0, 1, 2.0]) },
            lambda { |snd| env_sound([0, 1.0, 1, 0.5]) },
            "env_sound([0, 1.0, 1, 2.0])",
            ind1)
  test_orig(lambda { |snd| env_sound([0, 1.0, 1, 2.0, 2, 1.0]) },
            lambda { |snd| env_sound([0, 1.0, 1, 0.5, 2, 1.0]) },
            "env_sound([0, 1.0, 1, 2.0, 2, 1.0])",
            ind1)
  test_orig(lambda { |snd| env_channel(make_env([0, 1.0, 1, 2.0], :length, frames(snd))) },
            lambda { |snd| env_channel(make_env([[0, 1.0], [1, 0.5]], :length, frames(snd))) },
            "env_channel(make_env([0, 1.0, 1, 2.0]))",
            ind1)
  test_orig(lambda { |snd| env_channel([0, 1.0, 1, 2.0]) },
            lambda { |snd| env_channel([0, 1.0, 1, 0.5]) },
            "env_channel([0, 1.0, 1, 2.0])",
            ind1)
  test_orig(lambda { |snd|
              env_channel(make_env([0, 2, 1, 2, 2, 0.5, 3, 0.5], :base, 0, :length, frames(snd)))
            },
            lambda { |snd|
              env_channel(make_env([0, 0.5, 1, 0.5, 2, 2, 3, 2], :base, 0, :length, frames(snd)))
            },
            "env_channel(make_env([0, 2, 1, 2, 2, 0.5, 3, 0.5]))",
            ind1)
  test_orig(lambda { |snd| map_channel(lambda { |y| y * 2.0 }) },
            lambda { |snd| map_channel(lambda { |y| y * 0.5 }) },
            "map_channel(lambda { |y| y * 2.0 })",
            ind1)
  test_orig(lambda { |snd| map_channel(lambda { |y| y * 2.0 }, 1234) },
            lambda { |snd| map_channel(lambda { |y| y * 0.5 }, 1234) },
            "map_channel(lambda { |y| y * 2.0 }, 1234)",
            ind1)
  test_orig(lambda { |snd| map_channel(lambda { |y| y * 2.0 }, 12005, 10) },
            lambda { |snd| map_channel(lambda { |y| y * 0.5 }, 12005, 10) },
            "map_channel(lambda { |y| y * 2.0 }, 12005, 10)",
            ind1)
  outp = false
  test_orig(lambda { |snd| map_channel(lambda { |y| vct(y * 2.0, y * 2.0) }) },
            lambda { |snd| map_channel(lambda { |y| outp = (outp ? false : y * 0.5) }) },
            "map_channel(lambda { |y| vct(y * 2.0, y * 2.0) })",
            ind1)
  test_orig(lambda { |snd| map_chan(lambda { |y| y * 2.0 }) },
            lambda { |snd| map_chan(lambda { |y| y * 0.5 }) },
            "map_chan(lambda { |y| y * 2.0 })",
            ind1)
  test_orig(lambda { |snd| pad_channel(1000, 2000, snd) },
            lambda { |snd| delete_samples(1000, 2000, snd) },
            "pad_channel(1000, 2000, snd)",
            ind1)
  test_orig(lambda { |snd| clm_channel(make_one_zero(:a0, 2.0, :a1, 0.0)) },
            lambda { |snd| clm_channel(make_one_zero(:a0, 0.5, :a1, 0.0)) },
            "clm_channel(make_one_zero)",
            ind1)
  test_orig(lambda { |snd| clm_channel(make_one_pole(:a0, 2.0, :b1, 0.0)) },
            lambda { |snd| clm_channel(make_one_pole(:a0, 0.5, :b1, 0.0)) },
            "clm_channel(make_one_pole)",
            ind1)
  test_orig(lambda { |snd| filter_sound(make_one_zero(:a0, 2.0, :a1, 0.0), 2, snd, 0) },
            lambda { |snd| filter_sound(make_one_zero(:a0, 0.5, :a1, 0.0), 2, snd, 0) },
            "filter_sound(make_one_zero)",
            ind1)
  if (res = Snd.catch do src_sound([0, 0, 1, 1]) end).first != :out_of_range
    snd_display("src_sound env at 0: %s", res.inspect)
  end
  if (res = Snd.catch do src_sound([0, 1, 1, -1]) end).first != :out_of_range
    snd_display("src_sound env through 0: %s", res.inspect)
  end
  # 
  scale_to(1.0, ind1)
  v0 = make_vct(10)
  v1 = channel2vct(12000, 10, ind1, 0)
  v0[0] = 1.0
  array2file("fmv3.snd", v0, 10, 22050, 1)
  file_copy("oboe.snd", "fmv4.snd")
  convolve_with("fmv3.snd", 1.0, ind1)
  convolve_files("fmv4.snd", "fmv3.snd", 1.0, "fmv5.snd")
  v2 = channel2vct(12000, 10, ind1, 0)
  snd_test_any_neq(v2, v1, :vfequal?, "convolve_with (orig 0)") # okay
  file2array("fmv5.snd", 0, 12000, 10, v2)
  snd_test_any_neq(v2, v1, :vfequal?, "convolve_files (orig 0)") # okay
  delete_files("fmv3.snd", "fmv5.snd")
  convolve_files("2.snd", "oboe.snd", 0.5, "fmv5.snd")
  res = mus_sound_maxamp("fmv5.snd")
  unless (fneq(res[1], 0.25) or fneq(res[3], 0.5))
    snd_display("convolve_files stereo: %s", res)
  end
  delete_file("fmv5.snd")
  scale_to(0.25, ind1)
  set_y_bounds([], ind1)
  if $with_test_gui and (res = y_bounds(ind1)) != [-0.25, 0.25]
    snd_display("y_bounds []: %s", res)
  end
  revert_sound(ind1)
  #
  scale_to(1.0, ind1)
  v0 = make_vct(10)
  v1 = channel2vct(12000, 10, ind1, 0)
  v0[5] = 1.0
  array2file("fmv3.snd", v0, 10, 22050, 1)
  convolve_with("fmv3.snd", 1.0, ind1)
  convolve_files("fmv4.snd", "fmv3.snd", 1.0, "fmv5.snd")
  v2 = channel2vct(12005, 10, ind1, 0)
  snd_test_any_neq(v2, v1, :vfequal?, "convolve_with (orig 2)") # okay
  file2array("fmv5.snd", 0, 12005, 10, v2)
  snd_test_any_neq(v2, v1, :vfequal?, "convolve_files (orig 2)") # okay
  delete_files("fmv3.snd", "fmv4.snd", "fmv5.snd")
  revert_sound(ind1)
  #
  old_val = selection_creates_region
  old_regions = regions
  set_selection_creates_region(false)
  select_all(ind1)
  set_selection_creates_region(old_val)
  unless old_regions.eql?(regions)
    snd_display("selection_creates_region: %s -> %s", old_regions, regions)
  end
  convolve_selection_with("pistol.snd", maxamp)
  data = channel2vct(12000, 10, ind1, 0)
  convolve_with("pistol.snd", maxamp(ind1, 0, 0), ind1, 0, 0)
  new_data = channel2vct(12000, 10, ind1, 0)
  snd_test_any_neq(new_data, data, :vfequal?, "convolve_selection_with") # okay
  revert_sound(ind1)
  #
  make_selection(1000, 2000, ind1)
  ma = maxamp(ind1)
  convolve_selection_with("pistol.snd", ma)
  if fneq(maxamp(ind1), ma)
    snd_display("convolve_selection_with 1000: %s %s?", ma, maxamp(ind1))
  end
  make_selection(1000, 2000, ind1)
  id = make_region
  unless region?(id)
    snd_display("make_region argless: %s?", id)
  end
  if (res1 = region_frames(id, 0)) != (res2 = selection_frames)
    snd_display("region/selection_frames: %s %s (%s)?", res1, res2, region_frames(id))
  end
  if (res1 = region_sample(id, 0)) != (res2 = sample(1000, ind1))
    snd_display("region_sample from make_region: %s %s?", res1, res2)
  end
  close_sound(ind1)
end

def test_05_17
  ind = open_sound("2.snd")
  reg = make_region(0, 100, ind, true)
  if (res = region_home(reg)) != ["2.snd", 0, 100]
    snd_display("make + region_home: %s?", res)
  end
  if (res = region_chans(reg)) != 2
    snd_display("make_region chan true: %s", res)
  end
  close_sound(ind)
  # 
  ind = open_sound("2.snd")
  v0 = channel2vct(12000, 10, ind, 0)
  v1 = channel2vct(12000, 10, ind, 1)
  swap_channels(ind)
  v2 = channel2vct(12000, 10, ind, 0)
  v3 = channel2vct(12000, 10, ind, 1)
  if vequal(v0, v2) or vequal(v1, v3)
    snd_display("swap_channels 0: no change!\n# %s\n# %s\n# %s\n# %s", v0, v2, v1, v3)
  end
  swap_channels(ind)
  v2 = channel2vct(12000, 10, ind, 0)
  v3 = channel2vct(12000, 10, ind, 1)
  unless vequal(v0, v2) or vequal(v1, v3)
    snd_display("swap_channels 1: \n# %s\n# %s\n# %s\n# %s", v0, v2, v1, v3)
  end
  set_cursor(100, ind, 0)
  set_cursor(200, ind, 1)
  if (res0 = cursor(ind, 0)) != 100 or (res1 = cursor(ind, 1)) != 200
    snd_display("cursor: %s %s?", res0, res1)
  end
  set_sync(1, ind)
  scale_by([0.5, 0.25], ind)
  scale_by(vct(2.0, 4.0), ind)
  revert_sound(ind)
  amps = maxamp(ind, true)
  swap_channels(ind, 0, ind)
  newamps = maxamp(ind, true)
  if fneq(amps[0], newamps[1]) or fneq(amps[1], newamps[0])
    snd_display("swap_channels with cp def: %s %s?", amps, newamps)
  end
  swap_channels(ind, 1)
  newamps = maxamp(ind, true)
  if fneq(amps[0], newamps[0]) or fneq(amps[1], newamps[1])
    snd_display("swap_channels with cp def 0: %s %s?", amps, newamps)
  end
  close_sound(ind)
end

def test_05_18
  ind1 = open_sound("oboe.snd")
  ind2 = open_sound("2.snd")
  ups1 = count_matches(lambda do |n| n > 0.1 end, 0, ind1, 0)
  count = 0
  scan_chan(lambda do |n|
              if n > 0.1
                count += 1
              end
              false
            end, 0, frames(ind1), ind1, 0)
  ups2 = count
  if ups1 != ups2
    snd_display("scan_chan: %s %s?", ups1, ups2)
  end
  ups1 = count_matches(lambda do |n| n > 0.03 end, 0, ind2, 0)
  ups2 = count_matches(lambda do |n| n > 0.03 end, 0, ind2, 1)
  count = 0
  scan_chan(lambda do |n|
              if n > 0.03
                count += 1
              end
              false
            end, 0, frames(ind2), ind2, 0)
  ups3 = count
  count = 0
  scan_chan(lambda do |n|
              if n > 0.03
                count += 1
              end
              false
            end, 0, frames(ind2), ind2, 1)
  ups4 = count
  if ups1 != ups3
    snd_display("2[0] scan_chan: %s %s?", ups1, ups3)
  end
  if ups2 != ups4
    snd_display("2[1] scan_chan: %s %s?", ups2, ups4)
  end
  set_sync(true, ind2)
  count = 0
  scan_chans do |n|
    if n > 0.03
      count += 1
    end
    false
  end
  total = count
  if total != ups1 + ups2
    snd_display("scan_chans: %s %s?", total, ups1 + ups2)
  end
  set_sync(false, ind2)
  count = 0
  scan_sound_chans(0, frames(ind2), ind2) do |n|
    if n > 0.03
      count += 1
    end
    false
  end
  total = count
  if total != ups1 + ups2
    snd_display("scan_sound_chans: %s %s?", total, ups1 + ups2)
  end
  count = 0
  scan_across_all_chans do |data, len|
    data.each do |val|
      if val > 0.03
        count += 1
      end
    end
    false
  end
  total = count
  ups3 = count_matches(lambda do |n| n > 0.03 end, 0, ind1, 0)
  if total != ups1 + ups2 + ups3
    snd_display("scan_across_all_chans: %s %s?", total, ups1 + ups2 + ups3)
  end
  count = 0
  scan_all_chans do |n|
    if n > 0.03
      count += 1
    end
    false
  end
  total = count
  ups3 = count_matches(lambda do |n| n > 0.03 end, 0, ind1, 0)
  if total != ups1 + ups2 + ups3
    snd_display("scan_all_chans: %s %s?", total, ups1 + ups2 + ups3)
  end
  close_sound(ind1)
  close_sound(ind2)
end

def get_test_args(args, snd, chn, edpos)
  [(args[0] or snd), (args[1] or chn), (args[2] or edpos)]
end

def test_05_20
  ind1 = open_sound("oboe.snd")
  len = frames(ind1)
  ctr = 0
  map_chan(lambda do |n|
             ctr = (ctr == 1) ? 0 : 1
             ctr.zero? ? n * 2.0 : false
           end, 0, frames(ind1), "ignore: cut 2", ind1, 0)
  if frames(ind1) > (len * 2 + 1)
    snd_display("map_chan cut: %s %s?", len, frames(ind1))
  end
  revert_sound(ind1)
  ctr = 0
  map_chan(lambda do |n|
             ctr += 1
             ctr > 3 ? true : n
           end, 0, frames(ind1), "ignore: cut none", ind1, 0)
  if ctr > 4
    snd_display("map_chan no-edit count: %s?", ctr)
  end
  revert_sound(ind1)
  v1 = make_vct(2)
  map_chan(lambda do |n|
             v1[0] = n
             v1[1] = n * 3.0
             v1
           end, 0, frames(ind1), "ignore: cut 2", ind1, 0)
  if (frames(ind1) - len * 2).abs > 3
    snd_display("map_chan double: %s %s?", len, frames(ind1))
  end
  revert_sound(ind1)
  otime = maxamp_position(ind1)
  set_sample(1234, 0.9)
  ntime = maxamp_position(ind1)
  nval = maxamp(ind1)
  npos = edit_position(ind1, 0)
  if ntime != 1234
    snd_display("maxamp_position 1234: %s?", ntime)
  end
  ootime = maxamp_position(ind1, 0, 0)
  if ootime != otime
    snd_display("maxamp_position edpos 0: %s %s?", otime, ootime)
  end
  nntime = maxamp_position(ind1, 0, npos)
  if nntime != ntime
    snd_display("maxamp_position edpos %s: %s %s?", npos, ntime, nntime)
  end
  if fneq(nval, 0.9)
    snd_display("maxamp 0.9: %s?", nval)
  end
  set_sample(1234, 0.0)
  env_channel([0, 0, 1, 1])
  if (res = maxamp_position) != 35062
    snd_display("env_channel maxamp_position: %s (35062)?", res)
  end
  ootime = maxamp_position(ind1, 0, 0)
  if ootime != otime
    snd_display("maxamp_position edpos 0 (1): %s %s?", otime, ootime)
  end
  nntime = maxamp_position(ind1, 0, 1)
  if nntime != 1234
    snd_display("maxamp_position edpos 1 (1): %s (1234)?", nntime)
  end
  nntime = maxamp_position(ind1, 0, Current_edit_position)
  if nntime != 35062
    snd_display("maxamp_position edpos current: %s (35062)?", nntime)
  end
  revert_sound(ind1)
  make_selection(24000, 25000)
  if (res = selection_maxamp_position) != 971
    snd_display("selection_maxamp_position: %s (971)?", res)
  end
  make_region(24000, 25000)
  if (res = region_maxamp_position(regions.first)) != 971
    snd_display("region_maxamp_position: %s (971)?", res)
  end
  close_sound(ind1)
  ind1 = open_sound("oboe.snd")
  test_edpos(ind1, :maxamp) do | | scale_by(2.0, ind1, 0) end
  test_edpos(ind1, :frames) do | | src_sound(2.0, 1.0, ind1, 0) end
  test_edpos(ind1, :count_matches, lambda do |*args|
               snd, chn, edpos = get_test_args(args, 0, 0, Current_edit_position)
               count_matches(lambda do |n1| n1 > 0.1 end, 0, snd, chn, edpos)
             end) do | |
    scale_by(2.0, ind1, 0)
  end
  test_edpos(ind1, :find, lambda do |*args|
               snd, chn, edpos = get_test_args(args, 0, 0, Current_edit_position)
               find_channel(lambda do |n2| n2 > 0.1 end, 0, snd, chn, edpos)
             end) do | |
    delete_samples(0, 100, ind1, 0)
  end
  test_edpos(ind1, :scan_chan, lambda do |*args|
               snd, chn, edpos = get_test_args(args, 0, 0, Current_edit_position)
               samp = 0
               scan_chan(lambda do |n3|
                           if n3 > 0.1
                             samp
                           else
                             samp += 1
                             false
                           end
                         end, 0, frames(snd, chn), snd, chn, edpos)
               samp
             end) do | |
    delete_samples(0, 100, ind1, 0)
  end
  #
  src_sound(2.0, 1.0, ind1, 0)
  play(ind1, :channel, 0, :edit_position, 0, :wait, true)
  play(ind1, :channel, 0, :edit_position, 1, :wait, true)
  play(ind1, :channel, 0, :edit_position, lambda do |snd, chn|
         edit_position(snd, chn)
       end, :wait, true)
  undo_edit(1, ind1, 0)
  play(ind1, :channel, 0, :edit_position, 1, :wait, true)
  #
  delete_samples(0, 10000, ind1, 0)
  save_sound_as("fmv.snd", ind1, :edit_position, 0)
  save_sound_as("fmv1.snd", ind1, :edit_position, lambda do |snd, chn| 1 end)
  if (res = Snd.catch do
        save_sound_as("fmv2.snd", ind1, :channel, 1234)
      end).first != :no_such_channel
    snd_display("save_sound_as bad chan: %s", res)
  end
  if (res0 = mus_sound_frames("fmv.snd")) != (res1 = frames(ind1, 0, 0))
    snd_display("save_sound_as (edpos): %s %s?", res0, res1)
  end
  if (res0 = mus_sound_frames("fmv1.snd")) != (res1 = frames(ind1, 0, 1))
    snd_display("save_sound_as (edpos 1): %s %s?", res0, res1)
  end
  if (res0 = mus_sound_frames("fmv.snd")) == (res1 = frames(ind1, 0, 1))
    snd_display("save_sound_as (edpos 1)(2): %s %s?", res0, res1)
  end
  ind2 = open_sound("fmv.snd")
  ind3 = open_sound("fmv1.snd")
  unless vequal(res0 = channel2vct(12000, 10, ind1, 0, 0),
                res1 = channel2vct(12000, 10, ind2, 0))
    snd_display("save_sound_as (edpos 3): %s %s?", res0, res1)
  end
  unless vequal(res0 = channel2vct(12000, 10, ind1, 0, 1),
                res1 = channel2vct(12000, 10, ind3, 0))
    snd_display("save_sound_as (edpos 4): %s %s?", res0, res1)
  end
  if vequal(res0 = channel2vct(12000, 10, ind2), res1 = channel2vct(12000, 10, ind3, 0))
    snd_display("save_sound_as (edpos 5): %s %s?", res0, res1)
  end
  select_sound(ind3)
  set_comment("hiho")
  if comment != "hiho"
    snd_display("set_comment no index: %s?", comment)
  end
  close_sound(ind2)
  close_sound(ind3)
  delete_files("fmv.snd", "fmv1.snd")
  #
  test_edpos_1(:reverse_sound, ind1) do |snd, pos|
    reverse_sound(snd, 0, pos)
  end
  test_edpos_1(:env_sound, ind1) do |snd, pos|
    env_sound([0, 0, 1, 1, 2, 0], 0, 20000, 1.0, snd, 0, pos)
  end
  test_edpos_1(:src_sound, ind1) do |snd, pos|
    src_sound(0.5, 1.0, snd, 0, pos)
  end
  test_edpos_1(:filter_sound, ind1) do |snd, pos|
    filter_sound(make_fir_filter(6, vct(0.1, 0.2, 0.3, 0.3, 0.2, 0.1)), 6, snd, 0, pos)
  end
  test_edpos_1(:convolve_with, ind1) do |snd, pos|
    convolve_with("pistol.snd", 0.5, snd, 0, pos)
  end
  #
  ind = new_sound("fmv.snd")
  v = make_vct!(2000) do |i| sin(i * (PI / 5.0)) end
  vct2channel(v, 0, 2000, ind, 0)
  filter_sound([0, 0, 0.09, 0, 0.1, 1, 0.11, 0, 1, 0], 1024)
  if maxamp > 0.025
    snd_display("filter_sound maxamp 1: %s?", maxamp)
  end
  undo_edit
  filter_sound([0, 0, 0.19, 0, 0.2, 1, 0.21, 0, 1, 0], 1024)
  if maxamp < 0.9
    snd_display("filter_sound maxamp 2: %s?", maxamp)
  end
  undo_edit
  filter_sound([0, 0, 0.29, 0, 0.3, 1, 0.31, 0, 1, 0], 1024)
  if maxamp > 0.02
    snd_display("filter_sound maxamp 3: %s?", maxamp)
  end
  old_ssc = show_sonogram_cursor
  old_tgt = transform_graph_type
  set_show_sonogram_cursor(true)
  set_with_tracking_cursor(true)
  snd_test_neq(with_tracking_cursor(), true, "with_tracking_cursor set to true")
  set_transform_graph_type(Graph_as_sonogram)
  play(selected_sound, :wait, true)
  set_transform_graph?(true)
  set_transform_graph_type(old_tgt)
  set_show_sonogram_cursor(old_ssc)
  close_sound(ind)
  close_sound(ind1)
end

def peak_env_equal?(name, index, e, diff)
  rd = make_sampler(0, index, 0)
  e_size = e.first.length
  samps_per_bin = (frames(index) / e_size.to_f).ceil
  mins, maxs = e[0, 2]
  max_diff = 0.0
  e_bin = 0
  samp = 0
  mx = -10.0
  mn = 10.0
  until e_bin == e_size
    if samp >= samps_per_bin
      mxdiff = (mx - maxs[e_bin]).abs
      mndiff = (mn - mins[e_bin]).abs
      if mxdiff > max_diff
        max_diff = mxdiff
      end
      if mndiff > max_diff
        max_diff = mndiff
      end
      if mxdiff > diff or mndiff > diff
        snd_display("%s: peak_env_equal? [bin %s of %s]: %s %s %s?",
                    name, e_bin, e_size, mn, mx, [mxdiff, mndiff].max)
        return false
      end
      samp = 0
      mx = -10.0
      mn = 10.0
      e_bin += 1
    end
    val = next_sample(rd)
    if val < mn
      mn = val
    end
    if val > mx
      mx = val
    end
    samp += 1
  end
  true
end

def test_05_22
  ind = open_sound("oboe.snd")
  mx = maxamp(ind, 0)
  if (e0 = channel_amp_envs(ind, 0)).nil?
    snd_display("no amp env data")
  else
    mx1 = vct_peak(e0[0])
    mx2 = vct_peak(e0[1])
    if fneq(mx, [mx1, mx2].max)
      snd_display("amp env max: %s %s %s?", mx, mx1, mx2)
    end
    peak_env_equal?("straight peak", ind, e0, 0.0001)
    # 
    scale_by(3.0)
    e1 = channel_amp_envs(ind, 0, 1)
    mx3 = vct_peak(e1[0])
    mx4 = vct_peak(e1[1])
    if fneq(3.0 * mx1, mx3) or fneq(3.0 * mx2, mx4)
      snd_display("3.0 amp env max: %s %s %s %s?", mx1, mx2, mx3, mx4)
    end
    peak_env_equal?("scaled peak", ind, e1, 0.0001)
    if fneq(maxamp(ind, 0), 3.0 * mx)
      snd_display("maxamp after scale: %s %s?", mx, maxamp(ind, 0))
    end
    undo_edit
    # 
    set_selection_member?(false, true)
    set_selection_member?(true, ind, 0)
    set_selection_position(20000, ind, 0)
    set_selection_frames(12000, ind, 0)
    scale_selection_by(3.0)
    e1 = channel_amp_envs(ind, 0, 1)
    mx3 = vct_peak(e1[0])
    mx4 = vct_peak(e1[1])
    if fneq(3.0 * mx1, mx3) or fneq(3.0 * mx2, mx4)
      snd_display("selection 3.0 amp env max: %s %s %s %s?", mx1, mx2, mx3, mx4)
    end
    if fneq(maxamp(ind, 0), 3.0 * mx)
      snd_display("maxamp after selection scale: %s %s?", mx, maxamp(ind, 0))
    end
    peak_env_equal?("selection peak", ind, e1, 0.0001)
    # 
    map_chan(lambda do |n| n.abs end, 0, false, "test", ind, 0)
    e1 = channel_amp_envs(ind, 0, 2)
    mx3 = vct_peak(e1[0])
    mx4 = vct_peak(e1[1])
    if fneq(3.0 * mx2, mx4)
      snd_display("abs selection 3.0 amp env max: %s %s %s %s?", mx1, mx2, mx3, mx4)
    end
    if fneq(maxamp(ind, 0), 3.0 * mx)
      snd_display("maxamp after abs selection scale: %s %s?", mx, maxamp(ind, 0))
    end
    peak_env_equal?("map_chan peak", ind, e1, 0.0001)
    #
    delete_samples(10000, 5000)
    e1 = channel_amp_envs(ind, 0)
    mx3 = vct_peak(e1[0])
    mx4 = vct_peak(e1[1])
    if fneq(3.0 * mx2, mx4)
      snd_display("abs selection 3.0 amp env max: %s %s %s %s?", mx1, mx2, mx3, mx4)
    end
    if fneq(maxamp(ind, 0), 3.0 * mx)
      snd_display("maxamp after abs selection scale: %s %s?", mx, maxamp(ind, 0))
    end
    peak_env_equal?("delete peak", ind, e1, 0.0001)
    #
    scale_selection_by -0.333
    e1 = channel_amp_envs(ind, 0, 4)
    mx3 = vct_peak(e1[0])
    if fneq(maxamp(ind, 0), mx)
      snd_display("maxamp after minus selection scale: %s %s?", mx, maxamp(ind, 0))
    end
    if fneq(maxamp(ind, 0), mx3)
      snd_display("mx3 maxamp after minus abs selection scale: %s %s?", mx, mx3)
    end
    peak_env_equal?("scale_selection peak", ind, e1, 0.0001)
  end
  revert_sound(ind)
  ramp_channel(0.0, 1.0)
  peak_env_equal?("ramp_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.001)
  undo_edit
  env_channel([0, 0, 1, 1, 2, 0])
  peak_env_equal?("env_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.002)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0], :scaler, 0.5, :length, frames))
  peak_env_equal?("scaled env_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.002)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0], 0.5, :length, frames))
  peak_env_equal?("scaled nokey env_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.001)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0], :scaler, 0.5, :offset, 0.5, :length, frames))
  peak_env_equal?("scaled and offset env_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.001)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0.5, 3, 0], :base, 0.0, :length, frames))
  peak_env_equal?("env_channel base 0.0 peak", ind, channel_amp_envs(ind, 0, 1), 0.001)
  undo_edit
  xramp_channel(0.0, 1.0, 32.0)
  peak_env_equal?("xramp_channel 32.0 peak", ind, channel_amp_envs(ind, 0, 1), 0.008)
  undo_edit
  xramp_channel(0.0, 1.0, 0.032)
  peak_env_equal?("xramp_channel 0.032 peak", ind, channel_amp_envs(ind, 0, 1), 0.004)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0.5, 3, 0], :base, 10.0, :length, frames))
  peak_env_equal?("env_channel base 10.0 peak", ind, channel_amp_envs(ind, 0, 1), 0.01)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0], :base, 0.1, :length, frames))
  peak_env_equal?("env_channel base 0.1 peak", ind, channel_amp_envs(ind, 0, 1), 0.003)
  undo_edit
  insert_samples(1000, 5000, make_vct(5000, 0.5))
  peak_env_equal?("insert_samples peak", ind, channel_amp_envs(ind, 0, 1), 0.0001)
  undo_edit
  set_samples(500, 100, make_vct(100, 0.1))
  peak_env_equal?("set_samples peak", ind, channel_amp_envs(ind, 0, 1), 0.0001)
  undo_edit
  #
  revert_sound(ind)
  ramp_channel(0.0, 1.0)
  ramp_channel(1.0, 0.0)
  peak_env_equal?("2 ramp_channel peak", ind, channel_amp_envs(ind, 0, 2), 0.002)
  #
  revert_sound(ind)
  env_channel([0, 0, 1, 1])
  env_channel([0, 0, 1, 1, 2, 0])
  peak_env_equal?("2 env_channel peak", ind, channel_amp_envs(ind, 0, 2), 0.002)
  revert_sound(ind)
  ramp_channel(0.0, 1.0, 12000, 5000)
  peak_env_equal?("ramp_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.002)
  undo_edit
  env_channel([0, 0, 1, 1, 2, 0], 12000, 5000)
  peak_env_equal?("env_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.003)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0], :scaler, 0.5, :length, 5000), 12000, 5000)
  peak_env_equal?("scaled env_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.004)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0], 0.5, :length, 5000), 12000, 5000)
  peak_env_equal?("scaled nokey env_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.004)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0], :scaler, 0.5, :offset, 0.5, :length, 5000), 12000, 5000)
  peak_env_equal?("scaled and offset env_channel peak", ind, channel_amp_envs(ind, 0, 1), 0.002)
  undo_edit
  xramp_channel(0.0, 1.0, 32.0, 2000, 1000)
  peak_env_equal?("xramp_channel 32.0 peak (1)", ind, channel_amp_envs(ind, 0, 1), 0.009)
  undo_edit
  xramp_channel(0.0, 1.0, 0.032, 2000, 1000)
  peak_env_equal?("xramp_channel 0.032 peak (1)", ind, channel_amp_envs(ind, 0, 1), 0.01)
  undo_edit
  env_channel(make_env([0, 0, 1, 1, 2, 0.5, 3, 0], :base, 10.0, :length, 5000), 12000, 5000)
  peak_env_equal?("env_channel base 10.0 peak", ind, channel_amp_envs(ind, 0, 1), 0.1)
  undo_edit
  #
  revert_sound(ind)
  ramp_channel(0.0, 1.0)
  ramp_channel(1.0, 0.0, 2000, 1000)
  peak_env_equal?("2 ramp_channel peak", ind, channel_amp_envs(ind, 0, 2), 0.002)
  #
  revert_sound(ind)
  env_channel([0, 0, 1, 1])
  env_channel([0, 0, 1, 1, 2, 0], 2000, 1000)
  peak_env_equal?("2 env_channel peak", ind, channel_amp_envs(ind, 0, 2), 0.002)
  # 
  revert_sound(ind)
  env_channel([0, 0, 1, 1])
  env_channel([0, 0, 1, 1, 2, 0])
  env_channel([0, 0, 1, 1], 12000, 5000)
  peak_env_equal?("3 env_channel peak", ind, channel_amp_envs(ind, 0, 3), 0.01)
  revert_sound(ind)
  close_sound(ind)
  #
  ind = new_sound("test.snd")
  map_chan(lambda do |y| 1.0 end, 0, 50000)
  ramp_channel(0.5, 1.0, 1000, 4000)
  mn, mx = channel_amp_envs(ind, 0)[0, 2]
  (mn.length - 4).times do |i|
    if mn[i] < 0.5
      snd_display("peak min: %s %s?", mn[i], i)
      break
    end
    if mx[i] < 0.5
      snd_display("peak max: %s %s?", mx[i], i)
      break
    end
  end
  undo_edit
  map_chan(lambda do |y| -1.0 end, 0, 50000)
  ramp_channel(0.5, 1.0, 1000, 4000)
  mn, mx = channel_amp_envs(ind, 0)[0, 2]
  (mn.length - 4).times do |i|
    if mn[i] > -0.5
      snd_display("1 peak min: %s %s?", mn[i], i)
      break
    end
    if mx[i] > -0.5
      snd_display("1 peak max: %s %s?", mx[i], i)
      break
    end
  end
  close_sound(ind)
end

$g_init_val = 0

def test_channel_func(name, index, init_val, func, &val_func)
  len = frames(index)
  chns = chans(index)
  $g_init_val = init_val
  2.times do |k|
    val = val_func.call(len)
    set_sync(k, index)
    chns.times do |i|
      map_channel(lambda do |n| 0.0 end, 0, len, index, i)
      if res = scan_channel(lambda do |n| n.abs > 0.001 end, 0, len, index, i)
        snd_display("%s init scan: %s?", name, res)
      end
    end
    chns.times do |i|
      map_channel(lambda do |n| $g_init_val end, 0, len, index, i)
      func.call(0, len, index, i, false)
      chns.times do |j|
        vi = channel2vct(0, len, index, j)
        if j == i
          unless vequal(vi, val)
            snd_display("%s chan func: %s %s?", name, vi, val)
          end
        else
          if res = scan_channel(lambda do |n| n.abs > 0.001 end, 0, len, index, j)
            snd_display("%s chan func leaks? %s %s: %s", name, i, j, res)
          end
        end
      end
      map_channel(lambda do |n| 0.0 end, 0, len, index, i)
    end
    chns.times do |i|
      map_channel(lambda do |n| $g_init_val end, 0, len, index, i)
      ed = edit_position(index, i)
      map_channel(lambda do |n| $g_init_val + 1.0 end, 0, len, index, i)
      func.call(0, len, index, i, ed)
      chns.times do |j|
        vi = channel2vct(0, len, index, j)
        if j == i
          unless vequal(vi, val)
            snd_display("%s ed chan func: %s %s?", name, vi, val)
          end
        else
          if res = scan_channel(lambda do |n| n.abs > 0.001 end, 0, len, index, j)
            snd_display("%s ed chan func leaks? %s %s %s: %s", name, i, j, ed, res)
          end
        end
      end
      map_channel(lambda do |n| 0.0 end, 0, len, index, i)
    end
    beg = dur = (len / 3.0).floor
    nv = val_func.call(dur)
    vct_fill!(val, 0.0)
    i = beg
    dur.times do |j|
      val[i] = nv[j]
      i += 1
    end
    chns.times do |i|
      map_channel(lambda do |n| $g_init_val end, beg, dur, index, i)
      func.call(beg, dur, index, i, false)
      add_mark(beg, index, i)
      chns.times do |j|
        vi = channel2vct(0, len, index, j)
        if j == i
          unless vequal(vi, val)
            snd_display("%s chan func n: %s %s?", name, vi, val)
          end
        else
          if res = scan_channel(lambda do |n| n.abs > 0.001 end, 0, len, index, j)
            snd_display("%s dur chan func leaks? %s %s: %s", name, i, j, res)
          end
        end
      end
      map_channel(lambda do |n| 0.0 end, 0, len, index, i)
    end
  end
end

def test_05_23
  index = new_sound("fmv.snd", Mus_next, Mus_bshort, 22050, 2, "channel tests")
  insert_silence(0, 10, index, 0)
  insert_silence(0, 10, index, 1)
  test_channel_func(:env, index, 0.0,
                    lambda do |beg, dur, index, chan, edpos|
                      clm_channel(make_env(:envelope, [0, 0, 1, 1], :length, dur),
                                  beg, dur, index, chan, edpos)
                    end) do |dur|
    e = make_env(:envelope, [0, 0, 1, 1], :length, dur)
    make_vct!(dur) do env(e) end
  end
  test_channel_func(:oscil, index, 0.0,
                    lambda do |beg, dur, index, chan, edpos|
                      clm_channel(make_oscil(:frequency, 0.0, :initial_phase, PI / 2.0),
                                  beg, dur, index, chan, edpos)
                    end) do |dur| make_vct!(dur) do 1.0 end end
  test_channel_func(:scale_channel, index, 1.0,
                    lambda do |beg, dur, index, chan, edpos|
                      scale_channel(0.5, beg, dur, index, chan, edpos)
                    end) do |dur| make_vct!(dur) do 0.5 end end
  test_channel_func(:env_channel, index, 1.0,
                    lambda do |beg, dur, index, chan, edpos|
                      env_channel(make_env(:envelope, [0, 0, 1, 1], :length, dur),
                                  beg, dur, index, chan, edpos)
                    end) do |dur|
    e = make_env(:envelope, [0, 0, 1, 1], :length, dur)
    make_vct!(dur) do env(e) end
  end
  test_channel_func(:env_channel, index, 1.0,
                    lambda do |beg, dur, index, chan, edpos|
                      env_channel([0, 0, 1, 1], beg, dur, index, chan, edpos)
                    end) do |dur|
    e = make_env(:envelope, [0, 0, 1, 1], :length, dur)
    make_vct!(dur) do env(e) end
  end
  test_channel_func(:vct2channel, index, 1.0,
                    lambda do |beg, dur, index, chan, edpos|
                      vct2channel(make_vct!(dur) do -1.0 end, beg, dur, index, chan)
                    end) do |dur| make_vct!(dur) do -1.0 end end
  test_channel_func(:pad_channel, index, 1.0,
                    lambda do |beg, dur, index, chan, edpos|
                      delete_samples(beg, dur, index, chan, edpos)
                      pad_channel(beg, dur, index, chan, edpos)
                    end) do |dur| make_vct(dur) end
  test_channel_func(:insert_samples, index, 1.0,
                    lambda do |beg, dur, index, chan, edpos|
                      delete_samples(beg, dur, index, chan, edpos)
                      insert_samples(beg, dur, make_vct!(dur) do -1.0 end, index, chan, edpos)
                    end) do |dur| make_vct!(dur) do -1.0 end end
  test_channel_func(:set_samples, index, 1.0,
                    lambda do |beg, dur, index, chan, edpos|
                      set_samples(beg, dur, make_vct!(dur) do -1.0 end,
                                  index, chan, false, "test_channel", edpos)
                    end) do |dur| make_vct!(dur) do -1.0 end end
  test_channel_func(:reverse_channel, index, 1.0,
                    lambda do |beg, dur, index, chan, edpos|
                      env_channel(make_env(:envelope, [0, 0, 1, 1], :length, dur),
                                  beg, dur, index, chan, edpos)
                      reverse_channel(beg, dur, index, chan)
                    end) do |dur|
    e = make_env(:envelope, [0, 1, 1, 0], :length, dur)
    make_vct!(dur) do env(e) end
  end
  test_channel_func(:smooth_channel, index, 1.0,
                    lambda do |beg, dur, index, chan, edpos|
                      env_channel(make_env(:envelope, [0, 0, 1, 1], :length, dur),
                                  beg, dur, index, chan, edpos)
                      set_sample(beg + dur, 1.0, index, chan)
                      smooth_channel(beg, dur, index, chan)
                      if beg.nonzero?
                        set_sample(beg + dur, 0.0, index, chan)
                      end
                    end) do |dur|
    make_vct!(dur) do |i| 0.5 + 0.5 * cos(PI + (PI * i) / dur) end
  end
  #
  old_max = maxamp(index, true)
  regdata = Snd.regions.map do |n| region2vct(n, 0, 10) end
  old_reglen = Snd.regions.map do |n| region_frames(n) end
  s61_files = []
  $save_state_hook.add_hook!("snd-test") do |file|
    s61_files.push(file)
    false
  end
  delete_file("s61.rb")
  save_state("s61.rb")
  close_sound(index)
  Snd.regions.apply(:forget_region)
  load("s61.rb")
  if (res = Snd.regions.map do |n| region_frames(n) end) != old_reglen
    snd_display("region_frames after save: %s %s?", old_reglen, res)
  end
  Snd.regions.zip(regdata) do |n, data|
    unless vequal(res = region2vct(n, 0, 10), data)
      snd_display("region after save %s: %s %s?", n, data, res)
    end
  end
  index = find_sound("fmv.snd")
  if (res = maxamp(index, true)) != old_max
    snd_display("maxes: %s %s?", res, old_max)
  end
  if edits(index) != [275, 0]
    snd_display("saved channel edits: %s?", edits(index))
  end
  10.times do |i|
    pos = random(edits(index).first)
    scale_channel(random(2.0).abs, random(5.0), random(5.0), index, 0, pos)
    set_edit_position((edits(index).first * 0.7).floor, index)
  end
  close_sound(index)
  Snd.regions.apply(:forget_region)
  s61_files.each do |file| delete_file(file) end
  delete_file("s61.rb")
  $save_state_hook.reset_hook!
end

def test_05_24
  index = new_sound("fmv.snd", Mus_next, Mus_bshort, 22050, 2, "channel tests")
  sw = sinc_width
  set_sinc_width(10)
  v0 = make_vct(10)
  v0[0] = 1.0
  vct2channel(v0, 0, 10, index, 0)
  src_channel(0.5, 0, 10, index, 0)
  idx = -1
  s = make_src(:srate, 0.5,
               :input, lambda do |dir|
                 idx += 1
                 idx.zero? ? 1.0 : 0.0
               end)
  v = make_vct!(10) do src(s) end
  unless vequal(res = channel2vct(0, 10, index, 0), v)
    snd_display("src_channel: %s %s?", v, res)
  end
  unless vequal(res = channel2vct(0, 10, index, 1), make_vct(10))
    snd_display("src_channel leaks: %s?", res)
  end
  if (res = Snd.catch do src(s, 1.0, lambda do |a, b| a end) end).first != :bad_arity
    snd_display("src bad func: %s?", res.inspect)
  end
  if (res = Snd.catch do src_channel(120000.0) end).first != :mus_error
    snd_display("src_channel crazy srate: %s?", res.inspect)
  end
  if (res = Snd.catch do filter_sound(make_snd2sample()) end).first != :mus_error
    snd_display("filter_sound + un-run gen: %s?", res.inspect) # not relevant in Ruby?
  end
  revert_sound(index)
  vct2channel(v0, 0, 10, index, 1)
  vct2channel(v0, 10, 10, index, 1)
  src_channel(make_env(:envelope, [1, 1, 2, 2], :length, 21), 0, 20, index, 1)
  unless vequal(res = channel2vct(0, 10, index, 1),
                vct(1.000, 0.000, -0.048, 0.068, -0.059, 0.022, 0.030, -0.100, 0.273, 0.606))
    snd_display("src_channel env: %s?", res)
  end
  unless vequal(res = channel2vct(0, 10, index, 0), make_vct(10))
    snd_display("src_channel env leaks: %s?", res)
  end
  # 
  revert_sound(index)
  vct2channel(v0, 0, 10, index, 1)
  vct2channel(v0, 10, 10, index, 1)
  src_channel(make_env(:envelope, [1, 1, 2, 2], :length, 21), 0, 20, index, 1)
  unless vequal(res = channel2vct(0, 10, index, 1),
                vct(1.000, 0.000, -0.048, 0.068, -0.059, 0.022, 0.030, -0.100, 0.273, 0.606))
    snd_display("src_channel env: %s?", res)
  end
  unless vequal(res = channel2vct(0, 10, index, 0), make_vct(10))
    snd_display("src_channel env leaks: %s?", res)
  end
  # 
  revert_sound(index)
  vct2channel(v0, 0, 10, index, 1)
  vct2channel(v0, 10, 10, index, 1)
  src_channel([1, 1, 2, 2], 0, 20, index, 1)
  unless vequal(res = channel2vct(0, 10, index, 1),
                vct(1.000, 0.000, -0.051, 0.069, -0.056, 0.015, 0.042, -0.117, 0.320, 0.568))
    snd_display("src_channel lst: %s?", res)
  end
  unless vequal(res = channel2vct(0, 10, index, 0), make_vct(10))
    snd_display("src_channel lst leaks: %s?", res)
  end
  set_sinc_width(sw)
  close_sound(index)
end

def test_05_25
  ind = open_sound("oboe.snd")
  rid0 = make_region(2000, 2020, ind, 0)
  rid0_data = region2vct_1(rid0, 0, 20)
  scale_sound_by(2.0)
  play(rid0, :wait, true)
  unless vequal(res = region2vct_1(rid0, 0, 20), rid0_data)
    snd_display("deferred region after scaling:\n# %s\n# %s", rid0_data, res)
  end
  unless vequal(res = region_to_vct(rid0, 0, 20), rid0_data)
    snd_display("deferred region after scaling (rs):\n# %s\n# %s", rid0_data, res)
  end
  undo_edit
  scale_by(4.0)
  play(rid0, :wait, true)
  unless vequal(res = region2vct_1(rid0, 0, 20), rid0_data)
    snd_display("file region after scaling:\n# %s\n# %s", rid0_data, res)
  end
  unless vequal(res = region_to_vct(rid0, 0, 20), rid0_data)
    snd_display("file region after scaling (rs):\n# %s\n# %s", rid0_data, res)
  end
  rid1 = make_region(2000, 2020, ind, 0)
  rid1_data = region2vct_1(rid1, 0, 20)
  scale_to(0.5)
  unless vequal(res = region2vct_1(rid1, 0, 20), rid1_data)
    snd_display("deferred region after scale_to:\n# %s\n# %s", rid1_data, res)
  end
  close_sound(ind)
  play(rid0, :wait, true)
  play(rid1, :wait, true)
  unless vequal(res = region2vct_1(rid1, 0, 20), rid1_data)
    snd_display("deferred region after close:\n# %s\n# %s", rid1_data, res)
  end
  unless vequal(res = region2vct_1(rid0, 0, 20), rid0_data)
    snd_display("file region after close:\n# %s\n# %s", rid0_data, res)
  end
  [[2000, 20, 2000, 20],
    [2000, 10, 2000, 20],
    [2000, 20, 2000, 10],
    [0,    20, 2000, 20],
    [2000, 20, 0,    20],
    [0,    10, 2000, 20],
    [2000, 20, 0,    10]].each do |s1, l1, s2, l2|
    ind = open_sound("2.snd")
    set_selection_member?(false, true)
    set_selection_member?(true, ind, 0)
    set_selection_position(s1, ind, 0)
    set_selection_frames(l1, ind, 0)
    set_selection_member?(true, ind, 1)
    set_selection_position(s2, ind, 1)
    set_selection_frames(l2, ind, 1)
    rid2 = make_region
    rid20_data = region2vct_1(rid2, 0, l1)
    rid21_data = region2vct_1(rid2, 1, l2)
    if (res = region_chans(rid2)) != 2
      snd_display("region_chans of synced sound: %s?", res)
    end
    swap_channels(ind, 0, ind, 1)
    unless vequal(res = region2vct_1(rid2, 0, l1), rid20_data)
      snd_display("deferred region after scaling (20):\n# %s\n# %s", rid20_data, res)
    end
    unless vequal(res = region_to_vct(rid2, 0, l1), rid20_data)
      snd_display("deferred region after scaling (20 rs):\n# %s\n# %s", rid20_data, res)
    end
    unless vequal(res = region2vct_1(rid2, 1, l2), rid21_data)
      snd_display("deferred region after scaling (21):\n# %s\n# %s", rid21_data, res)
    end
    unless vequal(res = region_to_vct(rid2, 1, l2), rid21_data)
      snd_display("deferred region after scaling (21 rs):\n# %s\n# %s", rid21_data, res)
    end
    close_sound(ind)
    unless vequal(res = region2vct_1(rid2, 0, l1), rid20_data)
      snd_display("deferred region after scaling (20):\n# %s\n# %s", rid20_data, res)
    end
    unless vequal(res = region_to_vct(rid2, 0, l1), rid20_data)
      snd_display("deferred region after scaling (20 rs):\n# %s\n# %s", rid20_data, res)
    end
    unless vequal(res = region2vct_1(rid2, 1, l2), rid21_data)
      snd_display("deferred region after scaling (21):\n# %s\n# %s", rid21_data, res)
    end
    unless vequal(res = region_to_vct(rid2, 1, l2), rid21_data)
      snd_display("deferred region after scaling (21 rs):\n# %s\n# %s",
                  rid21_data, res)
    end
  end
  ind = open_sound("obtest.snd")
  set_read_only(true, ind)
  delete_samples(0, 1000, ind, 0)
  if integer?((res = Snd.catch do save_sound(ind) end).first)
    snd_display("save_sound read_only: %s", res)
  end
  if (res = edits(ind)) != [1, 0]
    snd_display("read_only ignored: %s?", res)
  end
  set_read_only(false, ind)
  revert_sound(ind)
  unless sound?((res = Snd.catch do save_sound(ind) end).first)
    snd_display("save_sound read_write: %s", res)
  end
  key(key_to_int(?j), 4)
  if $with_test_gui
    if (res = widget_text(sound_widgets(ind)[3])) != "no marks" and res != "no such mark"
      snd_display("C-j w/o marks: %s?", res)
    end
  end
  key(key_to_int(?-), 4)
  key(key_to_int(?j), 4)
  key(key_to_int(?j), 4)
  key(key_to_int(?x), 4)
  key(key_to_int(?c), 0)
  if $with_test_gui
    if (res = widget_text(main_widgets[1]))
      snd_display("widget_text of non-text widget: %s", res)
    end
    set_widget_text(channel_widgets(ind, 0)[2], "F")
    if (res = widget_text(channel_widgets(ind, 0)[2])) != "F"
      snd_display("set button label to F: %s?", res)
    end
    if (res = widget_text(sound_widgets(ind)[3])) != "no marks" and res != "no such mark"
      snd_display("C-x c w/o marks: %s?", res)
    end
  end
  add_mark(123)
  key(key_to_int(?u), 4)
  key(key_to_int(?6), 4)
  key(key_to_int(?j), 4)
  if $with_test_gui
    if (res = widget_text(sound_widgets(ind)[3])) != "no such mark"
      snd_display("C-u 6 C-j: %s?", res)
    end
  end
  key(key_to_int(?u), 4)
  key(key_to_int(?6), 4)
  key(key_to_int(?x), 4)
  key(key_to_int(?c), 0)
  if $with_test_gui
    if (res = widget_text(sound_widgets(ind)[3])) != "no such mark"
      snd_display("C-u 6 C-x c: %s?", res)
    end
  end
  close_sound(ind)
  #
  ns = new_sound
  v = make_vct(1000)
  unselect_all
  1000.times do |i|
    v[i] = 0.001 * i
  end
  vct2channel(v, 0, 1000, ns, 0)
  set_selection_member?(true, ns, 0)
  set_selection_position(200, ns, 0)
  set_selection_frames(300, ns, 0)
  delete_selection_and_smooth
  snd_test_neq(frames(ns, 0), 700, "delete_selection_and_smooth frames")
  snd_test_neq(sample(167, ns, 0), 0.167, "delete_selection_and_smooth 167")
  snd_test_neq(sample(234, ns, 0), 0.534, "delete_selection_and_smooth 234")
  snd_test_neq(sample(210, ns, 0), 0.406, "delete_selection_and_smooth 210")
  v1 = channel2vct
  maxdiff = 0.0
  mindiff = 10.0
  ls = v1[0]
  (1...700).each do |i|
    diff = v1[i] - ls
    ls = v1[i]
    if diff > maxdiff
      maxdiff = diff
    end
    if diff < mindiff
      mindiff = diff
    end
  end
  if mindiff < 0.0009
    snd_display(snd_format(mindiff, 0.0009, "<", "delete_selection_and_smooth min diff"))
  end
  if maxdiff > 0.007
    snd_display(snd_format(maxdiff, 0.007, ">", "delete_selection_and_smooth max diff"))
  end
  close_sound(ns)
  #
  ns = new_sound
  v = make_vct(1000)
  unselect_all
  1000.times do |i|
    v[i] = 0.001 * i
  end
  vct2channel(v, 0, 1000, ns, 0)
  delete_samples_and_smooth(200, 300, ns, 0)
  snd_test_neq(frames(ns, 0), 700, "delete_samples_and_smooth frames")
  snd_test_neq(sample(167, ns, 0), 0.167, "delete_samples_and_smooth 167")
  snd_test_neq(sample(234, ns, 0), 0.534, "delete_samples_and_smooth 234")
  snd_test_neq(sample(210, ns, 0), 0.406, "delete_samples_and_smooth 210")
  v1 = channel2vct
  maxdiff = 0.0
  mindiff = 10.0
  ls = v1[0]
  (1...700).each do |i|
    diff = v1[i] - ls
    ls = v1[i]
    if diff > maxdiff
      maxdiff = diff
    end
    if diff < mindiff
      mindiff = diff
    end
  end
  if mindiff < 0.0009
    snd_display(snd_format(mindiff, 0.0009, "<", "delete_samples_and_smooth min diff"))
  end
  if maxdiff > 0.007
    snd_display(snd_format(maxdiff, 0.007, ">", "delete_samples_and_smooth max diff"))
  end
  close_sound(ns)
  #
  old_beg = initial_beg
  old_dur = initial_dur
  old_show = show_full_duration
  $initial_graph_hook.reset_hook!
  #
  set_show_full_range(true)
  ns = open_sound("1a.snd")
  snd_test_neq(y_bounds(ns, 0), [-1.0, 1.0], "show_full_range 1.0 test")
  close_sound(ns)
  with_sound(:output, "test.snd", :clipped, false) do
    fm_violin_rb(0, 1, 440, 3.5)
  end
  ns = open_sound("test.snd")
  snd_test_neq(y_bounds(ns, 0), [-3.5, 3.5], "show_full_range 3.5 test")
  with_sound(:output, "test.snd", :clipped, false) do
    fm_violin_rb(0, 1, 440, 1.5)
  end
  update_sound(ns = find_sound("test.snd"))
  snd_test_neq(y_bounds(ns, 0), [-1.5, 1.5], "show_full_range 1.5 test")
  close_sound(ns)
  set_show_full_range(false)
  #
  set_show_full_duration(true)
  ns = open_sound("1.snd")
  ls = left_sample(ns, 0)
  rs = right_sample(ns, 0)
  fr = frames(ns, 0)
  snd_test_neq([fr, ls, rs], [220501, 0, 220501], "show_full_duration 1")
  close_sound(ns)
  set_show_full_duration(true)
  set_initial_beg(0.0)
  set_initial_dur(0.2)
  ns = open_sound("1.snd")
  ls = left_sample(ns, 0)
  rs = right_sample(ns, 0)
  fr = frames(ns, 0)
  snd_test_neq([fr, ls, rs], [220501, 0, 220501], "show_full_duration 2")
  close_sound(ns)
  set_show_full_duration(false)
  set_initial_beg(0.0)
  set_initial_dur(0.2)
  ns = open_sound("1.snd")
  ls = left_sample(ns, 0)
  rs = right_sample(ns, 0)
  fr = frames(ns, 0)
  snd_test_neq([fr, ls, rs], [220501, 0, 4410], "show_full_duration 3")
  close_sound(ns)
  set_initial_beg(2.0)
  set_initial_dur(1.0)
  ns = open_sound("1.snd")
  ls = left_sample(ns, 0)
  rs = right_sample(ns, 0)
  fr = frames(ns, 0)
  snd_test_neq([fr, ls, rs], [220501, 44100, 66150], "show_full_duration 4")
  close_sound(ns)
  set_initial_beg(old_beg)
  set_initial_dur(old_dur)
  set_show_full_duration(old_show)
  old_sync = sync_style
  set_sync_style(Sync_none)
  ns = open_sound("2.snd")
  snd_test_neq(sync(ns), 0, "Sync_none open")
  set_sync(1, ns)
  set_sync_style(Sync_by_sound)
  ns1 = open_sound("1a.snd")
  snd_test_eq(sync(ns1), 0, "Sync_by_sound open")
  snd_test_eq(sync(ns1), 1, "Sync_by_sound open")
  snd_test_neq(sync(ns), 1, "Sync_by_sound open")
  close_sound(ns1)
  close_sound(ns)
  set_sync_style(Sync_all)
  ns = open_sound("2.snd") 
  ns1 = open_sound("1a.snd")
  snd_test_neq(sync(ns), sync(ns1), "Sync_all open")
  snd_test_eq(sync(ns), 0, "Sync_all open")
  set_sync_style(Sync_none)
  ns2 = open_sound("oboe.snd")
  snd_test_neq(sync(ns), sync(ns1), "back to Sync_none open")
  snd_test_neq(sync(ns2), 0, "back to Sync_none open")
  close_sound(ns2)
  close_sound(ns1)
  close_sound(ns)
  set_sync_style(old_sync)
  #
  ind = view_sound("obtest.snd")
  delete_samples(0, 1000, ind, 0)
  if integer?((res = Snd.catch do save_sound(ind) end).first)
    snd_display("save_viewed_sound: %s", res)
  end
  if (res = edits(ind)) != [1, 0]
    snd_display("view read_only ignored: %s?", res)
  end
  close_sound(ind)
  #
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1)
  insert_silence(0, 150000)
  map_channel(lambda do |y| 0.5 end)
  env_sound([0, 0, 1, 1, 2, 0])
  fp(1.0, 0.3, 20)
  old_cursor = with_tracking_cursor
  set_with_tracking_cursor(true)
  play(selected_sound, :wait, true)
  set_with_tracking_cursor(old_cursor)
  close_sound(ind)
  #
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1)
  [150, 1500, 150000].each do |dur|
    insert_silence(0, dur)
    map_channel($init_channel)
    env_sound([0, 0, 1, 1, 2, 0])
    rd = make_sampler(frames - 1, ind, 0, -1)
    if (res = sampler_position(rd)) != (frames - 1)
      snd_display("sampler_position: %s?", res)
    end
    map_channel(lambda do |y| rd.call end)
    pos = 0
    e = make_env([0, 0, 1, 1, 2, 0], :length, dur + 1)
    scan_channel(lambda do |y|
                   if fneq(val = env(e), y)
                     snd_display("trouble in reverse read at %s %s %s", pos, val, y)
                     true
                   else
                     pos += 1
                     false
                   end
                 end)
    revert_sound
  end
  close_sound(ind)
  #
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1)
  insert_silence(0, 1000)
  map_channel($init_channel)
  env_sound([0, 0, 1, 1, 2, 0])
  scale_channel(0.0, 100, 200)
  rd = make_sampler(frames - 1, ind, 0, -1)
  map_channel(lambda do |y| rd.call end)
  pos = 0
  e = make_env([0, 0, 1, 1, 2, 0], :length, 1001)
  scan_channel(lambda do |y|
                 val = env(e)
                 if ((pos > 900 or pos <= 700) and fneq(val, y)) or
                     (pos > 700 and pos <= 900 and fneq(y, 0.0))
                   snd_display("trouble in reverse read 2 at %s %s %s", pos, val, y)
                   true
                 else
                   pos += 1
                   false
                 end
               end)
  close_sound(ind)
  #
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1)
  insert_silence(0, 150000)
  map_channel($init_channel)
  edpos = edit_position
  7.times do |i|
    if i == 5
      scale_channel(0.5, 1000, 12345)
    end
    env_sound([0, 0, 1, 1, 2.5, 0, 3, 1, 4, 0])
    case i
    when 1
      delete_samples(50, 100)
    when 2
      insert_samples(300, 100, make_vct!(100) do 0.5 end)
    when 3
      scale_channel(0.0, 1000, 1000)
    when 4
      vct2channel(make_vct!(100) do 0.5 end, 500, 100)
    when 6
      env_sound([0, 1, 1, 0], 10000, 2000)
    end
    rd = make_sampler(frames - 1, ind, 0, -1)
    map_channel(lambda do |y| rd.call end)
    rd = make_sampler(frames - 1, ind, 0, -1)
    map_channel(lambda do |y| rd.call end)
    old_rd = make_sampler(0, ind, 0, 1, edit_position(ind, 0) - 2)
    pos = 0
    scan_channel(lambda do |y|
                   if fneq(val = old_rd.call, y)
                     snd_display("trouble in reverse (%s) read at %s %s %s", i, pos, val, y)
                     true
                   else
                     pos += 1
                     false
                   end
                 end)
  end
  set_edit_position(edpos, ind, 0)
  close_sound(ind)
  #
  reader = false
  last_proc = false
  scan_again = lambda do
    result = false
    until sampler_at_end?(reader)
      if val = last_proc.call(reader.call)
        result = [val, sampler_position(reader) - 1]
        break
      end
    end
    result
  end
  my_scan_chan = lambda do |proc|
    if proc
      last_proc = proc
      reader = make_sampler(0)
    end
    scan_again.call
  end
  ind = open_sound("oboe.snd")
  set_cursor(1000, ind, 0)
  if fneq(res = sample(1000), sample())
    snd_display("sample no args: %s %s", sample(), res)
  end
  req = [true, 4423]
  res = my_scan_chan.call(lambda do |y| y > 0.1 end)
  if res != req
    snd_display("my_scan_chan: res %s != req %s?", res, req)
  end
  req = [true, 4463]
  res = scan_again.call
  if res != req
    snd_display("scan_again: res %s != req %s?", res, req)
  end
  set_cursor(1000)
  # FIXME (INFO)
  # set_sample(0.5) isn't possible
  set_sample(:undefined, 0.5)
  if fneq(res = sample(1000), 0.5)
    snd_display("set sample no arg: %s %s?", res, sample(0))
  end
  close_sound(ind)
end

def test_05_26
  ind = new_sound("test.snd")
  map_chan(lambda do |y| 1.0 end, 0, 1000)
  env_channel(make_env([0, 1, 1, 1], :scaler, 0.5, :length, 1001))
  check_maxamp(ind, 0.5, "simple scaler")
  check_env_vals("simple scaler", make_env([0, 1, 1, 1], :scaler, 0.5, :length, 1001))
  if edit_position == 2
    undo_edit
  else
    snd_display("env+scl was no-op")
  end
  env_channel(make_env([0, 1, 1, 1], :offset, 0.5, :length, 1001))
  check_maxamp(ind, 1.5, "simple scaler")
  check_env_vals("simple scaler", make_env([0, 1, 1, 1], :offset, 0.5, :length, 1001))
  if edit_position == 2
    undo_edit
  else
    snd_display("env+offset was no-op")
  end
  env_channel(make_env([0, 0, 1, 1, 2, 0], :offset, 0.5, :scaler, 2.0, :length, 1001))
  check_maxamp(ind, 2.5, "off+scl")
  check_env_vals("off+scl", make_env([0, 0, 1, 1, 2, 0], :offset, 0.5, :scaler, 2.0, :length, 1001))
  undo_edit
  env_channel(make_env([0, -0.5, 1, 0, 2, -1], :offset, 0.5, :scaler, 2.0, :length, 1001))
  check_maxamp(ind, 1.5, "off+scl #2")
  mx = -12.0
  scan_chan(lambda do |y|
              if y > mx
                mx = y
              end
              false
            end)
  snd_test_neq(mx, 0.5, "non abs max")
  check_env_vals("off+scl #2",
                 make_env([0, -0.5, 1, 0, 2, -1], :offset, 0.5, :scaler, 2.0, :length, 1001))
  undo_edit
  env_sound([0, 0.5, 1, 0.75, 2, 0.25], 0, frames, 32.0)
  check_maxamp(ind, 0.75, "xramp")
  check_env_vals("xramp", make_env([0, 0.5, 1, 0.75, 2, 0.25], :base, 32.0, :length, 1001))
  undo_edit
  env_channel_with_base([0, 0.5, 1, 0.75, 2, 0.25], 32.0)
  check_maxamp(ind, 0.75, "xramp1")
  check_env_vals("xramp1", make_env([0, 0.5, 1, 0.75, 2, 0.25], :base, 32.0, :length, 1001))
  close_sound(ind)
  #
  hlb = make_hilbert_transform(8)
  snd_test_neq(make_vct!(20) do |i| hilbert_transform(hlb, (i == 0 ? 1.0 : 0.0)) end,
               vct(0, -0.01, 0, -0.046, 0, -0.152, 0, -0.614, 0, 0.614,
                   0, 0.152, 0, 0.046, 0, 0.01, 0, 0, 0, 0),
               "hilbert_transform 8 impulse response")
  hlb = make_hilbert_transform(7)
  snd_test_neq(make_vct!(20) do |i| hilbert_transform(hlb, (i == 0 ? 1.0 : 0.0)) end,
               vct(-0.007, 0.0, -0.032, 0.0, -0.136, 0.0, -0.608, 0.0, 0.608, 0.0,
                   0.136, 0.0, 0.032, 0.0, 0.007, 0.0, 0.0, 0.0, 0.0, 0.0),
               "hilbert_transform 7 impulse response")
  ind = new_sound("test.snd")
  pad_channel(0, 1000)
  set_sample(100, 1.0)
  h = make_hilbert_transform(100)
  4.times do map_channel(lambda do |y| hilbert_transform(h, y) end) end
  res = sample(500)
  if (res - 0.98).abs > 0.01
    snd_display(snd_format(res, 0.01, ">", "hilbert impulse"))
  end
  set_sample(500, 0.0)
  res = maxamp(ind, 0)
  if res > 0.02
    snd_display(snd_format(res, 0.02, ">", "hilbert sidelobes"))
  end
  scale_channel(0.0)
  set_sample(100, 1.0)
  h = make_hilbert_transform(101)
  4.times do map_channel(lambda do |y| hilbert_transform(h, y) end) end
  res = sample(504)
  if (res - 0.98).abs > 0.01
    snd_display(snd_format(res, 0.01, ">", "hilbert 101 impulse"))
  end
  set_sample(504, 0.0)
  res = maxamp(ind, 0)
  if res > 0.02
    snd_display(snd_format(res, 0.02, ">", "hilbert 101 sidelobes"))
  end
  revert_sound
  pad_channel(0, 1000)
  set_sample(100, 1.0)
  lo = make_lowpass(PI * 0.1, 20)
  hi = make_highpass(PI * 0.1, 20)
  map_channel(lambda do |y| lowpass(lo, y) + highpass(hi, y) end)
  snd_test_neq(sample(120), 1.0, "lowpass+highpass impulse")
  set_sample(120, 0.0)
  snd_test_neq(maxamp(ind, 0), 0.0, "lowpass+highpass sidelobes")
  undo_edit(2)
  lo = make_bandpass(PI * 0.1, PI * 0.2, 20)
  hi = make_bandstop(PI * 0.1, PI * 0.2, 20)
  map_channel(lambda do |y| bandpass(lo, y) + bandpass(hi, y) end)
  snd_test_neq(sample(120), 1.0, "bandpass+bandstop impulse")
  set_sample(120, 0.0)
  snd_test_neq(maxamp(ind, 0), 0.0, "bandpass+bandstop sidelobes")
  close_sound(ind)
  # 
  ind = new_sound("test.snd")
  map_channel(lambda do |y| 1.0 - random(2.0) end, 0, 10000)
  f2 = make_bandpass_2(0.12 * PI, 0.15 * PI, 0.22 * PI, 0.25 * PI, 100)
  map_channel(lambda do |y| bandpass_2(f2, y) end)
  data = channel2vct
  undo_edit
  f1 = make_bandpass(0.12 * PI, 0.15 * PI, 100)
  f2 = make_bandpass(0.22 * PI, 0.25 * PI, 100)
  map_channel(lambda do |y| bandpass(f1, y) + bandpass(f2, y) end)
  data1 = channel2vct
  vct_subtract!(data, data1)
  res = vct_peak(data)
  if res > 0.00001
    snd_display(snd_format(res, 0.00001, "fir_filter 2"))
  end
  undo_edit
  close_sound(ind)
  #
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1, "ramp re-order tests", 100)
  map_channel(lambda do |y| 1.0 end)
  [["ramp-xramp", true,
      lambda do
        env_sound([0, 0, 1, 1, 2, 0])
        env_sound([0, 0, 1, 1], 0, 100, 2.0)
      end,
      lambda do
        env_sound([0, 0, 1, 1], 0, 100, 2.0)
        env_sound([0, 0, 1, 1, 2, 0])
      end],
    ["ramp2-xramp (1)", true,
      lambda do
        env_sound([0, 0, 1, 1, 2, 0])
        env_sound([0, 0, 1, 1, 3, 0])
        env_sound([0, 0, 1, 1], 0, 100, 2.0)
      end,
      lambda do 
        env_sound([0, 0, 1, 1, 2, 0])
        env_sound([0, 0, 1, 1], 0, 100, 2.0)
        env_sound([0, 0, 1, 1, 3, 0])
      end],
    ["ramp2-xramp (2)", true,
      lambda do
        env_sound([0, 0, 1, 1, 2, 0])
        env_sound([0, 0, 1, 1])
        env_sound([0, 0, 1, 1, 3, 0], 0, 100, 2.0)
      end,
      lambda do 
        env_sound([0, 0, 1, 1, 3, 0], 0, 100, 2.0)
        env_sound([0, 0, 1, 1, 2, 0])
        env_sound([0, 0, 1, 1])
      end],
    ["xramp2-ramp (1)", true,
      lambda do
        env_sound([0, 0, 1, 1, 2, 0], 0, 100, 2.0)
        env_sound([0, 0, 1, 1])
        env_sound([0, 0, 1, 1, 3, 0], 0, 100, 3.0)
      end,
      lambda do 
        env_sound([0, 0, 1, 1, 2, 0], 0, 100, 2.0)
        env_sound([0, 0, 1, 1, 3, 0], 0, 100, 3.0)
        env_sound([0, 0, 1, 1])
      end],
    ["xramp2-ramp (2)", true,
      lambda do
        env_sound([0, 0, 1, 1, 2, 0], 0, 100, 2.0)
        env_sound([0, 0, 1, 1, 3, 0])
        env_sound([0, 0, 1, 1], 0, 100, 3.0)
      end,
      lambda do 
        env_sound([0, 0, 1, 1, 3, 0])
        env_sound([0, 0, 1, 1, 2, 0], 0, 100, 2.0)
        env_sound([0, 0, 1, 1], 0, 100, 3.0)
      end],
    ["ramp4", true,
      lambda do
        env_sound([0, 0, 1, 1])
        env_sound([0, 0, 1, 1, 2, 0])
        env_sound([0, 0, 1, 1, 3, 0])
        env_sound([0, 0, 1, 1, 4, 0])
      end,
      lambda do 
        env_sound([0, 0, 1, 1, 4, 0])
        env_sound([0, 0, 1, 1, 2, 0])
        env_sound([0, 0, 1, 1, 3, 0])
        env_sound([0, 0, 1, 1])
      end]].each do |name, try_scale, f1, f2|
    edpos = edit_position(ind, 0)
    f1.call
    v1 = channel2vct(0, 100, ind, 0)
    set_edit_position(edpos, ind, 0)
    f2.call
    v2 = channel2vct(0, 100, ind, 0)
    snd_test_neq(v1, v2, "env reordering test %s", name)
    set_edit_position(edpos, ind, 0)
    if try_scale
      scale_by(2.0)
      f1.call
      v1 = channel2vct(0, 100, ind, 0)
      set_edit_position(edpos, ind, 0)
      f2.call
      scale_by(2.0)
      v2 = channel2vct(0, 100, ind, 0)
      snd_test_neq(v1, v2, "scaled (2) env reordering test %s", name)
      set_edit_position(edpos, ind, 0)
      f1.call
      scale_by(0.5)
      v1 = channel2vct(0, 100, ind, 0)
      set_edit_position(edpos, ind, 0)
      scale_by(0.5)
      f2.call
      v2 = channel2vct(0, 100, ind, 0)
      snd_test_neq(v1, v2, "scaled (0.5) env reordering test %s", name)
      set_edit_position(edpos, ind, 0)
    end
  end
  close_sound(ind)
  # offset channel
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1, "offset tests", 10)
  offset_channel(0.1)
  snd_test_neq(channel2vct(0, 10), Vct.new(10, 0.1), "offset_channel (0.1)")
  offset_channel(-0.2, 5, 5)
  snd_test_neq(channel2vct(0, 10),
               vct(0.1, 0.1, 0.1, 0.1, 0.1, -0.1, -0.1, -0.1, -0.1, -0.1),
               "offset_channel (-0.1)")
  undo_edit
  offset_channel(0.9, 0, 10, ind, 0)
  snd_test_neq(channel2vct(0, 10), Vct.new(10, 1.0), "offset_channel (1.0)")
  revert_sound(ind)
  # sine_env and sine_ramp...
  map_channel($init_channel)
  sine_ramp(0.0, 1.0)
  snd_test_neq(channel2vct,
               vct(0.000, 0.024, 0.095, 0.206, 0.345, 0.500, 0.655, 0.794, 0.905, 0.976),
               "sine_ramp 0 1")
  revert_sound(ind)
  offset_channel(1.0)
  sine_ramp(1.0, 0.0)
  snd_test_neq(channel2vct,
               vct(1.000, 0.976, 0.905, 0.794, 0.655, 0.500, 0.345, 0.206, 0.095, 0.024),
               "sine_ramp 1 0")
  close_sound(ind)
  # 
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1, "sine_env tests", 100)
  # map_channel($init_channel)
  map_channel(lambda do |y| 1.0 end)
  sine_env_channel([0, 0, 1, 1, 2, -0.5, 3, 1])
  snd_test_neq(channel2vct(20, 10),
               vct(0.664, 0.708, 0.750, 0.790, 0.827, 0.862, 0.893, 0.921, 0.944, 0.964),
               "sine_env_channel 0a")
  snd_test_neq(channel2vct(60, 10),
               vct(-0.381, -0.417, -0.446, -0.47, -0.486, -0.497, -0.5, -0.497, -0.486, -0.47),
               "sine_env_channel 0b")
  snd_test_neq(edit_position(ind, 0), 2, "as_one_edit sine_env_channel")
  revert_sound(ind)
  offset_channel(-1.0)
  sine_env_channel([0, 0, 1, 1, 2, 1, 3, 0], 40, 20)
  snd_test_neq(channel2vct(40, 20),
               vct(0, -0.05, -0.188, -0.389, -0.611, -0.812, -0.95, -1, -1, -1,
                   -1, -1, -1, -1, -1, -0.95, -0.812, -0.611, -0.389, -0.188),
               "off+sine_env a")
  snd_test_neq(channel2vct(30, 10), make_vct(10, -1.0), "off+sine_env b")
  revert_sound(ind)
  scale_by(0.0)
  dither_channel
  mx = maxamp
  if mx < 0.00003 or mx > 0.0001
    snd_display("dithering: %s", mx)
  end
  revert_sound(ind)
  map_channel(ring_mod(10, [0, 0, 1, hz2radians(100)]))
  osc_formants(0.99, vct(400, 800, 1200), vct(400, 800, 1200), vct(4, 2, 3))
  map_channel(zecho(0.5, 0.75, 6, 10.0))
  map_channel(flecho(0.5, 0.9))
  filtered_env([0, 0, 1, 1, 2, 0])
  map_channel(formant_filter(0.99, 2400))
  map_channel(comb_filter(0.8, 32))
  map_channel(zcomb(0.8, 32, [0, 0, 1, 10]))
  map_channel(notch_filter(0.8, 32))
  ind1 = open_sound("now.snd")
  select_sound(ind1)
  snd_test_neq(maxamp, 0.309, "squelch_vowels init")
  squelch_vowels
  snd_test_neq(maxamp, 0.047, "squelch_vowels maxamp")
  select_sound(ind)
  map_channel(cross_synthesis(ind1, 0.5, 128, 6.0))
  revert_sound(ind1)
  fft_edit(40, 8000)
  fft_squelch(0.1)
  close_sound(ind)
  revert_sound(ind1)
  scramble_channel(0.01)
  revert_sound(ind1)
  close_sound(ind1)
end

def test_05_27
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1, "special env tests", 100)
  map_channel($init_channel)
  blackman4_ramp(0.0, 1.0)
  vals = channel2vct
  undo_edit
  blackman4_env_channel([0, 0, 1, 1])
  unless vequal(res = channel2vct, vals)
    snd_display("blackman4_env_channel/ramp:\n# %s\n# %s", vals, res)
  end
  undo_edit
  blackman4_ramp(0.0, 1.0, 0, 50)
  vals = channel2vct
  undo_edit
  blackman4_env_channel([0, 0, 1, 1, 2, 1])
  unless vequal(res = channel2vct, vals)
    snd_display("blackman4_env_channel/ramp 1:\n# %s\n# %s", vals, res)
  end
  undo_edit
  blackman4_env_channel([0, 0, 1, 1, 2, -0.5, 3, 0])
  unless vequal(res = channel2vct(60, 10),
                vct(-0.109, -0.217, -0.313, -0.392, -0.451, -0.488, -0.499, -0.499, -0.499, -0.499))
    snd_display("blackman4_env_channel to -0.5: %s", res)
  end
  undo_edit
  # 
  ramp_squared(0.0, 1.0)
  vals = channel2vct
  undo_edit
  env_squared_channel([0, 0, 1, 1])
  unless vequal(res = channel2vct, vals)
    snd_display("env_squared/ramp:\n# %s\n# %s", vals, res)
  end
  undo_edit
  ramp_squared(0.0, 1.0, true, 0, 50)
  vals = channel2vct
  undo_edit
  env_squared_channel([0, 0, 1, 1, 2, 1])
  unless vequal(res = channel2vct, vals)
    snd_display("env_squared/ramp 1:\n# %s\n# %s", vals, res)
  end
  undo_edit
  env_squared_channel([0, 0, 1, 1, 2, -0.5, 3, 0])
  unless vequal(res = channel2vct(60, 10),
                vct(-0.450, -0.466, -0.478, -0.488, -0.494, -0.499, -0.500, -0.500, -0.498, -0.496))
    snd_display("env_squared to -0.5: %s", res)
  end
  undo_edit
  env_squared_channel([0, 0, 1, 1, 2, -0.5, 3, 0], false)
  unless vequal(res = channel2vct(60, 10),
                vct(-0.004, -0.080, -0.158, -0.240, -0.324, -0.410, -0.500, -0.500, -0.498, -0.496))
    snd_display("env_squared unsymmetric to -0.5: %s", res)
  end
  undo_edit
  # 
  ramp_squared(0.0, 1.0)
  vals = channel2vct
  undo_edit
  env_expt_channel([0, 0, 1, 1], 2)
  unless vequal(res = channel2vct, vals)
    snd_display("env_expt2/ramp:\n# %s\n# %s", vals, res)
  end
  undo_edit
  env_squared_channel([0, 0, 1, 1, 2, -0.5, 3, 0])
  vals = channel2vct
  undo_edit
  env_expt_channel([0, 0, 1, 1, 2, -0.5, 3, 0], 2.0)
  unless vequal(res = channel2vct, vals)
    snd_display("env_expt2/env_squared:\n# %s\n# %s", vals, res)
  end
  undo_edit
  env_squared_channel([0, 0, 1, 1, 2, -0.5, 3, 0], false)
  vals = channel2vct
  undo_edit
  env_expt_channel([0, 0, 1, 1, 2, -0.5, 3, 0], 2.0, false)
  unless vequal(res = channel2vct, vals)
    snd_display("env_expt2/env_squared unsymmetric:\n# %s\n# %s", vals, res)
  end
  undo_edit
  #
  ramp_expt(0.0, 1.0, 32.0)
  vals = channel2vct
  undo_edit
  env_expt_channel([0, 0, 1, 1], 32.0)
  unless vequal(res = channel2vct, vals)
    snd_display("env_expt/ramp 32:\n# %s\n# %s", vals, res)
  end
  undo_edit
  ramp_expt(0.0, 1.0, 32.0, false, 0, 50)
  vals = channel2vct
  undo_edit
  env_expt_channel([0, 0, 1, 1, 2, 1], 32.0)
  unless vequal(res = channel2vct, vals)
    snd_display("env_expt/ramp 1 32.0:\n# %s\n# %s", vals, res)
  end
  undo_edit
  ramp_expt(0.0, 1.0, 0.1)
  vals = channel2vct
  undo_edit
  env_expt_channel([0, 0, 1, 1], 0.1)
  unless vequal(res = channel2vct, vals)
    snd_display("env_expt/ramp 0.1:\n# %s\n# %s", vals, res)
  end
  undo_edit
  env_expt_channel([0, 0, 1, 1, 2, -0.5, 3, 0], 12.0)
  unless vequal(res = channel2vct(30, 10),
                vct(0.319, 0.472, 0.691, 1.000, 0.537, 0.208, -0.022, -0.182, -0.291, -0.365))
    snd_display("env_expt to -0.5 12.0\n# %s\n# %s", vals, res)
  end
  undo_edit
  env_expt_channel([0, 0, 1, 1, 2, -0.5, 3, 0], 12.0, false)
  unless vequal(res = channel2vct(30, 10),
                vct(0.319, 0.472, 0.691, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000))
    snd_display("env_expt ot -0.5 12.0 unsymmetric:\n# %s\n# %s", vals, res)
  end
  undo_edit
  close_sound(ind)
  #
  ind0 = open_sound("oboe.snd")
  ind1 = open_sound("pistol.snd")
  if res = channel_clipped?(ind0, 0)
    snd_display("channel_clipped? oboe.snd: %s?", res)
  end
  scale_to(1.5, ind0, 0)
  req = 4503
  res = channel_clipped?(ind0, 0)
  if res != req
    snd_display("channel_clipped? after scale: res %s != req %s?", res, req)
  end
  revert_sound(ind0)
  4.times do ramp_channel(0.0, 1.0, 0, false, ind1, 0) end
  make_selection(1000, 2000, ind1, 0)
  set_sync(1, ind0)
  set_selected_sound(ind0)
  env_selection([0, 0, 1, 1])
  if (res0 = edit_position(ind0, 0)) != 0 or (res1 = edit_position(ind1)) != 5
    snd_display("selection override of sync field: %s %s?", res0, res1)
  end
  env_sound([0, 0, 1, 1, 2, 0])
  if (res0 = edit_position(ind0, 0)) != 1 or (res1 = edit_position(ind1)) != 5
    snd_display("sync field over selection: %s %s?", res0, res1)
  end
  close_sound(ind0)
  close_sound(ind1)
end

def test_05
  $clmtest = 0
  test_05_00 if $with_test_gui # no set_x_axis_label
  test_05_01
  test_05_02
  test_05_03
  test_05_04
  test_05_05
  test_05_06
  test_05_07
  test_05_08
  test_05_09
  test_05_10 if $with_test_gui
  test_05_11
  test_05_12 if $with_test_gui
  test_05_13
  test_05_14 if $with_test_gui
  test_05_15
  test_05_16
  test_05_17
  test_05_18
  test_05_20
  test_05_22
  test_05_23 if $with_test_gui # load("s61.rb") -> set_transform_size(0)
  test_05_24
  test_05_25
  test_05_26
  test_05_27
end

# ---------------- test 06: vcts ----------------

def test_06
  # setting print_length (12) and vct_print_length (10) to the same size
  set_print_length(print_length)
  # 
  v0 = make_vct(10)
  v1 = Vct.new(10)
  vlst = make_vct(3)
  unless vct?(v0)
    snd_display("v0 is not a vct?")
  end
  unless v0.kind_of?(Vct)
    snd_display("v0 is not kind_of? Vct?")
  end
  if v0 == 10
    snd_display("v0 is 10!?")
  end
  if vct?(10)
    snd_display("10 is a vct?")
  end
  if v0.length != 10
    snd_display("v0 length = %s?", v0.length)
  end
  vct_fill!(v0, 1.0)
  v1.fill(0.5)
  if v0.eql?(v1)
    snd_display("vct %s.eql?(%s)?", v0, v1)
  end
  if v0 == v1
    snd_display("vct %s == %s?", v0, v1)
  end
  v2 = v1
  v3 = Vct.new(10)
  v4 = make_vct(3)
  unless v1.eql?(v2)
    snd_display("vct not %s.eql?(%s)?", v1, v2)
  end
  vct_fill!(v3, 0.5)
  unless v2.eql?(v1)
    snd_display("vct not %s.eql?(%s)?", v2, v1)
  end
  if v4.eql?(v1)
    snd_display("len diff vct %s.eql?(%s)?", v4, v1)
  end
  v3[0] = 1.0
  if fneq(v3[0], 1.0)
    snd_display("vct_set!: %s", v3[0])
  end
  vlst[1] = 0.1
  unless vequal(res = vct2list(vlst), [0.0, 0.1, 0.0])
    snd_display("vct2list: %s?", res)
  end
  vect = [0.0, 1.0, 2.0, 3.0]
  v123 = vct(0.0, 1.0, 2.0, 3.0)
  v2 = vect.to_vct
  v3 = v2
  str = format("%s", v2.to_s)
  str1 = format("%s", make_vct(32).to_s)
  unless vct?(res = vector2vct(make_array(0)))
    snd_display("vector2vct empty vect: %s", res)
  end
  unless vct?(res = make_array(0).to_vct)
    snd_display("make_array(0).to_vct empty vect: %s", res)
  end
  if str != "#<vct[len=4]: 0.000 1.000 2.000 3.000>"
    snd_display("vct print:\n# %s\n# %s?", str, v2.to_s)
  end
  if print_length == 12 and
      str1 != "#<vct[len=32]: 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 ...>"
    snd_display("vct(32) print: %s?", str1)
  end
  unless vequal(v123, v2)
    snd_display("vector2vct: %s", v2)
  end
  unless (res = vct2vector(v123)) == vect
    snd_display("vct2vector:\n# %s\n# %s", vect, res)
  end
  unless (res = v123.to_a) == vect
    snd_display("v123.to_a:\n# %s\n# %s", vect, res)
  end
  unless v3.eql?(v2)
    snd_display("vct=? %s %s", v2, v3)
  end
  if v2.length != 4
    snd_display("vector2vct length: %s?", v2.lenght)
  end
  if fneq(v2[2], 2.0)
    snd_display("vector2vct: %s?", v2)
  end
  vct_move!(v2, 0, 2)
  if fneq(v2[0], 2.0)
    snd_display("vct_move!: %s?", v2)
  end
  v2 = Vct.new(4) do |i| i end
  v2.move!(3, 2, true)
  if fneq(v2[3], 2.0) or fneq(v2[2], 1.0)
    snd_display("vct_move! back: %s?", v2)
  end
  unless vequal(vct(4, 3, 2, 1), res = vct_reverse!(vct(1, 2, 3, 4)))
    snd_display("vct_reverse: %s?", res)
  end
  unless vequal(vct(3, 2, 1), res = vct(1, 2, 3).reverse)
    snd_display("vct_reverse: %s?", res)
  end
  unless vequal(vct(2, 1), res = vct_reverse!(vct(1, 2)))
    snd_display("vct_reverse: %s?", res)
  end
  unless vequal(vct(1), res = vct(1).reverse)
    snd_display("vct_reverse: %s?", res)
  end
  unless vequal(vct(4, 3, 2, 1), res = vct(1, 2, 3, 4).reverse(4))
    snd_display("vct_reverse: %s?", res)
  end
  unless vequal(vct(3, 2, 1), res = vct_reverse!(vct(1, 2, 3), 3))
    snd_display("vct_reverse: %s?", res)
  end
  unless vequal(vct(2, 1), res = vct(1, 2).reverse(2))
    snd_display("vct_reverse: %s?", res)
  end
  unless vequal(vct(1), res = vct_reverse!(vct(1), 1))
    snd_display("vct_reverse: %s?", res)
  end
  #
  vv0 = Vct.new(3)
  if (res = Snd.catch do vct_ref(vv0, 10) end).first != :out_of_range
    snd_display("vct_ref high index: %s", res)
  end
  if (res = Snd.catch do vv0[-4] end).first != :out_of_range
    snd_display("[] low index: %s", res)
  end
  if (res = Snd.catch do vct_set!(vv0, 10, 1.0) end).first != :out_of_range
    snd_display("vct_set! high index: %s", res)
  end
  if (res = Snd.catch do vv0[-1] = 1.0 end).first != :out_of_range
    snd_display("[]= low index: %s", res)
  end
  if (res = Snd.catch do vct_move!(vv0, 10, 0, true) end).first != :out_of_range
    snd_display("vct_move! high index: %s", res)
  end
  if (res = Snd.catch do vv0.move(0, 10, true) end).first != :out_of_range
    snd_display("v.move high 2 index: %s", res)
  end
  if (res = Snd.catch do vct_move!(vv0, -10, 0, false) end).first != :out_of_range
    snd_display("vct_move! back high index: %s", res)
  end
  if (res = Snd.catch do vv0.move!(0, -10, false) end).first != :out_of_range
    snd_display("v.move! back high 2 index: %s", res)
  end
  10.times do |i|
    if fneq(v0[i], 1.0)
      snd_display("fill v0[%s] = %s?", i, v0[i])
    end
    if fneq(v1[i], 0.5)
      snd_display("preset v1[%s] = %s?", i, v1[i])
    end
  end
  # add
  v0.add(v1).each_with_index do |x, i|
    if fneq(x, 1.5)
      snd_display("v0.add[%s] = %s?", i, x)
    end
  end
  (v0 + v1).each_with_index do |x, i|
    if fneq(x, 1.5)
      snd_display("v0 + v1[%s] = %s?", i, x)
    end
  end
  vct_add!(v0, v1)
  v0.each_with_index do |x, i|
    if fneq(x, 1.5)
      snd_display("add v0[%s] = %s?", i, x)
    end
  end
  # subtract
  v0.subtract(v1).each_with_index do |x, i|
    if fneq(x, 1.0)
      snd_display("v0.subtract[%s] = %s?", i, x)
    end
  end
  (v0 - v1).each_with_index do |x, i|
    if fneq(x, 1.0)
      snd_display("v0 - v1 [%s] = %s?", i, x)
    end
  end
  vct_subtract!(v0, v1)
  v0.each_with_index do |x, i|
    if fneq(x, 1.0)
      snd_display("subtract v0[%s] = %s?", i, x)
    end
  end
  # dup, vct_copy
  v0.dup.each_with_index do |x, i|
    if fneq(x, 1.0)
      snd_display("v0.dup[%s] = %s?", i, x)
    end
  end
  v2 = vct_copy(v0)
  v2.each_with_index do |x, i|
    if fneq(x, 1.0)
      snd_display("copy v2[%s] = %s?", i, x)
    end
  end
  # scale
  v2.scale(5.0).each_with_index do |x, i|
    if fneq(x, 5.0)
      snd_display("v2.scale[%s] = %s?", i, x)
    end
  end
  (v2 * 5.0).each_with_index do |x, i|
    if fneq(x, 5.0)
      snd_display("v2 * 5.0 [%s] = %s?", i, x)
    end
  end
  vct_scale!(v2, 5.0)
  v2.each_with_index do |x, i|
    if fneq(x, 5.0)
      snd_display("scale v2[%s] = %s?", i, x)
    end
  end
  # offset
  v0.offset(-1.0).each_with_index do |x, i|
    if fneq(x, 0.0)
      snd_display("v0.offset[%s] = %s?", i, x)
    end
  end
  (v0 + -1.0).each_with_index do |x, i|
    if fneq(x, 0.0)
      snd_display("v0 + -1.0 [%s] = %s?", i, x)
    end
  end
  vct_offset!(v0, -1.0)
  v0.each_with_index do |x, i|
    if fneq(x, 0.0)
      snd_display("offset v0[%s] = %s?", i, x)
    end
  end
  # multiply
  v2.multiply(v1).each_with_index do |x, i|
    if fneq(x, 2.5)
      snd_display("v2.multiply[%s] = %s?", i, x)
    end
  end
  (v2 * v1).each_with_index do |x, i|
    if fneq(x, 2.5)
      snd_display("v2 * v1 [%s] = %s?", i, x)
    end
  end
  vct_multiply!(v2, v1)
  v2.each_with_index do |x, i|
    if fneq(x, 2.5)
      snd_display("multiply v2[%s] = %s?", i, x)
    end
  end
  # 
  if fneq(vct_peak(v2), 2.5)
    snd_display("peak of v2 is %s?", vct_peak(v2))
  end
  if fneq(v2.peak, 2.5)
    snd_display("v2.peak is %s?", vct_peak(v2))
  end
  v2[5] = 123.0
  if fneq(vct_peak(v2), 123.0)
    snd_display("set peak of v2 is %s?", vct_peak(v2))
  end
  if fneq(v2.peak, 123.0)
    snd_display("v2.peak is %s?", vct_peak(v2))
  end
  vn = Vct.new(32) do |i| i end
  vb = make_vct(64)
  vs = make_vct(3)
  vss = Vct.new(1)
  vnew = vct_subseq(vn, 3)
  if fneq(vnew[0], 3.0)
    snd_display("vct_subseq[3:] %s?", vneq[0])
  end
  if vnew.length != 29
    snd_display("vct_subseq[3:] length %s?", vnew.length)
  end
  vnew = vn.subseq(3, 8)
  if fneq(vnew[0], 3.0)
    snd_display("v.subseq[3:8] %s?", vneq[0])
  end
  if vnew.length != 6
    snd_display("v.subseq[3:8] length %s?", vnew.length)
  end
  vct_subseq(vn, 3, 3, vs)
  if fneq(vs[0], 3.0) or fneq(vs[1], 0.0) or fneq(vs[2], 0.0)
    snd_display("vct_subseq[3:3->vs] %s?", vs)
  end
  vn.subseq(0, 32, vs)
  if vs.length != 3
    snd_display("v.subseq[:32->vs] length %s?", vs.length)
  end
  vn.subseq(2, 3, vss)
  if fneq(vss[0], 2.0)
    snd_display("v.subseq[2:3->vss] %s?", vss[0])
  end
  vb[8] = 123.0
  vct_subseq(vn, 1, 8, vb)
  if fneq(vb[0], 1.0)
    snd_display("vct_subseq[1:8->vb] %s?", vb[0])
  end
  if fneq(vb[8], 123.0)
    snd_display("vct_subseq[1:8->vb][8] %s?", vb[8])
  end
  # vct_add, vct_multiply (vct+, vct*)
  v1 = Vct.new(3, 0.1)
  v2 = make_vct(4, 0.2)
  unless vequal(res = vct_add(v1.dup, v2), vct(0.3, 0.3, 0.3))
    snd_display("vct_add 0.1 0.2: %s?", res)
  end
  unless vequal(res = v1 + v2, vct(0.3, 0.3, 0.3))
    snd_display("v1 + v2 0.1 0.2: %s?", res)
  end
  v1[1] = 0.3
  unless vequal(res = vct_add(v1.dup, v2), vct(0.3, 0.5, 0.3))
    snd_display("vct_add 0.1 0.2 (1): %s?", res)
  end
  unless vequal(res = v1 + v2, vct(0.3, 0.5, 0.3))
    snd_display("v1 + v2 0.1 0.2 (1): %s?", res)
  end
  unless vequal(res = vct_add(v1.dup, 2.0), vct(2.1, 2.3, 2.1))
    snd_display("vct_add 0.1 2.0: %s?", res)
  end
  unless vequal(res = v1 + 2.0, vct(2.1, 2.3, 2.1))
    snd_display("v1 + 2.0 0.1 2.0: %s?", res)
  end
  unless vequal(res = vct_add(2.0, v1.dup), vct(2.1, 2.3, 2.1))
    snd_display("vct_add 0.1 2.0 (1): %s?", res)
  end
  unless vequal(res = 2.0 + v1, vct(2.1, 2.3, 2.1))
    snd_display("2.0 + v1 0.1 2.0 (1): %s?", res)
  end
  unless vequal(res = vct_multiply(2.0, v1.dup), vct(0.2, 0.6, 0.2))
    snd_display("vct_multiply 2.0: %s?", res)
  end
  unless vequal(res = 2.0 * v1, vct(0.2, 0.6, 0.2))
    snd_display("2.0 * v1 2.0: %s?", res)
  end
  unless vequal(res = vct_multiply(v1.dup, 2.0), vct(0.2, 0.6, 0.2))
    snd_display("vct_multiply 2.0 (1): %s?", res)
  end
  unless vequal(res = v1 * 2.0, vct(0.2, 0.6, 0.2))
    snd_display("v1 * 2.0 2.0 (1): %s?", res)
  end
  unless vequal(res = vct_multiply(v1.dup, v2), vct(0.02, 0.06, 0.02))
    snd_display("vct_multiply v1 v2: %s?", res)
  end
  unless vequal(res = v1 * v2, vct(0.02, 0.06, 0.02))
    snd_display("v1 * v2 v1 v2: %s?", res)
  end
  # 
  v0.map do |val| PI end.each_with_index do |x, i|
    if fneq(x, PI)
      snd_display("v0.map[%s] = %s?", i, x)
    end
  end
  vct_map!(v0, lambda do | | 1.0 end)
  v0.each_with_index do |x, i|
    if fneq(x, 1.0)
      snd_display("map v0[%s] = %s?", i, x)
    end
  end
  # 
  if fneq(vct(1.0, 2.0, 3.0)[1], 2.0)
    snd_display("vct(...) = %s?", vct(1.0, 2.0, 3.0)[1])
  end
  v1 = [1, 2, 3, 4].to_vct
  if fneq(v1[1], 2.0)
    snd_display("v1[1] = %s?", v1[1])
  end
  # 
  ind = open_sound("oboe.snd")
  set_speed_control(0.5, ind)
  play(selected_sound, :wait, true)
  apply_controls
  revert_sound
  reset_controls(ind)
  # 
  # try some special cases
  #
  apply_controls
  if edit_position(ind) != 0
    snd_display("apply_controls with no change: %s: %s", edits(ind), edit_tree(ind))
  end
  set_speed_control(-1.0, ind)
  apply_controls
  if $with_test_gui and edit_position(ind) != 1
    snd_display("apply_controls with srate -1.0: %s: %s", edits(ind), edit_tree(ind))
  end
  if ((res0 = frames(ind, 0)) - (res1 = frames(ind, 0, 0))).abs > 2
    snd_display("apply_controls srate -1.0 lengths: %s %s", res0, res1)
  end
  res1 = sample(9327)
  if fneq(res0 = maxamp, 0.147) or res1.abs < 0.01
    snd_display("apply_controls srate -1.0 samples: %s %s?", res0, res1)
  end
  if fneq(res = speed_control(ind), 1.0)
    snd_display("apply_controls -1.0 -> %s?", res)
  end
  ctr = 0
  $dac_hook.add_hook!("snd-test") do |data|
    ctr += 1
    if ctr >= 3
      set_playing(false)
    end
  end
  play(selected_sound, :wait, true)
  if ctr != 3
    snd_display("ctr after dac_hook: %s", ctr)
  end
  set_speed_control(1.5)
  apply_controls
  $dac_hook.reset_hook!
  revert_sound
  set_speed_control(1.5)
  ctr = 0
  $dac_hook.add_hook!("snd-test") do |data|
    ctr += 1
    if ctr == 3
      apply_controls
    end
  end
  play(selected_sound, :wait, true)
  if edit_position(ind, 0) != 1
    snd_display("apply_controls from hook: %s %s", edits(ind), edit_tree(ind))
  end
  $dac_hook.reset_hook!
  revert_sound
  set_speed_control(1.5)
  stop_playing
  $after_apply_controls_hook.add_hook!("snd-test") do |s|
    if (res = Snd.catch do apply_controls end).first != :cannot_apply_controls
      snd_display("after_apply_controls_hook: recursive attempt apply_controls: %s", res)
    end
  end
  apply_controls
  $after_apply_controls_hook.reset_hook!
  $dac_hook.add_hook!("snd-test") do |s|
    if (res = Snd.catch do apply_controls end).first != :cannot_apply_controls
      snd_display("dac_hook: recursive attempt apply_controls: %s", res)
    end
  end
  $dac_hook.reset_hook!
  revert_sound
  close_sound(ind)
  # 
  # Vct.new.map twice, Vct.new twice, and vct_map! twice
  # 
  v1 = Vct.new(32)
  v1.map! do
    v2 = Vct.new(3)
    v2.map! do 0.1 end
    v2.first
  end
  if fneq(v1[12], 0.1)
    snd_display("v.map! twice: %s?", v1[12])
  end
  Vct.new(32) do Vct.new(3) do 0.1 end.first end
  if fneq(v1[12], 0.1)
    snd_display("Vct.new twice: %s?", v1[12])
  end
  v1 = make_vct(32)
  vct_map!(v1, lambda do | |
             v2 = make_vct(3)
             vct_map!(v2, lambda do | | 0.1 end)
             vct_ref(v2, 0)
           end)
  if fneq(v1[12], 0.1)
    snd_display("vct_map! twice: %s?", v1[12])
  end
  hi = make_vct(3)
  if (res = Snd.catch do vct_subseq(hi, 1, 0) end).first != :out_of_range
    snd_display("vct_subseq 1 0: %s", res.inspect)
  end
  unless vct?(vct())
    snd_display("vct -> %s?", vct().inspect)
  end
  unless vct?(res = make_vct(0))
    snd_display("make_vct(0) -> %s?", res.inspect)
  end
  ho = make_vct(3)
  vct_add!(hi, ho, 4)
  v0 = make_vct(5, 0.1)
  v1 = make_vct(6, 0.2)
  v0.add!(v1, 2)
  unless vequal(v0, [0.1, 0.1, 0.3, 0.3, 0.3].to_vct)
    snd_display("v.add! + offset: %s?", v0)
  end
  # 
  # vct methods
  # 
  if (v1 = Vct.new(10)) != (v2 = make_vct(10))
    snd_display("Vct.new 0.000: %s %s?", v1, v2)
  end
  if (v1 = Vct.new(10, 3.14)) != (v2 = make_vct(10, 3.14))
    snd_display("Vct.new 3.140: %s %s?", v1, v2)
  end
  v1 = Vct.new(10) do |i| i * 0.01 end
  v2 = make_vct(10)
  ctr = -1
  vct_map!(v2, lambda do | | (ctr += 1) * 0.01 end)
  if v1 != v2
    snd_display("Vct.new 0.000...0.090: %s %s?", v1, v2)
  end
  if vct_ref(v1, 8) != v2[8] or v2[8] != 0.08
    snd_display("Vct#[]: %s %s?", vct_ref(v1, 8), v2[8])
  end
  vct_set!(v1, 8, 0.5)
  v2[8] = 0.5
  if vct_ref(v1, 8) != v2[8] or v2[8] != 0.5
    snd_display("Vct#[]=: %s %s?", vct_ref(v1, 8), v2[8])
  end
  if v1.length != vct_length(v2) or v2.length != vct_length(v1) or v2.length != 10
    snd_display("Vct#length: %s %s %s %s?", v1.length, vct_length(v1), v2.length, vct_length(v2))
  end
  v1.each_with_index do |val, i|
    if val != vct_ref(v2, i)
      snd_display("Vct#each: %s %s?", val, vct_ref(v2, i))
    end
  end
  if (v1 <=> v2).nonzero?
    snd_display("Vct#<=> (0): %s?", (v1 <=> v2))
  end
  if (v3 = Vct.new(10) do |i| i * 0.001 end <=> v1) != -1
    snd_display("Vct#<=> (-1): %s?", (v3 <=> v1))
  end
  if (v2 <=> (v3 = Vct.new(10) do |i| i * 0.001 end)) != 1
    snd_display("Vct#<=> (1): %s?", (v2 <=> v3))
  end
  v2.map! do |val| val + 0.5 end
  v3 = v1.map do |val| val + 0.5 end
  if v2 != v3
    snd_display("Vct#map(!): %s %s?", v2, v3)
  end
  v2 = v1.dup
  if (v1 <=> v2).nonzero?
    snd_display("Vct#dup: %s?", v1, v2)
  end
  vec1 = make_array(10) do |i| i * 0.01 end
  vec1[8] = 0.5
  vec2 = v2.to_a
  if vec1 != vec2
    snd_display("Vct#to_a: %s %s?", vec1, vec2)
  end
  if vec1.to_vct != v1
    snd_display("Array#to_vct: %s %s?", vec1.to_vct, v1)
  end
  if vct2string(v1) != v2.to_str or
      v2.to_str != "vct(0.000, 0.010, 0.020, 0.030, 0.040, 0.050, 0.060, 0.070, 0.500, 0.090)"
    snd_display("Vct#to_str:\n# %s\n# %s?", vct2string(v1), v2.to_str)
  end
  if v1.peak != vct_peak(v2)
    snd_display("Vct#peak: %s %s?", v1.peak, vct_peak(v2))
  end
  v3 = v1.dup
  v3.add!(v2)
  v4 = v1.add(v2)
  if v3 != v4
    snd_display("Vct#add(!): %s %s?", v3, v4)
  end
  v3 = v1.dup
  v3.subtract!(v2)
  v4 = v1.subtract(v2)
  if v3 != v4
    snd_display("Vct#subtract(!): %s %s?", v3, v4)
  end
  v3 = v1.dup
  v3.multiply!(v2)
  v4 = v1.multiply(v2)
  if v3 != v4
    snd_display("Vct#multiply(!): %s %s?", v3, v4)
  end
  v3 = v1.dup
  v3.offset!(0.5)
  v4 = v1.offset(0.5)
  if v3 != v4
    snd_display("Vct#offset(!): %s %s?", v3, v4)
  end
  v3 = v1.dup
  v3.scale!(2.0)
  v4 = v1.scale(2.0)
  if v3 != v4
    snd_display("Vct#scale(!): %s %s?", v3, v4)
  end
  v3 = Vct.new(10)
  v4 = Vct.new(10)
  v3.fill(0.5)
  vct_fill!(v4, 0.5)
  if v3 != v4
    snd_display("Vct#fill: %s %s?", v3, v4)
  end
  if v1.first != vct_ref(v2, 0)
    snd_display("Vct#first: %s %s?", v1.first, vct_ref(v2, 0))
  end
  if v1.last != vct_ref(v2, vct_length(v2) - 1)
    snd_display("Vct#last: %s %s?", v1.last, vct_ref(v2, vct_length(v2) - 1))
  end
  v1.first = 0.2
  vct_set!(v2, 0, 0.2)
  if v1.first != vct_ref(v2, 0) or v1.first != 0.2
    snd_display("Vct#first: %s %s?", v1.first, vct_ref(v2, 0))
  end
  v1.last = 0.3
  vct_set!(v2, vct_length(v2) - 1, 0.3)
  if v1.last != vct_ref(v2, vct_length(v2) - 1) or v1.last != 0.3
    snd_display("Vct#last: %s %s?", v1.last, vct_ref(v2, vct_length(v2) - 1))
  end
  # 
  # make_fm_violin (v.rb)
  # 
  samps = 1000
  ind = new_sound(:file, "fmv.snd", :srate, 22050, :channels, 2, :size, samps)
  dur = samples2seconds(samps)
  # thunk
  fmv1 = make_fm_violin(0, dur, 440, 0.5, :thunk?, true)
  v3 = make_vct(samps)
  vct_map!(v3, fmv1)
  vct2channel(v3, 0, samps, ind, 0)
  # proc with one arg
  fmv2 = make_fm_violin(0, dur, 440, 0.5, :thunk?, false)
  map_channel(fmv2, 0, samps, ind, 1)
  snd_test_neq(channel2vct(100, 100, ind, 0), channel2vct(100, 100, ind, 1), "make_fm_violin")
  close_sound(ind)
  delete_file("fmv.snd")
end

# ---------------- test 07: colors ----------------

def test_07_00
  c1 = Snd.catch(:no_such_color, false) do make_color(0, 0, 1) end.first
  c2 = c1
  c3 = Snd.catch(:no_such_color, false) do make_color(0, 0, 1) end.first
  unless c1.equal?(c2)
    snd_display("color equal? %s %s", c1, c2)
  end
  unless c1.eql?(c2)
    snd_display("color eql? %s %s", c1, c2)
  end
  unless c1 == c2
    snd_display("color == %s %s", c1, c2)
  end
  if (res = color2list(c1)) != [0.0, 0.0, 1.0]
    snd_display("color2list: %s %s?", c1, res)
  end
  true_color_list = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 1.0],
                     [0.0, 0.0, 7.01915007248035e-4], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0],
                     [0.0, 0.0, 0.49999], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0],
                     [1.0, 0.0, 1.0], [0.0, 0.500007629510948, 0.4], [1.0, 0.0, 0.0],
                     [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]
  15.times do |i|
    cm = integer2colormap(i)
    if colormap?(cm)
      res0 = colormap_ref(cm, 0)
      res1 = true_color_list[i]
      unless vequal(res0, res1)
        snd_display("colormap_ref[%s]: %s (%s)", cm, res0, res1)
      end
    end
  end
  Snd.catch do
    [[:basic_color, Ivory2],
     [:cursor_color, Red],
     [:data_color, Black],
     [:enved_waveform_color, Blue],
     [:filter_control_waveform_color, Blue],
     [:graph_color, White],
     [:highlight_color, Ivory1],
     [:listener_color, Alice_blue],
     [:listener_text_color, Black],
     [:mark_color, Red],
     [:mix_color, Dark_gray],
     [:position_color, Ivory3],
     [:sash_color, Light_green],
     [:selected_data_color, Black],
     [:selected_graph_color, White],
     [:selection_color, Lightsteelblue1],
     [:text_focus_color, White],
     [:zoom_color, Ivory4]].each do |getfnc, initval|
      unless color?(initval)
        snd_display("%s not color?", initval)
      end
      set_snd_func(getfnc, Beige)
      if (res = snd_func(getfnc)) != Beige
        snd_display("set_%s != Beige (%s)?", getfnc, res)
      end
      set_snd_func(getfnc, initval)
    end
    ind = open_sound("oboe.snd")
    set_selected_data_color(Light_green)
    set_data_color(Blue)
    set_selected_graph_color(Light_green)
    if $with_test_motif
      red = make_color_with_catch(1.0, 0.0, 0.0)
      set_foreground_color(red, ind, 0, Cursor_context)
      if (res = foreground_color(ind, 0, Cursor_context)) != red
        snd_display("set_foreground_color cursor: %s %s?", res, red)
      end
      set_foreground_color(Blue)
      if (res = foreground_color) != Blue
        snd_display("set_foreground_color: %s %s?", res, Blue)
      end
      set_foreground_color(Red, ind)
      if (res = foreground_color(ind)) != Red
        snd_display("set_foreground_color with ind (Red): %s %s?", res, Red)
      end
      set_foreground_color(Black, ind)
      if (res = foreground_color(ind)) != Black
        snd_display("set_foreground_color with ind (Black): %s %s?", res, Black)
      end
    end
    set_selected_graph_color(make_color_with_catch(0.96, 0.96, 0.86))
    set_data_color(Black)
    set_selected_data_color(Blue)
    set_data_color(White)
    close_sound(ind)
  end
end

def check_colormap(name, colmap, x, r, g, b, n, err)
  r1, g1, b1 = colormap_ref(colmap, x)
  if x < (1.0 - (1.0 / n)) and
      (fneq_err(r, r1, err) or  # okay
       fneq_err(g, g1, err) or  # okay
       fneq_err(b, b1, err))    # okay
    snd_display_prev_caller("%s %1.4f (%1.4f): %s %s",
                            name,
                            x,
                            [(r - r1).abs, (g - g1).abs, (b - b1).abs].max,
                            [r, g, b],
                            [r1, g1, b1])
  end
end

def test_07_01
  [[512, 0.005], [64, 0.04]].each do |n, err|
    set_colormap_size(n)
    10.times do |i|
      x = random(1.0)
      r = (x < (3.0 / 4)) ? ((7.0 / 8) * x) : ((11.0 / 8) * x - 3.0 / 8)
      g = (x < (3.0 / 8)) ? ((7.0 / 8) * x) : ((x < (3.0 / 4)) ?
                                               ((29.0 / 24) * x - 1.0 / 8) :
                                               ((7.0 / 8) * x + 1.0 / 8))
      b = (x < (3.0 / 8)) ? ((29.0 / 24) * x) : ((7.0 / 8) * x + 1.0 / 8)
      check_colormap("bone", $bone_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = (x < (4.0 / 5)) ? ((5.0 / 4) * x) : 1.0
      g = (4.0 / 5) * x
      b = (1.0 / 2) * x
      check_colormap("copper", $copper_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = 0.0
      g = x
      b = 1.0 - g / 2.0
      check_colormap("winter", $winter_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = 1.0
      g = x
      b = 0.0
      check_colormap("autumn", $autumn_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = x
      g = 1.0 - r
      b = 1.0
      check_colormap("cool", $cool_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = (x < (3.0 / 8)) ? ((8.0 / 3) * x) : 1.0
      g = (x < (3.0 / 8)) ? 0.0 : ((x < (3.0 / 4)) ? ((8.0 / 3) * x - 1.0) : 1.0)
      b = (x < (3.0 / 4)) ? 0.0 : (4.0 * x - 3)
      check_colormap("hot", $hot_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = (x < (3.0 / 8)) ? 0.0 : ((x < (5.0 / 8)) ? (4.0 * x - 3.0 / 2) :
                                   ((x < (7.0 / 8)) ? 1.0 : (-4.0 * x + 9.0 / 2)))
      g = (x < (1.0 / 8)) ? 0.0 : ((x < (3.0 / 8)) ? (4.0 * x - 0.5) :
                                   (((x < (5.0 / 8)) ? 1.0 :
                                     ((x < (7.0 / 8)) ? (-4.0 * x + 7.0 / 2) : 0.0))))
      b = (x < (1.0 / 8)) ? (4.0 * x + 0.5) : ((x < (3.0 / 8)) ? 1.0 :
                                               ((x < (5.0 / 8)) ? (-4.0 * x + 5.0 / 2) : 0.0)) 
      check_colormap("jet", $jet_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = (x < (3.0 / 8)) ? ((14.0 / 9) * x) : ((2.0 / 3) * x + 1.0 / 3)
      g = (x < (3.0 / 8)) ? ((2.0 / 3) * x) :
        ((x < (3.0 / 4)) ? ((14.0 / 9) * x - 1.0 / 3) : ((2.0 / 3) * x + 1.0 / 3))
      b = (x < (3.0 / 4)) ? ((2.0 / 3) * x) : (2.0 * x - 1.0)
      check_colormap("pink", $pink_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = 1.0
      g = x
      b = 1.0 - g
      check_colormap("spring", $spring_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = x
      g = x
      b = x
      check_colormap("gray", $gray_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = 0.0
      g = 0.0
      b = 0.0
      check_colormap("black_and_white", $black_and_white_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = x
      g = 0.5 + r / 2.0
      b = 0.4
      check_colormap("summer", $summer_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      r = (x < (2.0 / 5)) ? 1.0 : ((x < (3.0 / 5)) ? (-5.0 * x + 3) :
                                   ((x < (4.0 / 5)) ? 0.0 : (10.0 / 3 * x - 8.0 / 3)))
      g = (x < (2.0 / 5)) ? ((5.0 / 2) * x) : ((x < (3.0 / 5)) ? 1.0 :
                                               ((x < (4.0 / 5)) ? (-5.0 * x + 4) : 0.0))
      b = (x < (3.0 / 5)) ? 0.0 : ((x < (4.0 / 5)) ? (5.0 * x - 3) : 1.0)
      check_colormap("rainbow", $rainbow_colormap, x, r, g, b, n, err)
    end
    10.times do |i|
      x = random(1.0)
      rgb = colormap_ref($prism_colormap, x)
      if x < 1.0 - 1.0 / n and
          (not vequal(rgb, [1.0, 0.0, 0.0])) and
          (not vequal(rgb, [1.0, 0.5, 0.0])) and
          (not vequal(rgb, [1.0, 1.0, 0.0])) and
          (not vequal(rgb, [0.0, 1.0, 0.0])) and
          (not vequal(rgb, [0.0, 0.0, 1.0])) and
          (not vequal(rgb, [0.6667, 0.0, 1.0]))
        snd_display("prism %s", rgb)
      end
    end
    10.times do |i|
      x = random(1.0)
      rgb = colormap_ref($flag_colormap, x)
      if x < 1.0 - 1.0 / n and
          (not vequal(rgb, [1.0, 0.0, 0.0])) and
          (not vequal(rgb, [1.0, 1.0, 1.0])) and
          (not vequal(rgb, [0.0, 0.0, 1.0])) and
          (not vequal(rgb, [0.0, 0.0, 0.0]))
        snd_display("flag %s", rgb)
      end
    end
  end
end

def test_07_02(old_colormap_size)
  ind = add_colormap("white", lambda do |size|
                       [make_vct(size, 1.0), make_vct(size, 1.0), make_vct(size, 1.0)]
                     end)
  unless res = colormap?(ind)
    snd_display("add_colormap %s: %s?", ind, res)
  end
  unless vequal(res = colormap_ref(ind, 0.5), [1.0, 1.0, 1.0])
    snd_display("white colormap: %s?", res)
  end
  if (res = Snd.catch do set_colormap(ind) end).first == :no_such_colormap or colormap != ind
    snd_display("colormap white: %s %s %s", res, ind, colormap)
  end
  if (res = colormap_name(ind)) != "white"
    snd_display("white colormap name: %s?", res)
  end
  if (res = Snd.catch do delete_colormap(integer2colormap(1234)) end).first != :no_such_colormap
    snd_display("delete_colormap 1234: %s?", res)
  end
  if (res = Snd.catch do colormap_ref(integer2colormap(1234), 0.5) end).first != :no_such_colormap
    snd_display("colormap_ref 1234: %s?", res)
  end
  res = Snd.catch do colormap_ref(integer2colormap(-1), 0.5) end
  if res.first != :no_such_colormap and res.first != :wrong_type_arg
    snd_display("colormap_ref -1: %s?", res)
  end
  if (res = Snd.catch do set_colormap(integer2colormap(1234)) end).first != :no_such_colormap
    snd_display("set_colormap 1234: %s?", res)
  end
  res = Snd.catch do set_colormap(integer2colormap(-1)) end
  if res.first != :no_such_colormap and res.first != :wrong_type_arg
    snd_display("set_colormap -1: %s?", res)
  end
  if (res = Snd.catch do colormap_ref($copper_colormap, 2.0) end).first != :out_of_range
    snd_display("colormap_ref 2.0: %s?", res)
  end
  #
  set_colormap_size(old_colormap_size)
  if (res = colormap_size) != old_colormap_size
    snd_display("set_colormap_size: %s %s?", res, old_colormap_size)
  end
  if (res = colormap_name($black_and_white_colormap)) != "black-and-white"
    snd_display("black-and-white: %s?", res)
  end
  if (res = colormap_name($gray_colormap)) != "gray"
    snd_display("gray: %s?", res)
  end
  if (res = colormap_name($rainbow_colormap)) != "rainbow"
    snd_display("rainbow: %s?", res)
  end
  add_colormap("purple",
               lambda do |size|
                 r = make_vct(size)
                 g = make_vct(size)
                 b = make_vct(size)
                 er = [0, 60, 60, 116, 128, 252, 192, 252, 256, 60]
                 eg = [0,  0, 64,   0, 128, 252, 192, 252, 256,  0]
                 eb = [0, 80,          128, 252, 192,   0, 256, 80]
                 incr = 256.0 / size
                 x = 0.0
                 size.times do |i|
                   r[i] = envelope_interp(x, er) / 256.0
                   g[i] = envelope_interp(x, eg) / 256.0
                   b[i] = envelope_interp(x, eb) / 256.0
                   x += incr
                 end
                 [r, g, b]
               end)
  add_colormap("sin",
               lambda do |size|
                 r = make_vct(size)
                 g = make_vct(size)
                 b = make_vct(size)
                 incr = (2.0 * PI) / size
                 x = 0.0
                 size.times do |i|
                   r[i] = sin(1.5 * x).abs
                   g[i] = sin(3.5 * x).abs
                   b[i] = sin(2.5 * x).abs
                   x += incr
                 end
                 [r, g, b]
               end)
  add_colormap("another-sin",
               lambda do |size|
                 r = make_vct(size)
                 g = make_vct(size)
                 b = make_vct(size)
                 incr = (2.0 * PI) / size
                 x = 0.0
                 size.times do |i|
                   r[i] = sin(2.5 * x).abs
                   g[i] = sin(3.5 * x).abs
                   b[i] = sin(4.5 * x).abs
                   x += incr
                 end
                 [r, g, b]
               end)
  [1024, 256, 2, 512].each do |n|
    set_colormap_size(n)
    10.times do |i|
      x = random(1.0)
      r = (x < 4.0 / 5) ? ((5.0 / 4) * x) : 1.0
      g = (4.0 / 5) * x
      b = 0.5 * x
      rgb = colormap_ref($copper_colormap, x)
      r1, g1, b1 = rgb
      err = 0.01
      if n > 2 and
          x < (1.0 - (1.0 / n)) and
          (fneq_err(r, r1, err) or # okay
           fneq_err(g, g1, err) or # okay
           fneq_err(b, b1, err))   # okay
        snd_display("copper size reset %s: %1.4f (%1.4f): %s %s",
                    n,
                    x,
                    [(r - r1).abs, (g - g1).abs, (b - b1).abs].max,
                    [r, g, b],
                    [r1, g1, b1])
      end
    end
  end
  set_colormap_size(old_colormap_size)
end

def test_07
  if $with_test_gui
    old_colormap_size = colormap_size
    old_colormap = colormap
    test_07_00
    test_07_01
    test_07_02(old_colormap_size)
    set_colormap(old_colormap)
  end
end

# ---------------- test 08: clm ----------------

def sweep2bins(flt, bins)
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1, false, 22050)
  phase = 0.0
  freq = 0.0
  incr = PI / 22050
  map_channel(lambda do |y|
                val = sin(phase)
                phase += freq
                freq += incr
                0.5 * val
              end)
  if proc?(flt)
    map_channel(flt)
  else
    map_channel(lambda do |y| flt.run(y, 0.0) end)
  end
  mx = maxamp
  size = (22050 / bins).round
  resp = Vct.new(bins) do |i| channel2vct(i * size, size).peak end
  close_sound(ind)
  [mx, resp]
end

def filter_response_max(f1)
  mx = 0.0
  1000.times do |i| mx = [mx, f1.run(i.zero? ? 1.0 : 0.0, 0.0).abs].max end
  mx
end

def filter_equal?(f1, f2)
  f1.order == f2.order and vequal(f1.xcoeffs, f2.xcoeffs) and vequal(f1.ycoeffs, f2.ycoeffs)
end

def f05equal?(f1, f2)
  fequal_err(f1, f2, 0.05)
end

def analog_filter_tests
  #
  # Butterworth
  #
  poles = [vct(1.000, 1.414, 1.000),
           vct(1.000, 1.848, 1.000, 1.000, 0.765, 1.000),
           vct(1.000, 1.932, 1.000, 1.000, 1.414, 1.000, 1.000, 0.518, 1.000),
           vct(1.000, 1.962, 1.000, 1.000, 1.663, 1.000, 1.000, 1.111, 1.000, 1.000, 0.390, 1.000),
           vct(1.000, 1.975, 1.000, 1.000, 1.782, 1.000, 1.000, 1.414, 1.000, 1.000, 0.908, 1.000,
               1.000, 0.313, 1.000)]
  k = 0
  2.step(11, 2) do |i|
    vals = butterworth_prototype(i)
    snd_test_neq(vals[1], poles[k], "butterworth_prototype poles %d", i)
    len = (k + 1) * 3
    zeros = Vct.new(len)
    2.step(len, 3) do |j|
      zeros[j] = 1.0
      break if j >= (k + 1) * 3
    end
    snd_test_neq(vals[0], zeros, "butterworth_prototype zeros %d", i)
    k += 1
  end
  cutoff = 0.1
  3.times do |m|
    k = 1
    2.step(16, 2) do |i|
      local = make_butterworth_lowpass(i, cutoff)
      dsp = make_butter_lp(k, mus_srate * cutoff)
      snd_test_any_neq(local, dsp, :filter_equal?, "butterworth lowpass %1.4f", cutoff)
      local = make_butterworth_highpass(i, cutoff)
      dsp = make_butter_hp(k, mus_srate * cutoff)
      snd_test_any_neq(local, dsp, :filter_equal?, "butterworth highpass %1.4f", cutoff)
      k += 1
    end
    cutoff += 0.1
  end
  # 
  ind = open_sound("oboe.snd")
  hummer = make_eliminate_hum(550)
  map_channel(lambda do |x| eliminate_hum(hummer, x) end)
  peaker = make_peaking_2(500, 1000, 1.0)
  map_channel(peaker)
  map_channel(chordalize())
  close_sound(ind)
  #
  f1 = make_butterworth_lowpass(8, 0.1)
  vals = sweep2bins(f1, 10)
  snd_test_neq(vals[0], 0.5, "butterworth lp 8 max")
  snd_test_neq(vals[1],
               vct(0.500, 0.500, 0.359, 0.014, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000),
               "butterworth lp 8 0.1 spect")
  f1 = make_butterworth_lowpass(12, 0.25)
  vals = sweep2bins(f1, 10)
  snd_test_neq(vals[0], 0.5, "butterworth lp 12 max")
  snd_test_neq(vals[1],
               vct(0.500, 0.500, 0.500, 0.500, 0.499, 0.358, 0.010, 0.000, 0.000, 0.000),
               "butterworth lp 12 0.25 spect")
  f1 = make_butterworth_lowpass(10, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_neq(vals[0], 0.5, "butterworth lp 10 max")
  if (not vequal(vals[1], vct(0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.499, 0.361, 0.001))) and
      (not vequal(vals[1], vct(0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.499, 0.360, 0.002)))
    snd_display(snd_format_neq(vals[1],
                               vct(0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.499, 0.361, 0.001),
                               "butterworth lp 10 0.4 spect"))
  end
  2.step(12, 2) do |i|
    0.1.step(0.35, 0.1) do |j|
      f1 = make_butterworth_lowpass(i, j)
      mx = filter_response_max(f1)
      if mx > 1.0
        snd_display(snd_format(mx, 1.0, ">", "butter low max %d %d", i, j))
      end
    end
  end
  #
  f1 = make_butterworth_highpass(8, 0.1)
  vals = sweep2bins(f1, 10)
  snd_test_neq(vals[0], 0.5, "butterworth hp 8 max")
  snd_test_neq(vals[1],
               vct(0.001, 0.348, 0.500, 0.500, 0.500, 0.500, 0.500, 0.500, 0.500, 0.500),
               "butterworth hp 8 0.1 spect")
  f1 = make_butterworth_highpass(12, 0.25)
  vals = sweep2bins(f1, 10)
  snd_test_neq(vals[0], 0.5, "butterworth hp 12 max")
  snd_test_neq(vals[1],
               vct(0.000, 0.000, 0.000, 0.011, 0.348, 0.500, 0.500, 0.500, 0.500, 0.500),
               "butterworth hp 12 0.25 spect")
  f1 = make_butterworth_highpass(10, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_neq(vals[0], 0.5, "butterworth hp 10 max")
  snd_test_neq(vals[1],
               vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.005, 0.343, 0.501, 0.501),
               "butterworth hp 10 0.4 spect")
  2.step(12, 2) do |i|
    0.1.step(0.35, 0.1) do |j|
      f1 = make_butterworth_highpass(i, j)
      mx = filter_response_max(f1)
      if mx > 1.0
        snd_display(snd_format(mx, 1.0, ">", "butter high max %d %d", i, j))
      end
    end
  end
  #
  f1 = make_butterworth_bandpass(4, 0.1, 0.2)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "butterworth bp 4 max") # okay
  snd_test_neq(vals[1],
               vct(0.028, 0.350, 0.481, 0.479, 0.346, 0.132, 0.038, 0.009, 0.002, 0.000),
               "butterworth bp 4 0.1 0.2 spect")
  f1 = make_butterworth_bandpass(12, 0.1, 0.2)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "butterworth bp 12 max") # okay
  snd_test_neq(vals[1],
               vct(0.000, 0.323, 0.501, 0.500, 0.358, 0.009, 0.000, 0.000, 0.000, 0.000),
               "butterworth bp 12 0.1 0.2 spect")
  f1 = make_butterworth_bandpass(8, 0.3, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "butterworth bp 8 max") # okay
  snd_test_neq(vals[1],
               vct(0.000, 0.000, 0.000, 0.003, 0.034, 0.344, 0.499, 0.499, 0.353, 0.002),
               "butterworth bp 8 0.3 0.4 spect")
  #
  f1 = make_butterworth_bandstop(4, 0.1, 0.2)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "butterworth bs 4 max") # okay
  snd_test_neq(vals[1],
               vct(0.500, 0.500, 0.347, 0.339, 0.481, 0.499, 0.500, 0.500, 0.500, 0.500),
               "butterworth bs 4 0.1 0.2 spect")
  f1 = make_butterworth_bandstop(12, 0.1, 0.2)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "butterworth bs 12 max") # okay
  snd_test_neq(vals[1],
               vct(0.500, 0.500, 0.365, 0.334, 0.500, 0.500, 0.500, 0.500, 0.500, 0.500),
               "butterworth bs 12 0.1 0.2 spect")
  f1 = make_butterworth_bandstop(8, 0.3, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "butterworth bs 8 max") # okay
  snd_test_neq(vals[1],
               vct(0.500, 0.500, 0.500, 0.500, 0.500, 0.498, 0.354, 0.332, 0.500, 0.500),
               "butterworth bs 8 0.3 0.4 spect")
  #
  # Chebyshev
  #
  # ripple 0.01 0.1 1 for 2..10 even
  poles_01 = [vct(1.000, 4.456, 10.426),
              vct(1.000, 0.822, 2.006, 1.000, 1.984, 1.299),
              vct(1.000, 0.343, 1.372, 1.000, 0.937, 0.939, 1.000, 1.280, 0.506),
              vct(1.000, 0.189, 1.196, 1.000, 0.537, 0.925, 1.000, 0.804, 0.542,
                  1.000, 0.948, 0.272),
              vct(1.000, 0.119, 1.121, 1.000, 0.347, 0.940, 1.000, 0.540, 0.646,
                  1.000, 0.680, 0.352, 1.000, 0.754, 0.170)]
  zeros = [vct(0.000, 0.000, 1.000),
           vct(0.000, 0.000, 0.250, 0.000, 0.000, 1.000),
           vct(0.000, 0.000, 0.062, 0.000, 0.000, 1.000, 0.000, 0.000, 1.000),
           vct(0.000, 0.000, 0.016, 0.000, 0.000, 1.000, 0.000, 0.000, 1.000, 0.000, 0.000, 1.000),
           vct(0.000, 0.000, 0.004, 0.000, 0.000, 1.000, 0.000, 0.000, 1.000, 0.000, 0.000, 1.000,
               0.000, 0.000, 1.000)]
  poles_1 = [vct(1.000, 2.372, 3.314),
             vct(1.000, 0.528, 1.330, 1.000, 1.275, 0.623),
             vct(1.000, 0.229, 1.129, 1.000, 0.627, 0.696, 1.000, 0.856, 0.263),
             vct(1.000, 0.128, 1.069, 1.000, 0.364, 0.799, 1.000, 0.545, 0.416,
                 1.000, 0.643, 0.146),
             vct(1.000, 0.082, 1.044, 1.000, 0.237, 0.862, 1.000, 0.369, 0.568,
                 1.000, 0.465, 0.274, 1.000, 0.515, 0.092)]
  poles_10 = [vct(1.000, 1.098, 1.103),
              vct(1.000, 0.279, 0.987, 1.000, 0.674, 0.279),
              vct(1.000, 0.124, 0.991, 1.000, 0.340, 0.558, 1.000, 0.464, 0.125),
              vct(1.000, 0.070, 0.994, 1.000, 0.199, 0.724, 1.000, 0.298, 0.341,
                  1.000, 0.352, 0.070),
              vct(1.000, 0.045, 0.996, 1.000, 0.130, 0.814, 1.000, 0.203, 0.521,
                  1.000, 0.255, 0.227, 1.000, 0.283, 0.045)]
  k = 0
  2.step(11, 2) do |i|
    vals = chebyshev_prototype(i, 0.01)
    snd_test_neq(vals[1], poles_01[k], "chebyshev_prototype 0.01 poles %d", i)
    vals = chebyshev_prototype(i, 0.1)
    snd_test_neq(vals[1], poles_1[k], "chebyshev_prototype 0.1 poles %d", i)
    vals = chebyshev_prototype(i)
    snd_test_neq(vals[1], poles_10[k], "chebyshev_prototype 1 poles %d", i)
    snd_test_neq(vals[0], zeros[k], "chebyshev_prototype 0.01 zeros %d", i)
    k += 1
  end
  #
  f1 = make_chebyshev_lowpass(8, 0.1)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "chebyshev lp 8 max") # okay
  if (not vequal(vals[1], vct(0.508, 0.512, 0.468, 0.001, 0, 0, 0, 0, 0, 0))) and
      (not vequal(vals[1], vct(0.507, 0.512, 0.467, 0.001, 0, 0, 0, 0, 0, 0))) and
      (not vequal(vals[1], vct(0.508, 0.513, 0.469, 0.001, 0, 0, 0, 0, 0, 0))) and
      (not vequal(vals[1], vct(0.509, 0.508, 0.465, 0.001, 0, 0, 0, 0, 0, 0)))
    snd_display(snd_format_neq(vals[1],
                               vct(0.508, 0.512, 0.468, 0.001, 0, 0, 0, 0, 0, 0),
                               "chebyshev lp 8 0.1 spect"))
  end
  f1 = make_chebyshev_lowpass(12, 0.25)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "chebyshev lp 12 max") # okay
  snd_test_neq(vals[1],
               vct(0.509, 0.500, 0.508, 0.508, 0.507, 0.413, 0, 0, 0, 0),
               "chebyshev lp 12 0.25 spect")
  f1 = make_chebyshev_lowpass(10, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "chebyshev lp 10 max") # okay
  snd_test_neq(vals[1],
               vct(0.465, 0.493, 0.509, 0.508, 0.477, 0.507, 0.508, 0.507, 0.431, 0),
               "chebyshev lp 10 0.4 spect")
  f1 = make_chebyshev_lowpass(8, 0.1, 0.01)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.49, :ffequal?, "chebyshev lp 8 0.1 0.01 max") # okay
  snd_test_neq(vals[1],
               vct(0.492, 0.491, 0.483, 0.006, 0, 0, 0, 0, 0, 0),
               "chebyshev lp 8 0.1 0.01 spect")
  f1 = make_chebyshev_lowpass(12, 0.25, 0.1)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.49, :ffequal?, "chebyshev lp 12 0.1 max") # okay
  snd_test_neq(vals[1],
               vct(0.488, 0.488, 0.488, 0.488, 0.487, 0.403, 0, 0, 0, 0),
               "chebyshev lp 12 0.25 0.1 spect")
  f1 = make_chebyshev_lowpass(10, 0.4, 0.001)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.49, :ffequal?, "chebyshev lp 10 0.001 max") # okay
  snd_test_neq(vals[1],
               vct(0.497, 0.497, 0.497, 0.497, 0.497, 0.497, 0.497, 0.497, 0.488, 0),
               "chebyshev lp 10 0.4 0.001 spect")
  2.step(10, 2) do |i|
    0.1.step(0.35, 0.1) do |j|
      f1 = make_chebyshev_lowpass(i, j)
      mx = filter_response_max(f1)
      if mx > 1.0
        snd_display(snd_format(mx, 1.0, ">", "cheby low max %d %d", i, j))
      end
    end
  end
  #
  f1 = make_chebyshev_highpass(8, 0.1)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.55, :ffequal?, "chebyshev hp 8 max") # okay
  snd_test_neq(vals[1],
               vct(0, 0.341, 0.551, 0.509, 0.466, 0.501, 0.509, 0.505, 0.481, 0.461),
               "chebyshev hp 8 0.1 spect")
  f1 = make_chebyshev_highpass(12, 0.25)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.55, :ffequal?, "chebyshev hp 12 max") # okay
  snd_test_neq(vals[1],
               vct(0, 0, 0, 0, 0.299, 0.554, 0.509, 0.509, 0.500, 0.509),
               "chebyshev hp 12 0.25 spect")
  f1 = make_chebyshev_highpass(10, 0.4)
  vals = sweep2bins(f1, 10)
  # snd_test_any_neq(vals[0], 0.55, :ffequal?, "chebyshev hp 10 max") # okay
  if (not vequal(vals[1], vct(0, 0, 0, 0, 0, 0, 0, 0.297, 0.786, 0.677))) and
      (not vequal(vals[1], vct(0, 0, 0, 0, 0, 0, 0, 0.301, 0.788, 0.660))) and
      (not vequal(vals[1], vct(0, 0, 0, 0, 0, 0, 0, 0.322, 0.861, 0.724))) and
      (not vequal(vals[1], vct(0, 0, 0, 0, 0, 0, 0, 0.262, 0.571, 0.509)))
    snd_display(snd_format_neq(vals[1],
                               vct(0, 0, 0, 0, 0, 0, 0, 0.297, 0.786, 0.677),
                               "chebyshev hp 10 0.4 spect"))
  end
  f1 = make_chebyshev_highpass(8, 0.1, 0.01)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.49, :ffequal?, "chebyshev hp 8 0.1 0.01 max") # okay
  snd_test_neq(vals[1],
               vct(0, 0.498, 0.498, 0.492, 0.491, 0.492, 0.492, 0.492, 0.491, 0.491),
               "chebyshev hp 8 0.1 0.01 spect")
  f1 = make_chebyshev_highpass(12, 0.25, 0.1)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "chebyshev hp 12 0.1 max") # okay
  snd_test_neq(vals[1],
               vct(0, 0, 0, 0, 0.453, 0.516, 0.489, 0.489, 0.488, 0.488),
               "chebyshev hp 12 0.25 0.1 spect")
  f1 = make_chebyshev_highpass(10, 0.4, 0.001)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :ffequal?, "chebyshev 10 0.001 max") # okay
  snd_test_neq(vals[1],
               vct(0, 0, 0, 0, 0, 0, 0.002, 0.503, 0.505, 0.504),
               "chebyshev hp 10 0.4 0.001 spect")
  2.step(10, 2) do |i|
    0.1.step(0.35, 0.1) do |j|
      f1 = make_chebyshev_highpass(i, j)
      mx = filter_response_max(f1)
      if mx > 1.0
        snd_display(snd_format(mx, 1.0, ">", "cheby high max %d %d", i, j))
      end
    end
  end
  #
  f1 = make_chebyshev_bandpass(4, 0.1, 0.2)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "chebyshev bp 4 max") # okay
  snd_test_neq(vals[1],
               vct(0.009, 0.449, 0.509, 0.505, 0.442, 0.065, 0.013, 0.003, 0, 0),
               "chebyshev bp 4 0.1 0.2 spect")

  f1 = make_chebyshev_bandpass(6, 0.1, 0.2)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "chebyshev bp 6 max") # okay
  snd_test_neq(vals[1],
               vct(0.001, 0.376, 0.505, 0.498, 0.412, 0.011, 0.001, 0, 0, 0),
               "chebyshev bp 6 0.1 0.2 spect")
  f1 = make_chebyshev_bandpass(8, 0.3, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "chebyshev bp 8 max") # okay
  snd_test_neq(vals[1],
               vct(0, 0, 0, 0, 0.002, 0.363, 0.517, 0.513, 0.433, 0),
               "chebyshev bp 8 0.3 0.4 spect")
  f1 = make_chebyshev_bandpass(8, 0.2, 0.2, 0.01)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "chebyshev bp 10 0.2 max") # okay
  snd_test_neq(vals[1],
               vct(0, 0, 0.015, 0.483, 0.482, 0.021, 0.001, 0, 0, 0),
               "chebyshev bp 10 0.2 spect")
  # 
  f1 = make_chebyshev_bandstop(4, 0.1, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "chebyshev bs 4 max") # okay
  snd_test_neq(vals[1],
               vct(0.509, 0.505, 0.447, 0.033, 0.006, 0.006, 0.033, 0.445, 0.512, 0.509),
               "chebyshev bs 4 0.1 0.4 spect")
  f1 = make_chebyshev_bandstop(8, 0.1, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :f05equal?, "chebyshev bs 8 max") # okay
  if (not vequal(vals[1], vct(0.508, 0.512, 0.468, 0.001, 0, 0, 0.001, 0.345, 0.551, 0.507))) and
      (not vequal(vals[1], vct(0.507, 0.512, 0.467, 0.001, 0, 0, 0.001, 0.344, 0.59, 0.508))) and
      (not vequal(vals[1], vct(0.508, 0.513, 0.469, 0.001, 0, 0, 0.001, 0.345, 0.552, 0.508))) and
      (not vequal(vals[1], vct(0.509, 0.508, 0.465, 0.001, 0, 0, 0.001, 0.343, 0.548, 0.508)))
    snd_display(snd_format(vals[1],
                           vct(0.508, 0.512, 0.468, 0.001, 0, 0, 0.001, 0.345, 0.551, 0.507),
                           "chebyshev bs 8 0.1 0.4 spect"))
  end
  f1 = make_chebyshev_bandstop(8, 0.1, 0.4, 0.01)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "chebyshev bs 8 0.01 max") # okay
  snd_test_neq(vals[1],
               vct(0.492, 0.491, 0.483, 0.006, 0, 0, 0.006, 0.494, 0.495, 0.492),
               "chebyshev bs 8 0.1 0.4 0.01 spect")
  #
  # inverse-chebyshev
  #
  f1 = make_inverse_chebyshev_lowpass(8, 0.1)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "inverse_chebyshev lp 8 max") # okay
  if (not vequal(vals[1], vct(0.501, 0.496, 0.001, 0, 0.001, 0, 0, 0, 0, 0.001))) and
      (not vequal(vals[1], vct(0.500, 0.498, 0.001, 0, 0.001, 0, 0, 0, 0, 0.001)))
    snd_display(snd_format_neq(vals[1],
                               vct(0.501, 0.496, 0.001, 0, 0.001, 0, 0, 0, 0, 0.001),
                               "inverse_chebyshev lp 8 0.1 spect"))
  end
  f1 = make_inverse_chebyshev_lowpass(12, 0.25)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "inverse_chebyshev lp 12 max") # okay
  snd_test_neq(vals[1],
               vct(0.500, 0.500, 0.500, 0.500, 0.496, 0.001, 0.001, 0.001, 0.001, 0.001),
               "inverse_chebyshev lp 12 0.25 spect")
  f1 = make_inverse_chebyshev_lowpass(10, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "inverse_chebyshev lp 10 max") # okay
  if (not vequal(vals[1], vct(0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.497, 0.001, 0.001))) and
      (not vequal(vals[1], vct(0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.497, 0.002, 0.002)))
    snd_display(snd_format_neq(vals[1],
                               vct(0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.497, 0.001, 0.001),
                               "inverse_chebyshev lp 10 0.4 spect"))
  end
  f1 = make_inverse_chebyshev_lowpass(10, 0.4, 120)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "inverse_chebyshev lp 10 max") # okay
  snd_test_neq(vals[1],
               vct(0.501, 0.501, 0.501, 0.501, 0.501, 0.500, 0.345, 0.007, 0, 0),
               "inverse_chebyshev lp 10 0.4 120 spect")
  # 
  2.step(10, 2) do |i|
    0.1.step(0.35, 0.1) do |j|
      f1 = make_inverse_chebyshev_lowpass(i, j)
      mx = filter_response_max(f1)
      if mx > 1.0
        snd_display(snd_format(mx, 1.0, ">", "inv cheby low max %d %d", i, j))
      end
    end
  end
  #
  f1 = make_inverse_chebyshev_highpass(8, 0.1)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "inverse_chebyshev hp 8 max") # okay
  snd_test_neq(vals[1],
               vct(0.001, 0.001, 0.440, 0.505, 0.505, 0.503, 0.502, 0.501, 0.501, 0.501),
               "inverse_chebyshev hp 8 0.1 spect")
  f1 = make_inverse_chebyshev_highpass(12, 0.25)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "inverse_chebyshev hp 12 max") # okay
  snd_test_neq(vals[1],
               vct(0.001, 0.001, 0.001, 0.001, 0.001, 0.505, 0.506, 0.503, 0.501, 0.501),
               "inverse_chebyshev hp 12 0.25 spect")
  f1 = make_inverse_chebyshev_highpass(10, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "inverse_chebyshev hp 10 max") # okay
  if (not vequal(vals[1], vct(0, 0, 0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.503, 0.503))) and
      (not vequal(vals[1], vct(0, 0, 0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.505, 0.503))) and
      (not vequal(vals[1], vct(0, 0, 0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.509, 0.504)))
    snd_display(snd_format_neq(vals[1],
                               vct(0, 0, 0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.503, 0.503),
                               "inverse_chebyshev hp 10 0.4 spect"))
  end
  f1 = make_inverse_chebyshev_highpass(10, 0.1, 120)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :ffequal?, "inverse_chebyshev hp 10 0.1 120 max") # okay
  snd_test_neq(vals[1],
               vct(0, 0, 0.007, 0.328, 0.502, 0.502, 0.502, 0.501, 0.501, 0.501),
               "inverse_chebyshev hp 10 0.1 120 spect")
  # 
  2.step(10, 2) do |i|
    0.1.step(0.35, 0.1) do |j|
      f1 = make_inverse_chebyshev_highpass(i, j)
      mx = filter_response_max(f1)
      if mx > 1.0
        snd_display(snd_format(mx, 1.0, ">", "inv cheby high max %d %d", i, j))
      end
    end
  end
  #
  f1 = make_inverse_chebyshev_bandpass(10, 0.1, 0.2)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "inverse_chebyshev bp 10 max") # okay
  snd_test_neq(vals[1],
               vct(0.001, 0.001, 0.498, 0.485, 0.001, 0.001, 0, 0.001, 0, 0.001),
               "inverse_chebyshev bp 10 0.1 0.2 spect")
  f1 = make_inverse_chebyshev_bandpass(10, 0.1, 0.2, 30)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "inverse_chebyshev bp 10 30 max") # okay
  if (not vequal(vals[1], vct(0.026, 0.025, 0.509, 0.505, 0.02, 0.016, 0.012, 0.016, 0.011, 0.016))) and
      (not vequal(vals[1], vct(0.03, 0.042, 0.511, 0.505, 0.02, 0.016, 0.012, 0.016, 0.011, 0.016))) and
      (not vequal(vals[1], vct(0.022, 0.017, 0.511, 0.505, 0.02, 0.016, 0.012, 0.016, 0.011, 0.016)))
    snd_display(snd_format_neq(vals[1],
                               vct(0.026, 0.025, 0.509, 0.505, 0.02, 0.016, 0.012, 0.016, 0.011, 0.016),
                               "inverse_chebyshev bp 10 0.1 0.2 30 spect"))
  end
  f1 = make_inverse_chebyshev_bandpass(8, 0.1, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "inverse_chebyshev bp 8 max") # okay
  snd_test_neq(vals[1],
               vct(0.001, 0.001, 0.440, 0.506, 0.505, 0.503, 0.502, 0.434, 0.001, 0.001),
               "inverse_chebyshev bp 8 0.1 0.4 spect")
  f1 = make_inverse_chebyshev_bandpass(8, 0.3, 0.4, 40)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "inverse_chebyshev bp 8 40 max") # okay
  snd_test_neq(vals[1],
               vct(0.002, 0.005, 0.007, 0.007, 0.005, 0.005, 0.503, 0.505, 0.006, 0.005),
               "inverse_chebyshev bp 8 0.3 0.4 40 spect")
  # 
  f1 = make_inverse_chebyshev_bandstop(4, 0.1, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "inverse_chebyshev bs 4 max") # okay
  snd_test_neq(vals[1],
               vct(0.500, 0.054, 0.001, 0.001, 0, 0, 0, 0.001, 0.055, 0.503),
               "inverse_chebyshev bs 4 0.1 0.4 spect")
  f1 = make_inverse_chebyshev_bandstop(8, 0.1, 0.4)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.51, :f05equal?, "inverse_chebyshev bs 8 max") # okay
  if (not vequal(vals[1], vct(0.501, 0.496, 0.001, 0.001, 0, 0, 0, 0.001, 0.507, 0.506))) and
      (not vequal(vals[1], vct(0.506, 0.328, 0.001, 0.001, 0, 0, 0, 0, 0.268, 0.511))) and
      (not vequal(vals[1], vct(0.5, 0.498, 0.001, 0.001, 0, 0, 0, 0.001, 0.507, 0.506)))
    snd_display(snd_format_neq(vals[1],
                               vct(0.501, 0.496, 0.001, 0.001, 0, 0, 0, 0.001, 0.507, 0.506),
                               "inverse_chebyshev bs 8 0.1 0.4 spect"))
  end
  f1 = make_inverse_chebyshev_bandstop(8, 0.1, 0.4, 90)
  vals = sweep2bins(f1, 10)
  snd_test_any_neq(vals[0], 0.5, :f05equal?, "inverse_chebyshev bs 8 90 max") # okay
  snd_test_neq(vals[1],
               vct(0.505, 0.325, 0, 0, 0, 0, 0, 0, 0.270, 0.506),
               "inverse_chebyshev bs 8 0.1 0.4 90 spect")
  if $with_test_gsl
    if defined? gsl_roots
      # gsl_roots isn't defined for ruby in snd-xen.c
      #
      # bessel
      #
      f1 = make_bessel_lowpass(4, 0.1)
      vals = sweep2bins(f1, 10)
      snd_test_neq(vals[0], 0.5, "bessel lp 4 0.1 max")
      snd_test_neq(vals[1],
                   vct(0.500, 0.417, 0.209, 0.062, 0.018, 0.005, 0.001, 0, 0, 0),
                   "bessel lp 4 0.1 spect")
      f1 = make_bessel_lowpass(8, 0.1)
      vals = sweep2bins(f1, 10)
      snd_test_neq(vals[0], 0.5, "bessel lp 8 max")
      snd_test_neq(vals[1],
                   vct(0.499, 0.365, 0.116, 0.010, 0.001, 0, 0, 0, 0, 0),
                   "bessel lp 8 0.1 spect")
      f1 = make_bessel_lowpass(12, 0.25)
      vals = sweep2bins(f1, 10)
      snd_test_neq(vals[0], 0.5, "bessel lp 12 max")
      snd_test_neq(vals[1],
                   vct(0.500, 0.477, 0.410, 0.309, 0.185, 0.063, 0.006, 0, 0, 0),
                   "bessel lp 12 0.25 spect")
      f1 = make_bessel_lowpass(10, 0.4)
      vals = sweep2bins(f1, 10)
      snd_test_neq(vals[0], 0.5, "bessel lp 10 max")
      if (not vequal(vals[1], vct(0.5, 0.498, 0.491, 0.479, 0.458, 0.423, 0.364, 0.259, 0.086, 0.001))) and
          (not vequal(vals[1], vct(0.5, 0.498, 0.491, 0.479, 0.458, 0.423, 0.364, 0.259, 0.086, 0.002)))
        snd_display(snd_format_neq(vals[1],
                                   vct(0.5, 0.498, 0.491, 0.479, 0.458, 0.423, 0.364, 0.259, 0.086, 0.001),
                                   "bessel lp 10 0.4 spect"))
      end
      2.step(11, 2) do |i|
        0.1.step(0.44, 0.1) do |j|
          f1 = make_bessel_lowpass(i, j)
          mx = filter_response_max(f1)
          if mx > 1.0
            snd_display(snd_format(mx, 1.0, ">", "bess low max %d %d", i, j))
          end
        end
      end
      #
      f1 = make_bessel_highpass(8, 0.1)
      vals = sweep2bins(f1, 10)
      snd_test_neq(vals[0], 0.5, "bessel hp 8 max")
      snd_test_neq(vals[1],
                   vct(0.001, 0.115, 0.29, 0.386, 0.435, 0.465, 0.483, 0.493, 0.498, 0.5),
                   "bessel hp 8 0.1 spect")
      f1 = make_bessel_highpass(12, 0.25)
      vals = sweep2bins(f1, 10)
      snd_test_neq(vals[0], 0.5, "bessel hp 12 max")
      snd_test_neq(vals[1],
                   vct(0, 0, 0, 0.006, 0.063, 0.181, 0.309,0.410, 0.477, 0.5),
                   "bessel hp 12 0.25 spect")
      f1 = make_bessel_highpass(10, 0.4)
      vals = sweep2bins(f1, 10)
      snd_test_any_neq(vals[0], 0.5, :ffequal?, "bessel hp 10 max") # okay
      snd_test_neq(vals[1],
                   vct(0, 0, 0, 0, 0, 0, 0.004, 0.084, 0.343, 0.499),
                   "bessel hp 12 0.25 0.01 90 spect")
      #
      f1 = make_bessel_bandpass(4, 0.1, 0.2)
      vals = sweep2bins(f1, 10)
      snd_test_neq(vals[0], 0.5, "bessel bp 4 max")
      snd_test_neq(vals[1],
                   vct(0.023, 0.176, 0.245, 0.244, 0.179, 0.085, 0.031, 0.008, 0.001, 0),
                   "bessel bp 4 0.1 0.2 spect")
      # 
      f1 = make_bessel_bandstop(12, 0.1, 0.2)
      vals = sweep2bins(f1, 10)
      snd_test_neq(vals[0], 0.5, "bessel bs 12 max")
      snd_test_neq(vals[1],
                   vct(0.498, 0.325, 0.065, 0.066, 0.177, 0.297, 0.389, 0.452, 0.488, 0.5),
                   "bessel bs 12 0.1 0.2 spect")
    end
    #
    # elliptic
    #
    f1 = make_elliptic_lowpass(8, 0.1)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic lp 8 max") # okay
    if (not vequal(vals[1], vct(0.500, 0.515, 0.379, 0, 0, 0, 0, 0, 0, 0))) and
        (not vequal(vals[1], vct(0.500, 0.509, 0.385, 0, 0, 0, 0, 0, 0, 0))) and
        (not vequal(vals[1], vct(0.499, 0.498, 0.373, 0, 0, 0, 0, 0, 0, 0)))
      snd_display(snd_format_neq(vals[1],
                                 vct(0.500, 0.515, 0.379, 0, 0, 0, 0, 0, 0, 0),
                                 "elliptic lp 8 0.1 spect"))
    end
    f1 = make_elliptic_lowpass(12, 0.25)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic lp 12 max") # okay
    if (not vequal(vals[1], vct(0.476, 0.500, 0.491, 0.499, 0.494, 0.412, 0.003, 0.001, 0, 0))) and
        (not vequal(vals[1], vct(0.476, 0.500, 0.491, 0.499, 0.494, 0.561, 0.004, 0, 0, 0))) and
        (not vequal(vals[1], vct(0.476, 0.500, 0.491, 0.499, 0.493, 0.299, 0.006, 0.001, 0, 0)))
      snd_display(snd_format_neq(vals[1],
                                 vct(0.476, 0.500, 0.491, 0.499, 0.494, 0.412, 0.003, 0.001, 0, 0),
                                 "elliptic lp 12 0.25 spect"))
    end
    f1 = make_elliptic_lowpass(4, 0.4)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic lp 4 max") # okay
    snd_test_neq(vals[1],
                 vct(0.447, 0.453, 0.462, 0.477, 0.494, 0.500, 0.497, 0.496, 0.445, 0.003),
                 "elliptic lp 4 0.4 spect")
    f1 = make_elliptic_lowpass(8, 0.1, 0.1)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic lp 8 0.1 max") # okay
    snd_test_neq(vals[1],
                 vct(0.500, 0.499, 0.475, 0, 0, 0, 0, 0, 0, 0),
                 "elliptic lp 8 0.1 0.1 spect")
    f1 = make_elliptic_lowpass(8, 0.1, 0.1, 90)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic lp 8 0.1 90 max") # okay
    snd_test_neq(vals[1],
                 vct(0.500, 0.499, 0.475, 0, 0, 0, 0, 0, 0, 0),
                 "elliptic lp 8 0.1 0.1 90 spect")
    f1 = make_elliptic_lowpass(8, 0.25, 0.01, 90)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic lp 8 0.25 90 max") # okay
    snd_test_neq(vals[1],
                 vct(0.500, 0.500, 0.500, 0.500, 0.499, 0.495, 0.001, 0, 0, 0),
                 "elliptic lp 8 0.25 0.01 90 spect")
    #
    f1 = make_elliptic_highpass(4, 0.1)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic hp 4 max") # okay
    snd_test_neq(vals[1],
                 vct(0.004, 0.438, 0.516, 0.499, 0.502, 0.495, 0.478, 0.463, 0.453, 0.447),
                 "elliptic hp 4 0.1 spect")
    f1 = make_elliptic_highpass(12, 0.25)
    vals = sweep2bins(f1, 10)
    # snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic hp 12 max") # okay
    if (not vequal(vals[1], vct(0, 0.001, 0.001, 0.001, 0.026, 0.934, 0.518, 0.495, 0.503, 0.477))) and
        (not vequal(vals[1], vct(0, 0.001, 0.001, 0.001, 0.033, 1.185, 0.519, 0.495, 0.503, 0.477))) and
        (not vequal(vals[1], vct(0, 0.001, 0.001, 0.001, 0.018, 0.788, 0.520, 0.495, 0.503, 0.477)))
      snd_display(snd_format_neq(vals[1],
                                 vct(0, 0.001, 0.001, 0.001, 0.026, 0.934, 0.518, 0.495, 0.503, 0.477),
                                 "elliptic hp 12 0.25 spect: %s?"))
    end
    f1 = make_elliptic_highpass(12, 0.25, 0.01, 90)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic hp 12 90 max") # okay
    snd_test_neq(vals[1],
                 vct(0, 0, 0, 0, 0.499, 0.517, 0.503, 0.501, 0.500, 0.500),
                 "elliptic hp 12 0.25 0.01 90 spect")
    f1 = make_elliptic_highpass(4, 0.4)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic hp 4 0.4 max") # okay
    snd_test_neq(vals[1],
                 vct(0, 0, 0, 0.001, 0.001, 0.002, 0.023, 0.447, 0.515, 0.502),
                 "elliptic hp 4 0.4 spect")
    f1 = make_elliptic_highpass(8, 0.1, 0.1)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic hp 8 0.1 max") # okay
    snd_test_neq(vals[1],
                 vct(0, 0.478, 0.553, 0.506, 0.499, 0.501, 0.501, 0.499, 0.497, 0.495),
                 "elliptic hp 8 0.1 0.1 spect")
    f1 = make_elliptic_highpass(8, 0.1, 0.1, 90)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic hp 8 0.1 90 max") # okay
    snd_test_neq(vals[1],
                 vct(0, 0.478, 0.554, 0.506, 0.499, 0.501, 0.501, 0.499, 0.497, 0.495),
                 "elliptic hp 8 0.1 0.1 90 spect")
    f1 = make_elliptic_highpass(8, 0.25, 0.01, 90)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic hp 8 0.25 90 max") # okay
    snd_test_neq(vals[1],
                 vct(0, 0, 0, 0.001, 0.516, 0.517, 0.507, 0.503, 0.501, 0.500),
                 "elliptic hp 8 0.25 0.01 90 spect")
    #
    f1 = make_elliptic_bandpass(4, 0.1, 0.2, 0.1)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic bp 4 max") # okay
    snd_test_neq(vals[1],
                 vct(0.036, 0.546, 0.55, 0.51, 0.501, 0.032, 0.024, 0.009, 0.021, 0.024),
                 "elliptic bp 4 0.1 0.2 0.1 spect")
    f1 = make_elliptic_bandpass(6, 0.1, 0.2, 0.1, 90)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic bp 6 max") # okay
    snd_test_neq(vals[1],
                 vct(0.002, 0.511, 0.532, 0.503, 0.492, 0.003, 0.001, 0.001, 0.001, 0.001),
                 "elliptic bp 6 0.1 0.2 0.1 90 spect")
    # 
    f1 = make_elliptic_bandstop(4, 0.1, 0.3, 0.1)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic bs 4 max") # okay
    snd_test_neq(vals[1],
                 vct(0.499, 0.502, 0.498, 0.037, 0.05, 0.54, 0.544, 0.527, 0.526, 0.521),
                 "elliptic bs 4 0.1 0.3 0.1 spect")
    f1 = make_elliptic_bandstop(8, 0.1, 0.3, 0.1, 120)
    vals = sweep2bins(f1, 10)
    snd_test_any_neq(vals[0], 0.5, :fffequal?, "elliptic bs 8 max") # okay
    if (not vequal(vals[1], vct(0.500, 0.499, 0.476, 0, 0, 0.495, 0.526, 0.505, 0.501, 0.501))) and
        (not vequal(vals[1], vct(0.500, 0.499, 0.475, 0, 0, 0.495, 0.526, 0.505, 0.501, 0.501)))
      snd_display(snd_format_neq(vals[1],
                                 vct(0.500, 0.499, 0.476, 0, 0, 0.495, 0.526, 0.505, 0.501, 0.501),
                                 "elliptic bs 8 0.1 0.3 0.1 120 spect"))
    end
  end
end

def poly_roots_tests
  # degree=0
  unless (res = poly(0.0).roots).null?
    snd_display("poly_roots 0.0: %s?", res)
  end
  unless (res = poly(12.3).roots).null?
    snd_display("poly_roots 12.3: %s?", res)
  end
  # degree 0 + x=0
  if (res = poly(0.0, 1.0).roots) != [0.0]
    snd_display("poly_roots 0.0 1.0: %s?", res)
  end
  if (res = poly(0.0, 0.0, 0.0, 121.0).roots) != [0.0, 0.0, 0.0]
    snd_display("poly_roots 0.0 0.0 0.0 121.0: %s?", res)
  end
  # degree=1
  if (res = poly(-1.0, 1.0).roots) != [1.0]
    snd_display("poly_roots -1.0 1.0: %s?", res)
  end
  if (res = poly(-2.0, 4.0).roots) != [0.5]
    snd_display("poly_roots -2.0 4.0: %s?", res)
  end
  if (res = poly(Complex(0.0, -1.0), 1).roots) != [Complex(0.0, 1.0)]
    snd_display("poly_roots -i 1: %s?", res)
  end
  # linear x^n
  vals = poly(-1.0, 0.0, 0.0, 0.0, 1.0).roots
  if vcneql(vals, [Complex(0.0, -1.0), -1.0, Complex(0.0, 1.0), 1.0]) and
      vcneql(vals, [1.0, -1.0, Complex(0.0, 1.0), Complex(-0.0, -1.0)])
    snd_display("poly_roots -1 0 0 0 1: %s?", vals)
  end
  vals = poly(-16, 0, 0, 0, 1).roots
  if vcneql(vals, [Complex(0.0, -2.0), -2.0, Complex(0.0, 2.0), 2.0]) and
      vcneql(vals,  [2.0, -2.0, Complex(0.0, 2.0), Complex(-0.0, -2.0)])
    snd_display("poly_roots -16 0 0 0 1: %s?", vals)
  end
  if vcneql(res = poly(-32, 0, 0, 0, 0, 0, 0.5).roots,
            [Complex(1.0, -1.7320), Complex(-1.0, -1.7320), -2.0,
             Complex(-1.0, 1.7320), Complex(1.0, 1.7320), 2.0])
    snd_display("poly_roots -32 0 0 0 0 0 0.5: %s?", res)
  end
  # linear + x=0
  if (res = poly(0, -2, 4).roots) != [0.0, 0.5]
    snd_display("poly_roots 0 -2 4: %s?", res)
  end
  # degree=2
  if (res = poly(-1, 0, 1).roots) != [1.0, -1.0]
    snd_display("poly_roots -1 0 1: %s?", res)
  end
  if (res = poly(15, -8, 1).roots) != [5.0, 3.0]
    snd_display("poly_roots 15 -8 1: %s?", res)
  end
  if (res = poly(1, -2, 1).roots) != [1.0, 1.0]
    snd_display("poly_roots 1 -2 1: %s?", res)
  end
  if (res = poly(-1, Complex(0.0, 2.0), 1).roots) != [Complex(0.0, -1.0), Complex(0.0, -1.0)]
    snd_display("poly_roots -1 2i 1: %s?", res)
  end
  if vcneql(res = poly(1, 1, 5).roots, [Complex(-0.1, 0.43589), Complex(-0.1, -0.43589)])
    snd_display("poly_roots 1 1 5: %s?", res)
  end
  # 2 + x=0
  if (res = poly(0, 0, -1, 0, 1).roots) != [0.0, 0.0, 1.0, -1.0]
    snd_display("poly_roots 0 0 -1 0 1: %s?", res)
  end
  # quadratic in x^(n/2)
  if (res = poly(1, 0, -2, 0, 1).roots) != [-1.0, 1.0, -1.0, 1.0] and res !=  [1.0, 1.0, -1.0, -1.0]
    snd_display("poly_roots 1 0 -2 0 1: %s?", res)
  end
  if vcneql(res = poly(64, 0, 0, -16, 0, 0, 1).roots,
            [Complex(-1.0, -1.73205), Complex(-1.0, 1.73205), 2.0,
              Complex(-1.0, -1.73205), Complex(-1.0, 1.73205), 2.0])
    snd_display("poly_roots 64 0 0 -16 0 0 1: %s?", res)
  end
  # degree=3
  unless vequal(res = poly(-15, 23, -9, 1).roots, [5.0, 1.0, 3.0])
    snd_display("poly_roots -15 23 -9 1: %s?", res)
  end
  if vcneql(res = poly(-126, -15, 0, 1).roots,
            [6.0, Complex(-3.0, 3.46410), Complex(-3.0, -3.46410)])
    snd_display("poly_roots -126 -15 0 1: %s?", res)
  end
  if (res = poly(-1, 3, -3, 1).roots) != [1.0, 1.0, 1.0]
    snd_display("poly_roots -1 3 -3 1: %s?", res)
  end
  unless vequal(res = poly(1, -1, -1, 1).roots, [1.0, -1.0, 1.0])
    snd_display("poly_roots 1 -1 -1 1: %s?", res)
  end
  unless vequal(res = poly(2, -2, -2, 2).roots, [1.0, -1.0, 1.0])
    snd_display("poly_roots 2 -2 -2 2: %s %s %s?", res)
  end
  # degree=4
  if (res = poly(-15, 8, 14, -8, 1).roots) != [5.0, 3.0, 1.0, -1.0]
    snd_display("poly_roots -15 8 14 -8 1: [5.0, 3.0, 1.0, -1.0] != %s?", res)
  end
  vals = (poly(2, 1) * poly(-3, 1) * poly(8, 1) * poly(-9, 1)).reduce.roots
  unless vequal(vals, [9, 3, -2, -8])
    snd_display("poly_roots 4(1): %s?", vals)
  end
  vals = (poly(0.2, 1) * poly(-3, 1) * poly(0.8, 1) * poly(-9, 1)).reduce.roots
  unless vequal(vals, [9, 3, -0.2, -0.8])
    snd_display("poly_roots 4(2): %s?", vals)
  end
  vals = (poly(0.02, 1) * poly(-32, 1) * poly(0.8, 1) * poly(-9, 1)).reduce.roots
  unless vequal(vals, [32, 9, -0.02, -0.8])
    snd_display("poly_roots 4(3): %s?", vals)
  end
  # degree>4
  vals = (poly(1, 1) * poly(2, 1) * poly(-3, 1) * poly(-1, 1) * poly(-2, 1)).reduce.roots
  unless vequal(vals, [3, 2, -1, -2, 1])
    snd_display("poly_roots n(1): %s?", vals)
  end
  vals = (poly(1, 1) * poly(2, 1) * poly(-3, 1) * poly(8, 1) * poly(-9, 1)).reduce.roots
  unless vequal(vals, [9, 3, -2, -8, -1])
    snd_display("poly_roots n(2): %s?", vals)
  end
  vals = (poly(-1, 0, 1) * poly(9, 1) * poly(-3, 1) * poly(-10, 1) * poly(-2, 1)).reduce.roots
  unless vequal(vals, [10, 3, -1, -9, 2, 1])
    snd_display("poly_roots n(3): %s?", vals)
  end
  vals = poly(-1, 0, 1) * poly(-4, 0, 1) * poly(-3, 1) * poly(-10, 1) * poly(-9, 0, 1)
  vals = vals.reduce.roots
  unless vequal(vals, [10, 3, -2, -3, -1, 3, 2, 1])
    snd_display("poly_roots n(4): %s?", vals)
  end
  vals = (poly(-1, 0, 1) * poly(-4, 0, 1) * poly(-16, 0, 1) * poly(-25, 0, 1) *
            poly(-9, 0, 1)).reduce.roots
  unless vequal(vals, [5, -3, -4, -5, 4, -2, 3, -1, 2, 1])
    snd_display("poly_roots n(5): %s?", vals)
  end
  vals = (poly(1, 1) * poly(2, 1) * poly(-3, 1) * poly(1, 1) * poly(-2, 1)).reduce.roots
  unless vequal(vals, [3, -1, -1, -2, 2])
    snd_display("poly_roots n(6): %s?", vals)
  end
  vals = poly(-64, 0, 0, 0, 0, 0, 1).roots
  if vcneql(vals, [Complex(0.999, -1.732), Complex(-1.0, -1.732), -2.0,
                   Complex(-1.0, 1.732), Complex(1.0, 1.732), 2.0])
    snd_display("poly_roots 64 6: %s?", vals)
  end
  vals = poly(64, 0, 0, -16, 0, 0, 1).roots
  if vcneql(vals, [Complex(-1.0, -1.732), Complex(-1.0, 1.732), 2.0,
                   Complex(-1.0, -1.732), Complex(-1.0, 1.732), 2.0])
    snd_display("poly_roots 64 16 6: %s?", vals)
  end
  10.times do poly(random(1.0), random(1.0), random(1.0)).roots end
  10.times do poly(mus_random(1.0), mus_random(1.0), mus_random(1.0)).roots end
  vals1 = convolution(vct(1, 2, 3, 0, 0, 0, 0, 0), vct(1, 2, 3, 0, 0, 0, 0, 0), 8)
  vals2 = poly(1, 2, 3, 0) * poly(1, 2, 3, 0)
  unless vequal(vals1, vals2)
    snd_display("poly_multiply convolve: %s %s?", vals1, vals2)
  end
  10.times do
    poly(make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0))).roots
  end
  10.times do
    poly(make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0))).roots
  end
  10.times do poly(mus_random(1.0), mus_random(1.0), mus_random(1.0), mus_random(1.0)).roots end
  10.times do
    poly(make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0))).roots
  end
  10.times do
    poly(mus_random(1.0), mus_random(1.0), mus_random(1.0), mus_random(1.0), mus_random(1.0)).roots
  end
  10.times do
    poly(make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0)),
         make_rectangular(mus_random(1.0), mus_random(1.0))).roots
  end
  3.upto(20) do |i|
    v = Vct.new(i)
    v[0] = mus_random(1.0)
    v[i - 1] = 1.0
    poly_roots(v)
  end
  3.step(21, 2) do |i|
    v = Vct.new(i)
    v[0] = mus_random(1.0)
    v[i - 1] = 1.0
    v[(i - 1) / 2] = 1.0
    v.to_poly.roots
  end
  unless vequal(res = poly(1, -1, -1, 1).roots, [1.0, -1.0, 1.0])
    snd_display("poly_roots 1 -1 -1 1: %s?", res)
  end
  unless vequal(res = poly_roots(vct(2, -1, -2, 1)), [2.0, -1.0, 1.0])
    snd_display("poly_roots 2 -1 -2 1: %s?", res)
  end
  # FIXME (INFO)
  # 0.544 comes first with poly.rb
  if vcneql(res = poly(-1, 1, 1, 1).roots,
            [0.544, Complex(-0.772, 1.115), Complex(-0.772, -1.115)]) and
      vcneql(res = poly(-1, 1, 1, 1).roots,
             [Complex(-0.772, 1.115), Complex(-0.772, -1.115), 0.544])
    snd_display("poly_roots -1 1 1 1: %s?", res)
  end
  if (res = poly_roots(vct(-1, 3, -3, 1))) != [1.0, 1.0, 1.0]
    snd_display("poly_roots -1 3 -3 1: %s?", res)
  end
  if (res = poly_roots(vct(1, -4, 6, -4, 1))) != [1.0, 1.0, 1.0, 1.0]
    snd_display("poly_roots 1 -4 6 -4 1: %s?", res)
  end
  if vcneql(res = poly_roots(vct(0.5, 0, 0, 1)),
            [Complex(0.397, -0.687), -0.794, Complex(0.397, 0.687)]) and
      vcneql(res, [Complex(0.397, 0.687), Complex(0.397, -0.687), -0.794])
    snd_display("poly_roots 0.5 0 0 1: %s?", res)
  end
  # FIXME (INFO)
  # reduce added
  # without reduce: 3.0 -1.0 -2.0 -3.0 2.0-1.1555579666323415e-33i 1.0+2.9582283945787943e-31i
  res = (poly(-1, 1) * poly(1, 1) * poly(-2, 1) * poly(2, 1) * poly(-3, 1) * poly(3, 1)).reduce.roots
  if vcneql(res, [-3.0, 3.0, -1.0, 1.0, -2.0, 2.0])
    snd_display("cube in 2: %s?", res)
  end
end

def jc_reverb_1(decay_dur, low_pass, volume, amp_env)
  allpass1 = make_all_pass(-0.7, 0.7, 1051)
  allpass2 = make_all_pass(-0.7, 0.7,  337)
  allpass3 = make_all_pass(-0.7, 0.7,  113)
  comb1 = make_comb(0.742, 4799)
  comb2 = make_comb(0.733, 4999)
  comb3 = make_comb(0.715, 5399)
  comb4 = make_comb(0.697, 5801)
  outdel = make_delay((0.013 * srate).round)
  dur = decay_dur + frames / srate
  envA = (amp_env ? make_env(:envelope, amp_env, :scaler, volume, :duration, dur) : false)
  comb_sum_1 = comb_sum_2 = comb_sum = all_sums = delA = delB = 0.0
  map_chan(lambda do |inval|
             allpass_sum = all_pass(allpass3, all_pass(allpass2, all_pass(allpass1, inval)))
             comb_sum_2, comb_sum_1 = comb_sum_1, comb_sum
             comb_sum = (comb(comb1, allpass_sum) + comb(comb2, allpass_sum) +
                           comb(comb3, allpass_sum) + comb(comb4, allpass_sum))
             all_sums = if low_pass
                          0.25 * (comb_sum + comb_sum_2) + 0.5 * comb_sum_1
                        else
                          comb_sum
                        end
             inval + if envA
                       env(envA) * delay(outdel, all_sums)
                     else
                       volume * delay(outdel, all_sums)
                     end
           end, 0, (dur * srate).to_i)
end

# fm_violin
def fm_violin_1(start, dur, freq, amp, *args)
    fm_index, amp_env, periodic_vibrato_rate, random_vibrato_rate = nil
    periodic_vibrato_amp, random_vibrato_amp, noise_amount, noise_freq = nil
    ind_noise_freq, ind_noise_amount, amp_noise_freq, amp_noise_amount = nil
    gliss_env, gliss_amount, fm1_env, fm2_env, fm3_env, fm1_rat, fm2_rat, fm3_rat = nil
    fm1_index, fm2_index, fm3_index, base, index_type, reverb_amount, degree, distance = nil
    optkey(args, binding,
           [:fm_index, 1.0],
           [:amp_env, [0, 0, 25, 1, 75, 1, 100, 0]],
           [:periodic_vibrato_rate, 5.0],
           [:random_vibrato_rate, 16.0],
           [:periodic_vibrato_amp, 0.0025],
           [:random_vibrato_amp, 0.005],
           [:noise_amount, 0.0],
           [:noise_freq, 1000.0],
           [:ind_noise_freq, 10.0],
           [:ind_noise_amount, 0.0],
           [:amp_noise_freq, 20.0],
           [:amp_noise_amount, 0.0],
           [:gliss_env, [0, 0, 100, 0]],
           [:gliss_amount, 0.0],
           [:fm1_env, [0, 1, 25, 0.4, 75, 0.6, 100, 0]],
           [:fm2_env, [0, 1, 25, 0.4, 75, 0.6, 100, 0]],
           [:fm3_env, [0, 1, 25, 0.4, 75, 0.6, 100, 0]],
           [:fm1_rat, 1.0],
           [:fm2_rat, 3.0],
           [:fm3_rat, 4.0],
           [:fm1_index, false],
           [:fm2_index, false],
           [:fm3_index, false],
           [:base, 1.0],
           [:reverb_amount, 0.01],
           [:degree, random(90)],
           [:distance, 1.0])
  frq_scl = hz2radians(freq)
  modulate = fm_index.nonzero?
  maxdev = frq_scl * fm_index
  logfreq = log(freq)
  sqrtfreq = sqrt(freq)
  index1 = (fm1_index or [PI, maxdev * 5.0 / logfreq].min)
  index2 = (fm2_index or [PI, maxdev * 3.0 * (8.5 - logfreq) / (3.0 + freq * 0.001)].min)
  index3 = (fm3_index or [PI, maxdev * 4.0 / sqrtfreq].min)
  easy_case = (noise_amount.zero? and
                 fm1_env == fm2_env and 
                 fm1_env == fm3_env and 
                 fm1_rat == fm1_rat.floor and 
                 fm2_rat == fm2_rat.floor and 
                 fm3_rat == fm3_rat.floor)
  norm = ((easy_case and modulate and 1.0) or index1)
  carrier = make_oscil(:frequency, freq)
  fmosc1 = if modulate
             if easy_case
               make_polyshape(:frequency, fm1_rat * freq,
                              :coeffs, partials2polynomial([fm1_rat.to_i, index1,
                                                            (fm2_rat / fm1_rat).floor, index2,
                                                            (fm3_rat / fm1_rat).floor, index3]))
             else
               make_oscil(:frequency, fm1_rat * freq)
             end
           else
             false
           end
  fmosc2 = (modulate and (easy_case or make_oscil(:frequency, fm2_rat * freq)))
  fmosc3 = (modulate and (easy_case or make_oscil(:frequency, fm3_rat * freq)))
  ampf = make_env(:envelope, amp_env, :scaler, amp, :duration, dur, :base, base)
  indf1 = (modulate and make_env(:envelope, fm1_env, :scaler, norm, :duration, dur))
  indf2 = (modulate and
             (easy_case or make_env(:envelope, fm2_env, :scaler, index2, :duration, dur)))
  indf3 = (modulate and
             (easy_case or make_env(:envelope, fm3_env, :scaler, index3, :duration, dur)))
  frqf = make_env(:envelope, gliss_env, :scaler, gliss_amount * frq_scl, :duration, dur)
  pervib = make_triangle_wave(periodic_vibrato_rate, periodic_vibrato_amp *  frq_scl)
  ranvib = make_rand_interp(random_vibrato_rate, random_vibrato_amp * frq_scl)
  fm_noi = (noise_amount.nonzero? and make_rand(noise_freq, PI * noise_amount))
  ind_noi = ((ind_noise_amount.nonzero? and ind_noise_freq.nonzero?) and 
               make_rand_interp(ind_noise_freq, ind_noise_amount))
  amp_noi = ((amp_noise_amount.nonzero? and amp_noise_freq.nonzero?) and
               make_rand_interp(amp_noise_freq, amp_noise_amount))
  fuzz = modulation = 0.0
  ind_fuzz = amp_fuzz = 1.0
  out_data = make_vct!(seconds2samples(dur)) do
    if noise_amount.nonzero?
      fuzz = rand(fm_noi)
    end
    vib = env(frqf) + triangle_wave(pervib) + rand_interp(ranvib)
    if ind_noi
      ind_fuzz = 1.0 + rand_interp(ind_noi)
    end
    if amp_noi
      amp_fuzz = 1.0 + rand_interp(amp_noi)
    end
    if modulate
      modulation = if easy_case
                     env(indf1) * polyshape(fmosc1, 1.0, vib)
                   else
                     (env(indf1) * oscil(fmosc1, fm1_rat * vib + fuzz) +
                                  env(indf2) * oscil(fmosc2, fm2_rat * vib + fuzz) +
                                  env(indf3) * oscil(fmosc3, fm3_rat * vib + fuzz))
                   end
    end
    env(ampf) * amp_fuzz * oscil(carrier, vib + ind_fuzz * modulation)
  end
  beg = seconds2samples(start)
  loc = make_locsig(:channels, channels(false),
                    :degree, degree,
                    :reverb, reverb_amount,
                    :distance, distance)
  channels(false).times do |chn|
    mix_vct(vct_scale!(vct_copy(out_data), locsig_ref(loc, chn)), beg, false, chn, false)
  end
end

def fltit
  coeffs = vct(0.1, 0.2, 0.3, 0.4, 0.4, 0.3, 0.2, 0.1)
  flt = make_fir_filter(8, coeffs)
  es = make_array(8) do |i|
    if i == 5
      make_env(:envelope, [0, 0.4, 1, 1], :duration, 1.0)
    else
      make_env(:envelope, [0, coeffs[i], 1, 0], :length, 101)
    end
  end
  lambda do |x|
    val = fir_filter(flt, x)
    xcof = flt.xcoeffs
    es.each_with_index do |en, i| xcof[i] = env(en) end
    val
  end
end

def freq_sweep(dur)
  phase = 0.0
  freq = 0.0
  incr = PI / (dur * 1.05 * mus_srate)
  map_channel(lambda do |y|
                val = sin(phase)
                phase += freq
                freq += incr
                0.5 * val
              end)
end

def make_ssb_am_1(freq, order = 40)
  if order.even?
    order += 1
  end
  freq = freq.to_f
  carrier_freq = freq.abs
  cos_carrier = make_oscil(freq, 0.5 * PI)
  sin_carrier = make_oscil(freq)
  dly = make_delay(order)
  hlb = make_hilbert_transform(order)
  lambda do |y, fm|
    ccos = oscil(cos_carrier, fm)
    csin = oscil(sin_carrier, fm)
    yh = hilbert_transform(hlb, y)
    yd = delay(dly, y)
    if carrier_freq > 0.0
      ccos * yd - csin * yh # shift up
    else
      ccos * yd + csin * yh # shift down
    end
  end
end

def ssb_am_1(gen, y, fm = 0.0)
  gen.call(y, fm)
end

def rough_spectrum(ind)
  rd = make_sampler(0, ind, 0)
  mx = 0.0
  Vct.new(10) do
    sum = 0.0
    1000.times do
      val = rd.call
      sum = sum + val * val
    end
    if sum > mx
      mx = sum
    end
    sum
  end.scale!(1.0 / mx)
end

def print_and_check(gen, name, desc, desc1 = "", desc2 = "")
  if gen.name != name
    snd_display("mus_name %s: %s?", name, gen.name)
  end
  #  xen->sample: #<Proc:0x084bdd14@/usr/home/mike/Project/Sndtest/snd-test-new.rb:4470>
  if gen.name != "xen->sample"
    if gen.to_s != desc and gen.to_s != desc1 and gen.to_s != desc2
      snd_display_prev_caller("mus_describe %s: %s?", gen.name, gen)
    end
  end
  egen = gen
  unless egen.eql?(gen)
    snd_display_prev_caller("eql? %s: %s?", gen, egen)
  end
end

def test_gen_equal(g0, g1, g2)
  # g0 = g1 at start != g2
  g3 = g0
  gad = make_frame(2)
  unless g0.eql?(g3)
    snd_display_prev_caller("let %s %s.eql? %s?", g0.name, g0, g3)
  end
  unless g0.eql?(g1)
    snd_display_prev_caller("arg %s %s.eql? %s?", g0.name, g0, g1)
  end
  if g0 == g1
    snd_display_prev_caller("%s %s == %s?", g0.name, g0, g1)
  end
  if g0 == g2
    snd_display_prev_caller("%s %s == %s?", g0.name, g0, g2)
  end
  if g0 == gad
    snd_display_prev_caller("%s == frame %s %s?", g0.name, g0, gad)
  end
  g0.run
  g3.run
  g3.run
  unless g0.eql?(g3)
    snd_display_prev_caller("run let %s %s.eql? %s?", g0.name, g0, g3)
  end
  if g0.eql?(g1)
    snd_display_prev_caller("arg %s %s.eql? %s?", g0.name, g0, g1)
  end
  if g0 == g1
    snd_display_prev_caller("run %s %s == %s?", g0.name, g0, g1)
  end
  if g0 == g2
    snd_display_prev_caller("run %s %s != %s?", g0.name, g0, g2)
  end
end

def fm_test(gen)
  if mus_generator?(gen)
    gen.frequency = 0.0
    gen.phase = 0.0
    gen.run(0.0)
    if fneq(res = gen.phase, 0.0)
      snd_display_prev_caller("%s phase(0): %s?", gen, res)
    end
    gen.run(1.0)
    if fneq(res = gen.phase, 1.0)
      snd_display_prev_caller("%s phase(1): %s?", gen, res)
    end
    gen.run(0.0)
    if fneq(res = gen.phase, 1.0)
      snd_display_prev_caller("%s phase(1, 0): %s?", gen, res)
    end
    gen.frequency = radians2hz(2.0)
    if fneq(res = gen.increment, 2.0)
      snd_display_prev_caller("%s increment: %s", gen, res)
    end
    gen.increment = 2.0
    if fneq(res = gen.frequency, radians2hz(2.0))
      snd_display_prev_caller("%s set increment: %s %s", gen, gen.increment, hz2radians(res))
    end
    gen.run(0.0)
    if fneq(res = gen.phase, 3.0)
      snd_display_prev_caller("%s phase(1, 2): %s %s?", gen, res, gen.frequency)
    end
    gen.run(1.0)
    if fneq(res = gen.phase, 6.0)
      snd_display_prev_caller("%s phase(3, 2, 1): %s %s?", gen, res, gen.frequency)
    end
    10.times do gen.run(10.0) end
    if fneq(res = gen.phase, 26 + 100 - TWO_PI * 20)
      snd_display_prev_caller("%s phase (over): %s %s?", gen, res, gen.frequency)
    end
    gen.frequency = 0.0
    gen.phase = 0.0
    gen.run(1234567812345678)
    gen.run(-1234567812345678)
    gen.frequency = 0.0
    gen.phase = 0.0
    gen.run(-2.0)
    if fneq(res = gen.phase, -2.0) and fneq(res, TWO_PI - 2)
      snd_display_prev_caller("phase %s freq: %s?", res, gen.frequency)
    end
  else
    snd_display_prev_caller("%s not a gen?", gen.inspect)
  end
end

def frame_equal?(f1, f2)
  if f1 and f2 and (len = f1.length) == f2.length
    len.times do |chn|
      if fneq(frame_ref(f1, chn), frame_ref(f2, chn))
        return false
      end
    end
    true
  else
    false
  end
end

def make_random_mixer(size)
  mx = make_mixer(size)
  size.times do |i|
    size.times do |j|
      mixer_set!(mx, i, j, 1.0 - random(2.0))
    end
  end
  mx
end

def mixer_copy(umx)
  size = umx.length
  mx = make_mixer(size)
  size.times do |i|
    size.times do |j|
      mixer_set!(mx, i, j, mixer_ref(umx, i, j))
    end
  end
  mx
end

def test_08_00
  set_mus_srate(22050)
  samps = seconds2samples(1.0)
  secs = samples2seconds(22050)
  if samps != 22050
    snd_display("seconds2samples: %s?", samps)
  end
  if fneq(secs, 1.0)
    snd_display("samples2seconds: %s?", secs)
  end
  if mus_file_buffer_size != $default_file_buffer_size
    snd_display("mus_file_buffer_size: %s?", mus_file_buffer_size)
  end
  if (res = Snd.catch do set_mus_file_buffer_size(false) end).first != :wrong_type_arg
    snd_display("mus_file_buffer_size bad size: %s?", res)
  end
  set_mus_file_buffer_size(128)
  if (res = mus_file_buffer_size) != 128
    snd_display("set_mus_file_buffer_size: %s?", res)
  end
  set_mus_file_buffer_size($default_file_buffer_size)
  if (res = mus_array_print_length) != 8
    snd_display("mus_array_print_length: %s?", res)
  end
  set_mus_array_print_length(32)
  if (res = mus_array_print_length) != 32
    snd_display("set_mus_array_print_length: %s?", res)
  end
  set_mus_array_print_length(8)
  fudge = mus_float_equal_fudge_factor
  if (mus_float_equal_fudge_factor - 0.0000001).abs > 0.00000001
    snd_display("mus_float_equal_fudge_factor: %s?", mus_float_equal_fudge_factor)
  end
  set_mus_float_equal_fudge_factor(0.1)
  if fneq(mus_float_equal_fudge_factor, 0.1)
    snd_display("set_mus_float_equal_fudge_factor: %s?", mus_float_equal_fudge_factor)
  end
  set_mus_float_equal_fudge_factor(fudge)
  if fneq(mus_srate, 22050.0)
    snd_display("mus_srate: %s?", mus_srate)
  end
  if fneq(res = hz2radians(1.0), 2.84951704088598e-4)
    snd_display("hz2radians: %s?", res)
  end
  if fneq(res = radians2hz(2.84951704088598e-4), 1.0)
    snd_display("radians2hz: %s?", res)
  end
  if fneq(res = radians2degrees(1.0), 57.2957801818848)
    snd_display("radians2degrees: %s?", res)
  end
  if fneq(res = degrees2radians(57.2957801818848), 1.0)
    snd_display("degrees2radians: %s?", res)
  end
  if fneq(res = linear2db(0.25), -12.0411996841431)
    snd_display("linear2db: %s?", res)
  end
  if fneq(res = db2linear(-12.0411996841431), 0.25)
    snd_display("db2linear: %s?", res)
  end
  if fneq(res = ring_modulate(0.4, 0.5), 0.2)
    snd_display("ring_modulate: %s?", res)
  end
  if fneq(res = amplitude_modulate(1.0, 0.5, 0.4), 0.7)
    snd_display("amplitude_modulate: %s?", res)
  end
  if fneq(res = contrast_enhancement(0.1, 0.75), sin(0.1 * (PI / 2) + 0.75 * sin(0.1 * 2.0 * PI)))
    snd_display("contrast_enhancement: %s (0.562925306221587)", res)
  end
  if fneq(res = contrast_enhancement(1.0), 1.0)
    snd_display("contrast_enhancement: %s (1.0)", res)
  end
  #
  [[partials2polynomial([1, 1, 2, 1], Mus_chebyshev_first_kind),
    vct(-1.0, 1.0, 2.0)],
   [partials2polynomial([1, 1, 2, 1], Mus_chebyshev_second_kind),
    vct(1.0, 2.0, 0.0)],
   [partials2polynomial([1, 1, 2, 1, 3, 1, 5, 1], Mus_chebyshev_first_kind),
    vct(-1.0, 3.0, 2.0, -16.0, 0.0, 16.0)],
   [partials2polynomial([1, 1, 2, 1, 3, 1, 5, 1], Mus_chebyshev_second_kind),
    vct(1.0, 2.0, -8.0, 0.0, 16.0, 0.0)],
   [partials2polynomial([1, 1, 2, 0.5, 3, 0.1, 6, 0.01], Mus_chebyshev_first_kind),
    vct(-0.51, 0.7, 1.18, 0.4, -0.48, 0.0, 0.32)],
   [partials2polynomial([1, 1, 2, 0.5, 3, 0.1, 6, 0.01], Mus_chebyshev_second_kind),
    vct(0.9, 1.06, 0.4, -0.32, 0.0, 0.32, 0.0)],
   [partials2polynomial([1, 9, 2, 3, 3, 5, 4, 7, 5, 1]),
    vct(4.0, -1.0, -50.0, 0.0, 56.0, 16.0)],
   [partials2polynomial([7, 1]),
    vct(0.0, -7.0, 0.0, 56.0, 0.0, -112.0, 0.0, 64.0)],
   [partials2polynomial([7, 1], Mus_chebyshev_first_kind),
    vct(0.0, -7.0, 0.0, 56.0, 0.0, -112.0, 0.0, 64.0)],
   [partials2polynomial([7, 1], Mus_chebyshev_second_kind),
    vct(-1.0, 0.0, 24.0, 0.0, -80.0, 0.0, 64.0, 0.0)]].each_with_index do |args, i|
    vals, orig = args
    unless vequal(vals, orig)
      snd_display("partials2polynomial[%s]: %s?", i + 1, vals)
    end
  end
  #
  if defined? cosh
    lv7 = partials2polynomial([7, 1])
    lv8 = partials2polynomial([7, 1], Mus_chebyshev_second_kind)
    #
    if fneq(res1 = polynomial(lv7, 1.0), res2 = cosh(7.0 * acosh(1.0)))
      snd_display("ccosh cheb 7 1.0: %s %s?", res1, res2)
    end
    if fneq(res1 = polynomial(lv7, 1.0), res2 = cos(7.0 * acos(1.0)))
      snd_display("cos cheb 7 1.0: %s %s?", res1, res2)
    end
    if fneq(res1 = polynomial(lv8, 1.0), res2 = sin(7.0 * acos(1.0)) / sin(acos(1.0)))
      snd_display("acos cheb 7 1.0: %s %s?", res1, res2)
    end
    10.times do
      val = mus_random(1.0)
      if fneq(res1 = polynomial(lv7, val), res2 = cosh(7.0 * acosh(val)))
        snd_display("ccosh cheb 7 %s: %s %s?", val, res1, res2)
      end
      if fneq(res1 = polynomial(lv7, val), res2 = cos(7.0 * acos(val)))
        snd_display("cos cheb 7 %s: %s %s?", val, res1, res2)
      end
      if fneq(res1 = polynomial(lv8, val), res2 = sin(7.0 * acos(val)) / sin(acos(val)))
        snd_display("acos cheb 7 %s: %s %s?", val, res1, res2)
      end
    end
  end
  # 
  # check phase-quadrature cancellations
  #
  cos_coeffs = partials2polynomial([1, 1, 2, 1], Mus_chebyshev_first_kind)
  sin_coeffs = partials2polynomial([1, 1, 2, 1], Mus_chebyshev_second_kind)
  incr = (2 * PI * 440.0) / 22050.0
  a = 0.0
  1100.times do
    x = cos(a)
    y = sin(a)
    cax = polynomial(cos_coeffs, x)
    sax = polynomial(sin_coeffs, x)
    upper = cos(2 * a) * cax - sin(2 * a) * y * sax
    lower = cos(2 * a) * cax + sin(2 * a) * y * sax
    upper2 = cos(a * 3) + cos(a * 4)
    lower2 = 1.0 + cos(a)
    if fneq(upper, upper2) or fneq(lower, lower2)
      snd_display("%s %s, %s %s?", upper, upper2, lower, lower2)
    end
    a += incr
  end
  #
  if (res = Snd.catch do harmonicizer(550.0, [0.5, 0.3, 0.2], 10) end).first != :no_data
    snd_display("odd length arg to partials2polynomial: %s", res.inspect)
  end
  rdat = make_vct(16)
  idat = make_vct(16)
  vdat = make_vct(16)
  rdat[0] = 1.0
  vdat[0] = 1.0
  v0 = spectrum(rdat, idat, make_fft_window(Rectangular_window, 16), 1)
  v1 = snd_spectrum(vdat, Rectangular_window, 16, true)
  8.times do |i|
    if fneq(v0[i], v1[i])
      snd_display("spectra not equal 1: %s %s?", v0, v1)
    end
  end
  idat.scale!(0.0)
  rdat.scale!(0.0)
  rdat[0] = 1.0
  v0 = spectrum(rdat, idat, make_fft_window(Rectangular_window, 17), 1)
  v1 = snd_spectrum(vdat, Rectangular_window, 16, true)
  8.times do |i|
    if fneq(v0[i], v1[i])
      snd_display("spectra not equal 0: %s %s?", v0, v1)
    end
  end
  if (res = Snd.catch do spectrum(rdat, idat, false, -1) end).first != :out_of_range
    snd_display("spectrum bad type: %s", res.inspect)
  end
  #
  rdat = make_vct(16)
  idat = make_vct(16)
  xdat = make_vct(16)
  ydat = make_vct(16)
  rdat[3] = 1.0
  xdat[3] = 1.0
  fft(rdat, idat, 1)
  mus_fft(xdat, ydat, 16, 1)
  if fneq(rdat[0], xdat[0])
    snd_display("ffts: %s %s", rdat, xdat)
  end
  fft(rdat, idat, -1)
  mus_fft(xdat, ydat, 17, -1)
  16.times do |i|
    if (i == 3 and (fneq(rdat[i], 16.0) or fneq(xdat[i], 16.0))) or
        (i != 3 and (fneq(rdat[i], 0.0) or fneq(xdat[i], 0.0)))
      snd_display("fft real[%s]: %s %s?", i, rdat[i], xdat[i])
    end
    if fneq(idat[i], 0.0) or fneq(ydat[i], 0.0)
      snd_display("fft imag[%s]: %s %s?", i, idat[i], ydat[i])
    end
  end
  if (res = Snd.catch do mus_fft(xdat, ydat, -1, 0) end).first != :out_of_range
    snd_display("mus_fft bad len: %s", res.inspect)
  end
  #
  rdat = make_vct(20)
  idat = make_vct(19)
  rdat[3] = 1.0
  mus_fft(rdat, idat)
  convolution(rdat, idat)
  spectrum(rdat, idat, false)
  #
  v0 = make_vct(10)
  v1 = make_vct(10)
  vct_fill!(v0, 1.0)
  multiply_arrays(v0, v1, 1)
  unless vequal(v0, vct(0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0))
    snd_display("multiply_arrays[0]: %s?", v0)
  end
  multiply_arrays(v0, v1, 100)
  if fneq(vct_peak(v0), 0.0)
    snd_display("multiply_arrays[100]: %s?", v0)
  end
  vct_fill!(v0, 1.0)
  vct_fill!(v1, 0.5)
  multiply_arrays(v0, v1)
  if fneq(v0[0], 0.5)
    snd_display("multiply_arrays: %s?", v0[0])
  end
  if fneq(res = dot_product(v0, v1), 2.5)
    snd_display("dot_product: %s?", res)
  end
  if fneq(res = dot_product(v0, v1, 10), 2.5)
    snd_display("dot_product (10): %s?", res)
  end
  if fneq(res = dot_product(v0, v1, 3), 0.75)
    snd_display("dot_product (3): %s?", res)
  end
  clear_array(v0)
  if fneq(v0[3], 0.0)
    snd_display("clear_array: %s?", v0)
  end
  vct_fill!(v0, 1.0)
  vct_fill!(v1, 0.5)
  if fneq((res = rectangular2polar(v0, v1))[0], 1.118)
    snd_display("rectangular2polar: %s?", res)
  end
  vct_fill!(v0, 1.0)
  vct_fill!(v1, 1.0)
  rectangular2polar(v0, v1)
  if fneq(v0[0], sqrt(2.0)) or fneq(v1[0], -atan2(1.0, 1.0))
    snd_display("rectangular2polar (%s %s): %s %s?", sqrt(2.0), -atan2(1.0, 1.0), v0[0], v1[0])
  end
  polar2rectangular(v0, v1)
  if fneq(v0[0], 1.0) or fneq(v1[0], 1.0)
    snd_display("polar2rectangular (1 1): %s %s?", v0[0], v1[0])
  end
  v0 = make_vct(1, 1.0)
  v1 = make_vct(1, 1.0)
  val = 0.123
  rectangular2polar(v0, v1)
  val = v0.first
  polar2rectangular(v0, v1)
  v = vct(v1.first)
  if fneq(v[0], 1.0)
    snd_display("run r->p not inverted: %s?", v)
  end
  if fneq(val, sqrt(2.0))
    snd_display("r->p: %s?", val)
  end
  #
  ind = open_sound("oboe.snd")
  rl = channel2vct(1200, 512)
  im = make_vct(512)
  fft(rl, im, 512)
  rl_copy = vct_copy(rl)
  im_copy = vct_copy(im)
  rectangular2polar(rl, im)
  polar2rectangular(rl, im)
  512.times do |i|
    if fneq(rl[i], rl_copy[i]) or fneq(im[i], im_copy[i])
      snd_display("polar2rectangular[%s]: %s %s %s %s?", i, rl[i], rl_copy[i], im[i], im_copy[i])
    end
  end
  close_sound(ind)
  #
  if defined? edot_product                        # edot_product in dsp.rb
    vals = make_vct(1, 1.0)
    if fneq(res = edot_product(0.0, vals), 1.0)
      snd_display("edot 1.0: %s?", res)
    end
    vals[0] = 0.0
    if fneq(res = edot_product(0.0, vals), 0.0)
      snd_display("edot 0.0: %s?", res)
    end
    vals = make_array(1, 1.0)
    if fneq(res = edot_product(0.0, vals), 1.0)
      snd_display("edot 1.0: %s?", res)
    end
    vals[0] = Complex(0.0)
    if cneq(res = edot_product(0.0, vals), Complex(0.0))
      snd_display("edot i: %s?", res)
    end
    vals = make_vct(4, 1.0)
    v1 = edot_product(0.25 * TWO_PI, vals)
    v2 = exp(0.00 * TWO_PI) +
         exp(0.25 * TWO_PI) +
         exp(0.50 * TWO_PI) +
         exp(0.75 * TWO_PI)
    if fneq(v1, v2)
      snd_display("edot 4 i: %s %s?", v1, v2)
    end
    vals = make_array(4) do |i| i + 1.0 end
    v1 = edot_product(0.25 * TWO_PI * Complex(0.0), vals)
    v2 = 1 * exp(0.00 * TWO_PI * Complex(0.0)) +
         2 * exp(0.25 * TWO_PI * Complex(0.0)) +
         3 * exp(0.50 * TWO_PI * Complex(0.0)) +
         4 * exp(0.75 * TWO_PI * Complex(0.0))
    if cneq(v1, v2)
      snd_display("edot 4 -i: %s %s?", v1, v2)
    end
    vals.map! do |i| i + Complex(1.0) end
    v1 = edot_product(0.25 * TWO_PI * Complex(0.0, -1), vals)
    v2 = Complex(1.0) * exp(0.00 * TWO_PI * Complex(0.0, -1)) +
         Complex(2.0) * exp(0.25 * TWO_PI * Complex(0.0, -1)) +
         Complex(3.0) * exp(0.50 * TWO_PI * Complex(0.0, -1)) +
         Complex(4.0) * exp(0.75 * TWO_PI * Complex(0.0, -1))
    if cneq(v1, v2)
      snd_display("edot 4 -i * i: %s %s?", v1, v2)
    end
  end
  #
  v0 = vct(1.0, 0.5, 0.1)
  if fneq(res0 = polynomial(v0, 0.0), 1.0) or
      fneq(res1 = polynomial(v0, 1.0), 1.6) or
      fneq(res2 = polynomial(v0, 2.0), 2.4)
    snd_display("polynomial: %s %s %s?", res0, res1, res2)
  end
  if fneq(res = polynomial(vct(0.0, 2.0), 0.5), 1.0)
    snd_display("polynomial 2.0 * 0.5: %s?", res)
  end
  if (res = Snd.catch do polynomial(false, 1.0) end).first != :wrong_type_arg
    snd_display("polynomial empty coeffs: %s", res.inspect)
  end
  # 
  coeffs = vct(1.0, 0.0, -0.4999999963, 0.0, 0.0416666418,
               0.0, -0.0013888397, 0.0, 0.0000247609, 0.0, -0.0000002605)
  new_cos = lambda do |x|
    if (x = x.abs) <= HALF_PI
      polynomial(coeffs, x)
    elsif (nx = fmod(x, TWO_PI)) <= HALF_PI
      polynomial(coeffs, nx)
    elsif nx <= PI
      -polynomial(coeffs, PI - nx)
    elsif nx < 1.5 * PI
      -polynomial(coeffs, nx - PI)
    else
      polynomial(coeffs, TWO_PI - nx)
    end
  end
  err = 0.0
  x = -10.0
  2000.times do |i|
    diff = (Math.cos(x) - new_cos.call(x)).abs
    if diff > err
      err = diff
    end
    x += 0.01
  end
  if err > 1.1e-7
    snd_display("new_cos poly err: %s?", err)
  end
  # 
  # POLY
  #
  unless vequal(res = poly(0.1, 0.2, 0.3) + vct(0, 1, 2, 3, 4), vct(0.1, 1.2, 2.3, 3, 4))
    snd_display("poly_add 1: %s?", res)
  end
  unless vequal(res = poly(0.1, 0.2, 0.3) + 0.5, vct(0.6, 0.2, 0.3))
    snd_display("poly_add 2: %s?", res)
  end
  unless vequal(res = 0.5 + poly(0.1, 0.2, 0.3), vct(0.6, 0.2, 0.3))
    snd_display("poly_add 3: %s?", res)
  end
  # 
  unless vequal(res = poly(1, 1) * vct(-1, 1), vct(-1, 0, 1, 0))
    snd_display("poly_multiply 1: %s?", res)
  end
  unless vequal(res = poly(-5, 1) * vct(3, 7, 2), vct(-15, -32, -3, 2, 0))
    snd_display("poly_multiply 2: %s?", res)
  end
  unless vequal(res = poly(-30, -4, 2) * vct(0.5, 1), vct(-15, -32, -3, 2, 0))
    snd_display("poly_multiply 3: %s?", res)
  end
  unless vequal(res = poly(-30, -4, 2) * 0.5, vct(-15, -2, 1))
    snd_display("poly_multiply 4: %s?", res)
  end
  unless vequal(res = 2.0 * poly(-30, -4, 2), vct(-60, -8, 4))
    snd_display("poly_multiply 5: %s?", res)
  end
  #
  if (not vequal((res = poly(-1, 0, 1) / vct(1, 1))[0], vct(-1, 1, 0))) or
      (not vequal(res[1], vct(0, 0, 0)))
    snd_display("poly_div 1: %s?", res)
  end
  if (not vequal((res = poly(-15, -32, -3, 2) / vct(-5, 1))[0], vct(3, 7, 2, 0))) or
      (not vequal(res[1], vct(0, 0, 0, 0)))
    snd_display("poly_div 2: %s?", res)
  end
  if (not vequal((res = poly(-15, -32, -3, 2) / vct(3, 1))[0], vct(-5, -9, 2, 0))) or
      (not vequal(res[1], vct(0, 0, 0, 0)))
    snd_display("poly_div 3: %s?", res)
  end
  if (not vequal((res = poly(-15, -32, -3, 2) / vct(0.5, 1))[0], vct(-30, -4, 2, 0))) or
      (not vequal(res[1], vct(0, 0, 0, 0)))
    snd_display("poly_div 4: %s?", res)
  end
  if (not vequal((res = poly(-15, -32, -3, 2) / vct(3, 7, 2))[0], vct(-5, 1, 0, 0))) or
      (not vequal(res[1], vct(0, 0, 0, 0)))
    snd_display("poly_div 5: %s?", res)
  end
  unless vequal((res = poly(-15, -32, -3, 2) / 2.0)[0], vct(-7.5, -16, -1.5, 1))
    snd_display("poly_div 6: %s?", res)
  end
  unless vequal((res = poly(-1, 0, 0, 0, 1) / vct(1, 0, 1))[0], vct(-1, 0, 1, 0, 0)) and
      vequal(res[1], vct(0, 0, 0, 0, 0))
    snd_display("poly_div 7: %s?", res)
  end
  unless vequal((res = poly(-1, 0, 0, 0, 0, 0, 0, 0, 1) / vct(1, 0, 0, 0, 1))[0],
                vct(-1, 0, 0, 0, 1, 0, 0, 0, 0)) and
      vequal(res[1], vct(0, 0, 0, 0, 0, 0, 0, 0, 0))
    snd_display("poly_div 8: %s?", res)
  end
  unless vequal((res = poly(-1, 0, 1) / vct(-1, 0, 1))[0], vct(1, 0, 0)) and
      vequal(res[1], vct(0, 0, 0))
    snd_display("poly_div 9: %s?", res)
  end
  unless vequal((res = poly(-1, 0, 1) / vct(2, 1))[0], vct(-2, 1, 0)) and
      vequal(res[1], vct(3, 0, 0))
    snd_display("poly_div 10: %s?", res)
  end
  unless vequal((res = poly(2, 1) / vct(-1, 0, 1))[0], vct(0)) and
      vequal(res[1], vct(-1, 0, 1))
    snd_display("poly_div 11: %s?", res)
  end
  unless vequal((res = poly(1, 2, 3, 0, 1) / vct(0, 0, 0, 1))[0], vct(0, 1, 0, 0, 0)) and
      vequal(res[1], vct(1, 2, 3, 0, 0))
    snd_display("poly_div 12: %s?", res)
  end
  # 
  ind = open_sound("1a.snd")
  v1 = channel2vct(0, 100, ind, 0)
  v2 = channel2vct(0, 100, ind, 0)
  vals = poly_div(v1, v2)[0]
  res = make_vct(100)
  res[0] = 1.0
  unless vequal(vals, res)
    snd_display("poly1 1a: %s?", valse)
  end
  close_sound(ind)
  #
  unless vequal(res = poly(0.5, 1, 2, 4).derivative, vct(1, 4, 12))
    snd_display("poly_derivative: %s?", res)
  end
  # 
  unless vequal(res = poly(1, 2, 3).reduce, vct(1, 2, 3))
    snd_display("reduce 1: %s?", res)
  end
  unless vequal(res = poly(1, 2, 3, 0, 0, 0).reduce, vct(1, 2, 3))
    snd_display("reduce 2: %s?", res)
  end
  unless vequal(res = poly(0, 0, 0, 0, 1, 0).reduce, vct(0, 0, 0, 0, 1))
    snd_display("reduce 3: %s?", res)
  end
  #
  res = (poly(2, 1) * vct(-3, 1)).reduce.gcd(vct(2, 1))
  unless vequal(res, vct(2, 1))
    snd_display("poly_gcd 1: %s?", res)
  end
  res = (poly(2, 1) * vct(-3, 1)).reduce.gcd(vct(3, 1))
  unless vequal(res, vct(0))
    snd_display("poly_gcd 2: %s?", res)
  end
  res = (poly(2, 1) * vct(-3, 1)).reduce.gcd(vct(-3, 1))
  unless vequal(res, vct(-3, 1))
    snd_display("poly_gcd 3: %s?", res)
  end
  res = (poly(8, 1) * poly(2, 1) * poly(-3, 1)).reduce.gcd(vct(-3, 1))
  unless vequal(res, vct(-3, 1))
    snd_display("poly_gcd 4: %s?", res)
  end
  res = (poly(8, 1) * poly(2, 1) * [-3, 1]).reduce.gcd((poly(8, 1) * [-3, 1]).reduce)
  unless vequal(res, vct(-24, 5, 1))
    snd_display("poly_gcd 5: %s?", res)
  end
  unless vequal(res = poly(-1, 0, 1).gcd([2, -2, -1, 1]), [0])
    snd_display("poly_gcd 6: %s?", res)
  end
  unless vequal(res = poly(2, -2, -1, 1).gcd([-1, 0, 1]), [1, -1])
    snd_display("poly_gcd 7: %s?", res)
  end
  unless vequal(res = poly(2, -2, -1, 1).gcd([-2.5, 1]), [0])
    snd_display("poly_gcd 8: %s?", res)
  end
  #
  poly_roots_tests
  #
  if fneq(res = poly(-1, 0, 1).resultant([1, -2, 1]), 0.0)
    snd_display("poly_resultant 0: %s?", res)
  end
  if fneq(res = poly(-1, 0, 2).resultant([1, -2, 1]), 1.0)
    snd_display("poly_resultant 1: %s?", res)
  end
  if fneq(res = poly(-1, 0, 1).resultant([1, 1]), 0.0)
    snd_display("poly_resultant 2: %s?", res)
  end
  if fneq(res = poly(-1, 0, 1).resultant([2, 1]), 3.0)
    snd_display("poly_resultant 3: %s?", res)
  end
  
  if fneq(poly(-1, 0, 1).discriminant, -4.0)
    snd_display("poly_discriminat 0: %s?", res)
  end
  if fneq(poly(1, -2, 1).discriminant, 0.0)
    snd_display("poly_discriminat 1: %s?", res)
  end
  res = (poly(-1, 1) * poly(-1, 1) * poly(3, 1)).reduce.discriminant
  if fneq(res, 0.0)
    snd_display("poly_discriminat 2: %s?", res)
  end
  res = (poly(-1, 1) * poly(-1, 1) * poly(3, 1) * poly(2, 1)).reduce.discriminant
  if fneq(res, 0.0)
    snd_display("poly_discriminat 3: %s?", res)
  end
  res = (poly(1, 1) * poly(-1, 1) * poly(3, 1) * poly(2, 1)).reduce.discriminant
  if fneq(res, 2304.0)
    snd_display("poly_discriminat 4: %s?", res)
  end
  res = (poly(1, 1) * poly(-1, 1) * poly(3, 1) * poly(3, 1)).reduce.discriminant
  if fneq(res, 0.0)
    snd_display("poly_discriminat 5: %s?", res)
  end
  # 
  v0 = make_vct!(10) do |i| i end
  if fneq(res = array_interp(v0, 3.5), 3.5)
    snd_display("array_interp: %s?", res)
  end
  if fneq(res = array_interp(v0, 13.5), 3.5)
    snd_display("array_interp (13.5): %s?", res)
  end
  if fneq(res = array_interp(v0, -6.5), 3.5)
    snd_display("array_interp (-6.5): %s?", res)
  end
  if fneq(res = array_interp(v0, 103.6), 3.6)
    snd_display("array_interp (103.6): %s?", res)
  end
  if fneq(res = array_interp(v0, -106.6), 3.4)
    snd_display("array_interp (-106.6): %s?", res)
  end
  if fneq(res = array_interp(v0, -0.5), 4.5)
    snd_display("array_interp (-0.5): %s?", res)
  end
  if fneq(res = array_interp(v0, -0.9), 8.1)
    snd_display("array_interp (-0.9): %s?", res)
  end
  if fneq(res = array_interp(v0, -0.1), 0.9)
    snd_display("array_interp (-0.1): %s?", res)
  end
  if fneq(res = array_interp(v0, 9.1), 8.1)
    snd_display("array_interp (9.1): %s?", res)
  end
  if fneq(res = array_interp(v0, 9.9), 0.9)
    snd_display("array_interp (9.9): %s?", res)
  end
  if fneq(res = array_interp(v0, 10.1), 0.1)
    snd_display("array_interp (10.1): %s?", res)
  end
  if (res = Snd.catch do array_interp(v0, 1, -10) end).first != :out_of_range
    snd_display("array_interp bad index: %s", res.inspect)
  end
  #
  v0 = make_vct!(10) do |i| i end
  if fneq(res = mus_interpolate(Mus_interp_linear, 1.5, v0), 1.5)
    snd_display("mus_interpolate linear: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_all_pass, 1.5, v0), 1.5)
    snd_display("mus_interpolate all-pass: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_none, 1.5, v0), 1.0)
    snd_display("mus_interpolate none: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_hermite, 1.5, v0), 1.5)
    snd_display("mus_interpolate hermite: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_bezier, 1.5, v0), 1.5)
    snd_display("mus_interpolate bezier: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_lagrange, 1.5, v0), 1.5)
    snd_display("mus_interpolate lagrange: %s?", res)
  end
  v0.map_with_index! do |val, i| sin(PI * (i / 5.0)) end
  if fneq(res = mus_interpolate(Mus_interp_linear, 1.5, v0), 0.7694)
    snd_display("mus_interpolate linear sin: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_all_pass, 1.5, v0), 0.7694)
    snd_display("mus_interpolate all-pass sin: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_none, 1.5, v0), 0.5877)
    snd_display("mus_interpolate none sin: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_hermite, 1.5, v0), 0.8061)
    snd_display("mus_interpolate hermite sin: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_bezier, 1.5, v0), 0.6959)
    snd_display("mus_interpolate bezier sin: %s?", res)
  end
  if fneq(res = mus_interpolate(Mus_interp_lagrange, 1.5, v0), 0.7975)
    snd_display("mus_interpolate lagrange sin: %s?", res)
  end
  if (res = Snd.catch do mus_interpolate(1234, 1.0, make_vct(3)) end).first != :out_of_range
    snd_display("mus_interpolate 1234: %s", res.inspect)
  end
  if (res = Snd.catch do
        mus_interpolate(Mus_interp_linear, 1.0, make_vct(3), -1)
      end).first != :out_of_range
    snd_display("mus_interpolate size -1: %s", res.inspect)
  end
end

def test_08_01
  gen = make_delay(3)
  gen2 = make_delay(3)
  gen1 = make_delay(4, :initial_contents, [1.0, 0.5, 0.25, 0.0])
  gen3 = make_delay(4, :initial_contents, vct(1.0, 0.5, 0.25, 0.0))
  print_and_check(gen, "delay", "delay line[3, step]: [0.000 0.000 0.000]")
  v0 = make_vct!(10) do |i| delay(gen, i) end
  v1 = make_vct!(10) do |i| delay?(gen2) ? delay(gen2, i) : -1.0 end
  unless vequal(v1, v0)
    snd_display("map delay: %s %s?", v0, v1)
  end
  unless delay?(gen)
    snd_display("%s not a delay?", gen)
  end
  if gen.length != 3
    snd_display("delay length: %s?", gen.length)
  end
  if fneq(v0[1], 0.0) or fneq(v0[4], 1.0) or fneq(v0[8], 5.0)
    snd_display("delay output: %s?", v0)
  end
  if fneq(delay(gen1), 1.0) or fneq(delay(gen1), 0.5) or
      fneq(delay(gen1), 0.25) or fneq(delay(gen1), 0.0) or fneq(delay(gen1), 0.0)
    snd_display("delay with list initial-contents confused")
  end
  if fneq(delay(gen3), 1.0) or fneq(delay(gen3), 0.5) or
      fneq(delay(gen3), 0.25) or fneq(delay(gen3), 0.0) or fneq(delay(gen3), 0.0)
    snd_display("delay with vct initial-contents confused")
  end
  if (res = Snd.catch do make_delay(:size, false) end).first != :wrong_type_arg
    snd_display("make_delay bad size false: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_delay(3, :initial_element, make_oscil)
      end).first != :wrong_type_arg
    snd_display("make_delay bad initial element: %s", res.inspect)
  end
  if (res = Snd.catch do make_delay(-3) end).first != :out_of_range
    snd_display("make_delay bad size: %s", res.inspect)
  end
  d1 = make_delay(3)
  d2 = make_delay(3)
  d3 = make_delay(4)
  delay(d1, 1.0)
  delay(d2, 1.0)
  delay(d3, 1.0)
  test_gen_equal(d1, d2, d3)
  test_gen_equal(make_delay(3, :initial_element, 1.0),
                 make_delay(3, :initial_element, 1.0),
                 make_delay(3, :initial_element, 0.5))
  test_gen_equal(make_delay(3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_delay(3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_delay(3, :initial_contents, [1.0, 1.0, 1.0]))
  gen = make_delay(5)
  delay(gen, 1.0)
  delay(gen, 0.0)
  delay(gen, 0.5)
  data = vct_copy(gen.data)
  gen.data[0] = 0.3
  if fneq(gen.data[0], 0.3)
    snd_display("delay data 0: %s?", gen.data[0])
  end
  data[0] = 0.75
  gen.data = data
  if fneq(gen.data[0], 0.75)
    snd_display("delay set data 0: %s?", gen.data[0])
  end
  delay(gen, 0.0)
  delay(gen, 0.0)
  if fneq(res = delay(gen, 0.0), 0.75)
    snd_display("set delay data: %s %s?", res, gen.data)
  end
  if res = make_oscil.data
    snd_display("mus_data osc: %s?", res)
  end
  #
  del = make_delay(5, :max_size, 8)
  delay(del, 1.0)
  4.times do delay(del, 0.0) end
  v0 = make_vct!(5) do delay(del, 0.0, 0.4) end
  unless vequal(v0, vct(0.6, 0.4, 0.0, 0.0, 0.0))
    snd_display("zdelay: %s?", v0)
  end
  delay(del, 1.0)
  delay(del, 0.0, 0.4)
  if (res = del.to_s) != "delay line[5,8, linear]: [0.000 0.000 1.000 0.000 0.000]"
    snd_display("describe zdelay: %s", res)
  end
  if (res = Snd.catch do tap(make_oscil) end).first != :wrong_type_arg
    snd_display("tap of oscil: %s?", res.inspect)
  end
  #
  dly = make_delay(3)
  flt = make_one_zero(0.5, 0.4)
  v = make_vct(20)
  inval = 1.0
  vct_map!(v, lambda do | |
             res = delay(dly, inval + one_zero(flt, tap(dly)) * 0.6)
             inval = 0.0
             res
           end)
  unless vequal(v, vct(0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.3, 0.24, 0.0, 0.09,
                       0.144, 0.058, 0.027, 0.065, 0.052, 0.022, 0.026, 0.031, 0.019, 0.013))
    snd_display("tap with low pass: %s?", v)
  end
  #
  dly = make_delay(3)
  v = make_vct(20)
  inval = 1.0
  vct_map!(v, lambda do | |
             res = delay(dly, inval + tap(dly))
             inval = 0.0
             res
           end)
  unless vequal(v, vct(0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
                       0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0))
    snd_display("simple tap: %s?", v)
  end
  dly = make_delay(6)
  v = make_vct(20)
  inval = 1.0
  vct_map!(v, lambda do | |
             res = delay(dly, inval + tap(dly, -2.0))
             inval = 0.0
             res
           end)
  unless vequal(v, vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
                       1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0))
    snd_display("tap back 2: %s?", v)
  end
  #
  dly = make_delay(3, :initial_element, 32.0)
  unless vct?(dly.data)
    snd_display("delay data not vct?")
  else
    if dly.data.length != 3
      snd_display("delay data len not 3: %s (%s)?", dly.data.length, dly.data)
    else
      if fneq(res = dly.data[1], 32.0)
        snd_display("delay [1] 32: %s?", res)
      end
    end
  end
  if (res = Snd.catch do dly.length = -1 end).first != :out_of_range
    snd_display("len to -1 -> %s?", res.inspect)
  end
  if (res = Snd.catch do dly.length = 0 end).first != :out_of_range
    snd_display("len to 0 -> %s?", res.inspect)
  end
  if (res = Snd.catch do dly.length = 100 end).first != :out_of_range
    snd_display("len to 100 -> %s?", res.inspect)
  end
  if (res = Snd.catch do dly.data[100] = 0.1 end).first != :out_of_range
    snd_display("data 100 to 0.1 -> %s?", res.inspect)
  end
  data = make_vct(32, 1.0)
  dly.data = data
  unless vct?(dly.data)
    snd_display("set delay data not vct?")
  end
  if fneq(dly.data[1], 1.0)
    snd_display("set delay [1] 1: %s?", dly.data[1])
  end
  if dly.data.length != 32
    snd_display("set delay data len(32): %s?", dly.data.length)
  end
  if (res = Snd.catch do dly.length = 100 end).first != :out_of_range
    snd_display("set len to 100 -> %s", res.inspect)
  end
  if (res = Snd.catch do dly.data[100] = 0.1 end).first != :out_of_range
    snd_display("set data 100 to 0.1 -> %s", res.inspect)
  end
  #
  d1 = make_delay(4)
  d2 = make_delay(4, :max_size, 5, :type, Mus_interp_linear)
  d3 = make_delay(4, :max_size, 5, :type, Mus_interp_all_pass)
  d4 = make_delay(4, :max_size, 5, :type, Mus_interp_none)
  d5 = make_delay(4, :max_size, 4, :type, Mus_interp_lagrange)
  d6 = make_delay(4, :max_size, 4, :type, Mus_interp_hermite)
  d7 = make_delay(4, :max_size, 4, :type, Mus_interp_linear)
  v1 = make_vct(20)
  v2 = make_vct(20)
  v3 = make_vct(20)
  v4 = make_vct(20)
  v5 = make_vct(20)
  v6 = make_vct(20)
  v7 = make_vct(20)
  [[d1, Mus_interp_none],
   [d2, Mus_interp_linear],
   [d3, Mus_interp_all_pass],
   [d4, Mus_interp_none],
   [d5, Mus_interp_lagrange],
   [d6, Mus_interp_hermite],
   [d7, Mus_interp_linear]].each_with_index do |args, i|
    dly, type = args
    if dly.interp_type != type
      snd_display("d%s interp type: %s?", i + 1, dly.interp_type)
    end
  end
  [[v1, d1], [v2, d2], [v3, d3], [v4, d4], [v5, d5], [v6, d6], [v7, d7]].each do |v, d|
    v[0] = delay(d, 1.0)
    delay_tick(d, 0.0)
  end
  j = -0.2
  (1...20).each do |i|
    [[v1, d1], [v2, d2], [v3, d3], [v4, d4], [v5, d5], [v6, d6], [v7, d7]].each do |v, d|
      v[i] = tap(d, j)
    end
    j -= 0.2
  end
  if (not vequal(v1, vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                         1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0))) and
      (not vequal(v1, vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                          0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)))
    snd_display("delay interp none (1): %s?", v1)
  end
  unless vequal(v2, vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.4, 0.6, 0.8,
                        1.0, 0.8, 0.6, 0.4, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0))
    snd_display("delay interp linear (2): %s?", v2)
  end
  unless vequal(v3, vct(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.6, 0.16, 0.168, -0.168,
                        0.334, 0.199, 0.52, 0.696, -0.696, 0.557, -0.334, 0.134, -0.027))
    snd_display("delay interp all-pass (3): %s?", v3)
  end
  if (not vequal(v4, vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                         1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0))) and
      (not vequal(v4, vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                          0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)))
    snd_display("delay interp none (4): %s?", v4)
  end
  unless vequal(v5, vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.12, 0.28, 0.48, 0.72,
                        1.0, 0.96, 0.84, 0.64, 0.36, 0.0, -0.08, -0.12, -0.12, -0.08))
    snd_display("delay interp lagrange (5): %s?", v5)
  end
  unless vequal(v6, vct(0.0, -0.016, -0.048, -0.072, -0.064, 0.0, 0.168, 0.424, 0.696, 0.912,
                        1.0, 0.912, 0.696, 0.424, 0.168, 0.0, -0.064, -0.072, -0.048, -0.016))
    snd_display("delay interp hermite (6): %s?", v6)
  end
  unless vequal(v7, vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.4, 0.6, 0.8,
                        1.0, 0.8, 0.6, 0.4, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0))
    snd_display("delay interp linear (7): %s?", v7)
  end
  #
  dly = make_delay(:size, 2, :max_size, 3)
  impulse = 1.0
  data = make_vct!(5) do
    val = delay(dly, impulse, 0.4)
    impulse = 0.0
    val
  end
  unless vequal(data, vct(0.0, 0.0, 0.6, 0.4, 0.0))
    snd_display("delay size 2, max 3, off 0.4: %s", data)
  end
  dly = make_delay(:size, 2, :max_size, 3)
  impulse = 1.0
  data = make_vct!(5) do
    val = delay(dly, impulse, -0.4)
    impulse = 0.0
    val
  end
  unless vequal(data, vct(0.0, 0.4, 0.6, 0.0, 0.0))
    snd_display("delay size 2, max 3, off -0.4: %s", data)
  end
  # 
  dly = make_delay(:size, 1, :max_size, 2)
  impulse = 1.0
  data = make_vct!(5) do
    val = delay(dly, impulse, 0.4)
    impulse = 0.0
    val
  end
  unless vequal(data, vct(0.0, 0.6, 0.4, 0.0, 0.0))
    snd_display("delay size 1, max 2, off 0.4: %s", data)
  end
  # 
  dly = make_delay(:size, 0, :max_size, 1)
  impulse = 1.0
  data = make_vct!(5) do
    val = delay(dly, impulse, 0.4)
    impulse = 0.0
    val
  end
  unless vequal(data, vct(0.6, 0.0, 0.0, 0.0, 0.0))
    snd_display("delay size 0, max 1, off 0.4: %s", data)
  end
  dly = make_delay(:size, 0, :max_size, 1)
  if fneq(res = delay(dly, 0.0), 0.0)
    snd_display("initial delay 0 size val: %s?", res)
  end
  dly = make_delay(:size, 0, :max_size, 1)
  impulse = 1.0
  data = make_vct!(5) do
    val = delay(dly, impulse, -0.4)
    impulse = 0.0
    val
  end
  unless vequal(data, vct(1.4, 0.0, 0.0, 0.0, 0.0))
    snd_display("delay size 0, max 1, off -0.4: %s", data)
  end
  dly = make_delay(:size, 0, :max_size, 100)
  v = make_vct!(10) do |i| delay(dly, 0.5, i) end
  unless vequal(v, vct(0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0))
    snd_display("delay 0 -> 100: %s", v)
  end
  9.downto(0) do |i| v[i] = delay(dly, 0.5, i) end
  unless vequal(v, vct(0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.0))
    snd_display("delay 100 -> 0: %s", v)
  end
  dly.reset
  unless vequal(dly.data, Vct.new(100, 0.0))
    snd_display("after reset mus-data delay peak: %s?", dly.data.peak)
  end
  10.times do |i| v[i] = delay(dly, (i.odd? ? 1.0 : 0.0), i * 0.1) end
  unless vequal(v, vct(0.0, 0.9, 0.0, 0.7, 0.0, 0.5, 0.0, 0.3, 0.0, 0.1))
    snd_display("delay 0 -> 100 0.1: %s", v)
  end
  dly.reset
  10.times do |i| v[i] = delay(dly, (i.odd? ? 1.0 : 0.0), 1.0 + i * 0.1) end
  unless vequal(v, vct(0.0, 0.0, 0.8, 0.3, 0.6, 0.5, 0.4, 0.7, 0.2, 0.9))
    snd_display("delay 0 -> 100 1.1: %s", v)
  end
end

def test_08_02
  gen = make_all_pass(0.4, 0.6, 3)
  gen1 = make_all_pass(0.4, 0.6, 3)
  print_and_check(gen,
                  "all-pass",
                  "all-pass feedback: 0.400, feedforward: 0.600, line[3, step]:[0.000 0.000 0.000]")
  v0 = make_vct!(10) do all_pass(gen, 1.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | all_pass?(gen1) ? all_pass(gen1, 1.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map all-pass: %s %s?", v0, v1)
  end
  unless all_pass?(gen)
    snd_display("%s not all-pass?", gen)
  end
  if gen.length != 3
    snd_display("all-pass length: %s?", gen.length)
  end
  if gen.order != 3
    snd_display("all-pass order: %s?", gen.order)
  end
  if fneq(gen.feedback, 0.4)
    snd_display("all-pass feedback: %s?", gen.feedback)
  end
  if fneq(gen.feedforward, 0.6)
    snd_display("all-pass feedforward: %s?", gen.feedforward)
  end
  if fneq(v0[1], 0.6) or fneq(v0[4], 1.84) or fneq(v0[8], 2.336)
    snd_display("all-pass output: %s?", v0)
  end
  gen.feedback = 0.5
  if fneq(gen.feedback, 0.5)
    snd_display("all-pass set_feedback: %s?", gen.feedback)
  end
  gen.feedforward = 0.5
  if fneq(gen.feedforward, 0.5)
    snd_display("all-pass set_feedforward: %s?", gen.feedforward)
  end
  d1 = make_all_pass(0.7, 0.5, 3)
  d2 = make_all_pass(0.7, 0.5, 3)
  d3 = make_all_pass(0.7, 0.5, 4)
  all_pass(d1, 1.0)
  all_pass(d2, 1.0)
  all_pass(d3, 1.0)
  test_gen_equal(d1, d2, d3)
  test_gen_equal(make_all_pass(0.7, 0.5, 3, :initial_element, 1.0),
                 make_all_pass(0.7, 0.5, 3, :initial_element, 1.0),
                 make_all_pass(0.7, 0.5, 3, :initial_element, 0.5))
  test_gen_equal(make_all_pass(0.7, 0.5, 3, :initial_element, 1.0),
                 make_all_pass(0.7, 0.5, 3, :initial_element, 1.0),
                 make_all_pass(0.5, 0.5, 3, :initial_element, 1.0))
  test_gen_equal(make_all_pass(0.7, 0.5, 3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_all_pass(0.7, 0.5, 3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_all_pass(0.7, 0.5, 3, :initial_contents, [1.0, 1.0, 1.0]))
  err = Snd.catch do make_all_pass(:feedback, 0.2, :feedforward, 0.1, :size, -1) end
  if err.first != :out_of_range or
      err[1] != "make_all_pass" or
      err[2] != "size _1 < 0?" or
    snd_display("make_all_pass bad size error message: %s", err.inspect)
  end
  #
  gen = make_moving_average(4)
  gen1 = make_moving_average(4)
  print_and_check(gen, "moving-average", "moving-average 0.000, line[4]:[0.000 0.000 0.000 0.000]")
  v0 = make_vct!(10) do moving_average(gen, 1.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | moving_average?(gen1) ? moving_average(gen1, 1.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map moving_average: %s %s?", v0, v1)
  end
  unless moving_average?(gen)
    snd_display("%s not moving_average?", gen)
  end
  if gen.length != 4
    snd_display("moving-average length: %s?", gen.length)
  end
  if gen.order != 4
    snd_display("moving-average order: %s?", gen.order)
  end
  if fneq(v0[1], 0.5) or fneq(v0[4], 1.0) or fneq(v0[8], 1.0)
    snd_display("moving-average output: %s?", v0)
  end
  gen = make_moving_average(8)
  if fneq(val = moving_average(gen), 0.0)
    snd_display("empty moving_average: %s?", val)
  end
  if fneq(val = moving_average(gen, 1.0), 0.125)
    snd_display("moving-average 1: %s?", val)
  end
  if fneq(val = moving_average(gen, 1.0), 0.25)
    snd_display("moving-average 2: %s?", val)
  end
  if fneq(val = moving_average(gen, 0.5), 0.3125)
    snd_display("moving-average 2: %s?", val)
  end
  4.times do moving_average(gen, 0.0) end
  if fneq(val = moving_average(gen, 0.0), 0.3125)
    snd_display("moving-average 6: %s?", val)
  end
  if fneq(val = moving_average(gen, 0.0), 0.1875)
    snd_display("moving-average 7: %s?", val)
  end
  if fneq(val = moving_average(gen, 0.0), 0.0625)
    snd_display("moving-average 8: %s?", val)
  end
  if fneq(val = moving_average(gen, 0.0), 0.0)
    snd_display("moving-average 9: %s?", val)
  end
  gen = make_moving_average(10, :initial_element, 0.5)
  if fneq(val = moving_average(gen, 0.5), 0.5)
    snd_display("moving-average initial_element: %s?", val)
  end
  gen = make_moving_average(3, :initial_contents, [1.0, 1.0, 1.0])
  if fneq(val = moving_average(gen, 1.0), 1.0)
    snd_display("moving-average initial_contents: %s?", val)
  end
  d1 = make_moving_average(3, :initial_contents, [0.7, 0.5, 3])
  d2 = make_moving_average(3, :initial_contents, vct(0.7, 0.5, 3))
  d3 = make_moving_average(4, :initial_contents, [0.7, 0.5, 0.1, 4])
  moving_average(d1, 1.0)
  moving_average(d2, 1.0)
  moving_average(d3, 1.0)
  test_gen_equal(d1, d2, d3)
  test_gen_equal(make_moving_average(3, :initial_element, 1.0),
                 make_moving_average(3, :initial_element, 1.0),
                 make_moving_average(3, :initial_element, 0.5))
  test_gen_equal(make_moving_average(3, :initial_element, 1.0),
                 make_moving_average(3, :initial_element, 1.0),
                 make_moving_average(4, :initial_element, 1.0))
  test_gen_equal(make_moving_average(3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_moving_average(3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_moving_average(3, :initial_contents, [1.0, 1.0, 1.0]))
  err = Snd.catch do make_moving_average(:size, -2) end
  if err.first != :out_of_range or
      err[1] != "make_moving_average" or
      err[2] != "size _2 < 0?" or
    snd_display("make_moving_average bad size error message: %s", err.inspect)
  end
  #
  gen = make_comb(0.4, 3)
  gen1 = make_comb(0.4, 3)
  print_and_check(gen, "comb", "comb scaler: 0.400, line[3, step]: [0.000 0.000 0.000]")
  v0 = make_vct!(10) do comb(gen, 1.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | comb?(gen1) ? comb(gen1, 1.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map comb: %s %s?", v0, v1)
  end
  unless comb?(gen)
    snd_display("%s not comb?", gen)
  end
  if gen.length != 3
    snd_display("comb length: %s?", gen.length)
  end
  if gen.order != 3
    snd_display("comb order: %s?", gen.order)
  end
  if fneq(gen.feedback, 0.4)
    snd_display("comb feedback: %s?", gen.feedback)
  end
  if fneq(v0[1], 0.0) or fneq(v0[4], 1.0) or fneq(v0[8], 1.4)
    snd_display("comb output: %s?", v0)
  end
  d1 = make_comb(0.7, 3)
  d2 = make_comb(0.7, 3)
  d3 = make_comb(0.7, 4)
  comb(d1, 1.0)
  comb(d2, 1.0)
  comb(d3, 1.0)
  test_gen_equal(d1, d2, d3)
  test_gen_equal(make_comb(0.7, 3, :initial_element, 1.0),
                 make_comb(0.7, 3, :initial_element, 1.0),
                 make_comb(0.7, 3, :initial_element, 0.5))
  test_gen_equal(make_comb(0.7, 3, :initial_element, 1.0),
                 make_comb(0.7, 3, :initial_element, 1.0),
                 make_comb(0.5, 3, :initial_element, 1.0))
  test_gen_equal(make_comb(0.7, 3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_comb(0.7, 3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_comb(0.7, 3, :initial_contents, [1.0, 1.0, 1.0]))
  del = make_comb(0.0, 5, :max_size, 8)
  comb(del, 1.0)
  4.times do comb(del, 0.0) end
  v0 = make_vct!(5) do comb(del, 0.0, 0.4) end
  unless vequal(v0, vct(0.600, 0.400, 0.000, 0.000, 0.000))
    snd_display("zcomb: %s", v0)
  end
  comb(del, 1.0)
  comb(del, 0.0, 0.4)
  if (res = del.to_s) != "comb scaler: 0.000, line[5,8, linear]: [0.000 0.000 1.000 0.000 0.000]"
    snd_display("describe zcomb: %s", res)
  end
  del.feedback = 1.0
  if fneq(del.feedback, 1.0)
    snd_display("comb feedback set: %s?", del.feedback)
  end
  #
  gen = make_filtered_comb(0.4, 5, :filter, make_one_zero(0.3, 0.7))
  print_and_check(gen,
                  "filtered-comb",
                  "filtered-comb scaler: 0.400, line[5, step]: [0.000 0.000 0.000 0.000 0.000], filter: [one-zero a0: 0.300, a1: 0.700, x1: 0.000]")
  v0 = make_vct!(20) do |i| filtered_comb(gen, (i.zero? ? 1.0 : 0.0)) end
  unless vequal(v0,
                vct(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.12, 0.28, 0, 0, 0, 0.014, 0.067, 0.078, 0, 0))
    snd_display("filtered_comb: %s?", v0)
  end
  unless filtered_comb?(gen)
    snd_display("%s not filtered_comb?", gen)
  end
  if gen.length != 5
    snd_display("filtered_comb length: %s?", gen.length)
  end
  if gen.order != 5
    snd_display("filtered_comb order: %s?", gen.order)
  end
  if fneq(gen.feedback, 0.4)
    snd_display("filtered_comb feedback: %s?", gen.feedback)
  end
  gen = make_filtered_comb(0.9, 5, :filter, make_one_zero(0.5, 0.5))
  print_and_check(gen,
                  "filtered-comb",
                  "filtered-comb scaler: 0.900, line[5, step]: [0.000 0.000 0.000 0.000 0.000], filter: [one-zero a0: 0.500, a1: 0.500, x1: 0.000]")
  v0 = make_vct!(20) do |i| filtered_comb(gen, (i.zero? ? 1.0 : 0.0)) end
  unless vequal(v0,
                vct(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.45, 0.45, 0, 0, 0, 0.202, 0.405, 0.202, 0, 0))
    snd_display("filtered_comb: 0.5 0.5 %s?", v0)
  end
  gen = make_filtered_comb(0.9, 5, :filter, make_fir_filter(5, vct(0.1, 0.2, 0.3, 0.2, 0.1)))
  print_and_check(gen,
                  "filtered-comb",
                  "filtered-comb scaler: 0.900, line[5, step]: [0.000 0.000 0.000 0.000 0.000], filter: [fir-filter order: 5, xs: [0.100 0.200 0.300 0.200 0.100]]")
  v0 = make_vct!(20) do |i| filtered_comb(gen, (i.zero? ? 1.0 : 0.0)) end
  unless vequal(v0,
                vct(0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
                    0.09, 0.18, 0.27, 0.18, 0.09, 0.008, 0.032, 0.081, 0.13, 0.154))
    snd_display("filtered_comb (fir): %s?", v0)
  end
  d1 = make_filtered_comb(0.7, 3, :filter, make_one_pole(0.3, 0.7))
  d2 = make_filtered_comb(0.7, 3, :filter, make_one_pole(0.3, 0.7))
  d3 = make_filtered_comb(0.7, 4, :filter, make_one_pole(0.3, 0.7))
  filtered_comb(d1, 1.0)
  filtered_comb(d2, 1.0)
  filtered_comb(d3, 1.0)
  test_gen_equal(d1, d2, d3)
  test_gen_equal(make_filtered_comb(0.7, 3, :initial_element, 1.0,:filter, make_one_zero(0.5, 0.5)),
                 make_filtered_comb(0.7, 3, :initial_element, 1.0,:filter, make_one_zero(0.5, 0.5)),
                 make_filtered_comb(0.7, 3, :initial_element, 0.5,:filter, make_one_zero(0.5, 0.5)))
  test_gen_equal(make_filtered_comb(0.7, 3, :initial_element, 1.0,:filter, make_one_zero(0.5, 0.5)),
                 make_filtered_comb(0.7, 3, :initial_element, 1.0,:filter, make_one_zero(0.5, 0.5)),
                 make_filtered_comb(0.7, 3, :initial_element, 1.0,:filter, make_one_zero(0.25, 0.25)))
  test_gen_equal(make_filtered_comb(0.7, 3, :initial_contents, [1.0, 0.0, 0.0],
                                    :filter, make_one_zero(0.5, 0.5)),
                 make_filtered_comb(0.7, 3, :initial_contents, [1.0, 0.0, 0.0],
                                    :filter, make_one_zero(0.5, 0.5)),
                 make_filtered_comb(0.7, 3, :initial_contents, [1.0, 1.0, 1.0],
                                    :filter, make_one_zero(0.5, 0.5)))
  del = make_filtered_comb(0.0, 5, :max_size, 8, :filter, make_one_zero(0.5, 0.5))
  filtered_comb(del, 1.0)
  4.times do filtered_comb(del, 0.0) end
  v0 = make_vct!(5) do filtered_comb(del, 0.0, 0.4) end
  unless vequal(v0, vct(0.6, 0.4, 0, 0, 0))
    snd_display("zfiltered_comb: %s?", v0)
  end
  filtered_comb(del, 1.0)
  filtered_comb(del, 0.0, 0.4)
  if (res = mus_describe(del)) != "filtered-comb scaler: 0.000, line[5,8, linear]: [0.000 0.000 1.000 0.000 0.000], filter: [one-zero a0: 0.500, a1: 0.500, x1: 0.000]"
    snd_display("describe zfiltered_comb: %s?", res)
  end
  del.feedback = 1.0
  if fneq(del.feedback, 1.0)
    snd_display("filtered_echo feedback set: %s?", del.feedback)
  end
  #
  gen = make_notch(0.4, 3)
  gen1 = make_notch(0.4, 3)
  print_and_check(gen, "notch", "notch scaler: 0.400, line[3, step]: [0.000 0.000 0.000]")
  v0 = make_vct!(10) do notch(gen, 1.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | notch?(gen1) ? notch(gen1, 1.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map notch: %s %s?", v0, v1)
  end
  unless notch?(gen)
    snd_display("%s not notch?", gen)
  end
  if gen.length != 3
    snd_display("notch length: %s?", gen.length)
  end
  if gen.order != 3
    snd_display("notch order: %s?", gen.order)
  end
  if fneq(gen.feedforward, 0.4)
    snd_display("notch feedforward: %s?", gen.feedforward)
  end
  if fneq(v0[1], 0.4) or fneq(v0[4], 1.4) or fneq(v0[8], 1.4)
    snd_display("notch output: %s?", v0)
  end
  d1 = make_notch(0.7, 3)
  d2 = make_notch(0.7, 3)
  d3 = make_notch(0.7, 4)
  notch(d1, 1.0)
  notch(d2, 1.0)
  notch(d3, 1.0)
  test_gen_equal(d1, d2, d3)
  test_gen_equal(make_notch(0.7, 3, :initial_element, 1.0),
                 make_notch(0.7, 3, :initial_element, 1.0),
                 make_notch(0.7, 3, :initial_element, 0.5))
  test_gen_equal(make_notch(0.7, 3, :initial_element, 1.0),
                 make_notch(0.7, 3, :initial_element, 1.0),
                 make_notch(0.5, 3, :initial_element, 1.0))
  test_gen_equal(make_notch(0.7, 3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_notch(0.7, 3, :initial_contents, [1.0, 0.0, 0.0]),
                 make_notch(0.7, 3, :initial_contents, [1.0, 1.0, 1.0]))
  # make sure all-pass is the same as comb/notch given the appropriate
  # feedback/forward settings
  [[make_comb(0.5, 5), vct(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5)],
    [make_all_pass(0.5, 0.0, 5), vct(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5)],
    [make_notch(0.5, 5), vct(0.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)],
    [make_all_pass(0.0, 0.5, 5), vct(0.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)]
  ].each do |gen, v1|
    v0 = make_vct!(11) do |i| gen.run(i.zero? ? 1.0 : 0.0) end
    unless vequal(v0, v1)
      snd_display("0 %s (0.5, 0.0, 5): %s", gen.name, v0)
    end
  end
  # make sure all-pass is the same as zcomb/znotch given the
  # appropriate feedback/forward and "pm" settings
  [[make_comb(0.5, 5, :max_size, 20),
      vct(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5)],
    [make_all_pass(0.5, 0.0, 5, :max_size, 20),
      vct(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5)],
    [make_notch(0.5, 5, :max_size, 20),
      vct(0.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)],
    [make_all_pass(0.0, 0.5, 5, :max_size, 20),
      vct(0.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)]].each do |gen, v1|
    v0 = make_vct!(11) do |i| gen.run(i.zero? ? 1.0 : 0.0) end
    unless vequal(v0, v1)
      snd_display("1 %s (0.5, 0.0, 5): %s", gen.name, v0)
    end
  end
  # now actually use the size difference
  [[make_comb(0.5, 5, :max_size, 20),
      vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8, 0.4, 0.0, 0.0,
          0.0, 0.0, 0.0, 0.16, 0.36, 0.2, 0.04, 0.0, 0.0, 0.0)],
    [make_all_pass(0.5, 0.0, 5, :max_size, 20),
      vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8, 0.4, 0.0, 0.0,
          0.0, 0.0, 0.0, 0.16, 0.36, 0.2, 0.04, 0.0, 0.0, 0.0)],
    [make_notch(0.5, 5, :max_size, 20),
      vct(0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8, 0.4, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)],
    [make_all_pass(0.0, 0.5, 5, :max_size, 20),
      vct(0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8, 0.4, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)]].each do |gen, v1|
    angle = -0.2
    v0 = make_vct!(20) do |i| gen.run((i.zero? ? 1.0 : 0.0), angle += 0.2) end
    unless vequal(v0, v1)
      snd_display("2 %s (0.5, 0.0, 5): %s", gen.name, v0)
    end
  end
  [[make_comb(0.5, 5, :max_size, 20),
      vct(0.0, 0.0, 0.0, 0.0, 0.8, 0.0, 0.0, 0.16, 0.16, 0.0,
          0.08, 0.064, 0.016, 0.035, 0.013, 0.018, 0.007, 0.007, 0.003, 0.002)],
    [make_all_pass(0.5, 0.0, 5, :max_size, 20),
      vct(0.0, 0.0, 0.0, 0.0, 0.8, 0.0, 0.0, 0.16, 0.16, 0.0,
          0.08, 0.064, 0.016, 0.035, 0.013, 0.018, 0.007, 0.007, 0.003, 0.002)],
    [make_notch(0.5, 5, :max_size, 20),
      vct(0.5, 0.0, 0.0, 0.0, 0.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)],
    [make_all_pass(0.0, 0.5, 5, :max_size, 20),
      vct(0.5, 0.0, 0.0, 0.0, 0.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)]].each do |gen, v1|
    angle = +0.2
    v0 = make_vct!(20) do |i| gen.run((i.zero? ? 1.0 : 0.0), angle -= 0.2) end
    unless vequal(v0, v1)
      snd_display("3 %s (0.5, 0.0, 5): %s", gen.name, v0)
    end
  end
  [[make_comb(0.5, 5, :max_size, 20),
      vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.95, 0.06, 0.0, 0.0, 0.0,
          0.428, 0.079, 0.004, 0.0, 0.0, 0.182, 0.067, 0.008, 0.0, 0.0)],
    [make_all_pass(0.5, 0.0, 5, :max_size, 20),
      vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.95, 0.06, 0.0, 0.0, 0.0,
          0.428, 0.079, 0.004, 0.0, 0.0, 0.182, 0.067, 0.008, 0.0, 0.0)],
    [make_notch(0.5, 5, :max_size, 20),
      vct(0.5, 0.0, 0.0, 0.0, 0.0, 0.95, 0.06, 0.0, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)],
    [make_all_pass(0.0, 0.5, 5, :max_size, 20),
      vct(0.5, 0.0, 0.0, 0.0, 0.0, 0.95, 0.06, 0.0, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)]].each do |gen, v1|
    angle = -0.01
    v0 = make_vct!(20) do |i| gen.run((i.zero? ? 1.0 : 0.0), angle += 0.01) end
    unless vequal(v0, v1)
      snd_display("4 %s (0.5, 0.0, 5): %s", gen.name, v0)
    end
  end
  # now run off either end of the delay line "by accident"
  [[make_comb(0.5, 5, :max_size, 10),
      vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5,
          1.0, 0.25, 0.125, 0.094, 0.062, 0.055, 0.047, 0.039, 0.031, 0.029)],
    [make_all_pass(0.5, 0.0, 5, :max_size, 10),
      vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5,
          1.0, 0.25, 0.125, 0.094, 0.062, 0.055, 0.047, 0.039, 0.031, 0.029)],
    [make_notch(0.5, 5, :max_size, 10),
      vct(0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)],
    [make_all_pass(0.0, 0.5, 5, :max_size, 10),
      vct(0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)]].each do |gen, v1|
    angle = -0.5
    v0 = make_vct!(20) do |i| gen.run((i.zero? ? 1.0 : 0.0), angle += 0.5) end
    unless vequal(v0, v1)
      snd_display("5 %s (0.5, 0.0, 5): %s", gen.name, v0)
    end
  end
  [[make_comb(0.5, 5, :max_size, 10),
      vct(0.0, 0.0, 0.0, 0.5, 0.0, 0.125, 0.0, 0.031, 0.016, 0.004,
          1.0, 0.0, 0.25, 0.031, 0.0, 0.012, 0.002, 0.250, 0.125, 0.008)],
    [make_all_pass(0.5, 0.0, 5, :max_size, 10),
      vct(0.0, 0.0, 0.0, 0.5, 0.0, 0.125, 0.0, 0.031, 0.016, 0.004,
          1.0, 0.0, 0.25, 0.031, 0.0, 0.012, 0.002, 0.250, 0.125, 0.008)],
    [make_notch(0.5, 5, :max_size, 10),
      vct(0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)],
    [make_all_pass(0.0, 0.5, 5, :max_size, 10),
      vct(0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)]].each do |gen, v1|
    angle = +0.5
    v0 = make_vct!(20) do |i| gen.run((i.zero? ? 1.0 : 0.0), angle -= 0.5) end
    unless vequal(v0, v1)
      snd_display("6 %s (0.0, 0.5, 5): %s", gen.name, v0)
    end
  end
  #
  gen = make_filtered_comb(0.5, 5, :filter, make_one_zero(0.5, 0.5))
  v0 = Vct.new(21) do |i| filtered_comb(gen, i.zero? ? 1.0 : 0.0) end
  unless vequal(v0, 
                vct(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 
                    0.25, 0.25, 0, 0, 0, 0.062, 0.125, 0.062, 0, 0, 0.016))
    snd_display("0 filtered_comb (0.5, 5): %s?", v0)
  end
  gen = make_filtered_comb(0.5, 5, :max_size, 20, :filter, make_one_zero(0.25, 0.75))
  v0 = Vct.new(21) do |i| filtered_comb(gen, i.zero? ? 1.0 : 0.0) end
  unless vequal(v0, 
                vct(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 
                    0.125, 0.375, 0, 0, 0, 0.016, 0.094, 0.141, 0, 0, 0.002))
    snd_display("1 filtered_comb (0.5, 5): %s?", v0)
  end
  gen = make_filtered_comb(0.5, 5, :max_size, 20, :filter, make_one_zero(0.5, 0.5))
  angle = -0.2
  v0 = Vct.new(20) do |i| filtered_comb(gen, i.zero? ? 1.0 : 0.0, angle += 0.2) end
  unless vequal(v0, 
                vct(0, 0, 0, 0, 0, 0, 0.8, 0.4, 0, 0,
                    0, 0, 0, 0.08, 0.22, 0.3, 0.140, 0.04, 0, 0))
    snd_display("2 filtered_comb (0.5, 5): %s?", v0)
  end
  gen = make_filtered_comb(0.5, 5, :max_size, 20, :filter, make_one_zero(0.5, 0.5))
  angle = 0.2
  v0 = Vct.new(20) do |i| filtered_comb(gen, i.zero? ? 1.0 : 0.0, angle -= 0.2) end
  unless vequal(v0, 
                vct(0, 0, 0, 0, 0.8, 0, 0, 0.08, 0.2, 0.04, 
                    0.02, 0.068, 0.042, 0.019, 0.026, 0.015, 0.011, 0.009, 0.006, 0.004))
    snd_display("3 filtered_comb (0.5, 5): %s?", v0)
  end
  gen = make_filtered_comb(0.5, 5, :max_size, 20, :filter, make_one_zero(0.5, 0.5))
  angle = -0.01
  v0 = Vct.new(20) do |i| filtered_comb(gen, i.zero? ? 1.0 : 0.0, angle += 0.01) end
  unless vequal(v0, 
                vct(0, 0, 0, 0, 0, 0.95, 0.06, 0, 0, 0,
                    0.214, 0.251, 0.043, 0.002, 0, 0.045, 0.106, 0.081, 0.023, 0.003))
    snd_display("4 filtered_comb (0.5, 5): %s?", v0)
  end
end

def test_08_03
  gen = make_one_pole(0.4, 0.7)
  gen1 = make_one_pole(0.4, 0.7)
  print_and_check(gen, "one-pole", "one-pole a0: 0.400, b1: 0.700, y1: 0.000")
  v0 = make_vct!(10) do one_pole(gen, 1.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | one_pole?(gen1) ? one_pole(gen1, 1.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map one_pole: %s %s?", v0, v1)
  end
  unless one_pole?(gen)
    snd_display("%s not one_pole?", gen)
  end
  if gen.order != 1
    snd_display("one_pole order: %s?", gen.order)
  end
  if fneq(gen.a0, 0.4)
    snd_display("one_pole a0: %s?", gen.a0)
  end
  if fneq(gen.b1, 0.7)
    snd_display("one_pole b1: %s?", gen.b1)
  end
  if fneq(v0[1], 0.12) or fneq(v0[4], 0.275) or fneq(v0[8], 0.245)
    snd_display("one_pole output: %s?", v0)
  end
  if fneq(gen.ycoeff(1), 0.7)
    snd_display("1p ycoeff 1 0.7: %s?", gen)
  end
  gen.ycoeff = 1, 0.1
  if fneq(gen.ycoeff(1), 0.1)
    snd_display("1p set_ycoeff 1 0.1: %s?", gen)
  end
  if fneq(gen.xcoeff(0), 0.4)
    snd_display("1p xcoeff 0 0.4: %s?", gen)
  end
  gen.xcoeff = 0, 0.3
  if fneq(gen.xcoeff(0), 0.3)
    snd_display("1p set_xcoeff 0 0.3: %s?", gen)
  end
  # 
  gen = make_one_zero(0.4, 0.7)
  gen1 = make_one_zero(0.4, 0.7)
  print_and_check(gen, "one-zero", "one-zero a0: 0.400, a1: 0.700, x1: 0.000")
  v0 = make_vct!(10) do one_zero(gen, 1.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | one_zero?(gen1) ? one_zero(gen1, 1.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map one_zero: %s %s?", v0, v1)
  end
  unless one_zero?(gen)
    snd_display("%s not one_zero?", gen)
  end
  if gen.order != 1
    snd_display("one_zero order: %s?", gen.order)
  end
  if fneq(gen.a0, 0.4)
    snd_display("one_zero a0: %s?", gen.a0)
  end
  if fneq(gen.a1, 0.7)
    snd_display("one_zero a1: %s?", gen.a1)
  end
  if fneq(v0[1], 1.1)
    snd_display("one_zero output: %s?", v0)
  end
  if fneq(gen.xcoeff(0), 0.4)
    snd_display("1z xcoeff 0 0.4: %s?", gen)
  end
  gen.xcoeff = 0, 0.1
  if fneq(gen.xcoeff(0), 0.1)
    snd_display("1z set_xcoeff 0 0.1: %s?", gen)
  end
  # 
  gen = make_two_zero(0.4, 0.7, 0.3)
  gen1 = make_two_zero(0.4, 0.7, 0.3)
  print_and_check(gen,
                  "two-zero",
                  "two-zero a0: 0.400, a1: 0.700, a2: 0.300, x1: 0.000, x2: 0.000")
  v0 = make_vct!(10) do two_zero(gen, 1.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | two_zero?(gen1) ? two_zero(gen1, 1.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map two_zero: %s %s?", v0, v1)
  end
  unless two_zero?(gen)
    snd_display("%s not two_zero?", gen)
  end
  if gen.order != 2
    snd_display("two_zero order: %s?", gen.order)
  end
  if fneq(gen.a0, 0.4)
    snd_display("two_zero a0: %s?", gen.a0)
  end
  if fneq(gen.a1, 0.7)
    snd_display("two_zero a1: %s?", gen.a1)
  end
  if fneq(gen.a2, 0.3)
    snd_display("two_zero a2: %s?", gen.a2)
  end
  if fneq(v0[1], 1.1) or fneq(v0[8], 1.4)
    snd_display("two_zero output: %s?", v0)
  end
  if fneq(gen.xcoeff(0), 0.4)
    snd_display("2z xcoeff 0 0.4: %s?", gen)
  end
  gen.xcoeff = 0, 0.1
  if fneq(gen.xcoeff(0), 0.1)
    snd_display("2z set_xcoeff 0 0.1: %s?", gen)
  end
  gen.xcoeff = 0, 1.0
  r = gen.scaler
  gen.frequency = 500.0
  if fneq(gen.frequency, 500.0)
    snd_display("set_mus_frequency two_zero: %s?", gen.frequency)
  end
  if fneq(gen.scaler, r)
    snd_display("set_mus_frequency two_zero hit r: %s?", gen.scaler)
  end
  gen.scaler = 0.99
  if fneq(gen.scaler, 0.99)
    snd_display("set_mus_scaler two_zero: %s?", gen.scaler)
  end
  if fneq(gen.frequency, 500.0)
    snd_display("set_mus_scaler hit freq two_zero: %s?", gen.frequency)
  end
  g3 = make_two_zero(:radius, 0.99, :frequency, 500.0)
  if fneq(gen.a0, g3.a0) or fneq(gen.a1, g3.a1) or fneq(gen.a2, g3.a2)
    snd_display("two_zero setters: %s %s", gen, g3)
  end
  gen = make_two_zero(0.4, 0.7, 0.3)
  if fneq(val = gen.call(1.0, 0.0), 0.4)
    snd_display("2zero->0.4: %s?", val)
  end
  if fneq(val = gen.call(0.5, 0.0), 0.9)
    snd_display("2zero->0.9: %s?", val)
  end
  if fneq(val = gen.call(1.0, 0.0), 1.05)
    snd_display("2zero->1.05: %s?", val)
  end
  # 
  gen = make_two_pole(0.4, 0.7, 0.3)
  gen1 = make_two_pole(0.4, 0.7, 0.3)
  print_and_check(gen,
                  "two-pole",
                  "two-pole a0: 0.400, b1: 0.700, b2: 0.300, y1: 0.000, y2: 0.000")
  v0 = make_vct!(10) do two_pole(gen, 1.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | two_pole?(gen1) ? two_pole(gen1, 1.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map two_pole: %s %s?", v0, v1)
  end
  unless two_pole?(gen)
    snd_display("%s not two_pole?", gen)
  end
  if gen.order != 2
    snd_display("two_pole order: %s?", gen.order)
  end
  if fneq(gen.a0, 0.4)
    snd_display("two_pole a0: %s?", gen.a0)
  end
  if fneq(gen.b1, 0.7)
    snd_display("two_pole b1: %s?", gen.b1)
  end
  if fneq(gen.b2, 0.3)
    snd_display("two_pole b2: %s?", gen.b2)
  end
  if fneq(v0[1], 0.12) or fneq(v0[8], 0.201)
    snd_display("two_pole output: %s?", v0)
  end
  if fneq(gen.ycoeff(1), 0.7)
    snd_display("2p ycoeff 1 0.7: %s?", gen)
  end
  gen.ycoeff = 1, 0.1
  if fneq(gen.ycoeff(1), 0.1)
    snd_display("2p set_ycoeff 1 0.1: %s?", gen)
  end
  if fneq(gen.xcoeff(0), 0.4)
    snd_display("2p xcoeff 0 0.4: %s?", gen)
  end
  gen.xcoeff = 0, 0.3
  if fneq(gen.xcoeff(0), 0.3)
    snd_display("2p set_xcoeff 0 0.3: %s?", gen)
  end
  gen.xcoeff = 0, 1.0
  r = gen.scaler
  gen.frequency = 500.0
  if fneq(gen.frequency, 500.0)
    snd_display("set_mus_frequency two_pole: %s?", gen.frequency)
  end
  if fneq(gen.scaler, r)
    snd_display("set_mus_frequency two_pole hit r: %s?", gen.scaler)
  end
  gen.scaler = 0.99
  if fneq(gen.scaler, 0.99)
    snd_display("set_mus_scaler two_pole: %s?", gen.scaler)
  end
  if fneq(gen.frequency, 500.0)
    snd_display("set_mus_scaler hit freq two_pole: %s?", gen.frequency)
  end
  g3 = make_two_pole(:radius, 0.99, :frequency, 500.0)
  if fneq(gen.a0, g3.a0) or fneq(gen.a1, g3.a1) or fneq(gen.a2, g3.a2)
    snd_display("two_pole setters: %s %s", gen, g3)
  end
  gen = make_two_pole(0.4, 0.7, 0.3)
  if fneq(val = gen.call(1.0, 0.0), 0.4)
    snd_display("a0->out 2pole: %s?", val)
  end
  if fneq(val = gen.call(0.5, 0.0), -0.08)
    snd_display("a0->out 2pole (-0.08): %s?", val)
  end
  if fneq(val = gen.call(1.0, 0.0), 0.336)
    snd_display("a0->out 2pole (0.336): %s?", val)
  end
  if (res = Snd.catch do make_two_pole(:b1, 3.0) end).first != :mus_error
    snd_display("make_two_pole bad b1: %s", res.inspect)
  end
  if (res = Snd.catch do make_two_pole(:b2, 2.0) end).first != :mus_error
    snd_display("make_two_pole bad b2: %s", res.inspect)
  end
  if (res = Snd.catch do make_two_pole(:b2, 2.0, :b1) end).first != :mus_error
    snd_display("make_two_pole bad keys: %s", res.inspect)
  end
  if (res = Snd.catch do make_two_pole(:b2, 2.0, 3.0) end).first != :mus_error
    snd_display("make_two_pole bad args: %s", res.inspect)
  end
  #
  gen = make_oscil(440.0)
  gen1 = make_oscil(440.0)
  gen2 = make_oscil(440.0)
  print_and_check(gen, "oscil", "oscil freq: 440.000Hz, phase: 0.000")
  v0 = make_vct!(10) do oscil(gen, 0.0) end
  v1 = make_vct!(10) do mus_apply(gen1, 0.0, 0.0) end
  v2 = make_vct(10)
  vct_map!(v2, lambda do | | oscil?(gen2) ? oscil(gen2, 0.0) : -1.0 end)
  unless vequal(v0, v2)
    snd_display("map oscil: %s %s?", v0, v2)
  end
  unless oscil?(gen)
    snd_display("%s not oscil?", gen)
  end
  if fneq(gen.phase, 1.253787)
    snd_display("oscil phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("oscil frequency: %s?", gen.frequency)
  end
  if fneq(gen.length, 1)
    snd_display("oscil cosines: %s?", gen.length)
  end
  if fneq(v0[1], 0.125) or fneq(v0[8], 0.843)
    snd_display("oscil output: %s?", v0)
  end
  gen.phase = 0.0
  if fneq(gen.phase, 0.0)
    snd_display("oscil set_phase: %s?", gen.phase)
  end
  gen.frequency = 100.0
  if fneq(gen.frequency, 100.0)
    snd_display("oscil set_frequency: %s?", gen.frequency)
  end
  #
  v0.each_with_index do |val, i|
    if fneq(val, v1[i])
      snd_display("mus_apply oscil at %s: %s %s?", i, val, v1[i])
    end
  end
  if fneq(mus_apply, 0.0)
    snd_display("mus_apply: %s?", mus_apply)
  end
  gen1 = make_oscil(100.0)
  gen2 = make_oscil(-100.0)
  mx = 0.0
  100.times do mx = [mx, (gen1.run + gen2.run).abs].max end
  if fneq(mx, 0.0)
    snd_display("1 oscil +-: %s?", mx)
  end
  gen1 = make_oscil(100.0, PI * 0.5)
  gen2 = make_oscil(-100.0, PI * 0.5)
  mx = 0.0
  100.times do mx = [mx, (gen1.run - gen2.run).abs].max end
  if fneq(mx, 0.0)
    snd_display("2 oscil +-: %s?", mx)
  end
  fm_test(make_oscil)
  fm_test(make_square_wave)
  fm_test(make_triangle_wave)
  fm_test(make_ncos)
  fm_test(make_nsin)
  fm_test(make_sawtooth_wave)
  fm_test(make_rand)
  fm_test(make_rand_interp)
  fm_test(make_pulse_train)
  #
  gen = make_oscil(440.0)
  gen1 = make_oscil(440.0)
  10.times do
    if fneq(oval = oscil(gen, 0.1), mval = mus_run(gen1, 0.1))
      snd_display("mus_run %s but oscil %s?", oval, mval)
    end
  end
  gen = make_oscil(440.0)
  gen1 = make_oscil(440.0)
  gen2 = make_oscil(440.0)
  gen3 = make_oscil(440.0)
  fm_index = hz2radians(440.0)
  v0 = make_vct(10)
  v1 = make_vct(10)
  10.times do |i|
    v0[i] = oscil(gen, fm_index * oscil(gen1, 0.0))
    v1[i] = mus_apply(gen2, fm_index * mus_apply(gen3, 0.0, 0.0), 0.0)
  end
  if fneq(v0[1], 0.125) or fneq(v0[6], 0.83) or fneq(v0[8], 0.987)
    snd_display("oscil fm output: %s?", v0)
  end
  v0.each_with_index do |val, i|
    if fneq(val, v1[i])
      snd_display("mus_apply fm oscil at %s: %s %s?", i, val, v1[i])
    end
  end
  test_gen_equal(make_oscil(440.0), make_oscil(440.0), make_oscil(100.0))
  test_gen_equal(make_oscil(440.0), make_oscil(440.0), make_oscil(440.0, 1.0))
  gen = make_oscil(440.0)
  gen1 = make_oscil(440.0)
  pm_index = 2.0
  v0 = make_vct!(10) do gen.call(0.0, pm_index * gen1.call(0.0, 0.0)) end
  if fneq(v0[1], 0.367) or fneq(v0[6], 0.854) or fneq(v0[8], 0.437)
    snd_display("oscil pm output: %s?", v0)
  end
  gen = make_oscil(440.0)
  1100.times do |i|
    if fneq(val1 = sin(gen.phase), val2 = gen.call(0.0, 0.0))
      snd_display("oscil (sin): %s: %s %s?", i, val1, val2)
    end
  end
  gen = make_oscil(440.0, :initial_phase, PI * 0.5)
  a = 0.0
  900.times do |i|
    if fneq(val1 = cos(a), val2 = gen.call(0.0, 0.0))
      snd_display("oscil (cos): %s: %s %s?", i, val1, val2)
    end
    a = a + (2 * PI * 440) / 22050
  end
  gen = make_oscil(0.0)
  gen1 = make_oscil(40.0)
  a = 0.0
  1100.times do |i|
    if fneq(val1 = sin(sin(a)), val2 = oscil(gen, 0.0, oscil(gen1, 0.0)))
      snd_display("oscil  pm: %s: %s %s?", i, val1, val2)
    end
    a = a + (2 * PI * 40) / 22050
  end
  gen = make_oscil(0.0)
  gen1 = make_oscil(40.0)
  a = 0.0
  a1 = 0.0
  1100.times do |i|
    fm = sin(a)
    if fneq(val1 = sin(a1), val2 = oscil(gen, oscil(gen1, 0.0)))
      snd_display("oscil  fm: %s: %s %s?", i, val1, val2)
    end
    a = a + (2 * PI * 40) / 22050
    a1 += fm
  end
  #
  if (res = Snd.catch do mus_location(make_oscil) end).first != :mus_error
    snd_display("mus_location bad gen: %s", res.inspect)
  end
  if (res = Snd.catch do set_mus_location(make_oscil, 0) end).first != :mus_error
    snd_display("set_mus_location bad gen: %s", res.inspect)
  end
  if (res = Snd.catch do set_mus_scaler(make_oscil, 0) end).first != :mus_error
    snd_display("set_mus_scaler bad gen: %s", res.inspect)
  end
  if (res = Snd.catch do mus_frequency(make_one_pole) end).first != :mus_error
    snd_display("mus_frequency bad gen: %s", res.inspect)
  end
  if (res = Snd.catch do set_mus_frequency(make_one_pole, 0) end).first != :mus_error
    snd_display("set_mus_frequency bad gen: %s", res.inspect)
  end
  if (res = Snd.catch do make_delay(1024 * 1024 * 40) end).first != :out_of_range
    snd_display("make_delay huge line 1: %s", res.inspect)
  end
  if (res = Snd.catch do make_delay(32, :max_size, 1024 * 1024 * 40) end).first != :out_of_range
    snd_display("make_delay huge line 2: %s", res.inspect)
  end
end

def test_08_04
  gen = make_asymmetric_fm(440.0)
  gen1 = make_asymmetric_fm(440.0)
  print_and_check(gen,
                  "asymmetric-fm",
                  "asymmetric-fm freq: 440.000Hz, phase: 0.000, ratio: 1.000, r: 1.000")
  v0 = make_vct!(10) do asymmetric_fm(gen, 0.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | asymmetric_fm?(gen1) ? asymmetric_fm(gen1, 0.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map asymmetric_fm: %s %s?", v0, v1)
  end
  unless asymmetric_fm?(gen)
    snd_display("%s not asymmetric_fm?", gen)
  end
  if fneq(gen.phase, 1.253787)
    snd_display("asymmetric_fm phase: %s?", gen.phase)
  end
  gen.phase = 1.0
  if fneq(gen.phase, 1.0)
    snd_display("asymmetric_fm set_phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("asymmetric_fm frequency: %s?", gen.frequency)
  end
  gen.frequency = 100.0
  if fneq(gen.frequency, 100.0)
    snd_display("asymmetric_fm set_frequency: %s?", gen.frequency)
  end
  if fneq(v0[2], 0.969) or fneq(v0[8], 0.538)
    snd_display("asymmetric_fm output: %s?", v0)
  end
  if fneq(gen.scaler, 1.0)
    snd_display("asymmetric_fm set_scaler: %s?", gen.scaler)
  end
  gen.scaler = 0.5
  if fneq(gen.scaler, 0.5)
    snd_display("asymmetric_fm set_scaler: %s?", gen.scaler)
  end
  if fneq(gen.offset, 1.0)
    snd_display("asymmetric_fm offset: %s?", gen.offset)
  end
  test_gen_equal(make_asymmetric_fm(440),
                 make_asymmetric_fm(440),
                 make_asymmetric_fm(100))
  test_gen_equal(make_asymmetric_fm(440),
                 make_asymmetric_fm(440),
                 make_asymmetric_fm(440, 1))
  test_gen_equal(make_asymmetric_fm(440),
                 make_asymmetric_fm(440),
                 make_asymmetric_fm(440, 0.0, 3))
  gen1 = make_asymmetric_fm(1000, 0, 1, 0.1)
  gen2 = make_oscil(1000, :initial_phase, HALF_PI)
  100.times do |i|
    ss = asymmetric_fm(gen1, 0.0, 0.0)
    os = oscil(gen2, 0.0)
    if fneq(ss, os)
      snd_display("asymmetric_fm 1: %s: os: %s ss: %s?", i, os, ss)
      break
    end
  end
  gen3 = make_asymmetric_fm(1000, 0, 1.0, 0.2)
  gen4 = make_oscil(1000, :initial_phase, HALF_PI)
  gen5 = make_oscil(200)
  fm1 = hz2radians(0.2 * 1000)
  vct0 = make_vct(2048)
  vct1 = make_vct!(2048) do |i|
    vct0[i] = asymmetric_fm(gen3, 1.0, 0.0)
    oscil(gen4, fm1 * oscil(gen5))
  end
  spectr1 = snd_spectrum(vct0, Rectangular_window, 2048, true)
  spectr2 = snd_spectrum(vct1, Rectangular_window, 2048, true)
  (1...512).each do |i|
    if fneq_err(spectr1[i], spectr2[i], 0.02) # okay
      snd_display("asymmetric_fm 2: %s: %s %s?", i, spectr1[i], spectr2[i])
      break
    end
  end
  gen = make_asymmetric_fm(40.0, 0.0, 1.0, 0.1)
  gen1 = make_asyfm(:frequency, 40.0, :ratio, 0.1, :index, 2.0)
  a = 0.0
  1100.times do |i|
    val1 = asymmetric_fm(gen, 2.0)
    val3 = asyfm_J(gen1, 0.0)
    r = 1.0
    ratio = 0.1
    index = 2.0
    cr = 0.5 * (r - (1.0 / r))
    sr = 0.5 * (r + (1.0 / r))
    th = a
    mth = ratio * th
    val2 = exp(index * cr * (1.0 + cos(mth))) * cos(th + (index * sr * sin(mth)))
    if fneq(val1, val2) or fneq(val1, val3)
      snd_display("asyfm by hand: %s: 1 %s 2 %s 3 %s?", i, val1, val2, val3)
    end
    a = a + ((TWO_PI * 40.0) / mus_srate())
  end
  gen3 = make_asymmetric_fm(1000, 0, 2.0, 0.1)
  gen4 = make_asymmetric_fm(1000, 0, 0.5, 0.1)
  vct0 = make_vct(2048)
  vct1 = make_vct!(2048) do |i|
    vct0[i] = asymmetric_fm(gen3, 2.0, 0.0)
    asymmetric_fm(gen4, 2.0, 0.0)
  end
  spectr1 = snd_spectrum(vct0, Rectangular_window, 2048, true)
  spectr2 = snd_spectrum(vct1, Rectangular_window, 2048, true)
  s1_loc = 0
  s2_loc = 0
  (1...256).each do |i|
    if (1.0 - spectr1[i]).abs < 0.01
      s1_loc = i
    end
    if (1.0 - spectr2[i]).abs < 0.01
      s2_loc = i
    end
  end
  if s2_loc > s1_loc
    snd_display("asymmetric_fm peaks: %s %s?", s1_loc, s2_loc)
  end
  center = ((22050 / 2048.0) * 0.5 * (s1_loc + s2_loc)).round
  if (1000 - center).abs > 60
    snd_display("asymmetric_fm center: %s?", center)
  end
  gen3.scaler = 0.5
  2048.times do |i| vct0[i] = asymmetric_fm(gen3, 2.0, 0.0) end
  spectr1 = snd_spectrum(vct0, Rectangular_window, 2048, true)
  (1...256).each do |i|
    s1_loc = i if (1.0 - spectr1[i]).abs < 0.01
  end
  if s2_loc != s1_loc
    snd_display("asymmetric_fm set r peaks: %s %s?", s1_loc, s2_loc)
  end
  2048.times do |i| vct0[i] = asymmetric_fm(gen3, 2.0, 0.0) end
  snd_spectrum(vct0, Rectangular_window, 2048, true, 0.0, true)
  (1...256).each do |i|
    if (1.0 - spectr1[i]).abs < 0.01
      s1_loc = i
    end
  end
  if s2_loc != s1_loc
    snd_display("asymmetric_fm set r in place peaks: %s %s?", s1_loc, s2_loc)
  end
  #
  gen = make_asyfm(:frequency, 2000, :ratio, 0.1)
  asyfm_I(gen, 0.0)
end

class F_filter
  def initialize(coeffs)
    @coeffs = coeffs
    @xs = Vct.new(coeffs.length)
  end

  def f_filter(x)
    xlen = @xs.length
    @xs.move!(xlen - 1, xlen - 2, true)
    @xs.first = x
    dot_product(@coeffs, @xs, xlen)
  end
end

def make_f_filter(coeffs)
  F_filter.new(coeffs)
end

def f_filter(flt, x)
  flt.f_filter(x)
end

def test_08_05
  gen = make_fir_filter(3, vct(0.5, 0.25, 0.125))
  gen1 = make_fir_filter(3, vct(0.5, 0.25, 0.125))
  print_and_check(gen, "fir-filter", "fir-filter order: 3, xs: [0.500 0.250 0.125]")
  v0 = make_vct!(10) do |i| fir_filter(gen, i.zero? ? 1.0 : 0.0) end
  v1 = make_vct(10)
  inp = -1
  vct_map!(v1, lambda do | |
             inp += 1
             fir_filter?(gen1) ? fir_filter(gen1, inp.zero? ? 1.0 : 0.0) : -1.0
           end)
  unless vequal(v0, v1)
    snd_display("map fir_filter: %s %s?", v0, v1)
  end
  unless fir_filter?(gen)
    snd_display("%s not fir_filter?", gen)
  end
  if gen.length != 3
    snd_display("fir_filter length: %s?", gen.length)
  end
  if fneq(v0[1], 0.25) or fneq(v0[2], 0.125)
    snd_display("fir_filter output: %s?", v0)
  end
  data = gen.xcoeffs
  if fneq(data[1], 0.25)
    snd_display("fir_filter xcoeffs: %s?", data)
  end
  if (res = Snd.catch do mus_xcoeff(gen, 123) end).first != :mus_error
    snd_display("xcoeff 123: %s", res.inspect)
  end
  if (res = Snd.catch do mus_ycoeff(gen, 123) end).first != :mus_error
    snd_display("fir ycoeff 123: %s", res.inspect)
  end
  f1 = make_fir_filter(3, vct(0.5, 0.25, 0.125))
  f2 = make_fir_filter(3, vct(0.5, 0.25, 0.125))
  f3 = make_fir_filter(3, vct(0.75, 0.25, 0.125))
  fir_filter(f1, 1.0)
  fir_filter(f2, 1.0)
  fir_filter(f3, 1.0)
  test_gen_equal(f1, f2, f3)
  f1 = make_fir_filter(3, vct(0.5, 0.25, 0.125))
  f2 = make_fir_filter(3, vct(0.5, 0.25, 0.125))
  f3 = make_fir_filter(2, vct(0.5, 0.25))
  fir_filter(f1, 1.0)
  fir_filter(f2, 1.0)
  fir_filter(f3, 1.0)
  test_gen_equal(f1, f2, f3)
  coeffs = vct(0.1, 0.2, 0.3, 0.4, 0.4, 0.3, 0.2, 0.1)
  flt = make_fir_filter(8, coeffs)
  es = make_array(8) do |i| make_env([0, coeffs[i], 1, 0], :length, 102) end
  es[5] = make_env([0, 0.4, 1, 1], :length, 102)
  data = make_vct!(100) do |i|
    val = fir_filter(flt, (i % 12).zero? ? 1.0 : 0.0)
    xcof = flt.xcoeffs
    es.each_with_index do |en, j| xcof[j] = env(en) end
    val
  end
  if fneq(data[1], 0.2) or fneq(data[10], 0.0) or
      fneq(data[18], 0.166) or fneq(data[89], 0.923)
    snd_display("filter xcoeffs: %s?", data)
  end
  #
  fir1 = make_fir_filter(3, [1, 0.4, 0.1].to_vct)
  fir2 = make_f_filter([1, 0.4, 0.1].to_vct)
  10.times do |i|
    val1 = fir_filter(fir1, i.zero? ? 1.0 : 0.0)
    val2 = f_filter(fir2, i.zero? ? 1.0 : 0.0)
    if fneq(val1, val2)
      snd_display("f_filter %s -> %s %s?", i, val1, val2)
      break
    end
  end
  # 
  gen = make_iir_filter(3, vct(0.5, 0.25, 0.125))
  gen1 = make_iir_filter(3, vct(0.5, 0.25, 0.125))
  print_and_check(gen, "iir-filter", "iir-filter order: 3, ys: [0.500 0.250 0.125]")
  v0 = make_vct!(10) do |i| iir_filter(gen, i.zero? ? 1.0 : 0.0) end
  v1 = make_vct(10)
  inp = -1
  vct_map!(v1, lambda do | |
             inp += 1
             iir_filter?(gen1) ? iir_filter(gen1, inp.zero? ? 1.0 : 0.0) : -1.0
           end)
  unless vequal(v0, v1)
    snd_display("map iir_filter: %s %s?", v0, v1)
  end
  unless iir_filter?(gen)
    snd_display("%s not iir_filter?", gen)
  end
  if gen.length != 3
    snd_display("iir_filter length: %s?", gen.length)
  end
  if fneq(v0[1], -0.25) or fneq(v0[2], -0.062)
    snd_display("iir_filter output: %s?", v0)
  end
  data = gen.ycoeffs
  if fneq(data[1], 0.25)
    snd_display("iir_filter ycoeffs: %s?", data)
  end
  if (res = Snd.catch do mus_ycoeff(gen, 123) end).first != :mus_error
    snd_display("ycoeff 123: %s", res.inspect)
  end
  if (res = Snd.catch do mus_xcoeff(gen, 123) end).first != :mus_error
    snd_display("iir xcoeff 123: %s", res.inspect)
  end
  f1 = make_iir_filter(3, vct(0.5, 0.25, 0.125))
  f2 = make_iir_filter(3, vct(0.5, 0.25, 0.125))
  f3 = make_iir_filter(3, vct(0.75, 0.25, 0.125))
  iir_filter(f1, 1.0)
  iir_filter(f2, 1.0)
  iir_filter(f3, 1.0)
  test_gen_equal(f1, f2, f3)
  f1 = make_iir_filter(3, vct(0.5, 0.25, 0.125))
  f2 = make_iir_filter(3, vct(0.5, 0.25, 0.125))
  f3 = make_iir_filter(2, vct(0.5, 0.25))
  iir_filter(f1, 1.0)
  iir_filter(f2, 1.0)
  iir_filter(f3, 1.0)
  test_gen_equal(f1, f2, f3)
  # 
  gen = make_filter(3, vct(0.5, 0.25, 0.125), vct(0.5, 0.25, 0.125))
  gen1 = make_filter(3, vct(0.5, 0.25, 0.125), vct(0.5, 0.25, 0.125))
  print_and_check(gen,
                  "filter",
                  "filter order: 3, xs: [0.500 0.250 0.125], ys: [0.500 0.250 0.125]")
  v0 = make_vct!(10) do |i| filter(gen, i.zero? ? 1.0 : 0.0) end
  v1 = make_vct(10)
  inp = -1
  vct_map!(v1, lambda do | |
             inp += 1
             filter?(gen1) ? filter(gen1, inp.zero? ? 1.0 : 0.0) : -1.0
           end)
  unless vequal(v0, v1)
    snd_display("map filter: %s %s?", v0, v1)
  end
  unless filter?(gen)
    snd_display("%s not filter?", gen)
  end
  if gen.length != 3
    snd_display("filter length: %s?", gen.length)
  end
  if fneq(v0[1], 0.125) or fneq(v0[2], 0.031)
    snd_display("filter output: %s?", v0)
  end
  gen2 = make_biquad(0.1, 0.2, 0.3, 0.4, 0.5)
  unless filter?(gen2)
    snd_display("make_biquad: %s?", gen2)
  end
  xs = gen.xcoeffs
  ys = gen.ycoeffs
  if xs != vct(0.5, 0.25, 0.125) or xs != ys
    snd_display("mus_xcoeffs: %s %s?", xs, ys)
  end
  if (res = Snd.catch do
        make_filter(:order, 2, :xcoeffs, vct(1.0, 0.5), :ycoeffs, vct(2.0, 1.0, 0.5))
      end).first != :mus_error
    snd_display("make_filter bad coeffs: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_filter(:order, 0, :xcoeffs, vct(1.0, 0.5))
      end).first != :out_of_range
    snd_display("make_filter bad order: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_fir_filter(:order, 22, :xcoeffs, vct(1.0, 0.5))
      end).first != :mus_error
    snd_display("make_fir_filter bad coeffs: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_iir_filter(:order, 22, :ycoeffs, vct(1.0, 0.5))
      end).first != :mus_error
    snd_display("make_iir_filter bad coeffs: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_fir_filter(-1)
      end).first != :out_of_range
    snd_display("make_fir_filter bad order: %s", res.inspect)
  end
  unless iir_filter?(res = make_filter(:order, 2, :ycoeffs, vct(1.0, 0.5)))
    snd_display("make_filter with only y: %s", res)
  end
  f1 = make_filter(3, vct(0.5, 0.25, 0.125), vct(0.5, 0.25, 0.125))
  f2 = make_filter(3, vct(0.5, 0.25, 0.125), vct(0.5, 0.25, 0.125))
  f3 = make_filter(3, vct(0.5, 0.25, 0.125), vct(0.5, 0.5, 0.5))
  filter(f1, 1.0)
  filter(f2, 1.0)
  filter(f3, 1.0)
  test_gen_equal(f1, f2, f3)
  f1 = make_filter(3, vct(0.5, 0.25, 0.125), vct(0.5, 0.25, 0.125))
  f2 = make_filter(3, vct(0.5, 0.25, 0.125), vct(0.5, 0.25, 0.125))
  f3 = make_filter(3, vct(0.5, 0.5, 0.125), vct(0.5, 0.25, 0.0625))
  filter(f1, 1.0)
  filter(f2, 1.0)
  filter(f3, 1.0)
  test_gen_equal(f1, f2, f3)
  fr = make_fir_filter(6, vct(0, 1, 2, 3, 4, 5))
  if fr.length != 6
    snd_display("filter_length: %s?", fr.length)
  end
  # 
  unless vequal(res = cascade2canonical([vct(1, 0, 0), vct(1, 0.5, 0.25)]),
                vct(1.000, 0.500, 0.250, 0.000, 0.000))
    snd_display("cascade2canonical 0: %s?", res)
  end
  unless vequal(res = cascade2canonical([vct(1, 1, 0), vct(1, 0.5, 0.25)]),
                vct(1.000, 1.500, 0.750, 0.250, 0.000))
    snd_display("cascade2canonical 1: %s?", res)
  end
  unless vequal(res = cascade2canonical([vct(1, 0.8, 0), vct(1, 1.4, 0.65), vct(1, 0, 0)]),
                vct(1.000, 2.200, 1.770, 0.520, 0.000, 0.000, 0.000))
    snd_display("cascade2canonical 2: %s?", res)
  end
  unless vequal(res = cascade2canonical([vct(1, -0.9, 0), vct(1, 1, 0.74), vct(1, -1.6, 0.8)]),
                vct(1.000, -1.500, 0.480, -0.330, 0.938, -0.533, 0.000))
    snd_display("cascade2canonical 3: %s?", res)
  end
  #
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050)
  pad_channel(0, 10000)
  freq_sweep(0.45)
  sp = rough_spectrum(ind)
  if (not vequal(sp, vct(0.962, 0.998, 0.998, 0.998, 0.998, 0.999, 0.999, 0.998, 0.997, 1))) and
      (not vequal(sp, vct(0.963, 0.999, 0.999, 0.999, 0.999, 0.999, 1, 1, 0.998, 0.997)))
    snd_display("initial rough spectrum: %s?", sp)
  end
  b = make_butter_high_pass(440.0)
  v = make_vct!(10) do |i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.915, -0.162, -0.146, -0.131, -0.117, -0.103, -0.09, -0.078, -0.066, -0.056))
    snd_display("butter high: %s?", v)
  end
  b = make_butter_high_pass(1000.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  if (not vequal(sp, vct(0.150, 0.833, 0.980, 0.994, 0.997, 0.998, 0.999, 0.998, 0.997, 1))) and
      (not vequal(sp, vct(0.150, 0.833, 0.981, 0.995, 0.998, 0.999, 1, 1, 0.998, 0.997)))
    snd_display("hp rough spectrum: %s?", sp)
  end
  undo_edit
  # 
  b = make_butter_low_pass(440.0)
  v.map_with_index! do |val, i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.004, 0.014, 0.026, 0.035, 0.043, 0.049, 0.053, 0.055, 0.057, 0.057))
    snd_display("butter low: %s?", v)
  end
  b = make_butter_low_pass(1000.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  unless vequal(sp, vct(1, 0.212, 0.024, 0.005, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("lp rough spectrum: %s?", sp)
  end
  undo_edit
  # 
  b = make_butter_band_pass(440.0, 50.0)
  v.map_with_index! do |val, i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.007, 0.014, 0.013, 0.013, 0.012, 0.011, 0.009, 0.008, 0.007, 0.005))
    snd_display("butter bandpass: %s?", v)
  end
  b = make_butter_band_pass(1000.0, 500.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  unless vequal(sp, vct(0.888, 1, 0.144, 0.056, 0.027, 0.014, 0.008, 0.004, 0.002, 0))
    snd_display("bp rough spectrum: %s?", sp)
  end
  undo_edit
  # 
  b = make_butter_band_reject(440.0, 50.0)
  v.map_with_index! do |val, i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v,
                vct(0.993, -0.014, -0.013, -0.013, -0.012, -0.011, -0.009, -0.008, -0.007, -0.005))
    snd_display("butter bandstop: %s?", v)
  end
  b = make_butter_band_reject(1000.0, 500.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  if (not vequal(sp, vct(0.662, 0.687, 0.953, 0.980, 0.989, 0.994, 0.997, 0.997, 0.997, 1))) and
      (not vequal(sp, vct(0.664, 0.689, 0.955, 0.982, 0.992, 0.996, 0.999, 1, 0.999, 0.998)))
    snd_display("bs rough spectrum: %s?", sp)
  end
  undo_edit
  #
  # analog filter (requires --with-gsl)
  if $with_test_gsl
    analog_filter_tests
  end
  #
  v = spectrum2coeffs(10, vct(0, 1.0, 0, 0, 0, 0, 0, 0, 1.0, 0))
  v1 = make_fir_coeffs(10, vct(0, 1.0, 0, 0, 0, 0, 0, 0, 1.0, 0))
  unless vequal(v, vct(-0.190, -0.118, 0.000, 0.118, 0.190, 0.190, 0.118, 0.000, -0.118, -0.190))
    snd_display("spectrum2coeffs: %s?", v)
  end
  unless vequal(v, v1)
    snd_display("spectrum2coeffs v make_fir_coeffs: %s %s?", v, v1)
  end
  notched_spectr = make_vct(20)
  notched_spectr[2] = 1.0
  v = spectrum2coeffs(20, notched_spectr)
  v1 = make_fir_coeffs(20, notched_spectr)
  unless vequal(v, vct(0.095, 0.059, 0.000, -0.059, -0.095, -0.095, -0.059, 0.000, 0.059, 0.095,
                       0.095, 0.059, 0.000, -0.059, -0.095, -0.095, -0.059, 0.000, 0.059, 0.095))
    snd_display("spectrum2coeffs (notch): %s?", v)
  end
  unless vequal(v, v1)
    snd_display("spectrum2coeffs v(2) make_fir_coeffs: %s %s?", v, v1)
  end
  flt = make_fir_filter(20, v)
  map_channel(lambda do |y| fir_filter(flt, y) end)
  sp = rough_spectrum(ind)
  unless vequal(sp, vct(0.007, 0.493, 1.000, 0.068, 0.030, 0.019, 0.014, 0.011, 0.009, 0.009))
    snd_display("sp->coeff rough spectrum: %s?", sp)
  end
  undo_edit
  # 
  rspect = make_vct!(20) do random(1.0) end
  v = spectrum2coeffs(20, rspect)
  v1 = make_fir_coeffs(20, rspect)
  unless vequal(v, v1)
    snd_display("spectrum2coeffs v(3) make_fir_coeffs:\n# %s\n# %s", v, v1)
  end
  b = make_highpass(hz2radians(1000.0), 10)
  v = make_vct!(20) do |i| highpass(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(-0.001, -0.002, -0.005, -0.011, -0.021, -0.034, -0.049, -0.065,
                       -0.078, -0.087, 0.909, -0.087, -0.078, -0.065, -0.049, -0.034,
                       -0.021, -0.011, -0.005, -0.002))
    snd_display("dsp.rb high: %p?", v)
  end
  b = make_highpass(hz2radians(1000.0), 20)
  map_channel(lambda do |y| highpass(b, y) end)
  sp = rough_spectrum(ind)
  if (not vequal(sp, vct(0.053, 0.774, 0.998, 0.997, 0.997, 0.996, 0.996, 0.996, 0.997, 1))) and
      (not vequal(sp, vct(0.053, 0.776, 1.000, 0.998, 0.998, 0.998, 0.998, 0.998, 0.998, 1)))
    snd_display("dsp hp rough spectrum: %s?", sp)
  end
  undo_edit
  # 
  b = make_lowpass(hz2radians(1000.0), 10)
  v = make_vct!(20) do |i| lowpass(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.001, 0.002, 0.005, 0.011, 0.021, 0.034, 0.049, 0.065,
                       0.078, 0.087, 0.091, 0.087, 0.078, 0.065, 0.049, 0.034,
                       0.021, 0.011, 0.005, 0.002))
    snd_display("dsp.rb low: %s?", v)
  end
  b = make_lowpass(hz2radians(1000.0), 20)
  map_channel(lambda do |y| lowpass(b, y) end)
  sp = rough_spectrum(ind)
  unless vequal(sp, vct(1, 0.054, 0, 0, 0, 0, 0, 0, 0, 0))
    snd_display("dsp lp rough spectrum: %s?", sp)
  end
  undo_edit
  # 
  b = make_bandpass(hz2radians(1500.0), hz2radians(2000.0), 10)
  v = make_vct!(20) do |i| bandpass(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.001, -0.001, -0.005, -0.011, -0.017, -0.019, -0.013, 0.003,
                       0.022, 0.039, 0.045, 0.039, 0.022, 0.003, -0.013, -0.019,
                       -0.017, -0.011, -0.005, -0.001))
    snd_display("dsp.rb bp: %s?", v)
  end
  b = make_bandpass(hz2radians(1500.0), hz2radians(2000.0), 20)
  map_channel(lambda do |y| bandpass(b, y) end)
  sp = rough_spectrum(ind)
  unless vequal(sp, vct(0.010, 1, 0.154, 0, 0, 0, 0, 0, 0, 0))
    snd_display("dsp bp rough spectrum: %s?", sp)
  end
  undo_edit
  # 
  b = make_bandstop(hz2radians(1500.0), hz2radians(2000.0), 10)
  v = make_vct!(20) do |i| bandstop(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(-0.001, 0.001, 0.005, 0.011, 0.017, 0.019, 0.013, -0.003,
                       -0.022, -0.039, 0.955, -0.039, -0.022, -0.003, 0.013, 0.019,
                       0.017, 0.011, 0.005, 0.001))
    snd_display("dsp.rb bs: %s?", v)
  end
  b = make_bandstop(hz2radians(1500.0), hz2radians(2000.0), 20)
  map_channel(lambda do |y| bandstop(b, y) end)
  sp = rough_spectrum(ind)
  if (not vequal(sp, vct(0.904, 0.425, 0.821, 0.998, 0.997, 0.996, 0.996, 0.996, 0.997, 1))) and
      (not vequal(sp, vct(0.906, 0.425, 0.822, 1.000, 0.999, 0.998, 0.998, 0.998, 0.998, 1)))
    snd_display("dsp bp rough spectrum: %s?", sp)
  end
  undo_edit
  # 
  b = make_differentiator(10)
  v = make_vct!(20) do |i| differentiator(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(-0.008, 0.011, -0.021, 0.039, -0.066, 0.108, -0.171, 0.270, -0.456, 0.977,
                       0.000, -0.977, 0.456, -0.270, 0.171, -0.108, 0.066, -0.039, 0.021, -0.011))
    snd_display("dsp.rb df: %s?", v)
  end
  b = make_differentiator(20)
  map_channel(lambda do |y| differentiator(b, y) end)
  sp = rough_spectrum(ind)
  unless vequal(sp, vct(0.004, 0.027, 0.075, 0.147, 0.242, 0.362, 0.506, 0.674, 0.864, 1))
    snd_display("dsp df rough spectrum: %s?", sp)
  end
  undo_edit
  # 
  b = make_iir_high_pass_2(440.0)
  v = make_vct!(10) do |i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.915, -0.162, -0.146, -0.131, -0.117, -0.103, -0.09, -0.078, -0.066, -0.056))
    snd_display("iir-2 high: %s?", v)
  end
  b = make_iir_high_pass_2(1000.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  if (not vequal(sp, vct(0.150, 0.833, 0.980, 0.994, 0.997, 0.998, 0.999, 0.998, 0.997, 1))) and
      (not vequal(sp, vct(0.150, 0.833, 0.981, 0.995, 0.998, 0.999, 1, 1, 0.998, 0.997)))
    snd_display("iir-2 hp rough spectrum: %s?", sp)
  end
  undo_edit
  #
  b = make_iir_low_pass_2(440.0)
  v = make_vct!(10) do |i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.004, 0.014, 0.026, 0.035, 0.043, 0.049, 0.053, 0.055, 0.057, 0.057))
    snd_display("iir-2 low: %s?", v)
  end
  b = make_iir_low_pass_2(1000.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  unless vequal(sp, vct(1, 0.212, 0.024, 0.005, 0.001, 0, 0, 0, 0, 0))
    snd_display("iir-2 lp rough spectrum: %s?", sp)
  end
  undo_edit
  #
  b = make_iir_band_pass_2(440.0, 490.0)
  v = make_vct!(10) do |i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.007, 0.014, 0.013, 0.013, 0.012, 0.010, 0.009, 0.008, 0.006, 0.004))
    snd_display("iir bp-2 bandpass: %s?", v)
  end
  b = make_iir_band_pass_2(1000.0, 1500.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  unless vequal(sp, vct(0.239, 1, 0.117, 0.041, 0.019, 0.010, 0.005, 0.003, 0.001, 0))
    snd_display("iir bp-2 rough spectrum: %s?", sp)
  end
  undo_edit
  #
  b = make_iir_band_stop_2(440.0, 500.0)
  v = make_vct!(10) do |i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.992, -0.017, -0.016, -0.015, -0.014, -0.012, -0.011, -0.009, -0.007,-0.005))
    snd_display("iir bp-2 bandstop: %s?", v)
  end
  b = make_iir_band_stop_2(1000.0, 1500.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  if (not vequal(sp, vct(0.836, 0.525, 0.943, 0.979, 0.989, 0.994, 0.997, 0.997, 0.997, 1))) and
      (not vequal(sp, vct(0.838, 0.527, 0.945, 0.981, 0.991, 0.996, 0.999, 1, 0.999, 0.998)))
    snd_display("iir bs-2 rough spectrum: %s?", sp)
  end
  undo_edit
  #
  b = make_butter_hp(4, 440.0)
  v = make_vct!(10) do |i| butter(b, i.zero? ? 1.0 : 0.0) end
  if (not vequal(v, vct(0.725, -0.466, -0.315, -0.196, -0.104,
                        -0.036, 0.014, 0.047, 0.0685, 0.0775))) and
      (not vequal(v, vct(0.725, -0.466, -0.315, -0.196, -0.104,
                         0.035, 0.015, 0.049, 0.070, 0.081))) and
      (not vequal(v, vct(0.725, -0.466, -0.315, -0.196, -0.104,
                         -0.035, 0.014, 0.049, 0.069, 0.079)))
    snd_display(snd_format_neq(v,
                               vct(0.725, -0.466, -0.315, -0.196, -0.104,
                                   -0.036, 0.014, 0.047, 0.0685, 0.0775),
                               "butter hp"))
  end
  b = make_butter_hp(4, 1000.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  if (not vequal(sp, vct(0.0505, 0.982, 1.0, 1.0, 0.998, 0.998, 0.999, 0.998, 0.996, 0.999))) and
      (not vequal(sp, vct(0.051, 0.982, 1.0, 1.0, 0.998, 0.998, 0.998, 0.999, 0.997, 0.995))) and
      (not vequal(sp, vct(0.051, 0.991, 1.0, 1.0, 0.998, 0.998, 0.999, 0.999, 0.997, 0.995))) and
      (not vequal(sp, vct(0.045, 0.970, 1.0, 1.0, 0.998, 0.998, 0.999, 0.999, 0.997, 0.995))) and
      (not vequal(sp, vct(0.052, 0.971, 1.0, 1.0, 0.998, 0.998, 0.999, 0.999, 0.997, 0.995)))
    snd_display("butter hp rough spectrum: %s?", sp)
  end
  undo_edit
  #
  b = make_butter_lp(4, 440.0)
  v = make_vct!(10) do |i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, make_vct(10))
    snd_display("butter lp: %s?", v)
  end
  b = make_butter_lp(4, 1000.0)
  map_channel(lambda do |y| butter(b, y) end)
  sp = rough_spectrum(ind)
  if (not vequal(sp, vct(1, 0.035, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000))) and
      (not vequal(sp, vct(1, 0.038, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000)))
    snd_display("butter lp rough spectrum: %s?", sp)
  end
  undo_edit
  #
  b = make_butter_bp(4, 440.0, 500.0)
  v = make_vct!(10) do |i| butter(b, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, make_vct(10))
    snd_display("butter bp: %s?", v)
  end
  b = make_butter_bp(4, 1000.0, 1500.0)
  map_channel(lambda do |y| butter(b, y) end)
  undo_edit
  #
  b = make_butter_bs(4, 440.0, 500.0)
  v = make_vct!(10) do |i| butter(b, i.zero? ? 1.0 : 0.0) end
  if (not vequal(v, vct(0.978, -0.043, -0.041, -0.038, -0.035,
                        -0.031, -0.026, -0.0225, -0.015, -0.0085))) and
      (not vequal(v, vct(0.978, -0.043, -0.041, -0.038, -0.035,
                         -0.031, -0.027, -0.022, -0.017, -0.011))) and
      (not vequal(v, vct(0.978, -0.043, -0.041, -0.038, -0.035,
                         -0.031, -0.027, -0.021, -0.014, -0.011)))
    snd_display("butter bs: %s?", v)
  end
  b = make_butter_bs(4, 1000.0, 1500.0)
  map_channel(lambda do |y| butter(b, y) end)
  undo_edit
  #
  revert_sound
  test_scanned_synthesis(0.1, 10000, 1.0, 0.1, 0.0)
  close_sound(ind)
end

def test_08_06
  gen = make_sawtooth_wave(440.0)
  gen1 = make_sawtooth_wave(440.0)
  print_and_check(gen, "sawtooth-wave", "sawtooth-wave freq: 440.000Hz, phase: 3.142, amp: 1.000")
  v0 = Vct.new(10) do sawtooth_wave(gen, 0.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | sawtooth_wave?(gen1) ? sawtooth_wave(gen1, 0.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map sawtooth_wave: %s %s?", v0, v1)
  end
  unless sawtooth_wave?(gen)
    snd_display("%s not sawtooth_wave?", gen)
  end
  if fneq(gen.phase, 4.39538)
    snd_display("sawtooth_wave phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("sawtooth_wave frequency: %s?", gen.frequency)
  end
  gen.frequency = 100.0
  if fneq(gen.frequency, 100.0)
    snd_display("sawtooth_wave set_frequency: %s?", gen.frequency)
  end
  if fneq(gen.scaler, 1.0)
    snd_display("sawtooth_wave scaler: %s?", gen.scaler)
  end
  gen.scaler = 0.5
  if fneq(gen.scaler, 0.5)
    snd_display("sawtooth_wave set_scaler: %s?", gen.scaler)
  end
  if fneq(v0[1], 0.04) or fneq(v0[8], 0.319)
    snd_display("sawtooth_wave output: %s?", v0)
  end
  test_gen_equal(make_sawtooth_wave(440.0),
                 make_sawtooth_wave(440.0),
                 make_sawtooth_wave(120.0))
  test_gen_equal(make_sawtooth_wave(440.0),
                 make_sawtooth_wave(440.0),
                 make_sawtooth_wave(440.0, 1.0, 1.0))
  test_gen_equal(make_sawtooth_wave(440.0),
                 make_sawtooth_wave(440.0),
                 make_sawtooth_wave(440.0, 0.5))
  gen1 = make_sawtooth_wave(100.0)
  gen2 = make_sawtooth_wave(-100.0)
  mx = 0.0
  100.times do mx = [mx, (gen1.run + gen2.run).abs].max end
  if fneq(mx, 0.0)
    snd_display("sawtooth_wave +-: %s?", mx)
  end
  # 
  gen = make_square_wave(440.0)
  gen1 = make_square_wave(440.0)
  print_and_check(gen, "square-wave", "square-wave freq: 440.000Hz, phase: 0.000, amp: 1.000")
  v0 = make_vct!(10) do |i| square_wave(gen, 0.0) end
  v1 = make_vct(10)
  w = 1.0
  vct_map!(v1, lambda do | |
             w = gen1.width
             square_wave?(gen1) ? square_wave(gen1, 0.0) : -1.0
           end)
  if fneq(w, 0.5)
    snd_display("mus_width opt: %s?", w)
  end
  unless vequal(v0, v1)
    snd_display("map square_wave: %s %s?", v0, v1)
  end
  unless square_wave?(gen)
    snd_display("%s not square_wave?", gen)
  end
  if fneq(gen.phase, 1.253787)
    snd_display("square_wave phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("square_wave frequency: %s?", gen.frequency)
  end
  if fneq(gen.scaler, 1.0)
    snd_display("square_wave scaler: %s?", gen.scaler)
  end
  gen.scaler = 0.5
  if fneq(gen.scaler, 0.5)
    snd_display("square_wave set_scaler: %s?", gen.scaler)
  end
  if fneq(gen.width, 0.5)
    snd_display("square_wave width: %s?", gen.width)
  end
  gen.width = 0.75
  if fneq(gen.width, 0.75)
    snd_display("square_wave set_width: %s?", gen.width)
  end
  if fneq(v0[1], 1.0) or fneq(v0[8], 1.0)
    snd_display("square_wave output: %s?", v0)
  end
  test_gen_equal(make_square_wave(440.0),
                 make_square_wave(440.0),
                 make_square_wave(120.0))
  test_gen_equal(make_square_wave(440.0),
                 make_square_wave(440.0),
                 make_square_wave(440.0, 1.0, 1.0))
  test_gen_equal(make_square_wave(440.0),
                 make_square_wave(440.0),
                 make_square_wave(440.0, 0.5))
  old_srate = mus_srate
  set_mus_srate(500.0)
  gen = make_square_wave(100.0, -0.5, HALF_PI)
  v0 = make_vct!(20) do |i| gen.run end
  unless vequal(v0, vct(-0.5, -0.5, 0.0, 0.0, -0.5, -0.5, -0.5, 0.0, 0.0, -0.5,
                        -0.5, -0.5, 0.0, 0.0, -0.5, -0.5, -0.5, 0.0, 0.0, -0.5))
    snd_display("square_wave -0.5: %s?", v0)
  end
  set_mus_srate(old_srate)
  # 
  gen = make_triangle_wave(440.0)
  gen1 = make_triangle_wave(440.0, 1.0, PI)
  gen2 = make_triangle_wave(440.0)
  print_and_check(gen, "triangle-wave", "triangle-wave freq: 440.000Hz, phase: 0.000, amp: 1.000")
  v0 = make_vct!(10) do |i| triangle_wave(gen, 0.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | triangle_wave?(gen2) ? triangle_wave(gen2, 0.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map triangle_wave: %s %s?", v0, v1)
  end
  unless triangle_wave?(gen)
    snd_display("%s not triangle_wave?", gen)
  end
  if fneq(gen.phase, 1.253787)
    snd_display("triangle_wave phase: %s?", gen.phase)
  end
  if fneq(gen1.phase, PI)
    snd_display("init triangle_wave phase: %s?", gen1.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("triangle_wave frequency: %s?", gen.frequency)
  end
  if fneq(gen.scaler, 1.0)
    snd_display("triangle_wave scaler: %s?", gen.scaler)
  end
  gen.scaler = 0.5
  if fneq(gen.scaler, 0.5)
    snd_display("triangle_wave set_scaler: %s?", gen.scaler)
  end
  if fneq(v0[1], 0.08) or fneq(v0[8], 0.639)
    snd_display("triangle_wave output: %s?", v0)
  end
  test_gen_equal(make_triangle_wave(440.0),
                 make_triangle_wave(440.0),
                 make_triangle_wave(120.0))
  test_gen_equal(make_triangle_wave(440.0),
                 make_triangle_wave(440.0),
                 make_triangle_wave(440.0, 1.0, 1.0))
  test_gen_equal(make_triangle_wave(440.0),
                 make_triangle_wave(440.0),
                 make_triangle_wave(440.0, 0.5))
  gen1 = make_triangle_wave(100.0)
  gen2 = make_triangle_wave(-100.0)
  mx = 0.0
  100.times do mx = [mx, (gen1.run + gen2.run).abs].max end
  if fneq(mx, 0.0)
    snd_display("triangle_wave +-: %s?", mx)
  end
  # 
  gen = make_pulse_train(440.0)
  gen1 = make_pulse_train(440.0)
  print_and_check(gen, "pulse-train", "pulse-train freq: 440.000Hz, phase: 0.000, amp: 1.000")
  v0 = make_vct!(10) do |i| pulse_train(gen, 0.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | pulse_train?(gen1) ? pulse_train(gen1, 0.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map pulse_train: %s %s?", v0, v1)
  end
  unless pulse_train?(gen)
    snd_display("%s not pulse_train?", gen)
  end
  if fneq(gen.phase, 1.253787)
    snd_display("pulse_train phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("pulse_train frequency: %s?", gen.frequency)
  end
  if fneq(gen.scaler, 1.0)
    snd_display("pulse_train scaler: %s?", gen.scaler)
  end
  gen.scaler = 0.5
  if fneq(gen.scaler, 0.5)
    snd_display("pulse_train set_scaler: %s?", gen.scaler)
  end
  if fneq(v0[0], 1.0) or fneq(v0[8], 0.0)
    snd_display("pulse_train output: %s?", v0)
  end
  test_gen_equal(make_pulse_train(440.0),
                 make_pulse_train(440.0),
                 make_pulse_train(120.0))
  test_gen_equal(make_pulse_train(440.0),
                 make_pulse_train(440.0),
                 make_pulse_train(440.0, 1.0, 1.0))
  test_gen_equal(make_pulse_train(440.0),
                 make_pulse_train(440.0),
                 make_pulse_train(440.0, 0.5))
  old_srate = mus_srate
  set_mus_srate(500.0)
  gen = make_pulse_train(100.0, -0.5, HALF_PI)
  v0 = make_vct!(20) do |i| gen.run end
  unless vequal(v0, vct(0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, -0.5,
                        0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, -0.5))
    snd_display("pulse_train -0.5: %s?", v0)
  end
  set_mus_srate(old_srate)
  # 
  gen = make_two_pole(1200.0, 0.1)
  unless two_pole?(gen)
    snd_display("%s not 2-polar?", gen)
  end
  if gen.order != 2
    snd_display("2-polar order: %s?", gen.order)
  end
  if fneq(gen.a0, 1.0)
    snd_display("2-polar a0: %s?", gen.a0)
  end
  if fneq(gen.b1, -0.188)
    snd_display("2-polar b1: %s?", gen.b1)
  end
  if fneq(gen.b2, 0.01)
    snd_display("2-polar b2: %s?", gen.b2)
  end
  if fneq(gen.frequency, 1200.0)
    snd_display("2-polar freq: %s?", gen.frequency)
  end
  if fneq(gen.scaler, 0.1)
    snd_display("2-polar scaler: %s?", gen.scaler)
  end
  # 
  gen = make_two_pole(:frequency, 1200.0, :radius, 0.1)
  unless two_pole?(gen)
    snd_display("%s not f2-polar?", gen)
  end
  if gen.order != 2
    snd_display("f2-polar order: %s?", gen.order)
  end
  if fneq(gen.a0, 1.0)
    snd_display("f2-polar a0: %s?", gen.a0)
  end
  if fneq(gen.b1, -0.188)
    snd_display("f2-polar b1: %s?", gen.b1)
  end
  if fneq(gen.b2, 0.01)
    snd_display("f2-polar b2: %s?", gen.b2)
  end
  if fneq(gen.frequency, 1200.0)
    snd_display("f2-polar freq: %s?", gen.frequency)
  end
  if fneq(gen.scaler, 0.1)
    snd_display("f2-polar scaler: %s?", gen.scaler)
  end
  # 
  gen = make_two_zero(1200.0, 0.1)
  unless two_zero?(gen)
    snd_display("%s not 2-zp?", gen)
  end
  if gen.order != 2
    snd_display("2-zp order: %s?", gen.order)
  end
  if fneq(gen.a0, 1.0)
    snd_display("2-zp a0: %s?", gen.a0)
  end
  if fneq(gen.a1, -0.188)
    snd_display("2-zp a1: %s?", gen.a1)
  end
  if fneq(gen.a2, 0.01)
    snd_display("2-zp a2: %s?", gen.a2)
  end
  if fneq(gen.frequency, 1200.0)
    snd_display("2-zp freq: %s?", gen.frequency)
  end
  if fneq(gen.scaler, 0.1)
    snd_display("2-zp scaler: %s?", gen.scaler)
  end
  # 
  gen = make_two_zero(:frequency, 1200.0, :radius, 0.1)
  unless two_zero?(gen)
    snd_display("%s not f2-zp?", gen)
  end
  if gen.order != 2
    snd_display("f2-zp order: %s?", gen.order)
  end
  if fneq(gen.a0, 1.0)
    snd_display("f2-zp a0: %s?", gen.a0)
  end
  if fneq(gen.a1, -0.188)
    snd_display("f2-zp a1: %s?", gen.a1)
  end
  if fneq(gen.a2, 0.01)
    snd_display("f2-zp a2: %s?", gen.a2)
  end
  if fneq(gen.frequency, 1200.0)
    snd_display("f2-zp freq: %s?", gen.frequency)
  end
  if fneq(gen.scaler, 0.1)
    snd_display("f2-zp scaler: %s?", gen.scaler)
  end
  # 
  gen = make_formant(1200.0, 0.9)
  gen1 = make_formant(1200.0, 0.9)
  print_and_check(gen, "formant", "formant frequency: 1200.000, radius: 0.900")
  v0 = make_vct!(10) do |i| formant(gen, i.zero? ? 1.0 : 0.0) end
  v1 = make_vct(10)
  inp = -1
  vct_map!(v1, lambda do | |
             inp += 1
             formant?(gen1) ? formant(gen1, inp.zero? ? 1.0 : 0.0) : -1.0
           end)
  unless vequal(v0, v1)
    snd_display("map formant: %s %s?", v0, v1)
  end
  unless formant?(gen)
    snd_display("%s not formant?", gen)
  end
  if gen.order != 2
    snd_display("formant order: %s?", gen.order)
  end
  if fneq(gen.frequency, 1200.0)
    snd_display("formant frequency: %s?", gen.frequency)
  end
  if fneq(v0[0], 0.095) or fneq(v0[1], 0.161)
    snd_display("formant output: %s?", v0)
  end
  if fneq(gen.scaler, 0.9)
    snd_display("formant gain: %s?", gen.scaler)
  end
  gen.scaler = 2.0
  if fneq(gen.scaler, 2.0)
    snd_display("formant set_gain: %s?", gen.scaler)
  end
  f1 = make_formant(1200.0, 0.9)
  f2 = make_formant(1200.0, 0.9)
  f3 = make_formant(600.0, 0.9)
  formant(f1, 1.0)
  formant(f2, 1.0)
  formant(f3, 1.0)
  test_gen_equal(f1, f2, f3)
  f1 = make_formant(1200.0, 0.9)
  f2 = make_formant(1200.0, 0.9)
  f3 = make_formant(1200.0, 0.99)
  formant(f1, 1.0)
  formant(f2, 1.0)
  formant(f3, 1.0)
  test_gen_equal(f1, f2, f3)
  # 
  fs = [make_formant(1000.0, 0.1), make_formant(100.0, 0.2)]
  f0 = make_formant(1000.0, 0.1)
  f1 = make_formant(100.0, 0.2)
  amps = vct(0.5, 0.25)
  v0 = make_vct!(10) do |i|
    val = i.zero? ? 1.0 : 0.0
    (0.5 * formant(f0, val)) + (0.25 * formant(f1, val))
  end
  v1 = make_vct!(10) do |i| formant_bank(amps, fs, i.zero? ? 1.0 : 0.0) end
  unless vequal(v0, v1)
    snd_display("formant_bank 1: %s %s?", v0, v1)
  end
  # 
  fs = [make_formant(1000.0, 0.1), make_formant(100.0, 0.2)]
  amps = vct(0.5, 0.25)
  v = make_vct!(5) do |i| formant_bank(amps, fs, i.zero? ? 1.0 : 0.0) end
  unless vequal(v, vct(0.368, 0.095, -0.346, -0.091, -0.020))
    snd_display("run formant_bank: %s?", v)
  end
  fs = make_array(1)
  amps = make_vct(1, 1.0)
  fs[0] = make_oscil(440.0)
  if (res = Snd.catch do formant_bank(amps, fs, 1.0) end).first != :wrong_type_arg
    snd_display("formant_bank gets oscil: %s", res.inspect)
  end
  #
  ob = open_sound("oboe.snd")
  poltergeist = lambda do |frek, amp, r, gain, frek_env, r_env|
    # test courtesy of Anders Vinjar
    filt = make_formant(frek, r)
    fe = make_env(:envelope, frek_env, :length, frames, :offset, frek)
    re = make_env(:envelope, r_env, :length, frames, :offset, r)
    lambda do |y|
      outval = gain * formant(filt, amp * y)
      mus_set_formant_radius_and_frequency(filt, env(re), env(fe))
      outval
    end
  end
  map_chan(poltergeist.call(300, 0.1, 0.0, 30.0, [0, 100, 1, 4000], [0, 0.99, 1, 0.9]))
  play(ob, :wait, true)
  close_sound(ob)
end

include Mixer_matrix

def test_08_07
  gen = make_mixer(2, 0.5, 0.25, 0.125, 1.0)
  fr0 = make_frame(2, 1.0, 1.0)
  fr1 = make_frame(2, 0.0, 0.0)
  print_and_check(gen,
                  "mixer",
                  "mixer chans: 2, [
 0.500 0.250
 0.125 1.000
]")
  ap = mus_array_print_length
  mx = make_mixer(8)
  set_mus_array_print_length(4)
  mx.length.times do |i|
    mx.length.times do |j|
      mixer_set!(mx, i, j, j + i * 8)
    end
  end
  print_and_check(mx,
                  "mixer",
                  "mixer chans: 8, [
 0.000 1.000 2.000 3.000...
 8.000 9.000 10.000 11.000...
 16.000 17.000 18.000 19.000...
 24.000 25.000 26.000 27.000...
]")
  set_mus_array_print_length(12)
  print_and_check(mx,
                  "mixer",
                  "mixer chans: 8, [
 0.000 1.000 2.000 3.000 4.000 5.000 6.000 7.000
 8.000 9.000 10.000 11.000 12.000 13.000 14.000 15.000
 16.000 17.000 18.000 19.000 20.000 21.000 22.000 23.000
 24.000 25.000 26.000 27.000 28.000 29.000 30.000 31.000
 32.000 33.000 34.000 35.000 36.000 37.000 38.000 39.000
 40.000 41.000 42.000 43.000 44.000 45.000 46.000 47.000
 48.000 49.000 50.000 51.000 52.000 53.000 54.000 55.000
 56.000 57.000 58.000 59.000 60.000 61.000 62.000 63.000
]")
  set_mus_array_print_length(ap)
  print_and_check(fr0, "frame", "frame[2]: [1.000 1.000]")
  unless frame?(fr0)
    snd_display("%s not a frame?", fr0)
  end
  unless mixer?(gen)
    snd_display("%s not a mixer?", gen)
  end
  if fr0.eql?(fr1)
    snd_display("frame=? %s %s?", fr0, fr1)
  end
  if fr0.channels != 2
    snd_display("frame channels: %s?", fr0.channels)
  end
  if fr1.length != 2
    snd_display("frame length: %s?", fr1.length)
  end
  if gen.channels != 2
    snd_display("mixer channels: %s?", gen.channels)
  end
  frame2frame(fr0, gen, fr1)
  if fneq(frame_ref(fr0, 0), 1.0) or
      fneq(frame_ref(fr1, 1), 1.25) or
      fneq(mixer_ref(gen, 0, 0), 0.5)
    snd_display("fr0: %s?", fr0)
  end
  frame_set!(fr1, 0, 1.0)
  fr3 = frame_add(fr0, fr1)
  fr4 = frame_multiply(fr0, fr1)
  fr5 = sample2frame(fr1, 0.5)
  if fneq(frame_ref(fr3, 0), 2.0) or
      fneq(frame_ref(fr4, 0), 1.0)
    snd_display("fr+*: %s %s?", fr3, fr4)
  end
  if fneq(res = frame_ref(fr5, 0), 0.5)
    snd_display("sample2frame: %s?", res)
  end
  sample2frame(fr1, 0.5, fr5)
  if fneq(res = frame_ref(fr5, 0), 0.5)
    snd_display("repeat sample2frame: %s?", res)
  end
  fr3 = make_frame(2)
  fr4 = make_frame(4)
  frame_set!(fr3, 0, 1.0)
  frame_set!(fr4, 0, 0.5)
  frame_set!(fr4, 2, 0.5)
  unless vequal(frame2list(res = frame_add(fr3, fr4)), [1.5, 0.0])
    snd_display("frame_add unequal chans: %s?", res)
  end
  fr3.reset
  if fneq(frame_ref(fr3, 0), 0.0)
    snd_display("reset frame: %s?", fr3)
  end
  fr3 = make_frame(2)
  fr4 = make_frame(4)
  frame_set!(fr3, 0, 1.0)
  frame_set!(fr4, 0, 0.5)
  frame_set!(fr4, 2, 1.0)
  unless vequal(frame2list(res = frame_multiply(fr3, fr4)), [0.5, 0.0])
    snd_display("frame_multiply unequal chans: %s?", res)
  end
  mx1 = make_mixer(2, 1.0, 0.0, 0.0, 1.0)
  mx2 = mixer_multiply(gen, mx1)
  fr4 = make_frame(2, 1.0, 1.0)
  fr5 = make_frame(2, 1.0, 1.0)
  if fneq(res = frame2sample(mx1, fr1), 1.0)
    snd_display("frame2sample: %s?", res)
  end
  if fneq(res = frame2sample(fr5, fr4), 2.0)
    snd_display("frame2sample: %s?", res)
  end
  unless (res = frame2list(fr1)).eql?([1.0, 1.25])
    snd_display("frame2list: %s?", res)
  end
  if fneq(mixer_ref(mx2, 0, 1), 0.25) or fneq(mixer_ref(mx2, 1, 0), 0.125)
    snd_display("mixer_multiply: %s?", mx2)
  end
  unless mx2.eql?(gen)
    snd_display("mixer=? %s %s?", gen, mx2)
  end
  if mx2.eql?(mx1)
    snd_display("mixer!=? %s %s?", mx1, mx2)
  end
  unless vct?(fr4.data)
    snd_display("mus_data frame: %s?", fr4.data)
  end
  # mus-data doesn't apply from scheme (ruby) level here
  # unless vct?(mx1.data)
  #   snd_display("mus_data mixer: %s?", mx1.data)
  # end
  mixer_set!(mx2, 0, 0, 2.0)
  if fneq(mixer_ref(mx2, 0, 0), 2.0)
    snd_display("mixer_set!: %s?", mx2)
  end
  fr0 = sample2frame(mx2, 1.0)
  if fneq(frame_ref(fr0, 0), 2.0) or fneq(frame_ref(fr0, 1), 0.25)
    snd_display("sample2frame: %s?", fr0)
  end
  frout = make_frame(2)
  sample2frame(mx2, 1.0, frout)
  unless frout.eql?(fr0)
    snd_display("sample2frame via frout: %s %s?", frout, fr0)
  end
  fr1 = make_frame(2, 0.1, 0.2)
  val = frame_add(fr1, 1.0)
  if fneq(frame_ref(val, 0), 1.1) or fneq(frame_ref(val, 1), 1.2)
    snd_display("8 frame_offset: %s?", val)
  end
  val = frame_add(1.0, fr1)
  if fneq(frame_ref(val, 0), 1.1) or fneq(frame_ref(val, 1), 1.2)
    snd_display("8 frame_offset a: %s?", val)
  end
  val = frame_multiply(fr1, 2.0)
  if fneq(frame_ref(val, 0), 0.2) or fneq(frame_ref(val, 1), 0.4)
    snd_display("8 frame_scale: %s?", val)
  end
  val = frame_multiply(2.0, fr1)
  if fneq(frame_ref(val, 0), 0.2) or fneq(frame_ref(val, 1), 0.4)
    snd_display("8 frame_scale a: %s?", val)
  end
  val = frame_copy(fr1)
  if fneq(frame_ref(val, 0), 0.1) or fneq(frame_ref(val, 1), 0.2)
    snd_display("8 frame_copy: %s?", val)
  end
  #
  mx1 = make_mixer(2, 1, 2, 3, 4)
  mx2 = mixer_multiply(mx1, 2.0)
  unless mx2.eql?(make_mixer(2, 2, 4, 6, 8))
    snd_display("8 mixer_scale 2: %s?", mx2)
  end
  mx2 = mixer_multiply(2.0, mx1)
  unless mx2.eql?(make_mixer(2, 2, 4, 6, 8))
    snd_display("8 mixer_scale 2a: %s?", mx2)
  end
  mx2 = mixer_add(2.0, mx1)
  unless mx2.eql?(make_mixer(2, 3, 4, 5, 6))
    snd_display("8 mixer_scale 3: %s?", mx2)
  end
  mx2 = mixer_add(mx1, 2.0)
  unless mx2.eql?(make_mixer(2, 3, 4, 5, 6))
    snd_display("8 mixer_scale 3a: %s?", mx2)
  end
  # 
  mx1 = make_scalar_mixer(2, 2.0)
  mx2 = make_mixer(2, 0.1, 0.2, 0.3, 0.4)
  nmx = mixer_add(mx1, mx2)
  if fneq(mixer_ref(mx1, 0, 0), 2.0) or
      fneq(mixer_ref(mx1, 0, 1), 0.0) or
      fneq(mixer_ref(mx1, 1, 0), 0.0) or
      fneq(mixer_ref(mx1, 1, 1), 2.0)
    snd_display("make_scalar_mixer 2: %s?", mx1)
  end
  if fneq(mixer_ref(mx2, 0, 0), 0.1) or
      fneq(mixer_ref(mx2, 0, 1), 0.2) or
      fneq(mixer_ref(mx2, 1, 0), 0.3) or
      fneq(mixer_ref(mx2, 1, 1), 0.4)
    snd_display("make_mixer 0.1, 0.2, 0.3, 0.4: %s?", mx2)
  end
  if fneq(mixer_ref(nmx, 0, 0), 2.1) or
      fneq(mixer_ref(nmx, 0, 1), 0.2) or
      fneq(mixer_ref(nmx, 1, 0), 0.3) or
      fneq(mixer_ref(nmx, 1, 1), 2.4)
    snd_display("mixer_add: %s?", nmx)
  end
  mx1 = mixer_multiply(mx1, 0.5)
  if fneq(mixer_ref(mx1, 0, 0), 1.0) or
      fneq(mixer_ref(mx1, 0, 1), 0.0) or
      fneq(mixer_ref(mx1, 1, 0), 0.0) or
      fneq(mixer_ref(mx1, 1, 1), 1.0)
    snd_display("mixer_multiply (identity): %s?", mx1)
  end
  mx1.reset
  if fneq(mixer_ref(mx1, 0, 0), 0.0)
    snd_display("reset mixer: %s?", mx1)
  end
  #
  if (res = Snd.catch do make_mixer(2, 0.0, 0.0, 0.0, 0.0, 0.0) end).first != :mus_error
    snd_display("make_mixer extra args: %s", res.inspect)
  end
  if (res = Snd.catch do
        fr1 = make_frame(2, 1.0, 0.0)
        frame2sample(make_oscil, fr1)
      end).first != :mus_error
    snd_display("frame2sample bad arg: %s", res.inspect)
  end
  hi = make_mixer(1, 1)
  if (res = Snd.catch do mixer_set!(hi, 1, 1, 1.0) end).first != :mus_error
    snd_display("mixer_set! 1 1 of 0: %s (%s)", res.inspect, hi)
  end
  hi = make_mixer(1)
  if (res = Snd.catch do mixer_set!(hi, 1, 0, 1.0) end).first != :mus_error
    snd_display("mixer_set! 1 0 of 0: %s (%s)", res.inspect, hi)
  end
  hi = make_mixer(1)
  if (res = Snd.catch do mixer_set!(hi, 0, 1, 1.0) end).first != :mus_error
    snd_display("mixer_set! 0 1 of 0: %s (%s)", res.inspect, hi)
  end
  hi = make_frame(1)
  if (res = Snd.catch do frame_set!(hi, 1, 1.0) end).first != :mus_error
    snd_display("frame_set! 1 of 0: %s (%s)", res.inspect, hi)
  end
  if (res = Snd.catch do make_frame(0) end).first != :out_of_range
    snd_display("make_frame 0: %s", res.inspect)
  end
  if (res = Snd.catch do make_mixer(0) end).first != :out_of_range
    snd_display("make_mixer 0: %s", res.inspect)
  end
  #
  fr1 = make_frame(1, 1)
  fr2 = make_frame(2, 1, 2)
  fr4 = make_frame(4, 1, 2, 3, 4)
  fr8 = make_frame(8, 1, 2, 3, 4, 5, 6, 7, 8)
  mx1 = make_mixer(1, 5)
  mx1id = make_mixer(1, 1)
  mx2 = make_mixer(2, 1, 2, 3, 4)
  mx2id = make_mixer(2, 1, 0, 0, 1)
  mx4 = make_mixer(4)
  mx4id = make_mixer(4)
  mx8 = make_mixer(8)
  mx8id = make_mixer(8)
  4.times do |i|
    mixer_set!(mx4id, i, i, 1)
    mixer_set!(mx4, 0, i, 1)
  end
  8.times do |i|
    mixer_set!(mx8id, i, i, 1)
    mixer_set!(mx8, i, 0, 1)
  end
  unless (res = frame2frame(fr1, mx1id)).eql?(make_frame(1, 1))
    snd_display("frame2frame 1 id: %s?", res)
  end
  unless (res = frame2frame(fr1, mx1)).eql?(make_frame(1, 5))
    snd_display("frame2frame 1: %s?", res)
  end
  unless (res = frame2frame(fr1, mx2id)).eql?(make_frame(2, 1, 0))
    snd_display("frame2frame 2 1 id: %s?", res)
  end
  unless (res = frame2frame(fr1, mx2)).eql?(make_frame(2, 1, 2))
    snd_display("frame2frame 2 1: %s?", res)
  end
  unless (res = frame2frame(fr1, mx4)).eql?(make_frame(4, 1, 1, 1, 1))
    snd_display("frame2frame 4 1: %s?", res)
  end
  unless (res = frame2frame(fr1, mx8)).eql?(make_frame(8, 1, 0, 0, 0, 0, 0, 0, 0))
    snd_display("frame2frame 8 1: %s?", res)
  end
  unless (res = frame2frame(fr2, mx1)).eql?(make_frame(1, 5))
    snd_display("frame2frame 1 2: %s?", res)
  end
  unless (res = frame2frame(fr2, mx2id)).eql?(make_frame(2, 1, 2))
    snd_display("frame2frame 2 id 2: %s?", res)
  end
  unless (res = frame2frame(fr2, mx2)).eql?(make_frame(2, 7, 10))
    snd_display("frame2frame 2 2: %s?", res)
  end
  unless (res = frame2frame(fr2, mx4id)).eql?(make_frame(4, 1, 2, 0, 0))
    snd_display("frame2frame 4 id 2: %s?", res)
  end
  unless (res = frame2frame(fr2, mx8id)).eql?(make_frame(8, 1, 2, 0, 0, 0, 0, 0, 0))
    snd_display("frame2frame 8 id 2: %s?", res)
  end
  unless (res = frame2frame(fr2, mx4)).eql?(make_frame(4, 1, 1, 1, 1))
    snd_display("frame2frame 4 2: %s?", res)
  end
  unless (res = frame2frame(fr2, mx8)).eql?(make_frame(8, 3, 0, 0, 0, 0, 0, 0, 0))
    snd_display("frame2frame 8 2: %s?", res)
  end
  unless (res = frame2frame(fr4, mx1)).eql?(make_frame(1, 5))
    snd_display("frame2frame 1 4: %s?", res)
  end
  unless (res = frame2frame(fr8, mx1)).eql?(make_frame(1, 5))
    snd_display("frame2frame 1 8: %s?", res)
  end
  unless (res = frame2frame(fr4, mx4)).eql?(make_frame(4, 1, 1, 1, 1))
    snd_display("frame2frame 4 4: %s?", res)
  end
  unless (res = frame2frame(fr4, mx8)).eql?(make_frame(8, 10, 0, 0, 0, 0, 0, 0, 0))
    snd_display("frame2frame 8 4: %s?", res)
  end
  #
  fr1 = make_frame(2)
  fr2 = make_frame(2)
  mx1 = make_mixer(2)
  mx2 = make_mixer(2)
  frame_set!(fr1, 0, 0.1)
  fradd = frame_add(fr1, fr1, fr2)
  unless fr2.eql?(fradd)
    snd_display("frame_add with res frame: %s %s?", fr2, fradd)
  end
  unless fr2.eql?(make_frame(2, 0.2, 0.0))
    snd_display("frame_add res: %s?", fr2)
  end
  fradd = frame_multiply(fr1, fr1, fr2)
  unless fr2.eql?(fradd)
    snd_display("frame_multiply with res frame: %s %s?", fr2, fradd)
  end
  if fneq(frame_ref(fr2, 0), 0.01) or fneq(frame_ref(fr2, 1), 0.0)
    snd_display("frame_multiply res: %s?", fr2)
  end
  mixer_set!(mx1, 0, 0, 0.1)
  mxadd = mixer_multiply(mx1, mx1, mx2)
  unless mx2.eql?(mxadd)
    snd_display("mixer_multiply with res mixer: %s %s?", mx2, mxadd)
  end
  if fneq(mixer_ref(mx2, 0, 0), 0.01)
    snd_display("mixer_multiply res: %s?", mx2)
  end
  #
  [1, 2, 4, 8].each do |chans|
    m1 = make_mixer(chans)
    if m1.channels != chans or m1.length != chans
      snd_display("mixer %s chans but: %s %s?", chans, m1.channels, m1.length)
    end
    chans.times do |i|
      chans.times do |j|
        mixer_set!(m1, i, j, i * 0.01 + j * 0.1)
      end
    end
    chans.times do |i|
      chans.times do |j|
        if fneq(res0 = mixer_ref(m1, i, j), res1 = i * 0.01 + j * 0.1)
          snd_display("mixer[%s %s] = %s (%s)?", i, j, res0, res1)
        end
      end
    end
    mempty = make_mixer(chans)
    midentity = make_mixer(chans)
    mpick = make_mixer(chans)
    chans.times do |i| mixer_set!(midentity, i, i, 1.0) end
    mixer_set!(mpick, chans - 1, chans - 1, 1.0)
    mzero = mixer_multiply(m1, mempty)
    msame = mixer_multiply(m1, midentity)
    mone = mixer_multiply(m1, mpick)
    chans.times do |i|
      chans.times do |j|
        if fneq(res = mixer_ref(mzero, i, j), 0.0)
          snd_display("mzero %s %s = %s?", i, j, res)
        end
        if fneq(res0 = mixer_ref(m1, i, j), res1 = mixer_ref(msame, i, j))
          snd_display("msame %s %s?", res0, res1)
        end
        if fneq(res = mixer_ref(mone, i, j), 0.0) and
            i != chans - 1 and
            j != chans - 1
          snd_display("mone %s %s = %s?", i, j, res)
        end
      end
    end
  end
  #
  mx = make_mixer(4, 4)
  if (res = Snd.catch do mx.length = 2 end).first != :mus_error
    snd_display("set_mixer_length: %s %s", res.inspect, mx.length)
  end
  #
  if fneq(res = mixer_determinant(make_mixer(2, 1, 2, 3, 4)), -2.0)
    snd_display("mixer_determinant -2: %s?", res)
  end
  if fneq(res = mixer_determinant(make_mixer(3, 1, 2, 3, 4, 5, 6, 7, 8, 9)), 0.0)
    snd_display("mixer_determinant 0: %s?", res)
  end
  if fneq(res = mixer_determinant(make_mixer(4, 1, 2, 3, 4, 8, 7, 6, 5, 1, 8, 2, 7, 3, 6, 4, 5)),
          -144.0)
    snd_display("mixer_determinant -144: %s?", res)
  end
  if fneq(res = mixer_determinant(make_mixer(5,  2, 3, 5, 7, 11,  13, 17, 19, 23, 29,
                                             31, 37, 41, 43, 47,  53, 59, 61, 67, 71,
                                             73, 79, 83, 89, 97)), -4656.0)
    snd_display("mixer_determinant -4656: %s?", res)
  end
  if fneq(res = mixer_determinant(make_mixer(6,  2, 3, 5, 7, 11, 13,   17, 19, 23, 29, 31, 37,
                                             41, 43, 47, 53, 59, 61,  67, 71, 73, 79, 83, 89,  
                                             97, 101, 103, 107, 109, 113,
                                             127, 131, 137, 139, 149, 151)), -14304.0)
    snd_display("mixer_determinant -14304: %s?", res)
  end
  unless mixer_equal?(res = mixer_transpose(make_mixer(2, 1, 2, 3, 4)),
                      make_mixer(2, 1.0, 3.0, 2.0, 4.0))
    snd_display("mixer_transpose 1: %s?", res)
  end
  unless mixer_equal?(res = mixer_transpose(make_mixer(3, 1, 2, 3, 4, 5, 6, 7, 8, 9)),
                      make_mixer(3, 1.0, 4.0, 7.0, 2.0, 5.0, 8.0, 3.0, 6.0, 9.0))
    snd_display("mixer_transpose 2: %s?", res)
  end
  unless mixer_equal?(res = mixer_multiply(make_mixer(2, 1, 0, 0, 1), make_mixer(2, 2, 0, 0, 2)),
                      make_mixer(2, 2.0, 0.0, 0.0, 2.0))
    snd_display("mixer_multiply 1: %s?", res)
  end
  unless mixer_equal?(res = mixer_multiply(make_mixer(3, 2, 3, 5, 7, 11, 13, 19, 23, 29),
                                           make_mixer(3, 41, 43, 47, 53, 59, 61, 67, 71, 73)),
                      make_mixer(3, 576, 618, 642, 1741, 1873, 1949, 3941, 4233, 4413))
    snd_display("mixer_multiply 2: %s?", res)
  end
  unless mixer_equal?(res = mixer_inverse(make_mixer(2, 1, 0, 0, 1)), make_mixer(2, 1, 0, 0, 1))
    snd_display("mixer_inverse 1: %s?", res)
  end
  unless mixer_equal?(res = mixer_inverse(make_mixer(2, 2, 3, 5, 8)), make_mixer(2, 8, -3, -5, 2))
    snd_display("mixer_inverse 2: %s?", res)
  end
  unless mixer_equal?(res = mixer_inverse(make_mixer(3,  2, 3, 5,  7, 11, 13,  17, 19, 23)),
                      make_mixer(3, -0.077, -0.333, 0.205, -0.769, 0.5, -0.115,
                                 0.692, -0.167, -0.013))
    snd_display("mixer_inverse 3: %s?", res)
  end
  unless mixer_equal?(res = mixer_inverse(make_mixer(4,  2, 3, 5, 7,  17, 19, 23, 29,
                                                     41, 43, 47, 53,  67, 71, 73, 97)),
                      make_mixer(4, -7, 4.708, -1.042, -0.333, 9, -6.396, 1.396, 0.5, 
                                 -1, 0.875, -0.042, -0.167, -1, 0.771, -0.271, 0))
    snd_display("mixer_inverse 4: %s?", res)
  end
  unless mixer_equal?(res = mixer_inverse(make_mixer(6,  2, 3, 5, 7, 11, 13,
                                                     17, -19, 23, 29, 31, 37,
                                                     41, 43, 47, 53, 59, 61,
                                                     67, 71, 73, 79, 83, 89,
                                                     97, 101, 103, 107, 109, 113,
                                                     127, 131, 137, 139, 149, 151)), 
                      make_mixer(6, -1.355, 0.02, -0, 1.09, -1.153, 0.333, 0.092,
                                -0.025, 0, -0.042, 0.07, -0.029, 1.612,
                                 0.006, -0.25, -1.205, 1.249, -0.264,
                                 0.079, 0.002, 0.25, -0.314, 0.425, -0.241,
                                 -0.551, -0.011, 0.25, 0.2, -0.476, 0.188,
                                 0.068, 0.009, -0.25, 0.306, -0.145, 0.028))
    snd_display("mixer_inverse 5: %s?", res)
  end
  unless mixer_equal?(res = mixer_multiply(make_mixer(2, 2, 3, 5, 8),
                                           mixer_inverse(make_mixer(2, 2, 3, 5, 8))),
                      make_scalar_mixer(2, 1.0))
    snd_display("mixer_inverse 6: %s?", res)
  end
  unless mixer_equal?(res = mixer_multiply(make_mixer(3, 2, 3, 5, 7, 11, 13, 17, 19, 23),
                                           mixer_inverse(make_mixer(3, 2, 3, 5,
                                                                    7, 11, 13,
                                                                    17, 19, 23))),
                      make_scalar_mixer(3, 1.0))
    snd_display("mixer_inverse 7: %s?", res)
  end
  unless mixer_diagonal?(make_scalar_mixer(2, 2.0))
    snd_display("mixer_diagonal 1")
  end
  unless mixer_diagonal?(make_mixer(3, 1, 0, 0, 0, 1, 0, 0, 0, 1))
    snd_display("mixer_diagonal 2")
  end
  if mixer_diagonal?(make_mixer(3, 1, 0, 0, 0, 1, 1, 0, 0, 1))
    snd_display("mixer_diagonal 3")
  end
  unless mixer_diagonal?(make_mixer(3, 0, 0, 0, 0, 1, 0, 0, 0, 1))
    snd_display("mixer_diagonal 4")
  end
  unless mixer_symmetric?(make_mixer(3, 0, 0, 0, 0, 1, 0, 0, 0, 1))
    snd_display("mixer_symmetric 1")
  end
  unless mixer_symmetric?(make_mixer(3, 1, 2, 0, 2, 1, 0, 0, 0, 1))
    snd_display("mixer_symmetric 2")
  end
  if mixer_symmetric?(make_mixer(3, 1, 2, 0, 2, 1, 0, 0, 2, 1))
    snd_display("mixer_symmetric 3")
  end
  unless mixer_equal?(make_scalar_mixer(2, 2.0), make_mixer(2, 2.0, 0, 0, 2.0))
    snd_display("mixer_equal? 1")
  end
  if mixer_equal?(make_mixer(2, 1, 2, 3, 4), make_mixer(3, 1, 2, 3, 4, 5, 6, 7, 8, 9))
    snd_display("mixer_equal? 2")
  end
  if mixer_equal?(make_mixer(2, 1, 2, 3, 4), make_mixer(2, 1, 2, 3, 5))
    snd_display("mixer_equal? 3")
  end
  unless mixer_equal?(res = mixer_poly(make_mixer(2, 1, 0, 0, 1), 1, 1), make_mixer(2, 2, 0, 0, 2))
    snd_display("mixer_poly 1: %s?", res)
  end
  unless mixer_equal?(res = mixer_poly(make_mixer(1, 1), 1), make_mixer(1, 1))
    snd_display("mixer_poly 2: %s?", res)
  end
  unless mixer_equal?(res = mixer_poly(make_mixer(2, 1, 0, 0, 1), 1, 0, 0),
                      make_mixer(2, 1, 0, 0, 1))
    snd_display("mixer_poly 3: %s?", res)
  end
  unless mixer_equal?(res = mixer_poly(make_mixer(2, 1, 2, 4, 3), 1, 0, 0), 
                      make_mixer(2, 9, 8, 16, 17))
    snd_display("mixer_poly 4: %s?", res)
  end
  unless mixer_equal?(res = mixer_poly(make_mixer(2, 1, 2, 4, 3), 1, 1, 0), 
                      make_mixer(2, 10, 10, 20, 20))
    snd_display("mixer_poly 5: %s?", res)
  end
  unless mixer_equal?(res = mixer_poly(make_mixer(2, 1, 2, 4, 3), 1, 1, 2), 
                      make_mixer(2, 12, 10, 20, 22))
    snd_display("mixer_poly 6: %s?", res)
  end
  unless mixer_equal?(res = mixer_poly(make_mixer(2, 1, 2, 4, 3), 1, 0, 0, 0), 
                      make_mixer(2, 41, 42, 84, 83))
    snd_display("mixer_poly 7: %s?", res)
  end
  unless mixer_equal?(res = mixer_poly(make_mixer(2, 1, 2, 4, 3), 1, 0, 1, 0), 
                      make_mixer(2, 42, 44, 88, 86))
    snd_display("mixer_poly 8: %s?", res)
  end
end

def test_08_08
  # 
  # try random input to mixer_inverse
  #
  (2...20).each do |k|
    mx = make_random_mixer(k)
    imx = mixer_inverse(mixer_copy(mx))
    mmx = mixer_multiply(mx, imx)
    unless mixer_equal?(mmx, make_scalar_mixer(k, 1.0))
      snd_display("mixer_inverse %s: %s * %s -> %s?", k, mx, imx, mmx)
    end
  end
  unless frame_equal?(res = frame_reverse(make_frame(2, 0.5, 2.0)),
                      make_frame(2, 2.0, 0.5))
    snd_display("frame_reverse 2: %s?", res)
  end
  unless frame_equal?(res = frame_reverse(make_frame(3, 0.5, 1.0, 2.0)),
                      make_frame(3, 2.0, 1.0, 0.5))
    snd_display("frame_reverse 3: %s?", res)
  end
  #
  hi = make_mixer(3, 10, 5, 1, 1, 20, 5, 1, 3, 7)
  ho = make_mixer(3, 10, 5, 2, 1, 3, 2, 1, 3, 2)
  unless mixer_equal?(res = mixer_multiply(hi, ho),
                      make_mixer(3, 106, 68, 32, 35, 80, 52, 20, 35, 22))
    snd_display("mixer_multiply 3x3 1: %s?", res)
  end
  unless mixer_equal?(res = mixer_multiply(hi, mixer_transpose(ho)),
                      make_mixer(3, 127, 27, 27, 120, 71, 71, 39, 24, 24))
    snd_display("mixer_multiply 3x3 2: %s?", res)
  end
  unless mixer_equal?(res = mixer_multiply(mixer_transpose(hi), mixer_transpose(ho)),
                      make_mixer(3, 107, 15, 15, 156, 71, 71, 49, 30, 30))
    snd_display("mixer_multiply 3x3 3: %s?", res)
  end
  unless frame_equal?(res = mixer_solve(make_mixer(2, 0.001, 1, 1, 2), make_frame(2, 1, 3)),
                      make_frame(2, 1.002, 0.999))
    snd_display("mixer_solve G1: %s?", res)
  end
  unless frame_equal?(res = mixer_solve(make_mixer(2, 0.0001, 1, 1, 1), make_frame(2, 1, 3)),
                      make_frame(2, 2, 1))
    snd_display("mixer_solve G2: %s?", res)
  end
  unless frame_equal?(res = mixer_solve(make_mixer(2, 0.986, 0.579, 0.409, 0.237),
                                        make_frame(2, 0.235, 0.107)),
                      make_frame(2, 2, -3))
    snd_display("mixer_solve G3: %s?", res)
  end
  # G4, G5 (invert_matrix) skipped
  unless frame_equal?(res = mixer_solve(make_mixer(3, 1, 4, 7, 2, 5, 8, 3, 6, 10),
                                        make_frame(3, 1, 1, 1)),
                      make_frame(3, -0.333, 0.333, 0))
    snd_display("mixer_solve G6: %s?", res)
  end
  unless frame_equal?(res = mixer_solve(make_mixer(2, 1, 0, 0, 1.0e-6), make_frame(2, 1, 1.0e-6)),
                      make_frame(2, 1, 1))
    snd_display("mixer_solve G7: %s?", res)
  end
  # G8, G9 (invert_matrix) skipped
  unless frame_equal?(res = mixer_solve(make_mixer(2, 10, 100000, 1, 1), make_frame(2, 100000, 2)),
                      make_frame(2, 1, 1))
    snd_display("mixer_solve G10: %s?", res)
  end
  # 
  [[:Hamming_window, 0.0, vct(0.080, 0.115, 0.215, 0.364, 0.540, 0.716, 0.865, 1.000,
                              1.000, 0.865, 0.716, 0.540, 0.364, 0.215, 0.115, 0.080)],
   [:Rectangular_window, 0.0, vct(1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
                                  1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000)],
   [:Hann_window, 0.0, vct(0.000, 0.038, 0.146, 0.309, 0.500, 0.691, 0.854, 1.000,
                           1.000, 0.854, 0.691, 0.500, 0.309, 0.146, 0.038, 0.000)],
   [:Welch_window, 0.0, vct(0.000, 0.234, 0.438, 0.609, 0.750, 0.859, 0.938, 1.000,
                            1.000, 0.938, 0.859, 0.750, 0.609, 0.438, 0.234, 0.000)],
   [:Connes_window, 0.0, vct(0.000, 0.055, 0.191, 0.371, 0.562, 0.739, 0.879, 1.000,
                             1.000, 0.879, 0.739, 0.562, 0.371, 0.191, 0.055, 0.000)],
   [:Parzen_window, 0.0, vct(0.000, 0.125, 0.250, 0.375, 0.500, 0.625, 0.750, 1.000,
                             1.000, 0.750, 0.625, 0.500, 0.375, 0.250, 0.125, 0.000)],
   [:Bartlett_window, 0.0, vct(0.000, 0.125, 0.250, 0.375, 0.500, 0.625, 0.750, 1.000,
                               1.000, 0.750, 0.625, 0.500, 0.375, 0.250, 0.125, 0.000)],
   [:Blackman2_window, 0.0, vct(0.005, 0.020, 0.071, 0.177, 0.344, 0.558, 0.775, 1.000,
                                1.000, 0.775, 0.558, 0.344, 0.177, 0.071, 0.020, 0.005)],
   [:Blackman3_window, 0.0, vct(0.000, 0.003, 0.022, 0.083, 0.217, 0.435, 0.696, 1.000,
                                1.000, 0.696, 0.435, 0.217, 0.083, 0.022, 0.003, 0.000)],
   [:Blackman4_window, 0.0, vct(0.002, 0.002, 0.003, 0.017, 0.084, 0.263, 0.562, 1.000,
                                1.000, 0.562, 0.263, 0.084, 0.017, 0.003, 0.002, 0.002)],
   [:Blackman5_window, 0.0, vct(0.000, 0.000, 0.003, 0.022, 0.097, 0.280, 0.574, 1.000,
                                1.000, 0.574, 0.280, 0.097, 0.022, 0.003, 0.000, 0.000)],
   [:Blackman6_window, 0.0, vct(0.000, 0.000, 0.001, 0.011, 0.064, 0.223, 0.520, 1.000,
                                1.000, 0.520, 0.223, 0.064, 0.011, 0.001, 0.000, 0.000)],
   [:Blackman7_window, 0.0, vct(0.000, 0.000, 0.000, 0.006, 0.042, 0.177, 0.471, 1.000,
                                1.000, 0.471, 0.177, 0.042, 0.006, 0.000, 0.000, 0.000)],
   [:Blackman8_window, 0.0, vct(0.000, 0.000, 0.000, 0.003, 0.028, 0.141, 0.426, 1.000,
                                1.000, 0.426, 0.141, 0.028, 0.003, 0.000, 0.000, 0.000)],
   [:Blackman9_window, 0.0, vct(0.000, 0.000, 0.000, 0.001, 0.018, 0.112, 0.385, 1.000,
                                1.000, 0.385, 0.112, 0.018, 0.001, 0.000, 0.000, 0.000)],
   [:Blackman10_window, 0.0, vct(0.000, 0.000, 0.000, 0.001, 0.012, 0.089, 0.349, 1.000,
                                1.000, 0.349, 0.089, 0.012, 0.001, 0.000, 0.000, 0.000)],
   [:Rv2_window, 0.0, vct(0.000, 0.001, 0.021, 0.095, 0.250, 0.478, 0.729, 1.000,
                                1.000, 0.729, 0.478, 0.250, 0.095, 0.021, 0.001, 0.000)],
   [:Rv3_window, 0.0, vct(0.000, 0.000, 0.003, 0.029, 0.125, 0.330, 0.622, 1.000,
                                1.000, 0.622, 0.330, 0.125, 0.029, 0.003, 0.000, 0.000)],
   [:Rv4_window, 0.0, vct(0.000, 0.000, 0.000, 0.009, 0.062, 0.228, 0.531, 1.000,
                                1.000, 0.531, 0.228, 0.062, 0.009, 0.000, 0.000, 0.000)],
   [:Exponential_window, 0.0, vct(0.000, 0.087, 0.181, 0.283, 0.394, 0.515, 0.646, 0.944,
                                  0.944, 0.646, 0.515, 0.394, 0.283, 0.181, 0.087, 0.000)],
   [:Riemann_window, 0.0, vct(0.000, 0.139, 0.300, 0.471, 0.637, 0.784, 0.900, 1.000,
                              1.000, 0.900, 0.784, 0.637, 0.471, 0.300, 0.139, 0.000)],
   [:Kaiser_window, 2.5, vct(0.304, 0.426, 0.550, 0.670, 0.779, 0.871, 0.941, 1.000,
                             1.000, 0.941, 0.871, 0.779, 0.670, 0.550, 0.426, 0.304)],
   [:Cauchy_window, 2.5, vct(0.138, 0.173, 0.221, 0.291, 0.390, 0.532, 0.719, 1.000,
                             1.000, 0.719, 0.532, 0.390, 0.291, 0.221, 0.173, 0.138)],
   [:Poisson_window, 2.5, vct(0.082, 0.112, 0.153, 0.210, 0.287, 0.392, 0.535, 1.000,
                              1.000, 0.535, 0.392, 0.287, 0.210, 0.153, 0.112, 0.082)],
   [:Gaussian_window, 1.0, vct(0.607, 0.682, 0.755, 0.823, 0.882, 0.932, 0.969, 1.000,
                               1.000, 0.969, 0.932, 0.882, 0.823, 0.755, 0.682, 0.607)],
   [:Tukey_window, 0.0, vct(0.000, 0.038, 0.146, 0.309, 0.500, 0.691, 0.854, 1.000,
                            1.000, 0.854, 0.691, 0.500, 0.309, 0.146, 0.038, 0.000)],
   [:Hann_poisson_window, 0.0, vct(0.000, 0.038, 0.146, 0.309, 0.500, 0.691, 0.854, 1.000,
                                   1.000, 0.854, 0.691, 0.500, 0.309, 0.146, 0.038, 0.000)],
   [:Samaraki_window, 0.0, vct(1.000, 0.531, 0.559, 0.583, 0.604, 0.620, 0.631, 0.638,
                               0.640, 0.638, 0.631, 0.620, 0.604, 0.583, 0.559, 0.531)],
   [:Ultraspherical_window, 0.0, vct(1.000, 0.033, 0.034, 0.035, 0.036, 0.036, 0.037, 0.037,
                                     0.037, 0.037, 0.037, 0.036, 0.036, 0.035, 0.034, 0.033)],
   [:Dolph_chebyshev_window, 0.0, vct(1.000, 0.033, 0.034, 0.035, 0.036, 0.036, 0.037, 0.037,
                                      0.037, 0.037, 0.037, 0.036, 0.036, 0.035, 0.034, 0.033)],
   [:Dolph_chebyshev_window, 1.0, vct(1.000, 0.274, 0.334, 0.393, 0.446, 0.491, 0.525, 0.546,
                                      0.553, 0.546, 0.525, 0.491, 0.446, 0.393, 0.334, 0.274)]
  ].each do |win, beta, vals|
    Snd.catch do
      res = make_fft_window(Module.const_get(win), 16, beta)
      unless vequal(res, vals)
        snd_display("%s: %s?", win, res)
      end
    end
  end
  [[:Ultraspherical_window, 0.0, 0.0, :Dolph_chebyshev_window, 0.0, 0.0],
   [:Ultraspherical_window, 0.0, 1.0, :Samaraki_window,        0.0, 0.0],
   [:Ultraspherical_window, 0.5, 0.0, :Dolph_chebyshev_window, 0.5, 0.0],
   [:Ultraspherical_window, 0.5, 1.0, :Samaraki_window,        0.5, 0.0]
  ].each do |win1, beta1, alpha1, win2, beta2, alpha2|
    Snd.catch do
      val1 = make_fft_window(Module.const_get(win1), 16, beta1, alpha1)
      val2 = make_fft_window(Module.const_get(win2), 16, beta2, alpha2)
      unless vequal(val1, vals2)
        snd_display("%s/%s %s: %s %s?", win1, win2, beta1, val1, val2)
      end
    end
  end
  val1 = dolph(16, 1.0)
  val2 = make_fft_window(Dolph_chebyshev_window, 16, 1.0)
  unless vequal(val1, val2)
    snd_display("dolph/dolph 1: %s %s?", val1, val2)
  end
  val1 = dolph_1(16, 1.0).to_vct
  val2 = make_fft_window(Dolph_chebyshev_window, 16, 1.0)
  unless vequal(val1, val2)
    snd_display("dolph_1/dolph 1: %s %s?", val1, val2)
  end
  #
  gen = make_env(:envelope, [0, 0, 1, 1, 2, 0], :scaler, 0.5, :length, 11)
  gen1 = make_env(:envelope, [0, 0, 1, 1, 2, 0], :scaler, 0.5, :length, 11)
  print_and_check(gen,
                  "env",
                  "env linear, pass: 0 (dur: 11), index: 0, scaler: 0.5000, offset: 0.0000, data: [0.000 0.000 1.000 1.000 2.000 0.000]")
  unless env?(gen)
    snd_display("%s not env?", gen)
  end
  if fneq(gen.scaler, 0.5)
    snd_display("env scaler: %s?", gen.scaler)
  end
  if fneq(gen.increment, 1.0)
    snd_display("env base (1.0): %s?", gen.increment)
  end
  if gen.length != 11
    snd_display("env length: %s?", gen.length)
  end
  v0 = make_vct!(10) do env(gen) end
  v1 = make_vct(10)
  off = 123.0
  vct_map!(v1, lambda do | |
             off = gen1.offset
             env?(gen1) ? env(gen1) : -1.0
           end)
  if fneq(off, 0.0)
    snd_display("mus_offset opt: %s?", off)
  end
  unless vequal(v0, v1)
    snd_display("map env: %s %s?", v0, v1)
  end
  if fneq(v0[0], 0.0) or fneq(v0[1], 0.1) or fneq(v0[6], 0.4)
    snd_display("env output: %s?", v0)
  end
  if fneq(res = env_interp(1.6, gen), 0.2)
    snd_display("env_interp %s at 1.6: %s?", gen, res)
  end
  gen = make_env(:envelope, [0, 1, 1, 0], :base, 32.0, :length, 11)
  if fneq(gen.increment, 32.0)
    snd_display("env base (32.0): %s?", gen.increment)
  end
  v0.map! do |val| env(gen) end
  if fneq(v0[0], 1.0) or fneq(v0[1], 0.698) or fneq(v0[8], 0.032)
    snd_display("%s output: %s?", gen, v0)
  end
  gen = make_env(:envelope, [0, 1, 1, 0], :base, 0.0325, :length, 11)
  if fneq(gen.increment, 0.0325)
    snd_display("env base (0.0325): %s?", gen.increment)
  end
  v0.map! do |val| env(gen) end
  if fneq(v0[0], 1.0) or fneq(v0[1], 0.986) or fneq(v0[8], 0.513)
    snd_display("%s output: %s?", gen, v0)
  end
  gen = make_env(:envelope, [0, 1, 1, 0.5, 2, 0], :base, 0.0, :length, 11, :offset, 1.0)
  if fneq(gen.offset, 1.0)
    snd_display("mus_offset: %s?", gen.offset)
  end
  if fneq(gen.increment, 0.0)
    snd_display("env base (0.0): %s?", gen.increment)
  end
  v0.map_with_index! do |val, i|
    if i == 3
      if gen.location != 3
        snd_display("env location: %s?", gen.location)
      end
    end
    env(gen)
  end
  if fneq(v0[0], 2.0) or fneq(v0[6], 1.5) or fneq(v0[8], 1.5)
    snd_display("%s output: %s?", gen, v0)
  end
  if fneq(res = env_interp(1.5, gen), 1.5)
    snd_display("env_interp %s at 1.5: %s?", gen, res)
  end
  gen.location = 6
  if gen.location != 6
    snd_display("set_mus_location (6): %s?", gen.location)
  end
  if fneq(val = env(gen), 1.5)
    snd_display("set_mus_location 6 -> %s (1.5)?", val)
  end
  gen.location = 0
  if fneq(val = env(gen), 2.0)
    snd_display("set_mus_location 0 -> %s (2.0)?", val)
  end
  gen = make_env([0, 0, 1, -1, 2, 0], :length, 11)
  5.times do |i|
    if fneq(val = env(gen), i / -5.0)
      snd_display("neg env: %s %s?", i, val)
    end
  end
  5.times do |i|
    if fneq(val = env(gen), -1.0 + i / 5.0)
      snd_display("neg env: %s %s?", i, val)
    end
  end
  gen = make_env([0, 0, 1, -1, 2, 0], :length, 11, :base, 0.5)
  vct(0.0, -0.14869, -0.31950, -0.51571, -0.74110,
      -1.0, -0.74110, -0.51571, -0.31950, -0.14869).each_with_index do |val, i|
    if fneq(res = env(gen), val)
      snd_display("neg exp env: %s %s?", i, res)
    end
  end
  mus_apply(gen)
  e = make_env([0, 0, 1, 1], :length, 10)
  if fneq(res = env_interp(1.0, e), 1.0)
    snd_display("env_interp 0011 at 1: %s?", res)
  end
  if fneq(res = env_interp(2.0, e), 1.0)
    snd_display("env_interp 0011 at 2: %s?", res)
  end
  if fneq(res = env_interp(0.0, e), 0.0)
    snd_display("env_interp 0011 at 0: %s?", res)
  end
  if fneq(res = env_interp(0.444, e), 0.444)
    snd_display("env_interp 0011 at 0.444: %s?", res)
  end
  e.reset
  10.times do |i|
    if fneq(val = env(e), i * 0.111111)
      snd_display("ramp env over 10: %s at %s?", val, i)
    end
  end
  e = make_env([0, 0, 0.5, 0.5, 1, 1], :base, 32, :length, 10)
  x = 0.0
  vct(0, 0.0243, 0.0667, 0.1412, 0.2716, 0.5, 0.5958, 0.709, 0.8425, 1).each_with_index do |val, i|
    if fneq(res = env_interp(x, e), val)
      snd_display("[0, 0.5, 1] env_interp over 10: %s at %s (%s)", res, i, val)
    end
    x += 0.111111
  end
  e = make_env([0, -1.0, 1, 1], :base, 32, :length, 10)
  x = 0.0
  vct(-1.0, -0.9697, -0.9252, -0.8597, -0.7635,
      -0.6221, -0.4142, -0.1088, 0.34017, 1.0).each_with_index do |val, i|
    if fneq(res = env_interp(x, e), val)
      snd_display("[-1, 1] env_interp over 10: %s at %s (%s)", res, i, val)
    end
    x += 0.111111
  end
  e = make_env([0, -1.0, 0.5, 0.5, 1, 0], :base, 32, :length, 10)
  x = 0.0
  vct(-1.0, -0.952, -0.855, -0.661, -0.274,
      0.5, 0.356, 0.226, 0.107, 0.0).each_with_index do |val, i|
    if fneq(res = env_interp(x, e), val)
      snd_display("[-1, 0.5, 0] env_interp over 10: %s at %s (%s)", res, i, val)
    end
    x += 0.111111
  end
  e = make_env([0, 0.0, 0.5, 0.5, 1, -1.0], :base, 32, :length, 10)
  x = 0.0
  vct(0, 0.085, 0.177, 0.276, 0.384, 0.5, -0.397, -0.775, -0.933, -1).each_with_index do |val, i|
    if fneq(res = env_interp(x, e), val)
      snd_display("[0, 0.5, -1] env_interp over 10: %s at %s (%s)", res, i, val)
    end
    x += 0.111111
  end
  #
  e = make_env([0, 0, 1, 1], :length, 10, :base, 4)
  if fneq(res = env_interp(1.0, e), 1.0)
    snd_display("env_interp 0011 4 at 1: %s?", res)
  end
  if fneq(res = env_interp(0.0, e), 0.0)
    snd_display("env_interp 0011 4 at 0: %s?", res)
  end
  if fneq(res = env_interp(0.45, e), 0.2839)
    snd_display("env_interp 0011 4 at 0.45: %s?", res)
  end
  e = make_env([0, 0, 1, 1], :length, 10, :base, 0.2)
  if fneq(res = env_interp(1.0, e), 1.0)
    snd_display("env_interp 0011 2 at 1: %s?", res)
  end
  if fneq(res = env_interp(0.0, e), 0.0)
    snd_display("env_interp 0011 2 at 0: %s?", res)
  end
  if fneq(res = env_interp(0.45, e), 0.6387)
    snd_display("env_interp 0011 2 at 0.45: %s?", res)
  end
  e = make_env([0, 0, 1, 1], :offset, 2.0)
  e.offset = 3.0
  if fneq(e.offset, 3.0)
    snd_display("set_mus_offset env: %s?", e.offset)
  end
  #
  e1 = make_env([0, 0, 1, 1], :base, 32.0, :length, 11)
  vct(0, 0.013, 0.032, 0.059, 0.097, 0.150, 0.226, 0.333, 0.484, 0.698, 1).each do |val|
    if fneq(res = env(e1), val)
      snd_display("exp env direct (32.0): %s %s", res, val)
    end
  end
  e1 = make_env([0, 1, 1, 2], :base, 32.0, :length, 11)
  vct(1, 1.013, 1.032, 1.059, 1.097, 1.15, 1.226, 1.333, 1.484, 1.698, 2).each do |val|
    if fneq(res = env(e1), val)
      snd_display("exp env direct (32.0) offset: %s %s", res, val)
    end
  end
  e1 = make_env([0, 1, 1, 2], :base, 32.0, :length, 11)
  vct(1, 1.013, 1.032, 1.059, 1.097, 1.15, 1.226, 1.333, 1.484, 1.698, 2).each do |val|
    if fneq(res = env(e1), val)
      snd_display("exp env direct (32.0) offset (and dur): %s %s", res, val)
    end
  end
  e1 = make_env([0, 0, 1, 1], :base, 0.032, :length, 11)
  vct(0.000, 0.301, 0.514, 0.665, 0.772, 0.848, 0.902, 0.940, 0.967, 0.986, 1.0).each do |val|
    if fneq(res = env(e1), val)
      snd_display("exp env direct (0.032): %s %s", res, val)
    end
  end
  #
  e1 = make_env([0, 0, 1, 1], :base, 0.03125, :length, 11)
  e2 = make_env([0, 0, 1, 1, 2, 0], :base, 32.0, :length, 11)
  e3 = make_env([0, 0, 0.1, 1, 2, 0], :base, 1.1, :length, 101)
  10.times do |i|
    lv1 = env_interp(i * 0.1, e1)
    lv2 = env(e1)
    lv3 = env_interp(i * 0.2, e2)
    lv4 = env(e2)
    if fneq(lv1, lv2)
      snd_display("env_interp[rmp %s]: %s (%s)?", i * 0.1, lv1, lv2)
    end
    if fneq(lv3, lv4)
      snd_display("env_interp[pyr %s]: %s (%s)?", i * 0.2, lv3, lv4)
    end
  end
  100.times do |i|
    lv5 = env_interp(i * 0.02, e3)
    lv6 = env(e3)
    if fneq(lv5, lv6)
      snd_display("env_interp[tri %s]: %s (%s)?", i * 0.02, lv5, lv6)
    end
  end
  #
  e1 = make_env([0, 0, 1, 1, 2, 0], :length, 10)
  lv1 = make_vct!(11) do env(e1) end
  lv2 = make_vct!(11) do env(e1) end
  e1.reset
  lv3 = make_vct!(11) do env(e1) end
  unless vequal(lv1, lv3)
    snd_display("mus_reset: %s %s?", lv1, lv3)
  end
  unless vequal(lv2, make_vct(11))
    snd_display("mus_reset 1: %s?", lv2)
  end
  #
  gen = make_env([0, 0, 1, 1, 2, 0], :length, 11)
  4.times do env(gen) end
  if fneq(res = env(gen), 0.8)
    snd_display("env(5): %s?", res)
  end
  gen.reset
  4.times do env(gen) end
  if fneq(res = env(gen), 0.8)
    snd_display("mus_reset (via reset): %s?", res)
  end
  gen.location = 6
  if fneq(res = env(gen), 0.8)
    snd_display("set_mus_location 6 -> %s (0.8)?", res)
  end
  gen = make_env([0, 0, 1, 1], :base, 0.032, :length, 12)
  gen.location = 5
  if fneq(res = env(gen), 0.817)
    snd_display("set env location with base: %s %s?", res, gen)
  end
  gen = make_env([0, 0, 1, 1], :base, 0.032, :length, 12)
  gen.location = 5
  if fneq(res = env(gen), 0.817)
    snd_display("set env location with base and dur: %s %s?", res, gen)
  end
  #
  test_gen_equal(make_env([0, 0, 1, 1, 2, 0], :scaler, 0.50, :length,  10),
                 make_env([0, 0, 1, 1, 2, 0], :scaler, 0.50, :length,  10),
                 make_env([0, 0, 1, 1, 2, 0], :scaler, 0.25, :length,  10))
  test_gen_equal(make_env([0, 0, 1, 1, 2, 0], :scaler, 0.50, :length,  10),
                 make_env([0, 0, 1, 1, 2, 0], :scaler, 0.50, :length,  10),
                 make_env([0, 0, 1, 1, 2, 0], :scaler, 0.50, :length,  11))
  test_gen_equal(make_env([0, 0, 1, 1, 2, 0], :scaler, 0.50, :length,  10),
                 make_env([0, 0, 1, 1, 2, 0], :scaler, 0.50, :length,  10),
                 make_env([0, 0, 1, 1, 3, 0], :scaler, 0.50, :length,  10))
  #
  if (res = Snd.catch do make_env(:envelope, []) end).first != :no_data
    snd_display("make_env null env: %s", res.inspect)
  end
  if (res = Snd.catch do make_env(:length, 0) end).first != :no_data
    snd_display("make_env no env: %s", res.inspect)
  end
  if (res = Snd.catch do make_env(:envelope, [0, 0], :length, -1) end).first != :out_of_range
    snd_display("make_env bad end: %s", res.inspect)
  end
  if (res = Snd.catch do make_env(:envelope, [0, 0], :length, -1) end).first != :out_of_range
    snd_display("make_env bad dur: %s", res.inspect)
  end
  if (res = Snd.catch do make_env(:envelope, [0, 0], :duration, -1.0) end).first != :out_of_range
    snd_display("make_env bad duration: %s", res.inspect)
  end
  if (res = Snd.catch do make_env(:envelope, [0, 0], :base, -1.0) end).first != :out_of_range
    snd_display("make_env bad base: %s", res.inspect)
  end
  if (res = Snd.catch do make_env(:envelope, [1, 1, 0, 0], :length, 11) end).first != :mus_error
    snd_display("make_env bad env 1 1 0 0: %s", res.inspect)
  end
  if (res = Snd.catch do make_env(:envelope, [0, 1, -1, 0], :length, 11) end).first != :mus_error
    snd_display("make_env bad env 0 1 -1 0: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_env(:envelope, [0, 1, 1, 0], :length, 11, :length, 10)
      end).first != :mus_error
    snd_display("make_env bad end/dur: %s", res.inspect)
  end
end

def test_08_09
  gen = make_table_lookup(440.0, :wave, partials2wave([1, 1, 2, 1]))
  gen1 = make_table_lookup(440.0, :wave, partials2wave([1, 1, 2, 1], make_vct(512)))
  gen3 = make_table_lookup
  gen4 = make_table_lookup(440.0, :wave, partials2wave([1, 1, 2, 1]))
  print_and_check(gen,
                  "table-lookup",
                  "table-lookup freq: 440.000Hz, phase: 0.000, length: 512, interp: linear")
  if gen.length != 512
    snd_display("table_lookup length: %s?", gen.length)
  end
  if gen3.length != 512
    snd_display("default table_lookup length: %s?", gen3.length)
  end
  v0 = make_vct!(10) do table_lookup(gen, 0.0) end
  v1 = make_vct!(10) do mus_apply(gen1, 0.0) end
  v2 = make_vct(10)
  vct_map!(v2, lambda do | | table_lookup?(gen4) ? table_lookup(gen4) : -1.0 end)
  unless vequal(v0, v2)
    snd_display("map table_lookup: %s %s?", v0, v2)
  end
  gen4 = make_table_lookup(440.0, :wave, partials2wave([1, 1, 2, 1]))
  vct_map!(v2, lambda do | | table_lookup(gen4) end)
  unless vequal(v0, v2)
    snd_display("map table_lookup (no fm): %s %s?", v0, v2)
  end
  unless table_lookup?(gen)
    snd_display("%s not table_lookup?", gen)
  end
  unless vct?(gen.data)
    snd_display("mus_data table_lookup: %s?", gen.data)
  end
  if fneq(gen.phase, 1.253787)
    snd_display("table_lookup phase: %s?", gen.phase)
  end
  gen.phase = 1.0
  if fneq(gen.phase, 1.0)
    snd_display("table_lookup set_phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("table_lookup frequency: %s?", gen.frequency)
  end
  gen.frequency = 100.0
  if fneq(gen.frequency, 100.0)
    snd_display("table_lookup set_frequency: %s?", gen.frequency)
  end
  if fneq(v0[1], 0.373) or fneq(v0[8], 1.75)
    snd_display("table_lookup output: %s?", v0)
  end
  unless vequal(v0, v1)
    snd_display("mus_apply table_lookup: %s %s?", v0, v1)
  end
  gen = make_table_lookup(440.0, :wave, phase_partials2wave([1, 1, 0, 2, 1, HALF_PI]))
  v0.map! do |val| table_lookup(gen, 0.0) end
  if fneq(v0[1], 1.094) or fneq(v0[8], 0.421)
    snd_display("table_lookup phase output: %s?", v0)
  end
  if fneq(vct_peak(partials2wave([1, 1, 2, 1])), 1.76035475730896) or
      fneq(vct_peak(partials2wave([1, 1, 2, 1], false, true)), 1.0) or
      fneq(vct_peak(partials2wave([1, 1, 2, 1, 3, 1, 4, 1], false, true)), 1.0)
    snd_display("normalized partials?")
  end
  gen.data = phase_partials2wave([1, 1, 0, 2, 1, HALF_PI], false, true)
  #
  if (res = Snd.catch do phase_partials2wave([1, 0.3, 2, 0.2]) end).first != :wrong_type_arg
    snd_display("bad length arg to phase_partials2wave: %s", res.inspect)
  end
  if (res = Snd.catch do phase_partials2wave(["hiho", 0.3, 2, 0.2]) end).first != :wrong_type_arg
    snd_display("bad harmonic arg to phase_partials2wave: %s", res.inspect)
  end
  if (res = Snd.catch do phase_partials2wave([]) end).first != :no_data
    snd_display("nil list to phase_partials2wave: %s", res.inspect)
  end
  phase_partials2wave([1, 1, 0], make_vct(16), false).each_with_index do |val, i|
    if fneq(val, res = sin((TWO_PI * i) / 16.0))
      snd_display("phase_partials2wave 1 1 0 at %s: %s %s?", i, val, res)
    end
  end
  phase_partials2wave([1, 1, 0.25 * PI], make_vct(16), false).each_with_index do |val, i|
    if fneq(val, res = sin(0.25 * PI +  ((TWO_PI * i) / 16.0)))
      snd_display("phase_partials2wave 1 1 0.25 at %s: %s %s?", i, val, res)
    end
  end
  phase_partials2wave([1, 1, 0, 2, 1, 0], make_vct(16), false).each_with_index do |val, i|
    if fneq(val, res = sin((TWO_PI * i) / 16.0) +  sin((2.0 * TWO_PI * i) / 16.0))
      snd_display("phase_partials2wave 1 1 0 2 1 0 at %s: %s %s?", i, val, res)
    end
  end
  phase_partials2wave([1, 1, 0, 2, 1, HALF_PI], make_vct(16), false).each_with_index do |val, i|
    if fneq(val, res = sin((TWO_PI * i) / 16.0) +  sin(HALF_PI + ((2.0 * TWO_PI * i) / 16.0)))
      snd_display("phase_partials2wave 1 1 0 2 1 0.5 at %s: %s %s?", i, val, res)
    end
  end
  # 
  test_gen_equal(make_table_lookup(440.0, :wave, partials2wave([1, 1, 2, 1])),
                 make_table_lookup(440.0, :wave, partials2wave([1, 1, 2, 1])),
                 make_table_lookup(100.0, :wave, partials2wave([1, 1, 2, 1])))
  test_gen_equal(make_table_lookup(440.0, :wave, partials2wave([1, 1, 2, 1])),
                 make_table_lookup(440.0, :wave, partials2wave([1, 1, 2, 1])),
                 make_table_lookup(440.0, :wave, partials2wave([1, 1, 2, 0.5])))
  #
  if (res = Snd.catch do partials2wave([0.5, 0.3, 0.2]) end).first != :bad_type
    snd_display("odd length arg to partials2wave: %s", res.inspect)
  end
  #
  hi = make_table_lookup(:size, 256)
  if hi.length != 256
    snd_display("table_lookup set length: %s?", hi.length)
  end
  if (res = Snd.catch do make_table_lookup(:size, 0) end).first != :out_of_range
    snd_display("table_lookup size 0: %s", res.inspect)
  end
  gen = make_table_lookup(440.0, :wave, partials2wave([1, 1]))
  a = 0.0
  ainc = (TWO_PI * 440.0) / 22050.0
  1100.times do |i|
    if fneq(val1 = sin(a), val2 = gen.run(0.0))
      snd_display("table_lookup [1, 1]: %s: %s %s?", i, val1, val2)
    end
    a += ainc
  end
  gen = make_table_lookup(4.0, :wave, partials2wave([1, 1]))
  a = 0.0
  ainc = (TWO_PI * 4.0) / 22050.0
  1100.times do |i|
    if fneq(val1 = sin(a), val2 = gen.run(0.0))
      snd_display("table_lookup [1, 1] 4: %s: %s %s?", i, val1, val2)
    end
    a += ainc
  end
  gen = make_table_lookup(440.0, :wave, partials2wave([1, 0.75, 3, 0.25]))
  a = 0.0
  ainc = (TWO_PI * 440.0) / 22050.0
  1100.times do |i|
    val1 = 0.75 * sin(a) + 0.25 * sin(3.0 * a)
    if fneq(val1, val2 = gen.run(0.0))
      snd_display("table_lookup [1, 0.75, 3, 0.25]: %s: %s %s?", i, val1, val2)
    end
    a += ainc
  end
  gen = make_table_lookup(0.0, :wave, partials2wave([1, 1]))
  gen1 = make_table_lookup(40.0, :wave, partials2wave([1, 1]))
  a = 0.0
  a1 = 0.0
  ainc = (TWO_PI * 40.0) / 22050.0
  100.times do |i|
    if fneq(val1 = sin(a1), val2 = gen.run(gen1.run(0.0)))
      snd_display("table_lookup/table_lookup fm: %s: %s %s?", i, val1, val2)
    end
    a1 += sin(a)
    a += ainc
  end
  [[:Mus_interp_none, vct(0, 0.000, 0.000, 0.000, 0.000, 1.000, 1.000, 1.000, 1.000, 1.000)],
    [:Mus_interp_linear, vct(0, 0.200, 0.400, 0.600, 0.800, 1.000, 0.800, 0.600, 0.400, 0.200)],
    [:Mus_interp_lagrange, vct(0, 0.120, 0.280, 0.480, 0.720, 1.000, 0.960, 0.840, 0.640, 0.360)],
    [:Mus_interp_all_pass, vct(1, 0.000, 0.429, 0.143, 0.095, 0.905, 0.397, 0.830, 0.793, 0.912)],
    [:Mus_interp_hermite, vct(0, 0.168, 0.424, 0.696, 0.912, 1.000, 0.912, 0.696, 0.424, 0.168)]
  ].each do |type_sym, vals|
    type = Module.const_get(type_sym)
    tbl = make_table_lookup(:frequency, 0.0, :size, 4, :type, type)
    tbl.data[1] = 1.0
    fm = (TWO_PI * 0.2) / 4.0
    v = make_vct!(10) do table_lookup(tbl, fm) end
    if (not vequal(v, vals)) and
        (type_sym != :Mus_interp_all_pass) and
        (type_sym != :Mus_interp_none or (not vequal(v, vct(0, 0, 0, 0, 0, 0, 1, 1, 1, 1))))
      snd_display("tbl interp %s: %s?", type_sym, v)
    end
    if tbl.interp_type != type
      snd_display("tbl interp_type (%s) %s?", type_sym, tbl.interp_type)
    end
  end
  #
  gen0 = make_polyshape(440.0, :coeffs, partials2polynomial([1, 1]))
  gen = make_polyshape(440.0, :partials, [1, 1], :kind, Mus_chebyshev_first_kind)
  gen1 = make_polyshape(440.0)
  print_and_check(gen,
                  "polyshape",
                  "polyshape freq: 440.000Hz, phase: 0.000, coeffs[2]: [0.000 1.000]")
  if gen.length != 2
    snd_display("polyshape length: %s?", gen.length)
  end
  v0 = make_vct!(10) do
    if fneq(val0 = polyshape(gen0, 1.0, 0.0), val = mus_apply(gen, 1.0, 0.0))
      snd_display("polyshape: %s != %s?", val, val0)
    end
    val
  end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | polyshape?(gen1) ? polyshape(gen1, 1.0, 0.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map polyshape: %s %s?", v0, v1)
  end
  gen1 = make_polyshape(440.0, :coeffs, partials2polynomial([1, 1]))
  vct_map!(v1, lambda do | | polyshape(gen1, 1.0) end)
  unless vequal(v0, v1)
    snd_display("map polyshape (no fm): %s %s?", v0, v1)
  end
  unless polyshape?(gen)
    snd_display("%s not polyshape?", gen)
  end
  if fneq(gen.phase, 1.253787)
    snd_display("polyshape phase: %s?", gen.phase)
  end
  gen.phase = 1.0
  if fneq(gen.phase, 1.0)
    snd_display("polyshape set_phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("polyshape frequency: %s?", gen.frequency)
  end
  gen.frequency = 100.0
  if fneq(gen.frequency, 100.0)
    snd_display("polyshape set_frequency: %s?", gen.frequency)
  end
  unless vct?(gen.data)
    snd_display("mus_data polyshape: %s?", gen.data)
  end
  if fneq(v0[1], 0.992) or fneq(v0[8], 0.538)
    snd_display("polyshape output: %s?", v0)
  end
  gen0.data = make_vct(32)
  gen0.length = 32
  if gen0.length != 32
    snd_display("set_mus_length polyshape: %s?", gen0.length)
  end
  #
  test_gen_equal(make_polyshape(440.0, :partials, [1, 1]),
                 make_polyshape(440.0),
                 make_polyshape(100.0, :partials, [1, 1]))
  test_gen_equal(make_polyshape(440.0, :partials, [1, 1]),
                 make_polyshape(440.0),
                 make_polyshape(4400.0, :partials, [1, 1, 2, 0.5]))
  #
  gen = make_polyshape(440.0, :partials, [1, 1])
  1100.times do |i|
    a = gen.phase
    if fneq(val1 = cos(a), val2 = gen.run(1.0, 0.0))
      snd_display("polyshaper [1, 1] %s: %s %s?", i, val1, val2)
      break
    end
  end
  gen = make_polyshape(440.0) # check default for partials: [1, 1])
  1100.times do |i|
    a = gen.phase
    val1 = cos(a)
    if fneq(val1, val2 = gen.run(1.0, 0.0))
      snd_display("polyshaper [1, 1, 2, 0.5] %s: %s %s?", i, val1, val2)
      break
    end
  end
  gen = make_polyshape(440.0, :partials, [1, 1])
  1100.times do |i|
    a = gen.phase
    if fneq(val1 = 0.5 * cos(a), val2 = gen.run(0.5, 0.0))
      snd_display("polyshaper default [1, 1] 0.5 %s: %s %s?", i, val1, val2)
      break
    end
  end
  #
  if (res = Snd.catch do make_polyshape(440.0, :coeffs, 3.14) end).first != :wrong_type_arg
    snd_display("make_polyshape bad coeffs: %s", res.inspect)
  end
  gen = make_polyshape(0.0, :coeffs, partials2polynomial([1, 1]))
  gen1 = make_polyshape(40.0, :coeffs, partials2polynomial([1, 1]))
  a1 = 0.0
  a = 0.0
  400.times do |i|
    if ((val1 = cos(a1)) - (val2 = polyshape(gen, 1.0, polyshape(gen1, 1.0)))).abs > 0.002
      snd_display("polyshape fm: %s: %s %s?", i, val1, val2)
      break
    end
    a1 += cos(a)
    a = a + (TWO_PI * 40.0) / 22050.0
  end
end

def test_08_10
  gen = make_wave_train(440.0, 0.0, make_vct(20))
  gen1 = make_wave_train(440.0, 0.0, make_vct(20))
  print_and_check(gen,
                  "wave-train",
                  "wave-train freq: 440.000Hz, phase: 0.000, size: 20, interp: linear")
  20.times do |i|
    gen.data[i] = i * 0.5
    gen1.data[i] = gen.data[i]
  end
  if gen.length != 20
    snd_display("wave_train length: %s?", gen.length)
  end
  v0 = make_vct!(10) do wave_train(gen, 0.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | wave_train?(gen1) ? wave_train(gen1) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map wave_train: %s %s?", v0, v1)
  end
  unless wave_train?(gen)
    snd_display("%s not wave_train?", gen)
  end
  if fneq(gen.phase, 0.0)
    snd_display("wave_train phase: %s?", gen.phase)
  end
  gen.phase = 1.0
  if fneq(gen.phase, 1.0)
    snd_display("wave_train set_phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("wave_train frequency: %s?", gen.frequency)
  end
  gen.frequency = 100.0
  if fneq(gen.frequency, 100.0)
    snd_display("wave_train set_frequency: %s?", gen.frequency)
  end
  if fneq(v0[1], 0.5) or fneq(v0[8], 4.0)
    snd_display("wave_train output: %s?", v0)
  end
  gen.reset
  if fneq(gen.phase, 0.0)
    snd_display("wave_train reset phase: %s?", gen.phase)
  end
  if fneq(res = wave_train(gen, 0.0), 0.0)
    snd_display("wave_train data: %s?", res)
  end
  unless vct?(gen.data)
    snd_display("mus_data wave_train: %s?", gen.data)
  end
  gen.data = make_vct(3)
  make_oscil.data = make_vct(3)
  #
  test_gen_equal(make_wave_train(440.0, 0.0, make_vct(20)),
                 make_wave_train(440.0, 0.0, make_vct(20)),
                 make_wave_train(100.0, 0.0, make_vct(20)))
  test_gen_equal(make_wave_train(440.0, 0.0, make_vct(20)),
                 make_wave_train(440.0, 0.0, make_vct(20)),
                 make_wave_train(440.0, 1.0, make_vct(20)))
  #
  hi = make_wave_train(:size, 256)
  if hi.length != 256
    snd_display("wave_train set_length: %s?", hi.length)
  end
  hi.length = 128
  if hi.length != 128
    snd_display("wave_train set_length: %s?", hi.length)
  end
  [[:Mus_interp_none, vct(0.000, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1.000)],
    [:Mus_interp_linear, vct(0.200, 0.800, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.200, 0.800)],
    [:Mus_interp_lagrange, vct(0.120, 0.960, -0.080, 0.000, 0.0, 0.0, 0.0, 0.0, 0.120, 0.960)],
    [:Mus_interp_hermite, vct(0.168, 0.912, -0.064, -0.016, 0.0, 0.0, 0.0, 0.000, 0.168, 0.912)]
  ].each do |type_sym, vals|
    type = Module.const_get(type_sym)
    fm = (TWO_PI * 0.2) / 4.0
    tbl = make_wave_train(:frequency, 3000.0, :initial_phase, fm, :size, 4, :type, type)
    tbl.data[1] = 1.0
    v = make_vct!(10) do wave_train(tbl, 0.0) end
    unless vequal(v, vals)
      snd_display("wt tbl interp %s: %s?", type_sym, v)
    end
    if tbl.interp_type != type
      snd_display("wt tbl interp_type (%s) %s?", type_sym, tbl.interp_type)
    end
  end
  if (res = Snd.catch do make_wave_train(:size, 0) end).first != :out_of_range
    snd_display("wave_train size 0: %s", res.inspect)
  end
  #
  ind = new_sound(:size, 10)
  if frames != 10
    snd_display("new_sound size 10: %s?", frames)
  end
  map_channel($init_channel, 7, 8)
  if frames != 15
    snd_display("map_channel 7 8: %s?", frames)
  end
  map_channel($init_channel)
  if frames != 15
    snd_display("map_channel (no dur): %s?", frames)
  end
  revert_sound(ind)
  map_channel($init_channel, 9, 10)
  if frames != 19
    snd_display("map_channel 9 10: %s?", frames)
  end
  if (res = edit_position(ind, 0)) > 2
    snd_display("map_channel pad edits (1): %s?", res)
  end
  revert_sound(ind)
  map_channel($init_channel, 10, 10)
  if frames != 20
    snd_display("map_channel 10 10: %s?", frames)
  end
  if (res = edit_position(ind, 0)) > 2
    snd_display("map_channel pad edits (2): %s?", res)
  end
  revert_sound(ind)
  map_channel($init_channel, 20, 10)
  if frames != 30
    snd_display("map_channel 20 10: %s?", frames)
  end
  if (res = edit_position(ind, 0)) > 2
    snd_display("map_channel pad edits (3): %s?", res)
  end
  revert_sound(ind)
  if scan_channel(lambda do |y| false end, 30, 10)
    snd_display("scan_channel past end?")
  end
  ptree_channel($init_channel, 7, 8)
  if frames != 15
    snd_display("ptree_channel 7 8: %s?", frames)
  end
  ptree_channel($init_channel)
  if frames != 15
    snd_display("ptree_channel (no dur): %s?", frames)
  end
  revert_sound(ind)
  ptree_channel($init_channel, 9, 10)
  if frames != 19
    snd_display("ptree_channel 9 10: %s?", frames)
  end
  if (res = edit_position(ind, 0)) > 2
    snd_display("ptree_channel pad edits (1): %s?", res)
  end
  revert_sound(ind)
  ptree_channel($init_channel, 10, 10)
  if frames != 20
    snd_display("ptree_channel 10 10: %s?", frames)
  end
  if (res = edit_position(ind, 0)) > 2
    snd_display("ptree_channel pad edits (2): %s?", res)
  end
  revert_sound(ind)
  ptree_channel($init_channel, 20, 10)
  if frames != 30
    snd_display("ptree_channel 20 10: %s?", frames)
  end
  if (res = edit_position(ind, 0)) > 2
    snd_display("ptree_channel pad edits (3): %s?", res)
  end
  close_sound(ind)
  #
  ind = new_sound(:size, 1000)
  table = vct(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6)
  gen = make_wave_train(1000.0, :wave, table)
  map_channel(lambda do |y| wave_train(gen) end)
  if fneq(res = maxamp, 0.6)
    snd_display("wave_train 0 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.100, 0.200, 0.300, 0.400, 0.500, 0.600, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.100, 0.200, 0.300, 0.400, 0.500, 0.600))
    snd_display("wave_train 0 data: %s?", res)
  end
  unless vequal(res = channel2vct(85, 30),
                vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.100, 0.200, 0.300,
                    0.400, 0.500, 0.600, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.100, 0.200, 0.300))
    snd_display("wave_train 0 data 85: %s?", res)
  end
  undo_edit
  #
  table = make_vct(10, 0.1)
  gen = make_wave_train(1000.0, :initial_phase, PI, :wave, table)
  map_channel(lambda do |y| wave_train(gen) end)
  if fneq(res = maxamp, 0.1)
    snd_display("wave_train 1 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.100, 0.100, 0.100, 0.100, 0.100, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.000, 0.000))
    snd_display("wave_train 1 data: %s?", res)
  end
  undo_edit
  #
  table = make_vct(10, 0.1)
  gen = make_wave_train(2000.0, :wave, table)
  map_channel(lambda do |y| wave_train(gen) end)
  if fneq(res = maxamp, 0.1)
    snd_display("wave_train 2 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.000, 0.000, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.000, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100))
    snd_display("wave_train 2 data: %s?", res)
  end
  if (not vequal(res = channel2vct(440, 30),
                 vct(0.000, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                     0.100, 0.100, 0.100, 0.000, 0.000, 0.100, 0.100, 0.100,
                     0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.000,
                     0.100, 0.100, 0.100, 0.100, 0.100, 0.100))) and
      (not vequal(res = channel2vct(440, 30),
                  vct(0.000, 0.000, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                      0.100, 0.100, 0.100, 0.100, 0.000, 0.100, 0.100, 0.100,
                      0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.000,
                      0.100, 0.100, 0.100, 0.100, 0.100, 0.100)))
    snd_display("wave_train 2 data 440: %s?", res)
  end
  undo_edit
  # 
  table = make_vct(10, 0.1)
  gen = make_wave_train(3000.0, :wave, table)
  map_channel(lambda do |y| wave_train(gen) end)
  if fneq(res = maxamp, 0.2)
    snd_display("wave_train 3 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.200, 0.200, 0.100, 0.100, 0.100, 0.100, 0.100, 0.200,
                    0.200, 0.200, 0.100, 0.100, 0.100, 0.100, 0.100, 0.200,
                    0.200, 0.100, 0.100, 0.100, 0.100, 0.100))
    snd_display("wave_train 3 data: %s?", res)
  end
  unless vequal(res = channel2vct(440, 30),
                vct(0.100, 0.200, 0.200, 0.200, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.200, 0.200, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.200, 0.200, 0.200, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.200, 0.200, 0.100, 0.100, 0.100, 0.100))
    snd_display("wave_train 3 data 440: %s?", res)
  end
  undo_edit
  # 
  table = make_vct(10, 0.1)
  gen = make_wave_train(5000.0, :wave, table)
  map_channel(lambda do |y| wave_train(gen) end)
  if fneq(res = maxamp, 0.3)
    snd_display("wave_train 4 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.100, 0.100, 0.100, 0.100, 0.100, 0.200, 0.200, 0.200,
                    0.200, 0.300, 0.200, 0.200, 0.200, 0.200, 0.300, 0.200,
                    0.200, 0.200, 0.300, 0.200, 0.200, 0.200, 0.200, 0.300,
                    0.200, 0.200, 0.200, 0.300, 0.200, 0.200))
    snd_display("wave_train 4 data: %s?", res)
  end
  unless vequal(res = channel2vct(440, 30),
                vct(0.200, 0.200, 0.300, 0.200, 0.200, 0.200, 0.300, 0.200,
                    0.200, 0.200, 0.300, 0.300, 0.200, 0.200, 0.200, 0.300,
                    0.200, 0.200, 0.200, 0.300, 0.200, 0.200, 0.200, 0.200,
                    0.300, 0.200, 0.200, 0.200, 0.300, 0.200))
    snd_display("wave_train 4 data 440: %s?", res)
  end
  undo_edit
  # 
  table = make_vct(10, 0.1)
  gen = make_wave_train(1000.0, :wave, table)
  e = make_env([0, 1, 1, 2], :length, 1001)
  base_freq = mus_frequency(gen)
  map_channel(lambda do |y|
                res = wave_train(gen)
                set_mus_frequency(gen, env(e) * base_freq)
                res
              end)
  if fneq(res = maxamp, 0.1)
    snd_display("wave_train 5 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100))
    snd_display("wave_train 5 data: %s?", res)
  end
  unless vequal(res = channel2vct(440, 30),
                vct(0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.100))
    snd_display("wave_train 5 data 440: %s?", res)
  end
  unless vequal(res = channel2vct(900, 30),
                vct(0.100, 0.000, 0.000, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.000, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.000, 0.000, 0.100, 0.100, 0.100, 0.100))
    snd_display("wave_train 5 data 900: %s?", res)
  end
  undo_edit
  # 
  table = make_vct(10, 0.1)
  gen = make_wave_train(500.0, :wave, table)
  ctr = 0
  map_channel(lambda do |y|
                res = wave_train(gen)
                if ctr > 22
                  ctr = 0
                  vct_scale!(mus_data(gen), 1.05)
                else
                  ctr += 1
                end
                res
              end)
  if fneq(res = maxamp, 0.704)
    snd_display("wave_train 6 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("wave_train 6 data: %s?", res)
  end
  if (not vequal(res = channel2vct(440, 30),
                 vct(0.000, 0.241, 0.241, 0.241, 0.241, 0.241, 0.241, 0.241,
                     0.241, 0.241, 0.241, 0.000, 0.000, 0.000, 0.000, 0.000,
                     0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                     0.000, 0.000, 0.000, 0.000, 0.000, 0.000))) and
      (not vequal(res = channel2vct(440, 30),
                  vct(0.000, 0.000, 0.241, 0.241, 0.241, 0.241, 0.241, 0.241,
                      0.241, 0.241, 0.241, 0.241, 0.000, 0.000, 0.000, 0.000,
                      0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                      0.000, 0.000, 0.000, 0.000, 0.000, 0.000)))
    snd_display("wave_train 6 data 440: %s?", res)
  end
  unless vequal(res = channel2vct(900, 30),
                vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.639, 0.639, 0.639))
    snd_display("wave_train 6 data 900: %s?", res)
  end
  undo_edit
  fname = file_name(ind)
  close_sound(ind)
  delete_file(fname)
  #
  gen = make_readin("oboe.snd", 0, 1490)
  gen1 = make_readin("oboe.snd", 0, 1490)
  print_and_check(gen, "readin", "readin oboe.snd[chan 0], loc: 1490, dir: 1")
  v0 = make_vct!(10) do readin(gen) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | |
             if readin?(gen1)
               if gen1.channel.zero?
                 readin(gen1)
               else
                 1.0
               end
             else
               if gen1.file_name == "oboe.snd"
                 -1.0
               else
                 -1.0
               end
             end
           end)
  unless vequal(v0, v1)
    snd_display("map readin: %s %s?", v0, v1)
  end
  unless readin?(gen)
    snd_display("%s not readin?", gen)
  end
  unless mus_input?(gen)
    snd_display("%s not input?", gen)
  end
  if gen.length != 50828
    snd_display("readin length: %s?", gen.length)
  end
  if gen.channel.nonzero?
    snd_display("readin chan: %s?", gen.channel)
  end
  if gen.file_name != "oboe.snd"
    snd_display("readin mus_file_name: %s?", gen.file_name)
  end
  if fneq(v0[1], -0.009) or fneq(v0[7], 0.029)
    snd_display("readin output: %s?", v0)
  end
  set_mus_location(gen, 1000)
  if mus_location(gen) != 1000
    snd_display("readin set_mus_location: %s?", mus_location(gen))
  end
  if fneq(res = readin(gen), 0.033)
    snd_display("readin set_mus_location data: %s?", res)
  end
  set_mus_increment(gen, -1)
  if fneq(mus_increment(gen), -1.0)
    snd_display("readin set_mus_increment: %s?", mus_increment(gen))
  end
  if (res = Snd.catch do make_readin("/baddy/hiho", 0, 124) end).first != :no_such_file
    snd_display("make_readin w/o file: %s", res.inspect)
  end
  if (res = Snd.catch do make_readin("oboe.snd", 123, 124) end).first != :out_of_range
    snd_display("make_readin with bad chan: %s", res.inspect)
  end
  #
  test_gen_equal(make_readin("oboe.snd", 0),
                 make_readin("oboe.snd", 0),
                 make_readin("oboe.snd", 0, 1230))
  test_gen_equal(make_readin("oboe.snd", 0, :size, 512),
                 make_readin("oboe.snd", 0, :size, 512),
                 make_readin("pistol.snd", 0, :size, 512))
  test_gen_equal(make_readin("2.snd", 1),
                 make_readin("2.snd", 1),
                 make_readin("2.snd", 0))
  #
  gen = make_readin("2.snd", 1, :size, 1024)
  print_and_check(gen, "readin", "readin 2.snd[chan 1], loc: 0, dir: 1")
  v0.map! do readin(gen) end
  if gen.channel != 1
    snd_display("readin chan 1: %s?", gen.channel)
  end
  if fneq(v0[1], 0.01) or fneq(v0[7], -0.006)
    snd_display("readin 1 output: %s?", v0)
  end
  print_and_check(gen, "readin", "readin 2.snd[chan 1], loc: 10, dir: 1")
  # 
  gen = make_file2sample("oboe.snd")
  print_and_check(gen, "file->sample", "file->sample oboe.snd")
  unless file2sample?(gen)
    snd_display("%s not file2sample?", gen)
  end
  unless mus_input?(gen)
    snd_display("%s not input?", gen)
  end
  if gen.length != 50828
    snd_display("file2sample length: %s?", gen.length)
  end
  if gen.file_name != "oboe.snd"
    snd_display("file2sample mus_file_name: %s?", gen.file_name)
  end
  v0 = make_vct!(10) do |i| file2sample(gen, 1490 + i) end
  if fneq(v0[1], -0.009) or fneq(v0[7], 0.029)
    snd_display("file2sample output: %s?", v0)
  end
  if fneq(mus_increment(gen), 0.0)
    snd_display("file2sample increment: %s?", mus_increment(gen))
  end
  set_mus_increment(gen, 1)
  if fneq(mus_increment(gen), 1.0)
    snd_display("file2sample set_increment: %s?", mus_increment(gen))
  end
  mus_reset(gen)
  #
  ind = open_sound("oboe.snd")
  gen = make_snd2sample(ind)
  gen1 = make_snd2sample(ind)
  print_and_check(gen, "snd->sample", "snd->sample reading oboe.snd (1 chan) at 0:[no readers]")
  unless gen.eql?(gen)
    snd_display("snd2sample not eql? itself?")
  end
  if gen.eql?(gen1)
    snd_display("snd2sample eql? not itself?")
  end
  unless snd2sample?(gen)
    snd_display("%s not snd2sample?", gen)
  end
  unless mus_input?(gen)
    snd_display("%s not input?", gen)
  end
  if gen.length != frames(ind)
    snd_display("snd2sample length: %s?", gen.length)
  end
  if gen.file_name != (Dir.pwd + "/oboe.snd")
    snd_display("snd2sample mus_file_name: %s?", gen.file_name)
  end
  v0 = make_vct!(10) do |i| snd2sample(gen, 1490 + i) end
  if fneq(v0[1], -0.009) or fneq(v0[7], 0.029)
    snd_display("snd2sample output: %s?", v0)
  end
  if mus_channels(gen) != 1
    snd_display("snd2sample channels: %s?", mus_channels(gen))
  end
  if mus_location(gen) != 1499
    snd_display("snd2sample location: %s?", mus_location(gen))
  end
  v0.map_with_index! do |val, i| ina(1490 + i, gen) end
  if fneq(v0[1], -0.009) or fneq(v0[7], 0.029)
    snd_display("snd2sample output ina: %s?", v0)
  end
  close_sound(ind)
  # 
  ind = open_sound("2.snd")
  gen = make_snd2sample(ind)
  v0 = make_vct!(10) do |i|
    snd2sample(gen, 1490 + i, 0)
    snd2sample(gen, 1490 + i, 1)
  end
  print_and_check(gen,
                  "snd->sample",
                  "snd->sample reading 2.snd (2 chans) at 1499:[#<sampler: 2.snd[0: 0] from 1490, at 1500, forward>, #<sampler: 2.snd[1: 0] from 1490, at 1500, forward>]")
  unless snd2sample?(gen)
    snd_display("%s not snd2sample?", gen)
  end
  unless mus_input?(gen)
    snd_display("%s not input?", gen)
  end
  if gen.length != frames(ind)
    snd_display("snd2sample length: %s?", gen.length)
  end
  if gen.file_name != (Dir.pwd + "/2.snd")
    snd_display("snd2sample mus_file_name: %s?", gen.file_name)
  end
  if mus_channels(gen) != 2
    snd_display("snd2sample channels (2): %s?", mus_channels(gen))
  end
  if mus_location(gen) != 1499
    snd_display("snd2sample location (2): %s?", mus_location(gen))
  end
  close_sound(ind)
end

def test_08_11
  gen = make_file2frame("oboe.snd")
  print_and_check(gen, "file->frame", "file->frame oboe.snd")
  unless file2frame?(gen)
    snd_display("%s not file2frame?", gen)
  end
  unless mus_input?(gen)
    snd_display("%s not input?", gen)
  end
  if gen.length != 50828
    snd_display("file2frame length: %s?", gen.length)
  end
  v0 = make_vct!(10) do |i| frame_ref(file2frame(gen, 1490 + i, 0), 0) end
  unless file2frame?(gen)
    snd_display("%s not file2frame?", gen)
  end
  if gen.file_name != "oboe.snd"
    snd_display("file2frame mus_file_name: %s?", gen.file_name)
  end
  if fneq(v0[1], -0.009) or fneq(v0[7], 0.029)
    snd_display("file2frame output: %s?", v0)
  end
  # 
  delete_files("fmv.snd", "fmv1.snd", "fmv2.snd", "fmv3.snd")
  gen = make_sample2file("fmv.snd", 2, Mus_lshort, Mus_riff)
  print_and_check(gen, "sample->file", "sample->file fmv.snd")
  unless sample2file?(gen)
    snd_display("%s not sample2file?", gen)
  end
  unless mus_output?(gen)
    snd_display("%s not output?", gen)
  end
  if gen.length != mus_file_buffer_size
    snd_display("sample2file length: %s?", gen.length)
  end
  genx = gen
  unless gen.eql?(genx)
    snd_display("sample2file eql? %s %s", genx, gen)
  end
  if gen.file_name != "fmv.snd"
    snd_display("sample2file mus_file_name: %s?", gen.file_name)
  end
  100.times do |i|
    sample2file(gen, i, 0, i * 0.001)
    sample2file(gen, i, 1, i * 0.010)
  end
  outa(50, 0.015, gen)
  outb(50, 0.150, gen)
  out_any(60, 0.015, 0, gen)
  out_any(60, 0.150, 1, gen)
  mus_close(gen)
  gen = make_file2sample("fmv.snd")
  print_and_check(gen, "file->sample", "file->sample fmv.snd")
  val0 = in_any(20, 0, gen)
  val1 = in_any(20, 1, gen)
  val2 = ina(30, gen)
  val3 = inb(30, gen)
  val4 = file2sample(gen, 40, 0)
  val5 = file2sample(gen, 40, 1)
  val6 = in_any(50, 0, gen)
  val7 = in_any(50, 1, gen)
  val8 = in_any(60, 0, gen)
  val9 = in_any(60, 1, gen)
  if mus_channels(gen) != 2
    snd_display("sample2file channels: %s?", mus_channels(gen))
  end
  unless mus_input?(gen)
    snd_display("%s not input?", gen)
  end
  if fneq(val0, 0.02) or fneq(val1, 0.2)
    snd_display("in_any: %s %s?", val0, val1)
  end
  if fneq(val2, 0.03) or fneq(val3, 0.3)
    snd_display("ina|b: %s %s?", val2, val3)
  end
  if fneq(val4, 0.04) or fneq(val5, 0.4)
    snd_display("sample2file: %s %s?", val4, val5)
  end
  if fneq(val6, 0.065) or fneq(val7, 0.65)
    snd_display("outa|b: %s %s?", val6, val7)
  end
  if fneq(val8, 0.075) or fneq(val9, 0.75)
    snd_display("out_any: %s %s?", val8, val9)
  end
  #
  gen = Vct.new(10)
  let(-0.1) do |x|
    gen.length.times do |i| outa(i, x += 0.1, gen) end
  end
  unless vequal(gen, vct(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9))
    snd_display("outa->vct ramp: %s?", gen)
  end
  let(-0.1) do |x|
    gen.length.times do |i| outa(i, x += 0.1, gen) end
  end
  unless vequal(gen, vct(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9).scale!(2.0))
    snd_display("outa->vct ramp 2: %s?", gen)
  end
  if (res = mus_channels(gen)) != 1
    snd_display("mus_channels vct: %s?", res)
  end
  #
  gen = make_sound_data(4, 100)
  10.times do |i|
    outa(i, 0.1, gen)
    outb(i, 0.2, gen)
    outc(i, 0.3, gen)
    outd(i, 0.4, gen)
  end
  10.times do |i|
    outa(i, 0.01, gen)
    outb(i, 0.02, gen)
    outc(i, 0.03, gen)
    outd(i, 0.04, gen)
  end
  mus_close(gen)
  10.times do |i|
    if fneq(res1 = ina(i, gen), 0.11) or
        fneq(res2 = inb(i, gen), 0.22) or
        fneq(res3 = in_any(i, 2, gen), 0.33) or
        fneq(res4 = in_any(i, 3, gen), 0.44)
      snd_display("4-chan sd out/in[%s]: %s %s %s %s?", i, res1, res2, res3, res4)
    end
  end
  if (res = mus_channels(gen)) != 4
    snd_display("mus_channels sd 4: %s?", res)
  end
  # 
  gen = make_sound_data(4, 100)
  10.times do |i|
    out_any(i, 0.1, 0, gen)
    out_any(i, 0.2, 1, gen)
    out_any(i, 0.3, 2, gen)
    out_any(i, 0.4, 3, gen)
  end
  10.times do |i|
    out_any(i, 0.01, 0, gen)
    out_any(i, 0.02, 1, gen)
    out_any(i, 0.03, 2, gen)
    out_any(i, 0.04, 3, gen)
  end
  mus_close(gen)
  10.times do |i|
    if fneq(res1 = in_any(i, 0, gen), 0.11) or
        fneq(res2 = in_any(i, 1, gen), 0.22) or
        fneq(res3 = in_any(i, 2, gen), 0.33) or
        fneq(res4 = in_any(i, 3, gen), 0.44)
      snd_display("4-chan sd out/in_any[%s]: %s %s %s %s?", i, res1, res2, res3, res4)
    end
  end
  #
  gen = make_oscil(440.0)
  if (res = Snd.catch do outa(0, 0.1, gen) end).first != :wrong_type_arg
    snd_display("outa -> oscil: %s", res.inspect)
  end
  res = Snd.catch do outa(0, 0.1, false) end
  if (not number?(res.car)) or fneq(res.car, 0.1)
    snd_display("outa -> false: %s", res.inspect)
  end
  #
  gen = make_sample2file("fmv.snd", 4, Mus_lshort, Mus_riff)
  print_and_check(gen, "sample->file", "sample->file fmv.snd")
  10.times do |i|
    outa(i, 0.1, gen)
    outb(i, 0.2, gen)
    outc(i, 0.3, gen)
    outd(i, 0.4, gen)
  end
  10.times do |i|
    outa(i, 0.01, gen)
    outb(i, 0.02, gen)
    outc(i, 0.03, gen)
    outd(i, 0.04, gen)
  end
  mus_close(gen)
  gen = make_file2sample("fmv.snd")
  print_and_check(gen, "file->sample", "file->sample fmv.snd")
  10.times do |i|
    if fneq(res1 = ina(i, gen), 0.11) or
        fneq(res2 = inb(i, gen), 0.22) or
        fneq(res3 = in_any(i, 2, gen), 0.33) or
        fneq(res4 = in_any(i, 3, gen), 0.44)
      snd_display("4-chan out/in[%s]: %s %s %s %s?", i, res1, res2, res3, res4)
    end
  end
  if (res = Snd.catch do
        make_sample2file("fmv.snd", -1, Mus_lshort, Mus_next)
      end).first != :out_of_range
    snd_display("make_sample2file bad chans: %s", res.inspect)
  end
  if (res = Snd.catch do mus_location(make_oscil) end).first != :mus_error
    snd_display("set_mus_location(make_oscil): %s", res.inspect)
  end
  if (res = Snd.catch do
        make_sample2file("fmv.snd", 1, -1, Mus_next)
      end).first != :out_of_range
    snd_display("make_sample2file bad format: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_sample2file("fmv.snd", 1, Mus_lshort, -1)
      end).first != :out_of_range
    snd_display("make_sample2file bad type: %s", res.inspect)
  end
  # 
  gen = make_frame2file("fmv1.snd", 2, Mus_bshort, Mus_next)
  print_and_check(gen, "frame->file", "frame->file fmv1.snd")
  unless frame2file?(gen)
    snd_display("%s not frame2file?", gen)
  end
  unless mus_output?(gen)
    snd_display("%s not output?", gen)
  end
  if gen.length != mus_file_buffer_size
    snd_display("frame2file length: %s?", gen.length)
  end
  if gen.file_name != "fmv1.snd"
    snd_display("frame2file mus_file_name: %s?", gen.file_name)
  end
  gen.length = 4096
  if gen.length != 4096
    snd_display("frame2file length: %s?", gen.length)
  end
  gen.length = 8192
  fr0 = make_frame(2, 0.0, 0.0)
  100.times do |i|
    frame_set!(fr0, 0, i * 0.001)
    frame_set!(fr0, 1, i * 0.010)
    frame2file(gen, i, fr0)
  end
  mus_close(gen)
  gen = make_file2frame("fmv1.snd", 1024)
  val4 = file2frame(gen, 40)
  frout = make_frame(2)
  if fneq(frame_ref(val4, 0), 0.04) or fneq(frame_ref(val4, 1), 0.4)
    snd_display("frame2file output: %s?", val4)
  end
  file2frame(gen, 40, frout)
  unless frout.eql?(val4)
    snd_display("frame2file output via frame: %s %s?", frout, val4)
  end
  #
  gen = make_sample2file("fmv2.snd", 4, Mus_bshort, Mus_aifc)
  print_and_check(gen, "sample->file", "sample->file fmv2.snd")
  unless sample2file?(gen)
    snd_display("%s not sample2file?", gen)
  end
  unless mus_output?(gen)
    snd_display("%s not output?", gen)
  end
  100.times do |i|
    sample2file(gen, i, 0, i * 0.001)
    sample2file(gen, i, 1, i * 0.010)
    sample2file(gen, i, 2, i * 0.002)
    sample2file(gen, i, 3, i * 0.003)
  end
  outa(50, 0.015, gen)
  outb(50, 0.150, gen)
  outc(50, 0.020, gen)
  outd(50, 0.030, gen)
  out_any(60, 0.015, 0, gen)
  out_any(60, 0.150, 1, gen)
  out_any(60, 0.020, 2, gen)
  out_any(60, 0.030, 3, gen)
  mus_close(gen)
  gen = make_file2sample("fmv2.snd")
  val0 = in_any(20, 2, gen)
  val1 = in_any(20, 3, gen)
  val2 = file2sample(gen, 50, 2)
  val3 = file2sample(gen, 50, 3)
  val4 = file2sample(gen, 60, 2)
  val5 = file2sample(gen, 60, 3)
  if mus_channels(gen) != 4
    snd_display("file2sample channels (4): %s?", mus_channels(gen))
  end
  if fneq(mus_increment(gen), 0.0)
    snd_display("file2sample increment: %s?", mus_increment(gen))
  end
  if fneq(val0, 0.04) or fneq(val1, 0.06)
    snd_display("in_any(0, 4): %s %s?", val0, val1)
  end
  if fneq(val2, 0.12) or fneq(val3, 0.18)
    snd_display("file2sample(4): %s %s?", val2, val3)
  end
  if fneq(val4, 0.14) or fneq(val5, 0.21)
    snd_display("in_any(4, 4): %s %s?", val4, val5)
  end
  #
  delete_file("fmv.snd")
  mus_sound_forget("fmv.snd")
  sf = make_sample2file("fmv.snd", 2, Mus_bshort, Mus_next, "this is a comment")
  10.times do |i|
    sample2file(sf, i, 0, i * 0.10)
    sample2file(sf, i, 1, i * 0.01)
  end
  mus_close(sf)
  if (res = mus_sound_chans("fmv.snd")) != 2
    snd_display("sample2file chans: %s?", res)
  end
  if (res = mus_sound_frames("fmv.snd")) != 10
    snd_display("sample2file frames: %s?", res)
  end
  if (res = mus_sound_samples("fmv.snd")) != 20
    snd_display("sample2file samples: %s?", res)
  end
  if (res = mus_sound_header_type("fmv.snd")) != Mus_next
    snd_display("sample2file type: %s?", res)
  end
  if (res = mus_sound_data_format("fmv.snd")) != Mus_bshort
    snd_display("sample2file format: %s?", res)
  end
  if (res = mus_sound_comment("fmv.snd")) != "this is a comment"
    snd_display("sample2file comment: %s?", res)
  end
  rd = make_file2sample("fmv.snd")
  10.times do |i|
    if fneq(c0 = file2sample(rd, i, 0), i * 0.10) or fneq(c1 = file2sample(rd, i, 1), i * 0.01)
      snd_display("sample2file2sample at %s: %s %s?", i, c0, c1)
      break
    end
  end
  mus_close(rd)
  sf = continue_sample2file("fmv.snd")
  10.times do |i|
    sample2file(sf, i + 5, 0, i * -0.02)
    sample2file(sf, i + 5, 1, i * -0.01)
  end
  mus_close(sf)
  mus_sound_forget("fmv.snd")
  if (res = mus_sound_chans("fmv.snd")) != 2
    snd_display("continue_sample2file chans: %s?", res)
  end
  if (res = mus_sound_frames("fmv.snd")) != 15
    snd_display("continue_sample2file frames: %s?", res)
  end
  if (res = mus_sound_samples("fmv.snd")) != 30
    snd_display("continue_sample2file samples: %s?", res)
  end
  if (res = mus_sound_header_type("fmv.snd")) != Mus_next
    snd_display("continue_sample2file type: %s?", res)
  end
  if (res = mus_sound_data_format("fmv.snd")) != Mus_bshort
    snd_display("continue_sample2file format: %s?", res)
  end
  if (res = mus_sound_comment("fmv.snd")) != "this is a comment"
    snd_display("continue_sample2file comment: %s?", res)
  end
  ind = open_sound("fmv.snd")
  unless vequal(c0 = channel2vct(0, 15, ind, 0),
                vct(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.58, 0.66,
                    0.74, 0.82, -0.1, -0.12, -0.14, -0.16, -0.18))
    snd_display("continue_sample2file (0): %s", c0)
  end
  unless vequal(c0 = channel2vct(0, 15, ind, 1),
                vct(0.0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.05, 0.05,
                    0.05, 0.05, -0.05, -0.06, -0.07, -0.08, -0.09))
    snd_display("continue_sample2file (1): %s", c0)
  end
  close_sound(ind)
  #
  delete_file("fmv.snd")
  mus_sound_forget("fmv.snd")
  sf = make_frame2file("fmv.snd", 2, Mus_lfloat, Mus_riff, "this is a comment")
  10.times do |i|
    frame2file(sf, i, make_frame(2, i * 0.10, i * 0.01))
  end
  mus_close(sf)
  if (res = mus_sound_chans("fmv.snd")) != 2
    snd_display("frame2file chans: %s?", res)
  end
  if (res = mus_sound_frames("fmv.snd")) != 10
    snd_display("frame2file frames: %s?", res)
  end
  if (res = mus_sound_samples("fmv.snd")) != 20
    snd_display("frame2file samples: %s?", res)
  end
  if (res = mus_sound_header_type("fmv.snd")) != Mus_riff
    snd_display("frame2file type: %s?", res)
  end
  if (res = mus_sound_data_format("fmv.snd")) != Mus_lfloat
    snd_display("frame2file format: %s?", res)
  end
  if (res = mus_sound_comment("fmv.snd")) != "this is a comment"
    snd_display("frame2file comment: %s?", res)
  end
  rd = make_file2sample("fmv.snd")
  10.times do |i|
    f0 = file2frame(rd, i)
    if f0.length != 2 or
        fneq(frame_ref(f0, 0), i * 0.10) or
        fneq(frame_ref(f0, 1), i * 0.01)
      snd_display("frame2file2frame at %s: %s?", i, f0)
      break
    end
  end
  mus_close(rd)
  sf = continue_frame2file("fmv.snd")
  10.times do |i|
    frame2file(sf, i + 5, make_frame(2, i * -0.02, i * -0.01))
  end
  mus_close(sf)
  mus_sound_forget("fmv.snd")
  if (res = mus_sound_chans("fmv.snd")) != 2
    snd_display("continue_frame2file chans: %s?", res)
  end
  if (res = mus_sound_frames("fmv.snd")) != 15
    snd_display("continue_frame2file frames: %s?", res)
  end
  if (res = mus_sound_samples("fmv.snd")) != 30
    snd_display("continue_frame2file samples: %s?", res)
  end
  if (res = mus_sound_header_type("fmv.snd")) != Mus_riff
    snd_display("continue_frame2file type: %s?", res)
  end
  if (res = mus_sound_data_format("fmv.snd")) != Mus_lfloat
    snd_display("continue_frame2file format: %s?", res)
  end
  if (res = mus_sound_comment("fmv.snd")) != "this is a comment"
    snd_display("continue_frame2file comment: %s?", res)
  end
  ind = open_sound("fmv.snd")
  unless vequal(c0 = channel2vct(0, 15, ind, 0),
                vct(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.58, 0.66,
                    0.74, 0.82, -0.1, -0.12, -0.14, -0.16, -0.18))
    snd_display("continue_frame2file (0): %s", c0)
  end
  unless vequal(c0 = channel2vct(0, 15, ind, 1),
                vct(0.0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.05, 0.05,
                    0.05, 0.05, -0.05, -0.06, -0.07, -0.08, -0.09))
    snd_display("continue_frame2file (1): %s", c0)
  end
  close_sound(ind)
  delete_file("fmv.snd")
  mus_sound_forget("fmv.snd")
  #
  os = make_oscil(440.0)
  v0 = make_vct!(1000) do 0.1 * oscil(os) end
  array2file("fmv3.snd", v0, 10000, 22050, 1)     # 10000 deliberate
  v1 = make_vct(1000)
  file2array("fmv3.snd", 0, 0, 1000, v1)
  vct2vector(v0).zip(vct2vector(v1)) do |val1, val2|
    if fneq(val1, val2)
      snd_display("array2file2array: %s %s?", val1, val2)
    end
  end
  #
  if (res = Snd.catch do
        array2file("fmv3.snd", v0, -1, 1000, 1)
      end).first != :out_of_range
    snd_display("array2file bad samps: %s", res.inspect)
  end
  if (res = Snd.catch do
        array2file("/bad/baddy/fmv3.snd", v0, 1, 1000, 1)
      end).first != :mus_error
    snd_display("array2file bad file: %s", res.inspect)
  end
  if (res = Snd.catch do
        file2array("fmv3.snd", -1, 0, -1, v0)
      end).first != :out_of_range
    snd_display("file2array bad samps: %s", res.inspect)
  end
end

def test_08_12
  gen = make_rand(10000.0)
  print_and_check(gen, "rand", "rand freq: 10000.000Hz, phase: 0.000, amp: 1.000")
  v0 = make_vct!(10) do rand(gen, 0.0) end
  unless rand?(gen)
    snd_display("%s not rand?", gen)
  end
  if fneq(gen.phase, 3.3624296)
    snd_display("rand phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 10000.0)
    snd_display("rand frequency %s?", gen.frequency)
  end
  gen.scaler = 0.5
  if fneq(gen.scaler, 0.5)
    snd_display("rand set_scaler: %s?", gen.scaler)
  end
  if v0[1] == v0[8]
    snd_display("rand output: %s", v0)
  end
  # 
  gen = make_rand(10000.0, :envelope, [0, 0, 1, 1])
  print_and_check(gen,
                  "rand",
                  "rand freq: 10000.000Hz, phase: 0.000, amp: 1.000, with distribution envelope")
  v0 = make_vct!(10) do rand(gen, 0.0) end
  unless rand?(gen)
    snd_display("(dist) %s not rand?", gen)
  end
  if fneq(gen.frequency, 10000.0)
    snd_display("(dist) rand frequency %s?", gen.frequency)
  end
  if v0[1] == v0[8]
    snd_display("(dist) rand output: %s", v0)
  end
  if (not vct?(gen.data)) or gen.length != gen.data.length or gen.length != 512
    snd_display("(dist) rand data: %s %s?", gen.length, gen.data)
  end
  #
  gen1 = make_rand(10000.0, :envelope, [0, 0, 1, 1])
  gen2 = make_rand(10000.0, :envelope, [0, 1, 1, 0])
  up1 = 0
  down1 = 0
  bad1 = 0
  up2 = 0
  down2 = 0
  bad2 = 0
  1000.times do |i|
    val1 = rand(gen1)
    val2 = rand(gen2)
    if val1 >= 0.5
      up1 += 1
    else
      if val1 >= 0.0
        down1 += 1
      else
        bad1 += 1
      end
    end
    if val2 >= 0.5
      up2 += 1
    else
      if val2 >= 0.0
        down2 += 1
      else
        bad2 += 1
      end
    end
  end
  snd_test_neq(bad1, 0, "rand dist: down/up 1 %d/%d", down1, up1)
  snd_test_neq(bad2, 0, "rand dist: down/up 2 %d/%d", down2, up2)
  if 2.5 * down1 > up1
    snd_display(snd_format(2.5 * down1, up1, ">",
                           "rand dist: down/up/bad 1 %d/%d/%d", down1, up1, bad1))
  end
  if 2.5 * up2 > down2
    snd_display(snd_format(2.5 * up2, down2, ">",
                           "rand dist: down/up/bad 2 %d/%d/%d", down2, up2, bad2))
  end
  # 
  gen = make_rand_interp(4000.0)
  print_and_check(gen, "rand-interp", gen.to_s)
  v0 = make_vct!(10) do rand_interp(gen, 0.0) end
  unless rand_interp?(gen)
    snd_display("%s not rand_interp?", gen)
  end
  if fneq(gen.phase, 5.114882)
    snd_display("rand_interp phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 4000.0)
    snd_display("rand_interp frequency %s?", gen.frequency)
  end
  gen.scaler = 0.5
  if fneq(gen.scaler, 0.5)
    snd_display("rand_interp set_scaler: %s?", gen.scaler)
  end
  if v0[1] == v0[8]
    snd_display("rand_interp output: %s", v0)
  end
  # 
  gen = make_rand_interp(4000.0, :envelope, [-1, 1, 0, 0, 1, 1])
  print_and_check(gen, "rand-interp", gen.to_s)
  v0 = make_vct!(10) do rand_interp(gen, 0.0) end
  unless rand_interp?(gen)
    snd_display("(dist) %s not rand_interp?", gen)
  end
  if v0[1] == v0[8]
    snd_display("(dist) rand_interp output: %s", v0)
  end
  if (not vct?(gen.data)) or gen.length != gen.data.length or gen.length != 512
    snd_display("(dist) rand_interp data: %s %s?", gen.length, gen.data)
  end
  #
  gen = make_rand(10000.0, 1.0)
  gen1 = make_rand_interp(10000.0, 1.0)
  1000.times do
    val1 = gen.run(0.0)
    val2 = gen1.run(0.0)
    if val1 > 1.0 or val1 < -1.0
      snd_display(",rand: %s %s?", val1, gen)
    end
    if val2 > 1.0 or val2 < -1.0
      snd_display(",rand_interp: %s %s?", val2, gen1)
    end
  end
  gen = make_rand(10000.0, :distribution, inverse_integrate([0, 0, 1, 1]))
  print_and_check(gen,
                  "rand",
                  "rand freq: 10000.000Hz, phase: 0.000, amp: 1.000, with distribution envelope")
  v0.map! do rand(gen, 0.0) end
  unless rand?(gen)
    snd_display("(dist 2) %s not rand?", gen)
  end
  if fneq(gen.frequency, 10000.0)
    snd_display("(dist 2) rand frequency %s?", gen.frequency)
  end
  if v0[1] == v0[8]
    snd_display("(dist 2) rand output: %s", v0)
  end
  if (not vct?(gen.data)) or gen.length != gen.data.length or gen.length != 512
    snd_display("(dist 2) rand data: %s %s?", gen.length, gen.data)
  end
  #
  gen1 = make_rand(10000.0, :distribution, inverse_integrate([0, 0, 1, 1]))
  gen2 = make_rand(10000.0, :distribution, inverse_integrate([0, 1, 1, 0]))
  up1 = 0
  down1 = 0
  bad1 = 0
  up2 = 0
  down2 = 0
  bad2 = 0
  1000.times do |i|
    val1 = rand(gen1)
    val2 = rand(gen2)
    if val1 >= 0.5
      up1 += 1
    else
      if val1 >= 0.0
        down1 += 1
      else
        bad1 += 1
      end
    end
    if val2 >= 0.5
      up2 += 1
    else
      if val2 >= 0.0
        down2 += 1
      else
        bad2 += 1
      end
    end
  end
  if bad1.nonzero? or bad2.nonzero? or 2.5 * down1 > up1 or 2.0 * up2 > down2
    snd_display("rand dist 2: %s %s %s,  %s %s %s", down1, up1, bad1, down2, up2, bad2)
  end
  #
  v1 = inverse_integrate([-1, 1, 1, 1])
  if fneq(v1[4], -0.984)
    snd_display("inverse_integrate -1 to 1 uniform: %s?", v1)
  end
  v1 = inverse_integrate([0, 1, 1, 1])
  if fneq(v1[4], 0.008)
    snd_display("inverse_integrate 0 to 1 uniform: %s?", v1)
  end
  v1 = inverse_integrate([0, 1, 1, 0])
  if fneq(v1[4], 0.004)
    snd_display("inverse_integrate 0 to 1 1 to 0: %s?", v1)
  end
  v1 = inverse_integrate([0, 0, 0.5, 1, 1, 0])
  if fneq(v1[4], 0.073)
    snd_display("inverse_integrate triangle: %s?", v1)
  end
  v1 = inverse_integrate(gaussian_envelope(1.0))
  if fneq(v1[4], -0.593)
    snd_display("inverse_integrate gaussian: %s?", v1)
  end
end

def test_08_13
  minp = 1.0
  maxp = -1.0
  1100.times do
    val1 = mus_random(1.0)
    if val1 < minp
      minp = val1
    end
    if val1 > maxp
      maxp = val1
    end
    if val1 > 1.0 or val1 < -1.0
      snd_display("mus_random: %s?", val1)
    end
  end
  if maxp < 0.9 or minp > -0.9
    snd_display("mus_random: %s %s", minp, maxp)
  end
  minp = 12.0
  maxp = -12.0
  1100.times do
    val1 = mus_random(12.0)
    if val1 < minp
      minp = val1
    end
    if val1 > maxp
      maxp = val1
    end
    if val1 > 12.0 or val1 < -12.0
      snd_display("mus_random (12): %s?", val1)
    end
  end
  if maxp < 11.0 or minp > -11.0
    snd_display("mus_random (12): %s %s", minp, maxp)
  end
  res = lambda do |n|
    hits = make_array(10, 0)
    n.times do |i| hits[(5 + mus_random(5.0)).floor] += 1 end
    sum = 0.0
    pp = n / 10.0
    hits.each do |val|
      sum = sum + (((val - pp) * (val - pp)) / pp)
    end
    sum
  end.call(10000)
  if res < 4.0
    snd_display("mus_random not so random (chi)? %s", res)
  end
  res = lambda do |n|
    hits = make_array(10, 0)
    gen = make_rand(22050.0)
    n.times do |i| hits[(5 + 5 * rand(gen, 0.0)).floor] += 1 end
    sum = 0.0
    pp = n / 10.0
    hits.each do |val|
      sum = sum + (((val - pp) * (val - pp)) / pp)
    end
    sum
  end.call(10000)
  if res < 4.0
    snd_display("rand not so random (chi)? %s", res)
  end
  #
  data = make_vct!(65536) do mus_random(1.0) end
  ndat = snd_spectrum(data, Rectangular_window, 65536, true, 0.0, false, false)
  peak = vct_peak(ndat)
  sum = 0.0
  if peak > 1000.0
    snd_display("mus_random spectral peak: %s?", peak)
  end
  32768.times do |i| sum += ndat[i] end
  if (res = sum / 32768.0) > 200.0
    snd_display("random average: %s %s?", res, ndat[0])
  end
  data.map! do mus_random(1.0) end
  autocorrelate(data)
  data[0] = 0.0
  pk = vct_peak(data)
  if pk > 1000
    snd_display("random autocorrelate peak: %s?", pk)
  end
  sum = 0.0
  32768.times do |i| sum += ndat[i].abs end
  if (res = sum / 32768.0) > 200.0
    snd_display("random autocorrelate average: %s?", res)
  end
end

def test_08_14
  set_locsig_type(Mus_interp_linear)
  gen = make_locsig(30.0, :channels, 2)
  gen1 = make_locsig(60.0, :channels, 2)
  gen2 = make_locsig(60.0, :channels, 4)
  gen200 = make_locsig(200.0, :channels, 4)
  gen3 = gen1
  locsig(gen, 0, 1.0)
  print_and_check(gen, "locsig", "locsig chans 2, outn: [0.667 0.333], interp: linear")
  unless locsig?(gen)
    snd_display("%s not locsig?", gen)
  end
  unless gen1.eql?(gen3)
    snd_display("locsig %s.eql?(%s)?", gen1, gen3)
  end
  unless gen1 == gen3
    snd_display("locsig %s == %s?", gen1, gen3)
  end
  if gen1.eql?(gen2)
    snd_display("locsig 1 %s.eql?(%s)?", gen1, gen2)
  end
  if gen == gen1
    snd_display("locsig 2 %s == %s?", gen, gen1)
  end
  if gen == gen2
    snd_display("locsig 3 %s == %s?", gen, gen2)
  end
  if fneq(res1 = locsig_ref(gen, 0), 0.667) or fneq(res2 = locsig_ref(gen, 1), 0.333)
    snd_display("locsig ref: %s %s?", res1, res2)
  end
  unless vequal(mus_data(gen), vct(0.667, 0.333))
    snd_display("locsig gen outn: %s?", mus_data(gen))
  end
  unless vequal(mus_data(gen1), vct(0.333, 0.667))
    snd_display("locsig gen1 outn: %s?", mus_data(gen1))
  end
  unless vequal(mus_data(gen2), vct(0.333, 0.667, 0.000, 0.000))
    snd_display("locsig gen2 outn: %s?", mus_data(gen2))
  end
  unless vequal(mus_data(gen200), vct(0.000, 0.000, 0.778, 0.222))
    snd_display("locsig gen200 outn: %s?", mus_data(gen200))
  end
  locsig_set!(gen, 0, 0.25)
  unless vequal(mus_data(gen), vct(0.250, 0.333))
    snd_display("locsig gen 0.25 outn: %s?", mus_data(gen))
  end
  locsig(gen, 0, 1.0)
  locsig_set!(gen, 0, 0.5)
  unless vequal(mus_data(gen), vct(0.500, 0.333))
    snd_display("locsig gen 0.5 outn: %s?", mus_data(gen))
  end
  locsig(gen, 0, 1.0)
  gen = make_locsig(300.0, 2.0, 0.1, :channels, 4)
  unless vequal(mus_data(gen), vct(0.167, 0.000, 0.000, 0.333))
    snd_display("locsig gen 300 outn: %s?", mus_data(gen))
  end
  locsig(gen, 0, 1.0)
  move_locsig(gen1, 90.0, 1.0)
  unless vequal(mus_data(gen1), vct(0.000, 1.000))
    snd_display("locsig gen1 90 outn: %s?", mus_data(gen1))
  end
  move_locsig(gen1, 0.0, 1.0)
  unless vequal(mus_data(gen1), vct(1.000, 0.000))
    snd_display("locsig gen1 0 outn: %s?", mus_data(gen1))
  end
  move_locsig(gen1, 45.0, 1.0)
  unless vequal(mus_data(gen1), vct(0.500, 0.500))
    snd_display("locsig gen1 45 outn: %s?", mus_data(gen1))
  end
  move_locsig(gen1, 135.0, 2.0)
  unless vequal(mus_data(gen1), vct(0.000, 0.500))
    snd_display("locsig gen1 135 outn: %s?", mus_data(gen1))
  end
  move_locsig(gen1, -270.0, 3.0)
  unless vequal(mus_data(gen1), vct(0.333, 0.000))
    snd_display("locsig gen1 -270 outn: %s?", mus_data(gen1))
  end
  [1, 2, 4, 8].each do |chans|
    m1 = make_locsig(:channels, chans)
    if m1.channels != chans or m1.length != chans
      snd_display("locsig %s chans but: %s %s?", chans, m1.channels, m1.length)
    end
    chans.times do |i| locsig_set!(m1, i, i * 0.1) end
    chans.times do |i|
      if fneq(locsig_ref(m1, i), i * 0.1)
        snd_display("locsig[%s] = %s (%s)?", i, locsig_ref(m1, i), i * 0.1)
      end
    end
  end
  #
  if (res = Snd.catch do make_locsig(:channels, 0) end).first != :mus_error
    snd_display("make_locsig bad (0) chans: %s", res.inspect)
  end
  if (res = Snd.catch do make_locsig(:channels, -2) end).first != :out_of_range
    snd_display("make_locsig bad (-2) chans: %s", res.inspect)
  end
  if (res = Snd.catch do make_locsig(:output, 1) end).first != :wrong_type_arg
    snd_display("make_locsig bad output: %s", res.inspect)
  end
  res = Snd.catch do locsig_ref(make_locsig, 1) end
  if res != [0.0] and res.car != :mus_error
    snd_display("locsig_ref bad chan (0): %s", res.inspect)
  end
  res = Snd.catch do
    locs = make_locsig(200, :channels, 2)
    locsig_ref(locs, -1)
  end
  if res != [0.0] and res.car != :mus_error
    snd_display("locsig_ref bad chan (1): %s", res.inspect)
  end
  res = Snd.catch do
    locs = make_locsig
    locsig_set!(locs, 2, 0.1)
  end
  if res != [0.1] and res.car != :mus_error
    snd_display("locsig_set! bad chan (2): %s", res.inspect)
  end
  res = Snd.catch do
    locs = make_locsig(:reverb, 0.1)
    locsig_reverb_ref(locs, 2)
  end
  if res != [0.0] and res.car != :mus_error
    snd_display("locsig_reverb_ref bad reverb chan (2): %s", res.inspect)
  end
  res = Snd.catch do
    locs = make_locsig(:reverb, 0.1)
    locsig_reverb_set!(locs, 2, 0.1)
  end
  if res != [0.1] and res.car != :mus_error
    snd_display("locsig_reverb_set! bad reverb chan (2): %s", res.inspect)
  end
  #
  locs = make_locsig(:channels, 8, :degree, 0)
  move_locsig(locs, 180.0, 1.0)
  if fneq(locsig_ref(locs, 0), 0.0)
    snd_display("move_locsig by jump: %s?", locs.data)
  end
  unless vequal(locs.data, vct(0.000, 0.000, 0.000, 0.000, 1.000, 0.000, 0.000, 0.000))
    snd_display("move_locsig by jump data: %s?", locs.data)
  end
  move_locsig(locs, 120.0, 1.0)
  unless vequal(locs.data, vct(0.000, 0.000, 0.333, 0.667, 0.000, 0.000, 0.000, 0.000))
    snd_display("move_locsig by jump 120 data: %s?", locs.data)
  end
  move_locsig(locs, -20.0, 1.0)
  unless vequal(locs.data, vct(0.556, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.444))
    snd_display("move_locsig by jump -20 data: %s?", locs.data)
  end
  #
  sf = make_sample2file("fmv4.snd", 8, Mus_bshort, Mus_next, "this is a comment")
  sfrev = make_sample2file("fmv4.reverb", 8, Mus_bshort, Mus_next, "this is a comment")
  locs = make_locsig(:channels, 8, :degree, 0, :distance, 1.0, :reverb, 0.1,
                     :output, sf, :revout, sfrev, :type, Mus_interp_linear)
  unless vequal(locs.data, vct(1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("ws not move_locsig by jump data: %s?", locs.data)
  end
  unless vequal(locs.xcoeffs, vct(0.100, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("ws not move_locsig by jump rev data: %s?", locs.xcoeffs)
  end
  move_locsig(locs, 180.0, 2.0)
  if fneq(locsig_ref(locs, 0), 0.0)
    snd_display("ws move_locsig by jump: %s?", locs.data)
  end
  unless vequal(locs.data, vct(0.000, 0.000, 0.000, 0.000, 0.500, 0.000, 0.000, 0.000))
    snd_display("ws move_locsig by jump data: %s?", locs.data)
  end
  unless vequal(locs.xcoeffs, vct(0.000, 0.000, 0.000, 0.000, 0.071, 0.000, 0.000, 0.000))
    snd_display("ws move_locsig by jump rev data: %s?", locs.xcoeffs)
  end
  move_locsig(locs, 120.0, 3.0)
  unless vequal(locs.data, vct(0.000, 0.000, 0.111, 0.222, 0.000, 0.000, 0.000, 0.000))
    snd_display("ws move_locsig by jump 120 data: %s?", locs.data)
  end
  unless vequal(locs.xcoeffs, vct(0.000, 0.000, 0.019, 0.038, 0.000, 0.000, 0.000, 0.000))
    snd_display("ws move_locsig by jump 120 rev data: %s?", locs.xcoeffs)
  end
  move_locsig(locs, -20.0, 4.0)
  unless vequal(locs.data, vct(0.139, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.111))
    snd_display("ws move_locsig by jump -20 data: %s?", locs.data)
  end
  unless vequal(locs.xcoeffs, vct(0.028, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.022))
    snd_display("ws move_locsig by jump -20 rev data: %s?", locs.xcoeffs)
  end
  mus_close(sf)
  mus_close(sfrev)
  delete_files("fmv4.snd", "fmv4.reverb")
  mus_sound_prune
  #
  [Mus_caff,
   Mus_aifc,
   Mus_next,
   Mus_riff,
   Mus_rf64].each do |ht|
    ind = find_sound(with_sound(:channels, 8) do
                       8.times do |i|
                         locsig(make_locsig(:degree, i * 45, :output, $output), i, 0.5)
                       end
                     end.output)
    8.times do |chn|
      samps = channel2vct(0, 8, ind, chn)
      8.times do |k|
        if k == chn and fneq(samps[k], 0.5)
          snd_display("8 out %s chan %s samp %s (0.5): %s?",
                      mus_header_type2string(ht), chn, k, samps[k])
        end
        if k != chn and fneq(samps[k], 0.0)
          snd_display("8 out %s chan %s samp %s (0.0): %s?",
                      mus_header_type2string(ht), chn, k, samps[k])
        end
      end
    end
    close_sound(ind)
  end
  #
  gen = make_frame2file("fmv4.snd", 2, Mus_bshort, Mus_next)
  rev = make_frame2file("fmv4.reverb", 1, Mus_bshort, Mus_next)
  lc = make_locsig(60.0, :reverb, 0.1, :channels, 2, :output, gen, :revout, rev)
  100.times do |i| locsig(lc, i, 1.0) end
  if fneq(res = locsig_reverb_ref(lc, 0), 0.1)
    snd_display("locsig_reverb_ref: %s?", res)
  end
  locsig_reverb_set!(lc, 0, 0.3)
  if fneq(res = locsig_reverb_ref(lc, 0), 0.3)
    snd_display("locsig_reverb_set!: %s?", res)
  end
  locsig_reverb_set!(lc, 0, 0.2)
  if fneq(res = locsig_reverb_ref(lc, 0), 0.2)
    snd_display("locsig_reverb_set!: %s?", res)
  end
  mus_close(gen)
  mus_close(rev)
  v0 = make_vct(100)
  v1 = make_vct(100)
  v2 = make_vct(100)
  file2array("fmv4.snd", 0, 0, 100, v0)
  file2array("fmv4.snd", 1, 0, 100, v1)
  file2array("fmv4.reverb", 0, 0, 100, v2)
  if fneq(v2[0], 0.1)
    snd_display("locsig reverb: %s?", v2)
  end
  if fneq(2 * v0[0], v1[0])
    snd_display("locsig direct: %s %s?", v0[0], v1[0])
  end
  # 
  gen = make_frame2file("fmv4.snd", 4, Mus_bshort, Mus_next)
  rev = make_frame2file("fmv4.reverb", 4, Mus_bshort, Mus_next)
  lc = make_locsig(60.0, :reverb, 0.1, :channels, 4, :distance, 4.0, :output, gen, :revout, rev)
  print_and_check(lc,
                  "locsig",
                  "locsig chans 4, outn: [0.083 0.167 0.000 0.000], revn: [0.017 0.033 0.000 0.000], interp: linear")
  100.times do |i| locsig(lc, i, 1.0) end
  4.times do |i|
    locsig_reverb_set!(lc, i, i * 0.1)
    if fneq(res = locsig_reverb_ref(lc, i), i * 0.1)
      snd_display("locsig_reverb_set![%s]: %s?", i, res)
    end
  end
  print_and_check(lc,
                  "locsig",
                  "locsig chans 4, outn: [0.083 0.167 0.000 0.000], revn: [0.000 0.100 0.200 0.300], interp: linear")
  unless vct?(lc.data)
    snd_display("out data locsig: %s?", lc.data)
  end
  unless vct?(lc.xcoeffs)
    snd_display("rev data locsig: %s?", lc.xcoeffs)
  end
  xcs = lc.xcoeffs
  if fneq(res = mus_xcoeff(lc, 0), xcs[0])
    snd_display("locsig xcoeff: %s %s?", res, xcs[0])
  end
  if fneq(res = mus_xcoeff(lc, 1), 0.1)
    snd_display("locsig xcoeff 1: %s %s?", res, xcs[0])
  end
  mus_close(gen)
  mus_close(rev)
  # 
  print_and_check(make_locsig(160, :channels, 4),
                  "locsig",
                  "locsig chans 4, outn: [0.000 0.222 0.778 0.000], interp: linear")
  print_and_check(make_locsig(-200, :channels, 4),
                  "locsig",
                  "locsig chans 4, outn: [0.000 0.222 0.778 0.000], interp: linear")
  print_and_check(make_locsig(160, :channels, 4, :distance, 0.5),
                  "locsig",
                  "locsig chans 4, outn: [0.000 0.222 0.778 0.000], interp: linear")
  print_and_check(make_locsig(320, :channels, 4),
                  "locsig",
                  "locsig chans 4, outn: [0.556 0.000 0.000 0.444], interp: linear")
  print_and_check(make_locsig(-40, :channels, 4),
                  "locsig",
                  "locsig chans 4, outn: [0.556 0.000 0.000 0.444], interp: linear")
  print_and_check(make_locsig(320, :channels, 2),
                  "locsig",
                  "locsig chans 2, outn: [0.000 1.000], interp: linear")
  print_and_check(make_locsig(-40, :channels, 2),
                  "locsig",
                  "locsig chans 2, outn: [1.000 0.000], interp: linear")
  print_and_check(make_locsig(160, :channels, 4, :output, SoundData.new(4, 10)),
                  "locsig",
                  "locsig chans 4, outn: [0.000 0.222 0.778 0.000], interp: linear")
  print_and_check(make_locsig(0, :channels, 1, :output, Vct.new(10)),
                  "locsig",
                  "locsig chans 1, outn: [1.000], interp: linear")
  # 
  locsig_data = lambda do |g|
    make_vct!(g.channels) do |i| locsig_ref(g, i) end
  end
  gen = make_locsig(-0.1, :channels, 8)
  unless vequal(locsig_data.call(gen), vct(0.998, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.002))
    snd_display("locsig -0.1(8): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(-359.9, :channels, 8)
  unless vequal(locsig_data.call(gen), vct(0.998, 0.002, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("locsig -359.9(8): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(-359.9, :channels, 4)
  unless vequal(locsig_data.call(gen), vct(0.999, 0.001, 0.000, 0.000))
    snd_display("locsig -359.9(4): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(-360.1, :channels, 8)
  unless vequal(locsig_data.call(gen), vct(0.998, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.002))
    snd_display("locsig -360.1(8): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(-700, :channels, 8)
  unless vequal(locsig_data.call(gen), vct(0.556, 0.444, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("locsig -700(8): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(-700, :channels, 2)
  unless vequal(locsig_data.call(gen), vct(1.000, 0.000))
    snd_display("locsig -700(2): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(20, :channels, 2)
  unless vequal(locsig_data.call(gen), vct(0.778, 0.222))
    snd_display("locsig 20(2): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(123456.0, :channels, 8)
  unless vequal(locsig_data.call(gen), vct(0.467, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.533))
    snd_display("locsig 123456(8): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(336.0, :channels, 8)
  unless vequal(locsig_data.call(gen), vct(0.467, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.533))
    snd_display("locsig 336(8): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(-123456.0, :channels, 8)
  unless vequal(locsig_data.call(gen), vct(0.467, 0.533, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("locsig -123456(8): %s?", locsig_data.call(gen))
  end
  gen = make_locsig(24.0, :channels, 8)
  unless vequal(locsig_data.call(gen), vct(0.467, 0.533, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("locsig 24(8): %s?", locsig_data.call(gen))
  end
  #
  locsig_scalers = lambda do |chans, degree, type|
    if chans == 1
      vct(1.0)
    else
      deg = chans == 2 ? [0.0, [90.0, degree].min].max : degree.divmod(360.0)[1]
      degs_per_chan = chans == 2 ? 90.0 : (360.0 / chans)
      pos = deg / degs_per_chan
      left = pos.floor
      right = (left + 1) % chans
      frac = pos - left
      v = make_vct(chans)
      if type == Mus_interp_linear
        v[left] = 1.0 - frac
        v[right] = frac
      else
        ldeg = HALF_PI * (0.5 - frac)
        norm = sqrt(2.0) * 0.5
        c = cos(ldeg)
        s = sin(ldeg)
        v[left] = norm * (c + s)
        v[right] = norm * (c - s)
      end
      v
    end
  end
  [0, 1, 2, 4].each do |rev_chans|
    delete_file("test.reverb")
    revfile = if rev_chans > 0
                make_frame2file("test.reverb", rev_chans, Mus_bshort, Mus_next)
              else
                false
              end
    [Mus_interp_linear, Mus_interp_sinusoidal].each do |type|
      set_locsig_type(type)
      if locsig_type != type
        snd_display("locsig_type: %s %s?", type, locsig_type)
      end
      [0.0, 45.0, 90.0, 1234.0].each do |deg|
        gen = make_locsig(deg, :channels, 1, :revout, revfile, :reverb, 0.1, :distance, 2.0)
        revs = revfile ? locsig_scalers.call(rev_chans, deg, type) : []
        if gen.channels != 1
          snd_display("locsig %s: %s?", deg, gen)
        end
        if fneq(locsig_ref(gen, 0), 0.5)
          snd_display("locsig scaler[%s] %s: %s?", type, deg, locsig_ref(gen, 0))
        end
        revs.each_with_index do |val, i|
          if fneq(res1 = locsig_reverb_ref(gen, i), res2 = ((0.1 / sqrt(2.0)) * val))
            snd_display("mono locrev[%s] %s at %s: %s %s?", type, gen, deg, res1, res2)
            break
          end
        end
      end
      [Mus_interp_linear, Mus_interp_sinusoidal].each do |ltype|
        [0.0, 45.0, 90.0, 1234.0].each do |deg|
          gen = make_locsig(deg, :channels, 1, :type, ltype)
          if gen.channels != 1
            snd_display("locsig %s: %s?", deg, gen)
          end
          if fneq(res = locsig_ref(gen, 0), 1.0)
            snd_display("locsig[%s] scaler %s: %s?", ltype, deg, res)
          end
        end
      end
      [2, 3, 4, 5, 8, 12, 16, 24].each do |chans|
        [0.0, 45.0, 90.0, 120.0, 180.0, 275.0, 315.0, 300.0, 15.0, 1234.0].each do |deg|
          gen = make_locsig(deg, :channels, chans, :revout, revfile, :reverb, 0.1)
          if gen.channels != chans
            snd_display("multi locsig %s: %s?", deg, gen)
            break
          end
          locsig_scalers.call(chans, deg, type).each_with_index do |val, i|
            if fneq(res = locsig_ref(gen, i), val)
              snd_display("locrev[%s] %s at %s: %s %s?", type, gen, deg, res, val)
              break
            end
          end
          (revfile ? locsig_scalers.call(rev_chans, deg, type) : []).each_with_index do |val, i|
            if fneq(res1 = locsig_reverb_ref(gen, i), res2 = 0.1 * val)
              snd_display("locrev[%s] %s at %s: %s %s?", type, gen, deg, res1, res2)
              break
            end
          end
        end
      end
      [2, 3, 4, 5, 8, 12, 16, 24].each do |chans|
        [Mus_interp_linear, Mus_interp_sinusoidal].each do |ltype|
          [0.0, 45.0, 90.0, 120.0, 180.0, 275.0, 315.0, 300.0, 15.0, 1234.0].each do |deg|
            gen = make_locsig(deg, :channels, chans, :type, ltype,
                              :revout, revfile, :reverb, 0.1)
            if gen.channels != chans
              snd_display("stereo locsig %s: %s?", deg, gen)
              break
            end
            locsig_scalers.call(chans, deg, ltype).each_with_index do |val, i|
              if fneq(res = locsig_ref(gen, i), val)
                snd_display("locrev[%s] %s at %s: %s %s?", ltype, gen, deg, res, val)
                break
              end
            end
            (revfile ? locsig_scalers.call(rev_chans,deg,ltype): []).each_with_index do |val, i|
              if fneq(res1 = locsig_reverb_ref(gen, i), res2 = 0.1 * val)
                snd_display("locrev[%s] %s at %s: %s %s?", ltype, gen, deg, res1, res2)
                break
              end
            end
          end
        end
      end
    end
    revfile and mus_close(revfile)
  end
  # 
  set_locsig_type(Mus_interp_linear)
  outp = make_sound_data(1, 10)
  gen = make_locsig(0.0, :output, outp)
  if (res = mus_channels(gen)) != 1
    snd_display("make_locsig->sd chans (1)", res)
  end
  10.times do |i| locsig(gen, i, 1.0) end
  unless vequal(res = sound_data2vct(outp, 0), Vct.new(10, 1.0))
    snd_display("locsig->sd chan 0: %s?", res)
  end
  outp = make_sound_data(2, 10)
  gen = make_locsig(0.0, :output, outp)
  if (res = mus_channels(gen)) != 2
    snd_display("make_locsig->sd chans (2)", res)
  end
  10.times do |i| locsig(gen, i, 1.0) end
  unless vequal(res = sound_data2vct(outp, 0), Vct.new(10, 1.0))
    snd_display("locsig->sd chan 0: %s?", res)
  end
  unless vequal(res = sound_data2vct(outp, 1), Vct.new(10, 0.0))
    snd_display("locsig->sd chan 1: %s?", res)
  end
  outp = make_sound_data(2, 10)
  gen = make_locsig(45.0, :output, outp)
  if (res = mus_channels(gen)) != 2
    snd_display("make_locsig->sd chans (2)", res)
  end
  10.times do |i| locsig(gen, i, 1.0) end
  unless vequal(res = sound_data2vct(outp, 0), Vct.new(10, 0.5))
    snd_display("locsig->sd chan 0 (0.5): %s?", res)
  end
  unless vequal(res = sound_data2vct(outp, 1), Vct.new(10, 0.5))
    snd_display("locsig->sd chan 1 (0.5): %s?", res)
  end
  10.times do |i| locsig(gen, i, 0.5) end
  unless vequal(res = sound_data2vct(outp, 0), Vct.new(10, 0.75))
    snd_display("locsig->sd chan 0 (0.75): %s?", res)
  end
  unless vequal(res = sound_data2vct(outp, 1), Vct.new(10, 0.75))
    snd_display("locsig->sd chan 1 (0.75): %s?", res)
  end
  #
  outp = Vct.new(10)
  gen = make_locsig(0.0, :output, outp)
  if (res = mus_channels(gen)) != 1
    snd_display("make_locsig->vct chans (1)", res)
  end
  10.times do |i| locsig(gen, i, 1.0) end
  unless vequal(outp, Vct.new(10, 1.0))
    snd_display("locsig->vct chan 0: %s?", outp)
  end
  10.times do |i| locsig(gen, i, 0.5) end
  unless vequal(outp, Vct.new(10, 1.5))
    snd_display("locsig->vct chan 0: %s?", outp)
  end
  outp = Vct.new(10)
  gen = make_locsig(45.0, :channels, 2, :output, outp)
  if (res = mus_channels(gen)) != 2
    snd_display("make_locsig->vct chans (2)", res)
  end
  10.times do |i| locsig(gen, i, 1.0) end
  unless vequal(outp, Vct.new(10, 0.5))
    snd_display("locsig(2)->vct chan 0: %s?", outp)
  end
  10.times do |i| locsig(gen, i, 0.5) end
  unless vequal(outp, Vct.new(10, 0.75))
    snd_display("locsig(2)->vct chan 0: %s?", outp)
  end
  #
  outp = make_sound_data(4, 10)
  gen = make_locsig(135.0, :output, outp)
  if (res = mus_channels(gen)) != 4
    snd_display("make_locsig->sd chans (4)", res)
  end
  10.times do |i| locsig(gen, i, 1.0) end
  unless vequal(res = sound_data2vct(outp, 0), Vct.new(10, 0.0))
    snd_display("locsig(4)->sd chan 0 (0.5): %s?", res)
  end
  unless vequal(res = sound_data2vct(outp, 1), Vct.new(10, 0.5))
    snd_display("locsig(4)->sd chan 1 (0.5): %s?", res)
  end
  unless vequal(res = sound_data2vct(outp, 2), Vct.new(10, 0.5))
    snd_display("locsig(4)->sd chan 2 (0.5): %s?", res)
  end
  unless vequal(res = sound_data2vct(outp, 3), Vct.new(10, 0.0))
    snd_display("locsig(4)->sd chan 3 (0.5): %s?", res)
  end
  # 
  set_mus_array_print_length(8)
  outf1 = make_frame2file("fmv.snd", 1, Mus_bshort, Mus_next)
  outf4 = make_frame2file("fmv1.snd", 4, Mus_bshort, Mus_next)
  revf = make_frame2file("fmv2.snd", 1, Mus_bshort, Mus_next)
  start = 0
  len = 1000
  dur = 1.0
  gen1 = make_move_sound([start, len, 1, 0,
                          make_delay(32),
                          make_env([0, 0, 1, 1], :length, 1001),
                          make_env([0, 0, 1, 1], :length, 1001),
                          [make_delay(32)],
                          [make_env([0, 0, 1, 1], :length, 1001)],
                          false,
                          [0, 1]],
                         outf1)
  gen2 = make_move_sound([start, len, 4, 0,
                          make_delay(12),
                          make_env([0, 0, 10, 1], :duration, dur),
                          false,
                          Array.new(4, false),
                          [make_env([0, 0, 1, 1, 2, 0, 3, 0, 4, 0], :duration, dur),
                           make_env([0, 0, 1, 0, 2, 1, 3, 0, 4, 0], :duration, dur),
                           make_env([0, 0, 1, 0, 2, 0, 3, 1, 4, 0], :duration, dur),
                           make_env([0, 0, 1, 0, 2, 0, 3, 0, 4, 1], :duration, dur)],
                          false,
                          [0, 1, 2, 3]],
                         outf4)
  gen3 = make_move_sound([start, len, 1, 1,
                          make_delay(32),
                          make_env([0, 0, 1, 1], :length, 1001),
                          make_env([0, 0, 1, 1], :length, 1001),
                          [make_delay(32)],
                          [make_env([0, 0, 1, 1], :length, 1001)],
                          [make_env([0, 1, 1, 1], :length, 1001)],
                          [0, 1]],
                         outf1, revf)
  print_and_check(gen1,
                  "move-sound",
                  "move-sound start: 0, end: 1000, out chans 1, rev chans: 0
  doppler delay line[32, step]: [0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000...(0: 0.000, 0: 0.000)]
  doppler env linear, pass: 0 (dur: 1001), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 1.000]
  global reverb env linear, pass: 0 (dur: 1001), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 1.000]
  out_delays[1]:
    [0]: delay line[32, step]: [0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000...(0: 0.000, 0: 0.000)]
  out_envs[1]:
    [0]: env linear, pass: 0 (dur: 1001), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 1.000]
  rev_envs: nil
  out_map[1]: (0)
  free: arrays: true, gens: false
")
  print_and_check(gen2,
                  "move-sound",
                  "move-sound start: 0, end: 1000, out chans 4, rev chans: 0
  doppler delay line[12, step]: [0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000...(0: 0.000, 0: 0.000)]
  doppler env linear, pass: 0 (dur: 22050), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 10.000 1.000]
  global reverb null
  out_delays[4]:
    [0]: nil
    [1]: nil
    [2]: nil
    [3]: nil
  out_envs[4]:
    [0]: env linear, pass: 0 (dur: 22050), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 1.000 2.000 0.000 3.000 0.000...(0: 0.000, 8: 4.000)]
    [1]: env linear, pass: 0 (dur: 22050), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 0.000 2.000 1.000 3.000 0.000...(0: 0.000, 8: 4.000)]
    [2]: env linear, pass: 0 (dur: 22050), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 0.000 2.000 0.000 3.000 1.000...(0: 0.000, 8: 4.000)]
    [3]: env linear, pass: 0 (dur: 22050), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 0.000 2.000 0.000 3.000 0.000...(0: 0.000, 8: 4.000)]
  rev_envs: nil
  out_map[4]: (0 1 2 3)
  free: arrays: true, gens: false
")
  print_and_check(gen3,
                  "move-sound",
                  "move-sound start: 0, end: 1000, out chans 1, rev chans: 1
  doppler delay line[32, step]: [0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000...(0: 0.000, 0: 0.000)]
  doppler env linear, pass: 0 (dur: 1001), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 1.000]
  global reverb env linear, pass: 0 (dur: 1001), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 1.000]
  out_delays[1]:
    [0]: delay line[32, step]: [0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000...(0: 0.000, 0: 0.000)]
  out_envs[1]:
    [0]: env linear, pass: 0 (dur: 1001), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 0.000 1.000 1.000]
  rev_envs[1]:
    [0]: env linear, pass: 0 (dur: 1001), index: 0, scaler: 1.0000, offset: 0.0000, data: [0.000 1.000 1.000 1.000]
  out_map[1]: (0)
  free: arrays: true, gens: false
")
  #
  unless move_sound?(gen1)
    snd_display("not move_sound: %s?", gen1)
  end
  if gen1 == gen2
    snd_display("move_sounds are equal: %s == %s?", gen1, gen2)
  end
  if gen1.channels != 1
    snd_display("mus_channels move_sound (1): %s?", gen1.channels)
  end
  if gen2.channels != 4
    snd_display("mus_channels move_sound (4): %s?", gen2.channels)
  end
  gen1.reset                    # no-op
  #
  v = Vct.new(10) do |i|
    move_sound(gen1, i, 0.5) + gen2.run(i, 0.25) + move_sound(gen3, i, 0.125) 
  end
  unless vequal(v, Vct.new(10, 0.875))
    snd_display("move_sound output: %s?", v)
  end
  if (res = Snd.catch do
        make_move_sound([0, 1000, 1, 0, make_oscil(32), make_env([0, 0, 1, 1], :length, 1001),
                         make_env([0, 0, 1, 1], :length, 1001), [make_delay(32)],
                         [make_env([0, 0, 1, 1], :length, 1001)], false, [0, 1]],
                        outf1)
      end).first != :wrong_type_arg
    snd_display("make_move_sound bad doppler delay: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_move_sound([0, 1000, 1, 0, make_oscil(32), make_env([0, 0, 1, 1], :length, 1001),
                         make_env([0, 0, 1, 1], :length, 1001), [make_delay(32)]],
                        outf1)
      end).first != :wrong_type_arg
    snd_display("make_move_sound truncated list: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_move_sound([0, 1000, 1, 0, make_delay(32), make_env([0, 0, 1, 1], :length, 1001),
                         false, [false],
                         [make_env([0, 0, 1, 1], :length, 1001)], false, false],
                        outf1)
      end).first != :wrong_type_arg
    snd_display("make_move_sound no out map: %s", res.inspect)
  end
  mus_close(outf1)
  mus_close(outf4)
  mus_close(revf)
  delete_file("fmv.snd")
  delete_file("fmv1.snd")
  delete_file("fmv2.snd")
  mus_sound_prune
end

def test_08_15
  rd = make_readin("oboe.snd", 0, 2000)
  gen = make_src(:srate, 2.0, :input, rd)
  gen1 = make_src(:srate, 2.0, :input, make_readin("oboe.snd", 0, 2000))
  gen2 = make_src(:srate, 0.0)
  print_and_check(gen, "src", "src width: 10, x: 0.000, incr: 2.000, sinc table len: 10000")
  v0 = Vct.new(10) do
    src(gen)
  end
  v1 = make_vct(10)
  vct_map!(v1, lambda do ||
               src?(gen1) ? src(gen1) : -1.0
           end)
  unless vequal(v0, v1)
    snd_display("run src: %s %s?", v0, v1)
  end
  unless src?(gen)
    snd_display("%s not src?", gen)
  end
  req1 = 0.001
  req2 = 0.021
  snd_test_neq(v0[1], 0.001, "src output v0[1]")
  snd_test_neq(v0[7], 0.021, "src output v0[7]")
  if fneq(gen.increment, 2.0)
    snd_display("src increment: %s?", gen.increment)
  end
  if fneq(gen2.increment, 0.0)
    snd_display("src 0.0 increment: %s?", gen2.increment)
  end
  if fneq(rd.increment, 1.0)
    snd_display("readin increment: %s?", rd.increment)
  end
  if gen.length != 10
    snd_display("src length: %s?", gen.length)
  end
  gold = gen
  gen = make_src(lambda do |dir| 0.0 end)
  if gen.eql? gold
    snd_display("src eql? %s %s?", gen, gold)
  end
  if (res = Snd.catch do make_src(:width, -1) end).first != :out_of_range
    snd_display("make_src bad width: %s", res.inspect)
  end
  #
  s1 = make_src(lambda do |y| 1.0 end, 2.0)
  src(s1, 25.0)
  src(s1, 25.0)
  src(s1, 125.0)
  src(s1, -25.0)
  src(s1, -125.0)
  10.times do |i| make_src(lambda do |y| 1.0 end, 1.5, :width, 5 + i * 10) end
  clear_sincs
  #
  ctr = 0.0
  gen = make_src(:srate, 2.0, :input, lambda do |dir|
                   val = ctr
                   ctr += 1
                   val
                 end)
  v0 = make_vct!(10) do src(gen, 0.0) end
  ctr = 0.0
  gen.reset
  v0.each_with_index do |old_val, i|
    if fneq(old_val, new_val = src(gen, 0.0))
      snd_display("reset src %s: %s %s?", i, old_val, new_val)
    end
  end
  #
  gen = make_granulate(:expansion, 2.0)
  gen1 = make_granulate(:expansion, 2.0)
  rd = make_readin("oboe.snd", 0, 4000, 1, 2048)
  rd1 = make_readin(:file, "oboe.snd",
                    :channel, 0,
                    :start, 4000,
                    :direction, 1,
                    :size, mus_file_buffer_size)
  print_and_check(gen,
                  "granulate",
                  "granulate expansion: 2.000 (551/1102), scaler: 0.600, length: 0.150 secs (3308 samps), ramp: 0.060")
  v0 = make_vct!(1000) do
    granulate(gen, lambda do |dir| readin(rd) end)
  end
  v1 = make_vct(1000)
  vct_map!(v1, lambda do | |
             granulate?(gen1) ? granulate(gen1, lambda do |dir| readin(rd1) end) : -1.0
           end)
  if (worst = (vct_peak(v0) - vct_peak(v1)).abs) > 0.01
    snd_display("run granulate: %s?", worst)
  end
  genx = gen1
  unless genx.eql?(gen1)
    snd_display("granulate eql? %s %s %s", genx, gen1, genx.eql?(gen1))
  end
  if gen.eql? gen1
    snd_display("granulate eql? %s %s?", gen, gen1)
  end
  if vct_peak(v0).zero?
    snd_display("granulate output peak: %s?", vct_peak(v0))
  end
  unless granulate?(gen)
    snd_display("%s not granulate?", gen)
  end
  if fneq(gen.increment, 2.0)
    snd_display("granulate increment: %s?", gen.increment)
  end
  if fneq(gen.scaler, 0.6)
    snd_display("granulate scaler: %s?", gen.scaler)
  end
  if ffneq(gen.frequency, 0.05) # okay
    snd_display("granulate frequency: %s?", gen.frequency)
  end
  if gen.ramp != 1323
    snd_display("granulate ramp: %s?", gen.ramp)
  end
  if gen.length != 3308
    snd_display("granulate length: %s?", gen.length)
  end
  if gen.hop != 1102
    snd_display("granulate hop: %s?", gen.hop)
  end
  gen.hop = 1000
  if gen.hop != 1000
    snd_display("granulate set_hop: %s?", gen.hop)
  end
  gen.ramp = 1000
  if gen.ramp != 1000
    snd_display("granulate set_ramp: %s?", gen.ramp)
  end
  gen.length = 3000
  if gen.length != 3000
    snd_display("granulate set_length: %s?", gen.length)
  end
  gen.increment = 3.0
  if ffneq(gen.increment, 3.0)  # okay
    snd_display("granulate set_increment: %s?", gen.increment)
  end
  gen.location = 1
  if gen.location != 1
    snd_display("granulate set_location: %s?", gen.location)
  end
  gen.frequency = 0.1
  if fneq(gen.frequency, 0.1)
    snd_display("granulate set_frequency: %s?", gen.frequency)
  end
  # 
  if (res = Snd.catch do make_granulate(lambda do |a, b| a end) end).first != :bad_arity
    snd_display("make_granulate bad func: %s", res.inspect)
  end
  if (res = Snd.catch do make_granulate(:hop, 35.0, :length, 35.0) end).first != :out_of_range
    snd_display("make_granulate bad sizes: %s", res.inspect)
  end
  #
  ind = open_sound("oboe.snd")
  mx = maxamp(ind, 0)
  rd = make_sampler(0)
  grn = make_granulate(:expansion, 2.0,
                       :input, lambda do |dir| rd.call end,
                       :edit, lambda do |g|
                         g.data.scale!(2.0)
                         0
                       end)
  map_channel(lambda do |y| granulate(grn) end)
  if (maxamp / mx) < 1.4 or (mx / maxamp) > 2.5
    snd_display("gran edit 2* (0): %s %s?", mx, maxamp)
  end
  undo_edit
  rd = make_sampler(0)
  grn = make_granulate(:expansion, 2.0,
                       :input, lambda do |dir| rd.call end,
                       :edit, lambda do |g|
                         g.data.scale!(4.0)
                         0
                       end)
  map_channel(lambda do |y| granulate(grn) end)
  if (maxamp / mx) < 3.0 or (mx / maxamp) > 6.0
    snd_display("gran edit 4* (0): %s %s?", mx, maxamp)
  end
  revert_sound(ind)
  rd = make_sampler(0)
  grn = make_granulate(:expansion, 2.0,
                       :edit, lambda do |g|
                         g.data.scale!(2.0)
                         0
                       end)
  map_channel(lambda do |y| granulate(grn, lambda do |dir| rd.call end) end)
  if (maxamp / mx) < 1.4 or (mx / maxamp) > 2.5
    snd_display("gran edit 2* (1): %s %s?", mx, maxamp)
  end
  undo_edit
  rd = make_sampler(0)
  grn = make_granulate(:expansion, 2.0,
                       :edit, lambda do |g|
                         g.data.scale!(4.0)
                         0
                       end)
  map_channel(lambda do |y| granulate(grn, lambda do |dir| rd.call end) end)
  if (maxamp / mx) < 2.9 or (mx / maxamp) > 6.0
    snd_display("gran edit 4* (1): %s %s?", mx, maxamp)
  end
  revert_sound(ind)
  rd = make_sampler(0)
  grn = make_granulate(:expansion, 2.0)
  map_channel(lambda do |y|
                granulate(grn,
                          lambda do |dir| rd.call end,
                          lambda do |g|
                            g.data.scale!(2.0)
                            0
                          end)
                end)
  if (maxamp / mx) < 1.4 or (mx / maxamp) > 2.5
    snd_display("gran edit 2* (2): %s %s?", mx, maxamp)
  end
  undo_edit
  rd = make_sampler(0)
  grn = make_granulate(:expansion, 2.0)
  map_channel(lambda do |y|
                granulate(grn,
                          lambda do |dir| rd.call end,
                          lambda do |g|
                            g.data.scale!(4.0)
                            0
                          end)
                end)
  if (maxamp / mx) < 3.0 or (mx / maxamp) > 6.0
    snd_display("gran edit 4* (2): %s %s?", mx, maxamp)
  end
  close_sound(ind)
  ind = open_sound("oboe.snd")
  grn = make_granulate(:expansion, 2.0, :length, 0.01, :hop, 0.05)
  rd = make_sampler(0)
  map_channel(lambda do |y| granulate(grn, lambda do |dir| rd.call end) end)
  if (res = maxamp) > 0.2
    snd_display("trouble in granulate len 0.01 hop 0.05: %s?", res)
  end
  undo_edit
  grn = make_granulate(:expansion, 2.0, :length, 0.04, :hop, 0.05)
  rd = make_sampler(0)
  map_channel(lambda do |y| granulate(grn, lambda do |dir| rd.call end) end)
  if (res = maxamp) > 0.2
    snd_display("trouble in granulate len 0.04 hop 0.05: %s?", res)
  end
  undo_edit
  grn = make_granulate(:expansion, 2.0, :length, 0.01, :hop, 0.25)
  rd = make_sampler(0)
  map_channel(lambda do |y| granulate(grn, lambda do |dir| rd.call end) end)
  if (res = maxamp) > 0.2
    snd_display("trouble in granulate len 0.01 hop 0.25: %s?", res)
  end
  undo_edit
  grn = make_granulate(:expansion, 2.0, :length, 0.4, :hop, 0.5)
  rd = make_sampler(0)
  map_channel(lambda do |y| granulate(grn, lambda do |dir| rd.call end) end)
  if (res = maxamp) > 0.2
    snd_display("trouble in granulate len 0.4 hop 0.5: %s?", res)
  end
  undo_edit
  close_sound(ind)
end

def test_08_16
  ind = new_sound(:size, 1000)
  gen = make_granulate(:jitter, 0.0, :hop, 0.004, :length, 0.001)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.06)
    snd_display("gran 0 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.007, 0.013, 0.020, 0.027, 0.033, 0.040, 0.047,
                    0.053, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.053,
                    0.047, 0.040, 0.033, 0.027, 0.020, 0.013, 0.007, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 0 data: %s?", res)
  end
  unless vequal(res = channel2vct(85, 30),
                vct(0.000, 0.000, 0.000, 0.000, 0.007, 0.013, 0.020, 0.027,
                    0.033, 0.040, 0.047, 0.053, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.053, 0.047, 0.040, 0.033, 0.027, 0.020,
                    0.013, 0.007, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 0 data 85: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.002, :length, 0.001)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.06)
    snd_display("gran 1 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.007, 0.013, 0.020, 0.027, 0.033, 0.040, 0.047,
                    0.053, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.053,
                    0.047, 0.040, 0.033, 0.027, 0.020, 0.013, 0.007, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 1 data: %s?", res)
  end
  unless vequal(res = channel2vct(40, 30),
                vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.007, 0.013, 0.020,
                    0.027, 0.033, 0.040, 0.047, 0.053, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.053, 0.047, 0.040, 0.033, 0.027,
                    0.020, 0.013, 0.007, 0.000, 0.000, 0.000))
    snd_display("gran 1 data 40: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.002, :length, 0.001, :ramp, 0.1)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.06)
    snd_display("gran 2 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.030, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.030, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 2 data: %s?", res)
  end
  unless vequal(res = channel2vct(40, 30),
                vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.030, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.030, 0.000, 0.000, 0.000))
    snd_display("gran 2 data 40: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.002, :length, 0.001, :ramp, 0.5)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.06)
    snd_display("gran 3 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.005, 0.011, 0.016, 0.022, 0.027, 0.033, 0.038,
                    0.044, 0.049, 0.055, 0.060, 0.060, 0.055, 0.049, 0.044,
                    0.038, 0.033, 0.027, 0.022, 0.016, 0.011, 0.005, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 3 data: %s?", res)
  end
  unless vequal(res = channel2vct(85, 30),
                vct(0.000, 0.000, 0.000, 0.000, 0.005, 0.011, 0.016, 0.022,
                    0.027, 0.033, 0.038, 0.044, 0.049, 0.055, 0.060, 0.060,
                    0.055, 0.049, 0.044, 0.038, 0.033, 0.027, 0.022, 0.016,
                    0.011, 0.005, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 3 data 85: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.001, :length, 0.001, :ramp, 0.5)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.06)
    snd_display("gran 4 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.005, 0.011, 0.016, 0.022, 0.027, 0.033, 0.038,
                    0.044, 0.049, 0.055, 0.060, 0.060, 0.055, 0.049, 0.044,
                    0.038, 0.033, 0.027, 0.022, 0.016, 0.011, 0.005, 0.005,
                    0.011, 0.016, 0.022, 0.027, 0.033, 0.038))
    snd_display("gran 4 data: %s?", res)
  end
  unless vequal(res = channel2vct(85, 30),
                vct(0.022, 0.016, 0.011, 0.005, 0.005, 0.011, 0.016, 0.022,
                    0.027, 0.033, 0.038, 0.044, 0.049, 0.055, 0.060, 0.060,
                    0.055, 0.049, 0.044, 0.038, 0.033, 0.027, 0.022, 0.016,
                    0.011, 0.005, 0.005, 0.011, 0.016, 0.022))
    snd_display("gran 4 data 85: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.001, :length, 0.001, :ramp, 0.25, :scaler, 1.0)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.1)
    snd_display("gran 5 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.020, 0.040, 0.060, 0.080, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.080, 0.060, 0.040, 0.020, 0.020,
                    0.040, 0.060, 0.080, 0.100, 0.100, 0.100))
    snd_display("gran 5 data: %s?", res)
  end
  unless vequal(res = channel2vct(85, 30),
                vct(0.080, 0.060, 0.040, 0.020, 0.020, 0.040, 0.060, 0.080,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.080, 0.060,
                    0.040, 0.020, 0.020, 0.040, 0.060, 0.080))
    snd_display("gran 5 data 85: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.001, :length, 0.002, :ramp, 0.5, :scaler, 1.0)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.105)
    snd_display("gran 6 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.005, 0.009, 0.014, 0.018, 0.023, 0.027, 0.032,
                    0.036, 0.041, 0.045, 0.050, 0.055, 0.059, 0.064, 0.068,
                    0.073, 0.077, 0.082, 0.086, 0.091, 0.095, 0.100, 0.105,
                    0.105, 0.105, 0.105, 0.105, 0.105, 0.105))
    snd_display("gran 6 data: %s?", res)
  end
  unless vequal(res = channel2vct(85, 30),
                vct(0.105, 0.105, 0.105, 0.105, 0.105, 0.105, 0.105, 0.105,
                    0.105, 0.105, 0.105, 0.105, 0.105, 0.105, 0.105, 0.105,
                    0.105, 0.105, 0.105, 0.105, 0.105, 0.105, 0.105, 0.105,
                    0.105, 0.105, 0.105, 0.105, 0.105, 0.105))
    snd_display("gran 6 data 85: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.001, :length, 0.005, :ramp, 0.5, :scaler, 1.0)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.264)
    snd_display("gran 7 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.002, 0.004, 0.005, 0.007, 0.009, 0.011, 0.013,
                    0.015, 0.016, 0.018, 0.020, 0.022, 0.024, 0.025, 0.027,
                    0.029, 0.031, 0.033, 0.035, 0.036, 0.038, 0.040, 0.044,
                    0.047, 0.051, 0.055, 0.058, 0.062, 0.065))
    snd_display("gran 7 data: %s?", res)
  end
  unless vequal(res = channel2vct(85, 30),
                vct(0.244, 0.244, 0.244, 0.244, 0.245, 0.247, 0.249, 0.251,
                    0.253, 0.255, 0.256, 0.258, 0.260, 0.262, 0.264, 0.264,
                    0.262, 0.260, 0.258, 0.256, 0.255, 0.253, 0.251, 0.249,
                    0.247, 0.245, 0.245, 0.247, 0.249, 0.251))
    snd_display("gran 7 data 85: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.01, :length, 0.001, :ramp, 0.5,
                       :scaler, 1.0, :expansion, 2.0)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.1)
    snd_display("gran 8 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.009, 0.018, 0.027, 0.036, 0.045, 0.055, 0.064,
                    0.073, 0.082, 0.091, 0.100, 0.100, 0.091, 0.082, 0.073,
                    0.064, 0.055, 0.045, 0.036, 0.027, 0.018, 0.009, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 8 data: %s?", res)
  end
  unless vequal(res = channel2vct(220, 30),
                vct(0.000, 0.009, 0.018, 0.027, 0.036, 0.045, 0.055, 0.064,
                    0.073, 0.082, 0.091, 0.100, 0.100, 0.091, 0.082, 0.073,
                    0.064, 0.055, 0.045, 0.036, 0.027, 0.018, 0.009, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 8 data 220: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.01, :length, 0.001, :ramp, 0.5,
                       :scaler, 1.0, :expansion, 0.5)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.1)
    snd_display("gran 9 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.000, 0.009, 0.018, 0.027, 0.036, 0.045, 0.055, 0.064,
                    0.073, 0.082, 0.091, 0.100, 0.100, 0.091, 0.082, 0.073,
                    0.064, 0.055, 0.045, 0.036, 0.027, 0.018, 0.009, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 9 data: %s?", res)
  end
  unless vequal(res = channel2vct(220, 30),
                vct(0.000, 0.009, 0.018, 0.027, 0.036, 0.045, 0.055, 0.064,
                    0.073, 0.082, 0.091, 0.100, 0.100, 0.091, 0.082, 0.073,
                    0.064, 0.055, 0.045, 0.036, 0.027, 0.018, 0.009, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("gran 9 data 220: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.001, :length, 0.005, :ramp, 0.5, :scaler, 1.0)
  map_channel(lambda do |y|
                granulate(gen,
                          lambda do |dir| 0.1 end,
                          lambda do |g|
                            g.data.map! do |val| val *= 2.0 end
                            0
                          end)
              end)
  if fneq(res = maxamp, 2 * 0.264)
    snd_display("gran 10 max: %s?", res)
  end
  unless vequal(vct_scale!(res = channel2vct(0, 30), 0.5),
                vct(0.000, 0.002, 0.004, 0.005, 0.007, 0.009, 0.011, 0.013,
                    0.015, 0.016, 0.018, 0.020, 0.022, 0.024, 0.025, 0.027,
                    0.029, 0.031, 0.033, 0.035, 0.036, 0.038, 0.040, 0.044,
                    0.047, 0.051, 0.055, 0.058, 0.062, 0.065))
    snd_display("gran 10 data: %s?", res)
  end
  unless vequal(vct_scale!(res = channel2vct(85, 30), 0.5),
                vct(0.244, 0.244, 0.244, 0.244, 0.245, 0.247, 0.249, 0.251,
                    0.253, 0.255, 0.256, 0.258, 0.260, 0.262, 0.264, 0.264,
                    0.262, 0.260, 0.258, 0.256, 0.255, 0.253, 0.251, 0.249,
                    0.247, 0.245, 0.245, 0.247, 0.249, 0.251))
    snd_display("gran 10 data 85: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.005, :length, 0.002, :ramp, 0.0, :scaler, 1.0)
  forward = true
  ctr = -0.5
  incr = 0.001
  map_channel(lambda do |y|
                granulate(gen,
                          lambda do |dir|
                            ctr += incr
                            ctr
                          end,
                          lambda do |g|
                            len = g.length
                            if forward
                              forward = false
                            else
                              forward = true
                              vct_reverse!(g.data, len)
                            end
                            len
                          end)
              end)
  if (res = maxamp) > 0.6
    snd_display("gran 11 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(-0.499, -0.498, -0.497, -0.496, -0.495, -0.494, -0.493, -0.492,
                    -0.491, -0.490, -0.489, -0.488, -0.487, -0.486, -0.485, -0.484,
                    -0.483, -0.482, -0.481, -0.480, -0.479, -0.478, -0.477, -0.476,
                    -0.475, -0.474, -0.473, -0.472, -0.471, -0.470))
    snd_display("gran 11 data: %s?", res)
  end
  unless vequal(res = channel2vct(100, 30),
                vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, -0.345, -0.346, -0.347, -0.348, -0.349, -0.350,
                    -0.351, -0.352, -0.353, -0.354, -0.355, -0.356, -0.357, -0.358,
                    -0.359, -0.360, -0.361, -0.362, -0.363, -0.364))
    snd_display("gran 11 data 100: %s?", res)
  end
  undo_edit
  ctr = -0.5
  incr = 0.001
  gen = make_granulate(:jitter, 0.0, :hop, 0.005, :length, 0.002, :ramp, 0.0, :scaler, 1.0,
                       :input, lambda do |dir|
                         ctr += incr
                         ctr
                       end)
  map_channel(lambda do |y| granulate(gen) end)
  if (res = maxamp) > 0.6
    snd_display("gran 12 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(-0.499, -0.498, -0.497, -0.496, -0.495, -0.494, -0.493, -0.492,
                    -0.491, -0.490, -0.489, -0.488, -0.487, -0.486, -0.485, -0.484,
                    -0.483, -0.482, -0.481, -0.480, -0.479, -0.478, -0.477, -0.476,
                    -0.475, -0.474, -0.473, -0.472, -0.471, -0.470))
    snd_display("gran 12 data: %s?", res)
  end
  unless vequal(res = channel2vct(100, 30),
                vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, -0.389, -0.388, -0.387, -0.386, -0.385, -0.384,
                    -0.383, -0.382, -0.381, -0.380, -0.379, -0.378, -0.377, -0.376,
                    -0.375, -0.374, -0.373, -0.372, -0.371, -0.370))
    snd_display("gran 12 data 100: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.001, :length, 0.005, :ramp, 0.5, :scaler, 1.0,
                       :input, lambda do |dir| 0.1 end,
                       :edit, lambda do |g|
                         g.data.map! do |val| val *= 2.0 end
                         0
                       end)
  map_channel(lambda do |y| granulate(gen) end)
  if (res = maxamp) > 0.6
    snd_display("gran 13 max: %s?", res)
  end
  unless vequal(vct_scale!(res = channel2vct(0, 30), 0.5),
                vct(0.000, 0.002, 0.004, 0.005, 0.007, 0.009, 0.011, 0.013,
                    0.015, 0.016, 0.018, 0.020, 0.022, 0.024, 0.025, 0.027,
                    0.029, 0.031, 0.033, 0.035, 0.036, 0.038, 0.040, 0.044,
                    0.047, 0.051, 0.055, 0.058, 0.062, 0.065))
    snd_display("gran 13 data: %s?", res)
  end
  unless vequal(vct_scale!(res = channel2vct(85, 30), 0.5),
                vct(0.244, 0.244, 0.244, 0.244, 0.245, 0.247, 0.249, 0.251,
                    0.253, 0.255, 0.256, 0.258, 0.260, 0.262, 0.264, 0.264,
                    0.262, 0.260, 0.258, 0.256, 0.255, 0.253, 0.251, 0.249,
                    0.247, 0.245, 0.245, 0.247, 0.249, 0.251))
    snd_display("gran 13 data 85: %s?", res)
  end
  undo_edit
  forward = true
  ctr = -0.5
  incr = 0.001
  gen = make_granulate(:jitter, 0.0, :hop, 0.005, :length, 0.002, :ramp, 0.0, :scaler, 1.0,
                       :input, lambda do |dir|
                         ctr += incr
                         ctr
                       end,
                       :edit, lambda do |g|
                         len = mus_length(g)
                         if forward
                           forward = false
                         else
                           forward = true
                           vct_reverse!(mus_data(g), len)
                         end
                         len
                       end)
  map_channel(lambda do |y| granulate(gen) end)
  if (res = maxamp) > 0.6
    snd_display("gran 14 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(-0.499, -0.498, -0.497, -0.496, -0.495, -0.494, -0.493, -0.492,
                    -0.491, -0.490, -0.489, -0.488, -0.487, -0.486, -0.485, -0.484,
                    -0.483, -0.482, -0.481, -0.480, -0.479, -0.478, -0.477, -0.476,
                    -0.475, -0.474, -0.473, -0.472, -0.471, -0.470))
    snd_display("gran 14 data: %s?", res)
  end
  unless vequal(res = channel2vct(100, 30),
                vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, -0.345, -0.346, -0.347, -0.348, -0.349, -0.350,
                    -0.351, -0.352, -0.353, -0.354, -0.355, -0.356, -0.357, -0.358,
                    -0.359, -0.360, -0.361, -0.362, -0.363, -0.364))
    snd_display("gran 14 data 100: %s?", res)
  end
  undo_edit
  #
  gen = make_granulate(:jitter, 0.0, :hop, 0.004, :length, 0.001, :ramp, 0.0)
  e = make_env(:envelope, [0, 0, 1, 0.5], :length, 1001)
  base_ramp_len = mus_length(gen)
  map_channel(lambda do |y|
                result = granulate(gen, lambda do |dir| 0.1 end)
                set_mus_ramp(gen, (base_ramp_len * env(e)).round)
                result
              end)
  if fneq(res = maxamp, 0.06)
    snd_display("granf 0 max: %s?", res)
  end
  if (mus_ramp(gen) - 0.5 * mus_length(gen)).abs > 1
    snd_display("granf 0 ramp: %s %s?", gen.ramp, gen.length)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 0 data: %s?", res)
  end
  unless vequal(res = channel2vct(440, 30),
                vct(0.000, 0.012, 0.024, 0.036, 0.048, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.048, 0.036, 0.024, 0.012, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 0 data 440: %s?", res)
  end
  unless vequal(res = channel2vct(880, 30),
                vct(0.000, 0.006, 0.012, 0.018, 0.024, 0.030, 0.036, 0.042,
                    0.048, 0.054, 0.060, 0.060, 0.060, 0.060, 0.054, 0.048,
                    0.042, 0.036, 0.030, 0.024, 0.018, 0.012, 0.006, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 0 data 880: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.004, :length, 0.001, :ramp, 0.0)
  e = make_env(:envelope, [0, 1, 1, 0.25], :length, 1001)
  base_hop_len = mus_hop(gen)
  map_channel(lambda do |y|
                result = granulate(gen, lambda do |dir| 0.1 end)
                set_mus_hop(gen, (base_hop_len * env(e)).round)
                result
              end)
  if fneq(res = maxamp, 0.06)
    snd_display("granf 1 max: %s?", res)
  end
  if (mus_hop(gen) - 0.001 * mus_srate).abs > 1
    snd_display("granf 1 hop: %s?", gen.hop)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 1 data: %s?", res)
  end
  unless vequal(res = channel2vct(900, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060))
    snd_display("granf 1 data 900: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.004, :length, 0.001, :ramp, 0.0)
  e = make_env(:envelope, [0, 1, 1, 0.25], :length, 1001)
  base_freq = mus_frequency(gen)
  map_channel(lambda do |y|
                result = granulate(gen, lambda do |dir| 0.1 end)
                set_mus_frequency(gen, base_freq * env(e))
                result
              end)
  if fneq(res = maxamp, 0.06)
    snd_display("granf 2 max: %s?", res)
  end
  if (mus_hop(gen) - 0.001 * mus_srate).abs > 1
    snd_display("granf 2 hop: %s?", gen.hop)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 2 data: %s?", res)
  end
  unless vequal(res = channel2vct(900, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060))
    snd_display("granf 2 data 900: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.002, :length, 0.001, :ramp, 0.0, :scaler, 1.0)
  base_freq = mus_frequency(gen)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if fneq(res = maxamp, 0.1)
    snd_display("granf 3 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 3 data: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.004, :length, 0.001, :ramp, 0.0, :scaler, 1.0)
  e = make_env(:envelope, [0, 1, 1, 0], :length, 1001)
  base_freq = mus_frequency(gen)
  map_channel(lambda do |y|
                result = granulate(gen, lambda do |dir| 0.1 end)
                set_mus_scaler(gen, env(e))
                result
              end)
  if fneq(res = maxamp, 0.1)
    snd_display("granf 4 max: %s?", res)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100,
                    0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.100, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 4 data: %s?", res)
  end
  unless vequal(res = channel2vct(440, 30),
                vct(0.056, 0.056, 0.056, 0.056, 0.056, 0.056, 0.056, 0.056,
                    0.056, 0.056, 0.056, 0.056, 0.056, 0.056, 0.056, 0.056,
                    0.056, 0.056, 0.056, 0.056, 0.056, 0.056, 0.056, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 4 data 440: %s?", res)
  end
  unless vequal(res = channel2vct(900, 30),
                vct(0.012, 0.012, 0.012, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 4 data 900: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.006, :length, 0.001, :ramp, 0.0, :max_size, 2200)
  e = make_env(:envelope, [0, 1, 1, 5], :length, 1001)
  base_len = mus_length(gen)
  map_channel(lambda do |y|
                result = granulate(gen, lambda do |dir| 0.1 end)
                set_mus_length(gen, (base_len * env(e)).round)
                result
              end)
  if fneq(res = maxamp, 0.06)
    snd_display("granf 5 max: %s?", res)
  end
  if (gen.length - 5 * base_len).abs > 10
    snd_display("granf 5 length: %s %s?", mus_length(gen), 5 * base_len)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 5 data: %s?", res)
  end
  unless vequal(res = channel2vct(440, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 5 data 440: %s?", res)
  end
  unless vequal(res = channel2vct(800, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060))
    snd_display("granf 5 data 800: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.0, :hop, 0.006, :length, 0.005, :ramp, 0.0, :max_size, 2200)
  e = make_env(:envelope, [0, 1, 1, 0.2], :length, 1001)
  base_len = mus_length(gen)
  map_channel(lambda do |y|
                result = granulate(gen, lambda do |dir| 0.1 end)
                set_mus_length(gen, (base_len * env(e)).round)
                result
              end)
  if fneq(res = maxamp, 0.06)
    snd_display("granf 6 max: %s?", res)
  end
  if (gen.length - 0.2 * base_len).abs > 4
    snd_display("granf 6 length: %s %s?", mus_length(gen), 0.2 * base_len)
  end
  unless vequal(res = channel2vct(0, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.060))
    snd_display("granf 6 data: %s?", res)
  end
  unless vequal(res = channel2vct(820, 30),
                vct(0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060, 0.060,
                    0.060, 0.060, 0.060, 0.060, 0.060, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                    0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("granf 6 data 820: %s?", res)
  end
  undo_edit
  max_list = lambda do
    pts = []
    samp = 0
    lasty = 0.0
    scan_channel(lambda do |y|
                   if lasty < 0.1 and y >= 0.1
                     pts << samp
                   end
                   lasty = y
                   samp += 1
                   false
                 end)
    pts
  end
  gen = make_granulate(:jitter, 0.0, :hop, 0.01, :length, 0.001,
                       :ramp, 0.5, :scaler, 1.0, :expansion, 0.5)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if [11, 231, 451, 671, 891] != (res = max_list.call)
    snd_display("grn jitter 0 max: %s?", res)
  end
  undo_edit
  gen = make_granulate(:jitter, 0.3, :hop, 0.01, :length, 0.001,
                       :ramp, 0.5, :scaler, 1.0, :expansion, 0.5)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if [11, 231, 451, 671, 891] == (res = max_list.call)
    snd_display("grn jitter 0.3 max: %s?", res)
  end
  old_vals = res
  undo_edit
  gen = make_granulate(:jitter, 0.3, :hop, 0.01, :length, 0.001,
                       :ramp, 0.5, :scaler, 1.0, :expansion, 0.5)
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if (res = max_list.call) == old_vals
    snd_display("grn jitter 0.3 max: %s %s?", res, old_vals)
  end
  undo_edit
  old_vals = false
  gen = make_granulate(:jitter, 1.0, :hop, 0.01, :length, 0.001,
                       :ramp, 0.5, :scaler, 1.0, :expansion, 0.5)
  seed = gen.location
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  old_vals = max_list.call
  undo_edit
  gen = make_granulate(:jitter, 1.0, :hop, 0.01, :length, 0.001,
                       :ramp, 0.5, :scaler, 1.0, :expansion, 0.5)
  gen.location = seed
  map_channel(lambda do |y| granulate(gen, lambda do |dir| 0.1 end) end)
  if (res = max_list.call) != old_vals
    snd_display("grn jitter 1.0 max with seed: %s %s?", res, old_vals)
  end
  undo_edit
  fname = file_name(ind)
  close_sound(ind)
  delete_file(fname)
  if view_files_dialog(false)
    set_view_files_files(view_files_dialog(false), [])
    unless (res = view_files_files(view_files_dialog(false))).null?
      snd_display("set vf files list null: %s?", res)
    end
  end
end

def test_08_17
  ind = new_sound("tmp.snd", Mus_next, Mus_bfloat, 22050, 1, :size, 10000)
  gen = make_granulate(:expansion, 20.0,
                       :input, lambda do |dir| 0.01 end,
                       :length, 0.00995,
                       :hop, 0.01,
                       :ramp, 0.0,
                       :scaler, 1.0,
                       :jitter, 0.0)
  clm_channel(gen)                                # 0.01 max stable
  if fneq(maxamp, 0.01)
    snd_display("granulate stable 1: %s?", maxamp)
  end
  if minval = scan_channel(lambda do |y| y < 0.0099 end)
    snd_display("granulate stable 1 min: %s?", minval)
  end
  undo_edit
  gen = make_granulate(:expansion, 20.0,
                       :input, lambda do |dir| 0.1 end,
                       :length, 0.00995,
                       :hop, 0.01,
                       :ramp, 0.0,
                       :scaler, 0.5,
                       :jitter, 0.0)
  clm_channel(gen)                                # 0.05 max stable
  if fneq(maxamp, 0.05)
    snd_display("granulate stable 2: %s?", maxamp)
  end
  if minval = scan_channel(lambda do |y| y < 0.0499 end)
    snd_display("granulate stable 2 min: %s?", minval)
  end
  undo_edit
  gen = make_granulate(:expansion, 20.0,
                       :input, lambda do |dir| 0.05 end,
                       :length, 0.099975,
                       :hop, 0.1,
                       :ramp, 0.0,
                       :scaler, 1.0,
                       :jitter, 0.0)
  clm_channel(gen)                                # 0.05 max stable
  if fneq(maxamp, 0.05)
    snd_display("granulate stable 3: %s?", maxamp)
  end
  if minval = scan_channel(lambda do |y| y < 0.0499 end)
    snd_display("granulate stable 3 min: %s %s?", minval, sample(minval[1]))
  end
  undo_edit
  ctr = 0
  gen = make_granulate(:expansion, 2.0,
                       :input, lambda do |dir|
                         val = ctr * 0.0001
                         ctr += 1
                         val
                       end,
                       :length, 0.01,
                       :hop, 0.1,
                       :ramp, 0.0,
                       :scaler, 1.0,
                       :jitter, 0.0)
  clm_channel(gen)
  if fneq(maxamp, 0.462)
    snd_display("granulate ramped 4: %s?", maxamp)
  end
  vals = count_matches(lambda do |y| y != 0.0 end)
  if (vals - 1104).abs > 10
    snd_display("granulate ramped 4 not 0.0: %s?", vals)
  end
  if (not vequal(res1 = channel2vct(2203, 10),
                 vct(0.000, 0.000, 0.110, 0.110, 0.110, 0.111, 0.111, 0.111, 0.111, 0.111))) or
      (not vequal(res2 = channel2vct(4523, 10),
                  vct(0.232, 0.232, 0.232, 0.232, 0.232, 0.232, 0.232, 0.232, 0.233, 0.233))) or
      (not vequal(res3 = channel2vct(8928, 10),
                  vct(0.452, 0.452, 0.452, 0.452, 0.452, 0.452, 0.452, 0.452, 0.452, 0.452)))
    snd_display("granulate ramped 4 data off: %s %s %s?", res1, res2, res3)
  end
  undo_edit
  ctr = 0
  gen = make_granulate(:expansion, 2.0,
                       :input, lambda do |dir|
                         val = ctr * 0.0001
                         ctr += 1
                         val
                       end,
                       :length, 0.00995,
                       :hop, 0.01,
                       :ramp, 0.0,
                       :scaler, 1.0,
                       :jitter, 0.0)
  clm_channel(gen)
  if fneq(maxamp, 0.505)
    snd_display("granulate ramped 5: %s?", maxamp)
  end
  vals = count_matches(lambda do |y| y != 0.0 end)
  mxoff = 0.0
  mx = maxamp
  len = frames
  cur = 0.0
  incr = mx / len
  scan_channel(lambda do |y|
                 diff = (cur - y).abs
                 if diff > mxoff
                   mxoff = diff
                 end
                 cur += incr
                 false
               end)
  if mxoff > 0.02
    snd_display("granulate ramped 5 mxoff: %s?", mxoff)
  end
  undo_edit
  ctr = 0
  gen = make_granulate(:expansion, 2.0,
                       :input, lambda do |dir|
                         val = ctr * 0.0001
                         ctr += 1
                         val
                       end,
                       :length, 0.00995,
                       :hop, 0.01,
                       :ramp, 0.5,
                       :scaler, 1.0,
                       :jitter, 0.0)
  clm_channel(gen)
  if fneq(maxamp, 0.495)
    snd_display("granulate ramped 6: %s?", maxamp)
  end
  if (not vequal(res1 = channel2vct(2000, 10),
                 vct(0.018, 0.019, 0.020, 0.021, 0.022, 0.023, 0.024, 0.025, 0.026, 0.027))) or
      (not vequal(res2 = channel2vct(8000, 10),
                  vct(0.294, 0.298, 0.301, 0.305, 0.309, 0.313, 0.316, 0.320, 0.324, 0.328)))
    snd_display("granulate ramped 6 data: %s %s?", res1, res2)
  end
  undo_edit
  ctr = 0
  gen = make_granulate(:expansion, 2.0,
                       :input, lambda do |dir|
                         val = ctr * 0.0001
                         ctr += 1
                         val
                       end,
                       :length, 0.00995,
                       :hop, 0.01,
                       :ramp, 0.25,
                       :scaler, 1.0,
                       :jitter, 0.0)
  clm_channel(gen)
  if fneq(maxamp, 0.505)
    snd_display("granulate ramped 7: %s?", maxamp)
  end
  if (not vequal(res1 = channel2vct(2000, 10),
                 vct(0.037, 0.039, 0.040, 0.042, 0.044, 0.046, 0.048, 0.050, 0.052, 0.054))) or
      (not vequal(res2 = channel2vct(8000, 10),
                  vct(0.404, 0.404, 0.404, 0.404, 0.404, 0.405, 0.405, 0.405, 0.405, 0.405)))
    snd_display("granulate ramped 7 data: %s %s?", res1, res2)
  end
  undo_edit
  ctr = 0
  gen = make_granulate(:expansion, 2.0,
                       :input, lambda do |dir|
                         val = ctr * 0.0001
                         ctr += 1
                         val
                       end,
                       :length, 0.05,
                       :hop, 0.01,
                       :ramp, 0.25,
                       :scaler, 0.1,
                       :jitter, 0.0)
  clm_channel(gen)
  if fneq(maxamp, 0.201)
    snd_display("granulate ramped 8: %s?", maxamp)
  end
  mxoff = 0.0
  mx = maxamp
  len = frames
  cur = 0.0
  incr = mx / len
  scan_channel(lambda do |y|
                 diff = (cur - y).abs
                 if diff > mxoff
                   mxoff = diff
                 end
                 cur += incr
                 false
               end)
  if mxoff > 0.01
    snd_display("granulate ramped 8 mxoff: %s?", mxoff)
  end
  undo_edit
  ctr = 0
  gen = make_granulate(:expansion, 2.0,
                       :input, lambda do |dir|
                         val = ctr * 0.0001
                         ctr += 1
                         val
                       end,
                       :length, 0.1,
                       :hop, 0.01,
                       :ramp, 0.1,
                       :scaler, 0.1,
                       :jitter, 0.0)
  clm_channel(gen)
  if fneq(maxamp, 0.501)
    snd_display("granulate ramped 9: %s?", maxamp)
  end
  mxoff = 0.0
  mx = maxamp
  len = frames - 2000
  cur = sample(2000)
  incr = (mx - cur) / len
  scan_channel(lambda do |y|
                 diff = (cur - y).abs
                 if diff > mxoff
                   mxoff = diff
                 end
                 cur += incr
                 false
               end, 2000)
  if mxoff > 0.001
    snd_display("granulate ramped 9 mxoff: %s?", mxoff)
  end
  undo_edit
  ctr = 0
  gen = make_granulate(:expansion, 2.0,
                       :input, lambda do |dir|
                         val = ctr * 0.0001
                         ctr += 1
                         val
                       end,
                       :length, 0.4,
                       :hop, 0.01,
                       :ramp, 0.4,
                       :scaler, 0.025,
                       :jitter, 0.0)
  clm_channel(gen)
  if fneq(maxamp, 0.433)
    snd_display("granulate ramped 10: %s?", maxamp)
  end
  undo_edit
  close_sound(ind)
end

def test_08_18
  v0 = make_vct(32)
  v1 = make_vct(256)
  v2 = make_vct(256)
  v01 = make_vct(32)
  v11 = make_vct(256)
  v21 = make_vct(256)
  1.upto(15) do |i| v0[i] = v01[i] = 1.0 / i end
  v1[0] = v11[0] = 1.0
  gen = make_convolve(:filter, v0)
  gen1 = make_convolve(:filter, v01)
  n = n1 = -1
  print_and_check(gen, "convolve", "convolve size: 64")
  unless convolve?(gen)
    snd_display("%s not convolve?", gen)
  end
  genx = gen1
  unless genx.eql?(gen1)
    snd_display("convolve %s.eql?(%s)", genx, gen1)
  end
  if gen.eql?(gen1)
    snd_display("convolve %s.eql?(%s)", gen, gen1)
  end
  if mus_length(gen) != 64
    snd_display("convolve fft len: %s?", mus_length(gen))
  end
  128.times do |i|
    v2[i] = convolve(gen, lambda do |dir|
                       n += 1
                       v1[n]
                     end)
  end
  vct_map!(v21, lambda do | |
             if convolve?(gen1)
               convolve(gen1, lambda do |dir|
                          n1 += 1
                          v11[n1]
                        end)
             else
               -1.0
             end
           end)
  unless vequal(v2, v21)
    snd_display("run gran: %s %s?", v2, v21)
  end
  if fneq(v2[0], 0.0) or fneq(v2[1], 1.0) or fneq(v2[4], 0.25) or fneq(v2[7], 0.143)
    snd_display("convolve output: %s?", v2)
  end
  if (res = Snd.catch do convolve(gen, lambda do |a, b| a end) end).first != :bad_arity
    snd_display("convolve bad func: %s", res.inspect)
  end
  convolve_files("oboe.snd", "fyow.snd", 0.5, "fmv.snd")
  if fneq(res = mus_sound_maxamp("fmv.snd")[1], 0.5)
    snd_display("convolve_files: %s != 0.5?", res)
  end
  #
  fd = mus_sound_open_input("oboe.snd")
  chans = mus_sound_chans("oboe.snd")
  data = make_sound_data(chans, 2000)
  unless sound_data?(data)
    snd_display("%s not sound_data?", data)
  end
  if sound_data_chans(data) != 1
    snd_display("sound_data chans: %s?", data.chans)
  end
  if data.length != 2000
    snd_display("sound_data length: %s?", sound_data_length(data))
  end
  mus_sound_read(fd, 0, 1999, chans, data)
  mus_sound_close_input(fd)
  if fneq(res = data[0, 1497], 0.02893066)
    snd_display("mus_sound_read: %s?", res)
  end
  #
  ind = new_sound("fmv.snd")
  set_sample(1, 0.1)
  save_sound(ind)
  if edits(ind, 0) != [0, 0]
    snd_display("weird: edits not cleared after save_sound: %s?", edits(ind, 0))
  end
  close_sound(ind)
  ind = open_sound("fmv.snd")
  if frames(ind, 0) != 2
    snd_display("save_sound 2 samps: %s?", frames(ind, 0))
  end
  if fneq(sample(0), 0.0) or fneq(sample(1), 0.1)
    snd_display("save_sound: %s %s?", sample(0), sample(1))
  end
  3.upto(5) do |i|
    set_sample(i, i * 0.1)
    save_sound(ind)
    if edits(ind, 0) != [0, 0]
      snd_display("weird: edits not cleared after save_sound %s: %s?", i, edits(ind, 0))
    end
    close_sound(ind)
    ind = open_sound("fmv.snd")
    if frames(ind, 0) != i + 1
      snd_display("save_sound %s samps: %s?", i + 1, frames(ind, 0))
    end
    if fneq(sample(0), 0.0) or fneq(sample(1), 0.1) or fneq(sample(i), i * 0.1)
      snd_display("save_sound %s: %s %s %s?", i, sample(0), sample(1), sample(i))
    end
  end
  close_sound(ind)
  #
  ind = new_sound("test.snd", :srate, 22050, :channels, 1, :size, 1000)
  gen = make_ssb_am(100.0)
  map_channel(lambda do |y| ssb_am(gen, 0.0) end)
  if fneq(maxamp, 0.0)
    snd_display("ssb_am 0.0: %s?", maxamp)
  end
  gen1 = make_oscil(220.0)
  map_channel(lambda do |y| 0.5 * oscil(gen1) end)
  gen = make_ssb_am(100.0, 100)
  map_channel(lambda do |y| ssb_am(gen, y) end)
  delete_samples(0, 200)
  gen1 = make_oscil(320.0, :initial_phase, asin(2.0 * sample(0)))
  map_channel(lambda do |y| y - 0.5 * oscil(gen1) end)
  if maxamp > 0.004
    snd_display("ssb_am cancelled: %s?", maxamp)
  end
  undo_edit(3)
  gen = make_ssb_am(100.0, 100)
  map_channel(lambda do |y| ssb_am(gen, y, hz2radians(50.0)) end)
  delete_samples(0, 180)
  if defined? asin
    gen1 = make_oscil(370.0, :initial_phase, asin(2.0 * sample(0)))
    map_channel(lambda do |y| y - 0.5 * oscil(gen1) end)
    if maxamp > 0.004
      snd_display("ssb_am fm cancelled: %s?", maxamp)
    end
  end
  close_sound(ind)
  #
  if defined? mus_ssb_bank             # not defined if --with-modules
    bands = make_array(3) do make_bandpass(hz2radians(500.0), hz2radians(600.0), 10) end
    ssbs = make_array(3) do make_ssb_am(100.0 + random(400.0)) end
    mus_ssb_bank(ssbs, bands, 0.1, 3)
  end
  #
  ind = new_sound("test.snd", :srate, 22050, :channels, 1, :size, 1000)
  ctr = 0
  map_channel(lambda do |y|
                val = sin((TWO_PI * ctr) / 50)
                ctr += 1
                val
              end)
  ssb_bank(441, 882, 1, 100)
  delete_samples(0, 217)
  if defined? asin
    gen1 = make_oscil(882.0, :initial_phase, asin(sample(0)))
    map_channel(lambda do |y| y - oscil(gen1) end)
    if maxamp > 0.04
      snd_display("ssb_bank cancelled: %s?", maxamp)
    end
  end
  close_sound(ind)
  #
  snd_test_neq($output, false, "$output")
  $output = false
  nind = new_sound("fmv.snd", Mus_aifc, Mus_bshort, 22050, 1, "this is a comment")
  with_time("fm_violin_1(0, 1, 440, 0.1)") do fm_violin_1(0, 1, 440, 0.1) end
  play(nind, :wait, true)
  save_sound(nind)
  unless sound?(nind)
    snd_display("save_sound clobbered %s?", nind)
  end
  oboe_index = (find_sound("oboe.snd") or open_sound("oboe.snd"))
  if oboe_index == nind
    snd_display("find_sound found bogus case: %s (%s)?", oboe_index, nind)
  end
  cnvtest(oboe_index, nind, 0.1)
  select_sound(nind)
  select_channel(0)
  if selected_sound != nind
    snd_display("selected_sound: %s (%s)?", selected_sound, nind)
  end
  if selected_channel != 0
    snd_display("selected_channel: %s?", selected_channel)
  end
  jc_reverb_1(1.0, false, 0.1, false)
  play(nind, :wait, true)
  voiced2unvoiced(1.0, 256, 2.0, 2.0)
  pulse_voice(80, 20.0, 1.0, 1024, 0.01)
  map_chan(fltit)
  close_sound(oboe_index)
  unless sound?(nind)
    snd_display("close_sound clobbered %s?", nind)
  end
  fr = frames(nind, 0)
  10.times do
    delete_samples(10, 100, nind, 0)
    save_sound(nind)
  end
  if frames(nind, 0) != fr - 1000
    snd_display("delete_samples: %s %s?", fr, frames(nind, 0))
  end
  revert_sound(nind)
  close_sound(nind)
  delete_file("fmv.snd")
  nind = new_sound("fmv.snd")
  if (res1 = header_type(nind)) != (res2 = default_output_header_type)
    snd_display("new_sound default header_type: %s %s?",
                mus_header_type_name(res1),
                mus_header_type_name(res2))
  end
  if (res1 = data_format(nind)) != (res2 = default_output_data_format)
    snd_display("new_sound default data_format: %s %s?",
                mus_data_format_name(res1),
                mus_data_format_name(res2))
  end
  if (res1 = channels(nind)) != (res2 = default_output_chans)
    snd_display("new_sound default chans: %s %s?", res1, res2)
  end
  if (res1 = srate(nind)) != (res2 = default_output_srate)
    snd_display("new_sound default srate: %s %s?", res1, res2)
  end
  close_sound(nind)
  delete_file("fmv.snd")
end

def test_08_19
  nind = new_sound("fmv.snd", Mus_nist, Mus_bshort, 22050, 1, "this is a comment")
  set_sample(0, 1.0, nind)
  start_progress_report(nind)
  convolve_with("oboe.snd")
  progress_report(0.1, nind)
  if fneq(sample(1000), 0.223)
    snd_display("convolve_with: %s?", sample(1000))
  end
  progress_report(0.3, nind)
  revert_sound(nind)
  progress_report(0.5, nind)
  set_sample(200, 0.0001)
  set_sample(100, 1.0)
  progress_report(0.8, nind)
  smooth_sound(0, 100)
  finish_progress_report(nind)
  if fneq(sample(50), 0.5) or fneq(sample(30), 0.20608) or fneq(sample(90), 0.9755)
    snd_display("smooth: %s %s %s?", sample(50), sample(30), sample(90))
  end
  undo_edit
  old_sw = sinc_width
  set_sinc_width(40)
  set_sample(100, 0.5)
  if fneq(sample(100), 0.5)
    snd_display("set_sample(100): %s?", sample(100))
  end
  src_sound(0.1)
  if fneq(sample(1000), 0.5) or fneq(sample(1024), 0.0625) or fneq(sample(1010), 0.0)
    snd_display("src_sound: %s %s %s?", sample(100), sample(1024), sample(1010))
  end
  set_sinc_width(old_sw)
  revert_sound(nind)
  close_sound(nind)
  # 
  nind = new_sound("fmv.snd", Mus_riff, Mus_lshort, 22050, 1, "this is a comment", 22050)
  if frames(nind) != 22050
    snd_display("new_sound initial_length: %s?", frames(nind))
  end
  mix("pistol.snd")
  map_chan(expsrc(2.0, nind))
  undo_edit
  eds = edits
  if eds[0] != 1 or eds[1] != 1
    snd_display("undo edits: %s?", eds)
  end
  if edit_position != eds[0]
    snd_display("undo edit_position: %s %s?", edit_position, eds)
  end
  expsnd([0, 1, 2, 0.4])
  map_chan(comb_chord(0.95, 100, 0.3))
  map_chan(formants(0.99, 900, 0.02, 1800, 0.01, 2700))
  map_chan(moving_formant(0.99, [0, 1200, 1, 2400]))
  scale_to(0.3)
  eds = edits
  if eds[0] != 6 or eds[1] != 0
    snd_display("edits(6): %s?", eds)
  end
  if edit_position != eds[0]
    snd_display("edit_position(6): %s %s?", edit_position, eds)
  end
  set_edit_position(1)
  if edit_position != 1
    snd_display("set_edit_position(1): %s?", edit_position)
  end
  set_edit_position(4)
  if edit_position != 4
    snd_display("set_edit_position(4): %s?", edit_position)
  end
  revert_sound(nind)
  mix("pistol.snd")
  map_chan(zecho(0.5, 0.75, 6, 10.0), 0, 65000)
  map_chan(am(440))
  add_mark(1200)
  add_mark(2300)
  key(key_to_int(?x), 4)
  key(key_to_int(?c), 0)        # trigger mark_define_region
  reverse_sound(nind)
  revert_sound(nind)
  mid = mix_sound("pistol.snd", 0).car
  if mix?(mid) and mix_home(mid) != [selected_sound, 0, false, 0]
    snd_display("mix_sound mix_home: %s (%s or %s 0)?", mix_home(mid), selected_sound, nind)
  end
  hello_dentist(40.0, 0.1)
  fp(1.0, 0.3, 20)
  revert_sound(nind)
  enveloped_mix("oboe.snd", 0, [0, 0, 1, 1, 2, 0])
  pvoc(:pitch, 0.5, :time, 1.0, :snd, nind)
  revert_sound(nind)
  close_sound(nind)
end

def mus_mix_1(outf, inf, outloc = 0, frms = false, inloc = 0, mx = false, envs = false)
  unless number?(frms)
    frms = mus?(inf) ? mus_length(inf) : mus_sound_frames(inf)
  end
  mus_mix(outf, inf, outloc, frms, inloc, mx, envs)
  mus?(outf) and mus_close(outf)
end

def test_08_20
  make_mix_output = lambda do |name, i|
    if i == 0 or i == 1
      name
    else
      continue_sample2file(name)
    end
  end
  make_mix_input = lambda do |name, i|
    if i == 0 or i == 2
      name
    else
      make_file2frame(name)
    end
  end
  4.times do |k|
    delete_files("fmv.snd", "fmv1.snd", "fmv2.snd", "fmv3.snd")
    v0 = make_vct(12)
    vct_fill!(v0, 0.1)
    array2file("fmv1.snd", v0, 12, 22050, 1)
    vct_fill!(v0, 0.2)
    array2file("fmv2.snd", v0, 12, 22050, 2)
    file2array("fmv2.snd", 0, 0, 12, v0)
    vct_fill!(v0, 0.3)
    array2file("fmv3.snd", v0, 12, 22050, 4)
    v0.map_with_index! do |val, i| i * 0.01 end
    array2file("fmv.snd", v0, 12, 22050, 1)
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("fmv1.snd", k))
    file2array("fmv.snd", 0, 0, 12, v0)
    v0.each_with_index do |val, i|
      if fneq(val, 0.1 + i * 0.01)
        snd_display("%s mus_mix(1->1): %s?", k, v0)
        break
      end
    end
    mus_mix_1(make_mix_output.call("fmv.snd", k),
              make_mix_input.call("fmv2.snd", k),
              3, 9, 0, make_mixer(2, 0.3, 0.0, 0.7, 0.0))
    file2array("fmv.snd", 0, 0, 12, v0)
    if fneq(v0[0], 0.1) or fneq(v0[3], 0.33) or fneq(v0[9], 0.19)
      snd_display("%s mus_mix(2->1): %s?", k, v0)
    end
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("fmv3.snd", k))
    file2array("fmv.snd", 0, 0, 12, v0)
    if fneq(v0[0], 0.4) or fneq(v0[3], 0.33)
      snd_display("%s mus_mix(4->1): %s?", k, v0)
    end
    vf = make_array(1) do
      make_array(1) do
        make_env(:envelope, [0, 0, 1, 1], :length, 11)
      end
    end
    mus_mix_1(make_mix_output.call("fmv.snd", k),
              make_mix_input.call("fmv1.snd", k),
              0, 12, 0, make_mixer(1, 1.0), vf)
    file2array("fmv.snd", 0, 0, 12, v0)
    if fneq(v0[0], 0.4) or fneq(v0[3], 0.36) or fneq(v0[9], 0.28)
      snd_display("%s mus_mix(env): %s?", k, v0)
    end
    mus_mix_1(make_mix_output.call("fmv.snd", k),
              make_mix_input.call("fmv2.snd", k),
              0, 12, 0, make_mixer(2, 1.0, 1.0, 1.0, 1.0), vf)
    vf = make_array(2)
    vf1 = make_array(2)
    vf2 = make_array(2)
    vf[0] = vf1
    vf[1] = vf2
    vf1[0] = make_env(:envelope, [0, 0, 1, 1], :length, 10)
    vf2[1] = make_env(:envelope, [0, 0, 1, 1], :length, 10)
    mus_mix_1(make_mix_output.call("fmv.snd", k),
              make_mix_input.call("fmv2.snd", k),
              0, 12, 0, make_mixer(2, 1.0, 1.0, 1.0, 1.0), vf)
    if (res = Snd.catch do
          vf[0] = make_oscil
          mus_mix_1(make_mix_output.call("fmv.snd", k),
                    make_mix_input.call("fmv2.snd", k),
                    0, 12, 0, make_mixer(2, 1.0, 1.0, 1.0, 1.0), vf)
        end).first != :bad_type
      snd_display("%s mix w oscil-array: %s", k, res.inspect)
    end
    vf1[0] = make_env(:envelope, [0, 0, 1, 1], :length, 10)
    vf2[1] = make_env(:envelope, [0, 0, 1, 1], :length, 10)
    if (res = Snd.catch do
          vf1[0] = make_oscil
          vf2[1] = sqrt(-1.0)
          mus_mix_1(make_mix_output.call("fmv.snd", k),
                    make_mix_input.call("fmv2.snd", k),
                    0, 12, 0, make_mixer(2, 1.0, 1.0, 1.0, 1.0), vf)
        end).first != :bad_type
      snd_display("%s mix w oscil-env: %s", k, res.inspect)
    end
    delete_file("fmv.snd")
    v0.map_with_index! do |val, i| i * 0.01 end
    array2file("fmv.snd", v0, 12, 22050, 4)
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("fmv1.snd", k))
    file2array("fmv.snd", 0, 0, 3, v0)            # chan 0 start 0 len 3
    if fneq(v0[0], 0.1) or fneq(v0[2], 0.18)
      snd_display("%s mus_mix(1->4): %s?", k, v0)
    end
    mus_mix_1(make_mix_output.call("fmv.snd", k),
              make_mix_input.call("fmv2.snd", k),
              0, 3, 0, make_mixer(2, 0.3, 0.0, 0.7, 0.0))
    file2array("fmv.snd", 0, 0, 3, v0)
    if fneq(v0[0], 0.3) or fneq(v0[2], 0.38)
      snd_display("%s mus_mix(2->4): %s?", k, v0)
    end
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("fmv3.snd", k), 0, 2, 0)
    file2array("fmv.snd", 0, 0, 3, v0)
    if fneq(v0[0], 0.6) or fneq(v0[2], 0.38)
      snd_display("%s mus_mix(4->4): %s?", k, v0)
    end
    #
    delete_file("fmv.snd")
    v0 = make_vct(12)
    len = mus_sound_frames("oboe.snd")
    array2file("fmv.snd", v0, 12, 22050, 1)
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("oboe.snd", k))
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("oboe.snd", k),
              0, len, 0, make_mixer(1, 0.5))
    egen = make_array(1)
    outv = make_array(1)
    outv[0] = egen
    egen[0] = make_env(:envelope, [0, 0, 1, 1], :length, len)
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("oboe.snd", k),
              0, len, 0, false, outv)
    egen[0] = make_env(:envelope, [0, 1, 1, 0], :length, len)
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("oboe.snd", k),
              0, len, 0, make_mixer(1, 1.0), outv)
    ind_oboe = open_sound("oboe.snd")
    ind_mix = open_sound("fmv.snd")
    unless vequal(res1 = channel2vct(1000, 10, ind_oboe),
                  res2 = vct_scale!(channel2vct(1000, 10, ind_mix), 1.0 / 2.5))
      snd_display("%s mus_mix 1 chan:\n# %s\n# %s?", k, res1, res2)
    end
    close_sound(ind_oboe)
    close_sound(ind_mix)
    # 
    delete_file("fmv.snd")
    v0 = make_vct(12)
    len = mus_sound_frames("2.snd")
    array2file("fmv.snd", v0, 12, 22050, 2)
    if (res = mus_sound_chans("fmv.snd")) != 2
      snd_display("%s array2file chans %s?", k, res)
    end
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("2.snd", k))
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("2.snd", k),
              0, len, 0, make_mixer(2, 0.5, 0.0, 0.0, 0.5))
    egen0 = make_array(2)
    egen1 = make_array(2)
    outv = make_array(2)
    outv[0] = egen0
    outv[1] = egen1
    egen0[0] = make_env(:envelope, [0, 0, 1, 1], :length, len)
    egen1[1] = make_env(:envelope, [0, 0, 1, 1], :length, len)
    mus_mix_1(make_mix_output.call("fmv.snd", k), make_mix_input.call("2.snd", k),
              0, len, 0, false, outv)
    ind_mix = open_sound("fmv.snd")
    if channels(ind_mix) != 2
      snd_display("%s fmv re-read chans %s %s?", k, mus_sound_chans("fmv.snd"), channels(ind_mix))
    end
    close_sound(ind_mix)
    delete_file("fmv.snd")
  end
end

def test_08_21
  gen = make_phase_vocoder(false, 512, 4, 256, 1.0, false, false, false)
  if fneq((res = Snd.catch do phase_vocoder(gen) end).first, 0.0)
    snd_display("simple no-in pv call: %s", res.inspect)
  end
  if (res = Snd.catch do gen = make_phase_vocoder(:fft_size, 1234) end).first != :out_of_range
    snd_display("pv bad fft: %s?", res.inspect)
  end
  ind = open_sound("oboe.snd")
  pv = make_phase_vocoder(false, 512, 4, 128, 1.0, false, false, false)
  rd = make_sampler(0)
  unless phase_vocoder?(pv)
    snd_display("%s not phase_vocoder?", pv)
  end
  print_and_check(pv,
                  "phase-vocoder",
                  "phase-vocoder outctr: 128, interp: 128, filptr: 0, N: 512, D: 128, in_data: nil")
  pv = make_phase_vocoder
  set_mus_location(pv, 120)
  if (res = mus_location(pv)) != 120
    snd_display("pv set outctr: %s?", res)
  end
  select_sound(ind)
  map_chan(lambda do |val| phase_vocoder(pv, lambda do |dir| next_sample(rd) end) end)
  phase_vocoder_amp_increments(pv)[0] = 0.1
  if fneq(res = phase_vocoder_amp_increments(pv)[0], 0.1)
    snd_display("set_phase_vocoder_amp_increments: %s?", res)
  end
  phase_vocoder_amps(pv)[0] = 0.1
  if fneq(res = phase_vocoder_amps(pv)[0], 0.1)
    snd_display("set_phase_vocoder_amps: %s?", res)
  end
  phase_vocoder_phases(pv)[0] = 0.1
  if fneq(res = phase_vocoder_phases(pv)[0], 0.1)
    snd_display("set_phase_vocoder_phases: %s?", res)
  end
  phase_vocoder_phase_increments(pv)[0] = 0.1
  if fneq(res = phase_vocoder_phase_increments(pv)[0], 0.1)
    snd_display("set_phase_vocoder_phase_increments: %s?", res)
  end
  phase_vocoder_freqs(pv)[0] = 0.1
  if fneq(res = phase_vocoder_freqs(pv)[0], 0.1)
    snd_display("set_phase_vocoder_freqs: %s?", res)
  end
  undo_edit(1)
  free_sampler(rd)
  # 
  lastphases = make_vct(512)
  pv = make_phase_vocoder(false, 512, 4, 128, 1.0,
                          false,
                          lambda { |v|
                            n = mus_length(v)
                            d = mus_hop(v)
                            freqs = phase_vocoder_freqs(v)
                            pscl = 1.0 / d
                            kscl = TWO_PI / n
                            (n / 2).times do |k|
                              phasediff = freqs[k] - lastphases[k]
                              lastphases[k] = freqs[k]
                              while phasediff > PI
                                phasediff -= TWO_PI
                              end
                              while phasediff < -PI
                                phasediff += TWO_PI
                              end
                              freqs[k] = 0.5 * (pscl * phasediff + k * kscl)
                            end
                            false
                          },
                          false)
  rd = make_sampler(0)
  map_chan(lambda do |val| phase_vocoder(pv, lambda do |dir| next_sample(rd) end) end)
  undo_edit(1)
  free_sampler(rd)
  # 
  pv = make_phase_vocoder(false, 512, 4, (128 * 2.0).to_i, 1.0, false, false, false)
  rd = make_sampler(0)
  len = 1000
  data = make_vct!(len) do phase_vocoder(pv, lambda do |dir| next_sample(rd) end) end
  set_samples(0, len, data)
  undo_edit(1)
  free_sampler(rd)
  #
  incalls = outcalls = 0
  pv = make_phase_vocoder(false,
                          512, 4, (128 * 2.0).to_i, 1.0,
                          lambda { |v, infunc|
                            incalls += 1
                            true
                          },
                          false,
                          lambda { |v|
                            outcalls += 1
                            0.0
                          })
  rd = make_sampler(0)
  len = 1000
  data = make_vct!(len) do phase_vocoder(pv, lambda do |dir| next_sample(rd) end) end
  set_samples(0, len, data)
  undo_edit(1)
  free_sampler(rd)
  if incalls.zero? or outcalls.zero?
    snd_display("phase_vocoder incalls: %s, outcalls: %s?", incalls, outcalls)
  end
  set_mus_location(pv, mus_location(pv))
  if (res = Snd.catch do phase_vocoder(pv, lambda do |a, b| a end) end).first != :bad_arity
    snd_display("phase_vocoder bad input func: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_phase_vocoder(false, 512, 4, 256, 1.0, lambda do |a, b, c| false end, false, false)
      end).first != :bad_arity
    snd_display("make_phase_vocoder bad analyze func: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_phase_vocoder(false, 512, 4, 256, 1.0,
                           lambda do |a, b| 0.0 end,
                           lambda do |a, b, c| false end, false)
      end).first != :bad_arity
    snd_display("make_phase_vocoder bad edit func: %s", res.inspect)
  end
  if (res = Snd.catch do
        make_phase_vocoder(false, 512, 4, 256, 1.0,
                           lambda do |a, b| 0.0 end,
                           lambda do |a| false end,
                           lambda do |a, b| 0 end)
      end).first != :bad_arity
    snd_display("make_phase_vocoder bad synthesize func: %s", res.inspect)
  end
  geno = make_phase_vocoder(lambda do |dir| 0.0 end)
  genx = make_phase_vocoder(:input, lambda do |dir| 0.0 end)
  if geno.eql?(genx)
    snd_display("phase_vocoder %s.eql?(%s)?", geno, genx)
  end
  if fneq(genx.frequency, 1.0)
    snd_display("mus_frequency phase_vocoder: %s?", genx.frequency)
  end
  set_mus_frequency(genx, 2.0)
  if fneq(genx.frequency, 2.0)
    snd_display("set_mus_frequency phase_vocoder: %s?", genx.frequency)
  end
  if genx.increment != 128
    snd_display("mus_increment phase_vocoder: %s?", genx.increment)
  end
  set_mus_increment(genx, 256)
  if genx.increment != 256
    snd_display("set_mus_increment phase_vocoder: %s?", genx.increment)
  end
  if genx.hop != 128
    snd_display("mus_hop phase_vocoder: %s?", genx.hop)
  end
  set_mus_hop(genx, 64)
  if genx.hop != 64
    snd_display("set_mus_hop phase_vocoder: %s?", genx.hop)
  end
  if genx.length != 512
    snd_display("mus_length phase_vocoder: %s?", genx.length)
  end
  genxx = genx
  unless genx.eql?(genxx)
    snd_display("phase_vocoder %s.eql?(%s)?", genxx, genx)
  end
  close_sound(ind)
end

def test_08_22
  ind = open_sound("oboe.snd")
  gen = make_moog_filter(500.0, 0.1)
  if fneq(gen.frequency, 500.0)
    snd_display("moog freq: %s?", gen.frequency)
  end
  if fneq(gen.Q, 0.1)
    snd_display("moog Q: %s?", gen.Q)
  end
  unless vct?(gen.state)
    snd_display("moog state: %s?", gen.state)
  end
  if fneq(gen.A, 0.0)
    snd_display("moog A: %s?", gen.A)
  end
  if fneq(gen.freqtable, -0.861)
    snd_display("moog freqtable: %s?", gen.freqtable)
  end
  vals = make_vct!(20) do |i| moog_filter(gen, i.zero? ? 1.0 : 0.0) end
  unless vequal(vals, vct(0.0, 0.0, 0.0025, 0.0062, 0.0120, 0.0198, 0.0292, 0.0398,
                          0.0510, 0.0625, 0.0739, 0.0847, 0.0946, 0.1036, 0.1113, 0.1177,
                          0.1228, 0.1266, 0.1290, 0.1301))
    snd_display("moog output: %s?", vals)
  end
  close_sound(ind)
  #
  gen = make_ssb_am(440.0)
  gen1 = make_ssb_am(440.0)
  print_and_check(gen,
                  "ssb-am",
                  "ssb-am shift: up, sin/cos: 439.999975 Hz (0.000000 radians), order: 41",
                  "ssb-am shift: up, sin/cos: 440.000000 Hz (0.000000 radians), order: 41",
                  "ssb-am shift: up, sin/cos: 439.999969 Hz (0.000000 radians), order: 41")
  v0 = make_vct!(10) do ssb_am(gen, 0.0) end
  v1 = make_vct(10)
  vct_map!(v1, lambda do | | ssb_am?(gen1) ? ssb_am(gen1, 0.0) : -1.0 end)
  unless vequal(v0, v1)
    snd_display("map ssb_am: %s %s?", v0, v1)
  end
  unless ssb_am?(gen)
    snd_display("%s not ssb_am?", gen)
  end
  if fneq(gen.phase, 1.253787)
    snd_display("ssb_am phase: %s?", gen.phase)
  end
  if fneq(gen.frequency, 440.0)
    snd_display("ssb_am frequency: %s?", gen.frequency)
  end
  if gen.order != 41
    snd_display("ssb_am order: %s?", gen.order)
  end
  if gen.length != 41
    snd_display("ssb_am length: %s?", gen.length)
  end
  if gen.interp_type != Mus_interp_none
    snd_display("ssb_am interp_type: %s?", gen.interp_type)
  end
  if fneq(gen.xcoeff(0), -0.00124)
    snd_display("ssb_am xcoeff 0: %s?", gen.xcoeff(0))
  end
  if fneq(gen.xcoeff(1), 0.0)
    snd_display("ssb_am xcoeff 1: %s?", gen.xcoeff(1))
  end
  # 
  test_gen_equal(make_ssb_am(440.0), make_ssb_am(440.0), make_ssb_am(500.0))
  # 
  o1 = make_ssb_am(400.0)
  o2 = make_ssb_am_1(400.0)
  100.times do |i|
    inval = sin(0.1 * i)
    snd_test_neq(ssb_am_1(o2, inval), ssb_am(o1, inval), "ssb_am (up) at %d", i)
  end
  # 
  o1 = make_ssb_am(400.0)
  o2 = make_ssb_am_1(400.0)
  100.times do |i|
    inval = sin(0.1 * i)
    fmval = sin(0.2 * i)
    snd_test_neq(ssb_am_1(o2, inval, fmval), ssb_am(o1, inval, fmval), "ssb_am + fm (up) at %d", i)
  end
  # 
  o1 = make_ssb_am(-100.0)
  o2 = make_ssb_am_1(-100.0)
  100.times do |i|
    inval = random(1.0)
    snd_test_neq(ssb_am_1(o2, inval), ssb_am(o1, inval), "ssb_am (down) at %d", i)
  end
  # 
  o1 = make_ssb_am(1000.0, 100)
  o2 = make_ssb_am_1(1000.0, 100)
  100.times do |i|
    inval = random(1.0)
    snd_test_neq(ssb_am_1(o2, inval), ssb_am(o1, inval), "ssb_am (down) at %d", i)
  end
  #
  index = open_sound("pistol.snd")
  data = channel2vct(0, 100)
  convolve_with("oboe.snd", false)
  scl = maxamp
  convolve_with("oboe.snd", scl, index, 0, 0)
  if ffneq(maxamp, scl)         # okay
    snd_display("convolve_with amps: %s %s?", maxmap, scl)
  end
  close_sound(index)
  reader = make_sampler(0, "pistol.snd")
  10.times do |i|
    if fneq(data[i], next_sample(reader))
      snd_display("external reader trouble")
    end
  end
  free_sampler(reader)
  # 
  gen = make_moving_max(4)
  iv = vct(0.1, 0.05, -0.2, 0.15, -1.5, 0.1, 0.01, 0.001, 0.0, 0.0)
  tv = vct(0.1, 0.1, 0.2, 0.2, 1.5, 1.5, 1.5, 1.5, 0.1, 0.01)
  ov = Vct.new(10) do |i| moving_max(gen, iv[i]) end
  unless vequal(tv, ov)
    snd_display("moving_max: %s %s", ov, tv)
  end
  g1 = make_moving_max(10)
  1000.times do |i|
    if fneq(val = moving_max(g1, random(1.0)), pk = g1.data.peak)
      snd_display("moving_max[%s]: %s %s?", i, pk, val)
    end
  end
  #
  data = vct(1.0, 0.0, -1.1, 1.1001, 0.1, -1.1, 1.0, 1.0, 0.5, -0.01, 0.02, 0.0, 0.0, 0.0, 0.0)
  g = make_moving_max(3)
  odata = Vct.new(15) do |i| moving_max(g, data[i]) end
  unless vequal(odata, vct(1, 1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1, 1, 0.5, 0.02, 0.02, 0, 0))
    snd_display("moving_max odata: %s?", odata)
  end
  if odata[4] == odata[7]
    snd_display("moving_max 0.0001 offset?")
  end
  #
  odata = Vct.new(15, 0.0)
  data = vct(0.1, -0.2, 0.3, 0.4, -0.5, 0.6, 0.7, 0.8, -0.9, 1.0, 0.0, 0.0)
  g = make_moving_sum(3)
  data.each_with_index do |x, i| odata[i] = moving_sum(g, x) end
  unless vequal(odata, vct(0.1, 0.3, 0.6, 0.9, 1.2, 1.5, 1.8, 2.1, 2.4, 2.7, 1.9, 1, 0, 0, 0))
    snd_display("moving_sum odata: %s?", odata)
  end
  #
  odata = Vct.new(15, 0.0)
  g = make_moving_rms(4)
  data.each_with_index do |x, i| odata[i] = moving_rms(g, x) end
  unless vequal(odata,
                vct(0.05, 0.112, 0.187, 0.274, 0.367, 0.464, 0.561,
                    0.66, 0.758, 0.857, 0.783, 0.673, 0, 0, 0))
    snd_display("moving_rms odata: %s?", odata)
  end
  #
  odata = Vct.new(15, 0.0)
  g = make_moving_length(4)
  data.each_with_index do |x, i| odata[i] = moving_length(g, x) end
  unless vequal(odata,
                vct(0.1, 0.224, 0.374, 0.548, 0.735, 0.927, 1.122,
                    1.319, 1.517, 1.715, 1.565, 1.345, 0, 0, 0))
    snd_display("moving_length odata: %s?", odata)
  end
  #
  10.times do |i| data[i] = 0.5 - random(1.0) end
  g = make_moving_length(4)
  data.each_with_index do |x, i| odata[i] = moving_length(g, x) end
  k = 0
  (-3..8).each do |i|
    sum = 0.0
    4.times do |j|
      if i + j >= 0
        sum += data[i + j] * data[i + j]
      end
    end
    if fneq(odata[k], sqrt(sum))
      snd_display("moving_length ran: %s %s?", odata[k], sqrt(sum))
    end
    k += 1
  end
  # 
  10.times do |i| data[i] = 0.5 - random(1.0) end
  g = make_moving_sum(4)
  data.each_with_index do |x, i| odata[i] = moving_sum(g, x) end
  k = 0
  (-3..8).each do |i|
    sum = 0.0
    4.times do |j|
      if i + j >= 0
        sum += data[i + j].abs
      end
    end
    if fneq(odata[k], sum)
      snd_display("moving_sum ran: %s %s?", odata[k], sum)
    end
    k += 1
  end
  # 
  10.times do |i| data[i] = 0.5 - random(1.0) end
  g = make_moving_rms(4)
  data.each_with_index do |x, i| odata[i] = moving_rms(g, x) end
  k = 0
  (-3..8).each do |i|
    sum = 0.0
    4.times do |j|
      if i + j >= 0
        sum = sum + data[i + j] * data[i + j]
      end
    end
    if fneq(odata[k], sqrt(sum / 4.0))
      snd_display("moving_rms ran: %s %s?", odata[k], sqrt(sum / 4.0))
    end
    k += 1
  end
  # 
  ind = open_sound("oboe.snd")
  harmonicizer(550.0, [1, 0.5, 2, 0.3, 3, 0.2], 10)
  close_sound(ind)
  #
  argslist = make_array(16) do [:frequency, 440.0] end.flatten
  [:make_wave_train, :make_polyshape, :make_delay, :make_moving_average, :make_comb,
   :make_filtered_comb, :make_notch,
   :make_rand, :make_rand_interp, :make_table_lookup, :make_env,
   :make_readin, :make_locsig, :make_granulate, :make_convolve, :make_phase_vocoder].each do |make|
    if (res = Snd.catch do argslist.apply(:snd_func, make) end).first != :mus_error
      snd_display("long arglist to %s: %s", make, res.inspect)
    end
  end
  # 
  [[:table_lookup, [:wave]],
   [:wave_train, [:wave]],
   [:polyshape, [:coeffs]],
   [:delay, [:initial_contents]],
   [:filtered_comb, [:scaler, 0.5, :filter, make_one_zero(0.1, 0.2), :initial_contents]],
   [:rand, [:distribution]],
   [:fir_filter, [:xcoeffs]],
   [:iir_filter, [:ycoeffs]]].each do |name, args|
    v1 = Vct.new(10, 0.1)
    g1 = snd_func("make_" + name.to_s, *args + [v1])
    case name
    when :fir_filter
      if g1.xcoeffs != v1
        snd_display("%s data !=: %s %s?", name, g1.xcoeffs, v1)
      end
      v1[1] = 0.3
      if fneq(g1.xcoeffs[1], 0.3)
        snd_display("%s vct_set!: %s %s?", name, v1[1], g1.xcoeffs[1])
      end
      g1.xcoeffs[1] = 0.5
      if fneq(v1[1], 0.5)
        snd_display("%s set_data: %s %s?", name, v1[1], g1.xcoeffs[1])
      end
    when :iir_filter
      if g1.ycoeffs != v1
        snd_display("%s data !=: %s %s?", name, g1.ycoeffs, v1)
      end
      v1[1] = 0.3
      if fneq(g1.ycoeffs[1], 0.3)
        snd_display("%s vct_set!: %s %s?", name, v1[1], g1.ycoeffs[1])
      end
      g1.ycoeffs[1] = 0.5
      if fneq(v1[1], 0.5)
        snd_display("%s set_data: %s %s?", name, v1[1], g1.ycoeffs[1])
      end
    else
      if g1.data != v1
        snd_display("%s data !=: %s %s?", name, g1.data, v1)
      end
      v1[1] = 0.3
      if fneq(g1.data[1], 0.3)
        snd_display("%s vct_set!: %s %s?", name, v1[1], g1.data[1])
      end
      g1.data[1] = 0.5
      if fneq(v1[1], 0.5)
        snd_display("%s set_data: %s %s?", name, v1[1], g1.data[1])
      end
    end
  end
  f1 = make_frame(2, 0.1, 0.2)
  f2 = make_frame(2, 0.3, 0.5)
  f3 = make_frame(2, 0, 0)
  f4 = frame_add(f1, f2, f3)
  if f3 != f4
    snd_display("frame_add data !=: %s %s?", f3, f4)
  end
  f4 = frame_multiply(f1, f2, f3)
  if f3 != f4
    snd_display("frame_multiply data !=: %s %s?", f3, f4)
  end
end

def test_08_23
  [[:all_pass,       false, 0.0, false],
   [:asymmetric_fm,  false, 0.0, false],
   [:moving_average, false, 1.0, false],
   [:comb,           false, 0.0, false],
   [:convolve,       [:filter, vct(0, 1, 2)], lambda { |dir| 0.0 }, false],
   [:delay,          false, 0.0, false],
   [:env,            [:envelope, [0, 1, 1, 0]], false, false],
   [:filter,         [:xcoeffs, vct(0, 1, 2)], 0.0, false],
   [:filter,         [:ycoeffs, vct(0, 1, 2)], 0.0, false],
   [:filter,         [:xcoeffs, vct(1, 2, 3), :ycoeffs, vct(0, 1, 2)], 0.0, false],
   [:fir_filter,     [:xcoeffs, vct(0, 1, 2)], 0.0, false],
   [:formant,        false, 0.0, false],
   [:frame,          [3], 0, lambda { |gen, ind| frame_ref(gen, ind) }, false],
   [:granulate,      false, lambda { |dir| 0.0 }, false],
   [:iir_filter,     [:ycoeffs, vct(0, 1, 2)], 0.0, false],
   [:locsig,         false, 0.0, lambda { |gen, a| locsig(gen, 0, a) }],
   [:mixer,          [3, 3], 0, lambda { |gen, a| mixer_ref(gen, a, 0) }],
   [:notch,          false, 0.0, false],
   [:one_pole,       false, 0.0, false],
   [:one_zero,       false, 0.0, false],
   [:oscil,          false, 0.0, false],
   [:pulse_train,    false, 0.0, false],
   [:rand,           false, 0.0, false],
   [:rand_interp,    false, 0.0, false],
   [:sawtooth_wave,  false, 0.0, false],
   [:square_wave,    false, 0.0, false],
   [:src,            false, lambda { |dir| 0.0 }, lambda { |gen, a| src(gen, 0.0, a) }],
   [:table_lookup,   false, 0.0, false],
   [:triangle_wave,  false, 0.0, false],
   [:two_pole,       false, 0.0, false],
   [:two_zero,       false, 0.0, false],
   [:wave_train,     false, 0.0, false],
   [:polyshape,      false, 0.0, false],
   [:phase_vocoder,  false, lambda { |dir| 0.0 }, false],
   [:ssb_am,         false, 0.0, false]].each do |name_sym, make_args, arg, run_func|
    gen = if make_args
            snd_func(format("make_%s", name_sym), *make_args)
          else
            snd_func(format("make_%s", name_sym))
          end
    unless snd_func(format("%s%c", name_sym, ??), gen)
      snd_display("%s: %s?", name_sym, gen)
    end
    tag = if proc?(run_func)
            Snd.catch do arg ? run_func.call(gen, arg) : run_func.call(gen) end.first
          else
            Snd.catch do arg ? snd_func(name_sym, gen, arg) : snd_func(name_sym, gen) end.first
          end
    if (not number?(tag)) and (not frame?(tag))
      snd_display("%s (make_gen, gen, gen? test): %s %s?", name_sym, arg, tag)
    end
    [:mus_channel,
     :mus_channels,
     :mus_data,
     :mus_feedback,
     :mus_feedforward,
     :mus_frequency,
     :mus_hop,
     :mus_increment,
     :mus_length,
     :mus_location,
     :mus_order,
     :mus_phase,
     :mus_ramp,
     :mus_random,
     :mus_run,
     :mus_scaler,
     :mus_xcoeffs,
     :mus_ycoeffs].each do |func_sym|
      if (res = Snd.catch do snd_func(func_sym, false) end).first != :wrong_type_arg
        snd_display("generic function with false: %s.%s(false) -> %s",
                    name_sym, func_sym, res.inspect)
      end
      g1 = make_oscil
      g2 = make_one_pole(0.1, 0.9)
      res = Snd.catch do snd_func(func_sym, g1) end
      if symbol?(res) and
          res != :wrong_type_arg and
          res != :mus_error
        snd_display("generic %s of oscil: %s", name_sym, res.inspect)
      end
      res = Snd.catch do snd_func(func_sym, g2) end
      if symbol?(res) and
          res != :wrong_type_arg and
          res != :mus_error
        snd_display("generic %s of delay: %s", name_sym, res.inspect)
      end
      tag = Snd.catch do snd_func(func_sym, gen) end.first
      if (not symbol?(tag)) and
          tag != :mus_error and
          tag != :out_of_range and
          tag != :wrong_type_arg and
          (func_sym != :mus_data or vct?(tag))
        set_tag = Snd.catch do set_snd_func(func_sym, gen, tag) end.first
        if symbol?(set_tag) and
            set_tag != :mus_error and
            set_tag != :out_of_range and
            set_tag != :wrong_type_arg and
            set_tag != :no_method_error
          snd_display("%s.%s= tag: %s set_tag: %s?",
                      name_sym, func_sym.to_s[4..-1], tag.inspect, set_tag.inspect)
        end
      end
    end
  end
  # 
  functions = [[:all_pass,       false, false],
               [:asymmetric_fm,  false, false],
               [:moving_average, false, false],
               [:comb,           false, false],
               [:filtered_comb,  [:filter, make_one_zero(0.5, 0.5)], false],
               [:convolve,       [:filter, vct(0, 1, 2), :input, lambda { |dir| 1.0 }], false],
               [:delay,          false, false],
               [:env, [:envelope, [0, 1, 1, 0], :length, 11], lambda { |gen, ignored| env(gen) }],
               [:filter,         [:xcoeffs, vct(0, 1, 2)], false],
               [:filter,         [:ycoeffs, vct(0, 1, 2)], false],
               [:filter,         [:xcoeffs, vct(1, 2, 3), :ycoeffs, vct(0, 1, 2)], false],
               [:fir_filter,     [:xcoeffs, vct(0, 1, 2)], false],
               [:formant,        [:radius, 0.1, :frequency, 440.0], false],
               [:granulate,      [:input, lambda { |dir| 1.0 }], false],
               [:iir_filter,     [:xcoeffs, vct(0, 1, 2)], false],
               [:locsig,         false, lambda { |gen, a| locsig(gen, 0, 1.0) }],
               [:notch,          false, false],
               [:one_pole,       [0.3, 0.7], false],
               [:one_zero,       [0.5, 0.5], false],
               [:oscil,          false, false],
               [:pulse_train,    false, false],
               [:sawtooth_wave,  false, false],
               [:square_wave,    false, false],
               [:table_lookup,   [:wave, make_vct(128, 0.1)], false],
               [:triangle_wave,  false, false],
               [:two_pole,       [0.1, 0.3, 0.6], false],
               [:two_zero,       [0.1, 0.3, 0.5], false],
               [:polyshape,      [:frequency, 440.0, :partials, [1, 1]], false],
               [:phase_vocoder,  [lambda { |dir| 1.0 }], false],
               [:ssb_am,         false, false]]
  functions.each do |name_sym, make_args, run_func|
    gen = if make_args
            snd_func(format("make_%s", name_sym), *make_args)
          else
            snd_func(format("make_%s", name_sym))
          end
    data = make_vct!(10) do |i|
      if proc?(run_func)
        run_func.call(gen, i.zero? ? 1.0 : 0.0)
      else
        snd_func(name_sym, gen, i.zero? ? 1.0 : 0.0)
      end
    end
    2.times do |k|
      mus_reset(gen)
      unless proc?(run_func)
        not_zero = false
        first_val = k.zero? ? snd_func(name_sym, gen, 1.0) : mus_apply(gen, 1.0, 0.0)
        if data[0] != 0.0
          not_zero = true
        end
        if fneq(data[0], first_val)
          snd_display("[%s] %s: 0 %s %s?", k.zero? ? :run : :apply, name_sym, data[0], first_val)
        end
        (1...10).each do |i|
          old_val = data[i]
          new_val = k.zero? ? snd_func(name_sym, gen, 0.0) : mus_apply(gen, 0.0, 0.0)
          if old_val != 0.0
            not_zero = true
          end
          if fneq(old_val, new_val)
            snd_display("[%s] %s: %s %s %s?",
                        k.zero? ? :run : :apply, name_sym, i, old_val, new_val)
          end
        end
        unless not_zero
          case name_sym
          when :polyshape, :ssb_am
            next
          else
            snd_display("%s not much of a reset test!", name_sym)
          end
        end
      end
    end
  end
  random_args = [1.5,
                 "/hiho",
                 [0, 1],
                 1234,
                 make_vct(3),
                 make_color_with_catch(0.95, 0.95, 0.95),
                 :mus_error,
                 sqrt(-1.0),
                 make_delay(32),
                 lambda do || true end,
                 make_sound_data(2, 3),
                 0,
                 1,
                 -1,
                 make_hook("snd_test"),
                 false,
                 true,
                 0.0,
                 1.0,
                 -1.0, 
                 [],
                 3,
                 4,
                 2,
                 8,
                 16,
                 32,
                 64,
                 2.0 ** 21.5,
                 2.0 ** -18.0]
  functions.each do |name_sym, make_args, run_func|
    gen = if make_args
            snd_func(format("make_%s", name_sym), *make_args)
          else
            snd_func(format("make_%s", name_sym))
          end
    random_args.each do |arg1|
      Snd.catch do 
        if proc?(run_func)
          run_func.call(gen, arg1)
        else
          snd_func(name_sym, gen, arg1)
        end
      end
      random_args.each do |arg2|
        Snd.catch do
          mus_run(gen, arg1, arg2)
        end
      end
    end
  end
end

def test_08_24
  random_args = [2.0 ** 21.5,
                 2.0 ** -18.0,
                 1.5,
                 "/hiho",
                 [0, 1],
                 1234,
                 # FIXME (print_vct segfault)
                 # make_vct(3) removed from list because:
                 #   if $all_args is true
                 #   0x08096617 in print_vct (obj=Cannot access memory at address 0xbf003038)
                 # make_vct(3),
                 make_color_with_catch(0.1, 0.2, 0.3),
                 sqrt(-1.0),
                 make_delay(32),
                 lambda do || 0.0 end,
                 lambda do |dir| 1.0 end,
                 lambda do |a, b, c| 1.0 end,
                 0,
                 1,
                 -1,
                 false,
                 true,
                 0.0,
                 1.0,
                 -1.0,
                 []]
  random_gen = lambda do |*args|
    [:make_all_pass,
     :make_asymmetric_fm,
     :make_moving_average,
     :make_table_lookup,
     :make_triangle_wave,
     :make_comb,
     :make_delay,
     :make_env,
     :make_fft_window,
     :make_filter,
     :make_filtered_comb,
     :make_fir_filter,
     :make_formant,
     :make_frame,
     :make_iir_filter,
     :make_locsig,
     :make_mixer,
     :make_notch,
     :make_one_pole,
     :make_one_zero,
     :make_oscil,
     :make_pulse_train,
     :make_rand,
     :make_rand_interp,
     :make_sawtooth_wave,
     :make_polyshape,
     :make_polywave,
     :make_square_wave,
     :make_two_pole,
     :make_two_zero,
     :make_wave_train,
     :make_ssb_am].each do |make_func|
      if mus_generator?(gen = Snd.catch do
                          snd_func(make_func, *args)
                        end.first)
        random_args.each do |arg|
          Snd.catch do
            gen.run(arg)
          end
        end
      end
    end
  end
  random_gen.call([])
  random_args.each do |arg1|
    random_gen.call(arg1)
    random_args.each do |arg2|
      random_gen.call(arg1, arg2)
      if $all_args or $bigtest_08
        random_args.each do |arg3|
          random_gen.call(arg1, arg2, arg3)
          if $bigtest_08
            random_args.each do |arg4|
              random_gen.call(arg1, arg2, arg3, arg4)
            end
          end
        end
      end
    end
  end
end

def test_08
  test_08_00
  test_08_01
  test_08_02
  test_08_03
  test_08_04
  test_08_05
  test_08_06
  test_08_07
  test_08_08
  test_08_09
  test_08_10
  test_08_11
  test_08_12
  test_08_13
  test_08_14
  test_08_15
  test_08_16
  test_08_17
  test_08_18
  test_08_19
  test_08_20
  test_08_21
  test_08_22
  test_08_23
  test_08_24
end

# ---------------- test 09: mix ----------------

def test_09_00
  new_index = new_sound("hiho.wave", Mus_next, Mus_bshort, 22050, 1)
  select_sound(new_index)
  if res = find_mix(0, new_index, 0)
    snd_display("found non-existent mix: %s?", res)
  end
  unless mix?(mix_id = mix("pistol.snd", 100).car)
    snd_display("%s not mix?", mix_id)
  end
  view_mixes_dialog
  pos = mix_position(mix_id)
  len = mix_length(mix_id)
  spd = mix_speed(mix_id)
  snd, chn = mix_home(mix_id)[0, 2]
  nam = mix_name(mix_id)
  amp = mix_amp(mix_id)
  mr = make_mix_sampler(mix_id)
  unless mix_sampler?(mr)
    snd_display("%s not mix_sampler?", mr)
  end
  if region_sampler?(mr)
    snd_display("mix_sampler: region %s?", mr)
  end
  if (res = sampler_position(mr)).nonzero?
    snd_display("mix_sampler_position: %s?", res)
  end
  if sampler_at_end?(mr)
    snd_display("mix_sampler at end? %s", mr)
  end
  unless (res = sampler_home(mr)).eql?(mix_id)
    snd_display("%s home: %s?", sampler_home(mr), res)
  end
  if mr.to_s[0, 16] != "#<mix-sampler mi"
    snd_display("mix_sampler actually got: [%s]?", mr.to_s[0, 16])
  end
  99.times do |i|
    mx = i.odd? ? read_mix_sample(mr) : read_mix_sample(mr)
    sx = sample(100 + i)
    if fneq(mx, sx)
      snd_display("read_mix_sample: %s %s?", mx, sx)
    end
  end
  if fneq(mx = mr.call, sx = sample(199))
    snd_display("mix_sample 100: %s %s?", mx, sx)
  end
  free_sampler(mr)
  #
  if pos != 100
    snd_display("mix_position: %s?", pos)
  end
  if len != 41623
    snd_display("mix_length: %s?", len)
  end
  unless snd.eql?(new_index)
    snd_display("snd mix_home: %s?", snd)
  end
  if chn.nonzero?
    snd_display("chn mix_home: %s?", chn)
  end
  if fneq(amp, 1.0)
    snd_display("mix_amp: %s?", amp)
  end
  if fneq(spd, 1.0)
    snd_display("mix_speed: %s?", spd)
  end
  unless nam.null?
    snd_display("mix_name: %s?", nam)
  end
  Snd.catch(:mus_error, lambda do |*args| snd_display("cannot play mix: %s", args) end) do
    play(mix_id)
  end
  Snd.catch(:mus_error, lambda do |*args| snd_display("cannot play mix from 1000: %s", args) end) do
    play(mix_id, :start, 1000)
  end
  set_mix_name(mix_id, "test-mix")
  if (res = mix_name(mix_id)) != "test-mix"
    snd_display("mix_name set: %s?", res)
  end
  id = mix_name2id("test-mix")
  if mix2integer(id) != mix2integer(mix_id)
    snd_display("mix_name2id: %s %s?", id, mix_id)
  end
  set_mix_name(mix_id, "test-mix-again")
  if (res = mix_name(mix_id)) != "test-mix-again"
    snd_display("mix_name again: %s?", res)
  end
  set_mix_name(mix_id, false)
  unless (res = mix_name(mix_id)).null?
    snd_display("mix_name false: %s?", res)
  end
  set_mix_position(mix_id, 200)
  set_mix_amp(mix_id, 0.5)
  set_mix_speed(mix_id, 2.0)
  set_mix_amp_env(mix_id, [0, 0, 1, 1])
  val = mix_amp_env(mix_id)
  set_mix_amp_env(mix_id, mix_amp_env(mix_id))
  unless vequal(res = mix_amp_env(mix_id), val)
    snd_display("set_mix_amp_env to self: %s %s?", val, res)
  end
  set_mix_tag_y(mix_id, 20)
  pos = mix_position(mix_id)
  spd = mix_speed(mix_id)
  amp = mix_amp(mix_id)
  my = mix_tag_y(mix_id)
  if pos != 200
    snd_display("set_mix_position: %s?", pos)
  end
  if fneq(spd, 2.0)
    snd_display("set_mix_speed: %s?", spd)
  end
  if my != 20
    snd_display("set_mix_mix_tag_y: %s?", my)
  end
  if fneq(amp, 0.5)
    snd_display("set_mix_amp: %s?", amp)
  end
  if (res = mix_amp_env(mix_id)) != [0, 0, 1, 1]
    snd_display("set_mix_amp_env: %s?", res)
  end
  #
  mix_vct(make_vct(3, 0.1), 100)
  set_cursor(0)
  if (not mix?(nid = find_mix(100))) or mix_position(nid) != 100
    snd_display("find_mix(100) %s %s %s?",
                nid, (mix?(nid) and mix_position(nid)),
                mixes(new_index, 0).map do |m| mix_position(m) end)
  end
  if (not mix?(nid = find_mix(200))) or mix_position(nid) != 200
    snd_display("find_mix(200) %s %s?", nid, (mix?(nid) and mix_position(nid)))
  end
  #
  mix_id = mix("oboe.snd", 100).car
  set_mix_waveform_height(40)
  set_mix_property(:hiho, mix_id, 123)
  if (res = mix_property(:hiho, mix_id)) != 123
    snd_display("mix_property (123): %s?", res)
  end
  if (res = mix_property(:not_there, mix_id))
    snd_display("mix_not_property: %s?", res)
  end
  update_time_graph
  set_mix_waveform_height(20)
  revert_sound(new_index)
  #
  v1 = envelope_interp(1.0, [0, 0, 2.0, 1.0])
  v2 = envelope_interp(1.0, [0, 0.0, 1, 1.0, 2, 0.0])
  v3 = envelope_interp(2.0, [0, 0.0, 1, 1.0])
  v4 = envelope_interp(0.0, [1, 0.5, 2, 0])
  if fneq(v1, 0.5)
    snd_display("envelope_interp(1): %s (0.5)?", v1)
  end
  if fneq(v2, 1.0)
    snd_display("envelope_interp(2): %s (1.0)?", v2)
  end
  if fneq(v3, 1.0)
    snd_display("envelope_interp(3): %s (1.0)?", v3)
  end
  if fneq(v4, 0.5)
    snd_display("envelope_interp(4): %s (0.5)?", v4)
  end
  v1 = envelope_interp( 0.0, [-1,  0, 0, 1, 1, -1])
  v2 = envelope_interp(-0.5, [-1,  0, 0, 1, 1, -1])
  v3 = envelope_interp(-0.5, [-1, -1, 0, 1, 1, -1])
  v4 = envelope_interp(-0.5, [-1, -1, 1, 1])
  v5 = envelope_interp(-1.5, [-1, -1, 1, 1])
  v6 = envelope_interp( 1.5, [-1, -1, 1, 1])
  if fneq(v1,  1.0)
    snd_display("envelope_interp(1a): %s ( 1.0)?", v1)
  end
  if fneq(v2,  0.5)
    snd_display("envelope_interp(2a): %s ( 0.5)?", v2)
  end
  if fneq(v3,  0.0)
    snd_display("envelope_interp(3a): %s ( 0.0)?", v3)
  end
  if fneq(v4, -0.5)
    snd_display("envelope_interp(4a): %s (-0.5)?", v4)
  end
  if fneq(v5, -1.0)
    snd_display("envelope_interp(5a): %s (-1.0)?", v5)
  end
  if fneq(v6,  1.0)
    snd_display("envelope_interp(6a): %s ( 1.0)?", v6)
  end
  v1 = multiply_envelopes([0.0, 0.0, 2.0, 0.5], [0.0, 0.0, 1.0, 2.0, 2.0, 1.0])
  v2 = window_envelope(1.0, 3.0, [0.0, 0.0, 5.0, 1.0])
  unless vequal(v1, [0, 0, 0.5, 0.5, 1, 0.5])
    snd_display("multiply_envelopes: %s?", v1)
  end
  unless vequal(v2, [1, 0.2, 3, 0.6])
    snd_display("window_envelope: %s?", v2)
  end
  close_sound(new_index)
  dismiss_all_dialogs
end

def test_09_02
  ind = new_sound("fmv.snd", Mus_next, Mus_bshort, 22050, 1, "mix tests")
  insert_silence(0, 20, ind)
  indout = new_sound("test.snd", Mus_next, Mus_bshort, 22050, 1, "mix tests")
  insert_silence(0, 10, indout)
  set_sample(2, 0.5, indout, 0)
  set_sample(5, 0.25, indout, 0)
  save_sound(indout)
  close_sound(indout)
  tag = mix("test.snd").car
  samps = channel2vct(0, 20)
  v = make_vct(20)
  v[2] = 0.5
  v[5] = 0.25
  unless  vequal(samps, v)
    snd_display("mix 1->1: %s %s?", samps, v)
  end
  unless mix?(tag)
    snd_display("mix 1->1 tag: %s?", tag)
  end
  undo_edit
  tag = mix("test.snd", 5).car
  samps = channel2vct(0, 20)
  v = make_vct(20)
  v[7] = 0.5
  v[10] = 0.25
  unless  vequal(samps, v)
    snd_display("mix 1->1 at 5: %s %s?", samps, v)
  end
  unless mix?(tag)
    snd_display("mix 1->1 at 5 tag: %s?", tag)
  end
  undo_edit
  tag = mix("test.snd", 0, 0, ind, 0, false)
  samps = channel2vct(0, 20)
  v = make_vct(20)
  v[2] = 0.5
  v[5] = 0.25
  unless  vequal(samps, v)
    snd_display("mix 1->1 at 0: %s %s?", samps, v)
  end
  if mix?(tag)
    snd_display("mix 1->1 at 0 tag: %s?", tag)
  end
  undo_edit
  indout = new_sound("test.snd", Mus_next, Mus_bshort, 22050, 2, "mix tests")
  insert_silence(0, 10, indout, 0)
  insert_silence(0, 10, indout, 1)
  set_sample(2, 0.5, indout, 0)
  set_sample(5, 0.25, indout, 0)
  set_sample(2, 0.95, indout, 1)
  set_sample(5, 0.125, indout, 1)
  save_sound(indout)
  close_sound(indout)
  tag = mix("test.snd", 0, 1).car
  samps = channel2vct(0, 20)
  v = make_vct(20)
  v[2] = 0.95
  v[5] = 0.125
  unless  vequal(samps, v)
    snd_display("mix 2->1: %s %s?", samps, v)
  end
  unless mix?(tag)
    snd_display("mix 2->1 tag: %s?", tag)
  end
  undo_edit
  tag = mix("test.snd", 5, 1).car
  samps = channel2vct(0, 20)
  v = make_vct(20)
  v[7] = 0.95
  v[10] = 0.125
  unless  vequal(samps, v)
    snd_display("mix 2->1 at 5: %s %s?", samps, v)
  end
  unless mix?(tag)
    snd_display("mix 2->1 at 5 tag: %s?", tag)
  end
  undo_edit
  close_sound(ind)
  #
  ind = new_sound("fmv.snd", Mus_next, Mus_bshort, 22050, 2, "mix tests")
  insert_silence(0, 20, ind, 0)
  insert_silence(0, 20, ind, 1)
  tag = mix("test.snd", 0, true).car
  samps0 = channel2vct(0, 20, ind, 0)
  samps1 = channel2vct(0, 20, ind, 1)
  v = make_vct(20)
  v[2] = 0.5
  v[5] = 0.25
  unless  vequal(samps0, v)
    snd_display("mix 1->1 (2): %s %s?", samps0, v)
  end
  v[2] = 0.95
  v[5] = 0.125
  unless  vequal(samps1, v)
    snd_display("mix 1->1 (3): %s %s?", samps1, v)
  end
  unless mix?(tag)
    snd_display("mix 1->1 tag: %s?", tag)
  end
  undo_edit(1, ind, 0)
  undo_edit(1, ind, 1)
  tag = mix("test.snd", 0, 1, ind, 1, false)
  samps0 = channel2vct(0, 20, ind, 0)
  samps1 = channel2vct(0, 20, ind, 1)
  v = make_vct(20)
  unless  vequal(samps0, v)
    snd_display("mix 1->1 (4): %s %s?", samps0, v)
  end
  v[2] = 0.95
  v[5] = 0.125
  unless  vequal(samps1, v)
    snd_display("mix 1->1 (5): %s %s?", samps1, v)
  end
  if mix?(tag)
    snd_display("mix 1->1 tag: %s?", tag)
  end
  undo_edit(1, ind, 1)
  set_sync(1, ind)
  mix("test.snd", 0, true).car
  samps0 = channel2vct(0, 20, ind, 0)
  samps1 = channel2vct(0, 20, ind, 1)
  v = make_vct(20)
  v[2] = 0.5
  v[5] = 0.25
  unless  vequal(samps0, v)
    snd_display("mix 1->1 (6): %s %s?", samps0, v)
  end
  v[2] = 0.95
  v[5] = 0.125
  unless  vequal(samps1, v)
    snd_display("mix 1->1 (7): %s %s?", samps1, v)
  end
  undo_edit
  set_cursor(5, ind)
  mix("test.snd", cursor, true).car
  samps0 = channel2vct(0, 20, ind, 0)
  samps1 = channel2vct(0, 20, ind, 1)
  v = make_vct(20)
  v[7] = 0.5
  v[10] = 0.25
  unless  vequal(samps0, v)
    snd_display("mix 1->1 (8): %s %s?", samps0, v)
  end
  v[7] = 0.95
  v[10] = 0.125
  unless  vequal(samps1, v)
    snd_display("mix 1->1 (9): %s %s?", samps1, v)
  end
  undo_edit
  close_sound(ind)
  delete_files("test.snd", "fmv.snd")
  #
  # check ripple_mixes
  #
  ind = open_sound("oboe.snd")
  data = channel2vct(100, 100)
  m1 = mix_vct(data, 321, ind, 0, true)
  m2 = mix_vct(data, 123, ind, 0, true)
  set_mix_position(m1, 500)
  if (res = mix_position(m1)) != 500
    snd_display("mix_position m1[0]: %s?", res)
  end
  if (res = mix_position(m2)) != 123
    snd_display("mix_position m2[0]: %s?", res)
  end
  undo_edit
  set_mix_position(m2, 500)
  if (res = mix_position(m2)) != 500
    snd_display("mix_position m2[1]: %s?", res)
  end
  if (res = mix_position(m1)) != 321
    snd_display("mix_position m1[1]: %s?", res)
  end
  undo_edit
  insert_silence(0, 100)
  if (res = mix_position(m1)) != 321 + 100
    snd_display("mix_position m1[2]: %s?", res)
  end
  if (res = mix_position(m2)) != 123 + 100
    snd_display("mix_position m2[2]: %s?", res)
  end
  delete_samples(0, 50)
  if (res = mix_position(m1)) != 321 + 50
    snd_display("mix_position m1[3]: %s?", res)
  end
  if (res = mix_position(m2)) != 123 + 50
    snd_display("mix_position m2[3]: %s?", res)
  end
  undo_edit(2)
  set_mix_position(m2, 500)
  undo_edit
  scale_channel(0.5, 1000, 100)
  if (res = mix_position(m2)) != 123
    snd_display("mix_position m2[5]: %s?", res)
  end
  if (res = mix_position(m1)) != 321
    snd_display("mix_position m1[5]: %s?", res)
  end
  undo_edit
  set_mix_position(m2, 500)
  undo_edit
  set_mix_position(m2, 500)
  undo_edit
  ramp_channel(0.0, 1.0, 3000, 100)
  if (res = Snd.catch do
        if (res = mix_position(m2)) != 123
          snd_display("mix_position m2[7]: %s?", res)
        end
        if (res = mix_position(m1)) != 321
          snd_display("mix_position m1[7]: %s?", res)
        end
      end).first
    snd_display("mix_position trouble: %s", res.inspect)
  end
  close_sound(ind)
  #
  # check that current console is correct
  #
  ind = open_sound("storm.snd")
  set_x_bounds([0, 80])
  make_selection(1000000, 1050000)
  m1 = mix_selection(900000).car
  m2 = mix_selection(400000).car
  as_one_edit(lambda do | |
                set_mix_position(m1, 0)
                set_mix_position(m2, 1)
              end)
  if ((res1 = mix_position(m1)) != 0) or ((res2 = mix_position(m2)) != 1)
    snd_display("as_one_edit positions: %s %s?", res1, res2)
  end
  undo_channel
  if ((res1 = mix_position(m1)) != 900000) or ((res2 = mix_position(m2)) != 400000)
    snd_display("as_one_edit positions after undo: (%s): %s (%s): %s?", m1, res1, m2, res2)
  end
  redo_channel
  if (res1 = mix_position(m1)) != 0 or (res2 = mix_position(m2)) != 1
    snd_display("as_one_edit positions after redo: %s %s?", res1, res2)
  end
  close_sound(ind)
  #
  ind = open_sound("2.snd")
  make_selection(0, 10000, ind)
  if (res = selection_chans) != 2
    snd_display("stereo selection: %s?", res)
  end
  set_sync(true, ind)
  md = mix_selection(500, ind).car
  unless mix?(integer2mix(mix2integer(md) + 1))
    snd_display("where is 2nd mix? %s %s?", md, mixes)
  end
  if (res = edit_position(ind, 0)) != 1
    snd_display("edit_position 0 after stereo mix selection: %s?", res)
  end
  if (res = edit_position(ind, 1)) != 1
    snd_display("edit_position 1 after stereo mix selection: %s?", res)
  end
  close_sound(ind)
end

def test_mix_disconnect(name, id0, chn0, id1, chn1)
  amp0 = mix_amp(id0)
  amp1 = mix_amp(id1)
  env0 = mix_amp_env(id0)
  env1 = mix_amp_env(id1)
  set_mix_amp(id0, mix_amp(id0) * 0.5)
  if fneq(mix_amp(id0), 0.5 * amp0) or
      fneq(mix_amp(id1), amp1)
    snd_display("pan_mix disconnect amp %s: %s (%s) %s (%s)?",
                name, mix_amp(id0), amp0, mix_amp(id1), amp1)
  end
  set_mix_amp_env(id1, [0.0, random(1.0), 1.0, random(1.0)])
  if mix_amp_env(id0) != env0 or (not vequal(mix_amp_env(id1), (env1 or [])))
    snd_display("pan_mix disconnect amp_env %s: %s (%s) %s (%s)?",
                name,
                mix_amp_env(id0), env0,
                mix_amp_env(id1), env1)
  end
  if id0 != id1
    pos0 = mix_position(id0)
    pos1 = mix_position(id1)
    spd0 = mix_speed(id0)
    spd1 = mix_speed(id1)
    set_mix_position(id0, pos0 + 12)
    if mix_position(id0) == pos0 or mix_position(id1) != pos1
      snd_display("pan_mix disconnect position %s: %s (%s) %s (%s)?",
                  name, mix_position(id0), pos0, mix_position(id1), pos1)
    end
    set_mix_speed(id1, mix_speed(id1) * 1.5)
    if fneq(mix_speed(id1), 1.5 * spd1) or fneq(mix_speed(id0), spd0)
      snd_display("pan_mix disconnect speed %s: %s (%s) %s (%s)?",
                  name, mix_speed(id0), spd0, mix_speed(id1), spd1)
    end
  end
end

def test_09_03
  ind = new_sound("test.snd")
  v = Vct.new(20) do |i| i * 0.01 end
  vct2channel(v)
  v.map! do |val| -val end
  mx = mix_vct(v, 10)
  hi = make_mix_sampler(mx, 0)
  ho = make_mix_sampler(mx, 5)
  10.times do |i|
    ho_val = ho.call
    hi_val = hi.call
    if fneq(hi_val, i * -0.01)
      snd_display("mix_reader at %s from 0: %s?", i, hi_val)
      break
    end
    if fneq(ho_val, (i + 5)* -0.01)
      snd_display("mix_reader at %s from 5: %s?", i, ho_val)
      break
    end
  end
  revert_sound(ind)
  v = Vct.new(21)
  v.fill(0.5)
  vct2channel(v)
  mx = mix_vct(v, 10)
  set_mix_amp_env(mx, [0, 0, 1, 1])
  hi = make_mix_sampler(mx, 0)
  ho = make_mix_sampler(mx, 10)
  10.times do |i|
    ho_val = ho.call
    hi_val = hi.call
    if fneq(hi_val, i * 0.025)
      snd_display("mix_reader enved at %s from 0: %s?", i, hi_val)
      break
    end
    if fneq(ho_val, (i + 10)* 0.025)
      snd_display("mix_reader enved at %s from 5: %s?", i, ho_val)
      break
    end
  end
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  id = mix_vct(Vct.new(10, 0.1))
  set_mix_position(id, 100)
  if (res1 = mix_position(id)) != 100 or (res2 = edit_position(ind, 0)) != 2
    snd_display("mix_position init: %s %s?", res1, res2)
  end
  set_mix_position(id, 100)
  if (res1 = mix_position(id)) != mix_position(id) or (res2 = edit_position(ind, 0)) != 2
    snd_display("mix_position 2 (no-op): %s %s?", res1, res2)
  end
  set_mix_amp(id, 1.0)
  if fneq(res1 = mix_amp(id), 1.0) or (res2 = edit_position(ind, 0)) != 2
    snd_display("mix_amp no-op: %s %s?", res1, res2)
  end
  set_mix_amp(id, 0.5)
  if fneq(res1 = mix_amp(id), 0.5) or (res2 = edit_position(ind, 0)) != 3
    snd_display("mix_amp 0.5: %s %s?", res1, res2)
  end
  set_mix_amp(id, mix_amp(id))
  if fneq(res1 = mix_amp(id), 0.5) or (res2 = edit_position(ind, 0)) != 3
    snd_display("mix_amp no-op: %s %s?", res1, res2)
  end
  set_mix_speed(id, 1.0)
  if fneq(res1 = mix_speed(id), 1.0) or (res2 = edit_position(ind, 0)) != 3
    snd_display("mix_speed no-op: %s %s?", res1, res2)
  end
  set_mix_speed(id, 0.5)
  if fneq(res1 = mix_speed(id), 0.5) or (res2 = edit_position(ind, 0)) != 4
    snd_display("mix_speed 0.5: %s %s?", res1, res2)
  end
  set_mix_speed(id, mix_speed(id))
  if fneq(res1 = mix_speed(id), 0.5) or (res2 = edit_position(ind, 0)) != 4
    snd_display("mix_speed 2 no-op: %s %s?", res1, res2)
  end
  set_mix_amp_env(id, [0, 0, 1, 1])
  if (res = edit_position(ind, 0)) != 5
    snd_display("mix_amp init: %s %s?", mix_amp_env(id), res)
  end
  set_mix_amp_env(id, [0, 0, 1, 1])
  if (res = edit_position(ind, 0)) != 5
    snd_display("mix_amp no-op: %s %s?", mix_amp_env(id), res)
  end
  close_sound(ind)
  #
  if $with_test_motif
    ind = open_sound("oboe.snd")
    mix1 = mix_vct([0.1, 0.2, 0.3].to_vct, 120, ind, 0, true, "origin!")
    mix2 = mix_vct([0.1, 0.2, 0.3].to_vct, 1200, ind, 0, true)
    mix3 = mix_vct([0.1, 0.2, 0.3].to_vct, 12000, ind, 0, true)
    unless mixes(ind, 0) == [mix1, mix2, mix3]
      snd_display("mixes: %s %s?", mixes(ind, 0), [mix1, mix2, mix3])
    end
    unless mixes == [[[mix1, mix2, mix3]]]
      snd_display("mixes all: %s %s?", mixes, [[[mix1, mix2, mix3]]])
    end
    view_mixes_dialog
    set_mix_dialog_mix(mix1)
    mixd = dialog_widgets[16]
    if widget?(nxt = Snd.catch(:no_such_widget) do find_child(mixd, "Next") end.first)
      if widget?(prev = Snd.catch(:no_such_widget) do find_child(mixd, "Previous") end.first)
        force_event
        if (not RXtIsSensitive(nxt)) or RXtIsSensitive(prev)
          snd_display("mix_dialog next/previous: %s %s %s %s?",
                      nxt, RXtIsSensitive(nxt), prev, RXtIsSensitive(prev))
        end
        click_button(nxt)
        force_event
        click_button(nxt)
        force_event
        if RXtIsSensitive(nxt) or (not RXtIsSensitive(prev))
          snd_display("mix_dialog next/previous: %s %s %s %s?",
                      nxt, RXtIsSensitive(nxt), prev, RXtIsSensitive(prev))
        end
        click_button(prev)
        force_event
        click_button(prev)
        force_event
      else
        snd_display("find_child cannot find Previous: %s?", prev.inspect)
      end
    else
      snd_display("find_child cannot find Next: %s?", nxt.inspect)
    end
    close_sound(ind)
  end
  # 
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1, "lock mix tests", 300)
  mix1 = mix_vct(Vct.new(10, 0.5), 10)
  set_mix_amp(mix1, 0.0)
  if fneq(res = maxamp(ind, 0), 0.0)
    snd_display("delete mix maxamp: %s?", res)
  end
  undo_channel(1, ind, 0)
  if fneq(res = maxamp(ind, 0), 0.5)
    snd_display("undelete mix maxamp: %s?", res)
  end
  redo_channel(1, ind, 0)
  if fneq(res = maxamp(ind, 0), 0.0)
    snd_display("redelete mix maxamp: %s?", res)
  end
  undo_edit(2)
  if number?(mix?(mix1))
    snd_display("undo 2 kept mix?")
  end
  if fneq(res = maxamp(ind, 0), 0.0)
    snd_display("no delete_mix maxamp: %s?", res)
  end
  redo_edit
  if fneq(res = maxamp(ind, 0), 0.5)
    snd_display("reundelete mix maxamp: %s?", res)
  end
  close_sound(ind)
end

def test_09_04
  #
  set_with_mix_tags(true)
  ind = open_sound("oboe.snd")
  mx = mix_vct(Vct.new(100, 0.1), 1000)
  fr = mus_sound_frames("1a.snd")
  [[lambda do pad_channel(0,     100) end, 1100, false, :pad0],
   [lambda do pad_channel(0,    2000) end, 3000, false, :pad20],
   [lambda do pad_channel(800,   100) end, 1100, false, :pad800],
   [lambda do pad_channel(850,   100) end, 1100, false, :pad800],
   [lambda do pad_channel(990,   100) end, 1100, false, :pad990],
   [lambda do pad_channel(1010,  100) end, 1000, true,  :pad1010],
   [lambda do pad_channel(1050,   10) end, 1000, true,  :pad1050],
   [lambda do pad_channel(1110,  100) end, 1000, false, :pad1110],
   [lambda do pad_channel(2000,  100) end, 1000, false, :pad2000],
   [lambda do insert_samples(0,    100, Vct.new(100, 0.2)) end, 1100, false, :insert0],
   [lambda do insert_samples(800,  100, Vct.new(100, 0.2)) end, 1100, false, :insert800],
   [lambda do insert_samples(990,  100, Vct.new(100, 0.2)) end, 1100, false, :insert990],
   [lambda do insert_samples(1010, 100, Vct.new(100, 0.2)) end, 1000, true,  :insert1010],
   [lambda do insert_samples(1050,  10, Vct.new(100, 0.2)) end, 1000, true,  :insert1050],
   [lambda do insert_samples(1110, 100, Vct.new(100, 0.2)) end, 1000, false, :insert1110],
   [lambda do insert_samples(2000, 100, Vct.new(100, 0.2)) end, 1000, false, :insert2000],
   [lambda do insert_sound("1a.snd",    0) end, fr + 1000, false, :inserts0],
   [lambda do insert_sound("1a.snd",  800) end, fr + 1000, false, :inserts800],
   [lambda do insert_sound("1a.snd",  990) end, fr + 1000, false, :inserts990],
   [lambda do insert_sound("1a.snd", 1010) end,      1000, true,  :inserts1010],
   [lambda do insert_sound("1a.snd", 1050) end,      1000, true,  :inserts1050],
   [lambda do insert_sound("1a.snd", 1110) end,      1000, false, :inserts1110],
   [lambda do insert_sound("1a.snd", 2000) end,      1000, false, :inserts2000],
   [lambda do delete_samples(0,    100) end,  900, false, :delete0],
   [lambda do delete_samples(0,   2000) end, 1000, true,  :delete20],
   [lambda do delete_samples(800,  100) end,  900, false, :delete800],
   [lambda do delete_samples(850,  100) end,  900, false, :delete850],
   [lambda do delete_samples(950,   40) end,  960, false, :delete950],
   [lambda do delete_samples(990,  100) end, 1000, true,  :delete990],
   [lambda do delete_samples(1010, 100) end, 1000, true,  :delete1010],
   [lambda do delete_samples(1050,  10) end, 1000, true,  :delete1050],
   [lambda do delete_samples(1110, 100) end, 1000, false, :delete1110],
   [lambda do delete_samples(2000, 100) end, 1000, false, :delete2000],
   [lambda do set_samples(0,    100, Vct.new(100,  0.2)) end, 1000, false, :set0],
   [lambda do set_samples(0,   2000, Vct.new(2000, 0.2)) end, 1000, true,  :set0],
   [lambda do set_samples(800,  100, Vct.new(100,  0.2)) end, 1000, false, :set800],
   [lambda do set_samples(990,  100, Vct.new(100,  0.2)) end, 1000, true,  :set990],
   [lambda do set_samples(1010, 100, Vct.new(100,  0.2)) end, 1000, true,  :set1010],
   [lambda do set_samples(1050,  10, Vct.new(100,  0.2)) end, 1000, true,  :set1050],
   [lambda do set_samples(1110, 100, Vct.new(100,  0.2)) end, 1000, false, :set1110],
   [lambda do set_samples(2000, 100, Vct.new(100,  0.2)) end, 1000, false, :set2000],
   [lambda do scale_channel(2.0, 0,    100) end, 1000, false, :scale0],
   [lambda do scale_channel(2.0, 0,   2000) end, 1000, true,  :scale20],
   [lambda do scale_channel(2.0, 800,  100) end, 1000, false, :scale800],
   [lambda do scale_channel(2.0, 850,  100) end, 1000, false, :scale850],
   [lambda do scale_channel(2.0, 950,   40) end, 1000, false, :scale950],
   [lambda do scale_channel(2.0, 990,  100) end, 1000, true,  :scale990],
   [lambda do scale_channel(2.0, 1010, 100) end, 1000, true,  :scale1010],
   [lambda do scale_channel(2.0, 1050,  10) end, 1000, true,  :scale1050],
   [lambda do scale_channel(2.0, 1110, 100) end, 1000, false, :scale1110],
   [lambda do scale_channel(2.0, 2000, 100) end, 1000, false, :scale2000],
   [lambda do env_channel([0, 0, 1, 1], 0,    100) end, 1000, false, :env0],
   [lambda do env_channel([0, 0, 1, 1], 0,   2000) end, 1000, true,  :env20],
   [lambda do env_channel([0, 0, 1, 1], 800,  100) end, 1000, false, :env800],
   [lambda do env_channel([0, 0, 1, 1], 850,  100) end, 1000, false, :env850],
   [lambda do env_channel([0, 0, 1, 1], 950,   40) end, 1000, false, :env950],
   [lambda do env_channel([0, 0, 1, 1], 990,  100) end, 1000, true,  :env990],
   [lambda do env_channel([0, 0, 1, 1], 1010, 100) end, 1000, true,  :env1010],
   [lambda do env_channel([0, 0, 1, 1], 1050,  10) end, 1000, true,  :env1050],
   [lambda do env_channel([0, 0, 1, 1], 1110, 100) end, 1000, false, :env1110],
   [lambda do env_channel([0, 0, 1, 1], 2000, 100) end, 1000, false, :env2000]
  ].each do |func, beg, lock, name|
    edpos = edit_position(ind, 0)
    func.call
    set_edit_position(edpos, ind, 0)
  end
  close_sound(ind)
  #
  ind = open_sound("4.aiff")
  selind = open_sound("oboe.snd")
  make_selection(100, 500, selind, 0)
  mix_selection(500, ind, 2)
  if (res = edit_position(ind, 0)) != 0
    snd_display("mix_selection 0->2 0: %s?", res)
  end
  if (res = edit_position(ind, 1)) != 0
    snd_display("mix_selection 0->2 1: %s?", res)
  end
  if (res = edit_position(ind, 2)) != 1
    snd_display("mix_selection 0->2 2: %s?", res)
  end
  if (res = edit_position(ind, 3)) != 0
    snd_display("mix_selection 0->2 3: %s?", res)
  end
  revert_sound(ind)
  set_sync(1234, ind)
  mix_selection(500, ind, 1)
  if (res = edit_position(ind, 0)) != 0
    snd_display("mix_selection 1->2 0: %s?", res)
  end
  if (res = edit_position(ind, 1)) != 1
    snd_display("mix_selection 1->2 1: %s?", res)
  end
  if (res = edit_position(ind, 2)) != 0
    snd_display("mix_selection 1->2 2: %s?", res)
  end
  if (res = edit_position(ind, 3)) != 0
    snd_display("mix_selection 1->2 3: %s?", res)
  end
  revert_sound(ind)
  set_sync(0, ind)
  insert_selection(500, ind, 2)
  if (res = edit_position(ind, 0)) != 0
    snd_display("insert_selection 0->2 0: %s?", res)
  end
  if (res = edit_position(ind, 1)) != 0
    snd_display("insert_selection 0->2 1: %s?", res)
  end
  if (res = edit_position(ind, 2)) != 1
    snd_display("insert_selection 0->2 2: %s?", res)
  end
  if (res = edit_position(ind, 3)) != 0
    snd_display("insert_selection 0->2 3: %s?", res)
  end
  revert_sound(ind)
  set_sync(1234, ind)
  insert_selection(500, ind, 1)
  if (res = edit_position(ind, 0)) != 0
    snd_display("insert_selection 1->2 0: %s?", res)
  end
  if (res = edit_position(ind, 1)) != 1
    snd_display("insert_selection 1->2 1: %s?", res)
  end
  if (res = edit_position(ind, 2)) != 0
    snd_display("insert_selection 1->2 2: %s?", res)
  end
  if (res = edit_position(ind, 3)) != 0
    snd_display("insert_selection 1->2 3: %s?", res)
  end
  revert_sound(ind)
  set_sync(0, ind)
  close_sound(ind)
  close_sound(selind)
end

def test_09
  if $with_test_gui
    test_09_00
    test_09_02
    test_09_03
    test_09_04
  end
end

# ---------------- test 10: marks ----------------

def data_max(beg, fin)
  maxval = 0.0
  Snd.sounds.each do |snd|
    channels(snd).times do |chn|
      scan_chan(lambda do |data|
                  maxval = [maxval, data.abs].max
                  false
                end, 0, false, snd, chn)
    end
  end
  maxval
end

def data_max2(beg, fin, snd)
  maxval = 0.0
  channels(snd).times do |chn|
    scan_chan(lambda do |data|
                maxval = [maxval, data.abs].max
                false
              end, 0, false, snd, chn)
  end
  maxval
end

def data_max1(beg, fin, snd, chn)
  maxval = 0.0
  scan_chan(lambda do |data|
              maxval = [maxval, data.abs].max
              false
            end, beg, fin, snd, chn)
  maxval
end

def test_10_00
  ind0 = new_sound("fmv.snd",  Mus_aifc, Mus_bshort, 22050, 2, "this is a comment")
  ind1 = new_sound("fmv1.snd", Mus_aifc, Mus_bshort, 22050, 1, "this is a comment")
  v0 = make_array(10, 1.0)
  set_sync(123, ind0)
  set_sync(123, ind1)
  insert_samples(0, 10, v0, ind0, 0)
  insert_samples(0, 10, v0, ind0, 1)
  insert_samples(0, 10, v0, ind1, 0)
  env_sound([0, 0, 1, 1], 0, 10, 1.0, ind0)
  10.times do |i|
    if fneq(res = sample(i, ind0, 0), i * 0.1111)
      snd_display("ind0:0 1 env_sound[%s]: %s?", i, res)
    end
    if fneq(res = sample(i, ind0, 1), i * 0.1111)
      snd_display("ind0:1 1 env_sound[%s]: %s?", i, res)
    end
    if fneq(res = sample(i, ind0, 0), i * 0.1111)
      snd_display("ind1:0 1 env_sound[%s]: %s?", i, res)
    end
  end
  undo_edit
  env_sound(make_env(:envelope, [0, 0, 1, 1], :length, 10), 0, 10, 1.0, ind0)
  10.times do |i|
    if fneq(res = sample(i, ind0, 0), i * 0.1111)
      snd_display("ind0:0 2 env_sound[%s]: %s?", i, res)
    end
    if fneq(res = sample(i, ind0, 1), i * 0.1111)
      snd_display("ind0:1 2 env_sound[%s]: %s?", i, res)
    end
    if fneq(res = sample(i, ind0, 0), i * 0.1111)
      snd_display("ind1:0 2 env_sound[%s]: %s?", i, res)
    end
  end
  undo_edit
  env_sound([0, 0, 0.5, 1, 1, 1], 0, 10, 0.0, ind0)
  if fneq(res1 = sample(3, ind0, 0), 0.0) or fneq(res2 = sample(8, ind0, 0), 1.0)
    snd_display("ind0:0 env_sound stepped: %s %s?", res1, res2)
  end
  if fneq(res1 = sample(3, ind0, 0), 0.0) or fneq(res2 = sample(8, ind0, 0), 1.0)
    snd_display("ind0:1 env_sound stepped: %s %s?", res1, res2)
  end
  if fneq(res1 = sample(3, ind0, 0), 0.0) or fneq(res2 = sample(8, ind0, 0), 1.0)
    snd_display("ind1:0 env_sound stepped: %s %s?", res1, res2)
  end
  undo_edit
  revert_sound(ind0)
  revert_sound(ind1)
  insert_samples(0, 10, v0, ind0, 0)
  insert_samples(0, 10, v0, ind0, 1)
  insert_samples(0, 10, v0, ind1, 0)
  filter_sound(make_one_zero(:a0, 0.5, :a1, 0.0), 0, ind0)
  10.times do |i|
    if fneq(res = sample(i, ind0, 0), 0.5)
      snd_display("ind0:0 1 filter_sound[%s]: %s?", i, res)
    end
    if fneq(res = sample(i, ind0, 1), 0.5)
      snd_display("ind0:1 1 filter_sound[%s]: %s?", i, res)
    end
    if fneq(res = sample(i, ind0, 0), 0.5)
      snd_display("ind1:0 1 filter_sound[%s]: %s?", i, res)
    end
  end
  close_sound(ind0)
  close_sound(ind1)
  #
  ind0 = new_sound("fmv.snd", Mus_aifc, Mus_bshort, 22050, 1, "this is a comment")
  v0 = Vct.new(10, 0.1)
  old5 = sample(5, ind0)
  insert_samples(10, 10, v0, ind0)
  env_sound([0, 0, 1, 2], 10, 10, 1.0, ind0)
  10.times do |i|
    if fneq(res = sample(i + 10, ind0), i * 0.0222)
      snd_display("env_sound[%s]: %s?", i + 10, res)
    end
  end
  if fneq(res = sample(5, ind0), old5)
    snd_display("env_sound[5]: %s %s?", old5, res)
  end
  undo_edit
  env_sound([0, 0, 1, 2], 10, 10, 4.0, ind0)
  v0 = channel2vct(10, 10)
  if fneq(v0[3], 0.039) or fneq(v0[8], 0.162)
    snd_display("env_sound 4.0: %s?", v0)
  end
  undo_edit
  env_sound([0, 0, 1, 2], 10, 10, 0.05, ind0)
  v0 = channel2vct(10, 10)
  if fneq(v0[3], 0.133) or fneq(v0[8], 0.196)
    snd_display("env_sound 0.05: %s?", v0)
  end
  close_sound(ind0)
  #
  ind0 = new_sound("fmv.snd", Mus_aifc, Mus_bshort, 22050, 2, "this is a comment")
  ind1 = new_sound("fmv1.snd", Mus_next, Mus_bshort, 22050, 1, "this is a comment")
  insert_samples(0, 10, make_array(10, 1.00), ind0, 0)
  insert_samples(0, 10, make_array(10, 0.10), ind0, 1)
  insert_samples(0, 10, make_array(10, 0.01), ind1, 0)
  if fneq(res = data_max1(0, 9, ind0, 0), 1.00)
    snd_display("scan_chan[0,0]: %s?", res)
  end
  if fneq(res = data_max1(0, 9, ind0, 1), 0.10)
    snd_display("scan_chan[0,1]: %s?", res)
  end
  if fneq(res = data_max1(0, 9, ind1, 0), 0.01)
    snd_display("scan_chan[1,0]: %s?", res)
  end
  if fneq(res = data_max1(0, 9, false, false), 0.01)
    snd_display("scan_chans: %s?", res)
  end
  if fneq(res = data_max(0, 9), 1.00)
    snd_display("scan_all_chans: %s?", res)
  end
  if fneq(res = data_max2(0, 9, ind0), 1.00)
    snd_display("scan_across_all_chans: %s?", res)
  end
  close_sound(ind0)
  close_sound(ind1)
end

def test_10_01
  ind0 = new_sound("fmv.snd", Mus_aifc, Mus_bshort, 22050, 2, "this is a comment")
  mix("oboe.snd")
  m1 = add_mark(100)
  delete_sample(10)
  m2 = add_mark(200)
  delete_sample(10)
  m3 = add_mark(300)
  undo_edit
  save_sound
  if marks(ind0, 0).length != 2
    snd_display("marks after save: %s?", marks(ind0, 0))
  end
  if (not mark?(m1)) or mark_sample(m1) != 99
    snd_display("save_sound mark1: %s?", mark_sample(m1))
  end
  if (not mark?(m2)) or mark_sample(m2) != 200
    snd_display("save_sound mark2: %s?", mark_sample(m1))
  end
  if mark?(m3)
    snd_display("save_sound mark3: %s?", m3)
  end
  close_sound(ind0)
  #
  fd = open_sound("oboe.snd")
  m1 = add_mark(123)
  sync_val = mark_sync_max + 1
  unless mark?(m1)
    snd_display("mark?")
  end
  if mark_sample(m1) != 123
    snd_display("add_mark: %s?", mark_sample(m1))
  end
  set_mark_property(:hiho, m1, 123)
  if (res = mark_property(:hiho, m1)) != 123
    snd_display("mark_property: %s?", res)
  end
  if (res = mark_property(:not_there, m1))
    snd_display("mark_not_property: %s?", res)
  end
  if (res = Snd.catch do mark_sample(integer2mark(12345678)) end).first != :no_such_mark
    snd_display("mark_sample err: %s", res.inspect)
  end
  if (res = Snd.catch do add_mark(123, 123) end).first != :no_such_sound
    snd_display("add_mark err: %s", res.inspect)
  end
  m2 = add_mark(12345, fd, 0)
  unless mark?(m2)
    snd_display("add_mark failed?: %s", m2)
  end
  if mark_sample(m2) != 12345
    snd_display("add_mark 0 0: %s?", mark_sample(m2))
  end
  if mark_sync(m2) != 0
    snd_display("init mark_sync: %s?", mark_sync(m2))
  end
  set_mark_sync(m2, sync_val)
  if (res = mark_sync(m2)) != sync_val
    snd_display("set_mark_sync (%s): %s?", sync_val, res)
  end
  syncs = syncd_marks(sync_val)
  chans = marks(fd, 0)
  samps = chans.map do |chn| mark_sample(chn) end
  if syncs != [m2]
    snd_display("syncd_marks: %s?", syncs)
  end
  if chans != [m1, m2]
    snd_display("marks: %s?", chans)
  end
  if samps != [mark_sample(m1), mark_sample(m2)]
    snd_display("map samps: %s?", samps)
  end
  delete_samples(200, 100, fd, 0)
  chans = marks(fd)
  samps = chans.first.map do |chn| mark_sample(chn) end
  if samps != [mark_sample(m1, 0), mark_sample(m2, 0) - 100]
    snd_display("map samps: %s?", samps)
  end
  unless (res = describe_mark(m2)) == [[:mark, m2, :sound, fd, "oboe.snd", :channel, 0], 12345, 12245]
    snd_display("describe_mark: %s?", res)
  end
  set_mark_sync(m1, mark_sync(m2))
  move_syncd_marks(sync_val, 100)
  chans = marks(fd)
  samps = chans.first.map do |chn| mark_sample(chn) end
  if samps != [mark_sample(m1, 0) + 100, mark_sample(m2, 0)]
    snd_display("syncd move samps: %s?", samps)
  end
  set_cursor(500)
  set_mark_sync(m1, true)
  if mark_sync(m1) != 1
    snd_display("mark_sync via bool: %s?", mark_sync(m1))
  end
  delete_mark(m1)
  chans = marks(fd, 0)
  if chans != [m2]
    snd_display("delete_mark: %s?", chans)
  end
  undo_edit
  chans = marks(fd, 0)
  if chans != [m1, m2]
    snd_display("delete_mark then undo: %s?", chans)
  end
  redo_edit
  if mark_name(m2) != ""
    snd_display("init mark_name: %s?", mark_name(m2))
  end
  set_mark_name(m2, "hiho!")
  if mark_name(m2) != "hiho!"
    snd_display("set_mark_name: %s?", mark_name(m2))
  end
  undo_edit
  if mark_name(m2) != ""
    snd_display("undo mark_name: %s?", mark_name(m2))
  end
  redo_edit
  if mark_name(m2) != "hiho!"
    snd_display("redo mark_name: %s?", mark_name(m2))
  end
  #
  m3 = find_mark("hiho!")
  m4 = find_mark(mark_sample(m2))
  m5 = find_mark("not-a-mark")
  m6 = find_mark(123456787)
  m7 = mark_name2id("hiho!")
  if (not m2 == m3) or (not m4 == m7) or (not m2 == m4)
    snd_display("find_mark: %s %s %s %s?", m2, m3, m4, m7)
  end
  if (not m5 == m6) or m5 != false
    snd_display("find-not-a-mark: %s %s?", m5, m6)
  end
  set_mark_sample(m2, 2000)
  m1 = add_mark(1000)
  m3 = add_mark(3000)
  m4 = add_mark(4000)
  insert_samples(2500, 500, Vct.new(500), fd, 0)
  samps = (marks(fd, 0) or []).map do |m| mark_sample(m) end
  if samps != [1000, 2000, 3500, 4500]
    snd_display("insert ripple: %s?", samps)
  end
  set_mark_sample(m3, 300)
  set_cursor(500)
  sd = open_sound("4.aiff")
  m3 = add_mark(1000, sd, 2)
  m4 = add_mark(1000, sd, 3)
  unless mark_home(m3) == [sd, 2]
    snd_display("mark->sound 4: %s?", mark_home(m3))
  end
  close_sound(sd)
  file = save_marks(fd)
  if file != Dir.pwd + "/oboe.marks"
    snd_display("save_marks: %s?", file)
  end
  file = save_marks(fd, "hiho.marks")
  if file != "hiho.marks"
    snd_display("save_marks with arg: %s?", file)
  end
  unless shell("diff hiho.marks %s/oboe.marks", Dir.pwd)
    snd_display("save marks differs")
  end
  close_sound(fd)
  #
  s1 = open_sound("oboe.snd")
  s2 = open_sound("oboe.snd")
  add_mark(123, s1, 0)
  add_mark(321, s2, 0)
  set_with_verbose_cursor(true)
  delete_file("s61.rb")
  Snd.catch do save_state("s61.rb") end
  set_with_verbose_cursor(false)
  close_sound(s1)
  close_sound(s2)
  load("s61.rb")
  unless with_verbose_cursor
    snd_display("save_state with_verbose_cursor?")
  end
  s1 = find_sound("oboe.snd", 0)
  s2 = find_sound("oboe.snd", 1)
  if (not sound?(s1)) or (not sound?(s2))
    snd_display("cannot re-open sounds: %s %s?", s1, s2)
  else
    m1 = marks(s1)
    m2 = marks(s2)
    if m1.length != 1 or m2.length != 1 or m1.first.length != 1 or m2.first.length != 1
      snd_display("save_marks via save_state to: %s %s?", m1, m2)
    end
    samp1 = mark_sample(m1[0][0])
    samp2 = mark_sample(m2[0][0])
    if samp1 != 123 or samp2 != 321
      snd_display("save_marks via save_state positions: %s %s?", samp1, samp2)
    end
  end
  sound?(s1) and close_sound(s1)
  sound?(s2) and close_sound(s2)
  fd = open_sound("pistol.snd")
  if file = save_marks
    snd_display("save_marks no marks: %s?", file)
  end
  close_sound(fd)
  fd = open_sound("oboe.snd")
  load("oboe.marks")
  mlst = marks(fd, 0)
  if mlst.length != 4
    snd_display("restore oboe.marks: %s?", mlst.inspect)
  end
  close_sound(fd)
  fd = open_sound("oboe.snd")
  add_mark(1000)
  add_mark(2500)
  add_mark(frames - 4000)
  marks(fd, 0)
  src_sound(-0.5)
  unless (res1 = marks(fd, 0)) == (res2 = marks(fd, 0, 0).reverse)
    snd_display("src rev marks: %s %s?", res1.inspect, res2.inspect)
  end
  unless (res = (marks(fd, 0) or []).map do |m| mark_sample(m) end) == [7998, 96654, 99654]
    snd_display("src rev mark locs: %s?", res)
  end
  close_sound(fd)
  fd = open_sound("4.aiff")
  add_mark(1000, fd, 0)
  add_mark(2000, fd, 1)
  add_mark(3000, fd, 2)
  add_mark(4000, fd, 3)
  if marks.length.zero?
    snd_display("marks (no args): %s?", marks)
  end
  save_marks(fd)
  close_sound(fd)
  fd = open_sound("4.aiff")
  load("4.marks")
  delete_file("4.marks")
  (marks(fd) or []).each_with_index do |mlst, i|
    if mlst.length != 1
      snd_display("save_marks[%s]: %s?", i, mlst)
    end
    if (res = mark_sample(mlst.first)) != (i + 1) * 1000
      snd_display("save_marks[%s] at %s?", i, res)
    end
  end
  close_sound(fd)
end

def test_10_02
  ind = open_sound("pistol.snd")
  samp1 = 1834
  samp2 = 8345
  m1 = add_mark(samp1, ind, 0)
  m2 = add_mark(samp2)
  set_mark_sync(m1, 123)
  set_mark_sync(m2, 100)
  if (res = mark_sync_max()) != 123
    snd_display("mark_sync_max: %s?", res)
  end
  src_sound(-1.0)
  if mark_sample(m1) != 39788
    snd_display("src -1 m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != 33277
    snd_display("src -1 m2: %s?", mark_sample(m2))
  end
  undo_edit
  src_sound(0.5)
  if mark_sample(m1) != 2 * samp1
    snd_display("src 0.5 m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != 2 * samp2
    snd_display("src 0.5 m2: %s?", mark_sample(m2))
  end
  undo_edit
  delete_samples(1000, 100)
  if mark_sample(m1) != samp1 - 100
    snd_display("delete_samples 100 m1: %s?", mark_sample(m1))
  end
  insert_silence(1000, 100)
  if mark_sample(m1) != samp1
    snd_display("insert_silence 100 m1: %s?", mark_sample(m1))
  end
  revert_sound(ind)
  delete_samples(2000, 100)
  if mark_sample(m1) != samp1
    snd_display("delete_samples (2) 100 m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != samp2 - 100
    snd_display("delete_samples (2) 100 m2: %s?", mark_sample(m2))
  end
  insert_silence(2000, 100)
  if mark_sample(m1) != samp1
    snd_display("insert_silence (2) 100 m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != samp2
    snd_display("insert_silence (2) 100 m2: %s?", mark_sample(m2))
  end
  revert_sound(ind)
  delete_samples(10000, 100)
  if mark_sample(m1) != samp1
    snd_display("delete_samples (3) 100 m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != samp2
    snd_display("delete_samples (3) 100 m2: %s?", mark_sample(m2))
  end
  insert_silence(10000, 100)
  if mark_sample(m1) != samp1
    snd_display("insert_silence (3) 100 m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != samp2
    snd_display("insert_silence (3) 100 m2: %s?", mark_sample(m2))
  end
  src_sound([0, 0.5, 1, 0.5, 2, 1])
  if mark_sample(m1) != 2 * samp1
    snd_display("src env 0.5 m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != 2 * samp2
    snd_display("src env 0.5 m2: %s?", mark_sample(m2))
  end
  undo_edit
  reverse_sound
  if mark_sample(m1) != 39788
    snd_display("reverse_sound m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != 33277
    snd_display("reverse_sound m2: %s?", mark_sample(m2))
  end
  undo_edit
  src_sound([0, -0.5, 1, -0.5, 2, -1])
  if mark_sample(m1) != 68598
    snd_display("src -env m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != 61160
    snd_display("src -env m2: %s?", mark_sample(m2))
  end
  revert_sound(ind)
  # 
  src_channel(make_env(:envelope, [0, 0.5, 1, 1], :length, 8001), 2000, 10000)
  if mark_sample(m1) != samp1
    snd_display("src_channel (1) m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != 11345
    snd_display("src_channel (1) m2: %s?", mark_sample(m2))
  end
  undo_edit
  src_channel(make_env(:envelope, [0, 0.5, 1, 1], :length, 8001), 0, 8000)
  if mark_sample(m1) != 3303
    snd_display("src_channel (2) m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != samp2
    snd_display("src_channel (2) m2: %s?", mark_sample(m2))
  end
  undo_edit
  src_channel(make_env(:envelope, [0, 0.5, 1, 1], :length, 8001), 10000, 8000)
  if mark_sample(m1) != samp1
    snd_display("src_channel (3) m1: %s?", mark_sample(m1))
  end
  if mark_sample(m2) != samp2
    snd_display("src_channel (3) m2: %s?", mark_sample(m2))
  end
  close_sound(ind)
  #
  ind = open_sound("2.snd")
  set_sync(true, ind)
  m3 = add_mark(1000, ind, 0)
  m4 = add_mark(8000, ind, 1)
  swap_channels
  if (not (res1 = mark_home(m3)) == [ind, 1]) or (not (res2 = mark_home(m4)) == [ind, 0])
    snd_display("swapped mark homes: %s %s?", res1, res2)
  end
  if (res1 = mark_sample(m3)) != 1000 or (res2 = mark_sample(m4)) != 8000
    snd_display("swapped mark samples: %s %s?", res1, res2)
  end
  close_sound(ind)
  #
  ind = open_sound("2.snd")
  set_sync(true, ind)
  m3 = add_mark(1000, ind, 0)
  delete_samples(1000, 10, ind, 1)
  swap_channels
  unless (res = mark_home(m3)) == [ind, 1]
    snd_display("edited swapped mark home: %s?", res)
  end
  if (res = mark_sample(m3)) != 1000
    snd_display("edited swapped mark sample: %s?", res)
  end
  delete_marks
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  m1 = add_mark(123, ind, 0) 
  m2 = add_mark(234, ind, 0)
  define_selection_via_marks(m1, m2)
  if (not selection?)
    snd_display("define_selection_via_marks failed?")
  else
    mc = selection_members
    unless mc == [[ind, 0]]
      snd_display("selection_members after mark def: %s [[%s, 0]]", mc, ind)
    end
    if selection_position != 123
      snd_display("selection_position 123: %s?", selection_position)
    end
    if selection_frames != 112
      snd_display("selection_frames 112: %s?", selection_frames)
    end
  end
  m1 = add_mark(1000, ind, 0) 
  m2 = add_mark(2000, ind, 0)
  define_selection_via_marks(m1, m2)
  if (not selection?)
    snd_display("define_selection_via_marks repeat failed?")
  else
    mc = selection_members
    unless mc == [[ind, 0]]
      snd_display("selection_members after 2nd mark def: %s [[%s, 0]]", mc, ind)
    end
    if selection_position != 1000
      snd_display("selection_position 1000: %s?", selection_position)
    end
    if selection_frames != 1001
      snd_display("selection_frames 1001: %s?", selection_frames)
    end
  end
  set_selection_member?(false, true)
  if selection?
    snd_display("cannot clear selection via selection_members?")
  end
  set_selection_member?(true, ind, 0)
  set_selection_position(2000, ind, 0)
  set_selection_frames(1234, ind, 0)
  snap_marks
  unless mark?(m1 = find_mark(2000, ind, 0))
    snd_display("snap_marks start: %s?", Snd.marks(ind, 0).map do |m| mark_sample(m) end)
  end
  unless mark?(m2 = find_mark(2000 + 1234, ind, 0))
    snd_display("snap_marks end: %s?", Snd.marks(ind, 0).map do |m| mark_sample(m) end)
  end
  set_selection_position(frames(ind, 0) + 1234, ind, 0)
  if (res = selection_position(ind, 0)) != frames(ind) - 1
    snd_display("selection_position past eof: %s %s?", res, frames(ind) - 1)
  end
  revert_sound(ind)
  src_sound([0, 0.5, 1, 1.75665])
  # trying to hit previous dur on the nose "by accident..."
  # try to hit mark_size segfault
  as_one_edit(lambda do | |
                add_mark(10)
                mix("oboe.snd")
                20.times do |i|
                  scale_channel(1.2)
                  add_mark(i * 2)
                end
              end)
  scale_channel(0.5)
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  100.times do |i|
    current_marks = Snd.marks(ind, 0)
    current_samples = []
    if current_marks and (not current_marks.empty?)
      current_samples = current_marks.map do |m| mark_sample(m) end
      id = current_marks[random(current_marks.length - 1)]
      unless find_mark(mark_sample(id)).eql?(id)
        snd_display("two marks at %s: %s?",
                    mark_sample(id), current_marks.map do |m| mark_sample(m) end)
      end
      if res = find_mark("not-a-mark")
        snd_display("find-bogus-mark: %s?", res)
      end
    end
    case random(15)
    when 0
      beg = random(frames)
      dur = [1, random(100)].max
      insert_silence(beg, dur)
      if current_marks and (not current_marks.empty?)
        current_marks.zip(current_samples) do |id, old_loc|
          if old_loc > beg
            if mark?(id)
              if (res = mark_sample(id)) != old_loc + dur
                snd_display("insert, mark %s %s -> %s (%s)?", id, old_loc, res, dur)
              end
            else
              snd_display("insert clobbered mark: %s?", id)
            end
          end
        end
      end
    when 1
      if edits(ind, 0)[0] > 0
        undo_edit
      end
    when 2
      if edits(ind, 0)[1] > 0
        redo_edit
      end
    when 3
      scale_channel((maxamp(ind, 0) > 0.1) ? 0.5 : 2.0)
      unless (res = Snd.marks(ind, 0)) == current_marks
        snd_display("scaling changed marks: %s %s?", res, current_marks)
      end
      unless (res = Snd.marks(ind, 0).map do |m| mark_sample(m) end) == current_samples
        snd_display("scaling changed mark locations: %s %s?", res, current_samples)
      end
    when 4
      set_sample(random(frames - 1), 0.5)
      unless (res = Snd.marks(ind, 0)) == current_marks
        snd_display("set_sample changed marks: %s %s?", res, current_marks)
      end
      unless (res = Snd.marks(ind, 0).map do |m| mark_sample(m) end) == current_samples
        snd_display("set_sample changed mark location: %s %s?", res, current_samples)
      end
    when 5
      beg = random(frames)
      dur = [1, random(100)].max
      len = beg + dur
      delete_samples(beg, dur)
      if current_marks and (not current_marks.empty?)
        current_marks.zip(current_samples) do |id, old_loc|
          if old_loc > beg and old_loc < len and mark?(id)
            snd_display("delete did not clobber mark: %s %s [%s, %s]?", id, old_loc, beg, dur)
          else
            if old_loc > len and (res = mark_sample(id)) != old_loc - dur
              snd_display("delete ripple mark, mark %s %s -> %s (%s)?", id, old_loc, res, dur)
            else
              if old_loc < beg and (res = mark_sample(id)) != old_loc
                snd_display("delete but mark before: %s %s %s %s?", id, old_loc, res, dur)
              end
            end
          end
        end
      end
    when 6
      revert_sound
    when 7
      if current_marks and current_marks.length > 1
        id = current_marks[random(current_marks.length - 1)]
        delete_mark(id)
        if mark?(id)
          snd_display("delete_mark failed: %s?", id)
        end
        if (res = marks(ind, 0)).length != current_marks.length - 1
          snd_display("delete_mark list trouble: %s %s %s?", id, current_marks, res)
        end
      end
    when 8
      rate = (frames > 200000) ? 2.0 : 0.5
      src_channel(rate)
      if current_marks and (not current_marks.empty?)
        current_marks.zip(current_samples) do |id, old_loc|
          unless mark?(id)
            snd_display("src_channel clobbered mark: %s?", id)
          else
            if ((old_loc / rate) - (res = mark_sample(id))).abs > 2
              snd_display("src_channel moved mark: %s?", id)
            end
          end
        end
      end
    when 9
      reverse_channel
      if current_marks and (not current_marks.empty?)
        current_marks.zip(current_samples) do |id, old_loc|
          unless mark?(id)
            snd_display("reverse_channel clobbered mark: %s?", id)
          else
            if ((frames - old_loc) - (res = mark_sample(id))).abs > 2
              snd_display("reverse_channel moved mark: %s %s %s (%s)?",
                          id, old_loc, frames - old_loc, res)
            end
          end
        end
      end
    else
      add_mark(random(frames - 1))
    end
  end
  close_sound(ind)
  #
  defined?(mark_sync_color) and $with_test_motif and mark_sync_color("blue")
  ind = open_sound("oboe.snd")
  m0 = add_mark(4321)
  delete_sample(100)
  m1 = add_mark(1234)
  val0 = describe_mark(m0)
  val1 = describe_mark(m1)
  if mark2integer(val0[0][1]) != mark2integer(m0) or
      val0[0][3] != ind or
      val0[0][6] != 0 or
      val0[1] != 4321 or
      val0[2] != 4320
    snd_display("describe_mark m0: %s?", val0)
  end
  if mark2integer(val1[0][1]) != mark2integer(m1) or
      val1[0][3] != ind or
      val1[0][6] != 0 or
      val1[1] != false or
      val1[2] != 1234
    snd_display("describe_mark m1: %s?", val1)
  end
  delete_mark(m0)
  delete_sample(5000)
  val0 = describe_mark(m0)
  val1 = describe_mark(m1)
  if mark2integer(val0[0][1]) != mark2integer(m0) or
      val0[0][3] != ind or
      val0[0][6] != 0 or
      val0[1] != 4321 or
      val0[2] != false or
      val0[3] != false
    snd_display("describe_mark m0 (1): %s?", val0)
  end
  if mark2integer(val1[0][1]) != mark2integer(m1) or
      val1[0][3] != ind or
      val1[0][6] != 0 or
      val1[1] != false or
      val1[2] != 1234 or
      val1[3] != 1234
    snd_display("describe_mark m1 (1): %s?", val1)
  end
  revert_sound(ind)
  $draw_mark_hook.add_hook!("snd-test") do |id| true end
  m0 = add_mark(4321)
  m1 = add_mark(1234)
  dur = frames(ind) / srate(ind).to_f
  pad_marks([m0, m1], 0.01)
  if fneq(res = frames(ind) / srate(ind).to_f, dur + 0.02)
    snd_display("pad_marks: %s %s?", dur, res)
  end
  if mark_sample(m0) != 4763 and mark_sample(m0) != 4761
    snd_display("pad_marks m0 pos: %s", mark_sample(m0))
  end
  if fneq(res = sample(1235), 0.0)
    snd_display("pad_marks 1235: %s?", res)
  end
  close_sound(ind)
  $draw_mark_hook.reset_hook!
  # 
  ind = open_sound("oboe.snd")
  if res = find_mark(12345)
    snd_display("find_mark when no marks: %s?", res)
  end
  add_mark(123, ind, 0)
  delete_sample(0)
  m1 = add_mark(23, ind, 0)
  set_mark_name(m1, "23")
  delete_sample(0)
  unless find_mark(123, ind, 0, 0)
    snd_display("cannot find 00th mark")
  end
  unless find_mark("23")
    snd_display("cannot find 01th mark")
  end
  unless find_mark(121)
    snd_display("cannot find 02th mark")
  end
  delete_mark(find_mark("23"))
  scale_by(2.0)
  m1 = add_mark(1234)
  set_mark_name(m1, "23")
  if (m10 = find_mark("23"))
    if (res = mark_sample(m10)) != 1234
      snd_display("mark 10th: %s?", res)
    end
  else
    snd_display("cannot find 10th mark?")
  end
  if (m11 = find_mark("23", ind, 0, 1))
    if (res = mark_sample(m11, 1)) != 23
      snd_display("mark 11th: %s?", res)
    end
  else
    snd_display("cannot find 11th mark?")
  end
  if (m12 = find_mark("23", ind, 0, 2))
    snd_display("found 12th mark: %s %s %s?", m12, mark_sample(m12, 2), mark_name(m12))
  end
  set_mark_name(m1, false)
  close_sound(ind)
  # 
  ind = open_sound("oboe.snd")
  add_mark(123)
  add_mark(234, ind, 0, "hiho", 1)
  add_mark(345, ind, 0, false, 1)
  add_mark(456, ind, 0, "a mark", 2)
  add_mark(567, ind, 0, false, 1)
  save_marks(ind, "oboe.marks")
  close_sound(ind)
  ind = open_sound("oboe.snd")
  add_mark(1, ind, 0, "new mark", 1)
  load("oboe.marks")
  if mark?(m = find_mark(123, ind, 0))
    if mark_name(m).length.nonzero?
      snd_display("saved mark 123 name: %s?", mark_name(m))
    end
    if mark_sync(m).nonzero?
      snd_display("saved mark 123 sync: %s?", mark_sync(m))
    end
  else
    snd_display("saved marks missed 123: %s?", m.inspect)
  end
  m1_sync = 0
  if mark?(m = find_mark(234, ind, 0))
    if mark_name(m) != "hiho"
      snd_display("saved mark 234 name: %?", mark_name(m))
    end
    if mark_sync(m) == 0 or mark_sync(m) == 1
      snd_display("saved mark 234 sync: %s?", mark_sync(m))
    end
    m1_sync = mark_sync(m)
  else
    snd_display("saved marks missed 234: %s?", m.inspect)
  end
  if mark?(m = find_mark(345, ind, 0))
    if mark_name(m).length.nonzero?
      snd_display("saved mark 345 name: %?", mark_name(m))
    end
    if mark_sync(m) != m1_sync
      snd_display("saved mark 345 sync: %s %s?", mark_sync(m), m1_sync)
    end
  else
    snd_display("saved marks missed 345: %s?", m.inspect)
  end
  if mark?(m = find_mark(456, ind, 0))
    if mark_name(m) != "a mark"
      snd_display("saved mark 456 name: %?", mark_name(m))
    end
    if mark_sync(m) == m1_sync or
        mark_sync(m) == 0 or
        mark_sync(m) == 1
      snd_display("saved mark 456 sync: %s %s?", mark_sync(m), m1_sync)
    end
  else
    snd_display("saved marks missed 456: %s?", m.inspect)
  end
  if mark?(m = find_mark(567, ind, 0))
    if mark_name(m).length.nonzero?
      snd_display("saved mark 567 name: %?", mark_name(m))
    end
    if mark_sync(m) != m1_sync
      snd_display("saved mark 567 sync: %s %s?", mark_sync(m), m1_sync)
    end
  else
    snd_display("saved marks missed 567: %s?", m.inspect)
  end
  delete_file("oboe.marks")
  #
  ind1 = open_sound("2a.snd")
  add_mark(1, ind1, 0)
  add_mark(2, ind1, 1)
  add_mark(3, ind1, 0, "hi3")
  add_mark(6, ind1, 1, "hi6")
  add_mark(4, ind1, 0, false, 4)
  add_mark(8, ind1, 1, false, 5)
  add_mark(5, ind1, 0, false, 9)
  add_mark(10, ind1, 1, false, 9)
  add_mark(20, ind1, 0, false, 12)
  add_mark(40, ind1, 1, false, 12)
  add_mark(60, ind1, 1, false, 12)
  save_marks(ind1, "test.marks")
  close_sound(ind)
  close_sound(ind1)
  #
  ind = open_sound("2a.snd")
  load("test.marks")
  m1 = find_mark(1, ind, 0)
  m2 = find_mark(2, ind, 1)
  if mark?(m1) and mark?(m2)
    if mark_sync(m1).nonzero? or mark_sync(m2).nonzero?
      snd_display("save_marks 2a 1, 2 syncs: %s %s?", mark_sync(m1), mark_sync(m2))
    end
  else
    snd_display("save_marks 2a 1, 2: %s %s?", m1.inspect, m2.inspect)
  end
  m1 = find_mark(5, ind, 0)
  m2 = find_mark(10, ind, 1)
  if mark?(m1) and mark?(m2)
    if mark_sync(m1).zero? or mark_sync(m1) != mark_sync(m2)
      snd_display("save_marks 2a 5, 10 syncs: %s %s?", mark_sync(m1), mark_sync(m2))
    end
  else
    snd_display("save_marks 2a 5, 10: %s %s?", m1.inspect, m2.inspect)
  end
  m1 = find_mark(4, ind, 0)
  m2 = find_mark(8, ind, 1)
  m3 = find_mark(5, ind, 0)
  if mark?(m1) and mark?(m2)
    if mark_sync(m1).zero? or 
        mark_sync(m2).zero? or 
        mark_sync(m1) == mark_sync(m2) or
        mark_sync(m1) == mark_sync(m3)
      snd_display("save_marks 2a 4, 8 syncs: %s %s?", mark_sync(m1), mark_sync(m2), mark_sync(m3))
    end
  else
    snd_display("save_marks 2a 4, 8: %s %s?", m1.inspect, m2.inspect)
  end
  m1 = find_mark(3, ind, 0)
  m2 = find_mark(6, ind, 1)
  if mark?(m1) and mark?(m2)
    if mark_sync(m1).nonzero? or mark_sync(m2).nonzero?
      snd_display("save_marks 2a 3, 6 syncs: %s %s?", mark_sync(m1), mark_sync(m2))
    end
    if mark_name(m1) != "hi3"
      snd_display("save_marks 2a 3 name: %s?", mark_name(m1))
    end
    if mark_name(m2) != "hi6"
      snd_display("save_marks 2a 6 name: %s?", mark_name(m1))
    end
  else
    snd_display("save_marks 2a 3, 6: %s %s?", m1.inspect, m2.inspect)
  end
  m1 = find_mark(4, ind, 0)
  m2 = find_mark(5, ind, 0)
  m3 = find_mark(20, ind, 0)
  m4 = find_mark(40, ind, 1)
  m5 = find_mark(60, ind, 1)
  if mark?(m3) and mark?(m4) and mark?(m5)
    if mark_sync(m3).zero? or 
        (m1 and mark_sync(m1) == mark_sync(m3)) or
        (m2 and mark_sync(m2) == mark_sync(m3)) or
        mark_sync(m3) != mark_sync(m4) or
        mark_sync(m3) != mark_sync(m5)
      snd_display("save_marks 2a 20... syncs: %s %s %s?",mark_sync(m3),mark_sync(m4), mark_sync(m5))
    end
  else
    snd_display("save_marks 2a 20...: %s %s %s?", m3.inspect, m4.inspect, m5.inspect)
  end
  delete_file("test.marks")
  close_sound(ind)
  #
  ind = new_sound(:size, 1000)
  add_mark(123)
  add_mark(234, ind, 0, "hiho", 1)
  add_mark(345, ind, 0, false, 1)
  add_mark(456, ind, 0, "a mark", 2)
  add_mark(567, ind, 0, false, 1)
  $output_comment_hook.add_hook!("mark2string") do |str| marks2string(selected_sound) end
  save_sound_as("tst.snd")
  new_file_name = file_name(ind)
  close_sound(ind)
  delete_file(new_file_name)
  ind = open_sound("tst.snd")
  $output_comment_hook.reset_hook!
  if string?(str = comment(ind))
    Snd.catch do eval(str, TOPLEVEL_BINDING, "(eval-header)", 1) end
  end
  if (ms = marks(ind, 0)).nil?
    ms = []
  end
  if ms.length != 5
    snd_display("eval_header + marks2string: %s?", ms.inspect)
  end
  samps = ms.apply(:mark_sample)
  if (not samps.member?(123)) or (not samps.member?(567))
    snd_display("eval marked header samps: %s?", samps.inspect)
  end
  unless find_mark(234)
    snd_display("eval mark header no mark at 234?")
  end
  if mr = find_mark(456)
    if (res = mark_sync(mr)) != 2
      snd_display("eval mark header sync: %s?", res)
    end
  else
    snd_display("saved marks missed 456: %s?", mr)
  end
  close_sound(ind)
  mus_sound_forget("tst.snd")
  delete_file("tst.snd")
  # mark_explode
  ind = new_sound(:size, 31)
  ctr = -1
  map_channel(lambda do |y|
                ctr += 1
                if ctr < 10
                  0.1
                elsif ctr < 20
                  0.4
                else
                  0.8
                end
              end)
  add_mark(10)
  add_mark(20)
  add_mark(30)
  mark_explode
  if File.exists?("mark-0.snd")
    ind1 = open_sound("mark-0.snd")
    if frames(ind1, 0) != 10
      snd_display("mark-0 frames: %s?", frames(ind1, 0))
    end
    unless vequal(res = channel2vct, Vct.new(10, 0.1))
      snd_display("mark-0 vals: %s?", res)
    end
    close_sound(ind1)
    delete_file("mark-0.snd")
  else
    snd_display("mark_explode did not write mark-0.snd?")
  end
  if File.exists?("mark-1.snd")
    ind1 = open_sound("mark-1.snd")
    if frames(ind1, 0) != 10
      snd_display("mark-1 frames: %s?", frames(ind1, 0))
    end
    unless vequal(res = channel2vct, Vct.new(10, 0.4))
      snd_display("mark-1 vals: %s?", res)
    end
    close_sound(ind1)
    delete_file("mark-1.snd")
  else
    snd_display("mark_explode did not write mark-1.snd?")
  end
  if File.exists?("mark-2.snd")
    ind1 = open_sound("mark-2.snd")
    if frames(ind1, 0) != 10
      snd_display("mark-2 frames: %s?", frames(ind1, 0))
    end
    unless vequal(res = channel2vct, Vct.new(10, 0.8))
      snd_display("mark-2 vals: %s?", res)
    end
    close_sound(ind1)
    delete_file("mark-2.snd")
  else
    snd_display("mark_explode did not write mark-2.snd?")
  end
  if File.exists?("mark-3.snd")
    snd_display("mark_explode wrote too many files?")
    delete_file("mark-3.snd")
  end
  name = file_name(ind)
  close_sound(ind)
  delete_file(name)
end

def test_10
  clear_sincs
  test_10_00
  test_10_01 if $with_test_gui # load("s61.rb") -> set_transform_size(0)
  test_10_02
end

# ---------------- test 11: dialogs ----------------

def string_equal_ignore_white_space(s1, s2)
  s1 == s2 or s1.delete(":()-_, \n") == s2.delete(":()-_, \n")
end

define_envelope("env4", [0, 1, 1, 0]) # defines $env4

def test_11
  if $with_test_gui
    Snd.catch do peaks() end
    enved_dialog
    color_orientation_dialog
    transform_dialog
    view_files_dialog
    view_regions_dialog
    Snd.catch do edit_header_dialog() end
    open_file_dialog(false)
    mix_file_dialog(false)
    insert_file_dialog(false)
    help_dialog("Test", "snd-test here")
    save_envelopes("hiho.env")
    load("hiho.env")
    if $env4 != [0.0, 1.0, 1.0, 0.0]
      snd_display("save_envelopes: %s?", $env4)
    end
    delete_file("hiho.env")
    help_dialog("test2", "this is the next test",
                ["string 1{open-sound}", "{env-sound}string2", "string{close-sound}3"],
                ["extsnd.html#sndopen", "extsnd.html#sndenv", "extsnd.html#sndclose"])
    dismiss_all_dialogs
    # 
    ind = open_sound("oboe.snd")
    edit_header_dialog(ind)
    dismiss_all_dialogs
    close_sound(ind)
    # 
    if (res = snd_url(:open_sound)) != "extsnd.html#opensound"
      snd_display("snd_url :open_sound: %s?", res.inspect)
    end
    if (res = snd_url("open_sound")) != "extsnd.html#opensound"
      snd_display("snd_url \"open_sound\": %s?", res.inspect)
    end
    unless array?(res = snd_urls)
      snd_display("snd_urls: %s?", res.inspect)
    end
    str2 = snd_help(:open_sound)
    str3 = snd_help("open_sound")
    unless string_equal_ignore_white_space(str2, str3)
      snd_display("snd_help open_sound: %s %s?", str2, str3)
    end
    str1 = "(enved-base): envelope editor exponential base value (1.0)"
    str2 = snd_help(:enved_base)
    str3 = snd_help("enved_base")
    unless string_equal_ignore_white_space(str1, str2)
      snd_display("snd_help :enved_base: expected %s, got %s", str1, str2)
    end
    unless string_equal_ignore_white_space(str1, str3)
      snd_display("snd_help \"enved_base\": expected %s, got %s", str1, str3)
    end
    old_val = Hamming_window
    str1 = snd_help(:Hamming_window)
    str2 = snd_help("Hamming_window")
    if (not string_equal_ignore_white_space(str1, str2)) or
        (not string_equal_ignore_white_space(str1, "A raised cosine"))
      snd_display("snd_help Hamming_window: %s %s?", str1, str2)
    end
    if (not number?(Hamming_window)) or Hamming_window != old_val
      snd_display("snd_help clobbered out-of-module variable: %s %s?", old_val, Hamming_window)
    end
    #
    urls = snd_urls[0...25]
    urls.each do |fnc, url|
      if function?(fnc)
        snd_help(fnc, false)
      end
    end
    urls.each do |fnc, url|
      if function?(fnc)
        help_dialog(fnc, snd_help(fnc, false))
      end
    end
    # 
    set_show_indices(true)
    ind = open_sound("oboe.snd")
    if sound_widgets(ind).length < 4
      snd_display("sound_widgets: %s?", sound_widgets(ind))
    end
    report_in_minibuffer("hi there", ind)
    if (res = widget_text(sound_widgets(ind)[3])) != "hi there"
      snd_display("report_in_minibuffer: %s?", res)
    end
    if (res = widget_text(sound_widgets(ind)[3])) != "hi there"
      snd_display("report_in_minibuffer 1: %s?", res)
    end
    if (res = widget_text(main_widgets[1]))
      snd_display("widget text should be false: %s?", res)
    end
    if (not (res1 = widget_text(sound_widgets(ind)[1]))) or
        res1 != (res2 = format("%s: %s", sound2integer(ind), short_file_name(ind)))
      snd_display("name text: %s %s?", res1, res2)
    end
    clear_minibuffer
    close_sound(ind)
    set_show_indices(false)
    #
    define_envelope("test_ramp", [0, 0, 1, 1])
    if $test_ramp != [0, 0, 1, 1]
      snd_display("define_envelope $test_ramp: %s?", $test_ramp)
    end
    define_envelope("test_ramp", [0, 1, 1, 0])
    if $test_ramp != [0, 1, 1, 0]
      snd_display("re-define-envelope $test_ramp: %s?", $test_ramp)
    end
    #
    dialog  = view_files_dialog(false)
    vfamp   = view_files_amp(dialog)
    vfs     = view_files_speed(dialog)
    vfsort  = view_files_sort
    vfsort1 = view_files_sort(dialog)
    vfe     = view_files_amp_env(dialog)
    vffiles = view_files_files(dialog)
    vfsel   = view_files_selected_files(dialog)
    selected_file = false
    if fneq(vfamp, 1.0)
      snd_display("vf amp: %s", vfamp)
    end
    if fneq(vfs, 1.0)
      snd_display("vf speed: %s", vfs)
    end
    if vfsort != 0
      snd_display("vf sort: %s?", vfsort)
    end
    if vfsort1 != 0
      snd_display("vf sort(d): %s?", vfsort1)
    end
    if vfe != [0.0, 1.0, 1.0, 1.0]
      snd_display("vf amp env: %s", vfe)
    end
    unless array?(vffiles) or vffiles.nil?
      snd_display("vf selected files: %s", vffiles.inspect)
    end
    unless array?(vfsel) or vfsel.nil?
      snd_display("vf selected files: %s", vfsel.inspect)
    end
    if (res1 = view_files_speed_style(dialog)) != (res2 = speed_control_style)
      snd_display("vf speed_style def: %s %s", res1, res2)
    end
    old_val = view_files_amp(dialog)
    set_view_files_amp(dialog, 0.5)
    if fneq(res = view_files_amp(dialog), 0.5)
      snd_display("set vf amp: %s", res)
    end
    set_view_files_amp(dialog, old_val)
    old_val = view_files_speed(dialog)
    set_view_files_speed(dialog, 0.5)
    if fneq(res = view_files_speed(dialog), 0.5)
      snd_display("set vf speed: %s", res)
    end
    set_view_files_speed(dialog, old_val)
    old_val = view_files_speed_style(dialog)
    set_view_files_speed_style(dialog, Speed_control_as_ratio)
    if (res = view_files_speed_style(dialog)) != Speed_control_as_ratio
      snd_display("vf speed_style set: %s", res)
    end
    set_view_files_speed_style(dialog, old_val)

    old_val = view_files_sort(dialog)
    set_view_files_sort(dialog, 2)
    if (res = view_files_sort()) != 0
      snd_display("vf global sort after local set: %s?", res)
    end
    if (res = view_files_sort(dialog)) != 2
      snd_display("vf local sort after local set: %s?", res)
    end
    set_view_files_sort(4)
    if (res = view_files_sort()) != 4
      snd_display("vf global sort after global set: %s?", res)
    end
    if (res = view_files_sort(dialog)) != 2
      snd_display("vf local sort after global set: %s?", res)
    end
    set_view_files_sort(dialog, old_val)
    old_val = view_files_files(dialog)
    set_view_files_files(dialog, ["oboe.snd", "1a.snd", "pistol.snd", "storm.snd"])
    res = view_files_files(dialog)
    if (!res.member?("1a.snd") and !res.member?(cwd + "1a.snd")) or
        (!res.member?("pistol.snd") and !res.member?(cwd + "pistol.snd")) or
        res.length != 4
      snd_display("vf files set: %s", res)
    end
    old_sel = view_files_selected_files(dialog)
    $view_files_select_hook.reset_hook!
    $view_files_select_hook.add_hook!("test 11") do |w, file|
      unless string?(file)
        snd_display("vf select hook arg: %s", file)
      end
      unless w
        snd_display("vf select hook dialog: %s", w)
      end
      selected_file = file
    end
    set_view_files_selected_files(dialog, ["1a.snd"])
    if !string?(selected_file) or
        (selected_file != "1a.snd" and selected_file != cwd + "1a.snd")
      snd_display("vf set_selected_file select hook arg: %s", selected_file)
    end
    if view_files_selected_files(dialog) != ["1a.snd"] and
        view_files_selected_files(dialog) != [cwd + "1a.snd"]
      snd_display("vf selected_files set: %s", view_files_selected_files(dialog).inspect)
    end
    $view_files_select_hook.reset_hook!
    set_view_files_files(dialog, old_val)
    set_view_files_selected_files(dialog, old_sel)
  end
end

# ---------------- test 12: extensions ----------------

def spectral_difference(snd1, snd2)
  size = [frames(snd1), frames(snd2)].max
  pow2 = (log(size) / log(2)).ceil
  fftlen = (2 ** pow2).to_i
  fdr1 = channel2vct(0, fftlen, snd1, 0)
  fdr2 = channel2vct(0, fftlen, snd2, 0)
  spectr1 = snd_spectrum(fdr1, Blackman2_window, fftlen, true)
  spectr2 = snd_spectrum(fdr2, Blackman2_window, fftlen, true)
  diff = 0.0
  diffs = spectr1.subtract(spectr2)
  diffs.each do |d| diff += d.abs end
  diff
end

def test_spectral_difference(snd1, snd2, maxok)
  s1 = open_sound(snd1)
  s2 = open_sound(snd2)
  if (not sound?(s1)) or (not sound?(s2))
    snd_display_prev_caller("open_sound %s or %s failed?", snd1, snd2)
  end
  diff = spectral_difference(s1, s2)
  close_sound(s1)
  close_sound(s2)
  if diff > maxok
    snd_display_prev_caller("translate spectral difference %s %s: %s > %s?",
                            snd1, snd2, diff, maxok)
  end
end

def test_12
  if $with_test_gui
    sf_dir_files = []
    if string?($sf_dir)
      sound_files_in_directory($sf_dir).each do |file|
        dir = $sf_dir + file
        #Snd.catch(:mus_error) do
        Snd.catch do
          mus_sound_chans(dir).between?(1, 255) and
            mus_sound_data_format(dir) >= 0 and
            mus_sound_srate(dir) > 0 and
            mus_sound_frames(dir) >= 0 and
            sf_dir_files.push(dir)
        end
      end
    end
    if sound_file_extensions.null?
      set_sound_file_extensions($original_sound_file_extensions)
    end
    open_files = []
    open_ctr = 0
    add_sound_file_extension("wave")
    unless (exts = sound_file_extensions).member?("wave")
      snd_display("sound_file_extensions: %s?", exts)
    end
    set_sound_file_extensions([])
    unless (res = sound_file_extensions).nil?
      snd_display("sound_file_extensions set []: %s?", res)
    end
    set_sound_file_extensions(exts)
    unless (res = sound_file_extensions).member?("wave")
      snd_display("sound_file_extensions reset: %s?", res)
    end
    until open_ctr == 32
      len = open_files.length
      open_chance = (8 - len) * 0.125
      close_chance = len * 0.125
      if len.zero? or random(1.0) > 0.5
        name = sf_dir_files[random(sf_dir_files.length).floor]
        ht = Snd.catch(:all, 0) do mus_sound_header_type(name) end.first
        df = Snd.catch(:all, 0) do mus_sound_data_format(name) end.first
        fd = if ht == Mus_raw or ht == Mus_unsupported or df == Mus_unknown
               -1
             else
               Snd.catch(:all, -1) do view_sound(name) end.first or -1
             end
        if fd != -1
          open_ctr += 1
          open_files.push(fd)
        end
      else
        if len > 0 and random(1.0) > 0.3
          if sound?(fd = open_files.delete_at(random(open_files.length)))
            close_sound(fd)
          end
        end
      end
    end
    open_files.apply(:close_sound)
    open_files = []
    if Snd.sounds.length != 0
      snd_display("active_sounds: %s %s?",
                  sounds.inspect, Snd.sounds.map do |s| short_file_name(s) end)
    end
    fd = open_raw_sound(:file, $sf_dir + "addf8.nh",
                        :channels, 1, :srate, 8012, :data_format, Mus_mulaw)
    if data_format(fd) != Mus_mulaw
      snd_display("open_raw_sound: %s?", mus_data_format_name(data_format(fd)))
    end
    close_sound(fd)
    #
    $bad_header_hook.reset_hook!
    with_time("test_spectral_difference(oboe.snd, oboe.g723_24, 20)") do
      test_spectral_difference("oboe.snd", $sf_dir + "oboe.g723_24", 20.0)
    end
    test_spectral_difference($sf_dir + "o2.wave", $sf_dir + "o2_dvi.wave", 10.0)
    test_spectral_difference($sf_dir + "wood.riff", $sf_dir + "wood.sds", 4.0)
    test_spectral_difference($sf_dir + "nist-10.wav", $sf_dir + "nist-shortpack.wav", 1.0)
    $bad_header_hook.add_hook!("snd-test") do |n| true end
    #
    # dangling readers (overall)
    #
    ind = open_sound("oboe.snd")
    hi = make_sampler(0, ind, 0)
    close_sound(ind)
    unless sampler?(hi)
      snd_display("dangling reader: %s?", hi)
    end
    unless string?(hi.to_s)
      snd_display("dangling reader format: %s?", hi)
    end
    val = hi.call
    val1 = next_sample(hi)
    val2 = previous_sample(hi)
    val3 = read_sample(hi)
    if fneq(val, 0.0) or fneq(val1, 0.0) or fneq(val2, 0.0) or fneq(val3, 0.0)
      snd_display("dangling read: %s %s %s %s?", val, val1, val2, val3)
    end
    if res = sampler_home(hi)
      snd_display("dangling reader home: %s?", res)
    end
    if (res = sampler_position(hi)).nonzero?
      snd_display("dangling sampler_position: %s?", res)
    end
    unless (res = sampler_at_end?(hi))
      snd_display("dangling reader eof: %s?", res)
    end
    free_sampler(hi)
    #
    # same (pruned edit)
    #
    ind = open_sound("oboe.snd")
    delete_samples(100, 100)
    hi = make_sampler(0, ind, 0)
    revert_sound
    delete_samples(100, 100)
    unless sampler?(hi)
      snd_display("pruned dangling reader: %s?", hi)
    end
    unless string?(hi.to_s)
      snd_display("pruned dangling reader format: %s?", hi)
    end
    val = hi.call
    val1 = next_sample(hi)
    val2 = previous_sample(hi)
    val3 = read_sample(hi)
    if fneq(val, 0.0) or fneq(val1, 0.0) or fneq(val2, 0.0) or fneq(val3, 0.0)
      snd_display("pruned dangling read: %s %s %s %s?", val, val1, val2, val3)
    end
    if (res = sampler_home(hi)) != [ind, 0]
      snd_display("pruned dangling reader home: %s?", res)
    end
    unless (res = sampler_at_end?(hi))
      snd_display("pruned dangling reader eof: %s?", res)
    end
    free_sampler(hi)
    close_sound(ind)
    #
    # region reader
    # 
    ind = open_sound("oboe.snd")
    reg = make_region(1000, 2000, ind, 0)
    rd = make_region_sampler(reg, 0)
    if mix_sampler?(rd)
      snd_display("region_sampler mix: %s?", rd)
    end
    unless region_sampler?(rd)
      snd_display("region_sampler region: %s?", rd)
    end
    if sampler?(rd)
      snd_display("region_sampler normal: %s?", rd)
    end
    if (res = sampler_position(rd)).nonzero?
      snd_display("region_sampler position: %s?", res)
    end
    if (res = sampler_home(rd)) != [reg, 0]
      snd_display("region_sampler home: %s?", res)
    end
    if (res = sampler_at_end?(rd))
      snd_display("region_sampler_at_end?: %s?", res)
    end
    val = rd.call
    if fneq(val, 0.0328)
      snd_display("region_sampler at start: %s?", val)
    end
    unless string?(res = rd.to_s)
      snd_display("region_sampler: %s?", res)
    end
    close_sound(ind)
    forget_region(reg)
    val = read_sample(rd)
    if fneq(val, 0.0)
      snd_display("region_sampler at end: %s?", val)
    end
    unless sampler_at_end?(rd)
      snd_display("region_sampler after deletion?")
    end
    free_sampler(rd)
    #
    # mix reader
    #
    save_md = 0
    $mix_click_hook.add_hook!("mix-click-sets-amp") do |id| mix_click_sets_amp(id) end
    ind = open_sound("oboe.snd")
    reg = make_region(1000, 2000, ind, 0)
    md = mix_region(reg, 0, ind, 0, 0).car
    rd = make_mix_sampler(md)
    set_mix_property(:hi, md, "hi")
    save_md = md
    if (res = mix_property(:hi, md)) != "hi"
      snd_display("mix_property (hi): %s?", res)
    end
    val = rd.call
    if fneq(val, 0.0328)
      snd_display("mix_sampler at start: %s?", val)
    end
    unless string?(res = rd.to_s)
      snd_display("mix_sampler: %s?", res)
    end
    close_sound(ind)
    if (res = Snd.catch do mix_property(:hi, md) end).first != :no_such_mix
      snd_display("mix_property bad mix: %s", res.inspect)
    end
    if (res = rd.to_s) != "#<mix-sampler: inactive>"
      snd_display("mix_sampler released: %s?", res)
    end
    free_sampler(rd)
    $mix_click_hook.reset_hook!
    $close_hook.reset_hook!
    #
    sfiles = []
    ffiles = []
    old_dir = Dir.pwd
    Dir.chdir($sf_dir)
    each_sound_file do |file|
      Snd.catch do
        if mus_sound_chans(file) > 16
          ffiles.push(file)
        end
      end
    end
    map_sound_files do |file|
      Snd.catch do
        if mus_sound_chans(file) > 16
          sfiles.push(file)
        end
      end
    end
    if File.exists?("s24.snd") and
        (ffiles != [$sf_dir + "s24.snd"] or
         sfiles != [$sf_dir + "s24.snd"])
      snd_display("map|for_each_sound_file(s): %s %s?", ffiles, sfiles)
    end
    Dir.chdir(old_dir)
  end
  sf_dir_files.each do |n| mus_sound_forget($sf_dir + n) end
end

# ---------------- test 13: menus, edit lists, hooks, etc ----------------

def loop_through_files(description, select, &make_cmd)
  data = if select
           selection_to_temps(Mus_next, Mus_out_format)
         else
           sound_to_temps(Mus_next, Mus_out_format)
         end
  cont = true
  output_names = make_array(data.length) do |i|
    break unless cont
    outname = format("/tmp/snd_test_%s.snd", random(1.0).object_id)
    cont = shell(make_cmd.call(data[i], outname))
    outname
  end
  if select
    temps_to_selection(data, output_names, description)
  else
    temps_to_sound(data, output_names, description)
  end
end

def copyfile(select)
  loop_through_files("(cp)", select) do |input, output| format("cp %s %s", input, output) end
end

def carg0(hook)
  hook.call
end

def carg1(hook)
  hook.call(1)
end

def carg2(hook)
  hook.call(1, 2)
end

def carg3(hook)
  hook.call(1, 2, 3)
end

def carg4(hook)
  hook.call(1, 2, 3, 4)
end

def carg5(hook)
  hook.call(1, 2, 3, 4, 5)
end

def carg6(hook)
  hook.call(1, 2, 3, 4, 5, 6)
end

def carg7(hook)
  hook.call(1, 2, 3, 4, 5, 6, 7)
end

def harg0
  32
end

def harg1(a)
  a + 32
end

def harg2(a, b)
  a + b + 32
end

def harg3(a, b, c)
  a + b + c + 32
end

def harg4(a, b, c, d)
  a + b + c + d + 32
end

def harg5(a, b, c, d, e)
  a + b + c + d + e + 32
end

def harg6(a, b, c, d, e, f)
  a + b + c + d + e + f + 32
end

def harg7(a, b, c, d, e, f, g)
  a + b + c + d + e + f + g + 32
end

def test_hooks
  funcs = [[:harg0, :carg0, 32],
           [:harg1, :carg1, 33],
           [:harg2, :carg2, 35],
           [:harg3, :carg3, 38],
           [:harg4, :carg4, 42],
           [:harg5, :carg5, 47],
           [:harg6, :carg6, 53],
           [:harg7, :carg7, 60]]
  Snd_hooks.each do |hook|
    if hook? hook
      if hook.arity.between?(0, 7)
        vals = funcs[hook.arity]
        hook.add_hook!(get_func_name, &method(vals[0]))
        if (res = snd_func(vals[1], hook)) != vals[2]
          snd_display("hook.call: %s (%s) %s?", res.inspect, vals[2], hook.inspect)
        end
      else
        snd_display("hook arity: %s %s?", hook.arity, hook.inspect)
      end
    end
  end
  reset_almost_all_hooks
  Snd_hooks.each do |hook|
    if hook?(hook)
      next if hook.name == "$snd_error_hook"
      next if hook.name == "$mus_error_hook"
      unless hook.empty?
        snd_display("%s not empty?", hook.inspect)
      end
    end
  end
end

def mdt_test(a, b, c, d)
  false
end

def test_13_00
  fd = view_sound("oboe.snd")
  mb = add_to_main_menu("clm")
  if (res = Snd.catch do
        add_to_menu(-1, "fm-violin", lambda do | | false end)
      end).first != :no_such_menu
    snd_display("add_to_menu bad menu: %s", res.inspect)
  end
  if (res = Snd.catch do
        add_to_main_menu("oops", make_delay(11))
      end).first != :bad_arity
    snd_display("add_to_main_menu non-thunk: %s", res.inspect)
  end
  if (res = Snd.catch do
        add_to_menu(3, "oops", lambda do |a, b| make_delay(12) end)
      end).first != :bad_arity
    snd_display("add_to_menu non-thunk: %s", res.inspect)
  end
  set_cursor(2000, fd)
  set_transform_graph_type(Graph_once)
  set_transform_graph?(true, fd)
  if $with_test_gui
    add_to_menu(mb, "not here", lambda do | | snd_display("oops") end)
    remove_from_menu(mb,"not here")
    add_to_menu(3, "Denoise", lambda do | | report_in_minibuffer("denoise") end)
  end
  $help_hook.reset_hook!
  hi = snd_help(:cursor_position)
  $help_hook.add_hook!("snd-test") do |a, b|
    if a != "cursor_position"
      snd_display("in $help_hook subject: %s?", a)
    end
    if b != "(cursor-position :optional snd chn): current cursor position (x y in pixels) in snd's channel chn"
      snd_display("in $help_hook text: %s?", b)
    end
    "hiho:" + b
  end
  ho = snd_help(:cursor_position)
  # FIXME (INFO)
  # HI has one char more than HO:
  # HI: cursor_postion(:optional, snd, chn):
  # HO: (cursor-postion :optional snd chn):
  # That's why +4 instead of +5 like in snd-test.scm.
  if ho.length != (hi.length + 4)
    snd_display("length $help_hook\n\t<[%s]%s>\n\t<[%s]%s>?", hi.length, hi, ho.length, ho)
  end
  $help_hook.reset_hook!
  $help_hook.add_hook!("snd-test") do |a, b| false end
  ho = snd_help(:cursor_position)
  if hi != ho
    snd_display("$help_hook false: %s %s?", hi, ho)
  end
  $help_hook.reset_hook!
  # 
  fr = frames(fd)
  chn = chans(fd)
  sr = srate(fd)
  mx = maxamp(fd)
  copyfile(false)
  if (res = edit_fragment) != ["(cp)", "set", 0, 50828]
    snd_display("copyfile: %s?", res)
  end
  if fr != frames(fd) or chn != chans(fd) or fneq(mx, maxamp(fd)) or fneq(sr, srate(fd))
    snd_display("copyfile (1): %s %s %s %s?", frames(fd), chans(fd), srate(fd), maxamp(fd))
  end
  eds = edits
  add_file_to_view_files_list("oboe.snd")
  add_directory_to_view_files_list(".")
  select_all
  copyfile(true)
  if (res = edit_fragment) != ["(cp)", "set", 0, 50828]
    snd_display("copyfile (select): %s?", res)
  end
  if (res = edits) != [eds[0] + 1, eds[1]]
    snd_display("copyfile (select eds): %s %s?", eds, res)
  end
  if fr != frames(fd) or chn != chans(fd) or fneq(mx, maxamp(fd)) or fneq(sr, srate(fd))
    snd_display("copyfile (2): %s %s %s %s?", frames(fd), chans(fd), srate(fd), maxamp(fd))
  end
  #
  set_transform_size(256, fd, 0)
  dpys = [Graph_once, Graph_as_sonogram, Graph_as_spectrogram] * 2
  ffts = [transform2integer($fourier_transform)] * 3 + [transform2integer($autocorrelation)] * 3
  dpys.zip(ffts) do |dpy_type, fft_type|
    set_transform_graph_type(dpy_type, fd, 0)
    set_transform_type(integer2transform(fft_type), fd, 0)
    update_transform_graph(fd, 0)
    vals = transform2vct(fd, 0)
    if vals
      if fneq(res = transform_sample(0, 0, fd, 0), vals[0])
        snd_display("transform_sample %s %s -> %s %s?", dpy_type, fft_type, vals[0], res)
      end
      if vals.length < 256
        snd_display("transform2vct size: %s?", vals.length)
      end
    else
      snd_display("transform %s %s -> %s?", dpy_type, fft_type, vals)
    end
  end
  if (res = Snd.catch do transform_sample(5000, 0, fd, 0) end).first != :no_such_sample
    snd_display("access invalid (bin) transform sample: %s", res.inspect)
  end
  if (res = Snd.catch do transform_sample(0, 5000, fd, 0) end).first != :no_such_sample
    snd_display("access invalid (slice) transform sample: %s", res.inspect)
  end
  close_sound(fd)
  set_transform_type($fourier_transform)
  $after_open_hook.add_hook!("snd-test") do |snd|
    set_x_axis_style(X_axis_in_samples, snd, true)
  end
  fd = open_sound("2.snd")
  close_sound(fd)
  $after_open_hook.reset_hook!
  $after_open_hook.add_hook!("snd-test") do |snd|
    set_x_axis_style(X_axis_in_percentage, snd, true)
  end
  $initial_graph_hook.add_hook!("snd-test") do |snd, chn, dur|
    if mus_sound_maxamp_exists?(file_name(snd))
      amp_vals = mus_sound_maxamp(file_name(snd))
      max_val = amp_vals[chn * 2 + 1]
      [0.0, dur, -max_val, max_val]
    else
      [0.0, dur, -1.0, 1.0]
    end
  end
  $after_open_hook.reset_hook!
  $initial_graph_hook.reset_hook!
  $initial_graph_hook.add_hook!("snd-test") do |snd, chn, dur|
    [0.0, dur, -1.0, 1.0, "a label", -4.0, 4.0]
  end
  fd = open_sound("2.snd")
  if defined? axis_info                           # snd-nogui hasn't axis_info
    ax = axis_info
    if array?(ax) and (fneq(ax[2], 0.0) or
                         fneq(ax[3], -1.0) or
                         fneq(ax[4], mus_sound_duration("2.snd")) or
                         fneq(ax[5], 1.0) or
                         fneq(ax[6], 0.0) or
                         fneq(ax[7], -4.0) or
                         fneq(ax[8], mus_sound_duration("2.snd")) or
                         fneq(ax[9], 4.0))
      snd_display("$initial_graph_hook with ymin/max: %s?", ax)
    end
  end
  $initial_graph_hook.reset_hook!
  set_selection_position(1000, fd, 1)
  set_selection_frames(10, fd, 1)
  set_selection_member?(true, fd, 1)
  if selection_member?(fd, 0)
    snd_display("chan 0 is selection_member?")
  end
  2.times do |chn|
    set_selection_position(1000, fd, chn)
    set_selection_frames(10, fd, chn)
    set_selection_member?(true, fd, chn)
  end
  scale_selection_to([0.5, 0.25].to_vct)
  if fneq(maxamp(fd, 0), 0.5) or fneq(maxamp(fd, 1), 0.25)
    snd_display("scale_selection_to with vector: %s?", maxamp(fd, true))
  end
  close_sound(fd)
  #
  fd = open_sound("obtest.snd")
  Snd.sounds.apply(:close_sound)
  test_hooks
  $bad_header_hook.add_hook!("snd-hook") do |n| true end
  ind = open_sound("oboe.snd")
  set_cursor(2000)
  key(key_to_int(?u), 4, ind)
  key(key_to_int(?1), 0, ind)
  key(key_to_int(?0), 0, ind)
  key(key_to_int(?0), 0, ind)
  key(key_to_int(?x), 4, ind)
  key(key_to_int(?z), 4, ind)
  if (res = edit_fragment) != ["smooth_channel(2000, 100", "set", 2000, 100]
    snd_display("C-x C-z fragment: %s?", res)
  end
  set_cursor(0)
  select_all
  key(key_to_int(?x), 4, ind)
  key(key_to_int(?o), 0, ind)
  key(key_to_int(?-), 4, ind)
  key(key_to_int(?x), 4, ind)
  key(key_to_int(?o), 0, ind)
  key(key_to_int(?x), 4, ind)
  key(key_to_int(?o), 0, ind)
  key(key_to_int(?x), 4, ind)
  key(key_to_int(?p), 0, ind)
  set_selection_member?(false, true)
  revert_sound(ind)
  set_search_procedure(ind, lambda do |n4| n4 > 0.1 end)
  key(key_to_int(?a), 4, ind, 0)
  if cursor(ind, 0).nonzero?
    snd_display("C-a cursor: %s?", cursor(ind, 0))
  end
  key(key_to_int(?s), 4, ind, 0)
  key(key_to_int(?s), 4, ind, 0)
  if cursor(ind, 0) != 4423
    snd_display("search_procedure C-s C-s cursor: %s?", cursor(ind, 0))
  end
  set_search_procedure(ind, lambda do |n| n > 0.2 end)
  set_cursor(0, ind, 0)
  key(key_to_int(?s), 4, ind, 0)
  key(key_to_int(?s), 4, ind, 0)
  if cursor(ind, 0).nonzero?
    snd_display("search_procedure C-s C-s cursor failed: %s?", cursor(ind, 0))
  end
  snd = chn = 0
  edit_hook(ind, 0).reset_hook!
  edit_hook(ind, 0).add_hook!("snd-test") do | | snd + chn end
  edit_hook(ind, 0).reset_hook!
  after_edit_hook(ind, 0).reset_hook!
  after_edit_hook(ind, 0).add_hook!("snd-test") do | | snd + chn end
  after_edit_hook(ind, 0).reset_hook!
  undo_hook(ind, 0).reset_hook!
  undo_hook(ind, 0).add_hook!("snd-test") do | | snd + chn end
  undo_hook(ind, 0).reset_hook!
  calls = 0
  undo_hook(ind, 0).add_hook!("snd-test") do | | calls += 1 end
  delete_sample(0, ind, 0)
  undo_edit(1)
  redo_edit(1)
  revert_sound(ind)
  if calls != 3
    snd_display("undo_hook called %s times (3)?", calls)
  end
  undo_hook(ind, 0).reset_hook!
  close_sound(ind)
end

def test_13_01
  with_file("addf8.nh") do |file|
    unless $open_raw_sound_hook.empty?
      $open_raw_sound_hook.reset_hook!
    end
    $open_raw_sound_hook.add_hook!("snd-hook") do |file, choice| [1, 22050, Mus_bshort] end
    ind = open_sound(file)
    play(ind, :wait, true)
    $open_raw_sound_hook.remove_hook!("snd-hook")
    if (res = [chans(ind),
               srate(ind),
               data_format(ind),
               frames(ind)]) != [1, 22050, Mus_bshort, 23808]
      snd_display("open_raw: %s?", res)
    end
    set_search_procedure(ind, lambda do |n| n > 0.2 end)
    close_sound(ind)
  end
  save_as_dialog = true
  save_as_name = "hiho"
  save_as_index = false
  $after_save_as_hook.reset_hook!
  $after_save_as_hook.add_hook!("snd-test") do |ind, name, dial|
    save_as_index = ind
    save_as_name = name
    save_as_dialog = dial
  end
  ind = open_sound("oboe.snd")
  save_sound_as("test.snd", ind, Mus_raw)
  close_sound(ind)
  $open_raw_sound_hook.reset_hook!
  $after_save_as_hook.reset_hook!
  if save_as_dialog
    snd_display("$after_save_as_hook dialog: %s?", save_as_dialog)
  end
  if save_as_index != ind
    snd_display("$after_save_as_hook index: %s (%s)?", save_as_index, ind)
  end
  if Dir.pwd + "/test.snd" != save_as_name
    snd_display("$after_save_as_hook name: %s (%s)?", save_as_name, Dir.pwd + "/test.snd")
  end
  $open_raw_sound_hook.add_hook!("snd-hook-1") do |file, choice|
    if File.basename(file) != "test.snd"
      snd_display("$open_raw_sound_hook file: %s?", file)
    end
    unless choice == false
      snd_display("$open_raw_sound_hook choice 1: %s?", choice)
    end
    [2, 44100, Mus_mulaw]
  end
  ind = open_sound("test.snd")
  if (res = [header_type(ind), data_format(ind), chans(ind), srate(ind), frames(ind)]) \
    != [Mus_raw, Mus_mulaw, 2, 44100, 50828]
    snd_display("$open_raw_sound_hook 1: %s?", res)
  end
  close_sound(ind)
  $open_raw_sound_hook.add_hook!("snd-hook-2") do |file, choice|
    if choice != [2, 44100, Mus_mulaw]
      snd_display("$open_raw_sound_hook choice 2: %s?", choice)
    end
    [1, 22050, Mus_lint]
  end
  ind = open_sound("test.snd")
  if (res = [header_type(ind), data_format(ind), chans(ind), srate(ind), frames(ind)]) \
    != [Mus_raw, Mus_lint, 1, 22050, 50828 / 2]
    snd_display("$open_raw_sound_hook 2: %s?", res)
  end
  close_sound(ind)
  $open_raw_sound_hook.reset_hook!
  $open_raw_sound_hook.add_hook!("snd-hook-3") do |file, choice|
    [2]
  end
  ind = open_sound("test.snd")
  if (res = [header_type(ind), data_format(ind), chans(ind), srate(ind)]) \
    != [Mus_raw, Mus_lint, 2, 22050]
    snd_display("$open_raw_sound_hook 3: %s?", res)
  end
  close_sound(ind)
  $open_raw_sound_hook.reset_hook!
  $open_raw_sound_hook.add_hook!("snd-hook-4") do |file, choice|
    [1, 22050, Mus_bshort, 120, 320]
  end
  ind = open_sound("test.snd")
  if (res = [header_type(ind), data_format(ind), chans(ind), srate(ind),
             data_location(ind), data_size(ind), frames(ind)]) \
    != [Mus_raw, Mus_bshort, 1, 22050, 120, 320, 160]
    snd_display("$open_raw_sound_hook 4: %s?", res)
  end
  close_sound(ind)
  $open_raw_sound_hook.reset_hook!
  $during_open_hook.reset_hook!
  #
  ind = op = sl = aop = dop = cl = ig = scl = other = false
  $open_hook.add_hook!("snd-test") do |filename|
    if filename != mus_expand_filename("oboe.snd")
      snd_display("$open_hook: %s?", filename)
    end
    op = true
    false
  end
  $after_open_hook.add_hook!("snd-test") do |snd|
    aop = snd
  end
  $during_open_hook.add_hook!("snd-test") do |fd, filename, reason|
    if filename != mus_expand_filename("oboe.snd")
      snd_display("$during_open_hook filename: %s?", filename)
    end
    if reason != 1
      snd_display("$during_open_hook reason: %s?", reason)
    end
    dop = true
  end
  $initial_graph_hook.add_hook!("snd-test") do |snd, chn, dur|
    if chn.nonzero?
      snd_display("$initial_graph_hook (channel): %s not 0?", chn)
    end
    ig = true
    false
  end
  ind = open_sound("oboe.snd")
  unless op
    snd_display("$open_hook not called?")
  end
  unless dop
    snd_display("$during_open_hook not called?")
  end
  unless ig
    snd_display("$initial_graph_hook not called?")
  end
  unless sound?(aop)
    snd_display("$after_open_hook not called?")
  end
  if aop != ind
    snd_display("$after_open_hook %s but ind: %s?", aop, ind)
  end
  select_all
  $open_hook.reset_hook!
  $during_open_hook.reset_hook!
  $after_open_hook.reset_hook!
  $initial_graph_hook.reset_hook!
  $open_hook.add_hook!("snd-test") do |filename| true end
  unless (res = open_sound("pistol.snd")) == false
    snd_display("$open_hook true, but open_sound -> %s?", res)
    if sound?(res)
      close_sound(res)
    end
  end
  $open_hook.reset_hook!
  #
  gr = agr = gbf = abf = false
  $before_transform_hook.reset_hook!
  $after_transform_hook.reset_hook!
  $after_graph_hook.reset_hook!
  $graph_hook.reset_hook!
  $graph_hook.add_hook!("snd-test") do |snd, chn, y0, y1|
    unless snd.eql?(ind)
      snd_display("$graph_hook: %s not %s?", snd, ind)
    end
    if chn.nonzero?
      snd_display("$graph_hook: (channel): %s not 0?", chn)
    end
    gr = true
    false
  end
  $after_graph_hook.add_hook!("snd-test") do |snd, chn|
    unless snd.eql?(ind)
      snd_display("$after_graph_hook: %s not %s?", snd, ind)
    end
    if chn.nonzero?
      snd_display("$after_graph_hook: (channel): %s not 0?", chn)
    end
    agr = true
  end
  $before_transform_hook.add_hook!("snd-test") do |snd, chn|
    gbf = true
    cursor
  end
  $after_transform_hook.add_hook!("snd-test") do |snd, chn, scale|
    if transform_graph?(snd, chn) and transform_graph_type(snd, chn) == Graph_once
      report_in_minibuffer((2 * transform2vct(snd, chn).peak / transform_size(snd, chn)).to_s, snd)
    end
    abf = true
    false
  end
  set_transform_graph?(true, ind, 0)
  set_time_graph?(true, ind, 0)
  update_transform_graph(ind, 0)
  if $with_test_motif
    unless gr
      app = main_widgets.car
      1000.times do
        msg = RXtAppPending(app)
        if (msg & (RXtIMXEvent | RXtIMAlternateInput)).zero?
          break
        else
          RXtDispatchEvent(RXtAppNextEvent(app))
        end
      end
    end
  end
  unless gr
    snd_display("$graph_hook not called? %s %s %s %s?",
                time_graph?(ind), short_file_name(ind), ind, sounds)
  end
  unless agr
    snd_display("$after_graph_hook not called?")
  end
  unless gbf
    snd_display("$before_transform_hook not called?")
  end
  unless abf
    snd_display("$after_transform_hook not called?")
  end
  $before_transform_hook.reset_hook!
  set_transform_graph?(false, ind, 0)
  $graph_hook.reset_hook!
  $after_graph_hook.reset_hook!
  #
  other = open_sound("pistol.snd")
  $select_sound_hook.add_hook!("snd-test") do |snd|
    unless snd.eql?(ind)
      snd_display("$select_sound_hook: %s not %s?", snd, ind)
    end
    sl = true
  end
  $select_channel_hook.add_hook!("snd-test") do |snd, chn|
    unless snd.eql?(ind)
      snd_display("$select_channel_hook: %s not %s?", snd, ind)
    end
    if chn.nonzero?
      snd_display("$select_channel_hook: (channel): %s not 0?", chn)
    end
    scl = true
  end
  select_sound(ind)
  unless sl
    snd_display("$select_sound_hook not called?")
  end
  unless scl
    snd_display("$select_channel_hook not called?")
  end
  $select_sound_hook.reset_hook!
  $select_channel_hook.reset_hook!
  #
  spl = stl = ph = ph1 = false
  $start_playing_hook.add_hook!("snd-test") do |snd|
    unless snd.eql?(ind)
      snd_display("$start_playing_hook: %s not %s?", snd, ind)
    end
    spl = true
    false
  end
  $stop_playing_hook.add_hook!("snd-test") do |snd|
    unless snd.eql?(ind)
      snd_display("$stop_playing_hook: %s not %s?", snd, ind)
    end
    stl = true
    false
  end
  $play_hook.add_hook!("snd-test") do |n|
    if n < 128
      snd_display("$play_hook samps: %s?", n)
    end
    set_expand_control_hop(expand_control_hop)
    set_expand_control_length(expand_control_length)
    set_expand_control_ramp(expand_control_ramp)
    set_contrast_control_amp(contrast_control_amp)
    set_reverb_control_lowpass(reverb_control_lowpass)
    set_reverb_control_feedback(reverb_control_feedback)
    ph = true
  end
  $dac_hook.add_hook!("snd-test") do |n|
    unless sound_data?(n)
      snd_display("$dac_hook data: %s?", n)
    end
    if sound_data_length(n) < 128 and sound_data_length(n) != 64
      snd_display("$dac_hook data length: %s?", sound_data_length(n))
    end
    ph1 = true
  end
  set_expand_control?(true, ind)
  set_reverb_control?(true, ind)
  play(ind, :wait, true)
  set_expand_control?(false, ind)
  set_reverb_control?(false, ind)
  unless spl
    snd_display("$start_playing_hook not called?")
  end
  unless stl
    snd_display("$stop_playing_hook not called?")
  end
  unless ph
    snd_display("$play_hook not called?")
  end
  unless ph1
    snd_display("$dac_hook not called?")
  end
  $start_playing_hook.reset_hook!
  $start_playing_selection_hook.reset_hook!
  $stop_playing_hook.reset_hook!
  $play_hook.reset_hook!
  $dac_hook.reset_hook!
  $play_hook.add_hook!("snd-test") do |n|
    set_expand_control_hop(0.02)
    set_expand_control_length(0.02)
    set_expand_control_ramp(0.2)
    set_contrast_control_amp(0.5)
    set_reverb_control_lowpass(0.02)
    set_reverb_control_feedback(0.02)
  end
  play(ind, :wait, true)
  $play_hook.reset_hook!
  $start_playing_hook.add_hook!("snd-test") do |snd| true end
  play("4.aiff")
  $start_playing_hook.reset_hook!
  #
  ss = false
  old_reg = selection_creates_region
  set_selection_creates_region(true)
  $stop_playing_selection_hook.add_hook!("snd-test") do | | ss = true end
  reg = select_all
  play(selection, :wait, true)
  play(reg, :wait, true)
  unless ss
    snd_display("$stop_playing_selection_hook not called?")
  end
  $stop_playing_selection_hook.reset_hook!
  set_selection_creates_region(old_reg)
  ctr = 0
  $dac_hook.add_hook!("snd-test") do |n|
    ctr += 1
    stop_playing
  end
  play(ind, :wait, true)
  if ctr > 2
    snd_display("stop_playing: %s?", ctr)
  end
  $dac_hook.reset_hook!
  #
  pl = make_player(ind, 0)
  ctr = 0
  unless player?(pl)
    snd_display("make_player: %s?", pl)
  end
  if (not players) # players returs nil if empty
    snd_display("players: %s?", players.inspect)
  end
  $dac_hook.add_hook!("snd-test") do |n|
    ctr += 1
    if player?(pl)
      stop_player(pl)
    else
      if ctr == 1
        snd_display("player messed up")
      end
    end
  end
  add_player(pl)
  start_playing(1, 22050, false)
  if ctr > 2
    snd_display("stop_player: %s?", ctr)
  end
  $dac_hook.reset_hook!
  pl = make_player(ind, 0)
  free_player(pl)
  if player?(pl)
    snd_display("free_player: %s?", pl)
  end
  #
  e0 = e1 = u0 = u1 = a0 = a1 = false
  edit_hook(ind, 0).add_hook!("snd-test-1")         do | | e0 = true end
  edit_hook(other, 0).add_hook!("snd-test-2")       do | | e1 = true; false end
  undo_hook(ind, 0).add_hook!("snd-test-1")         do | | u0 = true end
  undo_hook(other, 0).add_hook!("snd-test-2")       do | | u1 = true end
  after_edit_hook(ind, 0).add_hook!("snd-test-1")   do | | a0 = true end
  after_edit_hook(other, 0).add_hook!("snd-test-2") do | | a1 = true end
  # 
  delete_sample(0, ind, 0)
  if edit_position(ind, 0) != 0
    snd_display("edit_hook true did not disallow edit!")
  end
  unless e0
    snd_display("edit_hook true not called?")
  end
  if a0
    snd_display("after_edit_hook 0 called?")
  end
  undo_edit(1, ind, 0)
  if u0
    snd_display("undo_hook called?")
  end
  # 
  delete_sample(0, other, 0)
  if edit_position(other, 0) != 1
    snd_display("edit_hook false did not allow edit!")
  end
  unless e1
    snd_display("edit_hook false not called?")
  end
  unless a1
    snd_display("after_edit_hook 1 not called?")
  end
  undo_edit(1, other, 0)
  unless u1
    snd_display("undo_hook not called?")
  end
  # 
  edit_hook(ind, 0).reset_hook!
  edit_hook(other, 0).reset_hook!
  undo_hook(ind, 0).reset_hook!
  undo_hook(other, 0).reset_hook!
  after_edit_hook(ind, 0).reset_hook!
  after_edit_hook(other, 0).reset_hook!
  #
  se = sw = me = false
  $snd_warning_hook.reset_hook!
  $snd_error_hook.reset_hook!
  $mus_error_hook.reset_hook!
  $snd_warning_hook.add_hook!("snd-test") do |msg| sw = true end
  $snd_error_hook.add_hook!("snd-test") do |msg| se = true end
  $mus_error_hook.add_hook!("snd-test") do |type, msg| me = true end
  snd_error("uhoh")
  snd_warning("hiho")
  mus_sound_samples("/bad/baddy")
  unless se
    snd_display("$snd_error_hook not called?")
  end
  unless sw
    snd_display("$snd_warning_hook not called?")
  end
  unless me
    snd_display("$mus_error_hook not called?")
  end
  $snd_error_hook.reset_hook!
  $snd_warning_hook.reset_hook!
  $mus_error_hook.reset_hook!
  $snd_error_hook.add_hook!("snd-test") do |msg| se = msg; true end
  snd_error("not an error")
  if (not string?(se)) or se != "not an error"
    snd_display("$snd_error_hook saw: %s?", se)
  end
  #
  $before_exit_hook.add_hook!("snd-test-1") do | | false end
  $before_exit_hook.add_hook!("snd-test-2") do | | true end
  $exit_hook.add_hook!("snd-test-3") do | | false end
  exit
  $exit_hook.reset_hook!
  $before_exit_hook.reset_hook!
  #
  sh = false
  delete_file("baddy.snd")
  $save_hook.add_hook!("snd-test") do |snd, filename|
    if (not string?(filename)) or filename != mus_expand_filename("baddy.snd")
      snd_display("$save_hook filename: %s?", filename)
    end
    unless snd.eql?(ind)
      snd_display("$save_hook: %s not %s?", snd, ind)
    end
    sh = true
  end
  save_sound_as("baddy.snd", ind)
  unless sh
    snd_display("$save_hook not called?")
  end
  if File.exists?("baddy.snd")
    snd_display("$save_hook did not cancel save?")
    delete_file("baddy.snd")
  end
  $save_hook.reset_hook!
  $close_hook.add_hook!("snd-test") do |snd|
    unless snd.eql?(ind)
      snd_display("$close_hook: %s not %s?", snd, ind)
    end
    cl = true
  end
  close_sound(ind)
  unless cl
    snd_display("$close_hook not called?")
  end
  $close_hook.reset_hook!
  close_sound(other)
end

def test_13_02
  $print_hook.add_hook!("snd-test") do |str|
    if str[0] == ?[ and (print_length == 30 and
                           str != "[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ...]" or
                           print_length == 12 and
                           str != "[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ...]")
      snd_display("array abbreviation: %s?", str)
    end
    false
  end
  snd_print(make_array(128, 1))
  $print_hook.reset_hook!
  unless $with_test_alsa
    in1 = open_sound("oboe.snd")
    in2 = open_sound("2.snd")
    set_sync(1, in1)
    set_sync(1, in2)
    play(selected_sound, :wait, true)
    close_sound(in1)
    close_sound(in2)
  end
  #
  ind = open_sound("oboe.snd")
  if $with_test_motif
    edhist = channel_widgets(ind, 0)[7]
    edp = RXtParent(edhist)
    pmax = RXtVaGetValues(edp, [RXmNpaneMaximum, 0]).cadr
    RXtUnmanageChild(edp)
    RXtVaSetValues(edp, [RXmNpaneMinimum, 100])
    RXtManageChild(edp)
  end
  edit_hook_ctr = 0
  after_edit_hook_ctr = 0
  edit_hook(ind, 0).add_hook!("snd-test") do | |
    edit_hook_ctr += 1
    true
  end
  after_edit_hook(ind, 0).add_hook!("snd-test") do | |
    after_edit_hook_ctr += 1
    true
  end
  all_tests = [[:apply_controls, lambda { | |
                  set_amp_control(0.5, ind, 0)
                  apply_controls(ind)
                  set_amp_control(1.0, ind, 0)
                }],
               [:clm_channel, lambda { | | clm_channel(make_two_zero(1, -1)) }],
               [:convolve_selection_with, lambda { | |
                  reg = select_all(ind, 0)
                  convolve_selection_with("1a.snd", 0.5)
                  region?(reg) and forget_region(reg)
                }],
               [:convolve_with, lambda { | | convolve_with("1a.snd", 0.5, ind, 0)}],
               [:delete_sample, lambda { | | delete_sample(123, ind, 0) }],
               [:delete_samples, lambda { | | delete_samples(123, 123, ind, 0) }],
               [:delete_selection, lambda { | |
                  reg = select_all(ind, 0)
                  delete_selection
                  region?(reg) and forget_region(reg)
                }],
               [:env_channel, lambda { | | env_channel([0, 0, 1, 1]) }],
               [:env_selection, lambda { | |
                  reg = select_all(ind, 0)
                  env_selection([0, 0, 1, 1], 1.0)
                  region?(reg) and forget_region(reg)
                }],
               [:env_sound, lambda { | | env_sound([0, 0, 1, 1]) }],
               [:filter_sound, lambda { | | filter_sound([0, 1, 1, 0], 1024) }],
               [:filter_selection, lambda { | |
                  reg = select_all(ind, 0)
                  filter_selection([0, 0, 1, 1], 6)
                  region?(reg) and forget_region(reg)
                }],
               [:insert_region, lambda { | |
                  reg = make_region(0, 100, ind, 0)
                  insert_region(reg, 123, ind, 0)
                  region?(reg) and forget_region(reg)
                }],
               [:insert_sample, lambda { | | insert_sample(123, 0.5, ind, 0) }],
               [:insert_samples, lambda { | | insert_samples(123, 3, make_vct(3, 1.0), ind, 0) }],
               [:insert_selection, lambda { | |
                  reg = select_all(ind, 0)
                  insert_selection(120, ind, 0)
                  region?(reg) and forget_region(reg)
                }],
               [:insert_silence, lambda { | | insert_silence(123, 456, ind, 0) }],
               [:insert_sound, lambda { | | insert_sound("1a.snd", 123) }],
               [:map_chan, lambda { | | map_chan(lambda { |y| y + 0.2 }) }],
               [:map_channel, lambda { | | map_channel(lambda { |y| y + 0.2 }) }],
               [:mix, lambda { | | mix("1a.snd", 123) }],
               [:mix_amp, lambda { | |
                  mx = mix_vct(make_vct(3, 1.0), 123)
                  mix?(mx) and set_mix_amp(mx, 0.123)
                }],
               [:mix_amp_env, lambda { | |
                  mx = mix_vct(make_vct(3, 1.0), 123)
                  mix?(mx) and set_mix_amp_env(mx, [0, 0, 1, 1])
                }],
               [:mix_position, lambda { | |
                  mx = mix_vct(make_vct(3, 1.0), 123)
                  mix?(mx) and set_mix_position(mx, 123)
                }],
               [:mix_speed, lambda { | |
                  mx = mix_vct(make_vct(3, 1.0), 123)
                  mix?(mx) and set_mix_speed(mx, 0.123)
                }],
               [:mix_region, lambda { | |
                  reg = make_region(0, 100, ind, 0)
                  mix_region(reg, 123, ind, 0)
                  region?(reg) and forget_region(reg)
                }],
               [:mix_selection, lambda { | |
                  reg = select_all(ind, 0)
                  mix_selection(1234, ind, 0)
                  region?(reg) and forget_region(reg)
                }],
               [:mix_vct, lambda { | | mix_vct(make_vct(10, 0.3), 123) }],
               [:pad_channel, lambda { | | pad_channel(123, 456, ind, 0) }],
               [:ramp_channel, lambda { | | ramp_channel(0.0, 0.5, 123, 456) }],
               [:reverse_channel, lambda { | | reverse_channel(123, 456, ind, 0) }],
               [:reverse_sound, lambda { | | reverse_sound(ind, 0) }],
               [:reverse_selection, lambda { | |
                  reg = select_all(ind, 0)
                  reverse_selection
                  region?(reg) and forget_region(reg)
                }],
               [:scale_by, lambda { | | scale_by(2.0) }],
               [:scale_channel, lambda { | | scale_channel(0.5, 123, 456, ind, 0) }],
               [:scale_selection_by, lambda { | |
                  reg = select_all(ind, 0)
                  scale_selection_by(2.0)
                  region?(reg) and forget_region(reg)
                }],
               [:scale_selection_to, lambda { | |
                  reg = select_all(ind, 0)
                  scale_selection_to(0.5)
                  region?(reg) and forget_region(reg)
                }],
               [:scale_to, lambda { | | scale_to(0.4) }],
               [:scale_sound_to, lambda { | | scale_sound_to(0.5) }],
               [:smooth_channel, lambda { | | smooth_channel(123, 456, ind, 0) }],
               [:smooth_sound, lambda { | | smooth_sound(123, 456, ind, 0) }],
               [:smooth_selection, lambda { | |
                  reg = select_all(ind, 0)
                  smooth_selection
                  region?(reg) and forget_region(reg)
                }],
               [:src_channel, lambda { | | src_channel(0.5, 123, 456, ind, 0) }],
               [:src_sound, lambda { | | src_sound([0, 0.5, 1, 1]) }],
               [:src_selection, lambda { | |
                  reg = select_all(ind, 0)
                  src_selection(0.5)
                  region?(reg) and forget_region(reg)
                }],
               [:swap_channels, lambda { | |
                  ind1 = open_sound("1a.snd")
                  swap_channels(ind, 0, ind1, 0)
                  close_sound(ind1)
                }],
               [:vct2channel, lambda { | | vct2channel(make_vct(3), 123, 3, ind, 0) }],
               [:xramp_channel, lambda { | | xramp_channel(0.5, 1.0, 32.0, 123, 456, ind, 0) }]]
  # 
  all_tests.each do |name, func|
    Snd.catch do func.call end
    if (res = edit_position(ind, 0)) != 0
      snd_display("%s: blocked edit: %s?", name, res)
    end
    if edit_hook_ctr != 1
      snd_display("%s: edit_hook calls: %s?", name, edit_hook_ctr)
    end
    if after_edit_hook_ctr != 0
      snd_display("%s: after_edit_hook calls: %s?", name, after_edit_hook_ctr)
    end
    edit_hook_ctr = 0
    if (res = mixes(ind, 0)) != nil
      snd_display("%s: mixes: %s?", name, res)
    end
  end
  #
  edit_hook_ctr = 0
  after_edit_hook_ctr = 0
  edit_hook(ind, 0).reset_hook!
  after_edit_hook(ind, 0).reset_hook!
  edit_hook(ind, 0).add_hook!("snd-test") do | |
    edit_hook_ctr += 1
    false
  end
  after_edit_hook(ind, 0).add_hook!("snd-test") do | |
    after_edit_hook_ctr += 1
    true
  end
  #
  all_tests.each do |name, func|
    func.call
    unless (res = edit_position(ind, 0)) > 0
      snd_display("%s: unblocked edit: %s?", name, res)
    end
    unless edit_hook_ctr > 0
      snd_display("%s: unblocked edit_hook calls: %s?", name, edit_hook_ctr)
    end
    unless after_edit_hook_ctr > 0
      snd_display("%s: unblocked after_edit_hook calls: %s?", name, after_edit_hook_ctr)
    end
    edit_hook_ctr = 0
    after_edit_hook_ctr = 0
    revert_sound(ind)
  end
  if $with_test_motif
    edhist = channel_widgets(ind, 0)[7]
    edp = RXtParent(edhist)
    pmax = RXtVaGetValues(edp, [RXmNpaneMaximum, 0]).cadr
    RXtUnmanageChild(edp)
    RXtVaSetValues(edp, [RXmNpaneMinimum, 100])
    RXtManageChild(edp)
  end
  close_sound(ind)
  # 
  # before|after-save-as-hook
  # 
  hook_called = false
  $before_save_as_hook.add_hook!("snd-test") do |snd, fname, sel, sr, type, fmt, com|
    if sr != srate(snd)
      channels(snd).times do |chn|
        src_channel(srate(snd).to_f / sr, 0, false, snd, chn)
      end
      save_sound_as(fname, snd, :header_type, type, :data_format, fmt, :srate, sr, :comment, com)
      channels(snd).times do |chn| undo_edit(1, snd, chn) end
      hook_called = true
      true
    else
      false
    end
  end
  ind = open_sound("2.snd")
  save_sound_as("test.snd", :srate, 44100)
  if (res = edit_position(ind, 0)).nonzero?
    snd_display("$before_save_as_hook undo_edit: %s?", res)
  end
  unless hook_called
    snd_display("$before_save_as_hook not called?")
  end
  close_sound(ind)
  ind = open_sound("test.snd")
  if (res = srate(ind)) != 44100
    snd_display("$before_save_as_hook src: %s?", res)
  end
  close_sound(ind)
  $before_save_as_hook.remove_hook!("snd-test")
  # 
  need_save_as_undo = false
  $before_save_as_hook.add_hook!("snd-test") do |snd, fname, sel, sr, type, fmt, com|
    need_save_as_undo = false
    if sr != srate(snd)
      src_sound(srate(snd).to_f / sr, 1.0, snd)
      need_save_as_undo = true
    end
    false
  end
  $after_save_as_hook.add_hook!("snd-test") do |snd, fname, dialog|
    if need_save_as_undo
      undo_edit
    end
  end
  ind = open_sound("oboe.snd")
  save_sound_as("test.snd", :srate, 44100)
  if (res = edit_position(ind, 0)).nonzero?
    snd_display("$after_save_as_hook undo_edit: %s?", res)
  end
  close_sound(ind)
  ind = open_sound("test.snd")
  if (res = srate(ind)) != 44100
    snd_display("$before|after_save_as_hook src: %s?", res)
  end
  close_sound(ind)
  $before_save_as_hook.reset_hook!
  $after_save_as_hook.reset_hook!
  #
  old_clip = clipping
  old_mus_clip = mus_clipping
  set_clipping(true)
  set_mus_clipping(true)
  $clip_hook.reset_hook!
  index = new_sound("test.snd", Mus_next, Mus_bshort, 22050, 1, "clip-hook test", 10)
  map_channel(lambda do |y| mus_random(0.999) end)
  set_sample(2,  1.0001)
  set_sample(4, -1.0)
  set_sample(6,  1.5)
  set_sample(8, -1.5)
  hook_called = 0
  vals = channel2vct(0, 10, index)
  $clip_hook.add_hook!("test-13-clip") do |val|
    if fneq(val, 1.0) and
        fneq(val, 1.5) and
        fneq(val, -1.5)
      snd_display("$clip_hook called upon: %s?", val)
    end
    hook_called += 1
    0.0
  end
  save_sound(index)
  $clip_hook.reset_hook!
  if hook_called != 3
    snd_display("$clip_hook called %s times?", hook_called)
  end
  close_sound(index)
  index = open_sound("test.snd")
  new_vals = channel2vct(0, 10, index)
  fixed_vals = vct_copy(vals)
  fixed_vals[2] = 0.0
  fixed_vals[6] = 0.0
  fixed_vals[8] = 0.0
  unless vequal(fixed_vals, new_vals)
    snd_display("$clip_hook results:\n%s\n%s\n%s?", new_vals, fixed_vals, vals)
  end
  close_sound(index)
  set_clipping(old_clip)
  set_mus_clipping(old_mus_clip)
end

def test_13
  if $with_test_gui
    reset_almost_all_hooks
    test_13_00
    test_13_01
    test_13_02
  end
end

# ---------------- test 14: all together now ----------------

def test_panel(func)
  val = snd_func(func, true)
  val1 = Snd.sounds.apply(:snd_func, func)
  val2 = Snd.sounds.reverse.apply(:snd_func, func)
  if (not vequal(val, val1)) and (not vequal(val, val2))
    snd_display_prev_caller("%s %s:\n\t%s\n\t%s\n\t%s?", get_func_name, func, val, val1, val2)
  end
end

def all_chans_zipped_reverse
  new_list = []
  Snd.sounds.reverse.each do |snd|
    channels(snd).times do |chn|
      new_list.push([snd, chn])
    end
  end
  new_list
end

def test_channel(func)
  val = snd_func(func, true, true).flatten
  val1 = all_chans_zipped.map do |snd, chn| snd_func(func, snd, chn) end.flatten
  val2 = all_chans_zipped_reverse.map do |snd, chn| snd_func(func, snd, chn) end.flatten
  if (not val.eql?(val1)) and (not val.eql?(val2))
    snd_display_prev_caller("%s %s:\n\t%s\n\t%s\n\t%s?", get_func_name, func, val, val1, val2)
  end
end

def duration(snd)
  frames(snd) / srate(snd).to_f
end

def safe_make_selection(beg, fin, snd)
  len = frames(snd)
  old_choice = selection_creates_region()
  set_selection_creates_region(true)
  if len > 1
    if fin < len
      make_selection(beg, fin, snd)
    else
      if beg < len
        make_selection(beg, len - 1, snd)
      else
        make_selection(0, len - 1, snd)
      end
    end
  end
  set_selection_creates_region(old_choice)
end

def clone_sound_as(new_name, snd = false)
  string?(save_dir) or set_save_dir("/tmp")
  old_snd = Snd.snd(snd)
  fdir, fbase = File.split(snd_tempnam)
  rb = fdir + "/" + File.basename(fbase, ".snd") + ".rb"
  save_edit_history(rb, old_snd)
  file_copy(file_name(old_snd), new_name)
  sfile = open_sound(new_name)
  eval(File.open(rb).read)
  delete_file(rb)
  sfile
end

def test_14
  if $with_test_gui
    cur_dir_files = []
    sound_files_in_directory(".").each do |f|
      if Snd.catch(:all, 0) do mus_sound_frames(f) end.first > 0
        cur_dir_files.push(f)
      end
    end
    $after_open_hook.add_hook!("make_player") do |snd| make_player(snd, 0) end
    open_ctr = 0
    stereo_files = []
    quad_files = []
    mono_files = []
    octo_files = []
    open_files = []
    cur_dir_files.each do |name|
      ht = mus_sound_header_type(name)
      df = mus_sound_data_format(name)
      len = mus_sound_frames(name)
      if ht != Mus_raw and len.nonzero? and df != -1
        case mus_sound_chans(name)
        when 1
          mono_files.push(name)
        when 2
          stereo_files.push(name)
        when 4
          quad_files.push(name)
        when 8
          octo_files.push(name)
        end
      end
    end
    with_buffers_menu
    Snd.sounds.each do |snd|
      mxpos = 0
      channels(snd).times do |chn| mxpos += edit_position(snd, chn) end
      if mxpos > 100 or channels(snd) > 4
        snd_display("revert %s at %s?", file_name(snd), mxpos)
        revert_sound(snd)
      end
    end
    clear_sincs
    files = Snd.sounds.length
    delete_file("s61.rb")
    Snd.sounds.each do |s|
      if channels(s) > 4
        open_files.delete_if do |f| f == s end
        close_sound(s)
      end
    end
    save_state("s61.rb")
    Snd.sounds.apply(:close_sound)
    Snd.regions.apply(:forget_region)
    load("s61.rb")
    if Snd.sounds.length != files
      snd_display("save_state restart from %s to %s sounds?", files, Snd.sounds.length)
    end
    open_files = Snd.sounds
    len = open_files.length
    open_chance = [0.0, (8.0 - len) * 0.125].max
    close_chance = len * 0.125
    if len.zero?
      name = cur_dir_files[random(cur_dir_files.length)]
      ht = mus_sound_header_type(name)
      df = mus_sound_data_format(name)
      fd = (ht == Mus_raw or df == -1) ? -1 : view_sound(name)
      if number?(fd) and fd != -1
        open_files.push(fd)
      else
        close_sound(fd)
      end
    end
    if len > 0
      fd = open_files[random(open_files.length)]
      close_sound(fd)
      open_files.delete_if do |f| f == fd end
    end
    open_ctr = open_files.length
    if open_ctr.zero?
      fd = view_sound("oboe.snd")
      open_ctr = 1
      open_files.push(fd)
    end
    choose_fd = lambda do Snd.sounds[random(Snd.sounds.length)] end
    curfd = choose_fd.call
    curloc = [0, [1200, frames(curfd, 0)].min].max
    old_marks = Snd.marks(curfd, 0).length
    if (dur = duration(curfd)) > 0.0
      set_x_bounds([0.0, [dur, 1.0].min], curfd)
      xb = x_bounds(curfd)
      if fneq(xb[0], 0.0) or fneq(xb[1], [dur, 1.0].min)
        snd_display("x_bounds: %s?", xb)
      end
    end
    set_y_bounds([-0.5, 0.5], curfd)
    yb = y_bounds(curfd)
    if fneq(yb[0], -0.5) or fneq(yb[1], 0.5)
      snd_display("y_bounds: %s?", yb)
    end
    set_cursor(curloc, curfd, 0)
    cl = cursor(curfd, 0)
    if cl != curloc and (res = frames(curfd, 0)) > curloc
      snd_display("cursor %s != %s (frames: %s)?", cl, curloc, res)
      curloc = cursor(curfd, 0)
    end
    if curloc >= frames(curfd, 0)
      curloc = 0
    end
    id = Snd.catch(:all, -1) do add_mark(curloc, curfd) end.car
    if number?(id) and id != -1
      cl = mark_sample(id)
      new_marks = Snd.marks(curfd, 0).length
      if cl != curloc
        snd_display("mark %s != %s?", cl, curloc)
      end
      if new_marks != (old_marks + 1)
        snd_display("marks %s %s?", new_marks, old_marks)
      end
      new_id = find_mark(curloc, curfd)
      if (not mark?(new_id)) or id != new_id
        snd_display("find_mark (by sample): %s %s (%s for %s %s)?",
                    id, new_id, curloc, mark_sample(id), mark_sample(new_id))
      end
      set_mark_name(id, "hiho")
      new_id = find_mark("hiho", curfd)
      if (not mark?(new_id)) or id != new_id
        snd_display("find_mark (by name): %s %s (%s for %s %s)?", id, new_id)
      end
      if (res = mark_name(id)) != "hiho"
        snd_display("mark_name: %s?", res)
      end
      set_mark_sample(id, [0, curloc - 100].max)
      cl = mark_sample(id)
      if cl != [0, curloc - 100].max
        snd_display("set_mark_sample %s != %s?", cl, curloc)
      end
      delete_mark(id)
    end
    if duration(curfd) > 1.2
      set_x_bounds([1.0, 1.1], curfd)
    end
    if frames(curfd) > 25
      add_mark(10, curfd)
      add_mark(20, curfd)
      key(key_to_int(?m), 0, curfd)
      set_cursor(0, curfd)
      new_marks = Snd.marks(curfd, 0).length
      delete_marks(curfd)
      if duration(curfd) > 0.0
        set_x_bounds([0.0, [duration(curfd), 0.1].min], curfd)
      end
      set_y_bounds([-1.0, 1.0], curfd)
      if Snd.marks(curfd, 0).length > 0 or new_marks != (old_marks + 3)
        snd_display("delete marks: %s %s?", new_marks, old_marks)
      end
    end
    revert_sound
    select_all
    Snd.catch do
      if region?(0) and selection?
        r1 = region_rms(regions.first)
        r2 = selection_rms
        r3 = selection_rms
        r4 = region_rms(regions.first)
        if fneq(r1, r4)
          snd_display("region_rms: %s %s?", r1, r4)
        end
        if fneq(r2, r3)
          snd_display("selection_rms: %s %s?", r2, r3)
        end
      end
    end
    Snd.catch do
      if region?(regions[2])
        play(regions[2], :wait, true)
      end
    end
    Snd.catch do mix_region(regions[2]) end
    frames < 100000 and play(selected_sound, :wait, true)
    scale_to(0.1, choose_fd.call)
    scale_by(2.0, choose_fd.call)
    save_controls
    set_amp_control(0.5)
    test_panel(:amp_control)
    restore_controls
    report_in_minibuffer("hi")
    append_to_minibuffer("ho")
    Snd.catch do
      cfd = choose_fd.call
      safe_make_selection(1000, 2000, cfd)
      src_selection(0.5)
      undo_edit(1, cfd)
      cfd = choose_fd.call
      safe_make_selection(1000, 2000, cfd)
      src_selection(-1.5)
      undo_edit(1, cfd)
      cfd = choose_fd.call
      safe_make_selection(1000, 2000, cfd)
      scale_selection_by(0.5)
      undo_edit(1, cfd)
      cfd = choose_fd.call
      safe_make_selection(1000, 2000, cfd)
      env_selection([0, 0, 1, 1, 2, 0])
      undo_edit(1, cfd)
      cfd = choose_fd.call
      safe_make_selection(1000, 2000, cfd)
      scale_selection_to(0.5)
      reverse_selection
      undo_edit(2, cfd)
      Snd.regions.length > 2 and forget_region(regions[2])
    end
    open_files.apply(:revert_sound)
    Snd.catch do
      cfd = open_files.first
      set_sync(1, cfd)
      open_files.length > 1 and set_sync(1, open_files[1])
      safe_make_selection(1000, 2000, cfd)
      src_selection(0.5)
      undo_edit(1, cfd)
      safe_make_selection(1000, 2000, cfd)
      src_selection(-1.5)
      undo_edit(1, cfd)
      safe_make_selection(1000, 2000, cfd)
      env_selection([0, 0, 1, 1, 2, 0])
      undo_edit(1, cfd)
      safe_make_selection(1000, 2000, cfd)
      reverse_selection
      undo_edit(1, cfd)
      safe_make_selection(1000, 2000, cfd)
      filter_selection([0, 0, 0.1, 1, 1, 0], 40)
      undo_edit(1, cfd)
      safe_make_selection(1000, 2000, cfd)
      convolve_selection_with("oboe.snd")
      undo_edit(1, cfd)
      safe_make_selection(1000, 2000, cfd)
      smooth_selection
      undo_edit(1, cfd)
      safe_make_selection(1000, 2000, cfd)
      scale_selection_by(0.5)
      undo_edit(1, cfd)
      scale_selection_to(0.5)
      reverse_selection
      undo_edit(2, cfd)
      src_selection([0, 0.5, 1, 1])
      undo_edit
      revert_sound(cfd)
      open_files.length > 1 and revert_sound(open_files[1])
    end
    #
    if frames > 1
      make_region(0, frames)
      convolve_selection_with("fyow.snd", 0.5)
      frames < 100000 and play(selected_sound, :wait, true)
    end
    convolve_with("fyow.snd", 0.25)
    insert_sound("oboe.snd")
    $graph_hook.reset_hook!
    $after_transform_hook.reset_hook!
    open_files.apply(:revert_sound)
    # 
    ind = choose_fd.call
    select_sound(ind)
    [[lambda { |beg| insert_sound("2a.snd", beg) },   lambda { |beg| insert_sound("4a.snd", beg) }],
     [lambda { |beg| reverse_sound },                 lambda { |beg| reverse_sound }],
     [lambda { |beg| convolve_with("2a.snd", 0.5) },  lambda { |beg| src_sound(2.0) }],
     [lambda { |beg| env_sound([0, 0, 1, 1, 2, 0]) }, lambda { |beg| env_sound([0, 0, 1, 1]) }],
     [lambda { |beg| smooth_sound },                  lambda { |beg| insert_silence(beg, 100) }]
    ].each do |func, func1|
      pad_channel(0, 100, ind, 0)
      func.call(0)
      pad_channel(0, 100, ind, 0)
      func1.call(0)
      revert_sound(ind)
      if channels(ind) > 1
        pad_channel(0, 100, ind, 1)
        func.call(0)
        pad_channel(0, 100, ind, 1)
        func1.call(0)
        revert_sound(ind)
      end
      delete_samples(0, 1000, ind, 0)
      func.call(2 * frames(ind, 0))
      delete_samples(0, 1000, ind, 0)
      func1.call(2 * frames(ind, 0))
      revert_sound(ind)
      if channels(ind) > 1
        delete_samples(0, 1000, ind, 1)
        func.call(2 * frames(ind, 1))
        delete_samples(0, 1000, ind, 1)
        func1.call(2 * frames(ind, 1))
        revert_sound(ind)
      end
    end
    #
    ind = open_sound("z.snd")
    restore_controls
    if frames(ind).nonzero?
      snd_display("frames z.snd: %s?", frames(ind))
    end
    if samples != false
      snd_display("samples of empty file (z): %s?", samples)
    end
    if channel2vct != false
      snd_display("channel2vct of empty file (z): %s?", channel2vct)
    end
    if fneq(maxamp(ind), 0.0)
      snd_display("maxamp z.snd: %s?", maxamp(ind))
    end
    if fneq(sample(100, ind), 0.0)
      snd_display("sample 100 z.snd: %s?", sample(100, ind))
    end
    scale_by(2.0)
    if (res = edit_position(ind, 0)).nonzero?
      snd_display("scale z: %s?", res)
    end
    env_sound([0, 0, 1, 1])
    if (res = edit_position(ind, 0)).nonzero?
      snd_display("env_sound z: %s?", res)
    end
    smooth_sound
    if (res = edit_position(ind, 0)).nonzero?
      snd_display("smooth_sound z: %s?", res)
    end
    reverse_sound
    if (res = edit_position(ind, 0)).nonzero?
      snd_display("reverse_sound z: %s?", res)
    end
    src_sound(2.0)
    if (res = edit_position(ind, 0)).nonzero?
      snd_display("src_sound z: %s?", res)
    end
    insert_sound("z.snd")
    if (res = edit_position(ind, 0)).nonzero?
      snd_display("insert_sound z: %s?", res)
    end
    mix("z.snd")
    if (res = edit_position(ind, 0)).nonzero?
      snd_display("mix z: %s?", res)
    end
    filter_sound(make_one_zero(:a0, 2.0, :a1, 0.0))
    if (res = edit_position(ind, 0)).nonzero?
      snd_display("filter_sound z: %s?", res)
    end
    if fneq(res = mus_sound_duration("z.snd"), 0.0)
      snd_display("mus_sound_duration z.snd: %s?", res)
    end
    Snd.catch(:io_error) do convolve_with("z.snd", 1.0) end
    if (res = edit_position(ind, 0)).nonzero?
      snd_display("convolve_with z: %s?", res)
    end
    if (res = Snd.catch do find_channel(lambda do |y| y > 0.1 end) end).first != :no_such_sample
      snd_display("find z: %s", res.inspect)
    end
    if (res = Snd.catch do count_matches(lambda do |y| y > 0.1 end) end).first != :no_such_sample
      snd_display("count_matches z: %s", res.inspect)
    end
    reader = make_sampler(0)
    val = next_sample(reader)
    if fneq(val, 0.0)
      snd_display("sampler z.snd: %s?", val)
    end
    unless string?(reader.to_s)
      snd_display("z.snd reader: %s?", reader.to_s)
    end
    if (res = cursor_position) != [0, 0]
      snd_display("cursor_position z: %s?", res)
    end
    if (res = cursor).nonzero?
      snd_display("cursor z: %ws?", res)
    end
    outer = make_player(ind, 0)
    pl = make_player(ind, 0)
    add_player(pl)
    start_playing(1, 22050, false)
    revert_sound(ind)
    set_transform_graph?(true, ind, 0)
    $lisp_graph_hook.add_hook!("display_energy") do |snd, chn| display_energy(snd, chn) end
    set_x_bounds([0.0, 0.01])
    set_sample(0, 0.5)
    set_x_bounds([0.0, 0.001])
    close_sound(ind)
    if (res = Snd.catch do add_player(outer) end).first != :no_such_player
      snd_display("dangling player: %s", res.inspect)
    end
    if res = channel_amp_envs("z.snd", 0, 100)
      snd_display("channel_amp_envs of empty file: %s?", res)
    end
    #
    zz = view_sound("z.snd")
    select_sound(zz)
    mix("4.aiff").car
    add_mark(0)
    add_mark(1200)
    delete_marks
    revert_sound(zz)
    editctr = edit_position(zz)
    if (res = edit_position(zz)).nonzero?
      snd_display("revert_sound edit_position: %s?", res)
    end
    s8_snd = (File.exists?("s8.snd") ? "s8.snd" : "oboe.snd")
    as_one_edit_rb do
      mix(s8_snd, 24000)
      reg = select_all
      if selection?
        filter_selection([0, 0, 0.2, 1, 0.5, 0, 1, 0], 40)
        delete_selection
        mix_region(reg)
      end
    end
    if (res = edit_position(zz)) != 1
      snd_display("as_one_edit mix zz: %s -> %s?", editctr, res)
    end
    close_sound(zz)
    s8 = view_sound(s8_snd)
    select_sound(s8)
    if channels(s8) == 8
      select_channel(5)
      if (not number?(selected_channel)) or selected_channel != 5
        snd_display("select_channel: %s?", select_channel)
      end
    end
    editctr = edit_position
    as_one_edit_rb do
      reg = select_all
      delete_selection
      mix("4.aiff")
      set_sync(1)
      mix("oboe.snd", 60000)
      scale_by(0.1)
      set_sync(1)
      if channels(s8) > 3
        select_channel(3)
      end
      if region?(reg)
        insert_region(reg, 80000)
      end
    end
    if (res = edit_position) != editctr + 1
      snd_display("as_one_edit s8: %s -> %s?", editctr, res)
    end
    revert_sound(s8)
    close_sound(s8)
    # 
    cfd = choose_fd.call
    if channels(cfd) > 1
      uval = random(3)
      set_channel_style(uval, cfd)
      if (res = channel_style(cfd)) != uval
        snd_display("channel_style: %s %s?", uval, res)
      end
    end
    src_sound(2.5, 1.0, cfd)
    src_sound(-2.5, 1.0, cfd)
    src_sound(0.5, 1.0, cfd)
    revert_sound(cfd)
    src_sound(-0.5, 1.0, cfd)
    src_sound([0, 0.5, 1, 1.5], 1.0, cfd)
    if frames(cfd) > 0
      src_sound(make_env(:envelope, [0, 0.5, 1, 1.5], :length, frames(cfd)), 1.0, cfd)
    end
    revert_sound(cfd)
    filter_sound([0, 1, 0.2, 0, 0.5, 1, 1, 0], 20, cfd)
    filter_sound([0, 0, 0.1, 0, 0.11, 1, 0.12, 0, 1, 0], 2048, cfd)
    env_sound([0, 0, 0.5, 1, 1, 0], 0, frames(cfd), 1.0, cfd)
    insert_sample(1200, 0.1, cfd)
    if fneq(res = sample(1200, cfd), 0.1)
      snd_display("insert_sample(looped): %s?", res)
    end
    revert_sound(cfd)
    #
    cfd = open_sound("obtest.snd")
    select_sound(cfd)
    cfd2 = open_sound("pistol.snd")
    select_sound(cfd2)
    set_amp_control(0.5)
    set_speed_control(2.0)
    test_panel(:speed_control)
    apply_controls
    frames < 100000 and play(selected_sound, :wait, true)
    if fneq(res1 = reverb_control_decay(cfd), res2 = reverb_control_decay)
      snd_display("reverb_control_decay local: %s, global: %s?", res1, res2)
    end
    set_reverb_control?(true)
    set_reverb_control_scale(0.2)
    test_panel(:reverb_control_scale)
    test_panel(:reverb_control_length)
    test_panel(:reverb_control_lowpass)
    test_panel(:reverb_control_feedback)
    apply_controls
    frames < 100000 and play(selected_sound, :wait, true)
    set_contrast_control?(true)
    set_contrast_control(0.5)
    test_panel(:contrast_control)
    test_panel(:contrast_control_amp)
    apply_controls
    frames < 100000 and play(selected_sound, :wait, true)
    set_expand_control?(true)
    set_expand_control(2.5)
    test_panel(:expand_control)
    test_panel(:expand_control_length)
    test_panel(:expand_control_hop)
    test_panel(:expand_control_ramp)
    apply_controls
    frames < 100000 and play(selected_sound, :wait, true)
    set_filter_control?(true)
    set_filter_control_order(40)
    test_panel(:filter_control_order)
    set_filter_control_envelope([0, 0, 0.1, 1, 0.2, 0, 1, 0])
    filter_control_envelope
    apply_controls
    frames < 100000 and play(selected_sound, :wait, true)
    set_amp_control(1.5)
    test_panel(:amp_control)
    apply_controls
    frames < 100000 and play(selected_sound, :wait, true)
    swap_channels(cfd, 0, cfd2, 0)
    set_amp_control(0.75, true)
    test_panel(:amp_control)
    if (res = amp_control(cfd2) - 0.75).abs > 0.05
      snd_display("set_amp_control 0.75, true: %s?", res)
    end
    set_contrast_control_amp(0.75, true)
    if fneq(res = contrast_control_amp(cfd2), 0.75)
      snd_display("set_contrast_control_amp 0.75, true: %s?", res)
    end
    set_contrast_control_bounds([2.0, 3.0], cfd2)
    unless vequal(res = contrast_control_bounds(cfd2), [2.0, 3.0])
      snd_display("set_contrast_control_bound (cfd2): %s?", res)
    end
    set_expand_control_length(0.025, true)
    if fneq(res = expand_control_length(cfd2), 0.025)
      snd_display("set_expand_control_length 0.025, true: %s?", res)
    end
    set_expand_control_hop(0.025, true)
    if fneq(res = expand_control_hop(cfd2), 0.025)
      snd_display("set_expand_control_hop 0.025, true: %s?", res)
    end
    set_expand_control_jitter(0.025, true)
    if fneq(res = expand_control_jitter(cfd2), 0.025)
      snd_display("set_expand_control_jitter 0.025, true: %s?", res)
    end
    set_expand_control_ramp(0.025, true)
    if fneq(res = expand_control_ramp(cfd2), 0.025)
      snd_display("set_expand_control_ramp 0.025, true: %s?", res)
    end
    clone = clone_sound_as("/tmp/cloned.snd", cfd2)
    if frames(cfd2) != frames(clone)
      snd_display("clone frames: %s %s?", frames(cfd2), frames(clone))
    end
    close_sound(clone)
    delete_file("/tmp/cloned.snd")
    mus_sound_forget("/tmp/cloned.snd")
    close_sound(cfd2)
    close_sound(cfd)
    edit_hook.add_hook!("snd-test") do | | false end
    editctr = edit_position
    as_one_edit_rb do
      set_sample(200, 0.2)
      set_sample(300, 0.3)
    end
    if (res = edit_position) != editctr + 1
      snd_display("as_one_edit: %s -> %s?", editctr, res)
    end
    as_one_edit_rb do false end
    if (res = edit_position) != editctr + 1
      snd_display("as_one_edit nil: %s -> %s?", editctr, res)
    end
    delete_sample(250)
    undo_hook.add_hook!("snd-test") do | | false end
    undo_edit
    delete_sample(250)
    undo_edit
    as_one_edit_rb do
      set_sample(20, 0.2)
      set_sample(30, 0.3)
    end
    undo_edit(1)
    as_one_edit_rb do
      set_sample(2, 0.2)
      as_one_edit_rb do set_sample(3, 0.3) end
    end
    undo_edit(2)
    undo_hook.reset_hook!
    edit_hook.reset_hook!
    $snd_error_hook.reset_hook!
    $snd_warning_hook.reset_hook!
    $snd_warning_hook.add_hook!("snd-test") do |msg|
      if msg != "hiho"
        snd_display("$snd_warning_hook: %s?", msg)
      end
      true
    end
    snd_warning("hiho")
    $snd_error_hook.reset_hook!
    $snd_warning_hook.reset_hook!
    if proc? $my_snd_error_hook
      $snd_error_hook.add_hook!("sndtestrc", &$my_snd_error_hook)
    end
    if proc? $my_mus_error_hook
      $mus_error_hook.add_hook!("sndtestrc", &$my_mus_error_hook)
    end
    $name_click_hook.add_hook!("snd-test") do |n| true end
    redo_edit(1)
    $name_click_hook.reset_hook!
    set_transform_graph?(true)
    test_channel(:transform_graph?)
    test_channel(:time_graph?)
    test_channel(:lisp_graph?)
    test_channel(:frames)
    test_channel(:cursor)
    test_channel(:cursor_size)
    test_channel(:cursor_style)
    test_channel(:tracking_cursor_style)
    test_channel(:left_sample)
    test_channel(:right_sample)
    test_channel(:squelch_update)
    test_channel(:x_zoom_slider)
    test_channel(:y_zoom_slider)
    test_channel(:x_position_slider)
    test_channel(:y_position_slider)
    test_channel(:edit_position)
    test_channel(:maxamp)
    test_channel(:edit_hook)
    test_channel(:after_edit_hook)
    test_channel(:undo_hook)
    func = lambda do |len, fd|
      steps = len / 16
      step = 1.0 / len
      v = Vct.new(len, 0.0)
      v.length.times do |i|
        val = next_sample(fd).abs
        bin = (val * 16.0).round
        if bin < steps
          steps.times do |j| v[j + bin] += step end
        end
      end
      v
    end
    set_transform_type(add_transform("histogram", "bins", 0.0, 1.0, func))
    set_x_bounds([0.1, 0.2])
    set_transform_type($fourier_transform)
    set_x_bounds([0.1, 0.2])
    $lisp_graph_hook.add_hook!("display_energy") do |snd, chn| display_energy(snd, chn) end
    $graph_hook.reset_hook!
    if channels() == 2
      $graph_hook.add_hook!("correlate") do |snd, chn, y0, y1|
        display_correlate(snd, chn, y0, y1)
      end
      set_x_bounds([0.1, 0.12])
      set_x_bounds([0.1, 0.2])
      $graph_hook.remove_hook!("correlate")
    end
    set_lisp_graph?(false)
    sum_of_squares = 0.0
    buffer = Vct.new(128)
    position = 0
    current_sample = 0
    chan_samples = frames
    map_chan_rb do |y|
      old_y = buffer[position]
      sum_of_squares = (sum_of_squares + y * y) - old_y * old_y
      buffer[position] = y
      position += 1
      if position == 128
        position = 0
      end
      current_sample += 1
      if sum_of_squares > 0.01
        if current_sample == chan_samples
          Vct.new(128) do |i|
            final_y = buffer[position]
            val = sum_of_squares > 0.01 ? final_y : 0.0
            sum_of_squares = sum_of_squares - final_y * final_y
            position += 1
            if position == 128
              position = 0
            end
            val
          end
        else
          old_y
        end
      else
        false
      end
    end
    # 
    maxval1 = maxamp + 0.01
    unless every_sample? do |y| y < maxval1 end
      res = scan_chan(lambda do |y| y >= maxval1 end)
      snd_display("%s, every_sample?: %s %s [%s: %s]?",
                  short_file_name, maxval1, res, cursor, sample(cursor))
      edit_position.times do |i|
        snd_display("%s: %s %s", i, maxamp(false, 0, i), edit_fragment(i))
      end
    end
    map_chan(echo(0.5, 0.75), 0, 60000)
    $after_transform_hook.reset_hook!
    $lisp_graph_hook.reset_hook!
    $lisp_graph_hook.add_hook!("snd-test") do |snd, chn| graph(vct(0, 1, 2)) end
    Snd.sounds.each do |snd| set_sync((random(3)), snd) end
    $graph_hook.add_hook!("superimpose_ffts") do |snd, chn, y0, y1|
      superimpose_ffts(snd, chn, y0, y1)
    end
    10.times do |i|
      Snd.sounds.each do |snd|
        if frames(snd) > 0
          dur = (frames(snd) / srate(snd).to_f).floor.to_f
          start = [0.0, [dur - 0.1, random(dur)].min].max
          if dur > 0.0
            set_x_bounds([start, [start + 0.1, dur].min], snd, 0)
          end
        end
      end
    end
    $graph_hook.reset_hook!
    $lisp_graph_hook.reset_hook!
    #
    # new variable settings
    #
    [[:amp_control, true, 0.1, 1.0],
     [:auto_update, false, false, true],
     [:channel_style, false, 0, 2],
     [:color_cutoff, false, 0.0,0.2],
     [:color_inverted, false, false, true],
     [:color_scale, false, 0.1, 1000.0],
     [:contrast_control, true, 0.0, 1.0],
     [:contrast_control_amp, true, 0.0, 1.0],
     [:contrast_control?, true, false, true],
     [:auto_update_interval, false, 60.0, 120.0],
     [:cursor_update_interval, false, 0.05, 0.1],
     [:cursor_location_offset, false, 0, 1024],
     [:with_tracking_cursor, false, false, true],
     [:cursor_size, false, 15, 25],
     [:cursor_style, false, Cursor_cross, Cursor_line],
     [:tracking_cursor_style, false, Cursor_line, Cursor_cross],
     [:clipping, false, false, true],
     [:dot_size, false, 1, 10],
     [:enved_base, false, 0.01, 100.0],
     [:enved_clip?, false, false, true],
     [:enved_in_dB, false, false, true],
     [:enved_style, false, Envelope_linear, Envelope_exponential],
     [:enved_power, false, 3.0, 3.5],
     [:enved_target, false, 0, 2],
     [:enved_wave?, false, false, true],
     [:expand_control, true, 0.1, 5.0],
     [:expand_control_hop, true, 0.01, 0.5],
     [:expand_control_jitter, true, 0.01, 0.5],
     [:expand_control_length, true, 0.1, 0.25],
     [:expand_control_ramp, true, 0.1, 0.4],
     [:expand_control?, true, false, true],
     [:fft_window_alpha, false, 0.0, 1.0],
     [:fft_window_beta, false, 0.0, 1.0],
     [:fft_log_frequency, false, false, true],
     [:fft_log_magnitude, false, false, true],
     [:fft_with_phases, false, false, true],
     [:transform_size, false, 16, 128],
     [:transform_graph_type, false, Graph_once, Graph_as_spectrogram],
     [:fft_window, false, 0, Dolph_chebyshev_window],
     [:transform_graph?, true, false, true],
     [:filter_control_in_dB, true, false, true],
     [:filter_control_in_hz, true, false, true],
     [:filter_control_order, true, 2, 400],
     [:filter_control?, true, false, true],
     # [:graph_cursor, false, 0, 35],
     [:time_graph_style, false, 0, 4],
     [:lisp_graph_style, false, 0, 4],
     [:transform_graph_style, false, 0, 4],
     [:graphs_horizontal, false, false, true],
     [:max_transform_peaks, false, 1, 100],
     [:max_regions, false, 1, 32],
     [:min_dB, false, -120.0, -30.0],
     [:log_freq_start, false, 50.0, 5.0],
     [:selection_creates_region, false, false, true],
     [:transform_normalization, false, Dont_normalize, Normalize_globally],
     [:view_files_sort, false, 0, 3],
     [:play_arrow_size, false, 2, 32],
     [:print_length, false, 2, 32],
     [:region_graph_style, false, Graph_lines, Graph_lollipops],
     [:reverb_control_decay, false, 0.0, 2.0],
     [:reverb_control_feedback, true, 1.0, 1.1],
     [:reverb_control_length, true, 1.0, 2.0],
     [:reverb_control_lowpass, true, 0.2, 0.99],
     [:reverb_control_scale, true, 0.0, 0.2],
     [:reverb_control?, true, false, true],
     [:show_axes, false, 0, 2],
     [:show_transform_peaks, false, false, true],
     [:show_indices, false, false, true],
     [:show_marks, false, false, true],
     [:show_mix_waveforms, true, false, true],
     [:show_selection_transform, false, false, true],
     [:show_y_zero, false, false, true],
     [:show_grid, false, false, true],
     [:grid_density, true, 0.1, 4.0],
     [:show_sonogram_cursor, false, false, true],
     [:sinc_width, false, 4, 100],
     [:spectrum_end, false, 0.5, 0.8],
     [:spectro_hop, false, 2, 20],
     [:spectrum_start, false, 0.0, 0.1],
     [:spectro_x_angle, false, 0.0, 90.0],
     [:spectro_x_scale, false, 0.1, 2.0],
     [:spectro_y_angle, false, 0.0, 90.0],
     [:spectro_y_scale, false, 0.1, 2.0],
     [:spectro_z_angle, false, 0.0, 359.0],
     [:spectro_z_scale, false, 0.1, 0.2],
     [:speed_control, true, 0.1, 5.0],
     [:speed_control_style, false, 0, 2],
     [:speed_control_tones, false, 2, 100],
     [:sync, true, 0, 5],
     [:sync_style, false, 0, 3],
     [:with_verbose_cursor, false, false, true],
     [:wavelet_type, false, 0, 10],
     [:time_graph?, true, false, true],
     [:x_axis_style, false, 0, 2],
     [:beats_per_minute, false, 60.0, 120.0],
     [:beats_per_measure, false, 4, 120],
     [:zero_pad, false, 0, 2],
     [:zoom_focus_style, false, 0, 3]].each do |func, index_p, minval, maxval|
      index = index_p ? choose_fd.call : false
      if index
        if minval == false
          set_snd_func(func, true, index)
        else
          val = minval + (maxval - minval) * random(1.0)
          set_snd_func(func, (integer?(minval) and val.floor or val), index)
        end
      else
        if minval == false
          set_snd_func(func, true)
        else
          val = minval + (maxval - minval) * random(1.0)
          set_snd_func(func, (integer?(minval) and val.floor or val))
        end
      end
    end
    # save_options("hiho.rb")
    if transform_type != $fourier_transform
      set_transform_graph?(false, true, true)
      set_transform_size([transform_size, 128].min)
    end
    open_files.apply(:close_sound)
    set_sync_style(Sync_none)
    set_mus_rand_seed(1234)
    if mus_rand_seed != 1234
      snd_display("mus_rand_seed: %s (1234)?", mus_rand_seed)
    end
    val = mus_random(1.0)
    val1 = mus_random(1.0)
    if fneq(val, -0.7828) or fneq(val1, -0.8804)
      snd_display("mus_random: %s %s?", val, val1)
    end
    if mus_rand_seed == 1234
      snd_display("mus_rand_seed: %s?", mus_rand_seed)
    end
    set_mus_rand_seed(1234)
    val = mus_random(1.0)
    val1 = mus_random(1.0)
    if fneq(val, -0.7828) or fneq(val1, -0.8804)
      snd_display("mus_random repeated: %s %s?", val, val1)
    end
    $after_open_hook.reset_hook!
    $close_hook.reset_hook!
    $open_hook.reset_hook!
  end
end

# ---------------- test 15: chan-local vars ----------------

def prefix_it(n, id)
  key(key_to_int(?u), 0, id)
  n.to_s.each_byte do |c| key(c, 0, id) end
end

def prefix_uit(n, id)
  n.to_s.each_byte do |c| key(c, 0, id) end
end

def test_history_channel(func, new_val, snd1, snd2, snd3)
  test_equal = lambda do |nv, new_val|
    if float?(nv)
      (not fneq(nv, new_val))
    else
      nv == new_val
    end
  end
  chan_equal_p = lambda do |vals, new_val|
    if vals.null?
      true
    elsif list_p(vals)
      (not vals.detect do |val| val != new_val end)
    else
      test_equal.call(vals, new_val)
    end
  end
  test_channel(func)
  old_value = snd_func(func)
  old_chan_value = snd_func(func, snd1, 0)
  set_snd_func(func, new_val, snd1, 0)
  unless test_equal.call(nv = snd_func(func, snd1, 0), new_val)
    snd_display_prev_caller("%s: set_%s[1]: %s %s?", get_func_name, func, new_val, nv)
  end
  set_snd_func(func, new_val, snd3, 2)
  unless test_equal.call(nv = snd_func(func, snd3, 2), new_val)
    snd_display_prev_caller("%s: set_%s[2]: %s %s?", get_func_name, func, new_val, nv)
  end
  unless test_equal.call(old_value, new_val)
    if test_equal.call(nv = snd_func(func, snd3, 1), new_val)
      snd_display_prev_caller("%s: set_%s[3]: %s %s?", get_func_name, func, new_val, nv)
    end
  end
  set_snd_func(func, new_val, snd2, true)
  unless test_equal.call(nv = snd_func(func, snd2, 1), new_val)
    snd_display_prev_caller("%s: set_%s[4]: %s %s?", get_func_name, func, new_val, nv)
  end
  set_snd_func(func, new_val)
  unless chan_equal_p.call(nv = snd_func(func, true, true).flatten, new_val)
    snd_display_prev_caller("%s: set_%s[5]: %s %s?", get_func_name, func, new_val, nv)
  end
  set_snd_func(func, old_value)
end

def freq_peak(beg, ind, size)
  interpolated_peak_offset = lambda do |la, ca, ra|
    pk = 0.001 + [la, ca, ra].max
    logla = log([la, 0.0000001].max / pk) / log(10)
    logca = log([ca, 0.0000001].max / pk) / log(10)
    logra = log([ra, 0.0000001].max / pk) / log(10)
    0.5 * (logla - logra) / ((logla + logra) - 2 * logca)
  end
  data = channel2vct(beg, size, ind, 0)
  spectr = snd_spectrum(data, Blackman2_window, size)
  peak0 = 0.0
  pk0loc = 0
  (size / 2).times do |i|
    if spectr[i] > peak0
      peak0 = spectr[i]
      pk0loc = i
    end
  end
  [((pk0loc + (pk0loc > 0 ? interpolated_peak_offset.call(spectr[pk0loc - 1],
                                                          spectr[pk0loc],
                                                          spectr[pk0loc + 1]) :
                       0.0)) * srate) / size,
   peak0]
end

def smoother(y0, y1, num)
  angle = y1 > y0 ? PI : 0.0
  off = 0.5 * (y0 + y1)
  incr = PI / num
  scale = 0.5 * (y1 - y0).abs
  Vct.new(num + 1) do |i| off + scale * cos(angle + i * incr) end
end

def test_selection(ind, beg, len, scaler)
  set_selection_member?(true, ind, 0)
  set_selection_position(beg)
  set_selection_frames(len)
  scale_selection_by(scaler)
  diff = 0.0
  pos = edit_position(ind, 0)
  old_reader = make_sampler(beg, ind, 0, 1, pos - 1)
  new_reader = make_sampler(beg, ind, 0, 1, pos)
  len.times do |i|
    ov = scaler * old_reader.call
    nv = next_sample(new_reader)
    val = (ov - nv).abs
    diff += val
  end
  if diff > 0.0
    snd_display_prev_caller("diff (%s %s): %s?", beg, len, diff)
  end
  diff = 0.0
  100.times do |i|
    ov = next_sample(old_reader)
    nv = next_sample(new_reader)
    val = (ov - nv).abs
    diff += val
  end
  if diff > 0.0
    snd_display_prev_caller("zdiff (%s %s): %s?", beg, len, diff)
  end
  free_sampler(old_reader)
  free_sampler(new_reader)
end

def test_selection_to(ind, beg, len, maxval)
  set_selection_member?(true, ind, 0)
  set_selection_position(beg)
  set_selection_frames(len)
  scale_selection_to(maxval)
  newmax = 0.0
  new_reader = make_sampler(beg, ind, 0)
  len.times do
    nv = next_sample(new_reader).abs
    if nv > newmax
      newmax = nv
    end
  end
  if fneq(newmax, maxval)
    snd_display_prev_caller("%s (%s %s) %s: %s?", get_func_name, beg, len, maxval, newmax)
  end
  free_sampler(new_reader)
end

def play_with_amps_1(snd, *amps)
  channels(snd).times do |chn|
    player = make_player(snd, chn)
    unless player?(player)
      snd_display("player? %s -> false?", player)
    end
    unless players.member?(player)
      snd_display("player: %s, but players: %s?", player, players)
    end
    if (res = player_home(player)) != [snd, chn]
      snd_display("player_home %s %s?", res, [snd, chn])
    end
    set_amp_control(amps[chn], player)
    set_speed_control(0.5, player)
    set_expand_control?(true, player)
    set_expand_control(2.0, player)
    set_contrast_control?(true, player)
    set_contrast_control(1.0, player)
    set_reverb_control?(true, player)
    set_reverb_control_scale(0.02, player)
  end
  start_playing(channels(snd), srate(snd), false)
end

def test_15_00
  snds = match_sound_files do |file|
    File.exists?(file) and # for $tests > 1
      mus_sound_header_type(file) != Mus_raw and
      mus_sound_chans(file) == 1
  end
  if snds.length > 0
    obi = open_sound(snds.first)
    if all_chans != [[obi], [0]]
      snd_display("all_chans (1): %s?", all_chans)
    end
    snds1 = match_sound_files do |file|
      File.exists?(file) and # for $tests > 1
        mus_sound_chans(file) == 2
    end
    if snds1.length > 0
      s2i = open_sound(snds1.first)
      if all_chans != [[obi, s2i, s2i], [0, 0, 1]] and all_chans != [[s2i, s2i, obi], [0, 1, 0]]
        snd_display("all_chans (2): %s?", all_chans)
      end
      if finfo("oboe.snd") != "oboe.snd: chans: 1, srate: 22050, Sun/Next, big endian short (16 bits), len: 2.305"
        snd_display("finfo: %s?", finfo("oboe.snd"))
      end
      close_sound(s2i)
    else
      snd_display("No sound file found for s2i: %s", snds1)
    end
    close_sound(obi)
  else
    snd_display("No sound file found obi: %s", snds)
  end
  if all_chans != [[], []]
    snd_display("all_chans (3): %s?", all_chans)
  end
  obi = open_sound("oboe.snd")
  set_cursor(1000, obi)
  if locate_zero(0.001) != 1050
    snd_display("locate_zero: %s?", locate_zero(0.001))
  end
  $graph_hook.add_hook!("auto_dot") do |snd, chn, y0, y1| auto_dot(snd, chn, y0, y1) end
  $graph_hook.add_hook!("superimpose_ffts") do |snd, chn, y0, y1|
    superimpose_ffts(snd, chn, y0, y1)
  end
  set_transform_graph?(true, obi, 0)
  update_graphs
  # 
  snds = match_sound_files do |file|
    File.exists?(file) and # for $tests > 1
      mus_sound_chans(file) == 2
  end
  if snds.length > 0
    s2i = open_sound(snds.first)
    if channels(s2i) != 2
      snd_display("match 2 got %s with %s chans", short_file_name(s2i), channels(s2i))
    end
    update_graphs
    $graph_hook.remove_hook!("auto_dot")
    $graph_hook.remove_hook!("superimpose_ffts")
    set_transform_graph?(false, obi, 0)
    select_sound(obi)
    m1 = add_mark(100, obi, 0)
    first_mark_in_window_at_left
    if (res = left_sample(obi, 0) - 100).abs > 1
      snd_display("first_mark_in_window_at_left: %s %s?", res, mark_sample(m1))
    end
    delete_mark(m1)
    close_sound(s2i)
  else
    snd_display("No sound file found: %s", snds)
  end
  safe_make_selection(1000, 2000, obi)
  unless selection?
    make_selection(1000, 2000, obi, 0)
  end
  delete_selection_and_smooth
  if (res = edit_fragment(0, obi, 0)) != ["", "init", 0, 50828]
    snd_display("edit_fragment (0): %s?", res)
  end
  if (res = edit_fragment(1, obi, 0)) != ["delete_samples(1000, 1001", "delete", 1000, 1001]
    snd_display("edit_fragment (1): %s?", res)
  end
  if (res = edit_fragment(2, obi, 0)) != ["delete_selection_and_smooth(", "set", 968, 64]
    snd_display("edit_fragment (2): %s?", res)
  end
  samp100 = sample(1100, obi, 0)
  select_sound(obi)
  safe_make_selection(1000, 2000, obi)
  unless selection?
    make_selection(1000, 2000, obi, 0)
  end
  eval_over_selection do |val| 2.0 * val end
  nsamp100 = sample(1100, obi, 0)
  if fneq(2.0 * samp100, nsamp100)
    snd_display("eval_over_selection: %s %s [%s %s]?",
                samp100, nsamp100, selection_position, selection_frames)
  end
  m2 = add_mark(1000, obi, 0)
  m3 = add_mark(2000, obi, 0)
  unless (res = marks(obi, 0)).eql?([m2, m3])
    snd_display("add_mark: %s %s?", res, [m2, m3])
  end
  set_left_sample(950, obi, 0)
  eval_between_marks do |val| 2.0 * val end
  msamp100 = sample(1100, obi, 0)
  if fneq(2.0 * nsamp100, msamp100)
    snd_display("eval_between_marks: %s %s?", nsamp100, msamp100)
  end
  revert_sound(obi)
  #
  maxa = maxamp(obi)
  normalized_mix("pistol.snd", 1000, 0, obi, 0)
  nmaxa = maxamp(obi)
  if fneq(maxa, nmaxa)
    snd_display("normalized_mix: %s %s?", maxa, nmaxa)
  end
  revert_sound(obi)
  snds = match_sound_files do |file|
    File.exists?(file) and # for $tests > 1
      mus_sound_chans(file) == 2 and
      mus_sound_frames(file) > 1000
  end
  if snds.length > 0
    s2i = open_sound(snds.first)
    if channels(s2i) != 2
      snd_display("match_sound_files: 2+1000 got %s with %s chans?",
                  short_file_name(s2i), channels(s2i))
    end
    o1 = sample(1000, obi, 0)
    s1 = sample(1000, s2i, 0)
    s2 = sample(1000, s2i, 1)
    do_all_chans("double all samples") do |val| (val ? (2.0 * val) : false) end
    o11 = sample(1000, obi, 0)
    s11 = sample(1000, s2i, 0)
    s21 = sample(1000, s2i, 1)
    if fneq(2.0 * o1, o11) or fneq(2.0 * s1, s11) or fneq(2.0 * s2, s21)
      snd_display("do_all_chans: %s?", [o1, s1, s2, o11, s11, s21])
    end
    update_graphs
    m1 = maxamp(obi, 0)
    m2 = maxamp(s2i, 0)
    m3 = maxamp(s2i, 1)
    mc = [[obi, 0], [s2i, 0], [s2i, 1]].map do |snd, chn| maxamp(snd, chn) end
    if fneq(m1, mc[0]) or fneq(m2, mc[1]) or fneq(m3, mc[2])
      snd_display("map maxamp: %s %s %s %s?", m1, m2, m3, mc)
    end
    set_sync(1, obi)
    set_sync(1, s2i)
    do_chans("*2") do |val| (val ? (2.0 * val) : false) end
    mc1 = [[obi, 0], [s2i, 0], [s2i, 1]].map do |snd, chn| maxamp(snd, chn) end
    if fneq(2.0 * m1, mc1[0]) or fneq(2.0 * m2, mc1[1]) or fneq(2.0 * m3, mc1[2])
      snd_display("do_chans: %s %s?", mc, mc1)
    end
    set_sync(0, obi)
    set_sync(0, s2i)
    select_sound(s2i)
    do_sound_chans("/2") do |val| (val ? (0.5 * val) : false) end
    mc2 = [[obi, 0], [s2i, 0], [s2i, 1]].map do |snd, chn| maxamp(snd, chn) end
    if fneq(2.0 * m1, mc2[0]) or fneq(m2, mc2[1]) or fneq(m3, mc2[2])
      snd_display("do_sound_chans: %s %s %s?", mc, mc1, mc2)
    end
    if every_sample? do |val| val > 0.5 end
      snd_display("every_sample? (0)?")
    end
    unless every_sample? do |val| val < 5.0 end
      snd_display("every_sample? (1)?")
    end
    select_sound(obi)
    bins = sort_samples(32)
    snd_test_neq(bins[1], 4504, "sort_samples")
    revert_sound(s2i)
    revert_sound(obi)
    set_sync(3, obi)
    set_sync(3, s2i)
    half_way = (0.5 * frames(obi)).floor
    o1 = sample(half_way, obi, 0)
    s1 = sample(half_way, s2i, 0)
    s2 = sample(half_way, s2i, 1)
    place_sound(obi, s2i, [0, 0.5, 1, 0.5])
    s21 = sample(half_way, s2i, 0)
    s22 = sample(half_way, s2i, 1)
    revert_sound(s2i)
    place_sound(obi, s2i, 45.0)
    s31 = sample(half_way, s2i, 0)
    s32 = sample(half_way, s2i, 1)
    if fneq(s1 + 0.5 * o1, s21) or fneq(s2 + 0.5 * o1, s22) or fneq(s21, s31) or fneq(s22, s32)
      snd_display("place_soundL %s?", [o1, s1, s2, s21, s22, s31, s32])
    end
    revert_sound(s2i)
    revert_sound(obi)
    set_sync(0, obi)
    set_sync(0, s2i)
    if fneq(res1 = compand.call(0.0), 0.0) or
        fneq(res2 = compand.call(1.0), 1.0) or
        fneq(res3 = compand.call(0.1), 0.2) or
        fneq(res4 = compand.call(0.99), 0.997) or
        fneq(res5 = compand.call(0.95), 0.984)
      snd_display("compand: %s?", [res1, res2, res3, res4, res5])
    end
    close_sound(obi)
    revert_sound(s2i)
    #
    s1 = sample(1000, s2i, 0)
    s2 = sample(1000, s2i, 1)
    set_sync(4, s2i)
    select_all
    if selection_chans != 2
      snd_display("selection_chans (2): %s?", selection_chans)
      Snd.sounds.each do |snd|
        channels(snd).times do |chn|
          if selection_member?(snd, chn)
            snd_display("%s[%s] at %s?", short_file_name(snd), chn, selection_position(snd, chn))
          end
        end
      end
    end
    if selection_srate != srate(s2i)
      snd_display("selection_srate: %s %s?", selection_srate, srate(s2i))
    end
    if selection_chans == 2
      swap_selection_channels
      if fneq(s1, sample(1000, s2i, 1)) or fneq(s2, sample(1000, s2i, 0))
        snd_display("swap_selection_channels: %s?",
                    [s1, s2, sample(1000, s2i, 1), sample(1000, s2i, 0)])
      end
    end
    revert_sound(s2i)
    close_sound(s2i)
  else
    snd_display("No sound file found s2i: %s", snds)
  end
  #
  obi = open_sound("oboe.snd")
  if $with_test_gui
    select_all
    Snd.regions.apply(:forget_region)
    if regions != nil
      snd_display("no regions: %s?", regions.inspect)
    end
    id = make_region(100, 200, obi, 0)
    if (!(regions.eql?([id])))
      snd_display("make_region regions: %s?", regions.inspect)
    end
    revert_sound(obi)
    oldlen = frames(obi)
    env_sound_interp([0, 0, 1, 1, 2, 0], 2.0, obi, 0)
    newlen = frames(obi)
    if (2 * oldlen - newlen).abs > 3
      snd_display("env_sound_interp: %s %s?", oldlen, newlen)
    end
  end
  # 
  revert_sound(obi)
  granulated_sound_interp([0, 0, 1, 0.1, 2, 1], 1.0, 0.2, [0, 0, 1, 1, 2, 0])
  if edit_position(obi, 0) != 1
    snd_display("granulated_sound_interp no-op 1?")
  end
  if (res = maxamp(obi, 0)) < 0.15
    snd_display("granulated_sound_interp 1 maxamp: %s?", res)
  end
  if (res = frames(obi, 0) - 50828) > 1000
    snd_display("granulated_sound_interp 1 frames: %s?", res)
  end
  revert_sound(obi)
  granulated_sound_interp([0, 0, 1, 1], 2.0)
  if edit_position(obi, 0) != 1
    snd_display("granulated_sound_interp no-op 2?")
  end
  if (res = maxamp(obi, 0)) < 0.15
    snd_display("granulated_sound_interp 2 maxamp: %s?", res)
  end
  if (res = frames(obi, 0) - 101656) > 1000
    snd_display("granulated_sound_interp 2 frames: %s?", res)
  end
  revert_sound(obi)
  granulated_sound_interp([0, 0, 1, 0.1, 2, 1], 1.0, 0.2, [0, 0, 1, 1, 2, 0], 0.02)
  if edit_position(obi, 0) != 1
    snd_display("granulated_sound_interp no-op 3?")
  end
  if (res = maxamp(obi, 0)) < 0.2
    snd_display("granulated_sound_interp 3 maxamp: %s?", res)
  end
  if (res = frames(obi, 0) - 50828) > 1000
    snd_display("granulated_sound_interp 3 frames: %s?", res)
  end
  close_sound(obi)
end

def test_15_01
  ind = new_sound("test.snd", :size, 20)
  offset_channel(1.0)
  env_sound([0, 0, 1, 1])
  osc = make_oscil(:frequency, 1000.0, :initial_phase, PI + HALF_PI)
  reader = make_sound_interp(0, ind, 0)
  len = frames(ind, 0) - 1
  map_channel_rb do |val| sound_interp(reader, len * (0.5 + 0.5 * oscil(osc))) end
  unless vequal(res = channel2vct,
                vct(0.000, 0.020, 0.079, 0.172, 0.291, 0.427, 0.569, 0.706, 0.825, 0.919,
                    0.979, 1.000, 0.981, 0.923, 0.831, 0.712, 0.576, 0.434, 0.298, 0.177))
    snd_display("sound_interp: %s?", res)
  end
  undo_edit
  osc = make_oscil(:frequency, 0.5, :initial_phase, PI + HALF_PI)
  reader = make_sound_interp(0, ind, 0)
  len = frames(ind, 0) - 1
  map_channel(lambda do |val| sound_interp(reader, len * (0.5 + 0.5 * oscil(osc))) end)
  undo_edit
  env_sound_interp([0, 0, 1, 1])
  snd_test_neq(channel2vct(),
               vct(0.000, 0.053, 0.105, 0.158, 0.211, 0.263, 0.316, 0.368, 0.421, 0.474,
                   0.526, 0.579, 0.632, 0.684, 0.737, 0.789, 0.842, 0.895, 0.947, 1.000),
               "env_sound_interp no change")
  undo_edit
  env_sound_interp([0, 0, 1, 0.95, 2, 0], 2.0)
  snd_test_neq(channel2vct(),
               vct(0.000, 0.050, 0.100, 0.150, 0.200, 0.250, 0.300, 0.350, 0.400, 0.450,
                   0.500, 0.550, 0.600, 0.650, 0.700, 0.750, 0.800, 0.850, 0.900, 0.950,
                   1.000, 0.950, 0.900, 0.850, 0.800, 0.750, 0.700, 0.650, 0.600, 0.550,
                   0.500, 0.450, 0.400, 0.350, 0.300, 0.250, 0.200, 0.150, 0.100, 0.050),
               "env_sound_interp twice len and back")
  revert_sound(ind)
  set_sample(10, 0.5)
  remove_clicks
  if fneq(sample(10), 0.0)
    snd_display("remove_clicks: %s?", sample(10))
  end
  undo_edit
  val = scan_channel(search_for_click)
  if val != 11
    snd_display("search_for_click: %s?", val)
  end
  close_sound(ind)
  #
  id = open_sound("oboe.snd")
  fr = frames(id, 0)
  mx = maxamp(id, 0)
  set_frames(25000, id, 0)
  if (res = frames(id, 0)) != 25000
    snd_display("set_frames 25000: %s?", res)
  end
  if (res = edit_position(id, 0)) != 1
    snd_display("set_frames 25000 edit: %s?", res)
  end
  set_frames(75000, id, 0)
  if (res = frames(id, 0)) != 75000
    snd_display("set_frames 75000: %s?", res)
  end
  if (res = edit_position(id, 0)) != 2
    snd_display("set_frames 75000 edit: %s?", res)
  end
  if fneq(res = sample(30000, id, 0), 0.0)
    snd_display("set_frames 75000 zeros: %s?", res)
  end
  set_frames(0, id, 0)
  if (res = frames(id, 0)) != 0
    snd_display("set_frames 0: %s?", res)
  end
  set_frames(100, id, 0)
  if (res = frames(id, 0)) != 100
    snd_display("set_frames 100: %s?", res)
  end
  revert_sound
  if fneq(res = sample(30000, id, 0), -0.0844)
    snd_display("revert from set_frames: %s?", res)
  end
  if (res = frames(id, 0)) != fr
    snd_display("revert set_frames: %s != %s?", res, fr)
  end
  set_maxamp(0.5, id, 0)
  if fneq(res = maxamp(id, 0), 0.5)
    snd_display("set_maxamp: %s?", res)
  end
  if (res = edit_position(id, 0)) != 1
    snd_display("set_maxamp edit: %s?", res)
  end
  set_maxamp(0.1, id, 0)
  if fneq(res = maxamp(id, 0), 0.1)
    snd_display("set_maxamp 0.1: %s?", res)
  end
  if (res = edit_position(id, 0)) != 2
    snd_display("set_maxamp 0.1 edit: %s?", res)
  end
  revert_sound
  if fneq(res = maxamp(id, 0), mx)
    snd_display("maxamp after set: %s %s?", res, mx)
  end
  set_x_position_slider(0.1, id, 0)
  if fneq(res = x_position_slider(id, 0), 0.1)
    snd_display("set_x_position_slider 0.1: %s?", res)
  end
  set_x_zoom_slider(0.5, id, 0)
  if fneq(res = x_zoom_slider(id, 0), 0.5)
    snd_display("set_x_zoom_slider 0.5: %s?", res)
  end
  if (res1 = (fr - 2 * ((res2 = right_sample(id, 0)) - (res3 = left_sample(id, 0)))).abs) > 10
    snd_display("set_x_zoom_slider: %s %s --> %s?", res2, res3, res1)
  end
  set_y_position_slider(0.1, id, 0)
  if $with_test_motif and fneq(res = y_position_slider(id, 0), 0.1)
    snd_display("set_y_position_slider 0.1: %s?", res)
  end
  set_y_zoom_slider(0.5, id, 0)
  if fneq(res = y_zoom_slider(id, 0), 0.5)
    snd_display("set_y_zoom_slider 0.5: %s?", res)
  end
  # 
  len = (channel_properties(id, 0) or []).length
  if res = channel_property(:hiho, id, 0)
    snd_display("channel_property :hiho: %s?", res)
  end
  set_channel_property(:hiho, 123, id, 0)
  if (res = channel_property(:hiho, id, 0)) != 123
    snd_display("channel_property :hiho (123): %s?", res)
  end
  if res = channel_property(:hi, id, 0)
    snd_display("channel_property :hi: %s?", res)
  end
  set_channel_property(:hi, PI, id, 0)
  if (res = channel_property(:hi, id, 0)) != PI
    snd_display("channel_property :hi (PI): %s?", res)
  end
  if (res = channel_property(:hiho, id, 0)) != 123
    snd_display("channel_property 2nd :hiho (123): %s?", res)
  end
  if (res = (channel_properties or []).length) != len + 2
    snd_display("channel_properties: %s?", res)
  end
  # 
  len = (sound_properties(id) or []).length
  if res = sound_property(:hiho, id)
    snd_display("sound_property :hiho: %s?", res)
  end
  set_sound_property(:hiho, 123, id)
  if (res = sound_property(:hiho, id)) != 123
    snd_display("sound_property :hiho (123): %s?", res)
  end
  if res = sound_property(:hi, id)
    snd_display("sound_property :hi: %s?", res)
  end
  set_sound_property(:hi, PI, id)
  if (res = sound_property(:hi, id)) != PI
    snd_display("sound_property :hi (PI): %s?", res)
  end
  if (res = sound_property(:hiho, id)) != 123
    snd_display("sound_property 2nd :hiho (123): %s?", res)
  end
  if (res = (sound_properties(id) or []).length) != len + 2
    snd_display("sound_properties: %s?", res)
  end
  if (res = Snd.catch do map_channel(lambda do |y| "hiho" end) end).first != :bad_type
    snd_display("map_channel bad val: %s", res.inspect)
  end
  close_sound(id)
  #
  id = open_sound("oboe.snd")
  prefix_it(1000, id)
  key(key_to_int(?x), 4, id)
  key(key_to_int(?b), 4, id)
  if (left = left_sample(id)) != 0
    snd_display("u1000: %s?", left)
  end
  prefix_it(0, id)
  key(key_to_int(?x), 4, id)
  key(key_to_int(?b), 4, id)
  if (left = left_sample(id)) != 0
    snd_display("u0: %s?", left)
  end
  set_cursor(1234, id)
  prefix_it(0, id)
  key(key_to_int(?f), 4, id)
  if (cr = cursor(id)) != 1234
    snd_display("0f: %s?", cr)
  end
  prefix_it(100, id)
  key(key_to_int(?f), 4, id)
  if (cr = cursor(id)) != 1334
    snd_display("100f: %s?", cr)
  end
  prefix_it(-100, id)
  key(key_to_int(?f), 4, id)
  if (cr = cursor(id)) != 1234
    snd_display("-100f: %s?", cr)
  end
  prefix_it(1, id)
  key(key_to_int(?f), 4, id)
  if (cr = cursor(id)) != 1235
    snd_display("1f: %s?", cr)
  end
  prefix_it(1000, id)
  key(key_to_int(?x), 4, id)
  key(key_to_int(?p), 4, id)
  if ((right = right_sample(id)) - (left = left_sample(id)) - 1000).abs > 2
    snd_display("1000xp: %s %s?", left, right)
  end
  prefix_it(1, id)
  key(key_to_int(?.), 0, id)
  key(key_to_int(?2), 0, id)
  key(key_to_int(?x), 4, id)
  key(key_to_int(?p), 4, id)
  if ((right = right_sample(id)) - (left = left_sample(id)) - (22050 * 1.2)).abs > 2
    snd_display("1.2xp: %s %s?", left, right)
  end
  #
  prefix_uit(1000, id)
  key(key_to_int(?x), 4, id)
  key(key_to_int(?b), 4, id)
  if (left = left_sample(id)) != 1000 and left != 1001
    snd_display("uu1000: %s?", left)
  end
  prefix_uit(0, id)
  key(key_to_int(?x), 4, id)
  key(key_to_int(?b), 4, id)
  if (left = left_sample(id)) != 0
    snd_display("uu0: %s?", left)
  end
  set_cursor(1234, id)
  prefix_uit(0, id)
  key(key_to_int(?f), 4, id)
  if (cr = cursor(id)) != 1234
    snd_display("u0f: %s?", cr)
  end
  prefix_uit(100, id)
  key(key_to_int(?f), 4, id)
  if (cr = cursor(id)) != 1334
    snd_display("u100f: %s?", cr)
  end
  prefix_uit(-100, id)
  key(key_to_int(?f), 4, id)
  if (cr = cursor(id)) != 1234
    snd_display("u-100f: %s?", cr)
  end
  prefix_uit(1, id)
  key(key_to_int(?f), 4, id)
  if (cr = cursor(id)) != 1235
    snd_display("u1f: %s?", cr)
  end
  prefix_uit(1000, id)
  key(key_to_int(?x), 4, id)
  key(key_to_int(?p), 4, id)
  if ((right = right_sample(id)) - (left = left_sample(id)) - 1000).abs > 2
    snd_display("u1000xp: %s %s?", left, right)
  end
  prefix_uit(1, id)
  key(key_to_int(?.), 0, id)
  key(key_to_int(?2), 0, id)
  key(key_to_int(?x), 4, id)
  key(key_to_int(?p), 4, id)
  if ((right = right_sample(id)) - (left = left_sample(id)) - (22050 * 1.2)).abs > 2
    snd_display("u1.2xp: %s %s?", left, right)
  end
  close_sound(id)
  # 
  snds = match_sound_files do |file|
    File.exists?(file) and # for $tests > 1
      mus_sound_chans(file) >= 2 and
      mus_sound_frames(file) > 1000
  end
  if snds.length > 0
    id = open_sound(snds.first)
    set_sync(1, id)
    select_sound(id)
    make_region(200, 500, id)
    select_channel(1)
    key(key_to_int(?x), 4, id)
    key(key_to_int(?v), 0, id)
    if $with_test_gui
      x0 = x_bounds(id, 0)
      x1 = x_bounds(id, 1)
      if fneq(x0[0], x1[0]) or fneq(x0[1], x1[1])
        snd_display("C-x v: %s %s?", x0, x1)
      end
      key(key_to_int(?u), 4, id)
      key(key_to_int(?1), 0, id)
      key(key_to_int(?x), 4, id)
      key(key_to_int(?q), 0, id)
    end
    close_sound(id)
  else
    snd_display("No sound file found id: %s", snds)
  end
end

def f3neq(a, b)
  fneq_err(a, b, 10)            # okay
end

def f4neq(a, b)
  fneq_err(a, b, 1)             # okay
end

def f5neq(a, b)
  fneq_err(a, b, 0.05 * [a, b].max) # okay
end

def test_15_02
  snd1 = open_sound("oboe.snd")
  snd2 = open_sound("2.snd")
  snd3 = open_sound("4.aiff")
  [[:time_graph_type, Graph_as_wavogram],
   [:wavo_hop, 12],
   [:wavo_trace, 512],
   [:max_transform_peaks, 3],
   [:show_transform_peaks, true],
   [:zero_pad, 32],
   [:transform_graph_type, Graph_as_sonogram],
   [:fft_window, Cauchy_window],
   [:with_verbose_cursor, true],
   [:fft_log_frequency, true],
   [:fft_log_magnitude, true],
   [:fft_with_phases, true],
   [:min_dB, -120.0],
   [:wavelet_type, 3],
   [:transform_size, 32],
   [:fft_window_alpha, 0.5],
   [:fft_window_beta, 0.5],
   [:transform_type, $autocorrelation],
   [:transform_normalization, 0],
   [:show_mix_waveforms, true],
   [:graph_style, Graph_lollipops],
   [:dot_size, 8],
   [:show_axes, Show_no_axes],
   [:show_y_zero, true],
   [:show_grid, true],
   [:show_marks, false],
   [:grid_density, 1.0],
   [:spectro_x_angle, 32.0],
   [:spectro_x_scale, 0.5],
   [:spectro_y_angle, 32.0],
   [:spectro_y_scale, 0.5],
   [:spectro_z_angle, 32.0],
   [:spectro_z_scale, 0.5],
   [:spectro_hop, 14],
   [:spectrum_end, 0.3],
   [:spectrum_start, 0.1],
   [:graphs_horizontal, false],
   [:x_axis_style, X_axis_in_samples],
   [:beats_per_minute, 120.0],
   [:beats_per_measure, 3],
   [:cursor_size, 15],
   [:cursor_style, Cursor_cross],
   [:tracking_cursor_style, Cursor_cross],
   [:show_sonogram_cursor, true]].each do |func, nv|
    test_history_channel(func, nv, snd1, snd2, snd3)
  end
  [snd1, snd2].apply(:close_sound)
  #
  set_time_graph_style(Graph_filled, snd3, true)
  4.times do |chn|
    if (res = time_graph_style(snd3, chn)) != Graph_filled
      snd_display("set_time_graph_style %s %s (filled): %s?", snd3, chn, res)
    end
  end
  set_time_graph_style(Graph_lines, snd3, 2)
  4.times do |chn|
    if chn == 2
      if (res = time_graph_style(snd3, chn)) != Graph_lines
        snd_display("set_time_graph_style %s %s (lines): %s?", snd3, chn, res)
      end
    else
      if (res = time_graph_style(snd3, chn)) != Graph_filled
        snd_display("set_time_graph_style %s %s (filled): %s?", snd3, chn, res)
      end
    end
  end
  set_time_graph_style(Graph_dots, snd3, true)
  4.times do |chn|
    if (res = time_graph_style(snd3, chn)) != Graph_dots
      snd_display("set_time_graph_style %s %s (dots): %s?", snd3, chn, res)
    end
  end
  set_graph_style(Graph_dots_and_lines)
  4.times do |chn|
    if (res = time_graph_style(snd3, chn)) != Graph_dots_and_lines
      snd_display("set_time_graph_style %s %s (dots and lines): %s?", snd3, chn, res)
    end
  end
  set_lisp_graph_style(Graph_filled, snd3, true)
  4.times do |chn|
    if (res = lisp_graph_style(snd3, chn)) != Graph_filled
      snd_display("set_lisp_graph_style %s %s (filled): %s?", snd3, chn, res)
    end
  end
  set_lisp_graph_style(Graph_lines, snd3, 2)
  4.times do |chn|
    if chn == 2
      if (res = lisp_graph_style(snd3, chn)) != Graph_lines
        snd_display("set_lisp_graph_style %s %s (lines): %s?", snd3, chn, res)
      end
    else
      if (res = lisp_graph_style(snd3, chn)) != Graph_filled
        snd_display("set_lisp_graph_style %s %s (filled): %s?", snd3, chn, res)
      end
    end
  end
  set_lisp_graph_style(Graph_lines, snd3, true)
  4.times do |chn|
    if (res = lisp_graph_style(snd3, chn)) != Graph_lines
      snd_display("set_lisp_graph_style %s %s (lines): %s?", snd3, chn, res)
    end
  end
  set_transform_graph_style(Graph_filled, snd3, true)
  4.times do |chn|
    if (res = transform_graph_style(snd3, chn)) != Graph_filled
      snd_display("set_transform_graph_style %s %s (filled): %s?", snd3, chn, res)
    end
  end
  set_transform_graph_style(Graph_lines, snd3, 2)
  4.times do |chn|
    if chn == 2
      if (res = transform_graph_style(snd3, chn)) != Graph_lines
        snd_display("set_transform_graph_style %s %s (lines): %s?", snd3, chn, res)
      end
    else
      if (res = transform_graph_style(snd3, chn)) != Graph_filled
        snd_display("set_transform_graph_style %s %s (filled): %s?", snd3, chn, res)
      end
    end
  end
  4.times do |chn|
    if (res = time_graph_style(snd3, chn)) != Graph_dots_and_lines
      snd_display("set fft and lisp -> time_graph_style: %s?", snd3, chn, res)
    end
  end
  4.times do |chn|
    if (res = lisp_graph_style(snd3, chn)) != Graph_lines
      snd_display("set fft and lisp -> lisp_graph_style: %s?", snd3, chn, res)
    end
  end
  close_sound(snd3)
  snd2 = open_sound("2.snd")
  sound?(snd2) and play_with_amps_1(snd2, 0.2, 0.1)
  close_sound(snd2)
  #
  old_bp = with_background_processes
  set_with_background_processes(false)
  ind = open_sound("1a.snd")
  player = make_player(ind, 0)
  len = frames(ind, 0)
  incr = dac_size
  e = make_env(:envelope, [0, 0, 1, 1], :length, (len.to_f / incr).floor + 1)
  samp = 0
  add_player(player, 0, -1, -1, lambda do |reason|
               $play_hook.reset_hook!
               close_sound(ind)
             end)
  $play_hook.add_hook!("snd-test") do |fr|
    set_amp_control(env(e), player)
    if fneq(res = amp_control(ind), 1.0)
      snd_display("amp_control snd: %s?", res)
    end
    if ((res1 = amp_control(player)) - (res2 = samp / len.to_f)).abs > 1.0
      snd_display("amp_control player: %s %s?", res1, res2)
    end
    samp += incr
  end
  start_playing(1, srate(ind))
  if find_sound("1a.snd")
    snd_display("stop proc did not close?")
    Snd.catch do close_sound(ind) end
  end
  set_with_background_processes(old_bp)
  #
  ind = open_sound("pistol.snd")
  if res = selection_member?(ind, 0)
    snd_display("initial selection_member?: %s %s?", res, selection?)
  end
  set_selection_member?(true, ind, 0)
  if (not (res1 = selection_member?(ind, 0))) or (not (res2 = selection_member?(ind)))
    snd_display("selection_member? %s %s %s?", res1, res2, selection?)
  end
  if (res = selection_frames) != 1
    snd_display("initial selection_frames: %s?", res)
  end
  set_selection_frames(1200)
  if (res = selection_frames) != 1200
    snd_display("selection_frames 1200: %s?", res)
  end
  delete_selection
  if selection?
    snd_display("selection active after cut?")
  end
  undo_edit
  unless selection?
    snd_display("selection inactive after undo?")
  end
  if (not (res1 = selection_member?(ind, 0))) or (not (res2 = selection_member?(ind)))
    snd_display("selection_member? after undo %s %s %s?", res1, res2, selection?)
  end
  if (res1 = selection_frames) != 1200 or (res2 = selection_position) != 0
    snd_display("selection after undo: [0, 1200] [%s, %s]?", res2, res1)
  end
  set_selection_position(1000)
  if (res1 = selection_frames) != 200 or (res2 = selection_position) != 1000
    snd_display("selection after undo: [1000, 200] [%s, %s]?", res2, res1)
  end
  reverse_selection
  if (res1 = selection_frames) != 200 or (res2 = selection_position) != 1000
    snd_display("selection after reverse: [1000, 200] [%s, %s]?", res2, res1)
  end
  old_frames = frames(ind)
  src_selection(0.5)
  if (frames(ind) - (200 + old_frames)).abs > 5 or
      ((res = selection_frames) - 400).abs > 5
    snd_display("selection after src 0.5: [1000, 400] [%s, %s]?", res, selection_position)
  end
  undo_edit
  redo_edit
  if (frames(ind) - (200 + old_frames)).abs > 5 or
      ((res = selection_frames) - 400).abs > 5
    snd_display("selection after src 0.5 with undo/redo: [1000, 400] [%s, %s]?",
                res, selection_position)
  end
  undo_edit(3)
  close_sound(ind)
  #
  # src-duration tests
  #
  ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1, "src-* tests", 10000)
  osc = make_oscil(:frequency, 500)
  if fneq(res1 = src_duration([0, 1, 1, 2]), 0.693147180559945) or
      fneq(res2 = src_duration([0, 2, 1, 1]), src_duration([0, 1, 1, 2])) or
      fneq(res3 = src_duration([0, 1, 0.5, 2]), src_duration([0, 1, 1, 2])) or
      fneq(res4 = src_duration([0.5, 1, 0.75, 2]), src_duration([0, 1, 1, 2]))
    snd_display("src_duration test1: %s %s %s %s?", res1, res2, res3, res4)
  end
  if fneq(res1 = src_duration([0, 1, 1, 0.5]), 1.38629436111989) or
      fneq(res2 = src_duration([0, 0.5, 1, 1]), src_duration([0, 1, 1, 0.5])) or
      fneq(res3 = src_duration([0, 1, 0.5, 0.5]), src_duration([0, 1, 1, 0.5])) or
      fneq(res4 = src_duration([0.5, 1, 0.75, 0.5]), src_duration([0, 1, 1, 0.5]))
    snd_display("src_duration test2: %s %s %s %s?", res1, res2, res3, res4)
  end
  if fneq(res1 = src_duration([0, 1, 1, 1]), 1.0) or
      fneq(res2 = src_duration([0, 2, 1, 2]), 0.5)
    snd_display("src_duration test3: %s %s?", res1, res2)
  end
  if fneq(res = src_duration([0, 0.5, 0.5, 3, 0.6, 1, 0.7, 0.1, 0.8, 1.5, 1, 1]), 1.02474349685432)
    snd_display("src_duration test4: %s?", res)
  end
  if fneq(res = src_duration([0, 1, 1, 2, 2, 1]), 0.693147180559945)
    snd_display("src_duration test5: %s?", res)
  end
  map_channel_rb do |y| 0.5 * oscil(osc) end
  vals = freq_peak(0, ind, 8192)
  if f4neq(vals[0], 500.0) or fneq(vals[1], 1.0)
    snd_display("src no-test: %s?", vals)
  end
  # 
  src_lists1 = [[2.0, 0.5], [0.5, 2.0], [5.0, 0.2], [0.2, 5.0]]
  src_lists2 = [[[0, 1, 1, 2], 500.0, 1000.0],
                [[0, 2, 1, 1], 1000.0, 500.0],
                [[0, 1, 1, 2, 2, 1], 500.0, 500.0],
                [[0, 0.5, 1, 1], 250.0, 500.0],
                [[0, 0.5, 1, 2], 250.0, 1000.0]]
  src_lists3 = [[0, 1, 1, 2], [0, 2, 1, 1], [0, 1, 1, 2, 2, 1], [0, 0.5, 1, 1], [0, 0.5, 1, 2]]
  # src_sound
  src_lists1.each do |sr, dur|
    src_sound(sr, 1.0, ind, 0)
    if fneq(res = frames(ind, 0) / 10000.0, dur)
      snd_display("src_sound %s: %s (%s)?", sr, res, dur)
    end
    vals = freq_peak(0, ind, 8192)
    if f4neq(vals[0], 500 * sr) or fneq(vals[1], 1.0)
      snd_display("src_sound %s freq: %s?", sr, vals)
    end
    undo_edit
  end
  src_lists2.each do |e, f0, f1|
    src_sound(e, 1.0, ind, 0)
    if fneq(res1 = frames(ind, 0) / 10000.0, res2 = src_duration(e))
      snd_display("src_sound (env) %s: %s (%s)?", e, res1, res2)
    end
    vals = freq_peak(0, ind, 256)
    if f5neq(vals[0], f0)
      snd_display("src_sound (env) 0 %s freq: %s?", f0, vals)
    end
    vals = freq_peak((src_duration(e) * 10000.0).floor - 256, ind, 256)
    if f5neq(vals[0], f1)
      snd_display("src_sound (env) 1 %s freq: %s?", f1, vals)
    end
    undo_edit
  end
  src_lists2.each do |e, f0, f1|
    src_sound(make_env(:envelope, e, :length, frames), 1.0, ind, 0)
    if fneq(res1 = frames(ind, 0) / 10000.0, res2 = src_duration(e))
      snd_display("src_sound (make_env) %s: %s (%s)?", e, res1, res2)
    end
    vals = freq_peak(0, ind, 256)
    if f5neq(vals[0], f0)
      snd_display("src_sound (make_env) 0 %s freq: %s?", f0, vals)
    end
    vals = freq_peak((src_duration(e) * 10000.0).floor - 256, ind, 256)
    if f5neq(vals[0], f1)
      snd_display("src_sound (make_env) 1 %s freq: %s?", f1, vals)
    end
    undo_edit
  end
  # src_channel
  src_lists1.each do |sr, dur|
    src_channel(sr)
    if fneq(res = frames(ind, 0) / 10000.0, dur)
      snd_display("src_channel %s: %s (%s)?", sr, res, dur)
    end
    vals = freq_peak(0, ind, 8192)
    if f4neq(vals[0], 500 * sr) or fneq(vals[1], 1.0)
      snd_display("src_channel %s freq: %s?", sr, vals)
    end
    undo_edit
  end
  src_lists2.each do |e, f0, f1|
    src_channel(e)
    if fneq(res1 = frames(ind, 0) / 10000.0, res2 = src_duration(e))
      snd_display("src_channel (env) %s: %s (%s)?", e, res1, res2)
    end
    vals = freq_peak(0, ind, 256)
    if f5neq(vals[0], f0)
      snd_display("src_channel (env f0) %s freq: %s?", f0, vals)
    end
    vals = freq_peak((src_duration(e) * 10000.0).floor - 256, ind, 256)
    if f5neq(vals[0], f1)
      snd_display("src_channel (env f1) %s freq: %s?", f1, vals)
    end
    undo_edit
  end
  src_lists1.each do |sr, dur|
    src_channel(sr, 1000, 2500)
    if f4neq(res1 = frames(ind, 0), (res2 = 7500 + dur * 2500))
      snd_display("src_channel section: %s %s?", res1, res)
    end
    vals = freq_peak(0, ind, 512)
    if f5neq(vals[0], 500.0)
      snd_display("src_channel section 0 %s freq: %s?", sr, vals)
    end
    vals = freq_peak(((dur * 2500.0).floor + 7500) - 512, ind, 512)
    if f5neq(vals[0], 500.0)
      snd_display("src_channel section 8000 %s freq: %s?", sr, vals)
    end
    vals = freq_peak(1000, ind, 512)
    if f5neq(vals[0], 500.0 * sr)
      snd_display("src_channel section %s freq: %s?", sr, vals)
    end
    undo_edit
  end
  src_lists3.each do |e|
    src_channel(make_env(:envelope, e, :length, 2501), 1000, 2500)
    if f3neq(res1 = frames(ind, 0), (res2 = 7500 + src_duration(e) * 2500))
      snd_display("src_channel section (make_env duration) %s: %s (%s %s)?",
                  e, src_duration(e), res1, res2)
    end
    vals = freq_peak(0, ind, 256)
    if f5neq(vals[0], 500.0)
      snd_display("src_channel section (make_env 0): %s?", vals)
    end
    vals = freq_peak(((src_duration(e) * 2500).floor + 7500) - 256, ind, 256)
    if f5neq(vals[0], 500.0)
      snd_display("src_channel section (make_env 1): %s?", vals)
    end
    undo_edit
  end
  # src_selection
  make_selection(1000, 3500, ind, 0)
  src_lists1.each do |sr, dur|
    src_selection(sr)
    if f3neq(res1 = frames(ind, 0), (res2 = 7500 + dur * 2500))
      snd_display("src_selection section: %s %s?", res1, res)
    end
    vals = freq_peak(0, ind, 512)
    if f5neq(vals[0], 500.0)
      snd_display("src_selection section 0 %s freq: %s?", sr, vals)
    end
    vals = freq_peak(((dur * 2500.0).floor + 7500) - 512, ind, 512)
    if f5neq(vals[0], 500.0)
      snd_display("src_selection section 8000 %s freq: %s?", sr, vals)
    end
    vals = freq_peak(1000, ind, 512)
    if f5neq(vals[0], 500.0 * sr)
      snd_display("src_selection section %s freq: %s?", sr, vals)
    end
    undo_edit
  end
  src_lists3.each do |e|
    src_selection(make_env(:envelope, e, :length, 2501))
    if f3neq(res1 = frames(ind, 0), (res2 = 7500 + src_duration(e) * 2500))
      snd_display("src_selection section (make_env duration) %s: %s (%s %s)?",
                  e, src_duration(e), res1, res2)
    end
    vals = freq_peak(0, ind, 256)
    if f5neq(vals[0], 500.0)
      snd_display("src_selection section (make_env 0): %s?", vals)
    end
    vals = freq_peak(((src_duration(e) * 2500).floor + 7500) - 256, ind, 256)
    if f5neq(vals[0], 500.0)
      snd_display("src_selection section (make_env 1): %s?", vals)
    end
    undo_edit
  end
  src_lists3.each do |e|
    src_selection(e)
    if f3neq(res1 = frames(ind, 0), (res2 = 7500 + src_duration(e) * 2500))
      snd_display("src_selection section (env duration) %s: %s (%s %s)?",
                  e, src_duration(e), res1, res2)
    end
    vals = freq_peak(0, ind, 256)
    if f5neq(vals[0], 500.0)
      snd_display("src_selection section (env 0): %s?", vals)
    end
    vals = freq_peak(((src_duration(e) * 2500).floor + 7500) - 256, ind, 256)
    if f5neq(vals[0], 500.0)
      snd_display("src_selection section (env 1): %s?", vals)
    end
    undo_edit
  end
  close_sound(ind)
end

def test_15_03
  ind = new_sound("hi.snd")
  10.times do |i| set_sample(i, i * 0.1, ind) end
  select_all(ind)
  set_sample(10, 1.0, ind)
  smooth_selection
  unless vequal(channel2vct(0, 11, ind).subseq(0, 9), smoother(0.0, 1.0, 10).subseq(0, 9))
    snd_display("smooth_selection: %s %s?", channel2vct(0, 11, ind), smoother(0.0, 1.0, 10))
  end
  revert_sound
  10.times do |i| set_sample(i, 1.0 - i * 0.1, ind) end
  select_all(ind)
  set_sample(10, 0.0, ind)
  smooth_selection
  unless vequal(channel2vct(0, 11, ind).subseq(0, 9), smoother(1.0, 0.0, 10).subseq(0, 9))
    snd_display("smooth_selection back: %s %s?", channel2vct(0, 11, ind), smoother(1.0, 0.0, 10))
  end
  close_sound(ind)
  # 
  ind = new_sound("hi.snd")
  10.times do |i| set_sample(i, i * 0.1, ind) end
  set_sample(10, 1.0, ind)
  smooth_sound(0, 10, ind)
  unless vequal(channel2vct(0, 11, ind).subseq(0, 9), smoother(0.0, 1.0, 10).subseq(0, 9))
    snd_display("smooth_sound: %s %s?", channel2vct(0, 11, ind), smoother(0.0, 1.0, 10))
  end
  revert_sound
  10.times do |i| set_sample(i, 1.0 - i * 0.1, ind) end
  set_sample(10, 0.0, ind)
  smooth_sound(0, 10, ind)
  unless vequal(channel2vct(0, 11, ind).subseq(0, 9), smoother(1.0, 0.0, 10).subseq(0, 9))
    snd_display("smooth_sound back: %s %s?", channel2vct(0, 11, ind), smoother(1.0, 0.0, 10))
  end
  close_sound(ind)
  delete_file("hi.snd")
  #
  ind = open_sound("oboe.snd")
  len = frames(ind)
  set_cursor(1200, ind)
  key(key_to_int(?u), 4, ind)
  key(key_to_int(?1), 0, ind)
  key(key_to_int(?0), 0, ind)
  key(key_to_int(?0), 0, ind)
  key(key_to_int(?o), 4, ind)
  if frames(ind) != 100 + len
    snd_display("C-o len: %s?", frames)
  end
  if $with_test_gui
    reader = make_sampler(1200, ind)
    100.times do |i|
      if fneq(val = next_sample(reader), 0.0)
        snd_display("C-o[%s]: %s?", i, val)
      end
    end
    if (res = sampler_position(reader)) != 1300
      snd_display("reader position: %s?", res)
    end
    free_sampler(reader)
  end
  revert_sound(ind)
  set_cursor(1200, ind)
  key(key_to_int(?u), 4, ind)
  key(key_to_int(?1), 0, ind)
  key(key_to_int(?0), 0, ind)
  key(key_to_int(?0), 0, ind)
  key(key_to_int(?z), 4, ind)
  if frames(ind) != len
    snd_display("C-z len: %s?", frames)
  end
  if $with_test_gui
    reader = make_sampler(1200, ind)
    100.times do |i|
      if fneq(val = next_sample(reader), 0.0)
        snd_display("C-z[%s]: %s?", i, val)
      end
    end
    free_sampler(reader)
  end
  set_cursor(0, ind)
  key(key_to_int(?u), 4, ind)
  key(key_to_int(?3), 0, ind)
  key(key_to_int(?.), 0, ind)
  key(key_to_int(?0), 0, ind)
  key(key_to_int(?z), 4, ind)
  if fneq(maxamp(ind, 0), 0.0)
    snd_display("C-z full: %s?", maxamp)
  end
  revert_sound(ind)
  set_cursor(1200, ind)
  key(key_to_int(?u), 4, ind)
  key(key_to_int(?1), 0, ind)
  key(key_to_int(?.), 0, ind)
  key(key_to_int(?0), 0, ind)
  key(key_to_int(?o), 4, ind)
  if frames(ind) != srate(ind) + len
    snd_display("C-o 1.0 len: %s?", frames)
  end
  if $with_test_gui
    reader = make_sampler(1200, ind)
    srate(ind).times do |i|
      if fneq(val = next_sample(reader), 0.0)
        snd_display("C-o 1.0[%s]: %s?", i, val)
      end
    end
    free_sampler(reader)
  end
  revert_sound(ind)
  set_cursor(1200, ind)
  key(key_to_int(?u), 4, ind)
  key(key_to_int(?1), 0, ind)
  key(key_to_int(?.), 0, ind)
  key(key_to_int(?0), 0, ind)
  key(key_to_int(?z), 4, ind)
  if frames(ind) != len
    snd_display("C-z 1.0 len: %s?", frames)
  end
  if $with_test_gui
    reader = make_sampler(1200, ind)
    srate(ind).times do |i|
      if fneq(val = next_sample(reader), 0.0)
        snd_display("C-z 1.0[%s]: %s?", i, val)
      end
    end
    free_sampler(reader)
  end
  close_sound(ind)
  #
  ind = open_sound("2.snd")
  set_sync(1, ind)
  key(key_to_int(?>), 4)
  key(key_to_int(?\s), 4)
  key(key_to_int(?<), 4)
  if (not (res1 = selection_member?(ind, 0))) or
      (not (res2 = selection_member?(ind, 1))) or
      (res3 = selection_position(ind, 0)) != 0 or
      (res4 = selection_position(ind, 1)) != 0 or
      (res5 = selection_frames(ind, 0)) != frames(ind, 0) or
      (res6 = selection_frames(ind, 1)) != frames(ind, 1)
    snd_display("sync selection via <-: %s %s %s %s %s %s?", res1, res2, res3, res4, res5, res6)
  end
  key(key_to_int(?\s), 4)
  key(key_to_int(?>), 4)
  if (not (res1 = selection_member?(ind, 0))) or
      (not (res2 = selection_member?(ind, 1))) or
      (res3 = selection_position(ind, 0)) != 0 or
      (res4 = selection_position(ind, 1)) != 0 or
      (res5 = selection_frames(ind, 0)) != frames(ind, 0) or
      (res6 = selection_frames(ind, 1)) != frames(ind, 1)
    snd_display("sync selection via ->: %s %s %s %s %s %s?", res1, res2, res3, res4, res5, res6)
  end
  set_cursor(0, ind, 1)
  set_cursor(1000, ind, 0)
  if (res = cursor(ind, 1)) != 1000
    snd_display("syncd cursors: %s %s?", cursor(ind, 0), res)
  end
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  test_selection(ind, 1200, 100, 2.0)
  test_selection(ind, 600, 1200, 2.0)
  test_selection(ind, 0, 100, 2.0)
  test_selection(ind, 22500, 50827 - 22500, 0.5)
  test_selection(ind, 0, 50828, 0.5)
  #
  test_selection_to(ind, 1200, 100, 1.0)
  test_selection_to(ind, 600, 1200, 0.1)
  test_selection_to(ind, 0, 100, 0.5)
  test_selection_to(ind, 22500, 50827 - 22500, 2.0)
  test_selection_to(ind, 0, 50828, 0.5)
  #
  revert_sound(ind)
  make_selection(1200, 1200)
  unless selection?
    snd_display("no selection from 1 samp region?")
  end
  if (res = selection_frames) != 1
    snd_display("1 samp selection: %s samps?", res)
  end
  scale_selection_to(1.0)
  if fneq(res = sample(1200, ind, 0).abs, 1.0)
    snd_display("scale 1 samp selection: %s?", res)
  end
  revert_sound(ind)
  id = make_region(500, 1000)
  src_selection(0.5)
  if ((res = region_frames(id)) - 500).abs > 1
    snd_display("region_frames after src_selection: %s?", res)
  end
  reg_mix_id = mix_region(id, 1500, ind, 0).car
  if (res1 = mix_length(reg_mix_id)) != (res2 = region_frames(id))
    snd_display("mix_region: %s != %s?", res1, res2)
  end
  if (res = mix_home(reg_mix_id)) != [ind, 0, false, 0]
    snd_display("mix_region mix_home %s [%s, 0, false, 0]?", res, ind)
  end
  sel_mix_id = mix_selection(2500, ind, 0).car
  if (res1 = mix_length(sel_mix_id)) != (res2 = selection_frames)
    snd_display("mix_selection frames: %s != %s?", res1, res2)
  end
  if ((res1 = mix_length(reg_mix_id)) * 2 - (res2 = mix_length(sel_mix_id))).abs > 3
    snd_display("mix selection and region: %s %s %s %s?",
                res1, res2, region_frames(id), selection_frames)
  end
  if (res = mix_home(reg_mix_id)) != [ind, 0, false, 0]
    snd_display("mix_selection mix_home %s [%s, 0, false, 0]?", res, ind)
  end
  insert_selection(3000, ind, 0)
  insert_selection(3000, ind)
  mix_selection(3000, ind)
  delete_selection
  revert_sound(ind)
  close_sound(ind)
  #
  if File.exists?("storm.snd")
    ind = open_sound("storm.snd")
    set_sinc_width(10)
    with_time("src_sound(1.3)") do src_sound(1.3) end
    with_time("env_sound([0, 0, 1, 1, 2, 0])") do env_sound([0, 0, 1, 1, 2, 0]) end
    with_time("filter_sound, FIR direct form") do
      filter_sound([0, 1, 0.2, 0, 0.5, 1, 1, 0], 20)
    end
    with_time("filter_sound, convolution") do
      filter_sound([0, 0, 0.1, 0, 0.11, 1, 0.12, 0, 1, 0], 2048)
    end
    revert_sound(ind)
    reg = make_region(0, 123000, ind, 0)
    region2vct(reg, 0, 10, 0, Vct.new(10))
    ramp_channel(0.0, 1.0)
    ramp_channel(0.0, 1.0)
    ramp_channel(0.0, 1.0)
    ramp_channel(0.0, 1.0)
    close_sound(ind)
  end
  if File.exists?("1a.snd") and $all_args
    ind = open_sound("1a.snd")
    with_time("rubber_sound(1.25)") do rubber_sound(1.25) end
    close_sound(ind)
  end
  oboe = open_sound("oboe.snd")
  a4 = open_sound("4.aiff")
  sr = srate(oboe)
  save_sound_as("test.aif", oboe, Mus_aifc)
  oboe_aif = open_sound("test.aif")
  if (res = header_type(oboe_aif)) != Mus_aifc
    snd_display("oboe_aif header: %s?", mus_header_type_name(res))
  end
  set_srate(oboe_aif, sr * 2)
  if fneq(res = srate(oboe_aif), sr * 2)
    snd_display("set_srate: %s %s?", sr * 2, res)
  end
  set_header_type(oboe_aif, Mus_next)
  if (res = header_type(oboe_aif)) != Mus_next
    snd_display("set_header: %s?", mus_header_type_name(res))
  end
  set_data_location(oboe_aif, 28)
  if (res = data_location(oboe_aif)) != 28
    snd_display("set_data_location: %s?", res)
  end
  set_data_format(oboe_aif, Mus_mulaw)
  if (res = data_format(oboe_aif)) != Mus_mulaw
    snd_display("set_data_format: %s?", mus_data_format_name(res))
  end
  save_sound_as("test.aif", oboe_aif, Mus_aifc, Mus_bshort, 22050, 0)
  close_sound(oboe_aif)
  delete_file("test.aif")
  set_selected_sound(a4)
  if selected_sound != a4
    snd_display("set_selected_sound: %s %s?", selected_sound, a4)
  end
  set_selected_channel(a4, 2)
  if selected_channel != 2
    snd_display("set_selected_channel a4: %s?", selected_channel(a4))
  end
  set_selected_channel(a4, 3)
  if selected_channel(a4) != 3
    snd_display("set_selected_channel a4: %s?", selected_channel(a4))
  end
  close_sound(a4)
  close_sound(oboe)
end

def test_15_04
  if fneq(res = envelope_interp(0.1, [0, 0, 1, 1]), 0.1)
    snd_display("envelope_interp 0.1: %s?", res)
  end
  if fneq(res = envelope_interp(0.1, [0, 0, 1, 1], 32.0), 0.01336172)
    snd_display("envelope_interp 0.013: %s?", res)
  end
  if fneq(res = envelope_interp(0.1, [0, 0, 1, 1], 0.012), 0.36177473)
    snd_display("envelope_interp 0.361: %s?", res)
  end
  if fneq(res = envelope_interp(0.3, [0, 0, 0.5, 1, 1, 0]), 0.6)
    snd_display("envelope_interp 0.3 [0, 0, 0.5, 1, 1, 0]: %s?", res)
  end
  unless vequal(res = window_envelope(1.0, 3.0, [0, 0, 5, 1]), [1, 0.2, 3, 0.6])
    snd_display("window_envelope: %s?", res)
  end
  unless vequal(res = multiply_envelopes([0, 0, 1, 1], [0, 0, 1, 1, 2, 0]), [0, 0, 0.5, 0.5, 1, 0])
    snd_display("multiply_envelopes: %s?", res)
  end
  if fneq(res = max_envelope([0, 0, 1, 1, 2, 3, 4, 0]), 3.0)
    snd_display("0 max_envelope: %s?", res)
  end
  if fneq(res = max_envelope([0, 1]), 1.0)
    snd_display("1 max_envelope: %s?", res)
  end
  if fneq(res = max_envelope([0, 1, 1, 1, 2, 2]), 2.0)
    snd_display("2 max_envelope: %s?", res)
  end
  if fneq(res = max_envelope([0, -1, 1, -2]), -1.0)
    snd_display("3 max_envelope: %s?", res)
  end
  if fneq(res = max_envelope([0, -2, 1, -1]), -1.0)
    snd_display("4 max_envelope: %s?", res)
  end
  if fneq(res = min_envelope([0, 0, 1, 1, 2, 3, 4, 0]), 0.0)
    snd_display("0 min_envelope: %s?", res)
  end
  if fneq(res = min_envelope([0, 1]), 1.0)
    snd_display("1 min_envelope: %s?", res)
  end
  if fneq(res = min_envelope([0, 1, 1, 1, 2, 2]), 1.0)
    snd_display("2 min_envelope: %s?", res)
  end
  if fneq(res = min_envelope([0, -1, 1, -2]), -2.0)
    snd_display("3 min_envelope: %s?", res)
  end
  if fneq(res = min_envelope([0, -2, 1, -1]), -2.0)
    snd_display("4 min_envelope: %s?", res)
  end
  if fneq(res = integrate_envelope([0, 0, 1, 1]), 0.5)
    snd_display("0 integrate_envelope: %s?", res)
  end
  if fneq(res = integrate_envelope([0, 1, 1, 1]), 1.0)
    snd_display("1 integrate_envelope: %s?", res)
  end
  if fneq(res = integrate_envelope([0, 0, 1, 1, 2, 0.5]), 1.25)
    snd_display("2 integrate_envelope: %s?", res)
  end
  unless vequal(res = stretch_envelope([0, 0, 1, 1], 0.1, 0.2), [0, 0, 0.2, 0.1, 1, 1])
    snd_display("stretch_envelope att: %s?", res)
  end
  unless vequal(res = stretch_envelope([0, 0, 1, 1, 2, 0], 0.1, 0.2, 1.5, 1.6),
                [0, 0, 0.2, 0.1, 1.1, 1, 1.6, 0.5, 2, 0])
    snd_display("stretch_envelope dec: %s?", res)
  end
  unless vequal(res = add_envelopes([0, 0, 1, 1, 2, 0], [0, 0, 1, 1]), [0, 0, 0.5, 1.5, 1, 1])
    snd_display("add_envelopes: %s?", res)
  end
  unless vequal(res = scale_envelope([0, 0, 1, 1], 2.0), [0, 0, 1, 2])
    snd_display("scale_envelope: %s?", res)
  end
  unless vequal(res = scale_envelope([0, 0, 1, 1], 2.0, 1.0), [0, 1, 1, 3])
    snd_display("scale_envelope offset: %s?", res)
  end
  unless vequal(res = reverse_envelope([0, 0, 1, 1]), [0, 1, 1, 0])
    snd_display("0 reverse_envelope ramp: %s?", res)
  end
  unless vequal(res = reverse_envelope([0, 0, 0.5, 1, 2, 0]), [0, 0, 1.5, 1, 2, 0])
    snd_display("1 reverse_envelope ramp 2: %s?", res)
  end
  unless vequal(res = reverse_envelope([0, 0, 0.5, 1, 2, 1]), [0, 1, 1.5, 1, 2, 0])
    snd_display("2 reverse_envelope ramp 2: %s?", res)
  end
  unless vequal(res = concatenate_envelopes([0, 0, 1, 1], [0, 1, 1, 0]),
                [0, 0, 1, 1, 2, 0])
    snd_display("0 concatenate_envelopes: %s?", res)
  end
  unless vequal(res = concatenate_envelopes([0, 0, 1, 1.5], [0, 1, 1, 0]),
                [0, 0, 1, 1.5, 1.01, 1, 2.01, 0])
    snd_display("1 concatenate_envelopes: %s?", res)
  end
  unless vequal(res = repeat_envelope([0, 0, 1, 100], 2), [0, 0, 1, 100, 1.01, 0, 2.01, 100])
    snd_display("0 repeat_envelope: %s?", res)
  end
  unless vequal(res = repeat_envelope([0, 0, 1.5, 1, 2, 0], 2), [0, 0, 1.5, 1, 2, 0, 3.5, 1, 4, 0])
    snd_display("1 repeat_envelope: %s?", res)
  end
  unless vequal(res = repeat_envelope([0, 0, 1.5, 1, 2, 0], 2, false, true),
                [0, 0, 0.75, 1, 1, 0, 1.75, 1, 2, 0])
    snd_display("2 repeat_envelope: %s?", res)
  end
  unless vequal(res = repeat_envelope([0, 0, 1.5, 1, 2, 0], 2, true),
                [0, 0, 1.5, 1, 2, 0, 2.5, 1, 4, 0])
    snd_display("3 repeat_envelope: %s?", res)
  end
  unless vequal(res = repeat_envelope([0, 0, 1.5, 1, 2, 0], 3),
                [0, 0, 1.5, 1, 2, 0, 3.5, 1, 4, 0, 5.5, 1, 6, 0])
    snd_display("4 repeat_envelope: %s?", res)
  end
  unless vequal(res = normalize_envelope([0, 0, 1, 1.5, 2, 1.0]), [0, 0.0, 1, 1.0, 2, 0.667])
    snd_display("0 normalize_envelope: %s?", res)
  end
  unless vequal(res = normalize_envelope([0, 0, 1, 0.5, 2, -0.8]), [0, 0.0, 1, 0.625, 2, -1.0])
    snd_display("1 normalize_envelope: %s?", res)
  end
  unless vequal(res = envelope_exp([0, 0, 1, 1], 2.0, 10),
                [0.000, 0.000, 0.100, 0.010, 0.200, 0.040, 0.300, 0.090, 0.400, 0.160, 0.500,
                 0.250, 0.600, 0.360, 0.700, 0.490, 0.800, 0.640, 0.900, 0.810, 1.000, 1.000])
    snd_display("0 envelope_exp: %s?", res)
  end
  unless vequal(res = envelope_exp([0, 0, 1, 1, 2, 0], 1.0, 10),
                [0.000, 0.000, 0.200, 0.200, 0.400, 0.400, 0.600, 0.600, 0.800, 0.800, 1.000,
                 1.000, 1.200, 0.800, 1.400, 0.600, 1.600, 0.400, 1.800, 0.200, 2.000, 0.000])
    snd_display("1 envelope_exp: %s?", res)
  end
  #
  ind = new_sound("fmv.snd")
  vct2channel(Vct.new(20, 1.0))
  if selection?
    set_selection_member?(false, true)
  end
  make_selection(5, 9, ind, 0)
  scale_selection_to(0.5)
  insert_selection(15, ind)
  if (res = frames(ind)) != 25
    snd_display("insert_selection 5: %s?", res)
  end
  unless vequal(res = channel2vct(0, 25),
                vct(1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0,
                    1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0))
    snd_display("insert_selection: %s?", res)
  end
  mix_selection(1, ind, 0)
  unless vequal(res = channel2vct(0, 10, ind, 0),
                vct(1.0, 1.5, 1.5, 1.5, 1.5, 1.0, 0.5, 0.5, 0.5, 0.5))
    snd_display("mix_selection vals: %s?", res)
  end
  close_sound(ind)
  # 
  ind = new_sound("fmv.snd")
  old_size = transform_size.zero? ? 128 : transform_size
  old_type = transform_type
  old_norm = transform_normalization
  old_grf = transform_graph_type
  v = Vct.new(2000) do |i| sin(i * 2.0 * (PI / 10)) end
  vct2channel(v, 0, 2000, ind, 0)
  set_transform_size(256, ind)
  set_transform_type($fourier_transform)
  set_transform_normalization(Normalize_by_channel)
  set_transform_graph_style(Graph_once)
  set_zero_pad(0)
  set_transform_graph?(true)
  make_selection(0, 200)
  set_show_selection_transform(true)
  set_selection_frames(300)
  update_transform_graph
  if vct?(data = transform2vct)
    pk = data.peak
    if pk.zero?
      snd_display("transform selection peak: %s?", pk)
    end
    if fneq(res = transform_sample(0), data[0])
      snd_display("transform_sample: %s, data: %s?", res, data[0])
    end
  else
    snd_display("transform2vct -> %s?", data)
  end
  set_zero_pad(100000)
  if zero_pad > 1000
    snd_display("zero_pad: %s?", zero_pad)
  end
  set_zero_pad(0)
  set_transform_size(old_size)
  set_transform_type(old_type)
  set_transform_normalization(old_norm)
  set_transform_graph_style(old_grf)
  close_sound(ind)
end

def test_15
  set_transform_type($fourier_transform)
  test_15_00
  if $with_test_gui
    test_15_01
    test_15_02 # set_transform_size(0) in test_history_channel
    test_15_03
    test_15_04
  end
end

# ---------------- test 16: regularized funcs ----------------

def undo_env(snd, chn)
  if (len = (edits(snd, chn) or []).first) > 0
    1.upto(len) do |i|
      if (ed = edit_fragment(i, snd, chn)) and (ed[1] == "env" or ed[1] == "ptree")
        set_edit_position(i - 1, snd, chn)
        return true
      end
    end
    false
  else
    false
  end
end

def opt_test(choice)
  cursnd = Snd.sounds.pick
  curchn = random(channels(cursnd))
  cur_maxamps = []
  cur_edits = []
  cur_frames = []
  all_chans_zipped.each do |s, c|
    cur_maxamps << maxamp(s, c)
    cur_edits << edit_position(s, c)
    cur_frames << frames(s, c)
  end
  cur_amp = maxamp(cursnd, curchn)
  cur_edit = edit_position(cursnd, curchn)
  cur_frame = frames(cursnd, curchn)
  snd_lst, chn_lst = all_chans
  case choice
  when 0 # scale_channel
    scaler = (cur_amp < 1.0) ? (random(1.0) + 1.0) : (random(0.5) + 0.5)
    cur_loc = random(cur_frame)
    cur_samp = sample(cur_loc, cursnd, curchn)
    scale_channel(scaler, 0, cur_frame, cursnd, curchn)
    if (res = edit_position(cursnd, curchn)) != cur_edit + 1 and res != cur_edit
      snd_display("scale_channel %s[%s] edit pos: %s %s?",
                  short_file_name(cursnd), curchn, res, cur_edit)
    end
    if (res = frames(cursnd, curchn)) != cur_frame
      snd_display("scale_channel %s[%s] frames: %s %s?",
                  short_file_name(cursnd), curchn, res, cur_frame)
    end
    if fneq(res1 = maxamp(cursnd, curchn), res2 = scaler * cur_amp)
      snd_display("scale_channel %s[%s] maxamp: %s %s (%s, scaler: %s)?",
                  short_file_name(cursnd), curchn, res1, res2, (res1 - res2).abs, scaler)
    end
    if fneq(res1 = sample(cur_loc, cursnd, curchn), res2 = scaler * cur_samp)
      snd_display("scale_channel %s[%s] cur_samp: %s %s?",
                  short_file_name(cursnd), curchn, res1, res2)
    end
    snd_lst.zip(chn_lst, cur_maxamps, cur_edits, cur_frames) do |s, c, amp, ed, fr|
      if (not s == cursnd and c == curchn)
        if (res = edit_position(s, c)) != ed
          snd_display("scale_channel %s[%s] wrong edit pos: %s %s?", short_file_name(s), c, res, ed)
        end
        if (res = frames(s, c)) != fr
          snd_display("scale_channel %s[%s] wrong frames: %s %s?", short_file_name(s), c, res, fr)
        end
        if fneq(res = maxamp(s, c), amp)
          snd_display("scale_channel %s[%s] wrong maxamp: %s %s?", short_file_name(s), c, res, amp)
        end
      end
    end
  when 1 # scale_by
    maxscl = cur_maxamps.max
    scaler = (maxscl < 1.0) ? (random(1.0) + 1.0) : (random(0.5) + 0.5)
    scale_by(scaler, cursnd, curchn)
    snd_lst.zip(chn_lst, cur_maxamps, cur_edits, cur_frames) do |s, c, amp, ed, fr|
      if (sync(cursnd) == 0 and (s != cursnd or c != curchn)) or (sync(s) != sync(cursnd))
        if (res = edit_position(s, c)) != ed
          snd_display("scale_by %s[%s] wrong edit pos: %s %s?", short_file_name(s), c, res, ed)
        end
        if (res = frames(s, c)) != fr
          snd_display("scale_by %s[%s] wrong frames: %s %s?", short_file_name(s), c, res, fr)
        end
        if fneq(res = maxamp(s, c), amp)
          snd_display("scale_by %s[%s] wrong maxamp: %s %s?", short_file_name(s), c, res, amp)
        end
      else
        if (res = edit_position(s, c)) != ed + 1 and res != ed
          snd_display("scale_by %s[%s] wrong edit pos: %s %s?", short_file_name(s), c, res, ed + 1)
        end
        if (res = frames(s, c)) != fr
          snd_display("scale_by %s[%s] wrong frames: %s %s?", short_file_name(s), c, res, fr)
        end
        if fneq(res1 = maxamp(s, c), res2 =  amp * scaler)
          snd_display("scale_by %s[%s] wrong maxamp: %s %s?", short_file_name(s), c, res1, res2)
        end
      end
    end
  when 2 # env_channel
    pts = random(6) + 1
    maxpt = 0.0
    x = y = 0.0
    e = make_array(pts)
    0.step(pts - 1, 2) do |i|
      e[i] = x
      if random(3) > 0
        y = random(2.0) - 1.0
      end
      e[i + 1] = y
      if y.abs > maxpt
        maxpt = y.abs
      end
      x += 0.01 + random(1.0)
    end
    if undo_env(cursnd, curchn)
      cur_maxamps = []
      cur_edits = []
      cur_frames = []
      all_chans_zipped.each do |s, c|
        cur_maxamps << maxamp(s, c)
        cur_edits << edit_position(s, c)
        cur_frames << frames(s, c)
        cur_amp = maxamp(cursnd, curchn)
        cur_edit = edit_position(cursnd, curchn)
        cur_frame = frames(cursnd, curchn)
      end
    end
    env_channel(e, 0, cur_frame, cursnd, curchn)
    if (res = edit_position(cursnd, curchn)) != cur_edit + 1 and res != cur_edit
      snd_display("env_channel %s[%s] edit pos: %s %s?",
                  short_file_name(cursnd), curchn, res, cur_edit + 1)
    end
    if (res = frames(cursnd, curchn)) != cur_frame
      snd_display("env_channel %s[%s] frames: %s %s?",
                  short_file_name(cursnd), curchn, res, cur_frame)
    end
    if (res1 = maxamp(cursnd, curchn)) - 0.01 > (res2 = maxpt * cur_amp)
      snd_display("env_channel %s[%s] maxamp: %s %s from %s?",
                  short_file_name(cursnd), curchn, res1, res2, e)
      Snd.throw(:mus_error, "env_channel maxamp", short_file_name(cursnd))
    end
    snd_lst.zip(chn_lst, cur_maxamps, cur_edits, cur_frames) do |s, c, amp, ed, fr|
      unless s == cursnd and c == curchn
        if (res = edit_position(s, c)) != ed
          snd_display("env_channel %s[%s] wrong edit pos: %s %s?", short_file_name(s), c, res, ed)
        end
        if (res = frames(s, c)) != fr
          snd_display("env_channel %s[%s] wrong frames: %s %s?", short_file_name(s), c, res, fr)
        end
        if fneq(res = maxamp(s, c), amp)
          snd_display("env_channel %s[%s] wrong maxamp: %s %s?", short_file_name(s), c, res, amp)
        end
      end
    end
  when 3 # env_sound
    pts = random(6) + 1
    maxpt = 0.0
    x = y = 0.0
    e = make_array(pts)
    0.step(pts - 1, 2) do |i|
      e[i] = x
      if random(3) > 0
        y = random(2.0) - 1.0
      end
      e[i + 1] = y
      if y.abs > maxpt
        maxpt = y.abs
      end
      x += 0.01 + random(1.0)
    end
    recalc = false
    minfr = cur_frames.min
    beg = random((minfr / 2.0).floor)
    all_chans_zipped.each do |s, c|
      unless (sync(cursnd) == 0 and (s != cursnd or c != curchn)) or sync(s) != sync(cursnd)
        recalc = (recalc or undo_env(s, c))
      end
    end
    if recalc
      cur_maxamps = []
      cur_edits = []
      cur_frames = []
      all_chans_zipped.each do |s, c|
        cur_maxamps << maxamp(s, c)
        cur_edits << edit_position(s, c)
        cur_frames << frames(s, c)
      end
      cur_amp = maxamp(cursnd, curchn)
      cur_edit = edit_position(cursnd, curchn)
      cur_frame = frames(cursnd, curchn)
    end
    env_sound(e, beg, [pts, minfr - beg].max, 1.0, cursnd, curchn)
    snd_lst.zip(chn_lst, cur_maxamps, cur_edits, cur_frames) do |s, c, amp, ed, fr|
      if (sync(cursnd) == 0 and (s != cursnd or c != curchn)) or sync(s) != sync(cursnd)
        if (res = edit_position(s, c)) != ed
          snd_display("env_sound %s[%s] wrong edit pos: %s %s?", short_file_name(s), c, res, ed)
        end
        if (res = frames(s, c)) != fr
          snd_display("env_sound %s[%s] wrong frames: %s %s?", short_file_name(s), c, res, fr)
        end
        if fneq(res = maxamp(s, c), amp)
          snd_display("env_sound %s[%s] wrong maxamp: %s %s?", short_file_name(s), c, res, amp)
        end
      else
        if (res = edit_position(s, c)) != ed + 1 and res != ed
          snd_display("env_sound %s[%s] edit pos: %s %s?", short_file_name(s), c, res, ed + 1)
        end
        if (res = frames(s, c)) != fr
          snd_display("env_sound %s[%s] frames: fr %s orig_fr %s?", short_file_name(s), c, res, fr)
        end
      end
    end
  when 4 # scale_sound_by
    maxscl = cur_maxamps.max
    scaler = (maxscl < 1.0) ? (random(1.0) + 1.0) : (random(0.5) + 0.5)
    scale_sound_by(scaler, 1000, 1000, cursnd)
    snd_lst.zip(chn_lst, cur_maxamps, cur_edits, cur_frames) do |s, c, amp, ed, fr|
      if s != cursnd
        if (res = edit_position(s, c)) != ed
          snd_display("scale_sound_by %s[%s] wrong edit pos: %s %s?",
                      short_file_name(s), c, res, ed)
        end
        if (res = frames(s, c)) != fr
          snd_display("scale_sound_by %s[%s] wrong frames: %s %s?", short_file_name(s), c, res, fr)
        end
        if fneq(res = maxamp(s, c), amp)
          snd_display("scale_sound_by %s[%s] wrong maxamp: %s %s?", short_file_name(s), c, res, amp)
        end
      else
        if (res = edit_position(s, c)) != ed + 1 and res != ed
          snd_display("scale_sound_by %s[%s] wrong edit pos: %s %s?",
                      short_file_name(s), c, res, ed + 1)
        end
        if (res = frames(s, c)) != fr
          snd_display("scale_sound_by %s[%s] wrong frames: %s %s?", short_file_name(s), c, res, fr)
        end
      end
    end
  when 5
    if (pos = edit_position(cursnd, curchn)) > 0
      undo_edit(random(pos), cursnd, curchn)
    end
  when 6
    if (len = frames(cursnd, curchn)) > 10000
      delete_samples(random((len / 2).floor), random(100) + 10, cursnd, curchn)
    end
  when 7
    set_samples(random(frames(cursnd, curchn) + 100), random(100) + 10,
                Vct.new(10, 1.0), cursnd, curchn)
  when 8
    insert_samples(random(frames(cursnd, curchn) + 100), random(100) + 10,
                   Vct.new(10, 1.0), cursnd, curchn)
  when 9
    add_mark(random(frames(cursnd, curchn)), cursnd, curchn)
  when 10
    mix_vct(Vct.new(random(100) + 10, random(1.0)),
            random(frames(cursnd, curchn) + 100), cursnd, curchn)
  when 11
    pad_channel(random(frames(cursnd, curchn) + 100), random(100) + 10, cursnd, curchn)
  when 12
    beg = random(frames(cursnd, curchn) - 210)
    dur = random(200) + 10
    preader0 = make_sampler(beg + dur - 1, cursnd, curchn, -1)
    reader0 = make_sampler(beg, cursnd, curchn)
    ptree_channel(lambda do |y| y * 2.0 end, beg, dur, cursnd, curchn, false, true)
    preader1 = make_sampler(beg + dur - 1, cursnd, curchn, -1)
    reader1 = make_sampler(beg, cursnd, curchn)
    dur.times do |i|
      pval0 = preader0.call
      val0 = reader0.call
      pval1 = preader1.call
      val1 = reader1.call
      if fneq(val0 * 2.0, val1) or fneq(pval0 * 2.0, pval1)
        snd_display("read ptree at %s: %s %s %s %s (%s %s %s %s): %s?",
                    i, val0 * 2.0, val1, pval0 * 2.0, pval1,
                    reader0, reader1, preader0, preader1,
                    safe_display_edits(cursnd, curchn))
        Snd_throw(:mus_error, "read ptree at", i)
      end
    end
  when 13
    scale_channel(0.5, random(frames(cursnd, curchn) - 100), random(100) + 10, cursnd, curchn)
  when 14
    beg = random(frames(cursnd, curchn) - 200)
    scale_channel(0.5, beg, random(100) + 10, cursnd, curchn)
    scale_channel(0.5, beg + 10, random(100) + 10, cursnd, curchn)
  when 15
    beg = random(frames(cursnd, curchn) - 200)
    scale_channel(0.5, beg, random(100) + 10, cursnd, curchn)
    scale_channel(2.0, beg, random(100) + 10, cursnd, curchn)
  when 16
    beg = random(frames(cursnd, curchn) - 200)
    pad_channel(beg, random(100) + 10, cursnd, curchn)
    pad_channel(beg + 10, random(100) + 10, cursnd, curchn)
  when 17
    beg = random(frames(cursnd, curchn) - 200)
    pad_channel(beg, random(100) + 10, cursnd, curchn)
    pad_channel(beg, random(100) + 10, cursnd, curchn)
  when 18
    beg = random(frames(cursnd, curchn) - 200)
    delete_sample(beg, cursnd, curchn)
    delete_sample(beg + random(100), cursnd, curchn)
  when 19
    beg = random(frames(cursnd, curchn) + 200)
    set_sample(beg, 0.1, cursnd, curchn)
    set_sample(beg + random(100), 0.2, cursnd, curchn)
  when 20
    beg = random(frames(cursnd, curchn) - 200)
    ramp_channel(random(2.0) - 1.0, random(2.0) - 1.0, beg, random(100) + 10, cursnd, curchn)
  when 21
    pts = random(8) + 1
    maxpt = 0.0
    x = y = 0.0
    e = make_array(pts)
    0.step(pts - 1, 2) do |i|
      e[i] = x
      if random(3) > 0
        y = random(2.0) - 1.0
      end
      e[i + 1] = y
      if y.abs > maxpt
        maxpt = y.abs
      end
      x += 0.01 + random(1.0)
    end
    beg = random(frames(cursnd, curchn) - 300)
    dur = random(200) + 80
    reader0 = make_sampler(beg, cursnd, curchn)
    env_channel(e, beg, dur, cursnd, curchn)
    reader1 = make_sampler(beg, cursnd, curchn)
    en = make_env(:envelope, e, :length, dur)
    dur.times do |i|
      e0 = env(en)
      val00 = reader0.call
      val0 = e0 * val00
      val1 = reader1.call
      if (val0 - val1).abs > 0.005
        delete_file("baddy.rb")
        save_state("baddy.rb")
        snd_display("read env off by %s:\n# (%s) at %s: %s %s (%s %s) [%s %s]\n# %s",
                    (val0 - val1).abs, e, i, val0, val1, reader0, reader1, e0, val00,
                    safe_display_edits(cursnd, curchn))
        Snd_throw(:mus_error, "read env off at", i)
      end
    end
  end
end

def amp_envs_equal?(snd, chn, pos0, pos1, df)
  env0 = channel_amp_envs(snd, chn, pos0)
  env1 = channel_amp_envs(snd, chn, pos1)
  len0 = (env0 and list_p(env0) and env0.length == 2 and env0[1].length)
  len1 = (env1 and list_p(env1) and env1.length == 2 and env1[1].length)
  if len0 and len1
    minlen = [len0, len1].min
    inc0 = len0 / minlen
    inc1 = len1 / minlen
    e0 = env0[1]
    e1 = env1[1]
    if integer?(inc0) and integer?(inc1)
      minlen.times do |i|
        max0 = -1.0
        max1 = -1.0
        if inc0 == 1
          max0 = e0[i]
        else
          inc0.times do |j|
            nmx = e0[j + inc0 * i]
            if nmx > max0
              max0 = nmx
            end
          end
        end
        if inc1 == 1
          max1 = e1[i]
        else
          inc1.times do |j|
            nmx = e1[j + inc1 * i]
            if nmx > max1
              max1 = nmx
            end
          end
        end
        if (max0 - max1).abs > df
          snd_display_prev_caller(snd_format_neq(max0, max1, "amp_env[%d of %d], df %1.4f",
                                                 i, minlen, df))
        end
      end
    else
      snd_display_prev_caller("lens: %s %s?", len0, len1)
    end
  end
  true
end

def vequal_at(v0, v1)
  v0.each_with_index do |val, i|
    if fneq_err(val, v1[i], 0.001) # okay
      return [i, val, v1[i]]
    end
  end
  false
end

def edits_not_equal?(tl0, tl1)
  tl0.each_with_index do |t0, i|
    t1 = tl1[i]
    if t0[0] != t1[0] or
        t0[1] != t1[1] or
        t0[2] != t1[2] or
        t0[3] != t1[3] or
        fneq(t0[4], t1[4]) or
        fneq(t0[5], t1[5]) or
        fneq(t0[6], t1[6])
      return [i, t0, t1]
    end
  end
  false
end

def check_edit_tree(expected_tree, expected_vals, name)
  current_vals = channel2vct
  len = current_vals.length
  if expected_vals and len != expected_vals.length
    snd_display_prev_caller("%s: lengths differ: %s %s?", name, len, expected_vals.length)
  else
    if expected_vals and (not vequal(current_vals, expected_vals))
      snd_display_prev_caller("checking %s, vals disagree (loc, dur, expect):\n# %s?",
                              name, vequal_at(current_vals, expected_vals))
    else
      tree = edit_tree
      if bad_data = edits_not_equal?(tree, expected_tree)
        snd_display_prev_caller("checking %s, trees disagree (loc, cur, expect):\n# %s\n# in %s?",
                                name, bad_data, tree)
      end
      if len > 5
        split_loc = 2 + random(len - 3)
        fread = make_sampler(split_loc)
        bread = make_sampler(split_loc - 1, false, false, -1)
        split_vals = Vct.new(len)
        split_loc.upto(len - 1) do |i| split_vals[i] = fread.call end
        (split_loc - 1).downto(0) do |i| split_vals[i] = bread.call end
        if expected_vals and (not vequal(split_vals, expected_vals))
          snd_display_prev_caller("checking %s, split vals disagree (loc, cur, expect):\n# %s?",
                                  name, vequal_at(split_vals, expected_vals))
        end
      end
    end
  end
end

def reversed_read(snd, chn)
  len = frames(snd, chn)
  sf  = make_sampler(len - 1, snd, chn, -1)
  Vct.new(len) do read_sample(sf) end.reverse
end

def init_sound(val, dur, chns)
  ind = new_sound("test.snd", Mus_next, Mus_bshort, 22050, chns)
  chns.times do |chn|
    insert_silence(0, dur, ind, chn)
    map_channel(lambda do |y| val end, 0, frames, ind, chn)
  end
  ind
end

def check_back_and_forth(ind, name, v)
  happy = true
  unless vequal(res = channel2vct(0, frames, ind, 0), v)
    happy = false
    snd_display_prev_caller("%s forth: %s %s?", name, res, v)
  end
  unless vequal(res = reversed_read(ind, 0), v)
    happy = false
    snd_display_prev_caller("%s back: %s %s?", name, res, v)
  end
  happy
end

def check_both_chans(ind, name, f0, f1)
  if (c0 = scan_channel(f0, 0, frames, ind, 0))
    snd_display_prev_caller("%s swap c0: %s?", name, c0)
  end
  if (c1 = scan_channel(f1, 0, frames, ind, 1))
    snd_display_prev_caller("%s swap c1: %s?", name, c1)
  end
end

def test_16_00
  oboe = open_sound("oboe.snd")
  [[lambda { scale_channel(2.0, 0, 0, oboe) }, :scale_channel],
   [lambda { env_channel(make_env([0, 0, 1, 1], :length, 123), 0, 0, oboe) }, :env_channel],
   [lambda { clm_channel(make_oscil, 0, 0, oboe) }, :clm_channel],
   [lambda { vct2channel(make_vct(3), 0, 0, oboe) }, :vct2channel],
   [lambda { smooth_channel(0, 0, oboe) }, :smooth_channel],
   [lambda { pad_channel(0, 0, oboe) }, :pad_channel],
   [lambda { src_channel(2.0, 0, 0, oboe) }, :src_channel],
   [lambda { mix_channel("pistol.snd", 0, 0, oboe) }, :mix_channel],
   [lambda { insert_channel("pistol.snd", 0, 0, oboe) }, :insert_channel],
   [lambda { reverse_channel(0, 0, oboe) }, :reverse_channel],
   [lambda { scale_sound_by(2.0, 0, 0, oboe) }, :scale_sound_by],
   [lambda { env_sound([0, 0, 1, 1], 0, 0, oboe) }, :env_sound],
   [lambda { set_samples(0, 0, Vct.new(3), oboe) }, :set_samples],
   [lambda { smooth_sound(0, 0, oboe) }, :smooth_soundxs],
   [lambda { insert_silence(0, 0, oboe) }, :insert_silence]].each do |func, name|
    func.call
    if (res = edit_position(oboe)) != 0
      snd_display("dur: 0 %s: %s %s?", name, res, edit_fragment)
    end
  end
  [[lambda { scale_channel(2.0, -1, 123, oboe) }, :scale_channel],
   [lambda { env_channel(make_env([0, 0, 1, 1], :length, 123), -1, 123, oboe) }, :env_channel],
   [lambda { clm_channel(make_oscil, -1, 123, oboe) }, :clm_channel],
   [lambda { vct2channel(make_vct(3), -1, 123, oboe) }, :vct2channel],
   [lambda { smooth_channel(-1, 123, oboe) }, :smooth_channel],
   [lambda { pad_channel(-1, 123, oboe) }, :pad_channel],
   [lambda { src_channel(2.0, -1, 123, oboe) }, :src_channel],
   [lambda { mix_channel("pistol.snd", -1, 123, oboe) }, :mix_channel],
   [lambda { insert_channel("pistol.snd", -1, 123, oboe) }, :insert_channel],
   [lambda { reverse_channel(-1, 123, oboe) }, :reverse_channel],
   [lambda { scale_sound_by(2.0, -1, 123, oboe) }, :scale_sound_by],
   [lambda { env_sound([0, 0, 1, 1], -1, 123, oboe) }, :env_sound],
   [lambda { set_samples(-1, 123, Vct.new(3), oboe) }, :set_samples],
   [lambda { smooth_sound(-1, 123, oboe) }, :smooth_soundxs],
   [lambda { insert_silence(-1, 123, oboe) }, :insert_silence]].each do |func, name|
    if (res = Snd.catch do func.call end).first != :no_such_sample
      snd_display("%s beg -1 -> %s", name, res.inspect)
    end
    if (res = edit_position(oboe)) != 0
      snd_display("beg: -1 %s: %s %s?", name, res, edit_fragment)
    end
  end
  [[lambda { scale_channel(2.0, 12345678, 123, oboe) }, :scale_channel],
   [lambda { env_channel(make_env([0, 0, 1, 1], :length, 123), 12345678, 123, oboe) }, :env_channel],
   [lambda { smooth_channel(12345678, 123, oboe) }, :smooth_channel],
   [lambda { src_channel(2.0, 12345678, 123, oboe) }, :src_channel],
   [lambda { reverse_channel(12345678, 123, oboe) }, :reverse_channel]].each do |func, name|
    func.call
    if (res = edit_position(oboe)) != 0
      snd_display("beg: 12345678 %s: %s %s?", name, res, edit_fragment)
    end
  end
  pos = 0
  [[lambda { scale_channel(2.0, 0, 123, oboe, 0) }, :scale_channel],
   [lambda { env_channel(make_env([0, 0, 1, 1], :length, 123), 0, 123, oboe, 0) }, :env_channel],
   [lambda { clm_channel(make_oscil, 0, 123, oboe, 0) }, :clm_channel],
   [lambda { vct2channel(make_vct(3), 0, 123, oboe, 0) }, :vct2channel],
   [lambda { smooth_channel(0, 123, oboe, 0) }, :smooth_channel],
   [lambda { pad_channel(0, 123, oboe, 0) }, :pad_channel],
   [lambda { src_channel(2.0, 0, 123, oboe, 0) }, :src_channel],
   [lambda { mix_channel("pistol.snd", 0, 123, oboe, 0) }, :mix_channel],
   [lambda { insert_channel("pistol.snd", 0, 123, oboe, 0) }, :insert_channel],
   [lambda { reverse_channel(0, 123, oboe, 0) }, :reverse_channel],
   [let(rd = make_sampler(0),
        make_src(:srate, 2.0, :input, lambda { |dir| rd.call })) { |rd, sr|
        lambda { clm_channel(sr, 0, 12345, oboe, 0) }
      }, "clm_channel src"],
   [let(rd = make_sampler(0),
        make_granulate(:expansion, 2.0, :input, lambda { |dir| rd.call })) { |rd, gr|
        lambda { clm_channel(gr, 0, 12345, oboe, 0) }
      }, "clm_channel granulate"],
   [let(rd = make_sampler(0),
        flt = [1, 0, 0, 0].to_vct,
        make_convolve(:input, lambda { |dir| rd.call }, :filter, flt)) { |rd, flt, cv|
        lambda { clm_channel(cv, 0, 12345, oboe, 0) }
      }, "clm_channel convolve"],
   [let(rd = make_sampler(0),
        make_phase_vocoder(:input, lambda { |dir| rd.call })) { |rd, pv|
        lambda { clm_channel(pv, 0, 12345, oboe, 0) }
      }, "clm_channel phase_vocoder"]].each do |func, name|
    func.call
    if (res = edit_position(oboe)) != pos += 1
      snd_display("%s[%s]: %s %s?", name, pos, res, edit_fragment)
    end
  end
  #
  revert_sound(oboe)
  edpos_fnc = lambda do |hi| false end
  [[lambda { scale_channel(2.0, 0, 123, oboe, 0, edpos_fnc) }, :scale_channel],
   [lambda { env_channel(make_env([0, 0, 1, 1], :length, 123), 0, 123, oboe, 0, edpos_fnc)}, :env_channel],
   [lambda { clm_channel(make_oscil, 0, 123, oboe, 0, edpos_fnc) }, :clm_channel],
   [lambda { vct2channel(make_vct(3), 0, 123, oboe, 0, edpos_fnc) }, :vct2channel],
   [lambda { smooth_channel(0, 123, oboe, 0, edpos_fnc) }, :smooth_channel],
   [lambda { pad_channel(0, 123, oboe, 0, edpos_fnc) }, :pad_channel],
   [lambda { src_channel(2.0, 0, 123, oboe, 0, edpos_fnc) }, :src_channel],
   [lambda { mix_channel("pistol.snd", 0, 123, oboe, 0, edpos_fnc) }, :mix_channel],
   [lambda { insert_channel("pistol.snd", 0, 123, oboe, 0, edpos_fnc) }, :insert_channel],
   [lambda { reverse_channel(0, 123, oboe, 0, edpos_fnc) }, :reverse_channel]].each do |func, name|
    if (res = Snd.catch do func.call end).first != :bad_arity
      snd_display("bad edpos_func %s: %s", name, res.inspect)
    end
    if (res = edit_position(oboe)) != 0
      snd_display("edpos:func %s: %s %s?", name, res, edit_fragment)
    end
  end
  [[lambda { scale_channel(2.0, 0, 123, oboe, 0, 123) }, :scale_channel],
   [lambda { env_channel(make_env([0, 0, 1, 1], :length, 123), 0, 123, oboe, 0, 123)}, :env_channel],
   [lambda { clm_channel(make_oscil, 0, 123, oboe, 0, 123) }, :clm_channel],
   [lambda { vct2channel(make_vct(3), 0, 123, oboe, 0, 123) }, :vct2channel],
   [lambda { smooth_channel(0, 123, oboe, 0, 123) }, :smooth_channel],
   [lambda { pad_channel(0, 123, oboe, 0, 123) }, :pad_channel],
   [lambda { src_channel(2.0, 0, 123, oboe, 0, 123) }, :src_channel],
   [lambda { mix_channel("pistol.snd", 0, 123, oboe, 0, 123) }, :mix_channel],
   [lambda { insert_channel("pistol.snd", 0, 123, oboe, 0, 123) }, :insert_channel],
   [lambda { reverse_channel(0, 123, oboe, 0, 123) }, :reverse_channel]].each do |func, name|
    if (res = Snd.catch do func.call end).first != :no_such_edit
      snd_display("bad edpos %s: %s", name, res.inspect)
    end
    if (res = edit_position(oboe)) != 0
      snd_display("edpos: 123 %s: %s %s?", name, res, edit_fragment)
    end
  end
  revert_sound(oboe)
  oldv = channel2vct(1000, 10, oboe)
  mix_channel("oboe.snd", 0)
  oldv.scale!(2.0)
  unless (res = channel2vct(1000, 10, oboe), oldv)
    snd_display("mix_channel at 0: %s %s?", oldv, res)
  end
  revert_sound(oboe)
  oldv.scale!(0.5)
  insert_channel("oboe.snd", 0)
  unless (res = channel2vct(1000, 10, oboe), oldv)
    snd_display("insert_channel at 0: %s %s?", oldv, res)
  end
  if (res1 = frames(oboe, 0)) != (res2 = frames(oboe, 0, 0)) * 2
    snd_display("insert_channel frames: %s %s?", res1, res2)
  end
  revert_sound(oboe)
  close_sound(oboe)
end

def funcs_equal?(name, func0, func1, oboe0, oboe1)
  func0.call(false, false, oboe0)
  func1.call(false, false, oboe1)
  unless vequal(res1 = channel2vct(1000, 100, oboe0), res2 = channel2vct(1000, 100, oboe1))
    snd_display("%s via false:\n# %s\n# %s?", res1, res2)
  end
  revert_sound(oboe0)
  revert_sound(oboe1)
  select_sound(oboe0)
  func0.call
  select_sound(oboe1)
  func1.call
  unless vequal(res1 = channel2vct(1000, 100, oboe0), res2 = channel2vct(1000, 100, oboe1))
    snd_display("%s via none:\n# %s\n# %s?", res1, res2)
  end
  revert_sound(oboe0)
  revert_sound(oboe1)
  func0.call(0, frames(oboe0), oboe0)
  func1.call(0, frames(oboe1), oboe1)
  unless vequal(res1 = channel2vct(1000, 100, oboe0), res2 = channel2vct(1000, 100, oboe1))
    snd_display("%s via 0 frames:\n# %s\n# %s?", res1, res2)
  end
  revert_sound(oboe0)
  revert_sound(oboe1)
end

def test_16_01
  if default_output_chans != 1
    set_default_output_chans(1)
  end
  ind = new_sound("fmv.snd")
  v0 = Vct.new(20, 1.0)
  vct2channel(v0)
  if frames != 20
    snd_display("vct2channel new 20: %s?", frames)
  end
  if fneq(maxamp, 1.0)
    snd_display("vct 1->new: %s?", maxamp)
  end
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 1], :base, 0, :length, 20))
  if (res = channel2vct) != vct(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
    snd_display("env_channel step 1: %s?", res)
  end
  undo_edit
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 1], :base, 0, :length, 20), 8)
  if (res = channel2vct) != vct(1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1)
    snd_display("env_channel step 1 at 8: %s?", res)
  end
  undo_edit
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 1], :base, 0, :length, 12))
  if (res = channel2vct) != vct(0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
    snd_display("env_channel step 1 at 0: %s?", res)
  end
  undo_edit
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 1], :base, 0, :length, 12), 4)
  if (res = channel2vct) != vct(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
    snd_display("env_channel step 1 at 4: %s?", res)
  end
  undo_edit
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 1], :base, 0, :length, 12), 4, 3)
  if (res = channel2vct) != vct(1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
    snd_display("env_channel step 1 at 4 by 3: %s?", res)
  end
  undo_edit
  env_channel(make_env(:envelope, [0, 1, 1, 0, 2, 0], :base, 0, :length, 8), 0, 12)
  if (res = channel2vct) != vct(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1)
    snd_display("env_channel step 1 at 0 for 7: %s?", res)
  end
  undo_edit
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 1, 3, 0, 4, 0], :base, 0, :length, 20))
  if (res = channel2vct) != vct(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0)
    snd_display("env_channel step 1: %s?", res)
  end
  undo_edit
  env_channel(make_env(:envelope, [0, 0, 1, 0.5, 2, 0.25, 3, 0, 4, 0], :base, 0, :length, 21))
  if (res = channel2vct) != vct(0, 0, 0, 0, 0, 0, 0.5, 0.5, 0.5, 0.5,
                                0.5, 0.25, 0.25, 0.25, 0.25, 0.25, 0, 0, 0, 0)
    snd_display("env_channel step 1 (0.5): %s?", res)
  end
  close_sound(ind)
  #
  set_x_axis_style(X_axis_as_percentage)
  ind = open_sound("2.snd")
  fr = frames
  m0 = maxamp(ind, 0)
  m1 = maxamp(ind, 1)
  set_sync(64, ind)
  insert_sound("2.snd")
  insert_sound("2.snd")
  if frames != fr * 3
    snd_display("2.snd 3x = %s %s?", fr, frames)
  end
  if (res1 = frames(ind, 0)) != (res2 = frames(ind, 1))
    snd_display("insert synced: %s %s?", res1, res2)
  end
  swap_channels
  if fneq(res1 = maxamp(ind, 1), m0) or fneq(res2 = maxamp(ind, 0), m1)
    snd_display("swapped: %s %s -> %s %s?", m0, m1, res1, res2)
  end
  close_sound(ind)
  set_x_axis_style(X_axis_in_seconds)
  new_snd = mono_files2stereo("test.snd", "oboe.snd", "pistol.snd")
  if channels(new_snd) != 2
    snd_display("mono_files2stereo not stereo: %s?", channels(new_snd))
  end
  if (res = short_file_name(new_snd)) != "test.snd"
    snd_display("mono_files2stereo filename: %s?", res)
  end
  if (res = frames(new_snd)) != 50828
    snd_display("mono_files2stereo frames: %s?", res)
  end
  close_sound(new_snd)
  #
  oboe0 = open_sound("oboe.snd")
  oboe1 = open_sound("oboe.snd")
  funcs_equal?(:scale_sound_by,
               lambda { |*args| scale_sound_by(2.0, *args)},
               lambda { |*args| scale_channel(2.0, *args)},
               oboe0, oboe1)
  funcs_equal?(:scale_and_ramp_0,
               lambda { |*args| scale_sound_by(0.0, *args)},
               lambda { |*args| ramp_channel(0.0, 0.0, *args)},
               oboe0, oboe1)
  funcs_equal?(:scale_and_ramp_2,
               lambda { |*args| scale_sound_by(2.0, *args)},
               lambda { |*args| ramp_channel(2.0, 2.0, *args)},
               oboe0, oboe1)
  funcs_equal?(:smooth_sound,
               lambda { |*args| smooth_sound(*args)},
               lambda { |*args| smooth_channel(*args)},
               oboe0, oboe1)
  funcs_equal?(:env_sound,
               lambda { |*args|
                 beg = (args[0] or 0)
                 len = (args[1] and number?(args[1])) ? (args[1] - 1) : false
                 snd = (args[2] or selected_sound)
                 env_sound([0, 0, 1, 1], beg, len, 1.0, snd)
               },
               lambda { |*args|
                 snd = (args[2] or selected_sound)
                 len = (args[1] and number?(args[1])) ? args[1] : (frames(snd) - 1)
                 env_channel(make_env(:envelope, [0, 0, 1, 1], :length, len), *args)
               },
               oboe0, oboe1)
  funcs_equal?(:map_chan,
               lambda { |*args|
                 beg = (args[0] or 0)
                 len = (args[1] and number?(args[1])) ? (args[1] - 1) : false
                 snd = (args[2] or selected_sound)
                 map_chan(lambda { |n| n * 2 }, beg, len, "testing...", snd)
               },
               lambda { |*args|
                 beg = (args[0] or 0)
                 len = (args[1] and number?(args[1])) ? (args[1] - 1) : false
                 snd = (args[2] or selected_sound)
                 map_channel(lambda { |n| n * 2 }, beg, len, snd)
               }, oboe0, oboe1)
  funcs_equal?(:src_sound,
               lambda { |*args| src_sound(2.0, 1.0, (args[2] or false)) },
               lambda { |*args| src_channel(2.0, *args) },
               oboe0, oboe1)
  funcs_equal?(:reverse_sound,
               lambda { |*args| reverse_sound((args[2] or false)) },
               lambda { |*args| reverse_channel(*args) },
               oboe0, oboe1)
  funcs_equal?(:mix,
               lambda { |*args| mix("pistol.snd", 0, 0, (args[2] or false)) },
               lambda { |*args| mix_channel("pistol.snd", *args) },
               oboe0, oboe1)
  funcs_equal?(:insert_sound,
               lambda { |*args| insert_sound("pistol.snd", 0, 0, (args[2] or false)) },
               lambda { |*args| insert_channel("pistol.snd", *args) },
               oboe0, oboe1)
  close_sound(oboe0)
  close_sound(oboe1)
  #
  ind = open_sound("oboe.snd")
  scale_by(0.5)
  scale_by(0.25)
  undo_edit
  [[lambda { |n| scale_channel(0.5, 0, false, n, 0, 2) }, :scale_channel],
   [lambda { |n| env_channel([0, 0, 1, 1, 2, 0], 0, false, n, 0, 2) }, :env_channel],
   [lambda { |n| pad_channel(0, 100, n, 0, 2) }, :pad_channel_0],
   [lambda { |n| pad_channel(100, 100, n, 0, 2) }, :pad_channel_100],
   [lambda { |n| delete_sample(100, n, 0, 2) }, :delete_sample],
   [lambda { |n| set_sample(100, 0.52, n, 0, 2) }, :set_sample]].each do |func, name|
    if (res = Snd.catch do func.call(ind) end).first != :no_such_edit
      snd_display("%s upon about-to-be-clobbered data: %s", name, res.inspect)
    end
  end
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  ind1 = new_sound("test.snd")
  old_save_dir = save_dir
  set_save_dir(false)
  map_channel(lambda do |y| 0.5 end, 0, 100, ind1, 0)
  save_sound(ind1)
  close_sound(ind1)
  insert_sound("test.snd", 12345)
  delete_file("hiho.rb")
  vals = channel2vct(12345 - 50, 200, ind, 0)
  save_state("hiho.rb")
  close_sound(ind)
  Snd.regions.apply(:forget_region)
  load("hiho.rb")
  ind = find_sound("oboe.snd")
  if sound?(ind)
    unless vequal(res = channel2vct(12345 - 50, 200, ind, 0), vals)
      snd_display("save_state hiho vals:\n# %s\n# %s?", vals, res)
    end
  else
    snd_display("save hiho failed?")
  end
  close_sound(ind)
  set_save_dir(old_save_dir)
  #
  set_x_axis_style(X_axis_in_beats)
  ind = open_sound("storm.snd")
  reverse_channel(500000, 1000000)
  set_sample(0, 0.1, ind, 0, Current_edit_position)
  if fneq(res = sample(0, ind, 0, Current_edit_position), 0.1)
    snd_display("set_sample + edpos: %s?", res)
  end
  close_sound(ind)
  set_x_axis_style(X_axis_in_seconds)
  #
  [1, 2, 4].each do |out_chans|
    ind = new_sound("new.snd", Mus_next, Mus_bfloat, 22050, out_chans, "edpos testing")
    mx = Snd.sounds.map do |s| sync(s) end.max
    set_sync(mx + 1, ind)
    ["2a.snd", "1a.snd", "4a.snd"].each do |in_snd|
      [lambda { |posfunc|
         chn = [random(out_chans + 1), out_chans - 1].min
         unless vequal(res = channel2vct(0, frames(ind, chn), ind, chn, 0), [0.0].to_vct)
           snd_display("start bad: %s?", res)
         end
         set_sample(0, 0.1, ind, chn)
         unless vequal(res = channel2vct(0, frames(ind, chn), ind, chn), [0.1].to_vct)
           snd_display("set bad: %s?", res)
         end
         pad_channel(0, 1, ind, chn, posfunc.call)
         if proc?(pos = posfunc.call)
           pos = pos.call(ind, chn)
         end
         data = channel2vct(0, frames(ind, chn), ind, chn)
         if (pos.zero? and (not vequal(data, [0.0, 0.0].to_vct))) or
             ((pos == Current_edit_position or pos == edit_position(ind, chn)) and
              (not vequal(data, [0.0, 0.1].to_vct))) or
             (pos == edit_position(ind, chn) - 1 and
              (not vequal(data, [0.0, 0.0].to_vct)))
           snd_display("pos[%s]: edpos %s of %s, pad result[%s, %s]: %s?",
                       chn, pos,
                       edit_position(ind, chn),
                       frames(ind, chn, pos),
                       frames(ind, chn),
                       data)
         end
         if channels(ind) > 1
           channels(ind).times do |i|
             next if chn == i
             unless vequal(res = channel2vct(0, frames(ind, i), ind, i), [0.0].to_vct)
               snd_display("pad[%s / %s] empty: %s?", i, chn, data)
             end
           end
         end
       },
       lambda { |posfunc|
         chn = [random(out_chans + 1), out_chans - 1].min
         set_sample(0, 0.1, ind, chn)
         set_sample(0, sample(0, ind, chn, posfunc.call()) * 2.0, ind, chn, posfunc.call())
         if proc?(pos = posfunc.call)
           pos = pos.call(ind, chn)
         end
         data = channel2vct(0, frames(ind, chn), ind, chn)
         if (pos.zero? and (not vequal(data, [0.0].to_vct))) or
             ((pos == Current_edit_position or pos == edit_position(ind, chn)) and
              (not vequal(data, [0.2].to_vct))) or
             (pos == edit_position(ind, chn) - 1 and
              (not vequal(data, [0.0].to_vct)))
           snd_display("pos[%s]: edpos %s of %s, set *2 result[%s, %s]: %s?",
                       chn, pos,
                       edit_position(ind, chn),
                       frames(ind, chn, pos),
                       frames(ind, chn),
                       data)
         end
         if channels(ind) > 1
           channels(ind).times do |i|
             next if chn == i
             unless vequal(res = channel2vct(0, frames(ind, i), ind, i), [0.0].to_vct)
               snd_display("scale[%s / %s] empty: %s?", i, chn, data)
             end
           end
         end
       }].each do |func|
        [lambda { Current_edit_position },
         lambda { 0 },
         lambda { lambda { |s, c| edit_position(s, c) - 1 } },
         lambda { lambda { |s, c| edit_position(s, c) } },
         lambda { lambda { |s, c| Current_edit_position } },
         lambda { lambda { |s, c| 0 } }].each do |edpos|
          func.call(edpos)
          revert_sound(ind)
        end
      end
    end
    close_sound(ind)
  end
  #
  ind = open_sound("oboe.snd")
  map_channel(lambda do |y| false end)
  if frames(ind) != 0
    snd_display("map_channel false frames: %s?", frames(ind))
  end
  if edits(ind) == [0, 0]
    snd_display("map_channel false edits backed up")
  end
  undo_edit(1, ind)
  if frames(ind) == 0
    snd_display("map_channel false frames after undo: %s?", frames(ind))
  end
  if (res = Snd.catch do map_channel(lambda do |y| "hiho" end) end).first != :bad_type
    snd_display("map_channel bad_type: %s", res.inspect)
  end
  ctr = 0
  if (res = Snd.catch do
        scan_channel(lambda do |y|
                       ctr += 1
                       asdf
                     end)
      end).first != :name_error
    snd_display("scan_channel unbound: %s", res.inspect)
  end
  if ctr != 1
    snd_display("scan_channel error exit: %s?", ctr)
  end
  if res = scan_channel(lambda do |y| false end)
    snd_display("scan_channel func false: %s?", res)
  end
  if res = scan_channel(lambda do |y| false end, 1234)
    snd_display("scan_channel func false with beg: %s?", res)
  end
  if res = scan_channel(lambda do |y| false end, 1234, 4321)
    snd_display("scan_channel func false with beg+dur: %s?", res)
  end
  revert_sound(ind)
  del = make_delay(1000)
  len = frames
  clm_channel(del, 0, len, ind, 0, 0, 2000)
  if frames(ind) != len + 2000
    snd_display("clm_channel overlap length: %s %s?", len, frames)
  end
  if edit_tree != [[0, 1, 0, 52827, 1.0, 0.0, 0.0, 0], [52828, -2, 0, 0, 0.0, 0.0, 0.0, 0]]
    snd_display("clm_channel overlaps: %s?", edit_tree)
  end
  reader = make_sampler(0)
  preader = make_sampler(0, ind, 0, 1, 0)
  1000.times do |i|
    if fneq(res = reader.call, 0.0)
      snd_display("clm_channel overlap delayed[%s]: %s?", i, res)
      break
    end
  end
  len.times do |i|
    if fneq(res1 = reader.call, res2 = preader.call)
      snd_display("clm_channel overlap main[%s]: %s %s?", i + 1000, res1, res2)
      break
    end
  end
  1000.times do |i|
    if fneq(res = reader.call, 0.0)
      snd_display("clm_channel overlap trailing garbage[%s]: %s?", i, res)
      break
    end
  end
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  oldamp = 0.0
  oldloc = 0
  ctr = 0
  scan_channel(lambda { |y|
                 if y.abs >= oldamp
                   oldloc = ctr
                   oldamp = y.abs
                 end
                 ctr += 1
                 false
               })
  scale_by(10.0)
  scale_by(0.1)
  reverse_channel(0, false, ind, 0, 1)
  amp = 0.0
  loc = 0
  ctr = frames - 1
  scan_channel(lambda { |y|
                 if y.abs > amp
                   amp = y.abs
                   loc = ctr
                 end
                 ctr -= 1
                 false
               })
  if fneq(oldamp, amp * 0.1) or loc != oldloc
    snd_display("reverse edpos screwup: %s at %s, %s at %s?", oldamp, oldloc, amp, loc)
  end
  reverse_channel(0, false, ind, 0, 2)
  amp = 0.0
  loc = 0
  ctr = frames - 1
  scan_channel(lambda { |y|
                 if y.abs > amp
                   amp = y.abs
                   loc = ctr
                 end
                 ctr -= 1
                 false
               })
  if fneq(oldamp, amp) or loc != oldloc
    snd_display("reverse unscaled edpos screwup: %s at %s, %s at %s?", oldamp, oldloc, amp, loc)
  end
  close_sound(ind)
end

class Mus
  alias old_call call
  alias call run
end

def check_env(name, r, e, dur)
  dur.times do |i|
    if fneq(rv = r.call, ev = e.call)
      snd_display("%s env check [%s]: %s %s?", name, i, rv, ev)
      return
    end
  end
end

def test_16_02
  mus_clipping and set_mus_clipping(false)
  clipping and set_clipping(false)
  ind = new_sound("fmv.snd", Mus_next, Mus_bfloat, 22050, 1,"edit trees") 
  select_sound(ind)
  select_channel(0)
  check_edit_tree([[0, 0, 0, 0, 0.0, 0.0, 0.0, 1],
                   [1, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  Vct.new(1), "initial new_sound")
  vals = Vct.new(100, 1.0)
  set_samples(0, 100, vals)
  check_edit_tree([[0, 1, 0, 99, 1.0, 0.0, 0.0, 1],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "set first samps to one")
  scale_channel(0.5, 10, 20)
  10.upto(29) do |i| vals[i] = 0.5 end
  check_edit_tree([[0, 1, 0, 9, 1.0, 0.0, 0.0, 1],
                   [10, 1, 10, 29, 0.5, 0.0, 0.0, 0],
                   [30, 1, 30, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "scale_channel(0.5, 10, 20)")
  env_channel(make_env(:envelope, [0, 0, 1, 1], :length, 11), 15, 10)
  e = make_env(:envelope, [0, 0, 1, 1], :length, 11)
  15.upto(24) do |i| vals[i] *= env(e) end
  check_edit_tree([[0, 1, 0, 9, 1.0, 0.0, 0.0, 0],
                   [10, 1, 10, 14, 0.5, 0.0, 0.0, 0],
                   [15, 1, 15, 24, 0.5, 0.0, 0.1, 1],
                   [25, 1, 25, 29, 0.5, 0.0, 0.0, 0],
                   [30, 1, 30, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env_channel(15, 10)")
  normalize_channel(1.0)
  check_edit_tree([[0, 1, 0, 9, 1.0, 0.0, 0.0, 0],
                   [10, 1, 10, 14, 0.5, 0.0, 0.0, 0],
                   [15, 1, 15, 24, 0.5, 0.0, 0.1, 1],
                   [25, 1, 25, 29, 0.5, 0.0, 0.0, 0],
                   [30, 1, 30, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env_channel(15, 10) a")
  select_all
  if fneq(res = selection_maxamp, 1.0)
    snd_display("selection_maxamp in checker: %s?", res)
  end
  scale_selection_to(1.0)
  check_edit_tree([[0, 1, 0, 9, 1.0, 0.0, 0.0, 0],
                   [10, 1, 10, 14, 0.5, 0.0, 0.0, 0],
                   [15, 1, 15, 24, 0.5, 0.0, 0.1, 1],
                   [25, 1, 25, 29, 0.5, 0.0, 0.0, 0],
                   [30, 1, 30, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env_channel(15, 10) b")
  set_selection_position(5)
  set_selection_frames(10)
  scale_selection_to(0.5)
  5.upto(14) do |i| vals[i] *= 0.5 end
  check_edit_tree([[0, 1, 0, 4, 1.0, 0.0, 0.0, 0],
                   [5, 1, 5, 9, 0.5, 0.0, 0.0, 0],
                   [10, 1, 10, 14, 0.25, 0.0, 0.0, 0],
                   [15, 1, 15, 24, 0.5, 0.0, 0.1, 1],
                   [25, 1, 25, 29, 0.5, 0.0, 0.0, 0],
                   [30, 1, 30, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "scale_selection_to(0.5)")
  set_sample(20, 0.1)
  vals[20] = 0.1
  check_edit_tree([[0, 1, 0, 4, 1.0, 0.0, 0.0, 0],
                   [5, 1, 5, 9, 0.5, 0.0, 0.0, 0],
                   [10, 1, 10, 14, 0.25, 0.0, 0.0, 0],
                   [15, 1, 15, 19, 0.5, 0.0, 0.1, 1],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 29, 0.5, 0.0, 0.0, 0],
                   [30, 1, 30, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "set_sample(20, 0.1)")
  reverse_channel(5, 10)
  j = 14
  5.upto(9) do |i|
    vals[i], vals[j] = vals[j], vals[i]
    j -= 1
  end
  check_edit_tree([[0, 1, 0, 4, 1.0, 0.0, 0.0, 0],
                   [5, 3, 0, 9, 1.0, 0.0, 0.0, 0],
                   [15, 1, 15, 19, 0.5, 0.0, 0.1, 1],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 29, 0.5, 0.0, 0.0, 0],
                   [30, 1, 30, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "reverse_channel(5, 10)")
  if fneq(res = selection_maxamp, 0.5)
    snd_display("selection_maxamp before: %s?", res)
  end
  mixvals = Vct.new(10, 0.1)
  old_sample4 = sample(4)
  old_sample5 = sample(5)
  id = mix_vct(mixvals, 4)
  4.upto(13) do |i| vals[i] += mixvals[i - 4] end
  check_edit_tree([[0, 1, 0, 3, 1.0, 0.0, 0.0, 0],
                   [4, 1, 4, 4, 1.0, 0.0, 0.0, 1],
                   [5, 3, 0, 8, 1.0, 0.0, 0.0, 1],
                   [14, 3, 9, 9, 1.0, 0.0, 0.0, 0],
                   [15, 1, 15, 19, 0.5, 0.0, 0.1, 4],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 29, 0.5, 0.0, 0.0, 0],
                   [30, 1, 30, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals,
                  format("mix_vct(id: %s (%s) [%s, %s] + 0.1 -> [%s, %s] [%s, %s]) 4 (0.1)",
                         id, ind, old_sample4, old_sample5, sample(4), sample(5), vals[4], vals[5]))
  delete_samples(28, 12)
  insert_silence(28, 12)
  28.upto(39) do |i| vals[i] = 0.0 end
  old_vals = vals.dup
  check_edit_tree([[0, 1, 0, 3, 1.0, 0.0, 0.0, 0],
                   [4, 1, 4, 4, 1.0, 0.0, 0.0, 1],
                   [5, 3, 0, 8, 1.0, 0.0, 0.0, 1],
                   [14, 3, 9, 9, 1.0, 0.0, 0.0, 0],
                   [15, 1, 15, 19, 0.5, 0.0, 0.1, 4],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 27, 0.5, 0.0, 0.0, 0],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 0],
                   [40, 1, 40, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "delete_samples(28, 12); insert_silence(28, 12)")
  if fneq(res = selection_maxamp, 0.6)
    snd_display("selection_maxamp after: %s?", res)
  end
  set_selection_position(50)
  set_selection_frames(10)
  scale_selection_by(0.1)
  if fneq(res = selection_maxamp, 0.1)
    snd_display("re-selection_maxamp: %s?", res)
  end
  50.upto(59) do |i| vals[i] = 0.1 end
  check_edit_tree([[0, 1, 0, 3, 1.0, 0.0, 0.0, 0],
                   [4, 1, 4, 4, 1.0, 0.0, 0.0, 1],
                   [5, 3, 0, 8, 1.0, 0.0, 0.0, 1],
                   [14, 3, 9, 9, 1.0, 0.0, 0.0, 0],
                   [15, 1, 15, 19, 0.5, 0.0, 0.1, 1],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 27, 0.5, 0.0, 0.0, 0],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 0],
                   [40, 1, 40, 49, 1.0, 0.0, 0.0, 0],
                   [50, 1, 50, 59, 0.1, 0.0, 0.0, 0],
                   [60, 1, 60, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "scale_selection_by(0.1)")
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 0, 3, 0], :length, 31, :base, 0), 50, 30)
  e = make_env(:envelope, [0, 0, 1, 1, 2, 0, 3, 0], :length, 31, :base, 0)
  50.upto(79) do |i| vals[i] *= env(e) end
  check_edit_tree([[0, 1, 0, 3, 1.0, 0.0, 0.0, 0],
                   [4, 1, 4, 4, 1.0, 0.0, 0.0, 1],
                   [5, 3, 0, 8, 1.0, 0.0, 0.0, 1],
                   [14, 3, 9, 9, 1.0, 0.0, 0.0, 0],
                   [15, 1, 15, 19, 0.5, 0.0, 0.1, 4],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 27, 0.5, 0.0, 0.0, 0],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 0],
                   [40, 1, 40, 49, 1.0, 0.0, 0.0, 0],
                   [50, 1, 50, 59, 0.0, 0.0, 0.0, 0],
                   [60, 1, 60, 60, 0.0, 0.0, 0.0, 0],
                   [61, 1, 61, 70, 1.0, 0.0, 0.0, 0],
                   [71, 1, 71, 79, 0.0, 0.0, 0.0, 0],
                   [80, 1, 80, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "step env 30")
  undo_channel(2)
  check_edit_tree([[0, 1, 0, 3, 1.0, 0.0, 0.0, 0],
                   [4, 1, 4, 4, 1.0, 0.0, 0.0, 1],
                   [5, 3, 0, 8, 1.0, 0.0, 0.0, 1],
                   [14, 3, 9, 9, 1.0, 0.0, 0.0, 0],
                   [15, 1, 15, 19, 0.5, 0.0, 0.1, 4],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 27, 0.5, 0.0, 0.0, 0],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 0],
                   [40, 1, 40, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  old_vals, "undo to delete/insert (over step env)")
  redo_channel(2)
  check_edit_tree([[0, 1, 0, 3, 1.0, 0.0, 0.0, 0],
                   [4, 1, 4, 4, 1.0, 0.0, 0.0, 1],
                   [5, 3, 0, 8, 1.0, 0.0, 0.0, 1],
                   [14, 3, 9, 9, 1.0, 0.0, 0.0, 0],
                   [15, 1, 15, 19, 0.5, 0.0, 0.1, 4],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 27, 0.5, 0.0, 0.0, 0],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 0],
                   [40, 1, 40, 49, 1.0, 0.0, 0.0, 0],
                   [50, 1, 50, 59, 0.0, 0.0, 0.0, 0],
                   [60, 1, 60, 60, 0.0, 0.0, 0.0, 0],
                   [61, 1, 61, 70, 1.0, 0.0, 0.0, 0],
                   [71, 1, 71, 79, 0.0, 0.0, 0.0, 0],
                   [80, 1, 80, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "redo past step env 30")
  set_sample(75, -0.5)
  vals[75] = -0.5
  flt = make_one_zero(0.5, 0.5)
  flt1 = make_one_zero(0.5, 0.5)
  clm_channel(flt, 75, 10)
  75.upto(84) do |i| vals[i] = one_zero(flt1, vals[i]) end
  check_edit_tree([[0, 1, 0, 3, 1.0, 0.0, 0.0, 0],
                   [4, 1, 4, 4, 1.0, 0.0, 0.0, 1],
                   [5, 3, 0, 8, 1.0, 0.0, 0.0, 1],
                   [14, 3, 9, 9, 1.0, 0.0, 0.0, 0],
                   [15, 1, 15, 19, 0.5, 0.0, 0.1, 4],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 27, 0.5, 0.0, 0.0, 0],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 0],
                   [40, 1, 40, 49, 1.0, 0.0, 0.0, 0],
                   [50, 1, 50, 59, 0.0, 0.0, 0.0, 0],
                   [60, 1, 60, 60, 0.0, 0.0, 0.0, 0],
                   [61, 1, 61, 70, 1.0, 0.0, 0.0, 0],
                   [71, 1, 71, 74, 0.0, 0.0, 0.0, 0],
                   [75, 6, 0, 9, 1.0, 0.0, 0.0, 0],
                   [85, 1, 85, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "clm_channel(flt, 75, 10)")
  map_channel(lambda do |y| y * 0.5 end, 3, 11)
  3.upto(13) do |i| vals[i] *= 0.5 end
  check_edit_tree([[0, 1, 0, 2, 1.0, 0.0, 0.0, 0],
                   [3, 8, 0, 10, 1.0, 0.0, 0.0, 0],
                   [14, 7, 10, 11, 1.0, 0.0, 0.0, 0],
                   [16, 1, 16, 19, 0.5, 0.1, 0.1, 4],
                   [20, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [21, 1, 21, 24, 0.5, 0.6, 0.1, 4],
                   [25, 1, 25, 27, 0.5, 0.0, 0.0, 0],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 2],
                   [40, 1, 40, 49, 1.0, 0.0, 0.0, 0],
                   [50, 1, 50, 59, 0.0, 0.0, 0.0, 0],
                   [60, 1, 60, 60, 0.0, 0.0, 0.0, 0],
                   [61, 1, 61, 70, 1.0, 0.0, 0.0, 0],
                   [71, 1, 71, 74, 0.0, 0.0, 0.0, 0],
                   [75, 6, 0, 9, 1.0, 0.0, 0.0, 0],
                   [85, 1, 85, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "map_channel 3 14")
  reader = make_sampler(50)
  map_channel(lambda do |y| y - reader.call end, 0, 25)
  25.times do |i| vals[i] -= vals[i + 50] end
  check_edit_tree([[0, 9, 0, 24, 1.0, 0.0, 0.0, 0],
                   [25, 1, 25, 27, 0.5, 0.0, 0.0, 0],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 2],
                   [40, 1, 40, 49, 1.0, 0.0, 0.0, 0],
                   [50, 1, 50, 59, 0.0, 0.0, 0.0, 2],
                   [60, 1, 60, 60, 0.0, 0.0, 0.0, 2],
                   [61, 1, 61, 70, 1.0, 0.0, 0.0, 0],
                   [71, 1, 71, 74, 0.0, 0.0, 0.0, 2],
                   [75, 6, 0, 9, 1.0, 0.0, 0.0, 0],
                   [85, 1, 85, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "back set via map_channel")
  set_selection_position(20)
  set_selection_frames(70)
  env_selection([0, 0, 1, 1])
  if fneq(res = selection_maxamp(ind, 0), 1.0)
    snd_display("selection_maxamp after env_selection: %s?", res)
  end
  x = 0.0
  incr = 1.0 / 69.0
  20.upto(89) do |i|
    vals[i] *= x
    x += incr
  end
  check_edit_tree([[0, 9, 0, 19, 1.0, 0.0, 0.0, 0],
                   [20, 9, 20, 24, 1.0, 0.0, 0.0145, 4],
                   [25, 1, 25, 27, 0.5, 0.072, 0.0145, 4],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 2],
                   [40, 1, 40, 49, 1.0, 0.2898, 0.0145, 4],
                   [50, 1, 50, 59, 0.0, 0.0, 0.0, 2],
                   [60, 1, 60, 60, 0.0, 0.0, 0.0, 2],
                   [61, 1, 61, 70, 1.0, 0.5942, 0.0145, 4],
                   [71, 1, 71, 74, 0.0, 0.0, 0.0, 2],
                   [75, 6, 0, 9, 1.0, 0.7971, 0.0145, 4],
                   [85, 1, 85, 89, 1.0, 0.942, 0.0145, 4],
                   [90, 1, 90, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env_selection([0, 0, 1, 1])")
  normalize_channel(0.5)
  vals.scale!(0.5)
  check_edit_tree([[0, 9, 0, 19, 0.5, 0.0, 0.0, 0],
                   [20, 9, 20, 24, 0.5, 0.0, 0.0145, 4],
                   [25, 1, 25, 27, 0.25, 0.0725, 0.0145, 4],
                   [28, -1, 0, 11, 0.0, 0.0, 0.0, 2],
                   [40, 1, 40, 49, 0.5, 0.2898, 0.0145, 4],
                   [50, 1, 50, 59, 0.0, 0.0, 0.0, 2],
                   [60, 1, 60, 60, 0.0, 0.0, 0.0, 2],
                   [61, 1, 61, 70, 0.5, 0.5942, 0.0145, 4],
                   [71, 1, 71, 74, 0.0, 0.0, 0.0, 2],
                   [75, 6, 0, 9, 0.5, 0.7971, 0.0145, 4],
                   [85, 1, 85, 89, 0.5, 0.942, 0.0145, 4],
                   [90, 1, 90, 99, 0.5, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "normalize_channel(0.5)")
  if fneq(res = selection_maxamp, 0.5)
    snd_display("selection_maxamp after scl: %s?", res)
  end
  delete_samples(0, 100)
  insert_silence(0, 100)
  vals.fill(0.0)
  check_edit_tree([[0, -1, 0, 99, 0.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "second set...")
  set_sample(50, 0.5)
  vals[50] = 0.5
  check_edit_tree([[0, -1, 0, 49, 0.0, 0.0, 0.0, 2],
                   [50, 10, 0, 0, 1.0, 0.0, 0.0, 0],
                   [51, -1, 51, 99, 0.0, 0.0, 0.0, 2],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "split silence")
  map_channel($init_channel, 0, 25)
  25.times do |i| vals[i] = 1.0 end
  check_edit_tree([[0, 11, 0, 24, 1.0, 0.0, 0.0, 0],
                   [25, -1, 25, 49, 0.0, 0.0, 0.0, 2],
                   [50, 10, 0, 0, 1.0, 0.0, 0.0, 0],
                   [51, -1, 51, 99, 0.0, 0.0, 0.0, 2],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "clobber silence start")
  map_channel($init_channel, 75, 25)
  75.upto(99) do |i| vals[i] = 1.0 end
  check_edit_tree([[0, 11, 0, 24, 1.0, 0.0, 0.0, 0],
                   [25, -1, 25, 49, 0.0, 0.0, 0.0, 2],
                   [50, 10, 0, 0, 1.0, 0.0, 0.0, 0],
                   [51, -1, 51, 74, 0.0, 0.0, 0.0, 2],
                   [75, 12, 0, 24, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "clobber silence end")
  scale_channel(0.0, 0, 100)
  vals.fill(0.0)
  check_edit_tree([[0, 0, 0, 99, 0.0, 0.0, 0.0, 1],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "scale_channel(0.0, 0, 100)")
  e = make_env(:envelope, [0, 0, 1, 1], :length, 101)
  e1 = make_env(:envelope, [0, 0, 1, 1], :length, 101)
  map_channel(lambda do |y| env(e) end)
  vals.map! do |val| env(e1) end
  check_edit_tree([[0, 13, 0, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env start")
  set_sample(50, -0.5)
  vals[50] = -0.5
  check_edit_tree([[0, 13, 0, 49, 1.0, 0.0, 0.0, 0],
                   [50, 14, 0, 0, 1.0, 0.0, 0.0, 0],
                   [51, 13, 51, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "split env segment")
  map_channel($init_channel, 0, 25)
  25.times do |i| vals[i] = 1.0 end
  check_edit_tree([[0, 15, 0, 24, 1.0, 0.0, 0.0, 0],
                   [25, 13, 25, 49, 1.0, 0.0, 0.0, 0],
                   [50, 14, 0, 0, 1.0, 0.0, 0.0, 0],
                   [51, 13, 51, 99, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "clobber env start")
  map_channel($init_channel, 75, 25)
  75.upto(99) do |i| vals[i] = 1.0 end
  check_edit_tree([[0, 15, 0, 24, 1.0, 0.0, 0.0, 0],
                   [25, 13, 25, 49, 1.0, 0.0, 0.0, 0],
                   [50, 14, 0, 0, 1.0, 0.0, 0.0, 0],
                   [51, 13, 51, 74, 1.0, 0.0, 0.0, 0],
                   [75, 16, 0, 24, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "clobber env end")
  env_channel(make_env(:envelope, [0, 1, 1, 0, 2, 1], :length, 20), 50, 20)
  e = make_env(:envelope, [0, 1, 1, 0, 2, 1], :length, 20)
  50.upto(69) do |i| vals[i] *= env(e) end
  check_edit_tree([[0, 15, 0, 24, 1.0, 0.0, 0.0, 0],
                   [25, 13, 25, 49, 1.0, 0.0, 0.0, 0],
                   [50, 14, 0, 0, 1.0, 1.0, -0.1, 4],
                   [51, 13, 51, 59, 1.0, 0.8999, -0.1, 4],
                   [60, 13, 60, 69, 1.0, 0.0, 0.1111, 4],
                   [70, 13, 70, 74, 1.0, 0.0, 0.0, 0],
                   [75, 16, 0, 24, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env on env")
  env_channel(make_env(:envelope, [0, 1, 1, 0, 2, 1], :length, 80), 10, 80)
  e = make_env(:envelope, [0, 1, 1, 0, 2, 1], :length, 80)
  10.upto(89) do |i| vals[i] *= env(e) end
  check_edit_tree([[0, 15, 0, 9, 1.0, 0.0, 0.0, 0],
                   [10, 15, 10, 24, 1.0, 1.0, -0.025, 4],
                   [25, 13, 25, 49, 1.0, 0.625, -0.025, 4],
                   [50, 14, 0, 0, 1.0, 1.0, -0.1, 6],
                   [51, 13, 51, 59, 1.0, 0.8999, -0.1, 6],
                   [60, 13, 60, 69, 1.0, 0.0, 0.1111, 6],
                   [70, 13, 70, 74, 1.0, 0.5128, 0.025, 4],
                   [75, 16, 0, 14, 1.0, 0.641, 0.026, 4],
                   [90, 16, 15, 24, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env on env 2")
  env_channel(make_env(:envelope, [0, 1, 1, 0, 2, 1], :length, 20), 50, 20)
  e = make_env(:envelope, [0, 1, 1, 0, 2, 1], :length, 20)
  50.upto(69) do |i| vals[i] *= env(e) end
  check_edit_tree([[0, 15, 0, 9, 1.0, 0.0, 0.0, 0],
                   [10, 15, 10, 24, 1.0, 1.0, -0.025, 4],
                   [25, 13, 25, 49, 1.0, 0.625, -0.025, 4],
                   [50, 14, 0, 0, 1.0, 1.0, -0.1, 10],
                   [51, 13, 51, 59, 1.0, 0.8999, -0.1, 10],
                   [60, 13, 60, 69, 1.0, 0.0, 0.1111, 10],
                   [70, 13, 70, 74, 1.0, 0.5128, 0.0256, 4],
                   [75, 16, 0, 14, 1.0, 0.641, 0.0256, 4],
                   [90, 16, 15, 24, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env on env 3")
  delete_samples(10, 20)
  insert_silence(10, 20)
  10.upto(29) do |i| vals[i] = 0.0 end
  check_edit_tree([[0, 15, 0, 9, 1.0, 0.0, 0.0, 0],
                   [10, -1, 0, 19, 0.0, 0.0, 0.0, 2],
                   [30, 13, 30, 49, 1.0, 0.5, -0.025, 4],
                   [50, 14, 0, 0, 1.0, 1.0, -0.1, 10],
                   [51, 13, 51, 59, 1.0, 0.8999, -0.1, 10],
                   [60, 13, 60, 69, 1.0, 0.0, 0.1111, 10],
                   [70, 13, 70, 74, 1.0, 0.5128, 0.026, 4],
                   [75, 16, 0, 14, 1.0, 0.641, 0.026, 4],
                   [90, 16, 15, 24, 1.0, 0.0, 0.0, 0],
                   [100, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env preclobbered")
  close_sound(ind)
  #
  [10, 10000].each do |dur|
    i1 = new_sound
    vct2channel(Vct.new(dur, 1.0))
    env_sound([0, 0, 1, 1])
    check_env(:ramp, make_sampler(0), make_env([0, 0, 1, 1], :length, dur), dur)
    reverse_channel
    check_env(:rev_ramp, make_sampler(0), make_env([0, 1, 1, 0], :length, dur), dur)
    undo_edit(2)
    env_sound([0, 0, 1, 1, 2, 0])
    check_env(:ramp, make_sampler(0), make_env([0, 0, 1, 1, 2, 0], :length, dur), dur)
    cur_read = make_sampler(0)
    reverse_channel
    check_env(:rev_pyr_1, cur_read, make_sampler(dur - 1, i1, 0, -1), dur)
    undo_edit(2)
    env_sound([0, 0, 1, 1, 2, 0, 3, 1])
    check_env(:ramp_3, make_sampler(0), make_env([0, 0, 1, 1, 2, 0, 3, 1],:length, dur), dur)
    cur_read = make_sampler(0)
    reverse_channel
    check_env(:rev_pyr_2, cur_read, make_sampler(dur - 1, i1, 0, -1), dur)
    undo_edit(2)
    env_sound([0, 0, 1, 1, 2, 1, 3, 0])
    check_env(:sqoff, make_sampler(0), make_env([0, 0, 1, 1, 2, 1, 3, 0], :length, dur), dur)
    undo_edit(1)
    env_sound([0, 0, 1, 0.5, 2, 0.5, 3, 0])
    check_env(:sqoff_5, make_sampler(0),
              make_env([0, 0, 1, 0.5, 2, 0.5, 3, 0], :length, dur), dur)
    undo_edit(1)
    scale_channel(0.5)
    env_sound([0, 0, 1, 1])
    check_env(:scl_ramp, make_sampler(0),
              make_env([0, 0, 1, 1], :length, dur, :scaler, 0.5), dur)
    reverse_channel
    check_env(:scl_rev_ramp, make_sampler(0),
              make_env([0, 1, 1, 0], :length, dur, :scaler, 0.5), dur)
    undo_edit(2)
    env_sound([0, 0, 1, 1, 2, 0])
    check_env(:scl_ramp_3, make_sampler(0),
              make_env([0, 0, 1, 1, 2, 0], :length, dur, :scaler, 0.5), dur)
    cur_read = make_sampler(0)
    reverse_channel
    check_env(:scl_rev_pyr, cur_read, make_sampler(dur - 1, i1, 0, -1), dur)
    undo_edit(3)
    #
    if dur == 10000
      [[   0, 1000],
       [   0, 6000],
       [1000, 1000],
       [1000, 4000],
       [4000, 2000],
       [5000, 1000],
       [6000, 1000],
       [5000, 5000]].each do |beg, local_dur|
        env_sound([0, 0, 1, 1, 2, 0])
        scale_channel(0.5, beg, local_dur)
        e = make_env([0, 0, 1, 1, 2, 0], :length, dur)
        ctr = 0
        check_env(:env_and_scl, make_sampler(0),
                  lambda {
                    val = env(e)
                    ctr += 1
                    if ctr.between?(beg + 1, beg + local_dur)
                      val * 0.5
                    else
                      val
                    end
                  }, dur)
        undo_edit(2)
      end
      [[   0, 1000,  500,   200],
       [   0, 6000,    0, 10000],
       [1000, 1000,    0,  1500],
       [1000, 4000, 2000,  1000],
       [4000, 2000, 5000,   500],
       [5000, 1000, 4000,  2000],
       [6000, 1000,    0,  2000],
       [5000, 5000, 8000,  2000]].each do |env_beg, env_dur, scl_beg, scl_dur|
        env_channel([0, 0, 1, 1, 2, 1, 3, 0], env_beg, env_dur)
        scale_channel(0.5, scl_beg, scl_dur)
        e = make_env([0, 0, 1, 1, 2, 1, 3, 0], :length, env_dur)
        ctr = 0
        check_env(:env_scl_partial, make_sampler(0),
                  lambda {
                    val = 1.0
                    ctr += 1
                    if ctr.between?(env_beg + 1, env_beg + env_dur)
                      val *= env(e)
                    end
                    if ctr.between?(scl_beg + 1, scl_beg + scl_dur)
                      val *= 0.5
                    end
                    val
                  }, dur)
        undo_edit(2)
      end
    end
    env_sound([0, 0, 1, 1])
    env_sound([0, 0, 1, 1])
    e = make_env([0, 0, 1, 1], :length, dur)
    check_env(:unenv_ramp, make_sampler(0),
              lambda {
                val = env(e)
                val * val
              }, dur)
    undo_edit(2)
    env_sound([0, 0, 1, 1])
    vct2channel(Vct.new(3, 1.0), 3, 3)
    unless vequal(res = channel2vct(0, 10),
                  vct(0, 1.111 / dur, 2.222 / dur, 1, 1, 1, 6.66 / dur,
                      7.77 / dur, 8.88 / dur, 10.0 / dur))
      snd_display("1 vals: %s?", res)
    end
    undo_edit(2)
    env_sound([0, 0, 1, 1])
    delete_samples(3, 3)
    insert_samples(3, 3, Vct.new(3, 1.0))
    unless vequal(res = channel2vct(0, 10),
                  vct(0, 1.111 / dur, 2.222 / dur, 1, 1, 1, 6.66 / dur,
                      7.77 / dur, 8.88 / dur, 10.0 / dur))
      snd_display("2 vals: %s?", res)
    end
    undo_edit(3)
    env_sound([0, 0, 1, 1])
    insert_samples(3, 3, Vct.new(3, 1.0))
    delete_samples(3, 3)
    check_env(:ramp_5, make_sampler(0), make_env([0, 0, 1, 1], :length, dur), dur)
    undo_edit(3)
    env_sound([0, 0, 1, 1, 2, 0])
    if dur == 10
      vct2channel(Vct.new(3, 1.0), 3, 3)
      unless vequal(res = channel2vct(0, 10), vct(0, 0.2, 0.4, 1, 1, 1, 0.75, 0.5, 0.25, 0))
        snd_display("4 vals (%s): %s?", dur, res)
      end
    else
      vct2channel(Vct.new(3, 0.0), 4998, 3)
      unless vequal(res = channel2vct(4995, 10),
                    vct(0.999, 0.999, 1, 0, 0, 0, 1, 0.999, 0.999, 0.999))
        snd_display("4 vals (%s): %s?", dur, res)
      end
    end
    undo_edit(2)
    if dur == 10
      env_sound([0, 0, 1, 1, 2, 0])
      delete_samples(3, 3)
      insert_samples(3, 3, Vct.new(3, 1.0))
      unless vequal(res = channel2vct(0, 10), vct(0, 0.2, 0.4, 1, 1, 1, 0.75, 0.5, 0.25, 0))
        snd_display("2 vals: %s?", res)
      end
      undo_edit(3)
      env_sound([0, 0, 1, 1, 2, 0])
      vct2channel(Vct.new(3, 1.0), 0, 3)
      unless vequal(res = channel2vct(0, 10), vct(1, 1, 1, 0.6, 0.8, 1, 0.75, 0.5, 0.25, 0))
        snd_display("4 vals: %s?", res)
      end
      undo_edit(2)
      env_sound([0, 0, 1, 1, 2, 0])
      vct2channel(Vct.new(3, 1.0), 7, 3)
      unless vequal(res = channel2vct(0, 10), vct(0, 0.2, 0.4, 0.6, 0.8, 1, 0.75, 1, 1, 1))
        snd_display("5 vals: %s?", res)
      end
      undo_edit(2)
    end
    file = file_name(i1)
    close_sound(i1)
    delete_file(file)
  end
  if $initial_graph_hook.empty?
    $update_hook.reset_hook!
    $close_hook.reset_hook!
    $exit_hook.reset_hook!
    require "env"
  end
end

def test_16_03
  ind = new_sound("fmv.snd", Mus_next, Mus_bfloat, 22050, 1, "envd edit trees")
  vals = Vct.new(10000, 1.0)
  select_sound(ind)
  select_channel(0)
  check_edit_tree([[0, 0, 0, 0, 0.0, 0.0, 0.0, 1],
                   [1, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  Vct.new(1), "initial new_sound")
  set_samples(0, 10000, vals)
  check_edit_tree([[0, 1, 0, 9999, 1.0, 0.0, 0.0, 0],
                   [10000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "envd set first samps to one")
  env_sound([0, 0, 1, 1])
  e = make_env(:envelope, [0, 0, 1, 1], :length, 10000)
  vals.map! do |val| e.run end
  check_edit_tree([[0, 1, 0, 9999, 1.0, 0.0, 1.0e-4, 4],
                   [10000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env frag [0, 0, 1, 1]")
  delete_samples(1000, 1000)
  v1 = Vct.new(9000)
  1000.times do |i| v1[i] = vals[i] end
  1000.upto(8999) do |i| v1[i] = vals[i + 1000] end
  check_edit_tree([[0, 1, 0, 999, 1.0, 0.0, 1.0e-4, 4],
                   [1000, 1, 2000, 9999, 1.0, 0.2, 1.0e-4, 4],
                   [9000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  v1, "env frag del")
  undo_edit(1)
  delete_samples(9000, 1000)
  insert_samples(3000, 1000, Vct.new(1000))
  9999.downto(4000) do |i| vals[i] = vals[i - 1000] end
  3000.upto(3999) do |i| vals[i] = 0.0 end
  check_edit_tree([[0, 1, 0, 2999, 1.0, 0.0, 1.0e-4, 4],
                   [3000, 2, 0, 999, 1.0, 0.0, 0.0, 0],
                   [4000, 1, 3000, 8999, 1.0, 0.3, 1.0e-4, 4],
                   [10000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "envd ins/del")
  delete_samples(0, 1000)
  insert_samples(0, 1000, Vct.new(1000))
  0.upto(999) do |i| vals[i] = 0.0 end
  check_edit_tree([[0, 3, 0, 999, 1.0, 0.0, 0.0, 0],
                   [1000, 1, 1000, 2999, 1.0, 0.1, 1.0e-4, 4],
                   [3000, 2, 0, 999, 1.0, 0.0, 0.0, 0],
                   [4000, 1, 3000, 8999, 1.0, 0.3, 1.0e-4, 4],
                   [10000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "envd predel")
  scale_by(0.5)
  vals.scale!(0.5)
  check_edit_tree([[0, 3, 0, 999, 0.5, 0.0, 0.0, 0],
                   [1000, 1, 1000, 2999, 0.5, 0.1, 1.0e-4, 4],
                   [3000, 2, 0, 999, 0.5, 0.0, 0.0, 0],
                   [4000, 1, 3000, 8999, 0.5, 0.3, 1.0e-4, 4],
                   [10000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "envd scl")
  reverse_channel
  j = 9999
  5000.times do |i|
    vals[i], vals[j] = vals[j], vals[i]
    j -= 1
  end
  check_edit_tree([[0, 4, 0, 9999, 1.0, 0.0, 0.0, 0],
                   [10000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "envd rev")
  revert_sound(ind)
  vals = Vct.new(100000, 1.0)
  vct2channel(vals, 0, 100000)
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 0], :length, 10000), 30000, 10000)
  e = make_env(:envelope, [0, 0, 1, 1, 2, 0], :length, 10000)
  30000.upto(39999) do |i| vals[i] = e.run end
  check_edit_tree([[0, 1, 0, 29999, 1.0, 0.0, 0.0, 0],
                   [30000, 1, 30000, 34999, 1.0, 0.0, 1.9e-4, 4],
                   [35000, 1, 35000, 39999, 1.0, 1.0, -2.0e-4, 4],
                   [40000, 1, 40000, 99999, 1.0, 0.0, 0.0, 0],
                   [100000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "partial env")
  scale_channel(0.5, 10000, 10000)
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 0], :length, 10000), 30000, 10000)
  e = make_env(:envelope, [0, 0, 1, 1, 2, 0], :length, 10000)
  30000.upto(39999) do |i| vals[i] *= e.run end
  10000.upto(19999) do |i| vals[i] *= 0.5 end
  check_edit_tree([[0, 1, 0, 9999, 1.0, 0.0, 0.0, 0],
                   [10000, 1, 10000, 19999, 0.5, 0.0, 0.0, 0],
                   [20000, 1, 20000, 29999, 1.0, 0.0, 0.0, 0],
                   [30000, 1, 30000, 34999, 1.0, 0.0, 1.9e-4, 6],
                   [35000, 1, 35000, 39999, 1.0, 1.0, -2.0e-4, 6],
                   [40000, 1, 40000, 99999, 1.0, 0.0, 0.0, 0],
                   [100000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env over env")
  env_channel(make_env(:envelope, [0, 0, 1, 1, 2, 0], :length, 10000), 5000, 10000)
  e = make_env(:envelope, [0, 0, 1, 1, 2, 0], :length, 10000)
  5000.upto(14999) do |i| vals[i] *= e.run end
  check_edit_tree([[0, 1, 0, 4999, 1.0, 0.0, 0.0, 0],
                   [5000, 1, 5000, 9999, 1.0, 0.0, 1.9e-4, 4],
                   [10000, 1, 10000, 14999, 0.5, 1.0, -2.0e-4, 4],
                   [15000, 1, 15000, 19999, 0.5, 0.0, 0.0, 0],
                   [20000, 1, 20000, 29999, 1.0, 0.0, 0.0, 0],
                   [30000, 1, 30000, 34999, 1.0, 0.0, 1.9e-4, 6],
                   [35000, 1, 35000, 39999, 1.0, 1.0, -2.0e-4, 6],
                   [40000, 1, 40000, 99999, 1.0, 0.0, 0.0, 0],
                   [100000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "env over scl")
  ramp_channel(0.5, -0.5, 25000, 1000)
  e = make_env(:envelope, [0, 0.5, 1, -0.5], :length, 1000)
  25000.upto(25999) do |i| vals[i] *= e.run end
  check_edit_tree([[0, 1, 0, 4999, 1.0, 0.0, 0.0, 0],
                   [5000, 1, 5000, 9999, 1.0, 0.0, 1.9e-4, 4],
                   [10000, 1, 10000, 14999, 0.5, 1.0, -2.0e-4, 4],
                   [15000, 1, 15000, 19999, 0.5, 0.0, 0.0, 0],
                   [20000, 1, 20000, 24999, 1.0, 0.0, 0.0, 0],
                   [25000, 1, 25000, 25999, 1.0, 0.5, -0.001, 4],
                   [26000, 1, 26000, 29999, 1.0, 0.0, 0.0, 0],
                   [30000, 1, 30000, 34999, 1.0, 0.0, 1.9e-4, 6],
                   [35000, 1, 35000, 39999, 1.0, 1.0, -2.0e-4, 6],
                   [40000, 1, 40000, 99999, 1.0, 0.0, 0.0, 0],
                   [100000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "ramp")
  scale_by(-1.0)
  vals.scale!(-1.0)
  check_edit_tree([[0, 1, 0, 4999, -1.0, 0.0, 0.0, 0],
                   [5000, 1, 5000, 9999, -1.0, 0.0, 1.9e-4, 4],
                   [10000, 1, 10000, 14999, -0.5, 1.0, -2.0e-4, 4],
                   [15000, 1, 15000, 19999, -0.5, 0.0, 0.0, 0],
                   [20000, 1, 20000, 24999, -1.0, 0.0, 0.0, 0],
                   [25000, 1, 25000, 25999, -1.0, 0.5, -0.001, 4],
                   [26000, 1, 26000, 29999, -1.0, 0.0, 0.0, 0],
                   [30000, 1, 30000, 34999, -1.0, 0.0, 1.9e-4, 6],
                   [35000, 1, 35000, 39999, -1.0, 1.0, -2.0e-4, 6],
                   [40000, 1, 40000, 99999, -1.0, 0.0, 0.0, 0],
                   [100000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "invert")
  reader = make_sampler(0, ind, 0, 1, edit_position - 1)
  map_channel(lambda { |y| y + reader.call })
  check_edit_tree([[0, 2, 0, 99999, 1.0, 0.0, 0.0, 0],
                   [100000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  Vct.new(100000), "invert and add")
  if fneq(maxamp, 0.0)
    snd_display("invert-and-add maxamp: %s?", maxamp)
  end
  undo_edit
  ramp_channel(-1.0, 1.0, 50000, 30000)
  e = make_env(:envelope, [0, -1, 1, 1], :length, 30000)
  50000.upto(79999) do |i| vals[i] *= e.run end
  check_edit_tree([[0, 1, 0, 4999, -1.0, 0.0, 0.0, 0],
                   [5000, 1, 5000, 9999, -1.0, 0.0, 1.9e-4, 4],
                   [10000, 1, 10000, 14999, -0.5, 1.0, -2.0e-4, 4],
                   [15000, 1, 15000, 19999, -0.5, 0.0, 0.0, 0],
                   [20000, 1, 20000, 24999, -1.0, 0.0, 0.0, 0],
                   [25000, 1, 25000, 25999, -1.0, 0.5, -0.001, 4],
                   [26000, 1, 26000, 29999, -1.0, 0.0, 0.0, 0],
                   [30000, 1, 30000, 34999, -1.0, 0.0, 1.9e-4, 6],
                   [35000, 1, 35000, 39999, -1.0, 1.0, -2.0e-4, 6],
                   [40000, 1, 40000, 49999, -1.0, 0.0, 0.0, 0],
                   [50000, 1, 50000, 79999, -1.0, -1.0, 6.6e-5, 4],
                   [80000, 1, 80000, 99999, -1.0, 0.0, 0.0, 0],
                   [100000, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  vals, "ramp")
  env_sound([0, 0, 1, 1])
  reverse_channel
  delete_samples(1, 99999)
  if fneq(sample(0), -1.0)
    snd_display("sample at end: %s?", sample(0))
  end
  if frames != 1
    snd_display("length at end: %s?", frames)
  end
  check_edit_tree([[0, 2, 0, 0, 1.0, 0.0, 0.0, 0],
                   [1, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  Vct.new(1, -1.0), "at end")
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  env_channel([0.0, 0.984011617147162, 0.644050741979388, 0.110976689002195,
               1.17272046995914, 0.384709990674106, 1.25650287720397, 0.551452668245628,
               1.4389507801877, 0.843827758574229, 2.16614272265275, 0.226832341237953])
  if (not number?(val = sample(50827))) or fneq(val, 0.0)
    snd_display("round-off env: %s?", val)
  end
  check_edit_tree([[0, 0, 0, 15111, 1.0, 0.984, -5.77e-5, 4], 
                   [15112, 0, 15112, 27516, 1.0, 0.111, 2.2e-5, 4], 
                   [27517, 0, 27517, 29482, 1.0, 0.3848, 8.48e-5, 4], 
                   [29483, 0, 29483, 33763, 1.0, 0.5515, 6.83e-5, 4], 
                   [33764, 0, 33764, 50827, 1.0, 0.8438, -3.62e-5, 4], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "round-off test")
  revert_sound(ind)
  map_channel($init_channel)
  env_channel([0, 0, 1, 1, 2, 0])
  scale_channel(0.5, 1000, 1000)
  if fneq(val = sample(800), 0.0314)
    snd_display("scl on env trouble: %s?", val)
  end
  check_edit_tree([[0, 1, 0, 999, 1.0, 0.0, 3.93e-5, 4], 
                   [1000, 1, 1000, 1999, 0.5, 0.0393, 3.93e-5, 4], 
                   [2000, 1, 2000, 25413, 1.0, 0.0786, 3.93e-5, 4], 
                   [25414, 1, 25414, 50827, 1.0, 1.0, -3.93e-5, 4], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "scl on env")
  revert_sound(ind)
  map_channel($init_channel)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  if fneq(val = sample(20000), (20000.0 / 50828) ** 3)
    snd_display("ramp_channels piled up: %s?", val)
  end
  check_edit_tree([[0, 1, 0, 50827, 1.0, 0.0, 1.9e-5, 10], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "ramp upon ramp")
  revert_sound(ind)
  map_channel($init_channel)
  ramp_channel(0.5, 1.0) # val = 0.5 + (20000/50828)*0.5
  ramp_channel(0.0, 0.5) # val * (20000/50828)*0.5
  ramp_channel(0.1, 0.4) # val * (0.1 + (20000/50828)*0.3)
  val = sample(20000)
  ratio = 20000.0 / 50828
  val1 = 0.5 + 0.5 * ratio
  val2 = val1 * 0.5 * ratio
  val3 = val2 * (0.1 + ratio * 0.3)
  if fneq(val, val3)
    snd_display("ramp-channels piled up (2): %s %s?", val, val3)
  end
  revert_sound(ind)
  env_channel([0, 0, 1, 1, 2, 0])
  check_edit_tree([[0, 0, 0, 25413, 1.0, 0.0, 3.9e-5, 4], 
                   [25414, 0, 25414, 50827, 1.0, 1.0, -3.93e-5, 4], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "env+scl 0")
  scale_channel(0.5, 0, 1000)
  check_edit_tree([[0, 0, 0, 999, 0.5, 0.0, 3.93e-5, 4], 
                   [1000, 0, 1000, 25413, 1.0, 0.0393, 3.93e-5, 4], 
                   [25414, 0, 25414, 50827, 1.0, 1.0, -3.93e-5, 4], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "env+scl 1")
  undo_edit
  scale_channel(0.5, 1000, 1000)
  check_edit_tree([[0, 0, 0, 999, 1.0, 0.0, 3.93e-5, 4], 
                   [1000, 0, 1000, 1999, 0.5, 0.0393, 3.93e-5, 4], 
                   [2000, 0, 2000, 25413, 1.0, 0.0786, 3.93e-5, 4], 
                   [25414, 0, 25414, 50827, 1.0, 1.0, -3.93e-5, 4], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "env+scl 2")
  undo_edit
  scale_channel(0.5, 0, 25415)
  check_edit_tree([[0, 0, 0, 25413, 0.5, 0.0, 3.93e-5, 4], 
                   [25414, 0, 25414, 25414, 0.5, 1.0, -3.93e-5, 4], 
                   [25415, 0, 25415, 50827, 1.0, 0.999, -3.93e-5, 4], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "env+scl 3")
  undo_edit
  scale_channel(0.5, 20000, 10000)
  check_edit_tree([[0, 0, 0, 19999, 1.0, 0.0, 3.93e-5, 4], 
                   [20000, 0, 20000, 25413, 0.5, 0.7869, 3.93e-5, 4], 
                   [25414, 0, 25414, 29999, 0.5, 1.0, -3.93e-5, 4], 
                   [30000, 0, 30000, 50827, 1.0, 0.8195, -3.93e-5, 4], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "env+scl 4")
  undo_edit
  scale_channel(0.5, 30000, 1000)
  check_edit_tree([[0, 0, 0, 25413, 1.0, 0.0, 3.93e-5, 4], 
                   [25414, 0, 25414, 29999, 1.0, 1.0, -3.93e-5, 4],
                   [30000, 0, 30000, 30999, 0.5, 0.8195, -3.93e-5, 4], 
                   [31000, 0, 31000, 50827, 1.0, 0.7802, -3.93e-5, 4], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "env+scl 5")
  undo_edit
  scale_channel(0.5, 25415, 1000)
  check_edit_tree([[0, 0, 0, 25413, 1.0, 0.0, 3.935e-5, 4], 
                   [25414, 0, 25414, 25414, 1.0, 1.0, -3.935e-5, 4], 
                   [25415, 0, 25415, 26414, 0.5, 0.9999, -3.935e-5, 4],
                   [26415, 0, 26415, 50827, 1.0, 0.96, -3.93e-5, 4],
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "env+scl 6")
  undo_edit
  scale_channel(0.5, 40000, 10828)
  check_edit_tree([[0, 0, 0, 25413, 1.0, 0.0, 3.935e-5, 4], 
                   [25414, 0, 25414, 39999, 1.0, 1.0, -3.935e-5, 4], 
                   [40000, 0, 40000, 50827, 0.5, 0.426, -3.935e-5, 4], 
                   [50828, -2, 0, 0, 0.0, 0.0, 0.0, 0]],
                  false, "env+scl 7")
  undo_edit
  close_sound(ind)
end

def check_envs(name, r_maker, e_maker, dur, i1, i2)
  check_env(format("%s-i1-0", name), r_maker.call(i1, 0), e_maker.call(i1, 0), dur)
  check_env(format("%s-i2-0", name), r_maker.call(i2, 0), e_maker.call(i2, 0), dur)
  check_env(format("%s-i2-1", name), r_maker.call(i2, 1), e_maker.call(i2, 1), dur)
end

def test_16_04
  [10, 10000].each do |dur|
    i1 = new_sound
    i2 = new_sound("fmv1.snd", Mus_next, Mus_bfloat, 44100, 2)
    v = Vct.new(dur, 1.0)
    vct2channel(v, 0, dur, i1)
    vct2channel(v, 0, dur, i2, 0)
    vct2channel(v, 0, dur, i2, 1)
    set_sync(1, i1)
    set_sync(1, i2)
    env_sound([0, 0, 1, 1])
    check_envs(:ramps,
               lambda { |s, c| make_sampler(0, s, c) },
               lambda { |s, c| make_env(:envelope, [0, 0, 1, 1], :length, dur) },
               dur, i1, i2)
    reverse_sound
    check_envs(:rev_ramps,
               lambda { |s, c| make_sampler(0, s, c) },
               lambda { |s, c| make_env(:envelope, [0, 1, 1, 0], :length, dur) },
               dur, i1, i2)
    undo_edit(2)
    env_sound([0, 0, 1, 1, 2, 0])
    check_envs(:ramps_2,
               lambda { |s, c| make_sampler(0, s, c) },
               lambda { |s, c| make_env(:envelope, [0, 0, 1, 1, 2, 0], :length, dur) },
               dur, i1, i2)
    undo_edit(1)
    scale_by(0.5)
    env_sound([0, 0, 1, 1])
    check_envs(:scl_ramps,
               lambda { |s, c| make_sampler(0, s, c) },
               lambda { |s, c| make_env(:envelope, [0, 0, 1, 1], :length, dur, :scaler, 0.5) },
               dur, i1, i2)
    reverse_sound
    check_envs(:scl_rev_ramps,
               lambda { |s, c| make_sampler(0, s, c) },
               lambda { |s, c| make_env(:envelope, [0, 1, 1, 0], :length, dur, :scaler, 0.5) },
               dur, i1, i2)
    undo_edit(3)
    env_sound([0, 0, 1, 1])
    env_sound([0, 0, 1, 1])
    check_envs(:unenv_ramps,
               lambda { |s, c| make_sampler(0, s, c) },
               lambda { |s, c|
                 e = make_env(:envelope, [0, 0, 1, 1], :length, dur)
                 lambda {
                   val = env(e)
                   val * val
                 }
               },
               dur, i1, i2)
    undo_edit(2)
    env_sound([0, 0, 1, 1])
    v1 = Vct.new(3, 1.0)
    vct2channel(v1, 3, 3, i1)
    vct2channel(v1, 3, 3, i2, 0)
    vct2channel(v1, 3, 3, i2, 1)
    unless vequal(res = channel2vct(0, 10, i1, 0),
                  vct(0, 1.111 / dur, 2.222 / dur, 1, 1, 1, 6.66 / dur,
                      7.77 / dur, 8.88 / dur, 10.0 / dur))
      snd_display("1 0 vals: %s?", res)
    end
    unless vequal(res = channel2vct(0, 10, i2, 0),
                  vct(0, 1.111 / dur, 2.222 / dur, 1, 1, 1, 6.66 / dur,
                      7.77 / dur, 8.88 / dur, 10.0 / dur))
      snd_display("2 0 vals: %s?", res)
    end
    unless vequal(res = channel2vct(0, 10, i2, 1),
                  vct(0, 1.111 / dur, 2.222 / dur, 1, 1, 1, 6.66 / dur,
                      7.77 / dur, 8.88 / dur, 10.0 / dur))
      snd_display("2 1 vals: %s?", res)
    end
    file = file_name(i1)
    close_sound(i1)
    delete_file(file)
    file = file_name(i2)
    close_sound(i2)
    delete_file(file)
  end
  #
  data = ["1a.snd", "oboe.snd", "storm.snd"].map do |sound|
    if File.exists?(sound)
      ind = view_sound(sound)
      set_squelch_update(true, ind)
      tms = [lambda { scale_channel(2.0) },
             lambda { reverse_channel },
             lambda { env_channel([0, 0, 1, 1]) },
             lambda { map_channel(lambda { |y| y * 2 }) },
             lambda { scan_channel(lambda { |y| y > 1.0 }) },
             lambda { pad_channel(0, 2000) },
             lambda { vct2channel(Vct.new(1000, 0.1), 0, 1000) },
             lambda { clm_channel(make_two_zero(0.5, 0.5)) },
             lambda { mix("pistol.snd", 12345) },
             lambda { src_channel(2.0) },
             lambda { delete_samples(10, 200) }].map do |func|
        with_time(&func).first
      end
      close_sound(ind)
      tms
    end
  end
  if $VERBOSE
    snd_info("          scl   rev   env   map   ptree scn   pad   wrt   clm   mix   src")
    str = ""
    data[0].each do |x| str << "%6.2f" % x end
    snd_info("    1a: %s", str)
    str = ""
    data[1].each do |x| str << "%6.2f" % x end
    snd_info("  oboe: %s", str)
    str = ""
    data[2].each do |x| str << "%6.2f" % x end
    snd_info(" storm: %s", str)
  end
  #
  ind = new_sound("fmv.snd", Mus_next, Mus_bfloat)
  set_sinc_width(10)
  pad_channel(0, 1000, ind)
  set_sample(100, 0.5)
  if fneq(res = sample(100, ind, 0, 2), 0.5)
    snd_display("sample(100 (2): %s?", res)
  end
  if fneq(res = sample(100, ind, 0, 1), 0.0)
    snd_display("sample(100 (1): %s?", res)
  end
  src_channel(0.5)
  if fneq(res = maxamp(ind, 0), 0.5)
    snd_display("src_channel max 0.5: %s?", res)
  end
  if fneq(res = sample(200), 0.5)
    snd_display("src_channel 0.5 200: %s?", res)
  end
  unless vequal(res = channel2vct(180, 40, ind, 0),
                vct(0.000, -0.000, 0.000, 0.001, -0.000, -0.003, 0.000, 0.007,
                    -0.000, -0.012, 0.000, 0.020, -0.000, -0.033, 0.000, 0.054,
                    -0.000, -0.100, -0.000, 0.316, 0.500, 0.316, -0.000, -0.100,
                    -0.000, 0.054, 0.000, -0.033, -0.000, 0.020, 0.000, -0.012,
                    -0.000, 0.007, 0.000, -0.003, -0.000, 0.001, 0.000, -0.000))
    snd_display("src_channel 0.5: %s?", res)
  end
  undo_edit(1, ind, 0)
  src_channel(0.25)
  if fneq(res = maxamp(ind, 0), 0.5)
    snd_display("src_channel max 0.25: res %1.4f != req 0.500?", res)
  end
  if fneq(res = sample(400), 0.5)
    snd_display("src_channel 0.25 400: res %1.4f != req 0.500?", res)
  end
  unless vequal(res = channel2vct(360, 80, ind, 0),
                vct(0.000, -0.000, -0.000, -0.000, 0.000, 0.000, 0.001, 0.001,
                    -0.000, -0.002, -0.003, -0.003, 0.000, 0.004, 0.007, 0.006,
                    -0.000, -0.008, -0.012, -0.010, 0.000, 0.013, 0.020, 0.016,
                    -0.000, -0.021, -0.033, -0.026, 0.000, 0.034, 0.054, 0.044,
                    -0.000, -0.060, -0.100, -0.087, -0.000, 0.148, 0.316, 0.449,
                    0.500, 0.449, 0.316, 0.148, -0.000, -0.087, -0.100, -0.060,
                    -0.000, 0.044, 0.054, 0.034, 0.000, -0.026, -0.033, -0.021,
                    -0.000, 0.016, 0.020, 0.013, 0.000, -0.010, -0.012, -0.008,
                    -0.000, 0.006, 0.007, 0.004, 0.000, -0.003, -0.003, -0.002,
                    -0.000, 0.001, 0.001, 0.000, 0.000, -0.000, -0.000, -0.000))
    snd_display("src_channel 0.25: %s?", res)
  end
  undo_edit(2, ind, 0)
  i = 0
  pi_div_100 = PI / 100.0
  map_channel_rb do |y|
    val = sin(i * pi_div_100)
    i += 1
    val * 0.5
  end
  [[2.00, 0.008],
   [1.50, 0.010],
   [3.00, 0.015],
   [3.14, 0.025]].each do |sr, df|
    src_channel(sr)
    if ((res = maxamp(ind, 0)) - 0.5).abs > df
      snd_display("src_channel sine %s: %s?", sr, res)
    end
    if integer?(sr)
      r0 = make_sampler(0)
      r1 = make_sampler(0, ind, 0, 1, edit_position - 1)
      500.times do |i|
        diff = (r0.call - r1.call).abs
        if diff > df
          snd_display("src_channel %s diff %s: %s?", sr, i, diff)
        end
        1.upto(sr - 1) do r1.call end
      end
    end
    50.times do |i|
      s1 = sample(i, ind, 0, edit_position)
      s2 = sample((sr * i).round, ind, 0, edit_position - 1)
      s3 = sample(i, ind, 0, 1)
      if (s1 - s2).abs > df
        snd_display("sample %s src(%s): %s %s?", i, sr, s1, s2)
      end
      if fneq(s3, 0.0)
        snd_display("sample %s (1): %s?", i, s3)
      end
    end
    undo_edit(1, ind, 0)
  end
  close_sound(ind)
  # 
  ind = open_sound("oboe.snd")
  orig_max = maxamp(ind, 0)
  [[2.00, 0.008],
   [1.50, 0.010],
   [3.00, 0.015],
   [3.14, 0.025]].each do |sr, df|
    src_channel(sr)
    if ((res = maxamp(ind, 0)) - orig_max).abs > df
      snd_display(snd_format_neq(res, orig_max,
                                       "src_channel oboe (1) sr %1.4f, df %1.4f", sr, df))
    end
    undo_edit(1, ind, 0)
  end
  #
  [[0.50, 0.001],
   [0.25, 0.001],
   [0.90, 0.001],
   [0.10, 0.001]].each do |sr, df|
    src_channel(sr)
    if ((res = maxamp(ind, 0)) - orig_max).abs > df
      snd_display(snd_format_neq(res, orig_max,
                                       "src_channel oboe (2) sr %1.4f, df %1.4f", sr, df))
    end
    50.times do |i|
      samp = i * 100
      s1 = sample(samp, ind, 0, edit_position)
      s2 = sample((sr * samp).floor, ind, 0, edit_position - 1)
      if (s1 - s2).abs > df
        snd_display(snd_format_neq(s1, s2,
                                   "sample %d oboe (2) sr %1.4f, df %1.4f", i, sr, df))
      end
    end
    undo_edit(1, ind, 0)
    amp_envs_equal?(ind, 0, edit_position, edit_position + 1, 0.01)
  end
  #
  revert_sound(ind)
  scale_by(2.0)
  scale_by(0.5)
  amp_envs_equal?(ind, 0, edit_position, edit_position - 2, 0.001)
  revert_sound(ind)
  close_sound(ind)
  # 
  ind = open_sound("oboe.snd")
  [[lambda { |beg, dur| env_channel([0, 0, 1, 1], beg, dur) },           0, 1000, 50828],
   [lambda { |beg, dur| map_channel(lambda { |y| y * 0.5 }, beg, dur) }, 0, 1000, 50828],
   [lambda { |beg, dur| reverse_channel(beg, dur) },                     0, 1000, 50828],
   [lambda { |beg, dur| scale_channel(2.0, beg, dur) },                  0, 1000, 50828],
   [lambda { |beg, dur| vct2channel(Vct.new(dur), beg, dur) },           0, 1000, 50828],
   [lambda { |beg, dur| smooth_channel(beg, dur) },                      0, 1000, 50828],
   [lambda { |beg, dur| pad_channel(beg, dur) },                         0, 1000, 51828],
   [lambda { |beg, dur| src_channel(0.5, beg, dur) },                    0, 1000, 52829],
   [lambda { |beg, dur| insert_silence(beg, dur) },                      0, 1000, 53829]
  ].each do |func, beg, dur, len|
    old_len = frames(ind)
    func.call(beg, dur)
    if (res = frames(ind)) != len
      snd_display("(%s %s %s) with %s: %s (%s)?", func, beg, dur, old_len, res, len)
    end
  end
  revert_sound(ind)
  [[ 1000, 1000, 51828],
   [60000, 1000, 61000],
   [    0, 1000, 62000],
   [62000,    1, 62001],
   [62000,    2, 62003],
   [62004,    1, 62005]].each do |beg, dur, len|
    old_len = frames(ind)
    pad_channel(beg, dur)
    if (res = frames(ind)) != len
      snd_display("(pad_channel %s %s) with %s: %s (%s)?", beg, dur, old_len, res, len)
    end
  end
  revert_sound(ind)
  [[lambda { |beg, dur| env_channel([0, 0, 1, 1], beg, dur) }, 1000, 50828],
   [lambda { |beg, dur| reverse_channel(beg, dur) },           1000, 50828],
   [lambda { |beg, dur| scale_channel(2.0, beg, dur) },        1000, 50828],
   [lambda { |beg, dur| scale_sound_by(2.0, beg, dur) },       1000, 50828],
   [lambda { |beg, dur| vct2channel(Vct.new(dur), beg, dur) }, 1000, 51928],
   [lambda { |beg, dur| smooth_channel(beg, dur) },            1000, 51928],
   [lambda { |beg, dur| pad_channel(beg, dur) },               1000, 53028],
   [lambda { |beg, dur| src_channel(0.5, beg, dur) },          1000, 53028],
   [lambda { |beg, dur| insert_silence(beg, dur) },            1000, 54028],
   [lambda { |beg, dur| env_sound([0, 0, 1, 1], beg, dur) },   1000, 54028]
  ].each do |func, dur, len|
    old_len = frames(ind)
    func.call(old_len + 100, dur)
    if (res = frames(ind)) != len
      snd_display("(%s %s) with %s: %s (%s)?", func, dur, old_len, res, len)
    end
  end
  revert_sound(ind)
  100.times do
    case random(10)
    when 0
      pad_channel(random(1.25 * frames), random(1000))
    when 1
      env_channel([0, 0, 1, 1, 2, 0], random(1.25 * frames), random(1000))
    when 2
      env_sound([0, 0, 1, 1, 2, 0], random(1.25 * frames), random(1000))
    when 3
      scale_channel(random(1.0), random(1.25 * frames), random(1000))
    when 4
      scale_sound_by(random(1.0), random(1.25 * frames), random(1000))
    when 5
      src_channel(random(0.2) + 0.9, random(1.25 * frames), random(1000))
    when 6
      ramp_channel(random(1.0), random(1.0), random(1.25 * frames), random(1000))
    when 7
      reverse_channel(random(1.25 * frames), random(1000))
    when 8
      dur = [2, random(100)].max
      vct2channel(Vct.new(dur), random(1.25 * frames), dur)
    when 9
      map_channel(lambda { |y| y * 2 }, random(0.5 * frames), random(1000))
    end
  end
  close_sound(ind)
end

def test_16_05
  ind0 = open_sound("oboe.snd")
  ind1 = open_sound("2.snd")
  ind2 = open_sound("4.aiff")
  set_squelch_update(true, ind0, true)
  set_squelch_update(true, ind1, true)
  set_squelch_update(true, ind2, true)
  Snd.catch(:mus_error, lambda do |*args| snd_display("caught error: %s", args) end) do
    500.times do
      set_sync(random(3), ind0)
      set_sync(random(3), ind1)
      set_sync(random(3), ind2)
      opt_test(random(22))
    end
  end
  set_squelch_update(false, ind0, true)
  set_squelch_update(false, ind1, true)
  set_squelch_update(false, ind2, true)
  close_sound(ind0)
  close_sound(ind1)
  close_sound(ind2)
  #
  ind = init_sound(0.5, 10, 2)
  save_sound(ind)
  scale_channel(2.0, 0, frames, ind, 1)
  swap_channels
  check_both_chans(ind, "1", lambda { |y| fneq(y, 1.0) }, lambda { |y| fneq(y, 0.5) })
  undo_edit(1, ind, 0)
  undo_edit(2, ind, 1)
  scale_channel(0.5, 0, frames, ind, 0)
  scale_channel(2.0, 0, frames, ind, 1)
  swap_channels
  check_both_chans(ind, "2", lambda { |y| fneq(y, 1.0) }, lambda { |y| fneq(y, 0.25) })
  undo_edit(2, ind, 0)
  undo_edit(2, ind, 1)
  delete_samples(2, 3, ind, 0)
  env_channel([0, 0, 1, 1, 2, 0], 0, frames(ind, 1), ind, 1)
  swap_channels
  if (res = frames(ind, 1)) != 11
    snd_display("frames swapped: %s?", res)
  end
  unless vequal(res = channel2vct(0, frames(ind, 0), ind, 0),
                vct(0.000, 0.100, 0.200, 0.300, 0.400, 0.500, 0.400, 0.300, 0.200, 0.100, 0.000))
    snd_display("swapped env: %s", res)
  end
  undo_edit(2, ind, 0)
  undo_edit(2, ind, 1)
  delete_samples(2, 7, ind, 0)
  swap_channels(ind, 0, ind, 1, 5, 4)
  unless vequal(res = channel2vct(0, 10, ind, 0),
                vct(0.500, 0.500, 0.500, 0.500, 0.000, 0.500, 0.500, 0.500, 0.500, 0.000))
    snd_display("partial swap 1: %s?", res)
  end
  unless vequal(res = channel2vct(0, 10, ind, 1),
                vct(0.500, 0.500, 0.500, 0.500, 0.500, 0.000, 0.000, 0.000, 0.000, 0.500))
    snd_display("partial swap 2: %s?", res)
  end
  revert_sound(ind)
  m0 = add_mark(3, ind, 0)
  m1 = add_mark(4, ind, 1)
  m2 = add_mark(5, ind, 1)
  scale_channel(0.5)
  swap_channels
  if (res = mark_sample(m0)) != 3
    snd_display("swapped m0: %s?", res)
  end
  if (res = mark_sample(m1)) != 4
    snd_display("swapped m1: %s?", res)
  end
  if (res = mark_sample(m2)) != 5
    snd_display("swapped m2: %s?", res)
  end
  if (res = mark_home(m0)) != [ind, 1]
    snd_display("mark_home m0: %s?", res)
  end
  if (res = mark_home(m1)) != [ind, 0]
    snd_display("mark_home m1: %s?", res)
  end
  if (res = mark_home(m2)) != [ind, 0]
    snd_display("mark_home m2: %s?", res)
  end
  undo_edit(1, ind, 0)
  undo_edit(1, ind, 1)
  if (res = mark_sample(m0)) != 3
    snd_display("swapped m0 (2): %s?", res)
  end
  if (res = mark_sample(m1)) != 4
    snd_display("swapped m1 (2): %s?", res)
  end
  if (res = mark_sample(m2)) != 5
    snd_display("swapped m2 (2): %s?", res)
  end
  if (res = mark_home(m0)) != [ind, 0]
    snd_display("mark_home m0 (2): %s?", res)
  end
  if (res = mark_home(m1)) != [ind, 1]
    snd_display("mark_home m1 (2): %s?", res)
  end
  if (res = mark_home(m2)) != [ind, 1]
    snd_display("mark_home m2 (2): %s?", res)
  end
  close_sound(ind)
  delete_file("test.snd")
  #
  ind = init_sound(0.5, 10, 4)
  scale_channel(0.5, 0, frames, ind, 1)
  scale_channel(0.25, 0, frames, ind, 2)
  scale_channel(0.125, 0, frames, ind, 3)
  swap_channels(ind, 1, ind, 2)
  maxs = maxamp(ind, true)
  if fneq(maxs[0], 0.5) or
      fneq(maxs[1], 0.125) or
      fneq(maxs[2], 0.25) or
      fneq(maxs[3], 0.0625)
    snd_display("swap midchans: %s?", maxs)
  end
  close_sound(ind)
  #
  ind0 = open_sound("oboe.snd")
  ind1 = open_sound("pistol.snd")
  mx0 = maxamp(ind0, 0)
  mx1 = maxamp(ind1, 0)
  swap_channels(ind0, 0, ind1, 0)
  if fneq(res = maxamp(ind0, 0), mx1)
    snd_display("maxamp cross swap 0: %s?", res)
  end
  if fneq(res = maxamp(ind1, 0), mx0)
    snd_display("maxamp cross swap 1: %s?", res)
  end
  close_sound(ind0)
  close_sound(ind1)
  #
  ind = init_sound(1.0, 10, 1)
  #
  # ramp+ramp
  #
  ramp_channel(0.0, 1.0)
  check_back_and_forth(ind, "ramp 1",
                       vct(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1))
  ramp_channel(0.0, 1.0)
  check_back_and_forth(ind, "ramp 2",
                       vct(0, 0.01, 0.04, 0.09, 0.16, 0.25, 0.36, 0.49, 0.64, 0.81, 1))
  undo_edit
  ramp_channel(1.0, 0.0)
  check_back_and_forth(ind, "ramp 3",
                       vct(0, 0.09, 0.16, 0.21, 0.24, 0.25, 0.24, 0.21, 0.16, 0.09, 0))
  undo_edit
  env_channel([0, 0, 1, 1, 2, 0])
  check_back_and_forth(ind, "ramp 4",
                       vct(0, 0.020, 0.080, 0.180, 0.320, 0.500, 0.480, 0.420, 0.320, 0.180, 0))
  undo_edit(2)
  env_channel([0, 0, 1, 1, 2, 0])
  check_back_and_forth(ind, "ramp 5",
                       vct(0, 0.200, 0.400, 0.600, 0.800, 1.000, 0.800, 0.600, 0.400, 0.200, 0))
  ramp_channel(0.0, 1.0)
  check_back_and_forth(ind, "ramp 6",
                       vct(0, 0.020, 0.080, 0.180, 0.320, 0.500, 0.480, 0.420, 0.320, 0.180, 0))
  scale_channel(0.5)
  check_back_and_forth(ind, "ramp 7",
                       vct(0, 0.010, 0.040, 0.090, 0.160, 0.250, 0.240, 0.210, 0.160, 0.090, 0))
  undo_edit(3)
  scale_channel(0.5)
  env_channel([0, 0, 1, 1, 2, 0])
  check_back_and_forth(ind, "ramp 8",
                       vct(0, 0.100, 0.200, 0.300, 0.400, 0.500, 0.400, 0.300, 0.200, 0.100, 0))
  ramp_channel(0.0, 1.0)
  check_back_and_forth(ind, "ramp 9",
                       vct(0, 0.010, 0.040, 0.090, 0.160, 0.250, 0.240, 0.210, 0.160, 0.090, 0))
  undo_edit(3)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  ramp_channel(0.0, 1.0)
  check_back_and_forth(ind, "ramp 10",
                       vct(0, 0.001, 0.008, 0.027, 0.064, 0.125, 0.216, 0.343, 0.512, 0.729, 1))
  undo_edit(3)
  #
  # ramp+scl (checking split loc)
  # 
  ramp_channel(0.0, 1.0, 0, 5)
  scale_channel(0.5, 3, 3)
  check_back_and_forth(ind, "ramp+scl 1",
                       vct(0, 0.250, 0.500, 0.375, 0.500, 0.500, 1, 1, 1, 1, 1))
  undo_edit(2)
  ramp_channel(1.0, 0.0, 5, 5)
  scale_channel(0.5, 4, 3)
  check_back_and_forth(ind, "ramp+scl 2",
                       vct(1, 1, 1, 1, 0.500, 0.500, 0.375, 0.500, 0.250, 0, 1))
  undo_edit(2)
  close_sound(ind)
  #
  if $all_args
    [[:scale_channel,         lambda { |snd, i| scale_channel(i * 0.01) }],
     [:set_sample,            lambda { |snd, i| set_sample(i, 0.5) }],
     [:env_channel,           lambda { |snd, i| env_channel([0, 0, 1, 1]) }],
     [:env_channel_with_base, lambda { |snd, i| env_channel_with_base([0, 0, 1, 1], 32.0) }],
     [:env_channel_with_base, lambda { |snd, i| env_channel_with_base([0, 0, 1, 1], 0.0) }],
     [:delete_sample,         lambda { |snd, i| delete_sample(i * 10) }],
     [:insert_sample,         lambda { |snd, i| insert_sample(i * 10, 0.5) }],
     [:pad_channel,           lambda { |snd, i| pad_channel(i * 10, i * 10) }],
     [:mix_no_tag,            lambda { |snd, i| mix("pistol.snd", 10 * i, 0, snd, 0, false) }],
     [:mix_tag,               lambda { |snd, i| mix("pistol.snd", 10 * i, 0, snd, 0, true) }],
     [:mix_scale_to,          lambda { |snd, i|
          mx = mix("pistol.snd", 100 * i).car
          set_mix_amp(mx, 0.01)
        }],
     [:mix_amp,               lambda { |snd, i| mix("pistol.snd", 100 * i); scale_to(0.5)}],
     [:src_sound_1,           lambda { |snd, i| src_sound(2.0); undo_edit }],
     [:src_sound_2,           lambda { |snd, i| src_sound(2.01); undo_edit }],
     [:filter_channel_1,      lambda { |snd, i| filter_channel(vct(0.25, 0.5, 0.25, 0.1), 4) }],
     [:filter_channel_2,      lambda { |snd, i| filter_channel(vct(0.25, 0.5, 0.5, 0.25), 4) }],
     [:filter_channel_3,      lambda { |snd, i|
          filter_channel(vct(0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.2, 0.1, 0.1), 10)
        }],
     [:filter_channel_4,      lambda { |snd, i|
          filter_channel(vct(0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1), 10)
        }],
     [:clm_channel,           lambda { |snd, i| clm_channel(make_two_zero(0.5, 0.5)) }],
     [:reverse_channel, lambda { |snd, i| reverse_channel(i * 10, i * 100) }]].each do |name, func|
      ["1.snd", "oboe.snd", "1a.snd"].each do |sound|
        ind = open_sound(sound)
        with_time do
          set_squelch_update(true, ind, 0)
          with_time(format("%s() [%s]", name, sound)) do
            256.times do |i| func.call(ind, i) end
          end
          revert_sound(ind)
          set_squelch_update(false, ind, 0)
          close_sound(ind)
        end
      end
    end
  end
end

def test_16
  test_16_00
  test_16_01 if $with_test_gui # load("hiho.rb") -> set_transform_size(0)
  test_16_02
  test_16_03
  test_16_04
  test_16_05
end

# ---------------- test 17: dialogs and graphics ----------------

add_help(:arrow2right,
         "arrow2right(x, y, size, snd, chn, cr)  \
draw an arrow pointing (from the left) at the point [x, y]")

def arrow2right(x, y, size, snd, chn, cr)
  size2 = size * 2
  fill_polygon([x, y,
                x - size2, y - size,
                x - size2, y + size,
                x, y],
               snd, chn, Time_graph, cr)
  fill_rectangle(x - 4 * size, (y - 0.4 * size).floor,
                 size2, (0.8 * size).floor,
                 snd, chn, Time_graph, false, cr)
end

def test_17
  # FIXME (Cairo segfault)
  # Assertion failed: (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&cr->ref_count)), function cairo_destroy, file cairo.c, line 435.
  # Abort (core dumped)
  # if $with_test_gui
  if $with_test_motif
    $after_graph_hook.add_hook!(get_func_name, &method(:display_previous_edits).to_proc)
    $lisp_graph_hook.add_hook!(get_func_name) do |snd, chn|
      lambda do | |
        cr = make_cairo(channel_widgets(snd, chn)[0])
        draw_string("hi",
                    x2position(0.5, snd, chn, Lisp_graph),
                    y2position(0.5, snd, chn, Lisp_graph),
                    snd, chn, Lisp_graph, cr)
        free_cairo(cr)
      end
    end
    ind = open_sound("oboe.snd")
    wids = channel_widgets
    wids1 = channel_widgets(selected_sound)
    wids2 = channel_widgets(selected_sound, selected_channel)
    1.upto(3) do |i|
      scale_by(0.5)
      set_x_bounds([0, i * 0.3])
    end
    revert_sound(ind)
    draw_bass_clef(100, 100, 100, 0, ind, 0)
    update_time_graph(ind, 0)
    draw_fermata(200, 100, 60, 0, ind, 0)
    cr = make_cairo(channel_widgets(ind, 0)[0])
    draw_line(100, 100, 200, 200, ind, 0, Time_graph, cr)
    draw_dot(300, 300, 10, ind, 0, Time_graph, cr)
    draw_string("hiho", 20, 20, ind, 0, Time_graph, cr)
    draw_dots([25, 25, 50, 50, 100, 100], 10, ind, 0, Time_graph, cr)
    arrow2right(100, 50, 10, ind, 0, cr)
    fill_rectangle(20, 20, 100, 100, ind, 0, Time_graph, false, cr)
    free_cairo(cr)
    make_bezier(0, 0, 20, 20, 40, 30, 60, 10, 10)
    update_time_graph(ind, 0)
    $after_graph_hook.reset_hook!
    $lisp_graph_hook.reset_hook!
    #
    ind = open_sound("oboe.snd")
    set_time_graph?(false, ind, 0)
    graph([vct(0, 1, 2), vct(3, 2, 1), vct(1, 2, 3), vct(1, 1, 1), vct(0, 1, 0), vct(3, 1, 2)])
    update_lisp_graph
    $lisp_graph_hook.add_hook!("snd-test") do |snd, chn|
      [basic_color, zoom_color, data_color, selected_data_color, mix_color]
    end
    graph([vct(0, 1, 2), vct(3, 2, 1), vct(1, 2, 3), vct(1, 1, 1), vct(0, 1, 0), vct(3, 1, 2)])
    update_lisp_graph
    $lisp_graph_hook.reset_hook!
    close_sound(ind)
    # 
    ind1 = open_sound("2.snd")
    wids3 = channel_widgets(ind1, 0)
    wids4 = channel_widgets(ind1, 1)
    if (not list_p(wids)) or (not list_p(wids3)) or
        ($with_test_motif and (wids1.length != 11 or wids2.length != 11))
      snd_display("channel_widgets confused: %s %s %s %s %s?", wids, wids1, wids2, wids3, wids4)
    end
    hide_widget(channel_widgets.first)
    show_widget(channel_widgets.first)
    close_sound(true)
  end
end

# ---------------- test 18: enved ----------------
  
def test_18
  if $with_test_gui
    start_enveloping
    ind = open_sound("oboe.snd")
    if (res = channel_envelope(ind, 0)) != [0.0, 1.0, 1.0, 1.0]
      snd_display("channel_envelope: %s?", res)
    end
    set_channel_envelope([0, 0, 1, 1, 2, 0], ind, 0)
    if (res = channel_envelope(ind, 0)) != [0, 0, 1, 1, 2, 0]
      snd_display("set_channel_envelope: %s?", res)
    end
    close_sound(ind)
    stop_enveloping
  end
end

# ---------------- test 19: save and restore ----------------

$after_save_state_hook_var = 0

def local_neq?(a, b)
  if float?(a) or float?(b)
    fneq(a, b)
  else
    a != b
  end
end

def insert_vct(v, beg = 0, dur = false, snd = false, chn = false)
  insert_samples(beg, dur, v, snd, chn, false, false,
                 format("%s(%s, %s, %s", get_func_name, v.to_str, beg, dur))
end

def clm_channel_test(snd = false, chn = false)
  clm_channel(make_two_zero(1, -1), 0, false, snd, chn, false, false, "clm_channel_test(")
end

def make_v_mix(snd = false, chn = false)
  mix_vct([0.1, 0.2, 0.3].to_vct, 100, snd, chn, true, "mix_vct(vct(0.1, 0.2, 0.3)")
end

def test_19_00
  nind = open_sound("oboe.snd")
  add_mark(123)
  delete_sample(12)
  set_x_bounds([0.2, 0.4])
  old_bounds = x_bounds
  set_show_axes(Show_all_axes)
  set_transform_graph_type(Graph_as_sonogram)
  set_speed_control_style(Speed_control_as_ratio)
  set_channel_style(Channels_superimposed)
  set_enved_target(Enved_srate)
  set_sound_property(:hi, "hi", nind)
  set_sound_property("ho", 1234, nind)
  set_channel_property(:ha, 3.14, nind, 0)
  $before_save_state_hook.reset_hook!
  $before_save_state_hook.add_hook!("snd-test") do |fname|
    File.open(File.expand_path(fname), "w") do |f|
      f.printf("# this comment will be at the top of the saved state file.\n")
    end
    true
  end
  $after_save_state_hook.reset_hook!
  $after_save_state_hook.add_hook!("snd-test") do |fname|
    File.open(File.expand_path(fname), "a+") do |f|
      f.printf("\n# from %s in %s", get_func_name, __FILE__)
      f.printf("\n$after_save_state_hook_var = 1234\n")
    end
  end
  delete_file(save_state_file)
  save_state(save_state_file)
  # save_options("test.temp")
  close_sound(nind)
  Snd.regions.apply(:forget_region)
  load(save_state_file)
  ind = find_sound("oboe.snd")
  if fneq_err(old_bounds[0], x_bounds(ind, 0)[0], 0.05) or # okay
      fneq_err(old_bounds[1], x_bounds(ind, 0)[1], 0.05)   # okay
    snd_display("save bounds: %s?", x_bounds(ind, 0))
  end
  if marks(ind, 0).length != 1
    snd_display("save marks: %s?", marks(ind, 0))
  end
  if (res = mark_sample(marks(ind, 0)[0])) != 122
    snd_display("save mark: %s?", res)
  end
  if (res = edit_position(ind, 0)) != 1
    snd_display("save edit_position: %s?", res)
  end
  if (res = edit_fragment(1, ind, 0)) != ["delete_samples(12, 1", "delete", 12, 1]
    snd_display("save edits: %s?", res)
  end
  if (res = edit_tree(ind, 0)) != [[0, 0, 0, 11, 1.0, 0.0, 0.0, 0],
                                   [12, 0, 13, 50827, 1.0, 0.0, 0.0, 0],
                                   [50827, -2, 0, 0, 0.0, 0.0, 0.0, 0]]
    snd_display("save edit_tree: %s?", res)
  end
  if (res = sound_property("ho", ind)) != 1234
    snd_display("sound_property saved: 1234 -> %s?", res.inspect)
  end
  if (res = sound_property(:hi, ind)) != "hi"
    snd_display("sound_property saved: hi -> %s?", res.inspect)
  end
  if (res = channel_property(:ha, ind, 0)) != 3.14
    snd_display("channel_property saved: 3.14 -> %s?", res.inspect)
  end
  close_sound(ind)
  if $after_save_state_hook_var != 1234
    snd_display("$after_save_state_hook_var: %s?", $after_save_state_hook_var)
  end
  $before_save_state_hook.reset_hook!
  $after_save_state_hook.reset_hook!
  if (res = Snd.catch(:cannot_save, 12345) do save_state("/bad/bad.save") end).first != 12345
    snd_display("save_state err: %s", res.inspect)
  end
  if (res = Snd.catch(:cannot_save, 12345) do save_listener("/bad/bad.save") end).first != 12345
    snd_display("save_listener err: %s", res.inspect)
  end
  #
  nind = open_sound("oboe.snd")
  set_sample(1, 0.5)
  delete_sample(100)
  insert_sample(10, 0.5)
  scale_channel(2.0)
  insert_silence(100, 20)
  save_edit_history("hiho.rb")
  revert_sound(nind)
  sfile = nind
  eval(File.open("hiho.rb").read)
  if (res = edit_fragment(1)) != ["set_sample(1, 0.5000", "set", 1, 1]
    snd_display("save_edit_history 1: %s?", res)
  end
  if (res = edit_fragment(2)) != ["delete_samples(100, 1", "delete", 100, 1]
    snd_display("save_edit_history 2: %s?", res)
  end
  if (res = edit_fragment(3)) != ["insert_sample(10, 0.5000", "insert", 10, 1]
    snd_display("save_edit_history 3: %s?", res)
  end
  if (res = edit_fragment(4)) != ["scale_channel(2.000, 0, false", "scale", 0, 50828]
    snd_display("save_edit_history 4: %s?", res)
  end
  if (res = edit_fragment(5)) != ["pad-channel", "zero", 100, 20]
    snd_display("save_edit_history 5: %s?", res)
  end
  save_edit_history("hiho.rb", nind, 0)
  scale_sound_to(1.0, 0, frames(nind, 0), nind, 0)
  eds = edit_position(nind, 0)
  val = insert_sound("zero.snd")
  if val.nonzero? or eds != edit_position(nind, 0)
    snd_display("insert_sound zero.snd, was an edit? %s %s %s", val, eds, edit_position(nind, 0))
  end
  revert_sound(nind)
  scale_sound_to(0.5, 0, frames(nind, 0), nind, 0)
  if fneq(res = maxamp(nind, 0), 0.5)
    snd_display("scale_sound_to(0.5): %s?", res)
  end
  close_sound(nind)
  # 
  nind = open_sound("oboe.snd")
  ramp_channel(0.0, 1.0)
  xramp_channel(0.0, 1.0, 32.0)
  save_edit_history("hiho.rb")
  revert_sound(nind)
  sfile = nind
  eval(File.open("hiho.rb").read)
  if (res = edit_fragment(1)) != ["ramp_channel(0.000, 1.000, 0, false", "env", 0, 50828]
    snd_display("save_edit_history ramp 1: %s?", res)
  end
  if (res = edit_fragment(2)) != ["xramp_channel(0.000, 1.000, 32.000, 0, false", "env", 0, 50828]
    snd_display("save_edit_history xramp 2: %s?", res)
  end
  revert_sound(nind)
  if (res = IO.readlines("hiho.rb")) != ["      ramp_channel(0.000, 1.000, 0, false, sfile, 0, false)\n",
                                         "      xramp_channel(0.000, 1.000, 32.000, 0, false, sfile, 0, false)\n"]
    snd_display("IO.readlines (file2string): %s?", res)
  end
  close_sound(nind)
  #
  ind = open_sound("oboe.snd")
  set_speed_control(Rational(2, 3).to_f, ind)
  set_filter_control_envelope([0.0, 0.0, 1.0, 1.0], ind)
  set_sound_property(:hi, 12345, ind)
  insert_samples(0, 100, Vct.new(100, 0.1), ind, 0)
  $save_state_hook.reset_hook!
  $save_state_hook.add_hook!("snd-test") do |fname| "savehook.snd" end
  save_state("s61.rb")
  close_sound(ind)
  if File.exists?("savehook.snd")
    load("s61.rb")
    ind = find_sound("oboe.snd")
    if sound?(ind)
      if fneq(res = speed_control(ind), Rational(2, 3).to_f)
        snd_display("save_state w/hook speed: %s?", res)
      end
      if (res = sound_property(:hi, ind)) != 12345
        snd_display("save_state w/hook property hi: %s?", res)
      end
      if (res = filter_control_envelope(ind)) != [0.0, 0.0, 1.0, 1.0]
        snd_display("save_state w/hook filter env: %s?", res)
      end
      $save_state_hook.reset_hook!
      $save_state_hook.add_hook!("snd-test") do |fname|
        snd_display("bogus $save_state_hook call!")
        "edit-list-to-function-saved.snd"
      end
      func = edit_list2function(ind, 0)
      if File.exists?("edit-list-to-function-saved.snd")
        snd_display("edit_list2function called $save_state_hook")
        delete_file("edit-list-to-function-saved.snd")
      end
      save_edit_history("save-edit-history-saved.rb", ind, 0)
      if File.exists?("edit-list-to-function-saved.snd")
        snd_display("save_edit_history called $save_state_hook")
        delete_file("edit-list-to-function-saved.snd")
      end
      delete_files("save-edit-history-saved.rb", "savehook.snd")
      close_sound(ind)
    else
      snd_display("save_state after hook restored but no sound?")
    end
  else
    snd_display("$save_state_hook redirect failed: %s?", $save_state_hook.inspect)
  end
  delete_file("s61.rb")
  $save_state_hook.reset_hook!
  # 
  add_sound_file_extension("ogg")
  add_sound_file_extension("OGG")
  add_sound_file_extension("sf")
  add_sound_file_extension("SF2")
  add_sound_file_extension("mp3")
  add_sound_file_extension("MP3")
  add_sound_file_extension("W01")
  add_sound_file_extension("W02")
  add_sound_file_extension("W03")
  add_sound_file_extension("W04")
  add_sound_file_extension("W05")
  add_sound_file_extension("W06")
  add_sound_file_extension("W07")
  add_sound_file_extension("W08")
  add_sound_file_extension("W09")
  add_sound_file_extension("W10")
  add_sound_file_extension("w01")
  add_sound_file_extension("w02")
  add_sound_file_extension("w03")
  add_sound_file_extension("w04")
  add_sound_file_extension("w05")
  add_source_file_extension("gad")
  # 
  ind = new_sound("fmv.snd")
  set_sample(10, 0.1)
  save_sound(ind)
  set_sample(1, 0.1)
  eds = safe_display_edits(ind)
  delete_file("t1.rb")
  save_state("t1.rb")
  close_sound(ind)
  # If savehook.snd doesn't exist, an IO-error will be raised.
  Snd.regions.each do |r| Snd.catch(:io_error) do forget_region(r) end end
  load("t1.rb")
  ind = find_sound("fmv.snd")
  unless sound?(ind)
    snd_display("save_state restored but no sound?")
  end
  3.upto(5) do |i|
    set_sample(i, i * 0.1)
    eds = safe_display_edits(ind)
    delete_file("t1.rb")
    save_state("t1.rb")
    close_sound(ind)
    Snd.regions.apply(:forget_region)
    load("t1.rb")
    ind = find_sound("fmv.snd")
    unless sound?(ind)
      snd_display("save_state %s restored but no sound?", i)
    end
  end
  close_sound(ind)
  delete_file("t1.rb")
  #
  ind = new_sound("fmv.snd", Mus_next, Mus_bshort, 22050, 8,
                  "this is an 8-channel save-state test")
  ind1 = new_sound("fmv1.snd", Mus_next, Mus_bshort, 22050, 2,
                   "this is an 2-channel save-state test")
  set_sample(10, 0.1, ind, 0)
  set_sample(10, 0.2, ind, 1)
  set_sample(10, 0.3, ind, 2)
  set_sample(10, 0.4, ind, 3)
  set_sample(10, -0.1, ind1, 0)
  set_sample(10, -0.2, ind1, 1)
  save_sound(ind)
  save_sound(ind1)
  set_sample(1, 0.1, ind, 0)
  set_sample(1, 0.2, ind, 1)
  set_sample(1, 0.3, ind, 2)
  set_sample(1, 0.4, ind, 3)
  set_sample(1, -0.1, ind1, 0)
  set_sample(1, -0.2, ind1, 1)
  eds = safe_display_edits(ind)
  eds1 = safe_display_edits(ind1)
  delete_file("t1.rb")
  save_state("t1.rb")
  close_sound(ind)
  close_sound(ind1)
  Snd.regions.apply(:forget_region)
  load("t1.rb")
  ind = find_sound("fmv.snd")
  ind1 = find_sound("fmv1.snd")
  if (not sound?(ind)) or (not sound?(ind1))
    snd_display("save_state (2) restored but no sound? %s %s", ind, ind1)
  end
  close_sound(ind)
  close_sound(ind1)
  delete_file("t1.rb")
  #
  ind = open_sound("oboe.snd")
  old_save_dir = save_dir
  old_eps_file = eps_file
  set_save_dir(false)
  set_samples(100, 32, Vct.new(32, 1.0))
  map_channel(lambda do |y| y + 0.1 end, 1000, 10000)
  set_show_axes(Show_no_axes, ind, 0)
  set_zoom_focus_style(Zoom_focus_middle)
  set_transform_normalization(Dont_normalize, ind, 0)
  set_graph_style(Graph_filled, ind, 0)
  set_transform_graph_type(Graph_as_spectrogram, ind, 0)
  unless $with_test_gtk
    # FIXME (GTK segfault)
    # doesn't work with GTK
    # snd-ruby-xg -noinit snd-test.rb 19 ==> hangs on load("s61.rb") below
    set_time_graph_type(Graph_as_wavogram, ind, 0)
  end
  set_x_axis_style(X_axis_as_percentage, ind, 0)
  set_speed_control_style(Speed_control_as_semitone, ind, 0)
  set_cursor(1234, ind, 0)
  set_eps_file("hiho.eps")
  set_amp_control_bounds([0.0, 2.5], ind)
  set_speed_control_bounds([1.0, 2.5], ind)
  set_reverb_control_scale_bounds([0.0, 2.5], ind)
  set_reverb_control_length_bounds([0.0, 2.5], ind)
  set_contrast_control_bounds([0.0, 2.5], ind)
  set_x_axis_label("time-x", ind, 0, Time_graph)
  set_y_axis_label("amp-y", ind, 0, Time_graph)
  old_srate = mus_srate
  old_file_buffer_size = mus_file_buffer_size
  old_array_print_length = mus_array_print_length
  old_clm_table_size = clm_table_size
  set_mus_srate(48000)
  set_mus_array_print_length(24)
  set_mus_file_buffer_size(4096)
  set_clm_table_size(256)
  delete_file("s61.rb")
  save_state("s61.rb")
  close_sound(ind)
  Snd.regions.apply(:forget_region)
  load("s61.rb")
  if fneq(res = mus_srate, 48000.0)
    snd_display("save/restore mus_srate: %s", res)
  end
  if (res = mus_file_buffer_size) != 4096
    snd_display("save/restore mus_file_buffer_size: %s", res)
  end
  if (res = mus_array_print_length) != 24
    snd_display("save/restore mus_array_print_length: %s", res)
  end
  if (res = clm_table_size) != 256
    snd_display("save/restore clm_table_size: %s", res)
  end
  set_mus_srate(old_srate)
  set_mus_array_print_length(old_array_print_length)
  set_mus_file_buffer_size(old_file_buffer_size)
  set_clm_table_size(old_clm_table_size)
  set_save_dir(old_save_dir)
  ind = find_sound("oboe.snd")
  if (res = show_axes(ind, 0)) != Show_no_axes
    snd_display("save Show_no_axes: %s?", res.inspect)
  end
  if (res = zoom_focus_style) != Zoom_focus_middle
    snd_display("save Zoom_focus_middle: %s?", res.inspect)
  end
  if (res = transform_normalization(ind, 0)) != Dont_normalize
    snd_display("save Dont_normalize: %s?", res.inspect)
  end
  if (res = graph_style(ind, 0)) != Graph_filled
    snd_display("save Graph_filled: %s?", res.inspect)
  end
  if (res = transform_graph_type(ind, 0)) != Graph_as_spectrogram
    snd_display("save Graph_as_spectrogram: %s?", res.inspect)
  end
  if (res = time_graph_type(ind, 0)) != Graph_as_wavogram
    snd_display("save Graph_as_wavogram: %s?", res.inspect)
  end
  if (res = x_axis_style(ind, 0)) != X_axis_as_percentage
    snd_display("save X_axis_as_percentage: %s?", res.inspect)
  end
  if (res = speed_control_style(ind)) != Speed_control_as_semitone
    snd_display("save Speed_control_as_semitone: %s?", res.inspect)
  end
  if (res = cursor(ind, 0)) != 1234
    snd_display("save cursor 1234: %s?", res.inspect)
  end
  if (res = eps_file) != "hiho.eps"
    snd_display("save eps_file: %s?", res.inspect)
  end
  if defined? set_x_axis_label
    if (res = x_axis_label(ind, 0, Time_graph)) != "time-x"
      snd_display("save x_axis_label: %s?", res.inspect)
    end
    if (res = y_axis_label(ind, 0, Time_graph)) != "amp-y"
      snd_display("save y_axis_label: %s?", res.inspect)
    end
  end
  if amp_control_bounds(ind) != [0.0, 2.5]
    snd_display("save amp_control_bounds: %s?", res.inspect)
  end
  if speed_control_bounds(ind) != [1.0, 2.5]
    snd_display("save speed_control_bounds: %s?", res.inspect)
  end
  if contrast_control_bounds(ind) != [0.0, 2.5]
    snd_display("save contrast_control_bounds: %s?", res.inspect)
  end
  if reverb_control_scale_bounds(ind) != [0.0, 2.5]
    snd_display("save reverb_control_scale_bounds: %s?", res.inspect)
  end
  if reverb_control_length_bounds(ind) != [0.0, 2.5]
    snd_display("save reverb_control_length_bounds: %s?", res.inspect)
  end
  set_eps_file(old_eps_file)
  delete_file("s61.rb")
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  old_tiny_font = tiny_font
  old_peaks_font = peaks_font
  old_bold_peaks_font = bold_peaks_font
  old_amp = amp_control_bounds
  old_speed = speed_control_bounds
  old_contrast = contrast_control_bounds
  old_revlen = reverb_control_length_bounds
  old_revscl = reverb_control_scale_bounds
  set_tiny_font("8x13")
  set_peaks_font("8x13")
  set_bold_peaks_font("8x13")
  set_amp_control_bounds([0.0, 2.5])
  set_speed_control_bounds([1.0, 2.5])
  set_reverb_control_scale_bounds([0.0, 2.5])
  set_reverb_control_length_bounds([0.0, 2.5])
  set_contrast_control_bounds([0.0, 2.5])
  save_state("s61.rb")
  close_sound(ind)
  Snd.regions.apply(:forget_region)
  load("s61.rb")
  ind = find_sound("oboe.snd")
  if (res = tiny_font) != "8x13"
    snd_display("save tiny_font: %s?", res)
  end
  if (res = peaks_font) != "8x13"
    snd_display("save peaks_font: %s?", res)
  end
  if (res = bold_peaks_font) != "8x13"
    snd_display("save bold_peaks_font: %s?", res)
  end
  if amp_control_bounds() != [0.0, 2.5]
    snd_display("save amp_control_bounds: %s?", res)
  end
  if speed_control_bounds() != [1.0, 2.5]
    snd_display("save speed_control_bounds: %s?", res)
  end
  if contrast_control_bounds() != [0.0, 2.5]
    snd_display("save contrast_control_bounds: %s?", res)
  end
  if reverb_control_scale_bounds() != [0.0, 2.5]
    snd_display("save reverb_control_scale_bounds: %s?", res)
  end
  if reverb_control_length_bounds() != [0.0, 2.5]
    snd_display("save reverb_control_length_bounds: %s?", res)
  end
  set_tiny_font(old_tiny_font)
  set_peaks_font(old_peaks_font)
  set_bold_peaks_font(old_bold_peaks_font)
  set_amp_control_bounds(old_amp)
  set_speed_control_bounds(old_speed)
  set_contrast_control_bounds(old_contrast)
  set_reverb_control_length_bounds(old_revlen)
  set_reverb_control_scale_bounds(old_revscl)
  delete_file("s61.rb")
  close_sound(ind)
end

def test_19_01
  #                                   new_globals         new_locals
  funcs = [[:transform_graph_type,    Graph_as_sonogram,  Graph_once],
           [:time_graph_type,         Graph_as_wavogram,  Graph_once],
           [:show_axes,               Show_all_axes,      Show_x_axis],
           [:transform_normalization, Normalize_by_sound, Normalize_by_channel],
           [:graph_style,             Graph_dots,         Graph_lines],
           [:x_axis_style,            X_axis_in_samples,  X_axis_in_seconds],
           [:spectro_x_scale,         0.1,                1.0],
           [:transform_size,          32,                 256],
           [:fft_window,              Bartlett_window,    Blackman2_window],
           [:dot_size,                4,                  1],
           [:max_transform_peaks,     10,                 100],
           [:with_verbose_cursor,     true,               false],
           [:zero_pad,                1,                  0],
           [:min_dB,                  -90,                -60],
           [:spectro_hop,             12,                 4],
           [:spectrum_end,            0.1,                1.0],
           [:cursor_size,             15,                 25],
           [:cursor_style,            Cursor_cross,       Cursor_line]]
  old_globals = funcs.map do |func, global, local| snd_func(func) end
  ind = open_sound("oboe.snd")
  funcs.each do |func, global, local|
    set_snd_func(func, global)
    set_snd_func(func, local, ind, 0)
  end
  set_zoom_focus_style(Zoom_focus_right)
  set_channel_style(Channels_combined)
  set_channel_style(Channels_separate, ind)
  delete_file("s61.rb")
  save_state("s61.rb")
  close_sound(ind)
  Snd.regions.apply(:forget_region)
  load("s61.rb")
  ind = find_sound("oboe.snd")
  funcs.each do |func, global, local|
    if $with_test_nogui
      next if func == :transform_normalization
      next if func == :spectro_x_scale
      next if func == :spectro_hop
      next if func == :spectrum_end
    end
    if local_neq?(res1 = snd_func(func), global) or
        local_neq?(res2 = snd_func(func, ind, 0), local)
      snd_display("save %s reversed: %s [%s] %s [%s]?", func, res1, global, res2, local)
    end
  end
  if (res = channel_style(ind)) != Channels_separate
    snd_display("save channel_style reversed: %s %s?", channel_style, res)
  end
  funcs.zip(old_globals) do |args, old_global|
    set_snd_func(args.first, old_global)
  end
  close_sound(ind)
  set_zoom_focus_style(Zoom_focus_active)
  set_channel_style(Channels_separate)
  delete_file("s61.rb")
  # 
  ind0 = open_sound("oboe.snd")
  ind1 = open_sound("oboe.snd")
  if (res = find_sound("oboe.snd", 0)) != ind0
    snd_display("find_sound 0: ind0 %s res %s?", ind0, res)
  end
  if (res = find_sound("oboe.snd", 1)) != ind1
    snd_display("find_sound 1: ind1 %s res %s?", ind1, res)
  end
  add_mark(123, ind0)
  add_mark(321, ind1)
  delete_file("s61.rb")
  save_state("s61.rb")
  close_sound(ind0)
  close_sound(ind1)
  load("s61.rb")
  ind0 = find_sound("oboe.snd", 0)
  ind1 = find_sound("oboe.snd", 1)
  if (not ind0) or (not ind1)
    snd_display("saved 2oboes, found: %s", Snd.sounds.map do |s| short_file_name(s) end)
  end
  unless find_mark(123, ind0)
    snd_display("saved 2oboes mark 0?")
  end
  if find_mark(123, ind1)
    snd_display("saved 2oboes mark 1->0?")
  end
  unless find_mark(321, ind1)
    snd_display("saved 2oboes mark 1?")
  end
  if find_mark(321, ind0)
    snd_display("saved 2oboes mark 0->1?")
  end
  close_sound(ind0)
  close_sound(ind1)
  # basic choices
  [[lambda { |ind| insert_sample(10, 0.5, ind, 0) },
    lambda { |ind|
      if fneq(sample(10), 0.5)
        snd_display("insert_sample save_state: %s?", channel2vct(5, 10, ind, 0))
      end
      if (res = frames(ind, 0)) != 101
        snd_display("insert_sample save_state len: %s?", res)
      end
    }],
   [lambda { |ind| delete_sample(10, ind, 0) },
    lambda { |ind|
      if fneq(sample(10), 0.0)
        snd_display("delete_sample save_state: %s?", channel2vct(5, 10, ind, 0))
      end
      if (res = frames(ind, 0)) != 99
        snd_display("delete_sample save_state len: %s?", res)
      end
    }],
   [lambda { |ind| set_sample(10, 0.5, ind, 0) },
    lambda { |ind|
      if fneq(sample(10), 0.5)
        snd_display("set_sample save_state: %s?", channel2vct(5, 10, ind, 0))
      end
      if (res = frames(ind, 0)) != 100
        snd_display("set_sample save_state len: %s?", res)
      end
    }],
   [lambda { |ind| set_sample(10, 0.5, ind, 0); scale_channel(0.5) },
    lambda { |ind|
      if fneq(sample(10), 0.25)
        snd_display("scl sample save_state: %s?", channel2vct(5, 10, ind, 0))
      end
      if (res = frames(ind, 0)) != 100
        snd_display("scl sample save_state len: %s?", res)
      end
      if (res = edit_position(ind, 0)) != 2
        snd_display("scl sample save_state edpos: %s?", res)
      end
    }],
   [lambda { |ind| vct2channel(Vct.new(10, 0.5), 10, 5, ind, 0); pad_channel(12, 5, ind, 0) },
    lambda { |ind|
      if (res = frames(ind, 0)) != 105
        snd_display("pad sample save_state len: %s?", res)
      end
      if (res = edit_position(ind, 0)) != 2
        snd_display("pad sample save_state edpos: %s?", res)
      end
      unless vequal(res = channel2vct(10, 10, ind, 0),
                    vct(0.5, 0.5, 0, 0, 0, 0, 0, 0.5, 0.5, 0.5))
        snd_display("pad sample save_state: %s?", res)
      end
    }],
   [lambda { |ind| map_channel(lambda { |y| 1.0 }); env_channel([0, 0, 1, 1], 0, 11, ind, 0) },
    lambda { |ind|
      if (res = frames(ind, 0)) != 100
        snd_display("env sample save_state len: %s?", res)
      end
      if (res = edit_position(ind, 0)) != 2
        snd_display("env sample save_state edpos: %s?", res)
      end
      unless vequal(res = channel2vct(0, 15, ind, 0),
                    vct(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1, 1, 1, 1))
        snd_display("env sample save_state: %s?", res)
      end
    }],
   # map_channel as backup
   [lambda { |ind|
      ctr = 0
      map_channel(lambda { |y|
                    ctr += 1
                    ctr.even? ? 0.1 : false
                  })
    },
    lambda { |ind|
      if (res = frames(ind, 0)) != 50
        snd_display("map false save_state len: %s?", res)
      end
      if (res = edit_position(ind, 0)) != 1
        snd_display("map false save_state edpos: %s?", res)
      end
      if fneq(res = maxamp(ind, 0), 0.1)
        snd_display("map false save_state max: %s?", res)
      end
      unless vequal(res = channel2vct(0, 10, ind, 0), Vct.new(10, 0.1))
        snd_display("map false save_state: %s?", res)
      end
    }],
   # as_one_edit
   [lambda { |ind|
      as_one_edit(lambda { | |
                    vct2channel(Vct.new(10) { |i| (i + 1) * 0.1 },  0, 10, ind, 0)
                    vct2channel(Vct.new(10) { |i| (i + 1) * 0.1 }, 20, 10, ind, 0)
                  })
    },
    lambda { |ind|
      if (res = edit_position(ind, 0)) != 1
        snd_display("save_state backup 2 vcts edpos: %s?", res)
      end
      unless vequal(res = channel2vct( 0, 10, ind, 0), Vct.new(10) { |i| (i + 1) * 0.1 })
        snd_display("as_one_edit save_state 1: %s?", res.to_str)
      end
      unless vequal(res = channel2vct(20, 10, ind, 0), Vct.new(10) { |i| (i + 1) * 0.1 })
        snd_display("as_one_edit save_state 2: %s?", res.to_str)
      end
    }],
   [lambda { |ind|
      as_one_edit(lambda { | |
                    vct2channel(Vct.new(10) { |i| (i + 1) * 0.1 },  0, 10, ind, 0)
                    scale_by(0.5)
                  })
    },
    lambda { |ind|
      if (res = edit_position(ind, 0)) != 1
        snd_display("save_state backup vct+scl edpos: %s?", res)
      end
      unless vequal(res = channel2vct(0, 10, ind, 0),
                    Vct.new(10) { |i| (i + 1) * 0.1 }.scale(0.5))
        snd_display("as_one_edit save_state 3: %s?", res)
      end
    }],
   [lambda { |ind|
      as_one_edit(lambda { | |
                    vct2channel(Vct.new(10) { |i| (i + 1) * 0.1 },  0, 10, ind, 0)
                    delete_samples(5, 5)
                  })
    },
    lambda { |ind|
      if (res = edit_position(ind, 0)) != 1
        snd_display("save_state backup vct+del edpos: %s?", res)
      end
      unless vequal(res = channel2vct(0, 10, ind, 0), Vct.new(10) { |i|
                      if i < 5
                        (i + 1) * 0.1
                      else
                        0.0
                      end
                    })
        snd_display("as_one_edit save_state 4: %s?", res)
      end
    }],
   [lambda { |ind|
      as_one_edit(lambda { | |
                    delete_samples(5, 5)
                    insert_samples(5, 2, vct(0.1, 0.2))
                  })
    },
    lambda { |ind|
      if (res = edit_position(ind, 0)) != 1
        snd_display("save_state backup del+insert edpos: %s?", res)
      end
      unless vequal(res = channel2vct(0, 10, ind, 0), Vct.new(10) { |i|
                      case i
                      when 5
                        0.1
                      when 6
                        0.2
                      else
                        0.0
                      end
                    })
        snd_display("as_one_edit save_state 5: %s?", res)
      end
      if (res = frames(ind, 0)) != 97
        snd_display("save_state backup del+insert len: %s?", res)
      end
    }],
   # 2 embedded as_one_edits
   [lambda { |ind|
      map_channel(lambda { |y| -1.0 })
      as_one_edit(lambda { | |
                    delete_samples(5, 5)
                    insert_samples(5, 2, vct(0.1, 0.2))
                  })
      scale_channel(2.0)
      as_one_edit(lambda { | |
                    vct2channel(Vct.new(10) { |i| (i + 1) * 0.1 }, 10, 10, ind, 0)
                    vct2channel(Vct.new(10) { |i| (i + 1) * 0.1 }, 20, 10, ind, 0)
                  })
      delete_samples(15, 10)
    },
    lambda { |ind|
      if (res = edit_position(ind, 0)) != 5
        snd_display("embed save_state edpos: %s?", res)
      end
      if (res = frames(ind, 0)) != 87
        snd_display("embed save_state len: %s?", res)
      end
      unless vequal(res = channel2vct(0, 25, ind, 0),
                    vct(-2, -2, -2, -2, -2, 0.2, 0.4, -2, -2, -2, 0.1, 0.2, 0.3,
                        0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, -2, -2, -2, -2, -2))
        snd_display("embed save_state 9: %s?", res.to_str)
      end
    }]].each_with_index do |args, i|
    func = args[0]
    test = args[1]
    ind = new_sound("test.snd", Mus_next, Mus_bfloat, 22050, 1, "mono save-state tests", 100)
    func.call(ind)
    delete_file("s61.rb")
    save_state("s61.rb")
    close_sound(ind)
    load("s61.rb")
    if sound?(ind = find_sound("test.snd"))
      test.call(ind)
      close_sound(ind)
    else
      snd_display("save_state test %s no test.snd?", i)
    end
  end
end

def test_19_02
  # 
  # edit_list2function
  # 
  ind = open_sound("oboe.snd")
  mx0 = maxamp
  frs = frames
  # simple scale
  scale_channel(2.0)
  if fneq(res = maxamp, 2 * mx0)
    snd_display("edit_list2function off to a bad start: %s?", res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 1: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  scale_channel(2.000, 0, false, snd, chn) }"
    snd_display("edit_list2function 1: %s", res)
  end
  func.call(ind, 0)
  if fneq(res = maxamp, 4 * mx0)
    snd_display("edit_list2function called (1): %s %s?", res, mx0)
  end
  revert_sound(ind)
  scale_by(2.0)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 1a: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  scale_channel(2.000, 0, false, snd, chn) }"
    snd_display("edit_list2function 1a: %s", res)
  end
  revert_sound(ind)
  normalize_channel(1.001)      # normalize_channel(1.0) seems to do nothing
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 1c: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  normalize_channel(1.001, 0, false, snd, chn) }"
    snd_display("edit_list2function 1c: %s", res)
  end
  revert_sound(ind)
  # simple delete
  delete_samples(10, 100)
  if (res = frames) != frs - 100
    snd_display("edit_list2function delete: %s %s?", frs, res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 2: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  delete_samples(10, 100, snd, chn) }"
    snd_display("edit_list2function 2: %s", res)
  end
  func.call(ind, 0)
  if (res = frames) != frs - 200
    snd_display("edit_list2function called (2): %s %s?", frs, res)
  end
  revert_sound(ind)
  delete_sample(100)
  if (res = frames) != frs - 1
    snd_display("edit_list2function delete (2a): %s %s?", frs, res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 2a: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  delete_samples(100, 1, snd, chn) }"
    snd_display("edit_list2function 2a: %s", res)
  end
  func.call(ind, 0)
  if (res = frames) != frs - 2
    snd_display("edit_list2function called (2a): %s %s?", frs, res)
  end
  revert_sound(ind)
  # simple zero pad
  pad_channel(10, 100)
  if (res = frames) != frs + 100
    snd_display("edit_list2function pad: %s %s?", frs, res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 3: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  pad_channel(10, 100, snd, chn) }"
    snd_display("edit_list2function 3: %s", res)
  end
  func.call(ind, 0)
  if (res = frames) != frs + 200
    snd_display("edit_list2function called (3): %s %s?", frs, res)
  end
  revert_sound(ind)
  insert_silence(10, 100)
  if (res = frames) != frs + 100
    snd_display("edit_list2function pad (3a): %s %s?", frs, res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 3a: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  pad_channel(10, 100, snd, chn) }"
    snd_display("edit_list2function 3a: %s", res)
  end
  func.call(ind, 0)
  if (res = frames) != frs + 200
    snd_display("edit_list2function called (3a): %s %s?", frs, res)
  end
  revert_sound(ind)
  # simple ramp
  ramp_channel(0.2, 0.9)
  snd_test_neq(maxamp(), 0.0899, "edit_list2function ramp")
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 4: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  ramp_channel(0.200, 0.900, 0, false, snd, chn) }"
    snd_display("edit_list2function 4: %s", res)
  end
  func.call(ind, 0)
  snd_test_neq(maxamp(), 0.061, "edit_list2function called (4)")
  revert_sound(ind)
  # simple xramp
  xramp_channel(0.2, 0.9, 32.0)
  snd_test_neq(maxamp(), 0.055, "edit_list2function xramp")
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 5: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  xramp_channel(0.200, 0.900, 32.000, 0, false, snd, chn) }"
    snd_display("edit_list2function 5: %s", res)
  end
  func.call(ind, 0)
  if fneq(res = maxamp, 0.0266)
    snd_display("edit_list2function called (5): %s?", res)
  end
  revert_sound(ind)
  # simple env
  env_sound([0, 0, 1, 1])
  snd_test_neq(maxamp(), 0.0906, "edit_list2function env")
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 6: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel([0.000, 0.000, 1.000, 1.000], 0, false, snd, chn) }"
    snd_display("edit_list2function 6: %s", res)
  end
  func.call(ind, 0)
  snd_test_neq(maxamp(), 0.0634, "edit_list2function called (6)")
  revert_sound(ind)
  # less simple env
  env_sound([0, 0, 1, 0.3, 2, 0.8, 3, 0])
  snd_test_neq(maxamp(), 0.107, "edit_list2function env (7)")
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 7: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel([0.000, 0.000, 1.000, 0.300, 2.000, 0.800, 3.000, 0.000], 0, false, snd, chn) }"
    snd_display("edit_list2function 7: %s", res)
  end
  func.call(ind, 0)
  if fneq(res = maxamp, 0.0857)
    snd_display("edit_list2function called (7): %s?", res)
  end
  revert_sound(ind)
  env_sound([0, 0, 1, 0.3, 2, 0.8, 3, 0])
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 7a: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel([0.000, 0.000, 1.000, 0.300, 2.000, 0.800, 3.000, 0.000], 0, false, snd, chn) }"
    snd_display("edit_list2function 7a: %s", res)
  end
  revert_sound(ind)
  env_sound([0, 0, 1, 0.3, 2, 0.8, 3, 0], 1000, 2000)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 7b: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel(make_env([0.000, 0.000, 1.000, 0.300, 2.000, 0.800, 3.000, 0.000], :base, 1.0000, :end, 1999), 1000, 2000, snd, chn) }"
    snd_display("edit_list2function 7b: %s", res)
  end
  revert_sound(ind)
  env_sound(make_env(:envelope, [0, 0, 1, 0.3, 2, 0.8, 3, 0], :base, 32.0, :length, 2000), 1000, 2000)
  mxenv0 = maxamp
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 7c: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel(make_env([0.000, 0.000, 1.000, 0.300, 2.000, 0.800, 3.000, 0.000], :base, 32.0000, :end, 1999), 1000, 2000, snd, chn) }"
    snd_display("edit_list2function 7c: %s", res)
  end
  revert_sound(ind)
  env_sound(make_env(:envelope, [0, 0, 1, 0.3, 2, 0.8, 3, 0],
                     :offset, 2.0,
                     :scaler, 3.0,
                     :length, 2000),
            1000, 2000)
  mxenv1 = maxamp
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 7d: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel(make_env([0.000, 2.000, 1.000, 2.900, 2.000, 4.400, 3.000, 2.000], :base, 1.0000, :end, 1999), 1000, 2000, snd, chn) }"
    snd_display("edit_list2function 7d: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if fneq(res = maxamp, mxenv1)
    snd_display("edit_list2function 7d max: %s %s %s?", res, mxenv1, mxenv0)
  end
  revert_sound(ind)
  5.times do |i| env_channel([0, 0, 1, 1, 2, 0]) end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 7e: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel([0.000, 0.000, 1.000, 1.000, 2.000, 0.000], 0, false, snd, chn); env_channel([0.000, 0.000, 1.000, 1.000, 2.000, 0.000], 0, false, snd, chn); env_channel([0.000, 0.000, 1.000, 1.000, 2.000, 0.000], 0, false, snd, chn); env_channel([0.000, 0.000, 1.000, 1.000, 2.000, 0.000], 0, false, snd, chn); env_channel([0.000, 0.000, 1.000, 1.000, 2.000, 0.000], 0, false, snd, chn) }"
    snd_display("edit_list2function 7e: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  snd_test_neq(maxamp(), 0.146, "edit_list2function 7e max")
  if (res = edit_position) != 5
    snd_display("edit_list2function 7e edpos: %s?", res)
  end
  revert_sound(ind)
  env_sound([0, 0, 1, 1, 2, 0], 0, frames, 32.0)
  snd_test_neq(maxamp(), 0.146, "edit_list2function 7f max")
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 7f: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel_with_base([0.000, 0.000, 1.000, 1.000, 2.000, 0.000], 32.0000, 0, false, snd, chn) }"
    snd_display("edit_list2function 7f: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  snd_test_neq(maxamp(), 0.146, "edit_list2function called (7f)")
  revert_sound(ind)
  env_sound([0, 0, 1, 1, 2, 1, 3, 0], 0, frames, 0.0)
  if fneq(res = sample(4000), 0.0)
    snd_display("edit_list2function env 7g: %s?", res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 7g: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel_with_base([0.000, 0.000, 1.000, 1.000, 2.000, 1.000, 3.000, 0.000], 0.0000, 0, false, snd, chn) }"
    snd_display("edit_list2function 7g: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if fneq(res = sample(4000), 0.0)
    snd_display("edit_list2function called (7g): %s?", res)
  end
  revert_sound(ind)
  # simple ptree skipped
  # simple 1 sample insert
  insert_sample(100, 0.1)
  if (res = frames) != frs + 1
    snd_display("edit_list2function insert_sample: %s %s?", frs, res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 9: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  insert_sample(100, 0.1000, snd, chn) }"
    snd_display("edit_list2function 9: %s", res)
  end
  func.call(ind, 0)
  unless vequal(res = channel2vct(99, 4), vct(0.0, 0.1, 0.1, 0.0))
    snd_display("edit_list2function func 9: %s?", res)
  end
  if (res = frames) != frs + 2
    snd_display("edit_list2function called (9): %s %s?", frs, res)
  end
  revert_sound(ind)
  # insert_samples with data
  insert_samples(0, 100, Vct.new(100, 0.1))
  if (res = frames) != frs + 100
    snd_display("edit_list2function insert_samples (100): %s %s?", frs, res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 9a: %s", func)
  end
  func.call(ind, 0)
  if (res = frames) != frs + 200
    snd_display("edit_list2function insert_samples (200): %s %s?", frs, res)
  end
  unless vequal(res = channel2vct(0, 5), vct(0.1, 0.1, 0.1, 0.1, 0.1))
    snd_display("edit_list2function func 9a: %s?", res)
  end
  revert_sound(ind)
  # set_samples with data
  set_samples(0, 100, Vct.new(100, 0.1))
  if (res = frames) != frs
    snd_display("edit_list2function set_samples (1): %s %s?", frs, res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 9b: %s", func)
  end
  func.call(ind, 0)
  if (res = frames) != frs
    snd_display("edit_list2function set_samples (2): %s %s?", frs, res)
  end
  unless vequal(res = channel2vct(0, 5), vct(0.1, 0.1, 0.1, 0.1, 0.1))
    snd_display("edit_list2function func 9b: %s?", res)
  end
  revert_sound(ind)
  # simple 1 sample set
  val = sample(100)
  set_sample(100, 0.1)
  if (res = frames) != frs
    snd_display("edit_list2function set_sample frames: %s %s?", frs, res)
  end
  if fneq(res = sample(100), 0.1)
    snd_display("edit_list2function set_sample val: %s %s?", val, res)
  end
  func = edit_list2function
  revert_sound(ind)
  if fneq(res = sample(100), val)
    snd_display("edit_list2function unset_sample val: %s %s?", val, res)
  end
  unless proc?(func)
    snd_display("edit_list2function 10: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  set_sample(100, 0.1000, snd, chn) }"
    snd_display("edit_list2function 10: %s", res)
  end
  func.call(ind, 0)
  unless vequal(res = channel2vct(99, 4), vct(0.0, 0.1, 0.0, 0.0))
    snd_display("edit_list2function func 10: %s?", res)
  end
  revert_sound(ind)
  pfrs = mus_sound_frames("pistol.snd")
  insert_sound("pistol.snd", 1000)
  if (res = frames) != frs + pfrs
    snd_display("edit_list2function insert_sound frames: %s %s?", frs, res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 10a: %s", func)
  end
  if (res = func.source) != format("Proc.new {|snd, chn|  insert_sound(%s, 1000, 0, snd, chn) }", File.expand_path("pistol.snd").inspect)
    snd_display("edit_list2function 10a: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if (res = frames) != frs + pfrs
    snd_display("edit_list2function called (10a): %s %s?", frs, res)
  end
  revert_sound(ind)
  pfrs = mus_sound_frames("pistol.snd")
  insert_samples(1000, pfrs, "pistol.snd")
  if (res = frames) != frs + pfrs
    snd_display("edit_list2function insert_samples frames: %s %s?", frs, res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 11: %s", func)
  end
  if (res = func.source) != format("Proc.new {|snd, chn|  insert_samples(1000, %s, %s, snd, chn) }",
                                   mus_sound_samples("pistol.snd"),
                                   File.expand_path("pistol.snd").inspect)
    snd_display("edit_list2function 11: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if (res = frames) != frs + pfrs
    snd_display("edit_list2function called (11): %s %s?", frs, res)
  end
  revert_sound(ind)
  smooth_channel(1000, 100)
  val = sample(1050)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 12: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  smooth_channel(1000, 100, snd, chn) }"
    snd_display("edit_list2function 12: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if fneq(res = sample(1050), val)
    snd_display("edit_list2function called (12): %s %s?", res, val)
  end
  revert_sound(ind)
  smooth_channel(1000, 100)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 12a: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  smooth_channel(1000, 100, snd, chn) }"
    snd_display("edit_list2function 12a: %s", res)
  end
  revert_sound(ind)
  # selection stuff
  make_selection(1000, 11000)
  scale_selection_by(2.0)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 13: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  scale_channel(2.000, 1000, 10001, snd, chn) }"
    snd_display("edit_list2function 13: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  snd_test_neq(maxamp(), 0.269, "edit_list2function called (13)")
  revert_sound(ind)
  scale_selection_to(1.0)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 13:a %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  normalize_channel(1.000, 1000, 10001, snd, chn) }"
    snd_display("edit_list2function 13a: %s", res)
  end
  revert_sound(ind)
  env_selection([0, 0, 1, 1, 2, 0])
  snd_test_neq(sample(4000), 0.0173, "edit_list2function 14 samp")
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 14: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  env_channel(make_env([0.000, 0.000, 1.000, 1.000, 2.000, 0.000], :base, 1.0000, :end, 10000), 1000, 10001, snd, chn) }"
    snd_display("edit_list2function 14: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  snd_test_neq(sample(4000), 0.0173, "edit_list2function called (14)")
  revert_sound(ind)
  make_selection(1000, 1100)
  smooth_selection
  val = sample(1050)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 14a: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  smooth_channel(1000, 101, snd, chn) }"
    snd_display("edit_list2function 14a: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if fneq(res = sample(1050), val)
    snd_display("edit_list2function called (14a): %s?", res)
  end
  revert_sound(ind)
  reverse_selection
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 14b: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  reverse_channel(1000, 101, snd, chn) }"
    snd_display("edit_list2function 14b: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  delete_selection
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 14c: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  delete_samples(1000, 101, snd, chn) }"
    snd_display("edit_list2function 14c: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  # simple reapply
  env_channel([0, 0, 1, 1, 2, 0])
  func = edit_list2function
  close_sound(ind)
  ind = new_sound("tmp.snd", Mus_next, Mus_bfloat, 22050, 1, :size, 20, :comment, false)
  map_channel(lambda do |y| 1.0 end)
  func.call(ind, 0)
  unless vequal(res = channel2vct,
                vct(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9,
                    1.0, 0.889, 0.778, 0.667, 0.556, 0.444, 0.333, 0.222, 0.111, 0.0))
    snd_display("edit_list2function env reapply: %s?", res)
  end
  close_sound(ind)
  ind = open_sound("oboe.snd")
  # insert-region
  reg = make_region(1000, 1100)
  insert_region(reg, 2000)
  val = sample(2050)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 16: %s", func)
  end
  if (res = func.source) != format("Proc.new {|snd, chn|  insert_region(integer2region(%s), 2000, snd, chn) }", region2integer(reg))
    snd_display("edit_list2function 16: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if fneq(res = sample(2050), val)
    snd_display("edit_list2function called (16): %s?", res)
  end
  revert_sound(ind)
  # reverse
  reverse_channel
  val = sample(2000)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 17: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  reverse_channel(0, false, snd, chn) }"
    snd_display("edit_list2function 17: %s", res)
  end
  if fneq(val, -0.002)
    snd_display("edit_list2function 17 val: %s?", val)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if fneq(val, -0.002)
    snd_display("edit_list2function 17 re-val: %s?", val)
  end
  revert_sound(ind)
  reverse_channel
  val = sample(2000)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 17a: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  reverse_channel(0, false, snd, chn) }"
    snd_display("edit_list2function 17a: %s", res)
  end
  if fneq(val, -0.002)
    snd_display("edit_list2function 17a val: %s?", val)
  end
  revert_sound(ind)
  reverse_channel(1000, 500)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 17b: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  reverse_channel(1000, 500, snd, chn) }"
    snd_display("edit_list2function 17b: %s", res)
  end
  revert_sound(ind)
  # src
  src_sound(2.0)
  if (frames - 25415).abs > 2
    snd_display("edit_list2function 18 len: %s?", frames)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 18: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  src_channel(2.0000, 0, false, snd, chn) }"
    snd_display("edit_list2function 18: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if (frames - 25415).abs > 2
    snd_display("edit_list2function 18 re-len: %s?", frames)
  end
  revert_sound(ind)
  src_channel(2.0, 1000, 500)
  frs = frames
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 18a: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  src_channel(2.0000, 1000, 500, snd, chn) }"
    snd_display("edit_list2function 18a: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if frames != frs
    snd_display("edit_list2function 18a re-len: %s?", frames)
  end
  revert_sound(ind)
  src_sound([0, 1, 1, 2, 2, 1])
  frs = frames
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 18b: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  src_channel([0.000, 1.000, 1.000, 2.000, 2.000, 1.000], 0, false, snd, chn) }"
    snd_display("edit_list2function 18b: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if frames != frs
    snd_display("edit_list2function 18b re-len: %s?", frames)
  end
  revert_sound(ind)
  src_channel([0, 1, 1, 2], 1000, 500)
  frs = frames
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 18c: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  src_channel([0.000, 1.000, 1.000, 2.000], 1000, 500, snd, chn) }"
    snd_display("edit_list2function 18c: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if frames != frs
    snd_display("edit_list2function 18c re-len: %s?", frames)
  end
  revert_sound(ind)
  # filter-channel
  filter_channel([0, 1, 1, 0], 10)
  mx = maxamp
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function 19: %s", func)
  end
  if (res = func.source) != "Proc.new {|snd, chn|  filter_channel([0.000, 1.000, 1.000, 0.000], 10, 0, false, snd, chn) }"
    snd_display("edit_list2function 19: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  if fneq(res = maxamp, mx)
    snd_display("edit_list2function 19 re-filter: %s %s?", mx, res)
  end
  revert_sound(ind)
  [[lambda { insert_vct(vct(1.0, 0.5), 0, 2) },
    "Proc.new {|snd, chn|  insert_vct(vct(1.000, 0.500), 0, 2, snd, chn) }"],
   [lambda { clm_channel_test },
    "Proc.new {|snd, chn|  clm_channel_test(snd, chn) }"],
   # examp.rb
   [lambda { fft_edit(1000, 3000) },
    "Proc.new {|snd, chn|  fft_edit(1000, 3000, snd, chn) }"],
   [lambda { fft_squelch(0.01) },
    "Proc.new {|snd, chn|  fft_squelch(0.01, snd, chn) }"],
   [lambda { fft_cancel(1000, 3000) },
    "Proc.new {|snd, chn|  fft_cancel(1000, 3000, snd, chn) }"],
   [lambda { squelch_vowels },
    "Proc.new {|snd, chn|  squelch_vowels(snd, chn) }"],
   [lambda { fft_env_edit([0, 0, 1, 1, 2, 0]) },
    "Proc.new {|snd, chn|  fft_env_edit([0, 0, 1, 1, 2, 0], snd, chn) }"],
   [lambda { fft_env_interp([0, 0, 1, 1, 2, 0], [0, 1, 1, 0, 2, 0], [0, 0, 1, 1]) },
    "Proc.new {|snd, chn|  fft_env_interp([0, 0, 1, 1, 2, 0], [0, 1, 1, 0, 2, 0], [0, 0, 1, 1], snd, chn) }"],
   [lambda { hello_dentist(10.0, 0.1) },
    "Proc.new {|snd, chn|  hello_dentist(10.0, 0.1, snd, chn) }"],
   [lambda { fp(1.0, 0.3, 20.0) },
    "Proc.new {|snd, chn|  fp(1.0, 0.3, 20.0, snd, chn) }"],
   [lambda { expsnd([0, 1, 1, 2]) },
    "Proc.new {|snd, chn|  expsnd([0, 1, 1, 2], snd, chn) }"],
   [lambda { voiced2unvoiced(1.0, 256, 2.0, 2.0) },
    "Proc.new {|snd, chn|  voiced2unvoiced(1.0, 256, 2.0, 2.0, snd, chn) }"],
   [lambda { env_sound_interp([0, 0, 1, 1, 2, 0], 2.0) },
    "Proc.new {|snd, chn|  env_sound_interp([0, 0, 1, 1, 2, 0], 2.0, snd, chn) }"],
   [lambda { add_notes([["1a.snd"], ["pistol.snd", 1.0, 2.0]]) },
    "Proc.new {|snd, chn|  add_notes([[\"1a.snd\"], [\"pistol.snd\", 1.0, 2.0]], snd, chn) }"],
   [lambda { compand_channel },
    "Proc.new {|snd, chn|  compand_channel(0, false, snd, chn) }"],
   [lambda { smooth_channel_via_ptree },
    "Proc.new {|snd, chn|  smooth_channel_via_ptree(0, false, snd, chn) }"],
   [lambda { ring_modulate_channel(300) },
    "Proc.new {|snd, chn|  ring_modulate_channel(300, 0, false, snd, chn) }"],
   [lambda { filtered_env([0, 0, 1, 1, 2, 0]) },
    "Proc.new {|snd, chn|  filtered_env([0, 0, 1, 1, 2, 0], snd, chn) }"],
   [lambda { reverse_by_blocks(0.1) },
    "Proc.new {|snd, chn|  reverse_by_blocks(0.1, snd, chn) }"],
   [lambda { reverse_within_blocks(0.1) },
    "Proc.new {|snd, chn|  reverse_within_blocks(0.1, snd, chn) }"], 
   # extensions.rb
   [lambda { mix_channel("1a.snd", 1200) },
    "Proc.new {|snd, chn|  mix_channel(\"1a.snd\", 1200, false, snd, chn) }"],
   [lambda { insert_channel("1a.snd", 1200) },
    "Proc.new {|snd, chn|  insert_channel(\"1a.snd\", 1200, false, snd, chn) }"],
   [lambda { sine_ramp(0.5, 0.9) },
    "Proc.new {|snd, chn|  sine_ramp(0.5, 0.9, 0, false, snd, chn) }"],
   [lambda { sine_env_channel([0, 0, 1, 1, 2, -0.5, 3, 1]) },
    "Proc.new {|snd, chn|  sine_env_channel([0, 0, 1, 1, 2, -0.5, 3, 1], 0, false, snd, chn) }"],
   [lambda { blackman4_ramp(0.0, 1.0) },
    "Proc.new {|snd, chn|  blackman4_ramp(0.0, 1.0, 0, false, snd, chn) }"],
   [lambda { blackman4_env_channel([0, 0, 1, 1, 2, -0.5, 3, 1]) },
    "Proc.new {|snd, chn|  blackman4_env_channel([0, 0, 1, 1, 2, -0.5, 3, 1], 0, false, snd, chn) }"],
   [lambda { ramp_squared(0.2, 0.8, true) },
    "Proc.new {|snd, chn|  ramp_squared(0.2, 0.8, true, 0, false, snd, chn) }"],
   [lambda { env_squared_channel([0, 0, 1, 1], true) },
    "Proc.new {|snd, chn|  env_squared_channel([0, 0, 1, 1], true, 0, false, snd, chn) }"],
   [lambda { ramp_expt(0.2, 0.8, 32.0, true) },
    "Proc.new {|snd, chn|  ramp_expt(0.2, 0.8, 32.0, true, 0, false, snd, chn) }"],
   [lambda { env_expt_channel([0, 0, 1, 1], 32.0, true) },
    "Proc.new {|snd, chn|  env_expt_channel([0, 0, 1, 1], 32.0, true, 0, false, snd, chn) }"],
   [lambda { offset_channel(0.1) },
    "Proc.new {|snd, chn|  offset_channel(0.1, 0, false, snd, chn) }"],
   [lambda { dither_channel(0.1) },
    "Proc.new {|snd, chn|  dither_channel(0.1, 0, false, snd, chn) }"],
   [lambda { contrast_channel(0.1) },
    "Proc.new {|snd, chn|  contrast_channel(0.1, 0, false, snd, chn) }"],
   # dsp.rb
   [lambda { ssb_bank(550, 600, 10) },
    "Proc.new {|snd, chn|  ssb_bank(550, 600, 10, 40, 50.0, 0, false, snd, chn) }"],
   [lambda { ssb_bank_env(550, 660, [0, 1, 1, 2], 10) },
    "Proc.new {|snd, chn|  ssb_bank_env(550, 660, [0, 1, 1, 2], 10, 40, 50.0, 0, false, snd, chn) }"],
   [lambda { down_oct(1) },
    "Proc.new {|snd, chn|  down_oct(1, snd, chn) }"],
   [lambda { freqdiv(8) },
    "Proc.new {|snd, chn|  freqdiv(8, snd, chn) }"],
   [lambda { adsat(8) },
    "Proc.new {|snd, chn|  adsat(8, false, false, snd, chn) }"],
   [lambda { spike },
    "Proc.new {|snd, chn|  spike(snd, chn) }"],
   [lambda { zero_phase },
    "Proc.new {|snd, chn|  zero_phase(snd, chn) }"],
   [lambda { rotate_phase(lambda { |x| random(PI) })  },
    format("Proc.new {|snd, chn|  rotate_phase(Proc.new {|val_r| rotate_phase_%s(val_r) }, snd, chn) }", edit_list_proc_counter + 1)],
   [lambda { brighten_slightly(0.5) },
    "Proc.new {|snd, chn|  brighten_slightly(0.5, snd, chn) }"],
   [lambda { shift_channel_pitch(100) },
    "Proc.new {|snd, chn|  shift_channel_pitch(100, 40, 0, false, snd, chn) }"],
   [lambda { channel_polynomial(vct(0, 0.5)) },
    "Proc.new {|snd, chn|  channel_polynomial(vct(0.000, 0.500), snd, chn) }"],
   [lambda { spectral_polynomial(vct(0, 0.5)) },
    "Proc.new {|snd, chn|  spectral_polynomial(vct(0.000, 0.500), snd, chn) }"],
   [lambda { notch_channel([60.0, 120.0, 240.0], false, false, false) },
    "Proc.new {|snd, chn|  notch_channel([60.0, 120.0, 240.0], false, false, false, snd, chn) }"],
   # effects.rb
   [lambda { effects_squelch_channel(0.1, 128) },
    "Proc.new {|snd, chn|  effects_squelch_channel(0.1, 128, snd, chn) }"],
   [lambda { effects_echo(false, 0.5, 0.1, 0, false) },
    "Proc.new {|snd, chn|  effects_echo(false, 0.5, 0.1, 0, false, snd, chn) }"],
   [lambda { effects_flecho_1(0.5, 0.1, false, 0, false) },
    "Proc.new {|snd, chn|  effects_flecho_1(0.5, 0.1, false, 0, false, snd, chn) }"],
   [lambda { effects_zecho_1(0.75, 0.75, 6.0, 10.0, false, 0, false) },
    "Proc.new {|snd, chn|  effects_zecho_1(0.75, 0.75, 6.0, 10.0, false, 0, false, snd, chn) }"],
   #[lambda { effects_comb_filter(0.1, 50, 0, false) },
   #"Proc.new {|snd, chn|  effects_comb_filter(0.1, 50, 0, false, snd, chn) }"],
   [lambda { effects_moog(10000, 0.5, 0, false) },
    "Proc.new {|snd, chn|  effects_moog(10000, 0.5, 0, false, snd, chn) }"],
   [lambda { effects_remove_dc },
    "Proc.new {|snd, chn|  effects_remove_dc(snd, chn) }"],
   [lambda { effects_compand },
    "Proc.new {|snd, chn|  effects_compand(snd, chn) }"],
   [lambda { effects_am(100.0, false) },
    "Proc.new {|snd, chn|  effects_am(100.0, false, 0, false, snd, chn) }"],
   [lambda { effects_rm(100.0, false) },
    "Proc.new {|snd, chn|  effects_rm(100.0, false, 0, false, snd, chn) }"],
   [lambda { effects_bbp(1000.0, 100.0, 0, false) },
    "Proc.new {|snd, chn|  effects_bbp(1000.0, 100.0, 0, false, snd, chn) }"],
   [lambda { effects_bbr(1000.0, 100.0, 0, false) },
    "Proc.new {|snd, chn|  effects_bbr(1000.0, 100.0, 0, false, snd, chn) }"],
   [lambda { effects_bhp(1000.0, 0, false) },
    "Proc.new {|snd, chn|  effects_bhp(1000.0, 0, false, snd, chn) }"],
   [lambda { effects_blp(1000.0, 0, false) },
    "Proc.new {|snd, chn|  effects_blp(1000.0, 0, false, snd, chn) }"],
   [lambda { effects_hello_dentist(50.0, 0.5, 0, false) },
    "Proc.new {|snd, chn|  effects_hello_dentist(50.0, 0.5, 0, false, snd, chn) }"],
   [lambda { effects_fp(1.0, 0.3, 20.0, 0, false) },
    "Proc.new {|snd, chn|  effects_fp(1.0, 0.3, 20.0, 0, false, snd, chn) }"],
   [lambda { effects_flange(5.0, 2.0, 0.001, 0, false) },
    "Proc.new {|snd, chn|  effects_flange(5.0, 2.0, 0.001, 0, false, snd, chn) }"],
   [lambda { effects_jc_reverb_1(0.1, 0, false) },
    "Proc.new {|snd, chn|  effects_jc_reverb_1(0.1, 0, false, snd, chn) }"]
  ].each_with_index do |args, i|
    func, descr = args
    func.call
    unless proc?(func = edit_list2function)
      snd_display("edit_list2function proc 20[%s]: %s", i, func.inspect)
    end
    if func.source != descr
      snd_display("edit_list2function source 20[%s]: %s", i, func.source)
    end
    revert_sound(ind)
    func.call(ind, 0)
    revert_sound(ind)
  end
  close_sound(ind)
end

def test_19_03
  #
  # apply controls edit lists
  # 
  ind = open_sound("oboe.snd")
  original_maxamp = maxamp
  reset_controls
  controls2channel([2.0])
  if fneq(res = amp_control(ind), 1.0)
    snd_display("controls2channel amp: %s?", res)
  end
  if fneq(res = maxamp, 2 * original_maxamp)
    snd_display("controls2channel maxamp: %s?", res)
  end
  func = edit_list2function
  if (res = func.source) != "Proc.new {|snd, chn|  scale_channel(2.000, 0, false, snd, chn) }"
    snd_display("edit_list2function controls2channel 1: %s", res)
  end
  func.call(ind, 0)
  revert_sound(ind)
  #
  controls2channel([false, 2.0])
  if fneq(res = channel2vct(22000, 22100).peak, 0.0479)
    snd_display("dp->end screwed up again!?!: %s?", res)
  end
  func = edit_list2function
  if (res = func.source) != "Proc.new {|snd, chn|  controls2channel([false, 2.0], 0, false, snd, chn) }"
    snd_display("edit_list2function controls2channel 2: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  snd_test_neq(speed_control(ind), 1.0, "controls2channel speed")
  #
  controls2channel([false, false, [0.5]])
  func = edit_list2function
  if (res = func.source) != "Proc.new {|snd, chn|  controls2channel([false, false, [0.5]], 0, false, snd, chn) }"
    snd_display("edit_list2function controls2channel 3: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  if fneq(res = contrast_control(ind), 0.0)
    snd_display("controls2channel contrast: %s?", res)
  end
  #
  controls2channel([false, false, [0.5, 2.0]])
  func = edit_list2function
  if (res = func.source) != "Proc.new {|snd, chn|  controls2channel([false, false, [0.5, 2.0]], 0, false, snd, chn) }"
    snd_display("edit_list2function controls2channel 3a: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  if fneq(res = contrast_control(ind), 0.0)
    snd_display("controls2channel contrast 3a: %s?", res)
  end
  #
  controls2channel([false, false, false, [0.5]])
  func = edit_list2function
  if (res = func.source) != "Proc.new {|snd, chn|  controls2channel([false, false, false, [0.5]], 0, false, snd, chn) }"
    snd_display("edit_list2function controls2channel 4: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  if fneq(res = expand_control(ind), 1.0)
    snd_display("controls2channel expand: %s?", res)
  end
  #
  controls2channel([false, false, false, [0.5, 0.1, 0.2, 0.06, 0.0]])
  func = edit_list2function
  if (res = func.source) != "Proc.new {|snd, chn|  controls2channel([false, false, false, [0.5, 0.1, 0.2, 0.06, 0.0]], 0, false, snd, chn) }"
    snd_display("edit_list2function controls2channel 4a: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  if fneq(res = expand_control(ind), 1.0)
    snd_display("controls2channel expand 4a: %s?", res)
  end
  #
  controls2channel([false, false, false, false, [0.1]])
  func = edit_list2function
  if (res = func.source) != "Proc.new {|snd, chn|  controls2channel([false, false, false, false, [0.1]], 0, false, snd, chn) }"
    snd_display("edit_list2function controls2channel 5: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  if fneq(res = reverb_control_scale(ind), 0.0)
    snd_display("controls2channel reverb: %s?", res)
  end
  #
  controls2channel([false, false, false, false, [0.1, 1.2, 0.9, 0.9, 2.0]])
  func = edit_list2function
  if (res = func.source) != "Proc.new {|snd, chn|  controls2channel([false, false, false, false, [0.1, 1.2, 0.9, 0.9, 2.0]], 0, false, snd, chn) }"
    snd_display("edit_list2function controls2channel 5a: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  if fneq(res = reverb_control_scale(ind), 0.0)
    snd_display("controls2channel reverb 5a: %s?", res)
  end
  #
  order = filter_control_order(ind)
  controls2channel([false, false, false, false, false, [10, [0, 0, 1, 1]]])
  func = edit_list2function
  if (res = func.source) != "Proc.new {|snd, chn|  controls2channel([false, false, false, false, false, [10, [0, 0, 1, 1]]], 0, false, snd, chn) }"
    snd_display("edit_list2function controls2channel 6: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  if (res = filter_control_order(ind)) != order
    snd_display("controls2channel filter: %s?", res)
  end
  #
  # mix stuff
  #
  id = make_v_mix(ind, 0)
  # mix-position
  set_mix_position(id, 200)
  if (res = mix_position(id)) != 200
    snd_display("edit_list2function mix off to a bad start: %s?", res)
  end
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function mix 1: %s?", func)
  end
  if (res = func.source) != format("Proc.new {|snd, chn| _mix_%s = false;  _mix_%s = mix_vct(vct(0.1, 0.2, 0.3), 100, snd, chn); set_mix_position(_mix_%s, 200) }", mix2integer(id), mix2integer(id), mix2integer(id))
    snd_display("edit_list2function mix 1: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  res1 = mixes(ind, 0)
  res2 = if res1.nil?
           false
         else
           res1.detect do |m| mix?(m) and mix_position(m) == 200 end
         end
  unless mix?(res2)
    snd_display("edit_list2function mix 1 repos: %s %s?", res1, res2)
  end
  revert_sound(ind)
  # mix-amp
  id = make_v_mix(ind, 0)
  set_mix_amp(id, 0.5)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function mix 4: %s?", func)
  end
  if (res = func.source) != format("Proc.new {|snd, chn| _mix_%s = false;  _mix_%s = mix_vct(vct(0.1, 0.2, 0.3), 100, snd, chn); set_mix_amp(_mix_%s, 0.5000) }", mix2integer(id), mix2integer(id), mix2integer(id))
    snd_display("edit_list2function mix 4: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  # mix-speed
  id = make_v_mix(ind, 0)
  set_mix_speed(id, 0.5)
  unless proc?(func = edit_list2function)
    snd_display("edit_list2function mix 5: %s?", func)
  end
  if (res = func.source) != format("Proc.new {|snd, chn| _mix_%s = false;  _mix_%s = mix_vct(vct(0.1, 0.2, 0.3), 100, snd, chn); set_mix_speed(_mix_%s, 0.5000) }", mix2integer(id), mix2integer(id), mix2integer(id))
    snd_display("edit_list2function mix 5: %s", res)
  end
  revert_sound(ind)
  func.call(ind, 0)
  revert_sound(ind)
  close_sound(ind)
  # 
  ind = open_sound("2.snd")
  src_sound(3.0, 1.0, ind)
  save_sound_as("test.snd")
  close_sound(ind)
  ind = open_sound("test.snd")
  if (res = chans(ind)) != 2
    snd_display("src_sound/save_sound_as: %s chans?", res)
  end
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  if (res = Snd.catch do save_sound_as("test.snd", :edit_position, 1) end).first != :no_such_edit
    snd_display("save_sound_as bad edpos: %s", res.inspect)
  end
  if (res = Snd.catch do
        save_sound_as("test.snd", :channel, 1, :edit_position, 1)
      end).first != :no_such_channel
    snd_display("save_sound_as bad chan: %s", res.inspect)
  end
  save_sound_as("test.snd", :comment, "this is a comment")
  close_sound(ind)
  ind = open_sound("test.snd")
  if (res = comment(ind)) != "this is a comment"
    snd_display("save_sound_as with comment: %s?", res)
  end
  close_sound(true)
end

def test_19
  if $with_test_gui # load(save_state_file) -> set_transform_size(0)
    test_19_00
    test_19_01
  end
  test_19_02
  test_19_03
  mus_sound_prune
end

# ---------------- test 20: transforms ----------------

def bes_test(func_sym)
  [0.0, 0.5, 1.0, 2.0, 20.0].each do |x|
    snd_test_neq(snd_func(func_sym, x), snd_func("#{func_sym.to_s}_1", x), "%s(%1.1f)", func_sym, x)
  end
  10.times do
    x = random(100.0)
    snd_test_neq(snd_func(func_sym, x), snd_func("#{func_sym.to_s}_1", x), "%s(%1.1f)", func_sym, x)
  end
end

def bes_j0_1(x)
  if x.abs < 8.0
    y = x * x
    ans1 = 57568490574.0 +
           y * (-13362590354.0 + \
                y * (651619640.7 + \
                     y * (-11214424.18 + \
                          y * (77392.33017 + \
                               y * -184.9052456))))
    ans2 = 57568490411.0 +
           y * (1029532985.0 + \
                y * (9494680.718 + \
                     y * (59272.64853 + \
                          y * (267.8532712 + y))))
    ans1 / ans2
  else
    ax = x.abs
    z = 8.0 / ax
    y = z * z
    xx = ax - 0.785398164
    ans1 = 1.0 +
           y * (-0.1098628627e-2 + \
                y * (0.2734510407e-4 + \
                     y * (-0.2073370639e-5 + \
                          y * 0.2093887211e-6)))
    ans2 = -0.1562499995e-1 +
           y * (0.1430488765e-3 + \
                y * (-0.6911147651e-5 + \
                     y * (0.7621095161e-6 + \
                          y * -0.934945152e-7)))
    sqrt(0.636619772 / ax) * (cos(xx) * ans1 - sin(xx) * z * ans2)
  end
end

def bes_j1_1(x)
  if x.abs < 8.0
    y = x * x
    ans1 = x *
           (72362614232.0 + \
            y * (-7895059235.0 + \
                 y * (242396853.1 + \
                      y * (-2972611.439 + \
                           y * (15704.48260 + \
                                y * -30.16036606)))))
    ans2 = 144725228442.0 +
           y * (2300535178.0 + \
                y * (18583304.74 + \
                     y * (99447.43394 + \
                          y * (376.9991397 + y))))
    ans1 / ans2
  else
    ax = x.abs
    z = 8.0 / ax
    y = z * z
    xx = ax - 2.356194491
    ans1 = 1.0 +
           y * (0.183105e-2 + \
                y * (-0.3516396496e-4 + \
                     y * (0.2457520174e-5 + \
                          y * -0.240337019e-6)))
    ans2 = 0.04687499995 +
           y * (-0.2002690873e-3 + \
                y * (0.8449199096e-5 + \
                     y * (-0.88228987e-6 + \
                          y * 0.105787412e-6)))
    sqrt(0.636619772 / ax) * (cos(xx) * ans1 - sin(xx) * z * ans2) * (x.zero? ? 0 : (x < 0 ? -1 : 1))
  end
end

def bes_jn_1(nn, x)
  n = nn.abs
  besn = if n.zero?
           bes_j0_1(x)
         elsif n == 1
           bes_j1_1(x)
         elsif x.zero?
           0.0
         else
           iacc = 40.0
           ans = 0.0
           bigno = 1.0e10
           bigni = 1.0e-10
           tox = 2.0 / x.abs
           if x.abs > n
             bjm = bes_j0_1(x.abs)
             bj = bes_j1_1(x.abs)
             bjp = 0.0
             (1...n).each do |i|
               bjp = i * tox * bj - bjm
               bjm, bj = bj, bjp
             end
             ans = bj
           else
             m = 2 * ((sqrt(iacc * n) + n) / 2.0).floor
             jsum = 0
             bjm = 0.0
             sum = 0.0
             bjp = 0.0
             bj = 1.0
             m.downto(1) do |i|
               bjm = i * tox * bj - bjp
               bjp, bj = bj, bjm
               if bj.abs > bigno
                 bj *= bigni
                 bjp *= bigni
                 ans *= bigni
                 sum *= bigni
               end
               if jsum.nonzero?
                 sum += bj
               end
               jsum = 1 - jsum
               if i == n
                 ans = bjp
               end
             end
             sum = 2.0 * sum - bj
             ans = ans / sum
           end
           if x < 0 and n.odd?
             -ans
           else
             ans
           end
         end
  if nn < 0 and nn.odd?
    -besn
  else
    besn
  end
end

def bes_test_jn
  10.times do |k|
    10.times do |i|
      x = random(100.0)
      snd_test_neq(bes_jn(k, x), bes_jn_1(k, x), "bes_jn(%k, %1.1f)", k, x)
    end
  end
end

def bes_y0_1(x)
  if x < 8.0
    y = x * x
    ans1 = -2957821389.0 +
           y * (7062834065.0 + \
                y * (-512359803.6 + \
                     y * (10879881.29 + \
                          y * (-86327.92757 + \
                               y * 228.4622733))))
    ans2 = 40076544269.0 +
           y * (745249964.8 + \
                y * (7189466.438 + \
                     y * (47447.26470 + \
                          y * (226.1030244 + y))))
    ans1 / ans2 + 0.636619772 * bes_j0(x) * log(x)
  else
    z = 8.0 / x
    y = z * z
    xx = x - 0.785398164
    ans1 = 1.0 +
           y * (-0.1098628627e-2 + \
                y * (0.2734510407e-4 + \
                     y * (-0.2073370639e-5 + \
                          y * 0.2093887211e-6)))
    ans2 = -0.1562499995e-1 +
           y * (0.1430488765e-3 + \
                y * (-0.6911147651e-5 + \
                     y * (0.7621095161e-6 + \
                          y * -0.934945152e-7)))
    sqrt(0.636619772 / x) * (sin(xx) * ans1 + cos(xx) * z * ans2)
  end
end

def bes_y1_1(x)
  if x < 8.0
    y = x * x
    ans1 = x *
           (-0.4900604943e13 + \
            y * (0.1275274390e13 + \
                 y * (-0.5153438139e11 + \
                      y * (0.7349264551e9 + \
                           y * (-0.4237922726e7 + \
                                y * 0.8511937935e4)))))
    ans2 = 0.2499580570e14 +
           y * (0.4244419664e12 + \
                y * (0.3733650367e10 + \
                     y * (0.2245904002e8 + \
                          y * (0.1020426050e6 + \
                               y * (0.3549632885e3 + y)))))
    ans1 / ans2 + 0.636619772 * (bes_j1(x) * log(x) - 1.0 / x)
  else
    z = 8.0 / x
    y = z * z
    xx = x - 2.356194491
    ans1 = 1.0 +
           y * (0.183105e-2 + \
                y * (-0.3516396496e-4 + \
                     y * (0.2457520174e-5 + \
                          y * -0.240337019e-6)))
    ans2 = 0.04687499995 +
           y * (-0.2002690873e-3 + \
                y * (0.8449199096e-5 + \
                     y * (-0.88228987e-6 + \
                          y * 0.105787412e-6)))
    sqrt(0.636619772 / x) * (sin(xx) * ans1 + cos(xx) * z * ans2)
  end
end

def bes_test_y(func_sym)
  [0.5, 1.0, 2.0, 20.0].each do |x|
    snd_test_neq(snd_func(func_sym, x), snd_func("#{func_sym.to_s}_1", x), "%s(%1.1f)", func_sym, x)
  end
  10.times do
    x = random(100.0)
    snd_test_neq(snd_func(func_sym, x), snd_func("#{func_sym.to_s}_1", x), "%s(%1.1f)", func_sym, x)
  end
end

def bes_yn_1(n, x)
  if n.zero?
    bes_y0_1(x)
  elsif n == 1
    bes_y1_1(x)
  else
    tox = 2.0 / x
    byp = 0.0
    by = bes_y1_1(x)
    bym = bes_y0_1(x)
    (1...n).each do |i|
      byp = i * tox * by - bym
      bym, by = by, byp
    end
    by
  end
end

def bes_test_yn
  10.times do |k|
    10.times do |i|
      x = random(100.0)
      snd_test_neq(bes_yn(k, x) / bes_yn_1(k, x), 1.0, "bes_yn(%d, %1.1f)", k, x)
    end
  end
end

def bes_i0_1(x)
  if x.abs < 3.75
    y = (x / 3.75) ** 2
    1.0 +
      y * (3.5156229 + \
           y * (3.0899424 + \
                y * (1.2067492 + \
                     y * (0.2659732 + \
                          y * (0.360768e-1 + \
                               y * 0.45813e-2)))))
  else
    ax = x.abs
    y = 3.75 / ax
    (exp(ax) / sqrt(ax)) *
              (0.39894228 + \
               y * (0.1328592e-1 + \
                    y * (0.225319e-2 + \
                         y * (-0.157565e-2 + \
                              y * (0.916281e-2 + \
                                   y * (-0.2057706e-1 + \
                                        y * (0.2635537e-1 + \
                                             y * (-0.1647633e-1 + \
                                                  y * 0.392377e-2))))))))
  end
end

def bes_test_i0
  [0.0, 0.5, 1.0, 2.0, 0.01].each do |x|
    snd_test_neq(bes_i0(x), bes_i0_1(x), "bes_i0(%1.1f)", x)
  end
  10.times do
    x = random(1.0)
    snd_test_neq(bes_i0(x), bes_i0_1(x), "bes_i0(%1.1f)", x)
  end
end

def bes_i1(x)
  if x.abs < 3.75
    y = (x / 3.75) ** 2
    x * (0.5 + \
         y * (0.87890594 + \
              y * (0.51498869 + \
                   y * (0.15084934 + \
                        y * (0.2658733e-1 + \
                             y * (0.301532e-2 + \
                                  y * 0.32411e-3))))))
  else
    ax = x.abs
    y = 3.75 / ax
    ans1 = 0.2282967e-1 +
           y * (-0.2895312e-1 + \
                y * (0.1787654e-1 + \
                     y * -0.420059e-2))
    ans2 = 0.39894228 +
           y * (-0.3988024e-1 + \
                y * (-0.362018e-2 + \
                     y * (0.163801e-2 + \
                          y * (-0.1031555e-1 + y * ans1))))
    exp(ax) / sqrt(ax) * ans2 * (x < 0 ? -1 : 1)
  end
end

def bes_test_i1
  [[ 1.0,    0.565159],
   [ 2.0,    1.59063685],
   [ 5.0,   24.33564],
   [10.0, 2670.9883]].each do |n, x|
    snd_test_neq(bes_i1(n), x, "bes_i1 %1.1f", n)
  end
end

def bes_in(n, x)
  if n.zero?
    bes_i0(x)
  elsif n == 1
    bes_i1(x)
  elsif x.zero?
    0.0
  else
    iacc = 40.0
    ans = 0.0
    bigno = 1.0e10
    bigni = 1.0e-10
    tox = 2.0 / x.abs
    bip = 0.0
    bi = 1.0
    bim = 0.0
    m = 2 * (n + sqrt(iacc * n).floor)
    m.downto(1) do |i|
      bim = bip + i * tox * bi
      bip, bi = bi, bim
      if bi.abs > bigno
        ans *= bigni
        bi *= bigni
        bip *= bigni
      end
      if i == n
        ans = bip
      end
    end
    if x < 0 and n.odd?
      ans = -ans
    end
    ans * (bes_i0(x) / bi)
  end
end

def bes_test_in
  [[ 1, 0.565159,     1.5906368,  24.33564],
   [ 2, 0.13574767,   0.6889484,  17.505615],
   [ 3, 0.02216842,   0.21273995, 10.331150],
   [ 5, 2.71463e-4,   0.009825679, 2.157974],
   [10, 2.752948e-10, 3.016963e-7, 0.004580044]].each do |n, x1, x2, x5|
    snd_test_neq(bes_in(n, 1.0), x1, "bes_in %d 1.0", n)
    snd_test_neq(bes_in(n, 2.0), x2, "bes_in %d 2.0", n)
    snd_test_neq(bes_in(n, 5.0), x5, "bes_in %d 5.0", n)
  end
end

def bes_k0(x)
  if x <= 2.0
    y = x * (x / 4.0)
    -log(x / 2.0) * bes_i0(x) + -0.57721566 +
                   y * (0.42278420 + \
                        y * (0.23069756 + \
                             y * (0.3488590e-1 + \
                                  y * (0.262698e-2 + \
                                       y * (0.10750e-3 + \
                                            y * 0.74e-5)))))
  else
    y = 2.0 / x
    (exp(-x) / sqrt(x)) * \
    (1.25331414 + \
     y * (-0.7832358e-1 + \
          y * (0.2189568e-1 + \
               y * (-0.1062446e-1 + \
                    y * (0.587872e-2 + \
                         y * (-0.251540e-2 + \
                              y * -0.53208e-3))))))
  end
end

def bes_test_k0
  [[ 1.0, 0.4210244],
   [ 2.0, 0.1138938],
   [10.0, 1.7780e-5]].each do |n, x|
    snd_test_neq(bes_k0(n), x, "bes_k0 %1.1f", n)
  end
end

def bes_k1(x)
  if x <= 2.0
    y = x * (x / 4.0)
    log(x / 2.0) * bes_i1(x) + 1.0 / x *
                  (1.0 + \
                   y * (0.15443144 + \
                        y * (-0.67278579 + \
                             y * (-0.18156897 + \
                                  y * (-0.1919402e-1 + \
                                       y * (-0.110404e-2 + \
                                            y * -0.4686e-4))))))
  else
    y = 2.0 / x
    (exp(-x) / sqrt(x)) * \
    (1.25331414 + \
     y * (0.23498619 + \
          y * (-0.3655620e-1 + \
               y * (0.1504268e-1 + \
                    y * (0.780353e-2 + \
                         y * (0.325614e-2 + \
                              y * -0.68245e-3))))))
  end
end

def bes_test_k1
  [[ 1.0, 0.60190723],
   [ 2.0, 0.1398658],
   [10.0, 1.86487e-5]].each do |n, x|
    snd_test_neq(bes_k1(n), x, "bes_k1 %1.1f", n)
  end
end

def bes_kn(n, x)
  if n.zero?
    bes_k0(x)
  elsif n == 1
    bes_k1(x)
  else
    tox = 2.0 / x
    bkp = 0.0
    bk = bes_k1(x)
    bkm = bes_k0(x)
    (1...n).each do |i|
      bkp = i * tox * bk + bkm
      bkm, bk = bk, bkp
    end
    bk
  end
end

def bes_test_kn
  [[1,   0.6019072, 0.139865,  0.00404461],
   [2,   1.6248389, 0.2537597, 0.0053089],
   [3,   7.1012629, 0.6473854, 0.0082917],
   [5, 360.96059,   9.431049,  0.0327062]].each do |n, x1, x2, x5|
    snd_test_neq(bes_kn(n, 1.0), x1, "bes_kn %d 1.0", n)
    snd_test_neq(bes_kn(n, 2.0), x2, "bes_kn %d 2.0", n)
    snd_test_neq(bes_kn(n, 5.0), x5, "bes_kn %d 5.0", n)
  end
end

def gammln(x)
  stp = 2.5066282746310005
  tmp = x + 5.5
  tmp1 = tmp - ((x + 0.5) * log(tmp))
  ser = 1.000000000190015 +
        76.18009172947146 / (x + 1.0) +
           -86.50532032941677 / (x + 2.0) +
               24.01409824083091 / (x + 3.0) +
                  -1.231739572450155 / (x + 4.0) +
                     0.1208650973866179e-2 / (x + 5.0) +
                       -0.5395239384953e-5 / (x + 6.0)
  log((stp * ser) / x) - tmp1
end

def test_lgamma
  10.times do
    x = random(100.0)
    res1 = lgamma(x)
    res2 = gammln(x)
    if array?(res1)
      # FIXME (INFO)
      # Ruby 1.9 returns an array
      res1 = res1[0]
    end
    snd_test_neq(res1, res2, "lgamma(%1.1f)", x)
  end
end

def test_erf
  [[0.0, 0.0],
   [0.5, 0.5204998],
   [1.0, 0.8427007]].each do |n, x|
    snd_test_neq(erf(n), x, "erf(%1.1f)", n)
  end
  10.times do
    val = random(2.0)
    res1 = erf(val)
    res2 = erfc(val)
    snd_test_neq(res1 + res2, 1.0, "erf+erfc erf %1.4f erfc %1.4f)", res1, res2)
  end
end

def inverse_haar(f)
  n = f.length
  g = Vct.new(n)
  s2 = sqrt(2.0)
  v = 1.0 / sqrt(n)
  f[0] *= v
  m = 2
  until m > n
    mh = m / 2
    k = 0
    0.step(m - 1, 2) do |j|
      x = f[k]
      y = f[mh + k] * v
      g[j] = x + y
      g[j + 1] = x - y
      k += 1
    end
    (m - 1).downto(0) do |i| f[i] = g[i] end
    v *= s2
    m *= 2
  end
  f
end

def wavelet(data, n, isign, wf, cc)
  cc_size = cc.length
  sig = -1.0
  j = cc_size - 1
  ccr = Vct.new(cc_size)
  cc.each do |val|
    ccr[j] = val * sig
    j -= 1
    sig = -sig
  end
  if n >= 4
    if isign >= 0
      nn = n
      until nn < 4
        snd_func(wf, data, nn, isign, cc, ccr)
        nn /= 2
      end
    else
      nn = 4
      until nn > n
        snd_func(wf, data, nn, isign, cc, ccr)
        nn *= 2
      end
    end
  end
end

def pwt(data, n, isign, cc, cr)
  data1 = Vct.new(n)
  n1 = n - 1
  ncof = cc.length
  nmod = ncof * n
  nh = (n / 2.0).floor
  ioff = joff = -(ncof / 2.0).floor
  ii = 0
  if isign >= 0
    1.step(n, 2) do |i|
      ni = i + nmod + ioff
      nj = i + nmod + joff
      1.upto(ncof) do |k|
        jf = n1 & (ni + k)
        jr = n1 & (nj + k)
        data1[ii] = data1[ii] + cc[k - 1] * data[jf]
        data1[ii + nh] = data1[ii + nh] + cr[k - 1] * data[jr]
      end
      ii += 1
    end
  else
    1.step(n, 2) do |i|
      ai = data[ii]
      ai1 = data[ii + nh]
      ni = i + nmod + ioff
      nj = i + nmod + joff
      1.upto(ncof) do |k|
        jf = n1 & (ni + k)
        jr = n1 & (nj + k)
        data1[jf] = data1[jf] + cc[k - 1] * ai
        data1[jr] = data1[jr] + cr[k - 1] * ai1
      end
      ii += 1
    end
  end
  data1.map_with_index do |val, i|
    data[i] = val
  end
end

def corr(x, y, n, m)
  r = Vct.new(m + 1)
  (m + 1).times do |i|
    r[i] = 0.0
    (n - i).times do |j|
      r[i] = r[i] + x[j + i] * y[j]
    end
  end
  r
end

def cross_correlate_3(rl1, rl2, fftlen)
  fftscale = 1.0 / fftlen
  im1 = Vct.new(fftlen)
  im2 = Vct.new(fftlen)
  fft(rl1, im1, 1)
  fft(rl2, im2, 1)
  tmprl = rl1.dup
  tmpim = im1.dup
  tmprl *= rl2
  tmpim *= im2
  im2 *= rl1
  rl2 *= im1
  tmprl += tmpim
  im2 -= rl2
  fft(tmprl, im2, -1).scale(fftscale)
end

def automorph(a, b, c, d, snd = false, chn = false)
  len = frames(snd, chn)
  pow2 = (log(len) / log(2)).ceil.to_i
  fftlen = (2 ** pow2).round
  fftscale = 1.0 / fftlen
  rl = channel2vct(0, fftlen, snd, chn)
  im = Vct.new(fftlen)
  fft(rl, im, 1)
  rl.scale!(fftscale)
  im.scale!(fftscale)
  c1 = Complex(rl[0], im[0])
  val = (a * c1 + b) / (c * c1 + d)
  rval = val.real
  ival = val.imag
  rl[0] = rval
  im[0] = ival
  k = fftlen - 1
  (1..(fftlen / 2)).each do |i|
    c1 = Complex(rl[i], im[i])
    val = (a * c1 + b) / (c * c1 + d)
    rval = val.real
    ival = val.imag
    rl[i] = rval
    im[i] = ival
    rl[k] = rval
    im[k] = -ival
    k -= 1
  end
  fft(rl, im, -1)
  vct2channel(rl, 0, len, snd, chn, false, format("%s(%s, %s, %s, %s", get_func_name, a, b, c, d))
end

sqrt2 = 1.41421356237309504880168872420969808
sqrt15 = 3.87298334620741688517927
$wts = {[:daub4, 0] =>
        vct(0.4829629131445341, 0.8365163037378079, 0.2241438680420134, -0.1294095225512604),
        [:daub6, 1] =>
        vct(0.332670552950, 0.806891509311, 0.459877502118, -0.135011020010,
            -0.085441273882, 0.035226291886),
        [:daub8, 2] =>
        vct(0.230377813309, 0.714846570553, 0.630880767930, -0.027983769417,
            -0.187034811719, 0.030841381836, 0.032883011667, -0.010597401785),
        [:daub10, 3] =>
        vct(0.160102397974, 0.603829269797, 0.724308528438, 0.138428145901,
            -0.242294887066, -0.032244869585, 0.077571493840, -0.006241490213,
            -0.012580751999, 0.003335725285),
        [:daub12, 4] =>
        vct(0.111540743350, 0.494623890398, 0.751133908021, 0.315250351709,
            -0.226264693965, -0.129766867567, 0.097501605587, 0.027522865530,
            -0.031582039317, 0.000553842201, 0.004777257511, -0.001077301085),
        [:daub14, 5] =>
        vct(0.077852054085, 0.396539319482, 0.729132090846, 0.469782287405,
            -0.143906003929, -0.224036184994, 0.071309219267, 0.080612609151,
            -0.038029936935, -0.016574541631, 0.012550998556, 0.000429577973,
            -0.001801640704, 0.000353713800),
        [:daub16, 6] =>
        vct(0.054415842243, 0.312871590914, 0.675630736297, 0.585354683654,
            -0.015829105256, -0.284015542962, 0.000472484574, 0.128747426620,
            -0.017369301002, -0.044088253931, 0.013981027917, 0.008746094047,
            -0.004870352993, -0.000391740373, 0.000675449406, -0.000117476784),
        [:daub18, 7] =>
        vct(0.038077947364, 0.243834674613, 0.604823123690, 0.657288078051,
            0.133197385825, -0.293273783279, -0.096840783223, 0.148540749338,
            0.030725681479, -0.067632829061, 0.000250947115, 0.022361662124,
            -0.004723204758, -0.004281503682, 0.001847646883, 0.000230385764,
            -0.000251963189, 0.000039347320),
        [:daub20, 8] =>
        vct(0.026670057901, 0.188176800077, 0.527201188931, 0.688459039453,
            0.281172343661, -0.249846424327, -0.195946274377, 0.127369340336,
            0.093057364604, -0.071394147166, -0.029457536822, 0.033212674059,
            0.003606553567, -0.010733175483, 0.001395351747, 0.001992405295,
            -0.000685856695, -0.000116466855, 0.000093588670, -0.000013264203),
        [:battle_lemarie, 9] =>
        vct(sqrt2 * -0.002, sqrt2 * -0.003, sqrt2 *  0.006, sqrt2 *  0.006,
            sqrt2 * -0.013, sqrt2 * -0.012, sqrt2 *  0.030, sqrt2 *  0.023,
            sqrt2 * -0.078, sqrt2 * -0.035, sqrt2 *  0.307, sqrt2 *  0.542,
            sqrt2 *  0.307, sqrt2 * -0.035, sqrt2 * -0.078, sqrt2 *  0.023,
            sqrt2 *  0.030, sqrt2 * -0.012, sqrt2 * -0.013, sqrt2 *  0.006,
            sqrt2 *  0.006, sqrt2 * -0.003, sqrt2 * -0.002, 0.0),
        [:burt_adelson, 10] =>
        vct(sqrt2 * (-1.0 / 20.0), sqrt2 * (5.0 / 20.0), sqrt2 * (12.0 / 20.0),
            sqrt2 * (5.0 / 20.0), sqrt2 * (-1.0 / 20.0), 0.0),
        [:beylkin, 11] =>
        vct(0.099305765374353, 0.424215360812961, 0.699825214056600, 0.449718251149468,
            -0.110927598348234, -0.264497231446384, 0.026900308803690, 0.155538731877093,
            -0.017520746266529, -0.088543630622924, 0.019679866044322, 0.042916387274192,
            -0.017460408696028, -0.014365807968852, 0.010040411844631, 0.0014842347824723,
            -0.002736031626258, 0.0006404853285212),
        [:coif2, 12] =>
        vct((sqrt2 * (sqrt15 - 3)) / 32.0, (sqrt2 * (1 - sqrt15)) / 32.0,
            (sqrt2 * (6 - 2 * sqrt15)) / 32.0, (sqrt2 * (2 * sqrt15 + 6)) / 32.0,
            (sqrt2 * (sqrt15 + 13)) / 32.0, (sqrt2 * (9 - sqrt15)) / 32.0),
        [:coif4, 13] =>
        vct(0.0011945726958388, -0.01284557955324, 0.024804330519353, 0.050023519962135,
            -0.15535722285996, -0.071638282295294, 0.57046500145033, 0.75033630585287,
            0.28061165190244, -0.0074103835186718, -0.014611552521451, -0.0013587990591632),
        [:coif6, 14] =>
        vct(-0.0016918510194918, -0.00348787621998426, 0.019191160680044, 0.021671094636352,
            -0.098507213321468, -0.056997424478478, 0.45678712217269, 0.78931940900416,
            0.38055713085151, -0.070438748794943, -0.056514193868065, 0.036409962612716,
            0.0087601307091635, -0.011194759273835, -0.0019213354141368, 0.0020413809772660,
            0.00044583039753204, -0.00021625727664696),
        [:sym2, 15] =>
        vct(sqrt2 * -0.125, sqrt2 *  0.25, sqrt2 *  0.75, sqrt2 *  0.25, sqrt2 * -0.125),
        [:sym3, 16] =>
        vct(sqrt2 / 8.0, (sqrt2 * 3.0) / 8.0, (sqrt2 * 3.0) / 8.0, sqrt2 / 8.0),
        [:sym4, 17] =>
        vct((sqrt2 *   3.0) / 128.0, (sqrt2 * -6.0) / 128.0, (sqrt2 * -16.0) / 128.0,
            (sqrt2 *  38.0) / 128.0, (sqrt2 * 90.0) / 128.0, (sqrt2 *  38.0) / 128.0,
            (sqrt2 * -16.0) / 128.0, (sqrt2 * -6.0) / 128.0, (sqrt2 *  3.0) / 128.0, 0.0),
        [:sym5, 18] =>
        vct((sqrt2 *  3.0) / 64.0, (sqrt2 * -9.0) / 64.0, (sqrt2 * -7.0) / 64.0,
            (sqrt2 * 45.0) / 64.0, (sqrt2 * 45.0) / 64.0, (sqrt2 * -7.0) / 64.0,
            (sqrt2 * -9.0) / 64.0, (sqrt2 *  3.0) / 64.0),
        [:sym6, 19] =>
        vct((sqrt2 *   -35.0) / 16384.0, (sqrt2 *  -105.0) / 16384.0,
            (sqrt2 *  -195.0) / 16384.0, (sqrt2 *   865.0) / 16384.0,
            (sqrt2 *   363.0) / 16384.0, (sqrt2 * -3489.0) / 16384.0,
            (sqrt2 *  -307.0) / 16384.0, (sqrt2 * 11025.0) / 16384.0,
            (sqrt2 * 11025.0) / 16384.0, (sqrt2 *  -307.0) / 16384.0,
            (sqrt2 * -3489.0) / 16384.0, (sqrt2 *   363.0) / 16384.0,
            (sqrt2 *   865.0) / 16384.0, (sqrt2 *  -195.0) / 16384.0,
            (sqrt2 *  -105.0) / 16384.0, (sqrt2 *   -35.0) / 16384.0)}

def test_20_00
  # check small transform cases
  index = open_sound("oboe.snd")
  set_transform_graph?(true)
  [$fourier_transform, $wavelet_transform, $autocorrelation,
   $walsh_transform, $cepstrum, $haar_transform].each do |transform|
    set_transform_type(transform)
    [8, 7, -7, 4, 3, 2, 1, 0].each do |size|
      Snd.catch do 
        set_transform_size(size)
        update_transform_graph
      end
    end
  end
  close_sound(index)
  # 
  # fft
  # 
  d0 = Vct.new(16)
  d0[0] = 1.0
  snd_transform($fourier_transform, d0, 0)
  d0.each_with_index do |val, i|
    if fneq(val, 1.0)
      snd_display("fourier (1.0) [%s]: %s?", i, val)
    end
  end
  d0 = Vct.new(16)
  d0[0] = 1.0
  snd_transform($fourier_transform, d0, 0)
  d0.each_with_index do |val, i|
    if fneq(val, 1.0)
      snd_display("fourier (1.0) [%s]: %s?", i, val)
    end
  end
  snd_transform($fourier_transform, d0, 0)
  d0.each_with_index do |val, i|
    if i.zero?
      if fneq(val, 256.0) and fneq(val, 361.0)
        snd_display("fourier (256.0) [%s]: %s?", i, val)
      end
    else
      if fneq(val, 0.0)
        snd_display("fourier (0.0) [%s]: %s?", i, val)
      end
    end
  end
  #
  r0 = Vct.new(8)
  i0 = Vct.new(8)
  r1 = Vct.new(8)
  i1 = Vct.new(8)
  r2 = Vct.new(8)
  i2 = Vct.new(8)
  r0[1] = 0.5
  r1[3] = 0.75
  r2[1] = 0.25
  r2[3] = 0.25
  mus_fft(r0, i0)
  mus_fft(r1, i1)
  mus_fft(r2, i2)
  r0.scale!(0.5)
  i0.scale!(0.5)
  r1.scale!(0.3333)
  i1.scale!(0.3333)
  r0.add!(r1)
  i0.add!(i1)
  if (not vequal(r0, r2)) or (not vequal(i0, i2))
    snd_display("fft additions/scaling: %s %s: %s %s?", r2, i2, r0, i0)
  end
  #
  d0 = Vct.new(8)
  d1 = Vct.new(8)
  d0[2] = 1.0
  mus_fft(d0, d1, 8, 1)
  if (not vequal(d0, vct(1.000, 0.000, -1.000, -0.000, 1.000, 0.000, -1.000, -0.000))) or
      (not vequal(d1, vct(0.000, 1.000, 0.000, -1.000, 0.000, 1.000, 0.000, -1.000)))
    snd_display("mus_fft 1: %s %s?", d0, d1)
  end
  mus_fft(d0, d1, 8, -1)
  if (not vequal(d0, vct(0.000, 0.000, 8.000, 0.000, 0.000, 0.000, 0.000, 0.000))) or
      (not vequal(d1, vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000)))
    snd_display("mus_fft -1: %s %s?", d0, d1)
  end
  d0.fill(1.0)
  d1.fill(0.0)
  mus_fft(d0, d1, 8)
  if (not vequal(d0, vct(8.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000))) or
      (not vequal(d1, vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000)))
    snd_display("mus_fft 2: %s %s?", d0, d1)
  end
  mus_fft(d0, d1, 8, -1)
  if (not vequal(d0, vct(8.000, 8.000, 8.000, 8.000, 8.000, 8.000, 8.000, 8.000))) or
      (not vequal(d1, vct(0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000)))
    snd_display("mus_fft -2: %s %s?", d0, d1)
  end
  d1.fill 0.0
  d0.map! do |val| random(1.0) end
  fn = d0.dup
  mus_fft(d0, d1, 8)
  mus_fft(d0, d1, 8, -1)
  d0.scale! 1.0 / 8.0
  unless vequal(d0, fn)
    snd_display("mus_fft 3: %s %s?", d0, fn)
  end
  d0 = Vct.new(8) do 1.0 - random(2.0) end
  d1 = Vct.new(8) do 1.0 - random(2.0) end
  save_d0 = d0.dup
  save_d1 = d1.dup
  reversed_d0 = Vct.new(8) do |i| d0[7 - i] end
  reversed_d1 = Vct.new(8) do |i| d1[7 - i] end
  mus_fft(d0, d1, 8)
  mus_fft(d0, d1, 8)
  d0.scale!(0.125)
  d1.scale!(0.125)
  7.times do |i|
    if fneq(d0[i + 1], reversed_d0[i])
      snd_display("mus_fft d0 reversed: %s %s?", d0, reversed_d0)
    end
    if fneq(d1[i + 1], reversed_d1[i])
      snd_display("mus_fft d1 reversed: %s %s?", d1, reversed_d1)
    end
  end
  mus_fft(d0, d1, 8)
  mus_fft(d0, d1, 8)
  d0.scale!(0.125)
  d1.scale!(0.125)
  unless vequal(d0, save_d0)
    snd_display("mus_fft d0 saved: %s %s?", d0, saved_d0)
  end
  unless vequal(d1, save_d1)
    snd_display("mus_fft d1 saved: %s %s?", d1, saved_d1)
  end
  catch(:done) do
    [8, 16].each do |size|
      d0 = Vct.new(size)
      d0[0] = 1.0
      dcopy = d0.dup
      d1 = snd_spectrum(d0, Rectangular_window, size)
      unless vequal(d0, dcopy)
        snd_display("snd_specrum not in-place?: %s %s?", d0, dcopy)
      end
      (size / 2).times do |i|
        if fneq(d1[i], 1.0)
          snd_display("snd_spectrum (1.0) [%s: %s]: %s?", i, size, d1[i])
        end
      end
      d0 = Vct.new(size, 1.0)
      d1 = snd_spectrum(d0, Rectangular_window)
      if fneq(d1[0], 1.0)
        snd_display("snd_spectrum back (1.0 %s): %s?", size, d1[0])
      end
      (1...(size / 2)).each do |i|
        if fneq(d1[i], 0.0)
          snd_display("snd_spectrum (0.0) [%s: %s]: %s?", i, size, d1[i])
          throw(:done)
        end
      end
      d0 = Vct.new(size)
      d0[0] = 1.0
      d1 = snd_spectrum(d0, Rectangular_window, size, false) # dB (0.0 = max)
      (size / 2).times do |i|
        if fneq(d1[i], 0.0)
          snd_display("snd_spectrum dB (0.0) [%s: %s]: %s?", i, size, d1[i])
          throw(:done)
        end
      end
      d0 = Vct.new(size, 1.0)
      d1 = snd_spectrum(d0, Rectangular_window, size, false)
      if fneq(d1[0], 0.0)
        snd_display("snd_spectrum dB back (0.0 %s): %s?", size, d1[0])
      end
      (1...(size / 2)).each do |i|
        if fneq(d1[i], -90.0)     # currently ignores min-dB (snd-sig.c 5023)
          snd_display("snd_spectrum dB (1.0) [%s: %s]): %s?", i, size, d1[i])
          throw(:done)
        end
      end
      d0 = Vct.new(size)
      d0[0] = 1.0
      dcopy = d0.dup
      d1 = snd_spectrum(d0, Rectangular_window, size, true, 1.0, true) # in-place
      if vequal(d0, dcopy)
        snd_display("snd_spectrum in-place: %s %s?", d0, dcopy)
      end
      unless vequal(d0, d1)
        snd_display("snd_spectrum returns in-place: %s %s?", d0, d1)
      end
      (size / 2).times do |i|
        if fneq(d1[i], 1.0)
          snd_display("snd_spectrum dB (1.0 true) [%s: %s]: %s?", i, size, d1[i])
          throw(:done)
        end
      end
      d0 = Vct.new(size)
      d0[0] = 1.0
      dcopy = d0.dup
      d1 = snd_spectrum(d0, Rectangular_window, size, false, 1.0, true) # in-place dB
      if vequal(d0, dcopy)
        snd_display("snd_spectrum dB in-place: %s %s?", d0, dcopy)
      end
      unless vequal(d0, d1)
        snd_display("snd_spectrum returns dB in-place: %s %s?", d0, d1)
      end
      (size / 2).times do |i|
        if fneq(d1[i], 0.0)
          snd_display("snd_spectrum dB (1.0 true) [%s: %s]: %s?", i, size, d1[i])
          throw(:done)
        end
      end
      d0 = Vct.new(size, 1.0)
      d1 = snd_spectrum(d0, Rectangular_window, size, true, 0.0, false, false) # linear (in-place)
      if fneq(d1[0], size.to_f)
        snd_display("snd_spectrum no more 0: %s?", d1)
      end
      (1...(size / 2)).each do |i|
        if fneq(d1[i], 0.0)
          snd_display("snd_spectrum no more (0.0) [%s: %s]: %s?", i, size, d1[i])
          throw(:done)
        end
      end
      d0 = Vct.new(size, 1.0)
      d1 = snd_spectrum(d0, Blackman2_window, size)
      if (not vequal(d1, vct(1.000, 0.721, 0.293, 0.091))) and
          (not vequal(d1, vct(1.000, 0.647, 0.173, 0.037, 0.024, 0.016, 0.011, 0.005)))
        snd_display("blackman2 snd_spectrum: %s?", d1)
      end
      d0 = Vct.new(size, 1.0)
      d1 = snd_spectrum(d0, Gaussian_window, size, true, 0.5)
      if (not vequal(d1, vct(1.000, 0.900, 0.646, 0.328))) and
          (not vequal(d1, vct(1.000, 0.870, 0.585, 0.329, 0.177, 0.101, 0.059, 0.028)))
        snd_display("gaussian 0.5  snd_spectrum: %s?", d1)
      end
      d0 = Vct.new(size, 1.0)
      d1 = snd_spectrum(d0, Gaussian_window, size, true, 0.85)
      if (not vequal(d1, vct(1.000, 0.924, 0.707, 0.383))) and
          (not vequal(d1, vct(1.000, 0.964, 0.865, 0.725, 0.566, 0.409, 0.263, 0.128)))
        snd_display("gaussian 0.85 snd_spectrum: %s?", d1)
      end
    end
  end
  #
  catch(:done) do
    [16, 128, 512, 1024].each do |len|
      rl = Vct.new(len, 1.0)
      xrl = Vct.new(len, 1.0)
      len2 = len / 2
      snd_transform($fourier_transform, rl)
      snd_transform($fourier_transform, xrl, true)
      len2.times do |i|
        if fneq(rl[i], xrl[i])
          snd_display("flat fft: %s at %s: %s %s?", len, i, rl[i], xrl[i])
          throw(:done)
        end
      end
      if fneq(rl[0], len * len.to_f)
        snd_display("%s at 0: %s?", len, rl[0])
      end
      rl[0] = 0.0
      if rl.peak > 0.001
        snd_display("%s impulse: %s?", len, rl.peak)
      end
    end
  end
  catch(:done) do
    [16, 128, 512, 1024].each do |len|
      rl = Vct.new(len)
      xrl = Vct.new(len)
      len2 = len / 2
      rl[len2] = 1.0
      xrl[len2] = 1.0
      snd_transform($fourier_transform, rl)
      snd_transform($fourier_transform, xrl, true)
      len2.times do |i|
        if fneq(rl[i], xrl[i])
          snd_display("impulse fft: %s at %s: %s %s?", len, i, rl[i], xrl[i])
          throw(:done)
        end
      end
      if fneq(rl[0], 1.0)
        snd_display("flat %s at 0: %s?", len, rl[0])
      end
    end  
  end  
  catch(:done) do
    [16, 128, 512, 1024, 4096].each do |len|
      rl = Vct.new(len)
      xrl = Vct.new(len) do |i| rl[i] = random(1.0) end
      len2 = len / 2
      rl[len2] = 1.0
      xrl[len2] = 1.0
      snd_transform($fourier_transform, rl)
      rl.scale!(1.0 / len)
      snd_transform($fourier_transform, xrl, true)
      xrl.scale!(1.0 / len)
      len2.times do |i|
        if fneq(rl[i], xrl[i])
          snd_display("random fft: %s at %s: %s %s?", len, i, rl[i], xrl[i])
          throw(:done)
        end
      end
    end  
  end  
  catch(:done) do
    [16, 128, 512, 1024, 4096].each do |len|
      rl = Vct.new(len)
      xrl = Vct.new(len) do |i| rl[i] = sin((TWO_PI * 10.0 * i) / len) end
      len2 = len / 2
      rl[len2] = 1.0
      xrl[len2] = 1.0
      snd_transform($fourier_transform, rl)
      rl.scale!(1.0 / len)
      snd_transform($fourier_transform, xrl, true)
      xrl.scale!(1.0 / len)
      len2.times do |i|
        if fneq(rl[i], xrl[i])
          snd_display("sin fft: %s at %s: %s %s?", len, i, rl[i], xrl[i])
          throw(:done)
        end
      end
    end
  end
  # 
  # autocorrelation
  #
  rl = Vct.new(16)
  rl[0] = 1.0
  autocorrelate(rl)
  unless vequal(rl, vct(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
    snd_display("autocorrelate 1: %s?", rl)
  end
  rl = Vct.new(16)
  rl[0] = 1.0
  rl[1] = -1.0
  autocorrelate(rl)
  unless vequal(rl, vct(2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
    snd_display("autocorrelate 1 -1: %s?", rl)
  end
  rl = Vct.new(16)
  rl[0] = 1.0
  rl[4] = -1.0
  autocorrelate(rl)
  unless vequal(rl, vct(2, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
    snd_display("autocorrelate 1 0 0 0 -1: %s?", rl)
  end
  rl = Vct.new(16)
  rl1 = Vct.new(16)
  8.times do |i| rl[i] = rl1[i] = 8.0 - 1 end
  nr = vct_subseq(corr(rl, rl, 16, 16), 0, 15)
  autocorrelate(rl1)
  unless vequal(rl1, nr)
    snd_display("autocorrelate/corr (ramp): %s?", rl1, nr)
  end
  rl = Vct.new(16)
  rl1 = Vct.new(16)
  8.times do |i| rl[i] = rl1[i] = 1.0 - random(2.0) end
  nr = vct_subseq(corr(rl, rl, 16, 16), 0, 15)
  autocorrelate(rl1)
  unless vequal(rl1, nr)
    snd_display("autocorrelate/corr: %s?", rl1, nr)
  end
  #
  catch(:done) do
    [16, 64, 256, 512].each do |len|
      rl = Vct.new(len)
      rla = Vct.new(len)
      xim = Vct.new(len)
      xrl = Vct.new(len)
      len2 = len / 2
      rl[0] = 1.0
      rl[4] = 1.0
      snd_transform($autocorrelation, rl, 0)
      if fneq(rl[0], 2.0)
        snd_display("autocorrelation %s 0: %s?", len, rl[0])
      end
      if fneq(rl[4], 1.0)
        snd_display("autocorrelation %s 4: %s?", len, rl[0])
      end
      rla[0] = 1.0
      rla[4] = 1.0
      autocorrelate(rla)
      if fneq(rla[0], 2.0)
        snd_display("autocorrelate %s 0: %s?", len, rla[0])
      end
      if fneq(rla[4], 1.0)
        snd_display("autocorrelate %s 4: %s?", len, rla[0])
      end
      xrl[0] = 1.0
      xrl[4] = 1.0
      mus_fft(xrl, xim, len, 1)
      xrl.map_with_index! do |val, i| val * val + xim[i] * xim[i] end
      xim.scale! 0.0
      mus_fft(xrl, xim, len, -1)
      xrl.scale! 1.0 / len
      len2.times do |i|
        if fneq(rl[i], xrl[i])
          snd_display("%s at %s: %s %s?", len, i, rl[i], xrl[i])
          throw(:done)
        end
      end
      rl[0] = 0.0
      rl[4] = 0.0
      (len / 2).upto(len - 1) do |i| rl[i] = 0.0 end
      if rl.peak > 0.001
        snd_display("autocorrelate peak: %s?", rl.peak)
      end
    end
  end
  catch(:done) do
    [16, 64, 256, 512].each do |len|
      rl = Vct.new(len)
      xim = Vct.new(len)
      xrl = Vct.new(len)
      len2 = len / 2
      ones = [2, random(len2)].max
      ones.times do
        ind = random(len)
        rl[ind] = xrl[ind] = random(1.0)
      end
      snd_transform($autocorrelation, rl, 0)
      mus_fft(xrl, xim, len, 1)
      xrl[0] *= xrl[0]
      xrl[len2] *= xrl[len2]
      j = len - 1
      (1...len2).each do |i|
        xrl[j] = xrl[i] = xrl[i] * xrl[i] + xim[j] * xim[j]
        j -= 1
      end
      xim.scale! 0.0
      mus_fft(xrl, xim, len, -1)
      xrl.scale! 1.0 / len
      len2.times do |i|
        if fneq(rl[i], xrl[i])
          snd_display("random %s at %s: %s %s?", len, i, rl[i], xrl[i])
          throw(:done)
        end
      end
    end
  end
  #
  # cepstrum
  #
  rl = vct(0.423618, 0.259318, -0.048365, 1.140571, -0.811856, -0.994098, -0.998613, -2.453642,
           -0.438549, -1.520463, -0.312065, -0.724707, 1.154010, 1.466936, 0.110463, -1.520854)
  nrl = snd_transform($cepstrum, rl, 0).scale(1.399)
  unless vequal(nrl, vct(1.3994950, 0.1416877, 0.0952407, 0.0052814, -0.0613192, 0.0082986,
                         -0.0233993, -0.0476585, 0.0259498, -0.0476585, -0.0233993, 0.0082986,
                         -0.0613192, 0.0052814, 0.0952407, 0.1416877))
    snd_display("cepstrum 16: %s?", nrl)
  end
  rl = Vct.new(16) do |i| i end
  nrl = snd_transform($cepstrum, rl, 0).scale(2.72)
  unless vequal(nrl, vct(2.720, 0.452, 0.203, 0.122, 0.082, 0.061, 0.048, 0.041,
                         0.039, 0.041, 0.048, 0.061, 0.082, 0.122, 0.203, 0.452))
    snd_display("cepstrum 16 by ones: %s?", nrl)
  end
  catch(:done) do
    [16, 64, 256, 512].each do |len|
      rl = Vct.new(len)
      xim = Vct.new(len)
      xrl = Vct.new(len)
      rl[0] = 1.0
      rl[4] = 1.0
      snd_transform($cepstrum, rl, 0)
      xrl[0] = 1.0
      xrl[4] = 1.0
      mus_fft(xrl, xim, len, 1)
      xrl.map_with_index! do |xval, i|
        if (val = xval * xval + xim[i] * xim[i]) > 0.0000001
          log(sqrt(val))
        else
          -10.0
        end
      end
      xim.scale! 0.0
      mus_fft(xrl, xim, len, -1)
      fscl = 0.0
      xrl.each do |val| fscl = [fscl, val.abs].max end
      xrl.scale! 1.0 / fscl
      rl.each_with_index do |val, i|
        if fneq(val, xrl[i])
          snd_display("%s at %s: %s %s?", len, i, val, xrl[i])
          throw(:done)
        end
      end
    end
  end
end

def test_20_01
  #
  # walsh
  #
  d0 = Vct.new(8)
  d0[0] = 1.0
  snd_transform($walsh_transform, d0)
  unless vequal(d0, Vct.new(8, 1.0))
    snd_display("walsh 1: %s?", d0)
  end
  snd_transform($walsh_transform, d0)
  unless vequal(d0, vct(8, 0, 0, 0, 0, 0, 0, 0))
    snd_display("walsh -1: %s?", d0)
  end
  #
  d0 = Vct.new(8)
  d0[1] = 1.0
  snd_transform($walsh_transform, d0)
  unless vequal(d0, vct(1, -1, 1, -1, 1, -1, 1, -1))
    snd_display("walsh 2: %s?", d0)
  end
  snd_transform($walsh_transform, d0)
  unless vequal(d0, vct(0, 8, 0, 0, 0, 0, 0, 0))
    snd_display("walsh -2: %s?", d0)
  end
  #
  d0 = Vct.new(8)
  d0[0] = 0.5
  d0[1] = 1.0
  snd_transform($walsh_transform, d0)
  unless vequal(d0, vct(1.5, -0.5, 1.5, -0.5, 1.5, -0.5, 1.5, -0.5))
    snd_display("walsh 3: %s?", d0)
  end
  snd_transform($walsh_transform, d0)
  unless vequal(d0, vct(4, 8, 0, 0, 0, 0, 0, 0))
    snd_display("walsh -3: %s?", d0)
  end
  #
  d0 = Vct.new(8) do random(1.0) end
  d1 = d0.dup
  snd_transform($walsh_transform, d0)
  snd_transform($walsh_transform, d0)
  d0.scale! 1.0 / 8.0
  unless vequal(d0, d1)
    snd_display("walsh 4: %s %s?", d0, d1)
  end
  #
  d0 = vct(1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1)
  d1 = snd_transform($walsh_transform, d0)
  unless vequal(d1, vct(4, 4, 4, -4, 4, 4, 4, -4, 4, 4, 4, -4, -4, -4, -4, 4))
    snd_display("walsh 5: %s?", d1)
  end
  # 
  d0 = vct(1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
  d1 = snd_transform($walsh_transform, d0)
  unless vequal(d1, vct(0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0))
    snd_display("walsh 6: %s?", d1)
  end
  d0 = vct(0.174, -0.880, -0.555, -0.879, 0.038, 0.696, -0.612, 0.006,
           -0.613, 0.334, -0.111, -0.821, 0.130, 0.030, -0.229, 0.170)
  d1 = snd_transform($walsh_transform, d0)
  unless vequal(d1, vct(-3.122, -0.434, 2.940, -0.468, -3.580, 2.716, -0.178, -1.386,
                        -0.902, 0.638, 1.196, 1.848, -0.956, 2.592, -1.046, 2.926))
    snd_display("walsh 7: %s?", d1)
  end
  #
  # haar
  #
  d0 = Vct.new(8)
  d0[2] = 1.0
  snd_transform($haar_transform, d0)
  unless vequal(d0, vct(0.354, 0.354, -0.500, 0.000, 0.000, 0.707, 0.000, 0.000))
    snd_display("haar 1: %s?", d0)
  end
  inverse_haar(d0)
  unless vequal(d0, vct(0.000, 0.000, 1.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("inverse_haar 1: %s?", d0)
  end
  #
  d0 = Vct.new(8)
  d0[0] = 1.0
  snd_transform($haar_transform, d0)
  unless vequal(d0, vct(0.354, 0.354, 0.500, 0.000, 0.707, 0.000, 0.000, 0.000))
    snd_display("haar 2: %s?", d0)
  end
  inverse_haar(d0)
  unless vequal(d0, vct(1.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000))
    snd_display("inverse_haar 2: %s?", d0)
  end
  #
  d0 = snd_transform($haar_transform,
                     vct(-0.483, 0.174, -0.880, -0.555, -0.879, 0.038, 0.696, -0.612))
  unless vequal(d0, vct(-0.884, -0.349, 0.563, -0.462, -0.465, -0.230, -0.648, 0.925))
    snd_display("haar 3: %s?", d0)
  end
  #
  sq2 = sqrt(2.0)
  d0 = snd_transform($haar_transform, vct(4, 6, 10, 12, 8, 6, 5, 5))
  unless vequal(d0, vct(14 * sq2, 2 * sq2, -6, 2, -sq2, -sq2, sq2, 0))
    snd_display("haar 4: %s?", d0)
  end
  d0 = snd_transform($haar_transform, vct(2, 4, 6, 8, 10, 12, 14, 16))
  unless vequal(d0, x = vct(18 * sq2, -8 * sq2, -4, -4, -sq2, -sq2, -sq2, -sq2))
    snd_display("haar 5: %s?", d0, x)
  end
  #
  d0 = Vct.new(8)
  d1 = Vct.new(8) do |i| d0[i] = random(1.0) end
  snd_transform($haar_transform, d0)
  inverse_haar(d0)
  unless vequal(d0, d1)
    snd_display("inverse_haar 6: %s %s?", d0, d1)
  end
  #
  # wavelet
  #
  d0 = snd_transform($wavelet_transform, vct(1, 1, 0, 0, 0, 0, 0, 0), 0) # daub4
  unless vequal(d0, vct(0.625, 0.375, -0.217, 1.083, -0.354, 0.000, 0.000, 0.354))
    snd_display("fxt wavelet 1: %s?", d0)
  end
  [16, 64].each do |size|
    $wts.each do |key, val|
      d1 = Vct.new(size)
      d2 = Vct.new(size)
      d1[2] = 1.0
      d2[2] = 1.0
      wavelet(d1, size, 0, :pwt, val)
      snd_transform($wavelet_transform, d2, key.last)
      unless vequal(d1, d2)
        snd_display("wavelet (%s) %s:\n# %s\n# %s?", size, key, d1, d2)
      end
      wavelet(d2, size, -1, :pwt, val)
      d1.fill 0.0
      d1[2] = 1.0
      unless vequal(d1, d2)
        if key.last.between?(9, 10)
          d2[2] = 0.0
          if d2.peak > 0.1
            snd_display("inverse wavelet a (%s) %s:\n# %s\n# %s?", size, key, d1, d2)
          end
        else
          if key.last > 14
            pk = d2[2]
            d2[2] = 0.0
            if d2.peak > pk
              snd_display("inverse wavelet b (%s) %s:\n# %s\n# %s?", size, key, d1, d2)
            end
          else
            snd_display("inverse wavelet c (%s) %s:\n# %s\n# %s?", size, key, d1, d2)
          end
        end
      end
    end
    $wts.each do |key, val|
      next if key.last > 8
      d2 = Vct.new(size) do random(1.0) end
      d1 = d2.dup
      snd_transform($wavelet_transform, d2, key.last)
      wavelet(d2, size, -1, :pwt, val)
      unless vequal(d1, d2)
        snd_display("random wavelet %s:\n# %s\n# %s?", key.first, d1, d2)
      end
    end
  end
  # 
  set_max_transform_peaks(100)
  ind = open_sound("oboe.snd")
  ftype = add_transform("low-pass", "filtered", 0.0, 1.0,
                        lambda { |len, fd|
                          flt = make_fir_filter(:order, 8, :xcoeffs, Vct.new(8, 0.0125))
                          Vct.new(len) do fir_filter(flt, read_sample(fd)) end
                        })
  unless transform?(ftype)
    snd_display("transform added: %s?", ftype)
  end
  set_transform_normalization(Dont_normalize)
  set_transform_type(ftype, ind, 0)
  set_transform_size(16, ind, 0)
  set_transform_graph_type(Graph_once, ind, 0)
  set_transform_graph?(true, ind, 0)
  set_cursor(12000, ind, 0)
  samps = transform2vct(ind, 0)
  delete_file("s61.rb")
  save_state("s61.rb")
  delete_file("s61.rb")
  close_sound(ind)
  # 
  ind = open_sound("oboe.snd")
  ftype = add_transform("abs-it", "absit", 0.0, 1.0,
                        lambda { |len, fd|
                          Vct.new(len) do read_sample(fd) end
                        })
  set_transform_normalization(Dont_normalize)
  set_transform_type(ftype, ind, 0)
  set_transform_size(256, ind, 0)
  set_transform_graph_type(Graph_once, ind, 0)
  set_transform_graph?(true, ind, 0)
  set_cursor(12000, ind, 0)
  samps = transform2vct(ind, 0)
  channel2vct(left_sample(ind, 0), 256).zip(samps) do |val1, val2|
    if fneq(val1, val2)
      snd_display("add_transform same (%s): %s %s?", ftype, val1, val2)
    end
  end
  set_dot_size(60, ind, 0)
  set_graph_style(Graph_lollipops, ind, 0)
  set_x_bounds([2.579, 2.580])
  update_time_graph
  delete_transform(ftype)
  if transform?(ftype)
    snd_display("transform? deleted: %s?", ftype)
  end
  if transform?(-1)
    snd_display("transform? -1: %s?", ftype)
  end
  if transform?(integer2transform(123))
    snd_display("transform? 123: %s?", ftype)
  end
  if (res = transform_type(ind, 0)) != $fourier_transform
    snd_display("after delete_transform %s -> %s?", ftype, res)
  end
  close_sound(ind)
  #
  if defined? gsl_dht
    add_transform("Hankel", "Hankel", 0.0, 1.0,
                  lambda do |n, rd|
                    v = make_vct!(n) do rd.call end
                    gsl_dht(n, v, 1.0, 1.0)
                  end)
    n = 16
    v = make_vct(n, 1.0)
    gsl_dht(n, v, 1.0, 1.0)
    if (res = Snd.catch do gsl_dht(-1, Vct.new(3), 1.0, 1.0) end).first != :out_of_range
      snd_display("gsl_dht bad size: %s?", res.inspect)
    end
  end
  #
  ind1 = open_sound("oboe.snd")
  set_time_graph_style(Graph_lollipops, ind1, 0)
  graph2ps("aaa.eps")
  set_transform_graph?(true, ind1, 0)
  set_transform_graph_type(Graph_as_sonogram, ind1, 0)
  set_transform_size(256)
  update_transform_graph
  size = transform_frames(ind1, 0)
  if number?(size) or size.length != 3
    snd_display("transform_frames of sonogram: %s?", size)
  end
  graph2ps("aaa.eps")
  old_colormap = colormap
  set_colormap(integer2colormap(0))
  update_transform_graph
  set_transform_graph_type(Graph_as_spectrogram, ind1, 0)
  update_transform_graph
  graph2ps("aaa.eps")
  set_colormap(old_colormap)
  close_sound(ind1)
  # 
  ind = new_sound("test.snd", Mus_next, Mus_bfloat)
  pad_channel(0, 1000)
  set_transform_graph_type(Graph_once, ind, 0)
  set_show_transform_peaks(true, ind, 0)
  set_fft_log_magnitude(true, ind, 0)
  set_fft_log_frequency(false, ind, 0)
  set_transform_graph?(true, ind, 0)
  set_x_bounds([0.0, 0.04])
  update_time_graph
  update_transform_graph
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  size = 8192
  v = channel2vct(1000, size, ind, 0)
  set_show_listener(false)
  set_window_height(800)
  set_lisp_graph?(true, ind, 0)
  graph(v, "biggy", 0.0, 1.0, 0.0, 1.0, ind, 0)
  set_transform_graph_type(Graph_once, ind, 0)
  set_show_transform_peaks(true, ind, 0)
  set_fft_log_magnitude(true, ind, 0)
  set_fft_log_frequency(false, ind, 0)
  set_transform_graph?(true, ind, 0)
  graph2ps("aaa.eps")
  set_x_bounds([0.0, 1.0])
  set_max_transform_peaks(3, ind, 0)
  update_time_graph
  update_transform_graph
  update_lisp_graph
  scale_by(0.0)
  update_time_graph
  update_transform_graph
  undo_edit
  set_transform_graph_type(Graph_as_sonogram, ind, 0)
  set_fft_log_magnitude(false, ind, 0)
  update_transform_graph
  unless $with_test_gtk
    # FIXME (Cairo segfault)
    # doens't work with GTK
    # Assertion failed: (! surface->finished), function _cairo_surface_begin_modification, file cairo-surface.c, line 385.
    # Abort (core dumped)
    graph2ps("aaa.eps")
  end
  set_with_gl(false)
  set_spectrum_end(0.2, ind, 0)
  set_transform_graph_type(Graph_as_spectrogram, ind, 0)
  update_transform_graph
  update_lisp_graph
  graph2ps("aaa.eps")
  set_show_listener(true)
  close_sound(ind)
end

def test_20_02
  snd_test_neq(dolph(16, 2.5),
               vct(0.097, 0.113, 0.221, 0.366, 0.536, 0.709, 0.860, 0.963,
                   1.000, 0.963, 0.860, 0.709, 0.536, 0.366, 0.221, 0.113),
               "dolph 16 2.5 (dsp.rb)")
  v = Vct.new(8)
  v0 = Vct.new(8) do |i| v[i] = random(2.0) - 1.0 end
  v = dht(dht(v)).scale(1.0 / 8.0)
  snd_test_any_neq(v, v0, :vvequal?, "dht twice") # okay
  v.fill 0.0
  v[1] = 1.0
  snd_test_neq(dht(v), vct(1, 1.414, 1, 0, -1, -1.414, -1, 0), "dht of pulse")
  #
  ind = open_sound("oboe.snd")
  snd_test_neq(find_sine(553.0, 2000, 3000, ind).first, 0.03835, "find_sine")
  snd_test_neq(find_sine(620.0, 2000, 3000, ind).first, 0.0012, "find_sine")
  snd_test_neq(spot_freq(2000, ind, 0).round, 553, "spot_freq")
  down_oct(2)
  frq = spot_freq(2000, ind, 0)
  unless frq.ceil.between?(275, 277)
    snd_display("spot_freq down_oct: %s?", frq)
  end
  undo_edit
  zero_phase
  snd_test_neq(sample(0), 0.1472, "zero_phase")
  undo_edit
  rotate_phase(lambda do |x| x end)
  undo_edit
  brighten_slightly(0.5)
  undo_edit
  spike
  close_sound(ind)
  #
  ind = open_sound("1a.snd")
  frms = frames(ind)
  valf = find_sine(440.0, 0, frms, ind).first
  valg = 2 * (goertzel(440.0, 0, frms, ind) / frms)
  valf1 = find_sine(100.0, 0, frms, ind).first
  valg1 = 2 * (goertzel(100.0, 0, frms, ind) / frms)
  valf2 = find_sine(440.0, 0, frms, ind).first
  valg2 = 2 * (goertzel(440.0, 0, frms, ind) / frms)
  valf3 = find_sine(437.0, 0, frms, ind).first
  valg3 = 2 * (goertzel(437.0, 0, frms, ind) / frms)
  snd_test_neq(valg, valf, "goertzel 0")
  snd_test_neq(valg1, valf1, "goertzel 1")
  snd_test_neq(valg2, valf2, "goertzel 2")
  snd_test_neq(valg3, valf3, "goertzel 3")
  close_sound(ind)
  #
  snd_test_neq(vct_polynomial(vct(0, 2), vct(1, 2)),
               vct(1, 5),
               "vct_polynomial 0")
  snd_test_neq(vct_polynomial(vct(0, 1, 2), vct(0, 2, 1)),
               vct(0, 3, 8),
               "vct_polynomial 1")
  snd_test_neq(vct_polynomial(vct(0, 1, 2), vct(0, 2, 1, 0.5)),
               vct(0, 3.5, 12),
               "vct_polynomial 2")
  snd_test_neq(vct_polynomial(vct(0, 1, 2), vct(1)),
               vct(1, 1, 1),
               "vct_polynomial 3")
  #
  ind = open_sound("pistol.snd")
  mx = maxamp(ind, 0)
  channel_polynomial(vct(0, 2), ind, 0)
  snd_test_neq(maxamp(), mx * 2.0, "channel_polynomial 2")
  undo_edit
  channel_polynomial(vct(0.0, 0.5, 0.25, 0.25), ind, 0)
  snd_test_neq(maxamp(), 0.222, "channel_polynomial 3")
  undo_edit
  channel_polynomial(vct(0, 0, 1), ind, 0)
  if pos = scan_channel(lambda { |y| y < 0.0 })
    snd_display("channel_polynomial squares: %s?", pos)
  end
  undo_edit
  channel_polynomial(vct(0.5, 1), ind, 0)
  if pos = scan_channel(lambda { |y| y < 0.0 })
    snd_display("channel_polynomial offset: %s?", pos)
  end
  snd_test_neq(maxamp, 0.8575, "channel_polynomial off mx")
  undo_edit
  spectral_polynomial(vct(0, 1), ind, 0)
  snd_test_neq(maxamp(), 0.493, "spectral_polynomial 0 mx")
  snd_test_neq(frames(ind, 0), 41623, "spectral_polynomial 0 len")
  undo_edit
  spectral_polynomial(vct(0, 0.5, 0.5), ind, 0)
  snd_test_neq(maxamp(), 0.493, "spectral_polynomial 1 mx")
  snd_test_neq(frames(ind, 0), 41623 * 2, "spectral_polynomial 1 len")
  undo_edit
  spectral_polynomial(vct(0, 0, 0, 1), ind, 0)
  snd_test_neq(maxamp(), 0.493, "spectral_polynomial 2 mx")
  snd_test_neq(frames(ind, 0), 41623 * 3, "spectral_polynomial 2 len")
  close_sound(ind)
  #
  vals = scentroid("oboe.snd")
  snd_test_neq(vals[0], 1876.085, "scentroid vals[0]")
  snd_test_neq(vals[1], 1447.004, "scentroid vals[1]")
  flt = make_fir_filter(3, vct(0.5, 0.25, 0.125))
  data = Vct.new(10)
  data[0] = 1.0
  undata = Vct.new(10) do |i| fir_filter(flt, data[i]) end
  fdata = invert_filter(vct(0.5, 0.25, 0.125))
  flt = make_fir_filter(fdata.length, fdata)
  undata.map! do |val| fir_filter(flt, val) end
  snd_test_neq(undata, data, "invert_filter")
  # 
  top = 0.9
  coeffs = Vct.new(6) do
    top -= 0.1
    top + random(0.2)
  end
  flt = make_fir_filter(6, coeffs)
  data = Vct.new(20)
  data[0] = 1.0
  undata = Vct.new(20) do |i| fir_filter(flt, data[i]) end
  fdata = invert_filter(coeffs)
  flt = make_fir_filter(fdata.length, fdata)
  undata.map! do |val| fir_filter(flt, val) end
  snd_test_neq(undata, data, "invert_filter (6)")
  #
  flt = make_volterra_filter(vct(1, 0.4), vct(0.3, 0.2, 0.1))
  snd_test_neq(Vct.new(10) do |i| volterra_filter(flt, (i == 1 ? 0.5 : 0.0)) end,
               vct(0.000, 0.575, 0.250, 0.025, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000),
               "volterra_filter")
  # 
  flt = make_volterra_filter(vct(1.0), vct(1.0))
  snd_test_neq(Vct.new(10) do |i| volterra_filter(flt, (i.zero? ? 1.0 : 0.0)) end,
               vct(2.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000),
               "volterra_filter x + x^2")
  # 
  flt = make_volterra_filter(vct(1.0), vct(1.0))
  x = 1.1
  snd_test_neq(Vct.new(10) do |i| volterra_filter(flt, x -= 0.1) end,
               vct(2.000, 1.710, 1.440, 1.190, 0.960, 0.750, 0.560, 0.390, 0.240, 0.110),
               "volterra_filter x + x^2 by -0.1")
  # 
  flt = make_volterra_filter(vct(1.0, 0.5), vct(1.0))
  snd_test_neq(Vct.new(10) do |i| volterra_filter(flt, (i.zero? ? 1.0 : 0.0)) end,
               vct(2.000, 0.500, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000),
               "volterra_filter x + 0.5x(n-1) + x^2")
  # 
  flt = make_volterra_filter(vct(1.0, 0.5), vct(1.0, 0.6))
  snd_test_neq(Vct.new(10) do |i| volterra_filter(flt, (i.zero? ? 0.9 : 0.0)) end,
               vct(1.710, 0.936, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000),
               "volterra_filter x + 0.5x(n-1) + x^2 + 0.6")
  # 
  ind = new_sound("test.snd", :size, 100)
  gen = make_oscil(440)
  map_chan(lambda { |y| oscil(gen) })
  down_oct(2)
  snd_test_neq(frames(), 200, "down_oct new len")
  r1 = make_sampler(0, ind, 0, 1, 1)
  r2 = make_sampler(0, ind, 0, 1, 2)
  200.times do |i|
    val1 = r1.call
    val2 = r2.call
    val3 = r2.call
    if fneq(val1, val2) and fneq(val1, val3)
      snd_display(snd_format_neq(val1, val2, "down_oct[%d] %1.4f", i, val3))
    end
  end
  close_sound(ind)
  # 
  d0 = Vct.new(8)
  d1 = Vct.new(8)
  d0[2] = 1.0
  vals = fractional_fourier_transform(d0, d1, 8, 1.0)
  snd_test_neq(vals[0], vct(1, 0, -1, 0, 1, 0, -1, 0), "fractional_fourier_transform vals[0]")
  snd_test_neq(vals[1], vct(0, 1, 0, -1, 0, 1, 0, -1), "fractional_fourier_transform vals[1]")
  d0 = Vct.new(8)
  d1 = Vct.new(8)
  d0[2] = 1.0
  vals = z_transform(d0, 8, exp(make_rectangular(0.0, 0.25 * PI)))
  vals.each_with_index do |val, i|
    d0[i] = val.real
    d1[i] = val.imag
  end
  snd_test_neq(d0, vct(1, 0, -1, 0, 1, 0, -1, 0), "z_transform d0")
  snd_test_neq(d1, vct(0, 1, 0, -1, 0, 1, 0, -1), "z_transform d1")
  v1 = Vct.new(16)
  v1[0] = 1.0
  snd_test_neq(z_transform(v1, 16, 0.5).to_vct, Vct.new(16, 1.0), "z_transform 0.5 0=1")
  snd_test_neq(z_transform(v1, 16, -1.0).to_vct, Vct.new(16, 1.0), "z_transform -1.0 0=1")
  v1[0] = 0.0
  v1[1] = 1.0
  snd_test_neq(z_transform(v1, 16, 0.5).to_vct,
               vct(1, 0.5, 0.25, 0.125, 0.062, 0.031, 0.016, 0.008,
                   0.004, 0.002, 0.001, 0, 0, 0, 0, 0),
               "z_transform 0.5 1=1")
  snd_test_neq(z_transform(v1, 16, 2.0).to_vct,
               vct(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768),
               "z_transform 2.0 1=1")
  v1[2] = 1.0
  snd_test_neq(z_transform(v1, 16, 0.5).to_vct,
               vct(2, 0.75, 0.3125, 0.14, 0.0664, 0.0322, 0.0158, 0.00787,
                   0.0039, 0.0019, 0, 0, 0, 0, 0, 0),
               "z_transform 0.5 2=1")
  snd_test_neq(z_transform(v1, 16, 2.0).to_vct,
               vct(2, 6, 20, 72, 272, 1056, 4160, 16512, 65792, 262656, 1049600,
                   4196352, 16781312, 67117056, 268451840, 1073774592),
               "z_transform 2.0 1=1 2=1")
  j = 1.0
  v1.map! do |ignored|
    val = j
    j *= 0.4
    val
  end
  snd_test_neq(z_transform(v1, 16, 1.0).to_vct, Vct.new(16, 1.0 / (1.0 - 0.4)), "z_transform 1 0.4g")
  ind = open_sound("oboe.snd")
  automorph(Complex(0.0, 1.0), 0, 0, 1)
  automorph(Complex(0.0, 1.0), 0, 0, 1)
  automorph(Complex(0.0, 1.0), 0, 0, 1)
  automorph(Complex(0.0, 1.0), 0, 0, 1)
  mxdiff = 0.0
  rd1 = make_sampler(0, ind, 0)
  rd2 = make_sampler(0, ind, 0, 1, 0)
  scan_channel(lambda do |y|
                 diff = (rd1.call - rd2.call).abs
                 if diff > mxdiff
                   mxdiff = diff
                 end
                 false
               end)
  if mxdiff > 0.003
    snd_display(snd_format(mxdiff, 0.003, ">", "automorph rotation"))
  end
  close_sound(ind)
end

def test_20
  if defined? bes_j0
    bes_test(:bes_j0)
    bes_test(:bes_j1)
    bes_test_jn
    bes_test_y(:bes_y0)
    bes_test_y(:bes_y1)
    bes_test_yn
    bes_test_i0
    bes_test_i1
    bes_test_in
    bes_test_k0
    bes_test_k1
    bes_test_kn
    test_lgamma
    test_erf
  end
  test_20_00
  test_20_01 if $with_test_gui
  test_20_02
end

# ---------------- test 21: new stuff ----------------

def display_samps_in_red(snd, chn)
  left = left_sample(snd, chn)
  right = right_sample(snd, chn)
  old_color = foreground_color(snd, chn)
  red = make_color_with_catch(1, 0, 0)
  if left < 2000 and right > 1000
    case data = make_graph_data(snd, chn)
    when Vct
      samps = [right, 2000].min - [left, 1000].max
      offset = [0, 1000 - left].max
      new_data = data.subseq(offset, offset + samps)
      set_foreground_color(red, snd, chn)
      cr = make_cairo(channel_widgets(snd, chn)[0])
      graph_data(new_data,
                 snd, chn, Copy_context, [1000, left].max, [2000, right].min, Graph_lines, cr)
      free_cairo(cr)
      set_foreground_color(old_color, snd, chn)
    when Array
      low_data = data[0]
      high_data = data[1]
      size = low_data.length
      samps = right - left
      left_offset = [0, 1000 - left].max
      left_bin = ((size * left_offset.to_f) / samps).round
      right_offset = [2000, right].min - left
      right_bin = ((size * right_offset.to_f) / samps).round
      new_low_data = low_data.subseq(left_bin, right_bin)
      new_high_data = high_data.subseq(left_bin, right_bin)
      set_foreground_color(red, snd, chn)
      cr = make_cairo(channel_widgets(snd, chn)[0])
      graph_data([new_low_data, new_high_data],
                 snd, chn, Copy_context, left_bin, right_bin, Graph_lines, cr)
      free_cairo(cr)
      set_foreground_color(old_color, snd, chn)
    end
  end
rescue
  snd_display("draw error in %s", get_func_name)
end

def show_greeting(snd, chn)
  ls = left_sample(snd, chn)
  rs = right_sample(snd, chn)
  if ls < 1000 and rs > 1000
    pos = x2position(1000.0 / srate(snd), snd, chn)
    old_color = foreground_color(snd, chn)
    cr = make_cairo(channel_widgets(snd, chn)[0])
    set_foreground_color(make_color_with_catch(0.75, 0.75, 0.75), snd, chn)
    fill_rectangle(pos, 10, 50, 20, snd, chn, Time_graph, false, cr)
    set_foreground_color(make_color_with_catch(1, 0, 0), snd, chn)
    draw_string("hi!", pos + 5, 24, snd, chn, Time_graph, cr)
    set_foreground_color(old_color, snd, chn)
    free_cairo(cr)
  end
rescue
  snd_display("draw error in %s", get_func_name)
end

def st_equal?(a, b)
  a == b
end

def st_eql?(a, b)
  a.eql?(b)
end

def st_vequal_2(a, b)
  vequal(a[0], b[0]) and vequal(a[1], b[1])
end

def test_sound_func_1(func, ind_1, ind_2, new_val, eq_func, leq_func, set_p, chan, global)
  old_val = snd_func(func)
  old_vals = snd_func(func, true)
  old_default = snd_func(func, false)
  old_1 = snd_func(func, ind_1)
  old_2 = snd_func(func, ind_2)
  sel_snd = selected_sound()
  unsel_snd = sel_snd == ind_1 ? ind_2 : ind_1
  caller = chan ? "channel" : "sound"
  snd_test_any_neq(old_val, old_default, eq_func, "%s sound_func: no arg false", func)
  unless method(leq_func).call(old_vals, [old_1, old_2]) or
      method(leq_func).call(old_vals, [old_2, old_1])
    snd_display(snd_format_neq(old_vals, [old_1, old_2], "%s sound_func true", func))
  end
  if set_p
    set_snd_func(func, new_val)
    res1 = snd_func(func)
    res2 = snd_func(func, sel_snd)
    res3 = snd_func(func, unsel_snd)
    snd_test_any_neq(res1, new_val, eq_func, "set_%s no arg", func)
    snd_test_any_neq(res1, res2, eq_func, "set_%s no arg sel", func)
    if (global and (not method(eq_func).call(res1, res3))) or
        (not global and method(eq_func).call(res1, res3))
      snd_display(snd_format_neq(res1, res3, "set_%s no arg unsel", func))
    end
    res1 = snd_func(func, true)
    unless method(leq_func).call(res1, [res2, res3]) or method(leq_func).call(res1, [res3, res2])
      snd_display(snd_format_neq(res1, [res2, res3], "set_%s %s_func true", func, caller))
    end
    set_snd_func(func, old_val)
    if set_p == :swap
      set_snd_func(func, ind_1, new_val)
    else
      set_snd_func(func, new_val, ind_1)
    end
    snd_test_any_neq(snd_func(func, ind_1), new_val, eq_func, "set_%s arg", func)
    snd_test_any_eq(snd_func(func, ind_2), new_val, eq_func, "set_%s arg (2)", func)
    res1 = snd_func(func, true)
    res2 = snd_func(func, ind_1)
    res3 = snd_func(func, ind_2)
    unless method(leq_func).call(res1, [res2, res3]) or method(leq_func).call(res1, [res3, res2])
      snd_display(snd_format_neq(res1, [res2, res3], "set_%s %s_func arg", func, caller))
    end
    if set_p == :swap
      set_snd_func(func, ind_1, old_1)
      set_snd_func(func, true, new_val)
    else
      set_snd_func(func, old_1, ind_1)
      set_snd_func(func, new_val, true)
    end
    res1 = snd_func(func, true)
    res2 = snd_func(func, ind_1)
    res3 = snd_func(func, ind_2)
    snd_test_any_neq(res1, [new_val, new_val], leq_func, "set_%s %s_func arg true", func, caller)
    snd_test_any_neq(res2, new_val, eq_func, "set_%s %s_func arg true", func, caller)
    snd_test_any_neq(res3, new_val, eq_func, "set_%s %s_func arg true (2)", func, caller)
    if set_p == :swap
      set_snd_func(func, ind_1, old_1)
      set_snd_func(func, ind_2, old_2)
    else
      set_snd_func(func, old_1, ind_1)
      set_snd_func(func, old_2, ind_2)
    end
    res2 = snd_func(func, ind_1)
    res3 = snd_func(func, ind_2)
    snd_test_any_neq(res2, old_1, eq_func, "set_%s arg true old", func)
    snd_test_any_neq(res3, old_2, eq_func, "set_%s arg true old (2)", func)
  end
end

def test_channel_func_1(func, ind_1, ind_2, new_val, eq_func, leq_func, set_p, global)
  old_1_0 = snd_func(func, ind_1, 0)
  old_2_0 = snd_func(func, ind_2, 0)
  old_2_1 = snd_func(func, ind_2, 1)
  old_1_all = snd_func(func, ind_1, true)
  old_2_all = snd_func(func, ind_2, true)
  old_all_0 = snd_func(func, true, 0)
  old_all_all = snd_func(func, true, true)
  snd_test_any_neq(old_1_0, old_1_all[0], eq_func, "%s channel_func: old 1/true", func)
  snd_test_any_neq(old_2_0, old_2_all[0], eq_func, "%s channel_func: old 2-1/true", func)
  snd_test_any_neq(old_2_1, old_2_all[1], eq_func, "%s channel_func: old 2-2/true", func)
  snd_test_any_neq(old_1_all, [old_1_0], leq_func, "%s channel_func true", func)
  snd_test_any_neq(old_2_all, [old_2_0, old_2_1], leq_func, "%s channel_func true", func)
  unless ((method(leq_func).call(old_all_all[0], old_1_all) or
           method(leq_func).call(old_all_all[0], old_2_all)) and
          (method(leq_func).call(old_all_all[1], old_1_all) or
           (method(leq_func).call(old_all_all[1], old_2_all))))
    snd_display(snd_format_neq(old_all_all[0], old_1_all, "%s channel_func true true", func))
  end
  if set_p
    set_snd_func(func, new_val, ind_1, 0)
    snd_test_any_neq(snd_func(func, ind_1, 0), new_val, eq_func, "set_%s channel_func", func)
    snd_test_any_eq(snd_func(func, ind_2, 0), new_val, eq_func, "set_%s 2 channel_func", func)
    set_snd_func(func, old_1_0, ind_1, 0)
    set_snd_func(func, new_val, ind_2, 1)
    snd_test_any_eq(snd_func(func, ind_1, 0), new_val, eq_func, "set_%s (2) channel_func", func)
    snd_test_any_neq(snd_func(func, ind_2, 1), new_val, eq_func, "set_%s (2) 2 channel_func", func)
    set_snd_func(func, new_val, ind_2, 0)
    set_snd_func(func, old_2_0, ind_2, true)
    snd_test_any_neq(snd_func(func, ind_2, 0), old_2_0, eq_func, "set_%s (true 0) 2 channel_func", func)
    snd_test_any_neq(snd_func(func, ind_2, 1), old_2_0, eq_func, "set_%s (true 1) 2 channel_func", func)
    set_snd_func(func, old_2_0, ind_2, 0)
    set_snd_func(func, old_2_1, ind_2, 1)
  end
end

def test_21_00
  unless sound_file?("oboe.snd")
    snd_display("oboe.snd not a sound file?")
  end
  unless sound_file?("4.aiff")
    snd_display("4.aiff not a sound file?")
  end
  if sound_file?("snd.h")
    snd_display("snd.h is a sound file?")
  end
  ind1 = open_sound("oboe.snd")
  save_sound_as("test.snd", ind1)
  ind2 = open_sound("test.snd")
  unless channels_equal?(ind1, 0, ind2, 0)
    snd_display("channels_equal? of copy")
  end
  unless channels_eql?(ind1, 0, ind2, 0)
    snd_display("channels_eql? of copy")
  end
  pad_channel(frames(ind2, 0), 100)
  if channels_equal?(ind1, 0, ind2, 0)
    snd_display("channels_equal? of pad")
  end
  unless channels_eql?(ind1, 0, ind2, 0)
    snd_display("channels_eql? of pad")
  end
  set_sample(50900, 0.1, ind2, 0)
  if channels_equal?(ind1, 0, ind2, 0)
    snd_display("channels_equal? of pad+set")
  end
  if channels_eql?(ind1, 0, ind2, 0)
    snd_display("channels_eql? of pad+set")
  end
  unless channels_eql?(ind1, 0, ind2, 0, 0.2)
    snd_display("channels_eql? of pad+set .2err")
  end
  add_comment(1234, "sample 1234", ind1, 0)
  comments = show_comments(ind1, 0)
  update_time_graph
  if comments.null?
    snd_display("add_comment failed?")
  end
  display_db(ind1, 0)
  display_samps_in_red(ind1, 0)
  update_time_graph
  show_greeting(ind1, 0)
  update_time_graph
  color_samples(highlight_color, 0, 100, ind1, 0)
  update_time_graph
  power_env_channel(make_power_env([0, 0, 0.325, 1, 1, 32, 2, 0, 32], :duration, 2.0))
  update_time_graph
  $with_test_motif and show_disk_space(ind1)
  update_time_graph
  revert_sound(ind1)
  make_selection(10000, 20000, ind1, 0)
  if selection?
    show_selection
    vals = x_bounds(ind1, 0)
    if vals.length == 2
      snd_test_neq(vals[0], 10000.0 / srate(ind1), "show_selection")
      snd_test_neq(vals[1], 20000.0 / srate(ind1), "show_selection")
    end
  else
    snd_display("make_selection for show failed?")
  end
  $graph_hook.add_hook!("test-21-zoom-spectrum", &method(:zoom_spectrum).to_proc)
  set_transform_graph?(true, ind1, 0)
  ind3 = open_sound("pistol.snd")
  overlay_sounds(ind2, ind1, ind3)
  update_time_graph(ind2, 0)
  $after_graph_hook.reset_hook!
  close_sound(ind3)
  samples_via_colormap(ind1, 0)
  close_sound(ind1)
  $graph_hook.remove_hook!("test-21-zoom-spectrum")
  close_sound(ind2)
  #
  ind = new_sound("tmp.snd", Mus_next, Mus_bfloat, 22050, 1, :size, 50)
  set_sample(3, 1.0)
  filter_channel(vct(0.5, 1.0, 0.5), 3)
  unless vequal(res = channel2vct(0, 10), vct(0, 0, 0, 0.5, 1, 0.5, 0, 0, 0, 0))
    snd_display("filter_channel (sym 3): %s?", res)
  end
  undo_edit
  filter_channel(vct(0.5, 1.0, 0.25), 3)
  unless vequal(res = channel2vct(0, 10), vct(0, 0, 0, 0.5, 1, 0.25, 0, 0, 0, 0))
    snd_display("filter_channel (3): %s?", res)
  end
  undo_edit
  filter_channel(vct(0.5, 1.0, 1.0, 0.5), 4)
  unless vequal(res = channel2vct(0, 10), vct(0, 0, 0, 0.5, 1, 1, 0.5, 0, 0, 0))
    snd_display("filter_channel (sym 4): %s?", res)
  end
  undo_edit
  filter_channel(vct(0.5, 1.0, 1.0, 0.25), 4)
  unless vequal(res = channel2vct(0, 10), vct(0, 0, 0, 0.5, 1, 1, 0.25, 0, 0, 0))
    snd_display("filter_channel (4): %s?", res)
  end
  undo_edit
  close_sound(ind)
  # 
  ind = new_sound("tmp.snd", Mus_next, Mus_bfloat, 22050, 1, false, 100)
  set_sample(10, 0.5)
  filter_sound(vct(1, 0, 1), 3)
  unless vequal(res = channel2vct(5, 10), vct(0, 0, 0, 0, 0, 0.5, 0, 0.5, 0, 0))
    snd_display("filter_sound 1 0 1: %s?", res)
  end
  undo_edit
  filter_channel(vct(1, 0, 1), 3)
  unless vequal(res = channel2vct(5, 10), vct(0, 0, 0, 0, 0, 0.5, 0, 0.5, 0, 0))
    snd_display("filter_channel (v) 1 0 1: %s?", res)
  end
  undo_edit
  filter_sound([0, 1, 1, 1], 100)
  coeffs = make_fir_coeffs(100, Vct.new(100, 0.5))
  data = channel2vct(10, 100)
  data.zip(coeffs) do |val, coeff|
    if fneq(val, coeff)
      snd_display("coeffs [0, 1, 1, 1]: %s %s?", val, coeff)
      break
    end
  end
  undo_edit
  filter_sound([0, 1, 1, 1], 1000)
  unless vequal(res = channel2vct(5, 10), vct(0, 0, 0, 0, 0, 0.5, 0, 0, 0, 0))
    snd_display("filter_sound 1 (1000): %s?", res)
  end
  undo_edit
  make_selection(5, 15)
  filter_selection([0, 1, 1, 1], 100)
  if (res = edit_fragment 2) != ["filter_selection([0.000, 1.000, 1.000, 1.000], 100", "set", 5, 11]
    snd_display("filter_selection truncated: %s", res)
  end
  undo_edit
  filter_selection([0, 1, 1, 1], 100, false)
  if (res = edit_fragment 2) != ["filter_selection([0.000, 1.000, 1.000, 1.000], 100", "set", 5,111]
    snd_display("filter_selection not truncated: %s", res)
  end
  unless vequal(res = channel2vct(50, 10),
                vct(-0.016, 0.018, -0.021, 0.024, -0.029, 0.035, -0.045, 0.064, -0.106, 0.318))
    snd_display("filter_selection no trunc: %s?", res)
  end
  undo_edit
  filter_selection([0, 1, 1, 1], 1000, true)
  if (res = edit_fragment 2) != ["filter_selection([0.000, 1.000, 1.000, 1.000], 1000", "set", 5,11]
    snd_display("filter_selection truncated (1000): %s", res)
  end
  if fneq(maxamp, 0.0)
    snd_display("filter_selection 1000 untrunc: %s?", maxamp)
  end
  undo_edit
  filter_selection([0, 1, 1, 1], 1000, false)
  if (res = edit_fragment 2) != ["filter_selection([0.000, 1.000, 1.000, 1.000], 1000",
                                 "set", 5, 1011]
    snd_display("filter_selection not truncated (1000): %s", res)
  end
  if fneq(maxamp, 0.318)
    snd_display("filter_selection 1000 no trunc: %s?", maxamp)
  end
  unless vequal(res = channel2vct(505, 10),
                vct(0.035, -0.045, 0.064, -0.106, 0.318, 0.318, -0.106, 0.064, -0.045, 0.035))
    snd_display("filter_selection 1000 no trunc: %s?", res)
  end
  undo_edit
  filter_channel([0, 1, 1, 1], 10)
  unless vequal(res = channel2vct(10, 10),
                vct(0.008, -0.025, 0.050, -0.098, 0.316, 0.316, -0.098, 0.050, -0.025, 0.008))
    snd_display("filter_channel 10: %s?", res)
  end
  undo_edit
  filter_channel([0, 1, 1, 1], 1000)
  unless vequal(res = channel2vct(5, 10), vct(0, 0, 0, 0, 0, 0.5, 0, 0, 0, 0))
    snd_display("filter_channel 1 (1000): %s?", res)
  end
  undo_edit
  filter_channel([0, 1, 1, 0], 10)
  unless vequal(channel2vct(0, 30),
                vct(0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0.005, 0.010, 0.006, 0.038, 0.192, 0.192, 0.038, 0.006, 0.010, 0.005,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
    snd_display("filter_channel lp: %s %s %s?",
                channel2vct(0, 10),  channel2vct(10, 10),  channel2vct(20, 10))
  end
  undo_edit
  filter_channel([0, 1, 1, 0], 10, 0, 20, false, false, false, false)
  unless vequal(channel2vct(0, 30),
                vct(0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0.005, 0.010, 0.006, 0.038, 0.192, 0.192, 0.038, 0.006, 0.010, 0.005,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
    snd_display("filter_channel lp no trunc: %s %s %s?",
                channel2vct(0, 10),  channel2vct(10, 10),  channel2vct(20, 10))
  end
  undo_edit
  close_sound(ind)
  # 
  ind = new_sound("tmp.snd", Mus_next, Mus_bfloat, 22050, 2, false, 100)
  set_sample(10, 0.5)
  set_sample(5, -0.5, ind, 1)
  set_sync(1, ind)
  filter_sound(vct(1, 0, 1), 3)
  unless vequal(res = channel2vct(5, 10, ind, 0), vct(0, 0, 0,0, 0, 0.5, 0, 0.5, 0, 0))
    snd_display("(2) filter_sound 1 0 1: %s?", res)
  end
  unless vequal(res = channel2vct(0, 10, ind, 1), vct(0, 0, 0,0, 0, -0.5, 0, -0.5, 0, 0))
    snd_display("(2) filter_sound 1 0 2: %s?", res)
  end
  undo_edit
  filter_sound([0, 1, 1, 1], 1000)
  unless vequal(res = channel2vct(5, 10, ind, 0), vct(0, 0, 0,0, 0, 0.5, 0, 0, 0, 0))
    snd_display("(2) filter_sound 1 (1000): %s?", res)
  end
  unless vequal(res = channel2vct(0, 10, ind, 1), vct(0, 0, 0,0, 0, -0.5, 0, 0, 0, 0))
    snd_display("(2) filter_sound 2 (1000): %s?", res)
  end
  undo_edit
  make_selection(0, 20)
  filter_selection(vct(1, 0, 1), 3)
  unless vequal(res = channel2vct(5, 10, ind, 0), vct(0, 0, 0,0, 0, 0.5, 0, 0.5, 0, 0))
    snd_display("(2) filter_selection 1 0 1: %s?", res)
  end
  unless vequal(res = channel2vct(0, 10, ind, 1), vct(0, 0, 0,0, 0, -0.5, 0, -0.5, 0, 0))
    snd_display("(2) filter_selection 1 0 2: %s?", res)
  end
  undo_edit
  set_sync(0, ind)
  filter_selection(vct(1, 0, 1), 3)
  unless vequal(res = channel2vct(5, 10, ind, 0), vct(0, 0, 0,0, 0, 0.5, 0, 0.5, 0, 0))
    snd_display("(2) filter_selection 1 0 1 (no sync): %s?", res)
  end
  unless vequal(res = channel2vct(0, 10, ind, 1), vct(0, 0, 0,0, 0, -0.5, 0, -0.5, 0, 0))
    snd_display("(2) filter_selection 1 0 2 (no sync): %s?", res)
  end
  undo_edit(1, ind, 0)
  undo_edit(1, ind, 1)
  if (res = edit_position(ind, 0)) != 1
    snd_display("edpos filter_selection undo 0: %s?", res)
  end
  if (res = edit_position(ind, 1)) != 1
    snd_display("edpos filter_selection undo 1: %s?", res)
  end
  filter_sound(vct(1, 0, 1), 3)
  unless vequal(res = channel2vct(5, 10, ind, 0), vct(0, 0, 0,0, 0, 0.5, 0, 0.5, 0, 0))
    snd_display("(2) filter_sound 1 0 1 (no sync): %s?", res)
  end
  unless vequal(res = channel2vct(0, 10, ind, 1), vct(0, 0, 0,0, 0, -0.5, 0, 0, 0, 0))
    snd_display("(2) filter_sound 1 0 2 (no sync): %s?", res)
  end
  undo_edit(1, ind, 0)
  filter_channel([0, 1, 1, 0], 10, false, false, ind, 1)
  unless vequal(channel2vct(0, 30, ind, 1),
                vct(0, 0, 0, 0, 0,
                    -0.005, -0.010, -0.006, -0.038, -0.192, -0.192, -0.038, -0.006, -0.010, -0.005,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
    snd_display("filter_channel lp:\n# %s\n# %s\n# %s",
                channel2vct(0, 10, ind, 1),
                channel2vct(10, 10, ind, 1),
                channel2vct(20, 10, ind, 1))
  end
  undo_edit(1, ind, 1)
  close_sound(ind)
  # 
  ind = new_sound("tmp.snd", Mus_next, Mus_bshort, 22050, 1, :size, 100)
  set_sample(10, 0.5)
  set_sample(20, -0.5)
  scale_to(1.0)
  if fneq(sample(10), 0.999)
    snd_display("scale_to 1.0 Mus_bshort (10): %s?", sample(10))
  end
  if fneq(sample(20), -0.999)
    snd_display("scale_to 1.0 Mus_bshort (20): %s?", sample(20))
  end
  close_sound(ind)
  ind = new_sound("tmp.snd", Mus_next, Mus_byte, 22050, 1, :size, 100)
  set_sample(10, 0.5)
  set_sample(20, -0.5)
  scale_to(1.0)
  if fneq(sample(10), 0.992)
    snd_display("scale_to 1.0 Mus_byte (10): %s?", sample(10))
  end
  if fneq(sample(20), -0.992)
    snd_display("scale_to 1.0 Mus_byte (20): %s?", sample(20))
  end
  close_sound(ind)
  # 
  set_transform_graph_type(Graph_once)
  set_fft_window(6)
  set_show_y_zero(false)
  set_show_transform_peaks(false)
  set_fft_log_frequency(false)
  set_fft_log_magnitude(false)
  set_with_verbose_cursor(false)
  set_show_grid(false)
  set_show_sonogram_cursor(false)
  set_with_tracking_cursor(false)
  set_show_controls(false)
  set_speed_control_tones(12)
  set_wavelet_type(0)
  set_spectrum_start(0.0)
  set_spectro_hop(4)
  set_fft_window_alpha(0.0)
  set_fft_window_beta(0.0)
  ind_1 = new_sound("test-1.snd", Mus_next, Mus_bfloat, 22050, 1, "mono testing", 100)
  ind_2 = new_sound("test-2.snd", Mus_aifc, Mus_bshort, 44100, 2, "stereo testing", 300)
  [[:srate,                 48000,    :st_equal?, :st_eql?,  :swap],
   [:data_format,           Mus_byte, :st_equal?, :st_eql?,  :swap],
   [:data_location,         123,      :st_equal?, :st_eql?,  :swap],
   [:data_size,             12348,    :st_equal?, :st_eql?,  :swap],
   [:frames,                12348,    :st_equal?, :st_eql?,  true],
   [:sync,                  2,        :st_equal?, :st_eql?,  true],
   [:channels,              0,        :st_equal?, :st_eql?,  false],
   [:chans,                 0,        :st_equal?, :st_eql?,  false],
   [:header_type,           0,        :st_equal?, :st_eql?,  false],
   [:amp_control,           0.5,      :ffequal?,  :vfequal?, true],
   [:contrast_control,      0.5,      :ffequal?,  :vfequal?, true],
   [:expand_control,        0.5,      :ffequal?,  :vfequal?, true],
   [:speed_control,         0.5,      :ffequal?,  :vfequal?, true],
   [:reverb_control_length, 0.5,      :ffequal?,  :vfequal?, true], 
   [:reverb_control_scale,  0.5,      :ffequal?,  :vfequal?, true],
   [:contrast_control?,     true,     :st_eql?,   :st_eql?,  true],
   [:expand_control?,       true,     :st_eql?,   :st_eql?,  true],
   [:filter_control?,       true,     :st_eql?,   :st_eql?,  true],
   [:reverb_control?,       true,     :st_eql?,   :st_eql?,  true],
   [:read_only,             true,     :st_eql?,   :st_eql?,  true],
   [:file_name,             nil,      :st_equal?, :st_eql?,  false],
   [:short_file_name,       nil,      :st_equal?, :st_eql?,  false],
   [:comment,               nil,      :st_equal?, :st_eql?,  false]
  ].each do |func, new_val, eq_func, leq_func, settable|
    test_sound_func_1(func, ind_1, ind_2, new_val, eq_func, leq_func, settable, false, false)
  end
  save_controls(true)
  restore_controls(true)
  reset_controls(true)
  close_sound(true)
  if sounds != nil
    snd_display("sounds after close_sound(true): %s?", sounds)
  end
  # 
  # snd chn cases
  # 
  ind_1 = new_sound("test-1.snd", Mus_next, Mus_bfloat, 22050, 1, "mono testing", 100)
  ind_2 = new_sound("test-2.snd", Mus_aifc, Mus_bshort, 44100, 2, "stereo testing", 300)
  set_sample(1, 0.1, ind_1, 0)
  set_sample(2, 0.2, ind_2, 0)
  set_sample(3, 0.3, ind_2, 1)
  vals = [[:min_dB,                  -100.0,            :ffequal?,  :vequal?, true, true],
          [:x_position_slider,       0.1,               :ffequal?,  :vequal?, true, false],
          [:y_position_slider,       0.5,               :ffequal?,  :vequal?, true, false],
          [:x_zoom_slider,           0.2,               :ffequal?,  :vequal?, true, false],
          [:y_zoom_slider,           0.2,               :ffequal?,  :vequal?, true, false],
          [:fft_window_alpha,        0.5,               :ffequal?,  :vequal?, true, true],
          [:fft_window_beta,         0.5,               :ffequal?,  :vequal?, true, true],
          [:spectrum_end,            0.2,               :ffequal?,  :vequal?, true, true],
          [:spectrum_start,          0.1,               :ffequal?,  :vequal?, true, true],
          [:spectro_x_angle,         10.0,              :ffequal?,  :vequal?, true, true],
          [:spectro_x_scale,         0.2,               :ffequal?,  :vequal?, true, true],
          [:spectro_y_angle,         10.0,              :ffequal?,  :vequal?, true, true],
          [:spectro_y_scale,         0.1,               :ffequal?,  :vequal?, true, true],
          [:spectro_z_angle,         10.0,              :ffequal?,  :vequal?, true, true],
          [:spectro_z_scale,         0.3,               :ffequal?,  :vequal?, true, true],
          [:beats_per_minute,        100.0,             :ffequal?,  :vequal?, true, true],
          [:spectro_hop,             10,                :st_equal?, :st_eql?, true, true],
          [:cursor,                  50,                :st_equal?, :st_eql?, true, false],
          [:cursor_style,            1,                 :st_equal?, :st_eql?, true, true],
          [:cursor_size,             10,                :st_equal?, :st_eql?, true, true],
          [:frames,                  50,                :st_equal?, :st_eql?, true, false],
          [:zero_pad,                1,                 :st_equal?, :st_eql?, true, true],
          [:wavelet_type,            1,                 :st_equal?, :st_eql?, true, true],
          # FIXME (segfault, see below)
          # [:time_graph_type,         Graph_as_wavogram, :st_equal?, :st_eql?, true, true],
          [:wavo_hop,                10,                :st_equal?, :st_eql?, true, true],
          [:wavo_trace,              10,                :st_equal?, :st_eql?, true, true],
          [:transform_size,          64,                :st_equal?, :st_eql?, true, true],
          [:transform_graph_type,    1,                 :st_equal?, :st_eql?, true, true],
          [:fft_window,              1,                 :st_equal?, :st_eql?, true, true],
          [:transform_normalization, 2,                 :st_equal?, :st_eql?, true, true],
          [:max_transform_peaks,     10,                :st_equal?, :st_eql?, true, true],
          [:dot_size,                10,                :st_equal?, :st_eql?, true, true],
          [:show_axes,               2,                 :st_equal?, :st_eql?, true, true],
          [:transform_graph?,        true,              :st_equal?, :st_eql?, true, false],
          [:time_graph?,             false,             :st_equal?, :st_eql?, true, false],
          [:lisp_graph?,             true,              :st_equal?, :st_eql?, true, false],
          [:squelch_update,          true,              :st_equal?, :st_eql?, true, false],
          [:show_y_zero,             true,              :st_equal?, :st_eql?, true, true],
          [:show_grid,               true,              :st_equal?, :st_eql?, true, true],
          [:grid_density,            0.5,               :ffequal?,  :vequal?, true, true],
          [:show_sonogram_cursor,    true,              :st_equal?, :st_eql?, true, true],
          [:show_marks,              false,             :st_equal?, :st_eql?, true, true],
          [:show_transform_peaks,    true,              :st_equal?, :st_eql?, true, true],
          [:fft_log_frequency,       true,              :st_equal?, :st_eql?, true, true],
          [:fft_log_magnitude,       true,              :st_equal?, :st_eql?, true, true],
          [:show_mix_waveforms,      false,             :st_equal?, :st_eql?, true, true],
          [:with_verbose_cursor,     true,              :st_equal?, :st_eql?, true, true]]
  unless $with_test_gtk
    # FIXME (GTK segfault)
    # doesn't work with GTK
    # set_time_graph_type(Graph_as_wavogram) ==> hangs or even segfaults
    vals += [[:time_graph_type, Graph_as_wavogram, :st_equal?, :st_eql?, true, true]]
  end
  vals.each do |func, new_val, eq_func, leq_func, settable, global|
    test_sound_func_1(func, ind_1, ind_2, new_val, eq_func, leq_func, settable, true, global)
    test_channel_func_1(func, ind_1, ind_2, new_val, eq_func, leq_func, settable, global)
  end
  update_time_graph(true, true)
  update_transform_graph(true, true)
  update_lisp_graph(true, true)
  close_sound(false)
  close_sound(false)
  if sounds != nil
    snd_display("sounds after close_sound(false) twice: %s?", sounds)
  end
  # 
  ind_1 = new_sound("test-1.snd", Mus_next, Mus_bfloat, 22050, 1, "mono testing", 100)
  ind_2 = new_sound("test-2.snd", Mus_aifc, Mus_bshort, 44100, 2, "stereo testing", 300)
  # test_sound_func_2
  [[:filter_control_in_dB,         true,                      :st_eql?,   :st_eql?],
   [:filter_control_in_hz,         true,                      :st_eql?,   :st_eql?],
   [:show_controls,                true,                      :st_eql?,   :st_eql?],
   [:speed_control_tones,          14,                        :st_equal?, :st_eql?],
   [:speed_control_style,          Speed_control_as_semitone, :st_equal?, :st_eql?],
   [:filter_control_order,         14,                        :st_equal?, :st_eql?],
   [:expand_control_length,        0.25,                      :ffequal?,  :vequal?],
   [:expand_control_ramp,          0.25,                      :ffequal?,  :vequal?],
   [:expand_control_hop,           0.25,                      :ffequal?,  :vequal?],
   [:expand_control_jitter,        0.25,                      :ffequal?,  :vequal?],
   [:contrast_control_amp,         0.25,                      :ffequal?,  :vequal?],
   [:reverb_control_feedback,      0.25,                      :ffequal?,  :vequal?],
   [:reverb_control_lowpass,       0.25,                      :ffequal?,  :vequal?],
   [:reverb_control_decay,         0.25,                      :ffequal?,  :vequal?],
   [:amp_control_bounds,           [0.0, 2.0],                :vequal?,   :st_vequal_2],
   [:contrast_control_bounds,      [0.0, 2.0],                :vequal?,   :st_vequal_2],
   [:expand_control_bounds,        [0.1, 2.0],                :vequal?,   :st_vequal_2],
   [:speed_control_bounds,         [0.1, 2.0],                :vequal?,   :st_vequal_2],
   [:reverb_control_length_bounds, [0.0, 2.0],                :vequal?,   :st_vequal_2],
   [:reverb_control_scale_bounds,  [0.0, 2.0],                :vequal?,   :st_vequal_2]
  ].each do |func, new_val, eq_func, leq_func|
    old_global_val = snd_func(func)
    old_vals = snd_func(func, true)
    old_1 = snd_func(func, ind_1)
    old_2 = snd_func(func, ind_2)
    sel_snd = selected_sound
    unsel_snd = sel_snd == ind_1 ? ind_2 : ind_1
    unless method(leq_func).call(old_vals, [old_1, old_2]) or
        method(leq_func).call(old_vals, [old_2, old_1])
      snd_display(snd_format_neq(old_vals, [old_1, old_2], "%s sound_func true", func))
    end
    # 
    set_snd_func(func, new_val)
    res1 = snd_func(func)
    res2 = snd_func(func, sel_snd)
    res3 = snd_func(func, unsel_snd)
    snd_test_any_neq(res1, new_val, eq_func, "set_%s global no arg", func)
    snd_test_any_neq(res1, res2, eq_func, "set_%s global no arg sel", func)
    snd_test_any_neq(res1, res3, eq_func, "set_%s global no arg unsel", func)
    res1 = snd_func(func, true)
    unless method(leq_func).call(res1, [res2, res3]) or method(leq_func).call(res1, [res3, res2])
      snd_display(snd_format_neq(res1, [res2, res3], "set_%s true", func))
    end
    set_snd_func(func, old_global_val)
    set_snd_func(func, new_val, ind_1)
    res1 = snd_func(func, true)
    res2 = snd_func(func, ind_1)
    res3 = snd_func(func, ind_2)
    snd_test_any_neq(res2, new_val, eq_func, "set_%s arg", func)
    snd_test_any_eq(res3, new_val, eq_func, "set_%s arg (2)", func)
    unless method(leq_func).call(res1, [res2, res3]) or method(leq_func).call(res1, [res3, res2])
      snd_display(snd_format_neq(res1, [res2, res3], "set_%s arg", func))
    end
    set_snd_func(func, old_1, ind_1)
    set_snd_func(func, new_val, true)
    res1 = snd_func(func, true)
    res2 = snd_func(func, ind_1)
    res3 = snd_func(func, ind_2)
    snd_test_any_neq(res1, [new_val, new_val], leq_func, "set_%s arg true", func)
    snd_test_any_neq(res2, new_val, eq_func, "set_%s arg true", func)
    snd_test_any_neq(res3, new_val, eq_func, "set_%s arg true (2)", func)
    res1 = snd_func(func)
    snd_test_any_eq(res1, new_val, eq_func, "set_%s overwrote global", func)
    set_snd_func(func, old_1, ind_1)
    set_snd_func(func, old_2, ind_2)
    res2 = snd_func(func, ind_1)
    res3 = snd_func(func, ind_2)
    snd_test_any_neq(res2, old_1, eq_func, "set_%s arg true old", func)
    snd_test_any_neq(res3, old_2, eq_func, "set_%s arg true old (2)", func)
  end
  close_sound(true)
end

def test_21_01
  file_copy("2a.snd", "test.snd")
  ind = open_sound("test.snd")
  clear_listener
  2500.times do |i|
    k = random(200) + 1
    s = case random(5)
        when 1
          4
        when 2
          8
        when 3
          12
        else
          0
        end
    if k > 127
      k = key_to_int(?x)
      s = 4
    end
    if random(1.0) > 0.99
      clear_listener
    end
    if k == key_to_int(?e) or k == key_to_int(?E)
      snd_simulate_keystroke(ind, 0, key_to_int(?g), 0)
    end
    snd_simulate_keystroke(ind, random(channels(ind)), k, s)
    if sound?(ind) and frames(ind, 0) > 1000000
      close_sound(ind)
      file_copy("2a.snd", "test.snd")
    end
    unless sound?(ind)
      ind = open_sound("test.snd")
    end
  end
  clear_listener
  close_sound(ind)
end

def test_21_02
  set_remember_sound_state(true)
  ind = open_sound("oboe.snd")
  set_transform_graph?(true, ind, 0)
  set_show_transform_peaks(true, ind, 0)
  set_show_y_zero(true, ind, 0)
  close_sound(ind)
  ind = open_sound("oboe.snd")
  res1 = transform_graph?(ind, 0)
  res2 = show_transform_peaks(ind, 0)
  res3 = show_y_zero(ind, 0)
  if (not res1.kind_of?(TrueClass)) or
      (not res2.kind_of?(TrueClass)) or
      (not res3.kind_of?(TrueClass))
    snd_display("remember_sound_state: %s %s %s?", res1.inspect, res2.inspect, res3.inspect)
  end
  close_sound(ind)
  reset_almost_all_hooks
  set_remember_sound_state(false)
  #
  map_sound_files do |n|
    if mus_sound_duration(n) > 1000.0
      snd_display("%s is pretty long! %s", n, mus_sound_duration(n))
    end
    mus_sound_forget(n)
  end
  map_sound_files($sf_dir) do |n|
    Snd.catch do
      if mus_sound_duration(n) > 1000.0
        snd_display("%s is pretty long! %s", n, mus_sound_duration(n))
      end
      mus_sound_forget(n)
    end
  end
  #
  snd = new_sound("test.snd")
  pad_channel(0, 20)
  map_channel($init_channel)
  env_channel_with_base([0, 0, 1, 1], 1.0)
  snd_test_neq(channel2vct(0, 20),
               vct(0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45,
                   0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95),
               "env_chan 1.0")
  undo_edit
  env_channel_with_base([0, 0, 1, 1, 2, 1, 3, 0], 0.0)
  snd_test_neq(channel2vct(0, 20),
               vct(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 
                   1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
               "env_chan 0.0")
  undo_edit
  env_channel_with_base([0, 0, 1, 1], 100.0)
  snd_test_neq(channel2vct(0, 20),
               vct(0.0, 0.003, 0.006, 0.010, 0.015, 0.022, 0.030, 0.041, 0.054, 0.070, 
                   0.091, 0.117, 0.150, 0.191, 0.244, 0.309, 0.392, 0.496, 0.627, 0.792),
               "env_chan 100.0")
  undo_edit
  env_channel_with_base([0, 0, 1, 1], 0.01)
  snd_test_neq(channel2vct(0, 20),
               vct(0.0, 0.208, 0.373, 0.504, 0.608, 0.691, 0.756, 0.809, 0.850, 0.883,
                   0.909, 0.930, 0.946, 0.959, 0.970, 0.978, 0.985, 0.990, 0.994, 0.997),
               "env_chan 0.01")
  undo_edit
  env_channel_with_base([0, 0, 1, 1], 1.0, 5, 10)
  snd_test_neq(channel2vct(0, 20),
               vct(1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.111, 0.222, 0.333, 0.444,
                   0.556, 0.667, 0.778, 0.889, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
               "env_chan 1.0 seg")
  undo_edit
  env_channel_with_base([0, 0, 1, 1, 2, 1, 3, 0], 0.0, 5, 10)
  snd_test_neq(channel2vct(0, 20),
               vct(1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
                   1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
               "env_chan 0.0 seg")
  undo_edit
  env_channel_with_base([0, 0, 1, 1], 100.0, 5, 10)
  snd_test_neq(channel2vct(0, 20),
               vct(1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.007, 0.018, 0.037, 0.068, 
                   0.120, 0.208, 0.353, 0.595, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
               "env_chan 100.0 seg")
  undo_edit
  env_channel_with_base([0, 0, 1, 1], 0.01, 5, 10)
  snd_test_neq(channel2vct(0, 20),
               vct(1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.405, 0.647, 0.792, 0.880, 
                   0.932, 0.963, 0.982, 0.993, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
               "env_chan 0.01 seg")
  undo_edit
  close_sound(snd)
  #
  ind1 = open_sound("now.snd")
  ind2 = open_sound("oboe.snd")
  [[:channel_mean,          5.02560673308833e-5],
   [:channel_total_energy,  50.7153476262465],
   [:channel_average_power, 0.00155078578803922],
   [:channel_rms,           0.039380017623653],
   [:channel_norm,          7.12147088923675],
   [:channel_variance,      50.7153476237207]].each do |func_sym, req|
    snd_test_neq(snd_func(func_sym, ind1, 0), req, "%s", func_sym)
  end
  [[2, 7.12147088923675],
   [1, 775.966033935547]].each do |arg, req|
    snd_test_neq(channel_lp(arg, ind1, 0), req, "channel_lp %d", arg)
  end
  snd_test_neq(channel2_inner_product(ind1, 0, ind2, 0), 1.52892031334341, "channel2_inner_product")
  snd_test_neq(channel2_angle(ind1, 0, ind2, 0), 1.55485084385627, "channel2_angle")
  snd_test_neq(channel2_orthogonal?(ind1, 0, ind2, 0), false, "channel2_orthogonal?")
  snd_test_neq(channel2_coefficient_of_projection(ind1, 0, ind2, 0),
               0.0301470932351876,
               "channel2_coefficient_of_projection")
  close_sound(ind1)
  ind1 = open_sound("oboe.snd")
  scale_by(0.99, ind1, 0)
  snd_test_neq(channel_distance(ind1, 0, ind2, 0), 0.1346, "channel_distance")
  [ind1, ind2].apply(:close_sound)
  #
  file_copy("oboe.snd", "test.snd")
  ind = open_sound("test.snd")
  mx = maxamp(ind, 0)
  chns = channels(ind)
  sr = srate(ind)
  fr = frames(ind, 0)
  with_local_hook($update_hook,
                  lambda do |orig_ind|
                    lambda do |new_ind|
                      ind = new_ind
                    end
                  end) do
    10.times do |i|
      v = channel2vct
      array2file("test.snd", v, fr, sr, chns)
      update_sound(ind)
      snd_test_neq(maxamp(ind, 0), mx, "update_sound looped maxamp %d", i)
      snd_test_neq(chans(ind), chns, "update_sound looped chans")
      snd_test_neq(srate(ind), sr, "update_sound looped srate")
      snd_test_neq(frames(ind), fr, "update_sound looped frames")
    end
    old_ind = open_sound("oboe.snd")
    diff = 0.0
    rd = make_sampler(0, ind, 0)
    home = sampler_home(rd)
    scan_channel(lambda do |y|
                   if (cd = (y - rd.call).abs) > diff
                     diff = cd
                   end
                   false
                 end, 0, fr, old_ind, 0)
    snd_test_neq(diff, 0.0, "update_sound looped overall max diff old_ind %s rd %s", old_ind, home)
    close_sound(old_ind)
  end
  close_sound(ind)
  # 
  delete_file("test.snd")
  ind = open_sound("oboe.snd")
  data = channel2vct
  5.times do |i|
    array2file("test.snd", data, frames(ind), 22050, 1)
    file2array("test.snd", 0, 0, frames, data)
    diff = 0.0
    ctr = 0
    scan_channel(lambda do |y|
                   if (cd = (y - data[ctr]).abs) > diff
                     diff = cd
                   end
                   ctr += 1
                   false
                 end)
    snd_test_neq(diff, 0.0, "array2file2array overall max diff")
  end
  #
  set_colormap_size(16)
  set_transform_size(8, ind, 0)
  set_transform_graph_type(Graph_as_sonogram, ind, 0)
  set_transform_graph?(true, ind, 0)
  update_transform_graph
  set_x_bounds([0.0, 0.04])
  update_time_graph
  update_transform_graph
  set_zoom_focus_style(lambda do |s, c, z, x0, x1, range| 0 end)
  unless proc?(res = zoom_focus_style)
    snd_display("zoom_focus_style as func: %s?", res)
  end
  set_zoom_focus_style(Zoom_focus_right)
  snd_test_neq(zoom_focus_style(), Zoom_focus_right, "unset zoom_focus_style as func")
  close_sound(ind)
  # 
  delete_files("test.snd", "fmv.snd")
  rdin = false
  rdout = false
  len = mus_sound_frames("oboe.snd")
  types = [Mus_riff, Mus_aifc, Mus_next, Mus_nist, Mus_ircam]
  forms = [Mus_lshort, Mus_bshort, Mus_b24int, Mus_l24int, Mus_bint]
  file_copy("oboe.snd", "fmv.snd")
  types.zip(forms) do |tp, fm|
    rdin = make_readin(:file, "fmv.snd")
    rdout = make_sample2file("test.snd", 1, fm, tp)
    len.times do |k| sample2file(rdout, k, 0, readin(rdin)) end
    rdout.close
    rdin.close
    file_copy("test.snd", "fmv.snd")
    delete_file("test.snd")
    ["test.snd", "fmv.snd"].apply(:mus_sound_forget)
  end
  diff = 0.0
  ctr = 0
  ind1 = open_sound("oboe.snd")
  ind2 = make_file2sample("fmv.snd")
  scan_channel(lambda do |y|
                 if (cd = (y - file2sample(ind2, ctr, 0)).abs) > diff
                   diff = cd
                 end
                 ctr += 1
                 false
               end)
  snd_test_neq(diff, 0.0, "file2sample2file overall max diff")
  close_sound(ind1)
  # 
  ind = open_sound("1a.snd")
  mx = maxamp()
  [["scale_by", lambda do
      scale_by(2.0)
    end],
   ["scale_channel", lambda do
      scale_channel(2.0)
    end],
   ["map_channel", lambda do
      map_channel(lambda do |y| y * 2.0 end)
    end],
   ["set_maxamp", lambda do
      set_maxamp(2 * maxamp)
    end],
   ["env_sound", lambda do
      env_sound([0, 2, 1, 2])
    end],
   ["env_channel", lambda do
      env_channel(make_env([0, 1, 1, 1], :scaler, 2.0, :length, frames))
    end],
   ["clm_channel", lambda do
      clm_channel(make_one_zero(:a0, 2.0, :a1, 0.0))
    end],
   ["filter_channel", lambda do
      filter_channel(vct(2.0), 1)
    end],
   ["vct2channel", lambda do
      vct2channel(channel2vct.scale(2.0), 0)
    end],
   ["mix_selection", lambda do
      select_all
      mix_selection(0)
    end],
   ["scale_selection_by", lambda do
      select_all
      scale_selection_by(2.0)
    end],
   ["mix", lambda do
      save_sound_as("temp.snd")
      mix("temp.snd", 0)
      delete_file("temp.snd")
    end],
   ["sound_data", lambda do
      sd = vct2sound_data(channel2vct())
      frames.times do |i|
        sd[0, i] *= 2.0
      end
      set_samples(0, frames, sd.to_vct)
    end],
   ["convolve", lambda do
      flt = Vct.new(8)
      flt[0] = 2.0
      cnv = make_convolve(:filter, flt)
      sf = make_sampler(0)
      map_channel(lambda do |y| convolve(cnv, lambda do |dir| read_sample(sf) end) end)
    end],
   ["fft", lambda do
      len = frames
      fsize = 2 ** (log(len) / log(2)).ceil
      rl = channel2vct(0, fsize)
      im = Vct.new(fsize)
      mus_fft(rl, im, fsize)
      mus_fft(rl, im, fsize)
      mus_fft(rl, im, fsize)
      mus_fft(rl, im, fsize)
      vct2channel(rl.scale(2.0 / (fsize * fsize)), 0, len)
    end],
   ["set_samples", lambda do
      set_squelch_update(true)
      100.times do |i|
        set_sample(i, 2.0 * sample(i))
      end
      set_squelch_update(false)
    end],
   ["coroutines", lambda do
      set_squelch_update(true)
      make_scaler = lambda do |start, dur|
        ctr = start
        us = lambda do |them|
          set_sample(ctr, 2.0 * sample(ctr))
          ctr += 2
          if ctr <= dur
            them.call(us)
          end
        end
        us
      end
      make_scaler.call(0, 100).call(make_scaler.call(1, 100))
      set_squelch_update(false)
    end]].each do |name, func|
    func.call
    next if name == "set_samples"
    next if name == "coroutines"
    snd_test_neq(maxamp() / mx, 2.0, "silly scalers %s", name)
    revert_sound
  end
  close_sound(ind)
end

def test_21
  test_21_00 if $with_test_gui
  test_21_01
  test_21_02
end

# ---------------- test 23: with-sound ----------------

require "clm-ins"

class Instrument
  # v.rb
  alias fm_violin fm_violin_rb
  alias jc_reverb jc_reverb_rb
  # clm-ins.rb
  alias nrev nrev_rb
end

def bigbird_2(start, dur, freq, freqskew, amp, freq_envelope, amp_envelope, partials)
  gls_env = make_env(:envelope, freq_envelope, :scaler, hz2radians(freqskew), :duration, dur)
  os = make_oscil(:frequency, freq)
  amp_env = make_env(:envelope, amp_envelope, :scaler, amp, :duration, dur)
  coeffs = partials2polynomial(normalize_partials(partials))
  run_instrument(start, dur) do
    env(amp_env) * polynomial(coeffs, oscil(os, env(gls_env)))
  end
end

def bird_2(start, dur, freq, freqskew, amp, freq_envelope, amp_envelope)
  gls_env = make_env(:envelope, freq_envelope, :scaler, hz2radians(freqskew), :duration, dur)
  os = make_oscil(:frequency, freq)
  amp_env = make_env(:envelope, amp_envelope, :scaler, amp, :duration, dur)
  run_instrument(start, dur) do
    env(amp_env) * oscil(os, env(gls_env))
  end
end

# scissor-tailed flycatcher
# 
# mix a scissor-tailed flycatcher call into the current sound
# see bird.rb for lots more birds

def scissor_2(start)
  bigbird_2(start, 0.05, 1800, 1800, 0.2,
            [0, 0, 40, 1, 60, 1, 100, 0], # scissor function
            [0, 0, 25, 1, 75, 1, 100, 0],
            [1, 0.5, 2, 1, 3, 0.5, 4, 0.1, 5, 0.01])
end

def bobwhite_2(beg)
  main_amp = [0.00, 0.00, 0.25, 1.00, 0.60, 0.70, 0.75, 1.00, 1.00, 0.0]
  bobup1 = [0.00, 0.00, 0.40, 1.00, 1.00, 1.0]
  bobup2 = [0.00, 0.00, 0.65, 0.50, 1.00, 1.0]
  bigbird_2(0.4, 0.2, 1800, 200, 0.1, bobup1, main_amp, [1, 1, 2, 0.02])
  bigbird_2(1, 0.20, 1800, 1200, 0.2, bobup2, main_amp, [1, 1, 2, 0.02])
end

class Sinc_train
  def initialize(freq, width)
    @range = if width
               width
             else
               PI * ((2 * (mus_srate() / (2.2 * freq)).floor) - 1)
             end
    @ang = -(@range * 0.5)
    @frq = (@range * freq) / mus_srate()
  end

  def sinc_train(fm)
    top = @range * 0.5
    val = (@ang.zero? ? 1.0 : (sin(@ang) / @ang))
    new_ang = @ang + @frq + fm
    @ang = ((new_ang > top) ? (new_ang - @range) : new_ang)
    val
  end
end

def make_sinc_train(freq = 440.0, width = false)
  Sinc_train.new(freq, width)
end

def sinc_train(gen, fm = 0.0)
  gen.sinc_train(fm)
end

def ws_sine(freq)
  os = make_oscil(freq)
  100.times do |i| outa(i, oscil(os), $output) end
end

def step_src
  rd = make_sampler(0)
  os = make_oscil(2205)
  sr = make_src(:srate, 0.0)
  incr = 2.0 + oscil(os)
  tempfile = with_sound(:output,  snd_tempnam,
                        :srate,   srate,
                        :comment, get_func_name) do
    samp = 0
    until sampler_at_end?(rd)
      out_any(samp, src(sr, incr, lambda do |dir| read_sample(rd) end), 0, $output)
      if (samp % 2205).zero?
        incr = 2.0 + oscil(os)
      end
      samp += 1
    end
  end.output
  len = mus_sound_frames(tempfile)
  set_samples(0, len - 1, tempfile, false, false, true, get_func_name, 0, false, true)
end

# optkey returns an array of values or, if array length is 1, returns
# the single value.  All examples below could be written without local
# variables:
# 
# def optkey_n(*args)
#   optkey(args, :a, ...)
# end
def optkey_1(*args)
  # single return value, no array returned
  a = optkey(args, :a)
  a
end

def optkey_2(*args)
  # local variables a and b will be set by optkey, thats why function
  # binding as second argument
  a, b = nil
  optkey(args, binding, [:a, 3], :b)
  [a, b]
end

def optkey_3(*args)
  a, b, c = nil
  optkey(args, binding, :a, :b, :c)
  [a, b, c]
end

def optkey_4(*args)
  a, b, c, d = nil
  optkey(args, binding, [:a, 1], [:b, 2], [:c, 3], :d)
  [a, b, c, d]
end

def test_23_00
  set_mus_srate($clm_srate = 22050)
  set_default_output_srate(22050)
  with_sound(:reverb, :nrev) do
    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]
    # 
    drone(0, 4, 115, 0.125, solid, bassdr2, 0.1, 0.5, 0.03, 45, 1, 0.01, 10)
    drone(0, 4, 229, 0.125, solid, tenordr, 0.1, 0.5, 0.03, 45, 1, 0.01, 11)
    drone(0, 4, 229.5, 0.125, solid, tenordr, 0.1, 0.5, 0.03, 45, 1, 0.01, 9)
    canter(0.000, 0.231, 918.000, 0.175, 45.0, 1, 0.05, ampf, ranf, skwf, 0.05, 0.01, 10, index,
           0.005, 0.005, amp1, ind1, fmt1, amp2, ind2, fmt2, amp3, ind3, fmt3, amp4, ind4, fmt4)
    canter(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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)
  end
  if sound?(ind = find_sound("test.snd"))
    close_sound(ind)
  else
    snd_display("missing test.snd (%s)", get_func_name)
  end
end

def test_23_01
  set_mus_srate($clm_srate = 22050)
  set_default_output_srate(22050)
  with_sound(:srate, 22050, :play, false) do
    fm_violin(0, 0.01, 440, 0.1, :noise_amount, 0.0)
    pluck(0.05, 0.01, 330, 0.1, 0.95, 0.95)
    maraca(0.1, 0.1)
    big_maraca(0.2, 0.5, 0.25, 0.95, 0.9985, 0.0312, [2300, 5600, 8100], [0.96, 0.995, 0.995], 0.01)
    fm_bell(0.3, 1.0, 220, 0.5,
            [0, 0, 0.1, 1, 10, 0.6, 25, 0.3, 50, 0.15, 90, 0.1, 100, 0],
            [0, 1, 2, 1.1, 25, 0.75, 75, 0.5, 100, 0.2],
            1.0)
    singer(0.4, 0.1, [[0.4, Ehh_shp, Test_glt, 523.0, 0.8, 0.0, 0.01],
                      [0.6, Oo_shp, Test_glt, 523.0, 0.7, 0.1, 0.01]])
    stereo_flute(0.6, 0.2, 440, 0.55, :flow_envelope, [0, 0, 1, 1, 2, 1, 3, 0])
    fofins(1, 0.3, 270, 0.4, 0.001, 730, 0.6, 1090, 0.3, 2440, 0.1)
    bow(1.2, 0.3, 400, 0.5, :vb, 0.15, :fb, 0.1, :inharm, 0.25)
    pqw_vox(1.5, 1, 300, 300, 0.1,
            [0, 0, 50, 1, 100, 0],
            [0, 0, 100, 0],
            0,
            [0, :L, 100, :L],
            [0.33, 0.33, 0.33],
            [[1, 1, 2, 0.5],
              [1, 0.5, 2, 0.5, 3, 1],
              [1, 1, 4, 0.5]])
    fm_noise(2, 0.5, 500, 0.25,
             [0, 0, 25, 1, 75, 1, 100, 0], 0.1, 0.1, 1000,
             [0, 0, 100, 1], 0.1, 0.1, 10, 1000,
             [0, 0, 100, 1], 0, 0, 100, 500,
             [0, 0, 100, 1], 0, 0)
    bes_fm(2.5, 0.5, 440, 5.0, 1.0, 8.0)
    chain_dsps(3, 0.5, [0, 0, 1, 0.1, 2, 0], make_oscil(440))
    chain_dsps(3.5, 1.0, [0, 0, 1, 1, 2, 0], make_one_zero(0.5), make_readin("oboe.snd"))
    vox(4, 2, 170, 0.4,
        [0, 0, 25, 1, 75, 1, 100, 0],
        [0, 0, 5, 0.5, 10, 0, 100, 1],
        0.1,
        [0, :E, 25, :AE, 35, :ER, 65, :ER, 75, :I, 100, :UH],
        0.05, 0.1)
    p(5.0, :duration, 0.5, :keyNum, 36, :strike_velocity, 0.5, :amp, 0.4,
      :dryPedalResonanceFactor, 0.25)
    bobwhite_2(5.5)
    scissor_2(2.0)
    plucky(3.25, 0.3, 440, 0.2, 1.0)
    bowstr(3.75, 0.3, 220, 0.2, 1.0)
    brass(4.2, 0.3, 440, 0.2, 1.0)
    clarinet(5.75, 0.3, 440, 0.2, 1.0)
    flute(6, 0.3, 440, 0.2, 1.0)
    fm_trumpet(6.5, 0.25)
    touch_tone(6.75, [7, 2, 3, 4, 9, 7, 1])
    pins(7.0, 1.0, "now.snd", 1.0, :time_scaler, 2.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]
    fm_insect(7.000, 1.699, 4142.627, 0.015, amp, 60, -16.707, locust, 500.866, bug_hi, 0.346, 0.5)
    fm_insect(7.195, 0.233, 4126.284, 0.030, amp, 60, -12.142, locust, 649.490, bug_hi, 0.407, 0.5)
    fm_insect(7.217, 2.057, 3930.258, 0.045, amp, 60,  -3.011, locust, 562.087, bug_hi, 0.591, 0.5)
    fm_insect(9.100, 1.500,  900.627, 0.060, amp, 40, -16.707, locust, 300.866, bug_hi, 0.346, 0.5)
    fm_insect(10.00, 1.500,  900.627, 0.060, amp, 40, -16.707, locust, 300.866, bug_hi, 0.046, 0.5)
    fm_insect(10.45, 1.500,  900.627, 0.090, amp, 40, -16.707, locust, 300.866, bug_hi, 0.006, 0.5)
    fm_insect(10.95, 1.500,  900.627, 0.120, amp, 40, -10.707, locust, 300.866, bug_hi, 0.346, 0.5)
    fm_insect(11.30, 1.500,  900.627, 0.090, amp, 40, -20.707, locust, 300.866, bug_hi, 0.246, 0.5)
    # 
    fm_drum(7.5, 1.5, 55, 0.3, 5, false)
    fm_drum(8, 1.5, 66, 0.3, 4, true)
    gong(9, 3, 261.61, 0.6)
    attract(10, 0.25, 0.5, 2.0)
    pqw(11, 0.5, 200, 1000, 0.2, [0, 0, 25, 1, 100, 0], [0, 1, 100, 0], [2, 0.1, 3, 0.3, 6, 0.5])
    #
    zn(10, 1, 100, 0.1, 20, 100, 0.995)
    zn(11.5, 1, 100, 0.1, 100, 20, 0.995)
    zc(11, 1, 100, 0.1, 20, 100, 0.95)
    zc(12.5, 1, 100, 0.1, 100, 20, 0.95)
    za(13, 1, 100, 0.1, 20, 100, 0.95, 0.95)
    za(14.5, 1, 100, 0.1, 100, 20, 0.95, 0.95)
    #
    tubebell(12, 2, 440, 0.2)
    wurley(12.5, 0.25, 440, 0.2)
    rhodey(12.75, 0.25, 440, 0.2)
    hammondoid(13, 0.25, 440, 0.2)
    metal(13.5, 0.25, 440, 0.2)
    reson(14.0, 1.0, 440, 0.1, 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,
          [[[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]])
    cellon(14.5, 1, 220, 0.1,
           [0, 0, 25, 1, 75, 1, 100, 0],
           [0, 0, 25, 1, 75, 1, 100, 0], 0.75, 1.0, 0, 0, 0, 0, 1, 0, 0, 220,
           [0, 0, 25, 1, 75, 1, 100, 0], 0, 0, 0, 0,
           [0, 0, 100, 0], 0, 0, 0, 0, [0, 0, 100, 0])
    scratch(15.0, "now.snd", 1.5, [0.0, 0.5, 0.25, 1.0])
    two_tab(15, 1, 440, 0.1)
    exp_snd("fyow.snd", 15, 3, 1, [0, 1, 1, 3], 0.4, 0.15, [0, 2, 1, 0.5], 0.05)
    exp_snd("oboe.snd", 16, 3, 1, [0, 1, 1, 3], 0.4, 0.15, [0, 2, 1, 0.5], 0.2)
    gran_synth(15.5, 1, 300, 0.0189, 0.03, 0.4)
    spectra(16, 1, 440, 0.1,
            [1, 0.4, 2, 0.2, 3, 0.2, 4, 0.1, 6, 0.1],
            [0, 0, 1, 1, 5, 0.9, 12, 0.5, 25, 0.25, 100, 0])
    lbj_piano(16.5, 1, 440, 0.2)
    resflt(17, 1, 0, 0, 0, false, 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)
    resflt(17.5, 1, 1, 10000, 0.01, [0, 0, 50, 1, 100, 0], 0, 0, 0, 0,
           false, false,
           500, 0.995, 0.1, 1000, 0.995, 0.1, 2000, 0.995, 0.1)
    bes_fm(18, 1, 440, 10.0, 1.0, 4.0)
    graph_eq("oboe.snd")
  end
  if sound?(ind = find_sound("test.snd"))
    close_sound(ind)
  else
    snd_display("missing test.snd (%s)", get_func_name)
  end
end

def test_23_02
  set_mus_srate($clm_srate = 22050)
  set_default_output_srate(22050)
  [Mus_bshort,
   Mus_lshort,
   Mus_mulaw,
   Mus_alaw,
   Mus_byte,
   Mus_lfloat,
   Mus_bint,
   Mus_lint,
   Mus_b24int,
   Mus_l24int,
   Mus_ubshort,
   Mus_ulshort,
   Mus_ubyte,
   Mus_bfloat,
   Mus_bdouble,
   Mus_ldouble].each do |type|
    ind = find_sound(with_sound(:data_format, type) do
                       fm_violin(0, 0.1, 440, 0.1)
                       fm_violin(10, 0.1, 440, 0.1)
                       fm_violin(100, 0.1, 440, 0.1)
                       fm_violin(1000, 0.1, 440, 0.1)
                     end.output)
    snd_test_any_neq(maxamp(ind), 0.1, :ffequal?, "format %s", mus_data_format2string(type)) # okay
  end
  3.times do |i|
    with_sound(:srate, 22050) do
      fm_violin(0, 0.1, 110.0 * (1.0 + i), 0.1)
    end
    ind = find_sound("test.snd")
    unless ind
      snd_display("with_sound: %s?", Snd.sounds.map do |s| file_name(s) end)
    end
    if fneq(res = maxamp, 0.1)
      snd_display("with_sound max (%s): %s?", i, res)
    end
    if srate(ind) != 22050
      snd_display("with_sound srate: %s (%s %s)?",
                  srate(ind), mus_srate, mus_sound_srate("test.snd"))
    end
    if frames(ind) != 2205
      snd_display("with_sound frames (%s): %s?", i, frames(ind))
    end
  end
  with_sound(:continue_old_file, true) do
    fm_violin(0.2, 0.1, 440, 0.25)
  end
  ind = find_sound("test.snd")
  unless ind
    snd_display("with_sound continued: %s?", file_name(true))
  end
  if Snd.sounds.length != 1
    snd_display("with_sound continued: %s?", short_file_name(true))
  end
  if fneq(res = maxamp, 0.25)
    snd_display("with_sound continued max: %s?", res)
  end
  if srate(ind) != 22050
    snd_display("with_sound continued srate: %s (%s %s)?",
                srate(ind), mus_srate, mus_sound_srate("test.snd"))
  end
  if frames(ind) != 3 * 2205
    snd_display("with_sound continued frames: %s?", frames(ind))
  end
  close_sound(ind)
  # 
  with_sound do
    fm_violin(0, 0.1, 440, 0.1)
  end
  with_sound(:continue_old_file, true) do
    fm_violin(0.2, 0.1, 660, 0.04)
  end
  ind = find_sound("test.snd")
  if fneq(res = maxamp, 0.1)
    snd_display("maxamp after continued sound: %s?", res)
  end
  if fneq(res = frames(ind) / srate(ind).to_f, 0.3)
    snd_display("duration after continued sound: %s?", res)
  end
  close_sound(ind)
  # 
  with_sound(:srate,    22050,
             :channels, 2,
             :output,   "test1.snd") do
    fm_violin(0, 0.1, 440, 0.1, :degree, 45.0)
  end
  ind = find_sound("test1.snd")
  unless ind
    snd_display("with_sound (1): %s?", file_name(true))
  end
  if fneq(res = maxamp, 0.05)
    snd_display("with_sound max (1): %s?", res)
  end
  if srate(ind) != 22050 or mus_sound_srate("test1.snd") != 22050
    snd_display("with_sound srate (1): %s (%s %s)?",
                srate(ind), mus_srate, mus_sound_srate("test1.snd"))
  end
  if frames(ind) != 2205
    snd_display("with_sound frames (1): %s?", frames(ind))
  end
  if chans(ind) != 2 or mus_sound_chans("test1.snd") != 2
    snd_display("with_sound chans (1): %s?", chans(ind))
  end
  close_sound(ind)
  delete_file("test1.snd")
  #
  with_sound(:srate,       48000,
             :channels,    2,
             :header_type, Mus_riff,
             :data_format, Mus_lshort,
             :output,      "test1.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  ind = find_sound("test1.snd")
  if srate(ind) != 48000 or mus_sound_srate("test1.snd") != 48000
    snd_display("with_sound srate (48000, r): %s (%s, %s)?",
                srate(ind),
                mus_srate,
                mus_sound_srate("test1.snd"))
  end
  if (res = header_type(ind)) != Mus_riff
    snd_display("with_sound type (%s, r): %s?", Mus_riff, res)
  end
  if (res = chans(ind)) != 2
    snd_display("with_sound chans (2, r): %s?", res)
  end
  close_sound(ind)
  delete_file("test1.snd")
  # 
  with_sound(:srate,       48000,
             :channels,    2,
             :header_type, Mus_caff,
             :data_format, Mus_lshort,
             :output,      "test1.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  ind = find_sound("test1.snd")
  if srate(ind) != 48000 or mus_sound_srate("test1.snd") != 48000
    snd_display("with_sound srate (48000, r): %s (%s, %s)?",
                srate(ind),
                mus_srate,
                mus_sound_srate("test1.snd"))
  end
  if (res = header_type(ind)) != Mus_caff
    snd_display("with_sound type (%s, r): %s?", Mus_caff, res)
  end
  if (res = channels(ind)) != 2
    snd_display("with_sound Mus_caff chans (2, r): %s?", res)
  end
  close_sound(ind)
  delete_file("test1.snd")
  # 
  with_sound(:srate,       8000,
             :channels,    3,
             :header_type, Mus_next,
             :output,      "test1.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  ind = find_sound("test1.snd")
  if srate(ind) != 8000 or mus_sound_srate("test1.snd") != 8000
    snd_display("with_sound srate (8000, s): %s (%s, %s)?",
                srate(ind),
                mus_srate,
                mus_sound_srate("test1.snd"))
  end
  if (res = header_type(ind)) != Mus_next
    snd_display("with_sound type (%s, s): %s?", Mus_next, res)
  end
  if (res = channels(ind)) != 3
    snd_display("with_sound chans (3, s): %s?", res)
  end
  close_sound(ind)
  delete_file("test1.snd")
  #
  my_srate = 96000
  with_sound(:srate,       my_srate,
             :channels,    4,
             :header_type, Mus_aifc,
             :output,      "test1.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  ind = find_sound("test1.snd")
  if srate(ind) != my_srate or mus_sound_srate("test1.snd") != my_srate
    snd_display("with_sound srate (%s, t): %s (%s, %s)?",
                my_srate,
                srate(ind),
                mus_srate,
                mus_sound_srate("test1.snd"))
  end
  if (res = header_type(ind)) != Mus_aifc
    snd_display("with_sound type (%s, t): %s?", Mus_aifc, res)
  end
  if (res = channels(ind)) != 4
    snd_display("with_sound chans (4, t): %s?", res)
  end
  close_sound(ind)
  delete_file("test1.snd")
  with_sound(:srate,       22050,
             :channels,    1,
             :header_type, Mus_raw,
             :output,      "test1.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  ind = find_sound("test1.snd")
  if srate(ind) != 22050 or mus_sound_srate("test1.snd") != 22050
    snd_display("with_sound srate (22050, u: %s (%s, %s)?",
                srate(ind),
                mus_srate,
                mus_sound_srate("test1.snd"))
  end
  if (res = header_type(ind)) != Mus_raw
    snd_display("with_sound type (%s, u): %s?", Mus_raw, res)
  end
  if (res = channels(ind)) != 1
    snd_display("with_sound chans (1, u): %s?", res)
  end
  close_sound(ind)
  delete_file("test1.snd")
  # 
  with_sound(:srate,    22050,
             :channels, 2,
             :output,   "test1.snd",
             :reverb,   :jc_reverb) do
    if (res = mus_sound_srate(mus_file_name($output))) != 22050
      snd_display("srate file $output: %s?", res)
    end
    if (res = mus_sound_srate(mus_file_name($reverb))) != 22050
      snd_display("srate file $reverb: %s?", res)
    end
    fm_violin(0, 0.1, 440, 0.1, :degree, 45.0)
  end
  if sound?(ind = find_sound("test1.snd"))
    if frames(ind) != 22050 + 2205
      snd_display("with_sound reverbed frames (2): %s?", frames(ind))
    end
    close_sound(ind)
  else
    snd_display("with_sound (2): %s?", file_name(true))
  end
  # 
  3.times do
    with_sound(:srate,  22050,
               :output, "test1.snd",
               :reverb, :jc_reverb) do
      fm_violin(0, 0.1, 440, 0.1)
    end
  end
  ind = find_sound("test1.snd")
  unless ind
    snd_display("with_sound (3): %s?", file_name(true))
  end
  if frames(ind) != 22050 + 2205
    snd_display("with_sound reverbed frames (3): %s?", frames(ind))
  end
  close_sound(ind)
  delete_file("test1.snd")
  # 
  with_sound(:srate,     22050,
             :comment,   "Snd+Run!",
             :scaled_to, 0.5) do
    fm_violin(0, 0.1, 440, 0.1)
  end
  ind = find_sound("test.snd")
  unless ind
    snd_display("with_sound: %s?", file_name(true))
  end
  if fneq(res = maxamp(ind), 0.5)
    snd_display("with_sound scaled_to: %s?", res)
  end
  if (res = comment(ind)) != "Snd+Run!"
    snd_display("with_sound comment: %s (%s)?", res, mus_sound_comment("test.snd"))
  end
  close_sound(ind)
  # 
  with_sound(:srate,       22050,
             :scaled_by,   0.5,
             :header_type, Mus_aifc,
             :data_format, Mus_bfloat) do
    fm_violin(0, 0.1, 440, 0.1)
  end
  ind = find_sound("test.snd")
  unless ind
    snd_display("with_sound: %s?", file_name(true))
  end
  if fneq(res = maxamp(ind), 0.05)
    snd_display("with_sound scaled_by: %s?", res)
  end
  if (res = header_type(ind)) != Mus_aifc
    snd_display("with_sound type: %s (%s)?", res, mus_header_type_name(res))
  end
  if (res = data_format(ind)) != Mus_bfloat
    snd_display("with_sound format: %s (%s)?", res, mus_data_format_name(res))
  end
  close_sound(ind)
  # 
  $open_raw_sound_hook.add_hook!("with-sound") do |file, choice| [1, 22050, Mus_bshort] end
  with_sound(:header_type, Mus_raw) do
    fm_violin(0, 0.1, 440, 0.1)
  end
  $open_raw_sound_hook.remove_hook!("with-sound")
  unless sound?(ind = find_sound("test.snd"))
    snd_display("with_sound (raw out): %s?", file_name(true))
  end
  if (res = header_type(ind)) != Mus_raw
    snd_display("with_sound type raw: %s (%s)?", res, mus_header_type_name(res))
  end
  if (res = data_format(ind)) != Mus_bshort and res != Mus_bfloat and res != Mus_lfloat
    snd_display("with_sound format raw: %s (%s)?", res, mus_data_format_name(res))
  end
  close_sound(ind)
  # 
  with_sound(:srate, 44100) do
    ws_sine(1000)
  end
  ind = find_sound("test.snd")
  i = -1
  scan_channel(lambda { |y|
                 i += 1
                 if fneq(y, res = sin(TWO_PI * i * (1000.0 / 44100.0)))
                   snd_display("with_sound sine: %s %s %s", i, y, res)
                   true
                 else
                   false
                 end
               })
  close_sound(ind)
  # 
  if File.exists?("ii.rb")
    with_time("load(\"ii.rb\")") do load("ii.rb") end
    Snd.sounds.apply(:close_sound)
    delete_files("test.snd", "rest.reverb")
  end
  set_mus_srate($clm_srate = 22050)
  set_default_output_srate(22050)
  #
  outer = with_sound do
    sound_let(Proc.new do fm_violin(0, 0.1, 440, 0.1) end) do |a|
      mus_mix(@output, a)
    end
  end.output
  unless string?(outer)
    snd_display("with_sound returns: %s?", outer)
  end
  ind = find_sound(outer)
  if (not sound?(ind)) or frames(ind) != (mus_srate * 0.1).floor
    snd_display("sound_let: %s %s?", frames(ind), (mus_srate * 0.1).floor)
  end
  close_sound(ind)
  delete_file("test.snd")
  #  
  outer = with_sound do
    sound_let(Proc.new do fm_violin(0, 0.1, 440, 0.1) end,
              100) do |a, b|
      mus_mix(@output, a, b)
      sound_let([:channels, 1, :output, "temp.snd",
                 Proc.new do fm_violin(0, 0.1, 110, 0.1) end]) do |c|
        mus_mix(@output, c)
      end
    end
  end.output
  unless string?(outer)
    snd_display("with_sound (2) returns: %s?", outer)
  end
  ind = find_sound(outer)
  if (not sound?(ind)) or frames(ind) != (res = 100 + (mus_srate * 0.1).floor)
    snd_display("sound_let (2): %s %s?", frames(ind), res)
  end
  if File.exists?("temp.snd")
    snd_display("sound_let explicit output exists?")
  end
  close_sound(ind)
end

def test_23_03
  set_mus_srate($clm_srate = 22050)
  set_default_output_srate(22050)
  with_sound(:channels, 2) do
    fullmix("pistol.snd")
    fullmix("oboe.snd", 1, 2, 0, [[0.1, make_env([0, 0, 1, 1], :duration, 2, :scaler, 0.5)]])
  end
  if sound?(ind = find_sound("test.snd"))
    close_sound(ind)
  end
  with_sound(:channels, 2) do
    fullmix("4.aiff", 0.0, 0.1, 36.4, [[0.0, 0.0], [0.0, 0.0], [1.0, 0.0], [0.0, 1.0]])
  end
  if sound?(ind = find_sound("test.snd"))
    snd_test_neq(maxamp(), 0.664947509765625, "4->2(0) fullmix")
    close_sound(ind)
  end
  with_sound(:channels, 1) do
    fullmix("4.aiff", 0.0, 0.1, 36.4, [[1.0], [0.0], [0.0], [0.0]])
  end
  if sound?(ind = find_sound("test.snd"))
    snd_test_neq(maxamp(), 0.221649169921875, "4->1(0) fullmix")
    close_sound(ind)
  end
  with_sound(:channels, 1) do
    fullmix("4.aiff", 0.0, 0.1, 36.4, [[0.0], [1.0], [0.0], [0.0]])
  end
  if sound?(ind = find_sound("test.snd"))
    snd_test_neq(maxamp(), 0.44329833984375, "4->1(1) fullmix")
    close_sound(ind)
  end
  with_sound(:channels, 1) do
    fullmix("4.aiff", 0.0, 0.1, 36.4, [[0.0], [0.0], [1.0], [0.0]])
  end
  if sound?(ind = find_sound("test.snd"))
    snd_test_neq(maxamp(), 0.664947509765625, "4->1(2) fullmix")
    close_sound(ind)
  end
  with_sound(:channels, 1) do
    fullmix("4.aiff", 0.0, 0.1, 36.4, [[0.0], [0.0], [0.0], [1.0]])
  end
  if sound?(ind = find_sound("test.snd"))
    snd_test_neq(maxamp(), 0.8865966796875, "4->1(3) fullmix")
    close_sound(ind)
  end
  with_sound(:channels, 2) do
    fullmix("4.aiff", 0.0, 0.1, 36.4, [[0.0, 0.0], [0.0, 0.0], [1.0, 0.0], [0.0, 1.0]])
  end
  if sound?(ind = find_sound("test.snd"))
    mxs = maxamp(ind, true)
    snd_test_neq(mxs[0], 0.664947509765625, "4->2(1) fullmix")
    snd_test_neq(mxs[1], 0.8865966796875, "4->2(1) fullmix")
    close_sound(ind)
  end
  with_sound(:channels, 2) do
    fullmix("4.aiff", 0.0, 0.1, 36.4, [[0.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 0.0]])
  end
  if sound?(ind = find_sound("test.snd"))
    req1 = 0.664947509765625
    req2 = 0.8865966796875
    mxs = maxamp(ind, true)
    snd_test_neq(mxs[0], 0.8865966796875, "4->2(2) fullmix")
    snd_test_neq(mxs[1], 0.664947509765625, "4->2(2) fullmix")
    close_sound(ind)
  end
  with_sound(:channels, 2, :reverb, :nrev) do
    fullmix("pistol.snd", 0.0, 2.0, 0.25, nil, 2.0, 0.1)
    fullmix("pistol.snd", 1.0, 2.0, 0.25, 0.2, 2.0, 0.1)
    fullmix("2a.snd", nil, nil, nil, [[0.5, 0.0], [0.0, 0.75]])
    fullmix("oboe.snd", nil, nil, nil, [[[0, 0, 1, 1, 2, 0], 0.5]])
    fullmix("oboe.snd", 3, 2, 0, [[0.1, make_env([0, 0, 1, 1], :duration, 2, :scaler, 0.5)]])
  end
  Snd.sounds.apply(:close_sound)
end

def test_23_04
  set_mus_srate($clm_srate = 22050)
  set_default_output_srate(22050)
  with_sound(:srate, 22050) do
    sound_let(Proc.new do
                fm_violin(0, 1, 440, 0.1)
              end,
              Proc.new do
                fm_violin(0, 2, 660, 0.1, :base, 32.0)
                fm_violin(0.125, 0.5, 880, 0.1)
              end) do |temp_1, temp_2|
      mus_mix(@output, temp_1, 0)
      mus_mix(@output, temp_2, 22050)
    end
  end
  if sound?(ind = find_sound("test.snd"))
    unless maxamp(ind).between?(0.15, 0.2)
      snd_display("with_sound+sound_lets maxamp: %s?", maxamp(ind))
    end
    if fneq(res = frames(ind) / srate(ind).to_f, 3.0)
      snd_display("with_sound+sound_lets dur: res %s frms %s sr %s?", res, frames(ind), srate(ind))
    end
    close_sound(ind)
  else
    snd_display("with_sound+sound_lets init: no test.snd?")
  end
 # 
  with_sound(:srate, 44100) do
    bigbird_2(0, 2.0, 60, 0, 0.5,
              [0, 0, 1, 1],
              [0, 0, 1, 1, 2, 1, 3, 0],
              [1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1])
  end
  ind = (find_sound("test.snd") or open_sound("test.snd"))
  mx = maxamp
  freqs = []
  60.step(3000, 60) do |i| freqs.push(i) end
  notch_sound(freqs)
  snd_test_neq(mx, 0.5, "notch_sound 60 Hz 1a")
  snd_test_any_neq(maxamp(), 0.027, :ffequal?, "notch_sound 60 Hz 1b") # okay
  undo_edit
  notch_sound(freqs, false, ind, 0, 10)
  snd_test_neq(maxamp(), 0.004, "notch_sound 60 Hz 2")
  undo_edit
  notch_channel(freqs, false, false, false, ind, 0, false, false, 10)
  snd_test_neq(maxamp(), 0.004, "notch_channel 60 Hz 2")
  undo_edit
  make_selection(10000, 11000)
  notch_selection(freqs, false)
  play_sound do |data| data.map!(0) do |val| val * 2.0 end end
  close_sound(ind)
  #
  with_sound(:srate, 44100) do
    bigbird_2(0, 60.0, 60, 0, 0.5,
              [0, 0, 1, 1],
              [0, 0, 1, 1, 2, 1, 3, 0],
              [1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1])
  end
  ind = find_sound("test.snd")
  freqs = []
  60.step(3000, 60) do |i| freqs.push(i) end
  notch_sound(freqs, false, ind, 0, 10)
  snd_test_neq(maxamp(), 0.036, "notch_sound 60 Hz 2 60")
  close_sound(ind)
  # 
  play_sine(440, 0.1)
  play_sines([[425, 0.05], [450, 0.01], [470, 0.01], [546, 0.02],
              [667, 0.01], [789, 0.034], [910, 0.032]])
  #
  ind = open_sound("oboe.snd")
  with_sound(:output, "test1.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  set_samples(0, 2205, "test1.snd", ind, 0, false, "set_samples auto-delete test", 0, false, true)
  unless File.exists?("test1.snd")
    snd_display("oops: auto-delete test1.snd?")
  end
  undo_edit(1, ind)
  with_sound(:output, "test2.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  insert_sound("test2.snd", 0, 0, ind, 0, false, true)
  if File.exists?("test1.snd")
    snd_display("auto-delete set_samples?")
  end
  undo_edit(1, ind)
  with_sound(:output, "test3.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  insert_samples(0, 2205, "test3.snd", ind, 0, false, true)
  if File.exists?("test2.snd")
    snd_display("auto-delete insert_sound?")
  end
  undo_edit(1, ind)
  with_sound(:output, "test4.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  mix("test4.snd", 0, 0, ind, 0, false, true)
  if File.exists?("test3.snd")
    snd_display("auto-delete insert_samples?")
  end
  undo_edit(1, ind)
  delete_sample(100, ind, 0)
  if File.exists?("test4.snd")
    snd_display("auto-delete mix?")
  end
  with_sound(:output, "test5.snd") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  mix("test5.snd", 0, 0, ind, 0, true, true)
  revert_sound(ind)
  close_sound(ind)
  if File.exists?("test5.snd")
    snd_display("auto-delete mix (with-tag)?")
  end
  Snd.sounds.apply(:close_sound)
  # 
  if (res = optkey_1(1))     != 1
    snd_display("optkey_1: %s?", res)
  end
  if (res = optkey_1(:a, 1)) != 1
    snd_display("optkey_1 1: %s?", res)
  end
  if (res = optkey_1)        != nil
    snd_display("optkey_1 2: %s?", res)
  end
  # 
  if (res = optkey_2(1, 2))         != [1, 2]
    snd_display("optkey_2: %s?", res)
  end
  if (res = optkey_2(:a, 1, :b, 2)) != [1, 2]
    snd_display("optkey_2 1: %s?", res)
  end
  if (res = optkey_2)               != [3, nil]
    snd_display("optkey_2 2: %s?", res)
  end
  if (res = optkey_2(1, :b, 2))     != [1, 2]
    snd_display("optkey_2 3: %s?", res)
  end
  # 
  if (res = optkey_3(1, 2, 3))         != [1, 2, 3]
    snd_display("optkey_3: %s?", res)
  end
  if (res = optkey_3(1, :b, 2, :c, 3)) != [1, 2, 3]
    snd_display("optkey_3 1: %s?", res)
  end
  if (res = optkey_3(1, 2, :c, 3))     != [1, 2, 3]
    snd_display("optkey_3 2: %s?", res)
  end
  # 
  if (res = optkey_4) != [1, 2, 3, nil]
    snd_display("optkey_4: %s?", res)
  end
  if (res = optkey_4(1, :b, 3, :c, 4, :d, 5)) != [1, 3, 4, 5]
    snd_display("optkey_4 1: %s?", res)
  end
  if (res = optkey_4(1, :d, 5, :c, 4, :b, 3)) != [1, 3, 4, 5]
    snd_display("optkey_4 2: %s 1?", res)
  end
  if (res = optkey_4(1, 3, 4, 5)) != [1, 3, 4, 5]
    snd_display("optkey_4 3: %s 2?", res)
  end
  # 
  if defined? variable_display
    wid1 = Snd.catch do make_variable_display("do-loop-1", "i*1", :text) end.first
    wid2 = Snd.catch do make_variable_display("do-loop-2", "i*2", :scale, [-1.0, 1.0]) end.first
    wid3 = Snd.catch do make_variable_display("do-loop-3", "i3", :spectrum) end.first
    wid4 = Snd.catch do make_variable_display("do-loop-4", "i4", :graph) end.first
    if variable_display?(wid1) and
        variable_display?(wid2) and
        variable_display?(wid3) and
        variable_display?(wid4)
      1000.times do |i|
        variable_display(wid4,
                         variable_display(wid3,
                                          variable_display(wid2,
                                                           sin(variable_display(wid1, 1) * 0.1)) * \
                                          0.5))
      end
      tag = Snd.catch do set_sample(0, 0.5, wid3.snd, 0) end
      if (res = edit_position(wid3.snd, 0)) > 0
        snd_display("edited variable graph: %s %s?", tag, res)
      end
      [wid1, wid2, wid3, wid4].each do |w|
        variable_display_close(w)
      end
    else
      [wid1, wid2, wid3, wid4].each_with_index do |w, i|
        if variable_display?(w)
          variable_display_close(w)
        else
          snd_display("variable_display(do-loop-%d): %s", i, w.inspect)
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  [[:clm_srate,           default_output_srate],
   [:clm_channels,        default_output_chans],
   [:clm_header_type,     default_output_header_type],
   [:clm_data_format,     default_output_data_format],
   [:clm_reverb_channels, 1],
   [:clm_file_name,       "test.snd"],
   [:clm_play,            false],
   [:clm_verbose,         false],
   [:clm_statistics,      false],
   [:clm_reverb,          nil],
   [:clm_delete_reverb,   false],
   [:clm_reverb_data,     []]].each do |sym, req|
    snd_test_neq(eval("$" + sym.to_s), req, "$%s", sym.to_s)
  end
  # 
  $clm_channels      = 2
  $clm_srate         = 44100
  $clm_file_name     = "test.wav"
  $clm_verbose       = false
  $clm_statistics    = false
  $clm_play          = false
  $clm_data_format   = Mus_mulaw
  $clm_header_type   = Mus_riff
  $clm_delete_reverb = true
  $clm_reverb        = :jc_reverb
  $clm_reverb_data   = [:low_pass, true, :volume, 2.0, :amp_env, [0, 1, 3, 1, 4, 0]]
  with_sound(:comment, "fm_violin(:reverb_amount, 0.1)") do 
    fm_violin(0, 1, 440, 0.1, :reverb_amount, 0.1)
  end
  unless sound?(ind = find_sound("test.wav"))
    snd_display("default output in ws: %s?", file_name(true))
  else
    if (res = srate(ind)) != 44100
      snd_display("default srate in ws: %s %s?", res, $clm_srate)
    end
    if (res = channels(ind)) != 2
      snd_display("default channels in ws: %s %s?", res, $clm_channels)
    end
    if (res = data_format(ind)) != Mus_mulaw
      snd_display("default format in ws: %s %s?", res, $clm_data_format)
    end
    if (res = header_type(ind)) != Mus_riff
      snd_display("default type in ws: %s %s?", res, $clm_header_type)
    end
    if (res = frames(ind)) != 88200
      snd_display("reverb+1 sec out in ws: %s?", res)
    end
    if File.exists?("test.reverb")
      snd_display("perhaps reverb not deleted in ws?")
    end
    close_sound(ind)
  end
  #
  val = 0
  old_hook = $clm_notehook
  $clm_notehook = lambda do |*args| val = 1 end
  with_sound(:comment, "$clm_notehook") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  if val != 1
    snd_display("$clm_notehook (1): %s %s?", val, $clm_notehook.inspect)
  end
  with_sound(:notehook, lambda do |*args| val=2 end, :comment, ":notehook") do
    fm_violin(0, 0.1, 440, 0.1) 
  end
  if val != 2
    snd_display(":notehook: %s?", val)
  end
  with_sound(:comment, "$clm_notehook (2)") do
    fm_violin(0, 0.1, 440, 0.1)
  end
  if val != 1
    snd_display("$clm_notehook (2): %s %s?", val, $clm_notehook.inspect)
  end
  $clm_notehook = old_hook
  # 
  $clm_channels      = 1
  $clm_srate         = 22050
  $clm_file_name     = "test.snd"
  $clm_verbose       = false
  $clm_statistics    = false
  $clm_play          = false
  $clm_data_format   = Mus_lfloat
  $clm_header_type   = Mus_next
  $clm_delete_reverb = false
  $clm_reverb        = nil
  $clm_reverb_data   = []
  # 
  with_sound(:reverb, :jl_reverb) do
    attract(0, 1, 0.1, 2.0)
    expfil(0, 2, 0.2, 0.01, 0.1, "oboe.snd", "fyow.snd")
    fm_violin(0, 0.1, 660, 0.1, :reverb_amount, 0.1)
    anoi("oboe.snd", 1, 1)
    ind = open_sound("oboe.snd")
    ind1 = open_sound("now.snd")
    zp = make_zipper(make_env(:envelope, [0, 0, 1, 1], :length, 22050),
                     0.05,
                     make_env(:envelope, [0, mus_srate() * 0.05], :length, 22050))
    reader0 = make_sampler(0, ind, 0)
    reader1 = make_sampler(0, ind1, 0)
    22050.times do |i| outa(i, zipper(zp, reader0, reader1), $output) end
    [ind, ind1].apply(:close_sound)
  end
  if sound?(ind = find_sound("test.snd"))
    select_sound(ind)
    zip_sound(1, 1, "fyow.snd", "now.snd", [0, 0, 1, 1], 0.05)
    zip_sound(2, 3, "mb.snd", "fyow.snd", [0, 0, 1, 0, 1.5, 1, 3, 1], 0.025)
    close_sound(ind)
  else
    snd_display("zip_sound: no test.snd?")
  end
  # 
  ind = open_sound("oboe.snd")
  pv = make_pvocoder(256, 4, 64)
  rd = make_sampler(0)
  map_channel(lambda do |y| pvocoder(pv, rd) end)
  close_sound(ind)
  # 
  make_birds
  if $with_test_nogui
    puts
  end
  Snd.sounds.apply(:close_sound)
  # 
  # clm23.scm tests skipped
  #
  file = with_sound(:clipped,     false,
                    :data_format, Mus_bfloat,
                    :header_type, Mus_next) do
    fm_violin(0, 0.1, 440, PI)
  end.output
  ind = find_sound(file)
  if fneq(res = maxamp(ind), PI)
    snd_display("clipped false: %s?", res)
  end
  close_sound(ind)
  file = with_sound(:clipped,     true,
                    :data_format, Mus_bfloat,
                    :header_type, Mus_next) do
    fm_violin(0, 0.1, 440, PI)
  end.output
  ind = find_sound(file)
  if fneq(res = maxamp(ind), 1.0)
    snd_display("clipped true: %s?", res)
  end
  close_sound(ind)
  file = with_sound(:clipped,     false,
                    :data_format, Mus_bfloat,
                    :header_type, Mus_next,
                    :scaled_by,   0.1) do
    fm_violin(0, 0.1, 440, PI)
  end.output
  ind = find_sound(file)
  if fneq(res = maxamp(ind), 0.314159)
    snd_display("scaled_by: %s?", res)
  end
  close_sound(ind)
  file = with_sound(:clipped,     false,
                    :data_format, Mus_bfloat,
                    :header_type, Mus_next,
                    :scaled_to,   0.1) do
    fm_violin(0, 0.1, 440, PI)
  end.output
  ind = find_sound(file)
  if fneq(res = maxamp(ind), 0.1)
    snd_display("scaled_to: %s?", res)
  end
  close_sound(ind)
  # 
  old_bufsize = $clm_file_buffer_size
  old_tsize = $clm_table_size
  old_arrp = $clm_array_print_length
  $clm_file_buffer_size = 1024 * 1024
  $clm_table_size = 256
  $clm_array_print_length = 123
  tsize = 0
  arrp = 0
  mx = 0
  file = with_sound(:data_format, Mus_bfloat,
                    :header_type, Mus_next) do
    mx = mus_file_buffer_size
    tsize = clm_table_size
    arrp = mus_array_print_length
    fm_violin(0, 0.1, 440, 0.1)
  end.output
  ind = find_sound(file)
  if mx != 1024 * 1024
    snd_display("$clm_file_buffer_size: %s?", mx)
  end
  if tsize != 256
    snd_display("$clm_table_size: %s?", tsize)
  end
  if arrp != 123
    snd_display("$clm_array_print_length: %s?", arrp)
  end
  $clm_file_buffer_size = old_bufsize
  $clm_table_size = old_tsize
  $clm_array_print_length = old_arrp
  close_sound(ind)
  #
  # FIXME (bug in ws.rb (with_sound))
  ind = find_sound(with_sound do
                     fm_violin(0, 3, 440, 0.1)
                   end.output)
  if $with_test_gui
    set_amp_control(0.5, ind)
    set_x_bounds([1.0, 2.0], ind, 0)
  end
  ind = find_sound(with_sound do
                     fm_violin(0, 4, 440, 0.1)
                   end.output)
  if $with_test_gui
    if fneq(res = amp_control(ind), 0.5)
      snd_display("update ws amp: %s?", res)
    end
    res = x_bounds(ind, 0)
    if (fneq(res[0], 1.0) or fneq(res[1], 2.0))
      snd_display("update ws bounds: %s?", res)
    end
  end
  close_sound(ind)
  # 
  file = with_sound(:reverb, :jc_reverb) do
    fm_violin(0, 0.1, 440, 0.1, :reverb_amount, 0.1)
  end.output
  ind = find_sound(file)
  mx = maxamp(ind)
  file = with_sound(:reverb, :jc_reverb,
                    :reverb_data, [:volume, 12.0, :amp_env, [0, 0, 1, 1, 20, 1, 21, 0]]) do
    fm_violin(0, 4, 440, 0.1, :reverb_amount, 0.1)
  end.output
  ind = find_sound(file)
  if maxamp(ind) <= mx
    snd_display("reverb_data: %s %s?", mx, maxamp(ind))
  end
  close_sound(ind)
  #
  ind = open_sound("oboe.snd")
  step_src
  if (frames - 24602).abs > 100
    snd_display("step_src frames: %s (%s)?", frames, edits)
  end
  close_sound(ind)
  Snd.sounds.apply(:close_sound)
  #
  file = with_sound do
    gen = make_sinc_train(440.0, 9 * PI)
    1102.times do |i| outa(i, sinc_train(gen), $output) end
  end.output
  ind = find_sound(file)
  if sound?(ind)
    if fneq(res = maxamp(ind), 1.0)
      snd_display("with_sound sinc_train max: %s?", res)
    end
    close_sound(ind)
  else
    snd_display("with_sound let -> %s (%s)?", ind, file)
  end
end

require "strad"
require "noise"
require "piano"
require "maraca"
require "play"
require "prc95"
require "singer"
require "zip"

def test_23
  $clm_verbose    = false
  $clm_statistics = false
  $clm_play       = false
  $clm_info       = $DEBUG
  test_23_00
  test_23_01
  test_23_02
  test_23_03
  test_23_04
end

# ---------------- test 28: errors ----------------

def check_error_tag(expected_tag, &thunk)
  if (tag = Snd.catch do thunk.call end).first != expected_tag
    snd_display_prev_caller("%s %s: %s", get_func_name, expected_tag.inspect, tag.inspect)
  end
end

Procs = [:add_mark, :add_sound_file_extension, :add_source_file_extension, :sound_file_extensions,
         :sound_file?, :add_to_main_menu, :add_to_menu, :add_transform, :amp_control,
         :ask_about_unsaved_edits, :as_one_edit, :ask_before_overwrite, :audio_input_device,
         :audio_output_device, :auto_resize, :auto_update, :autocorrelate, :axis_color, :axis_info,
         :axis_label_font, :axis_numbers_font, :basic_color, :bind_key, :bomb, :apply_controls,
         :change_samples_with_origin, :channel_style, :channel_widgets, :channels, :chans,
         :peaks_font, :bold_peaks_font, :close_sound, :combined_data_color, :color_cutoff,
         :color_orientation_dialog, :colormap_ref, :add_colormap,
         :delete_colormap, :colormap_size, :colormap_name, :color_inverted, :color_scale,
         :color2list, :colormap, :color?, :comment, :contrast_control, :contrast_control_amp,
         :channel_properties, :channel_property, :controls2channel, :amp_control_bounds,
         :speed_control_bounds, :expand_control_bounds, :contrast_control_bounds,
         :sound_file_extensions, :reverb_control_length_bounds, :reverb_control_scale_bounds,
         :cursor_update_interval, :cursor_location_offset, :auto_update_interval,
         :count_matches, :current_font, :cursor, :cursor_color, :with_tracking_cursor,
         :cursor_size, :cursor_style, :tracking_cursor_style, :dac_combines_channels,
         :dac_size, :clipping, :data_color, :data_format, :data_location, :data_size,
         :default_output_chans, :default_output_data_format, :default_output_srate,
         :default_output_header_type, :insert_file_dialog, :file_write_date, :define_envelope,
         :delete_mark, :delete_marks, :forget_region, :delete_sample, :delete_samples,
         :delete_samples_and_smooth, :delete_selection, :delete_selection_and_smooth,
         :dialog_widgets, :display_edits, :dot_size, :draw_dot,
         :draw_dots, :draw_line, :draw_lines, :draw_string, :edit_header_dialog,
         :edit_fragment, :edit_list2function, :edit_position, :edit_tree, :edits,
         :env_selection, :env_sound, :enved_envelope, :enved_base, :enved_clip?,
         :enved_in_dB, :enved_dialog, :enved_style, :enved_power, :enved_target,
         :enved_waveform_color, :enved_wave?, :eps_file, :eps_left_margin, :eps_bottom_margin,
         :eps_size, :expand_control, :expand_control_hop, :expand_control_jitter,
         :expand_control_length, :expand_control_ramp, :expand_control?, :fft,
         :fft_window_beta, :fft_window_alpha, :fft_with_phases, :fft_log_frequency,
         :fft_log_magnitude, :transform_size, :disk_kspace, :transform_graph_type,
         :fft_window, :transform_graph?, :view_files_dialog, :mix_file_dialog, :file_name,
         :fill_polygon, :fill_rectangle, :filter_sound, :filter_control_in_dB,
         :filter_control_envelope, :enved_filter_order, :enved_filter, :filter_control_in_hz,
         :filter_control_order, :filter_selection, :filter_channel,
         :filter_control_waveform_color, :filter_control?, :find_channel, :find_mark,
         :find_sound, :finish_progress_report, :foreground_color, :frames, :free_sampler,
         :graph, :transform?, :delete_transform,
         :graph_color, :graph_cursor, :graph_data, :graph2ps, :gl_graph2ps, :graph_style,
         :lisp_graph?, :graphs_horizontal, :header_type, :help_dialog, :info_dialog,
         :highlight_color, :call_in, :insert_region, :insert_sample, :insert_samples,
         :insert_samples_with_origin, :insert_selection, :insert_silence, :insert_sound,
         :just_sounds, :key, :key_binding, :left_sample, :listener_color, :listener_font,
         :listener_prompt, :listener_selection, :listener_text_color, :main_widgets,
         :make_color, :make_graph_data, :make_mix_sampler, :make_player, :make_region,
         :make_region_sampler, :make_sampler, :map_chan, :mark_color, :mark_name,
         :mark_properties, :mark_property, :mark_sample, :mark_sync, :mark_sync_max,
         :mark_home, :marks, :mark?, :max_transform_peaks, :max_regions,
         :max_virtual_ptrees, :maxamp, :maxamp_position, :menu_widgets,
         :minibuffer_history_length, :min_dB, :log_freq_start, :mix, :mixes, :mix_amp,
         :mix_amp_env, :mix_color, :mix_length, :mix?, :view_mixes_dialog, :mix_position,
         :mix_dialog_mix, :mix_name, :mix_sync_max, :mix_sync, :mix_properties, :mix_property,
         :mix_region, :mix_sampler?, :mix_selection, :mix_home, :mix_speed, :mix_tag_height,
         :mix_tag_width, :mark_tag_height, :mark_tag_width, :mix_tag_y, :mix_vct,
         :mix_waveform_height, :time_graph_style, :lisp_graph_style, :transform_graph_style,
         :read_mix_sample, :next_sample, :read_region_sample,
         :show_full_duration, :show_full_range, :initial_beg, :initial_dur, :transform_normalization,
         :open_file_dialog_directory, :open_raw_sound, :open_sound, :color_orientation_dialog,
         :previous_sample, :peaks, :player?, :players, :play_arrow_size, :position_color,
         :position2x, :position2y, :add_directory_to_view_files_list, :add_file_to_view_files_list,
         :view_files_amp, :view_files_speed, :view_files_files, :view_files_selected_files,
         :view_files_speed_style, :view_files_amp_env, :view_files_sort,
         :print_length, :progress_report, :prompt_in_minibuffer, :read_only,
         :redo_edit, :region_chans, :view_regions_dialog, :region_home,
         :region_graph_style, :region_frames, :region_position, :region_maxamp,
         :region_maxamp_position, :remember_sound_state, :selection_maxamp,
         :selection_maxamp_position, :region_sample, :region2vct, :region_srate, :regions, :region?,
         :remove_from_menu, :report_in_minibuffer, :reset_controls, :restore_controls,
         :restore_region, :reverb_control_decay, :reverb_control_feedback,
         :reverb_control_length, :reverb_control_lowpass, :reverb_control_scale,
         :reverb_control?, :reverse_sound, :reverse_selection, :revert_sound,
         :right_sample, :sample, :sampler_at_end?, :sampler?, :samples, :sampler_position,
         :sash_color, :save_controls, :ladspa_dir, :peak_env_dir, :save_dir,
         :save_edit_history, :save_envelopes, :save_listener, :save_marks, :save_region,
         :save_selection, :save_sound, :save_sound_as, :save_state, :save_state_file,
         :scale_by, :scale_selection_by, :scale_selection_to, :scale_to, :scan_chan,
         :search_procedure, :select_all, :select_channel, :select_sound, :selected_channel,
         :selected_data_color, :selected_graph_color, :selected_sound, :selection_position,
         :selection_color, :selection_creates_region, :selection_frames, :selection_member?,
         :selection?, :short_file_name, :show_axes, :show_controls, :show_transform_peaks,
         :show_indices, :show_listener, :show_selection, :unselect_all, :show_marks,
         :show_mix_waveforms, :show_selection_transform, :show_y_zero, :sinc_width, :show_grid,
         :show_sonogram_cursor, :grid_density, :smooth_sound, :smooth_selection, :snd_spectrum,
         :snd_tempnam, :snd_version, :sound_files_in_directory, :sound_loop_info, :sound_widgets,
         :soundfont_info, :sound?, :sounds, :spectrum_end, :spectro_hop, :spectrum_start,
         :spectro_x_angle, :spectro_x_scale, :spectro_y_angle, :spectro_y_scale, :spectro_z_angle,
         :spectro_z_scale, :speed_control, :speed_control_style, :speed_control_tones,
         :squelch_update, :srate, :src_sound, :src_selection, :start_progress_report,
         :stop_player, :stop_playing, :swap_channels, :syncd_marks, :sync, :sync_max,
         :sound_properties, :sound_property, :temp_dir, :text_focus_color, :tiny_font,
         :region_sampler?, :transform_dialog, :transform_sample, :transform2vct,
         :transform_frames, :transform_type, :trap_segfault, :with_file_monitor,
         :unbind_key, :update_transform_graph, :update_time_graph, :update_lisp_graph,
         :update_sound, :clm_table_size, :with_verbose_cursor, :view_sound, :wavelet_type,
         :with_inset_graph, :with_interrupts, :with_pointer_focus, :with_smpte_label,
         :with_toolbar, :with_tooltips, :with_menu_icons, :save_as_dialog_src,
         :save_as_dialog_auto_comment, :time_graph?, :time_graph_type, :wavo_hop,
         :wavo_trace, :window_height, :window_width, :window_x, :window_y, :with_mix_tags,
         :with_relative_panes, :with_gl, :x_axis_style, :beats_per_measure, :beats_per_minute,
         :x_bounds, :x_position_slider, :x2position, :x_zoom_slider, :mus_header_type2string,
         :mus_data_format2string, :y_bounds, :y_position_slider, :y2position, :y_zoom_slider,
         :zero_pad, :zoom_color, :zoom_focus_style, :sync_style,
         :mus_set_formant_radius_and_frequency, :mus_sound_samples, :mus_sound_frames,
         :mus_sound_duration, :mus_sound_datum_size, :mus_sound_data_location, :data_size,
         :mus_sound_chans, :mus_sound_srate, :mus_sound_header_type, :mus_sound_data_format,
         :mus_sound_length, :mus_sound_type_specifier, :mus_header_type_name,
         :mus_data_format_name, :mus_sound_comment, :mus_sound_write_date, :mus_bytes_per_sample,
         :mus_sound_loop_info, :mus_sound_mark_info, :mus_audio_describe,
         :mus_sound_maxamp, :mus_sound_maxamp_exists?, :mus_file_prescaler,
         :mus_prescaler, :mus_clipping, :mus_file_clipping, :mus_header_raw_defaults,
         :moving_average, :moving_average?, :make_moving_average, :mus_expand_filename,
         :make_sound_data, :sound_data_ref, :sound_data_set!, :sound_data_scale!,
         :sound_data_fill!, :sound_data?, :sound_data_length, :sound_data_multiply!,
         :sound_data_add!, :sound_data_offset!, :sound_data_copy, :sound_data_reverse!,
         :sound_data_maxamp, :sound_data_chans, :sound_data2vct, :vct2sound_data,
         :sound_data_peak, :all_pass, :all_pass?, :amplitude_modulate, :array2file,
         :array_interp, :mus_interpolate, :asymmetric_fm, :asymmetric_fm?,
         :sound_data2sound_data, :clear_array, :comb, :comb?, :filtered_comb,
         :filtered_comb?, :contrast_enhancement, :convolution, :convolve, :convolve?,
         :db2linear, :degrees2radians, :delay, :delay?, :dot_product, :env, :env_interp,
         :env?, :file2array, :file2frame, :file2frame?, :file2sample, :file2sample?,
         :filter, :filter?, :fir_filter, :fir_filter?, :formant, :formant_bank, :formant?,
         :frame_multiply, :frame_add, :frame2file, :clear_minibuffer, :frame2file?,
         :frame2frame, :frame2list, :frame2sample, :frame_ref, :frame_set!, :frame?,
         :granulate, :granulate?, :hz2radians, :iir_filter, :iir_filter?, :in_any, :ina, :inb,
         :linear2db, :locsig, :locsig_ref, :locsig_reverb_ref, :locsig_reverb_set!, :locsig_set!,
         :locsig?, :make_all_pass, :make_asymmetric_fm, :make_comb, :make_convolve, :make_delay,
         :make_env, :make_fft_window, :make_file2frame, :make_file2sample, :make_filter,
         :make_fir_filter, :make_formant, :make_frame, :make_frame2file, :make_granulate,
         :make_iir_filter, :make_locsig, :move_locsig, :make_mixer, :make_notch,
         :make_one_pole, :make_one_zero, :make_oscil, :make_pulse_train, :make_rand,
         :make_rand_interp, :make_readin, :make_sample2file, :make_sawtooth_wave,
         :make_square_wave, :make_src, :make_ssb_am, :make_table_lookup, :make_triangle_wave,
         :make_two_pole, :make_two_zero, :make_wave_train, :mixer_multiply, :mixer_ref,
         :mixer_set!, :mixer?, :mixer_add, :move_sound, :make_move_sound, :move_sound?,
         :mus_float_equal_fudge_factor, :multiply_arrays, :mus_array_print_length, :mus_channel,
         :mus_channels, :make_polyshape, :polyshape, :polyshape?, :mus_close, :mus_data,
         :mus_feedback, :mus_feedforward, :mus_fft, :mus_frequency, :mus_hop, :mus_increment,
         :mus_input?, :mus_file_name, :mus_length, :mus_location, :mus_mix, :mus_order,
         :mus_output?, :mus_phase, :mus_ramp, :mus_random, :mus_scaler, :mus_srate,
         :mus_xcoeffs, :mus_ycoeffs, :notch, :notch?, :one_pole, :one_pole?, :one_zero,
         :one_zero?, :oscil, :oscil?, :out_any, :outa, :outb, :outc, :outd, :partials2polynomial,
         :partials2wave, :phase_partials2wave, :polynomial, :pulse_train, :pulse_train?,
         :radians2degrees, :radians2hz, :rand, :rand_interp, :rand_interp?, :rand?, :readin,
         :readin?, :rectangular2polar, :rectangular2magnitudes, :ring_modulate, :sample2file,
         :sample2file?, :sample2frame, :sawtooth_wave, :sawtooth_wave?, :spectrum, :square_wave,
         :square_wave?, :src, :src?, :ssb_am, :ssb_am?, :table_lookup, :table_lookup?, :tap,
         :triangle_wave, :triangle_wave?, :two_pole, :two_pole?, :two_zero, :two_zero?,
         :wave_train, :wave_train?, :make_vct, :vct_add!, :vct_subtract!, :vct_copy,
         :vct_length, :vct_multiply!, :vct_offset!, :vct_ref, :vct_scale!, :vct_fill!,
         :vct_set!, :vct_peak, :vct?, :list2vct, :vct2list, :vector2vct, :vct2vector,
         :vct_move!, :vct_reverse!, :vct_subseq, :vct, :little_endian?, :vct2string,
         :clm_channel, :env_channel, :map_channel, :scan_channel, :reverse_channel,
         :seconds2samples, :samples2seconds, :vct2channel, :smooth_channel, :channel2vct,
         :src_channel, :scale_channel, :ramp_channel, :pad_channel, :normalize_channel,
         :cursor_position, :mus_sound_prune, :mus_sound_forget, :xramp_channel, :ptree_channel,
         :snd2sample, :snd2sample?, :make_snd2sample, :make_scalar_mixer, :beats_per_minute,
         :beats_per_measure, :channel_amp_envs, :convolve_files, :filter_control_coeffs,
         :locsig_type, :make_phase_vocoder, :mus_describe, :mus_error_type2string,
         :mus_file_buffer_size, :mus_name, :mus_offset, :mus_reset, :mus_rand_seed,
         :mus_width, :phase_vocoder?, :polar2rectangular, :phase_vocoder_amp_increments,
         :phase_vocoder_amps, :phase_vocoder_freqs, :phase_vocoder_phase_increments,
         :phase_vocoder_phases, :mus_generator?, :read_sample, :reset_listener_cursor,
         :goto_listener_end, :sampler_home, :selection_chans, :selection_srate, :snd_gcs,
         :snd_font, :snd_color, :snd_warning, :channel_data, :x_axis_label, :variable_graph?,
         :y_axis_label, :snd_url, :snd_urls, :free_player, :delay_tick, :playing, :draw_axes,
         :copy_sampler, :html_dir, :html_program, :make_fir_coeffs, :mus_interp_type, :mus_run,
         :phase_vocoder, :player_home, :redo_edit, :undo_edit, :widget_position, :widget_size,
         :focus_widget]

Set_procs = [:amp_control, :ask_before_overwrite, :audio_input_device, :audio_output_device,
             :auto_resize, :sound_file_extensions, :auto_update, :axis_color, :axis_label_font,
             :axis_numbers_font, :channel_style, :peaks_font, :bold_peaks_font,
             :show_full_duration, :show_full_range, :initial_beg, :initial_dur, :color_cutoff,
             :color_inverted, :color_scale, :contrast_control, :contrast_control_amp,
             :combined_data_color, :amp_control_bounds, :speed_control_bounds,
             :expand_control_bounds, :contrast_control_bounds, :reverb_control_length_bounds,
             :reverb_control_scale_bounds, :cursor_update_interval, :cursor_location_offset,
             :contrast_control?, :auto_update_interval, :current_font, :cursor, :cursor_color,
             :channel_properties, :channel_property, :with_tracking_cursor, :cursor_size,
             :cursor_style, :tracking_cursor_style, :dac_combines_channels, :dac_size,
             :clipping, :data_color, :default_output_chans, :default_output_data_format,
             :default_output_srate, :default_output_header_type, :dot_size, :enved_envelope,
             :enved_base, :enved_clip?, :enved_in_dB, :enved_style, :enved_power,
             :enved_target, :enved_waveform_color, :enved_wave?, :eps_file, :eps_left_margin,
             :eps_bottom_margin, :eps_size, :expand_control, :expand_control_hop,
             :expand_control_jitter, :expand_control_length, :expand_control_ramp,
             :expand_control?, :fft_window_beta, :fft_window_alpha, :fft_with_phases,
             :fft_log_frequency, :fft_log_magnitude, :transform_size, :transform_graph_type,
             :fft_window, :transform_graph?, :filter_control_in_dB, :filter_control_envelope,
             :axis_color, :enved_filter_order, :enved_filter, :filter_control_in_hz,
             :filter_control_order, :filter_control_waveform_color, :filter_control?,
             :foreground_color, :graph_color, :graph_cursor, :graph_style, :lisp_graph?,
             :graphs_horizontal, :highlight_color, :just_sounds, :left_sample, :listener_color,
             :listener_font, :listener_prompt, :listener_text_color, :mark_color, :mark_name,
             :mark_properties, :mark_property, :mark_sample, :mark_sync, :max_transform_peaks,
             :max_regions, :min_dB, :log_freq_start, :mix_amp, :mix_amp_env, :mix_color,
             :mix_name, :mix_position, :mix_sync, :mix_properties, :mix_property,
             :max_virtual_ptrees, :mix_speed, :mix_tag_height, :mix_tag_width, :mix_tag_y,
             :mark_tag_width, :mark_tag_height, :mix_waveform_height, :transform_normalization,
             :open_file_dialog_directory, :position_color, :view_files_sort, :print_length,
             :play_arrow_size, :region_graph_style, :reverb_control_decay, :reverb_control_feedback,
             :reverb_control_length, :reverb_control_lowpass, :reverb_control_scale,
             :time_graph_style, :lisp_graph_style, :transform_graph_style, :reverb_control?,
             :sash_color, :ladspa_dir, :peak_env_dir, :save_dir, :save_state_file,
             :selected_data_color, :selected_graph_color, :selection_color,
             :selection_creates_region, :show_axes, :show_controls, :show_transform_peaks,
             :show_indices, :show_marks, :show_mix_waveforms, :show_selection_transform,
             :show_listener, :show_y_zero, :show_grid, :show_sonogram_cursor, :sinc_width,
             :spectrum_end, :spectro_hop, :spectrum_start, :spectro_x_angle, :grid_density,
             :spectro_x_scale, :spectro_y_angle, :spectro_y_scale, :spectro_z_angle,
             :spectro_z_scale, :speed_control, :speed_control_style, :speed_control_tones,
             :squelch_update, :sync, :sound_properties, :sound_property, :temp_dir,
             :text_focus_color, :tiny_font, :y_bounds, :transform_type, :trap_segfault,
             :with_file_monitor, :with_verbose_cursor, :with_inset_graph, :with_interrupts,
             :with_pointer_focus, :wavelet_type, :x_bounds, :with_smpte_label, :with_toolbar,
             :with_tooltips, :with_menu_icons, :save_as_dialog_src, :save_as_dialog_auto_comment,
             :time_graph?, :wavo_hop, :wavo_trace, :with_gl, :with_mix_tags, :x_axis_style,
             :beats_per_minute, :zero_pad, :zoom_color, :zoom_focus_style, :sync_style,
             :with_relative_panes, :window_x, :window_y,
             :window_width, :window_height, :mix_dialog_mix, :beats_per_measure,
             :channels, :chans, :colormap, :comment, :data_format, :data_location,
             :data_size, :edit_position, :frames, :header_type, :maxamp,
             :minibuffer_history_length, :read_only, :right_sample, :sample, :samples,
             :selected_channel, :colormap_size, :selected_sound, :selection_position,
             :selection_frames, :selection_member?, :sound_loop_info, :srate, :time_graph_type,
             :x_position_slider, :x_zoom_slider, :y_position_slider, :y_zoom_slider,
             :sound_data_ref, :mus_array_print_length, :mus_float_equal_fudge_factor,
             :mus_feedback, :mus_feedforward, :mus_frequency, :mus_hop,
             :mus_increment, :mus_length, :mus_location, :mus_name, :mus_phase, :mus_ramp,
             :mus_scaler, :x_axis_label, :locsig_type, :mus_file_buffer_size,
             :mus_rand_seed, :mus_width, :clm_table_size, :mus_offset, :html_dir,
             :html_program, :widget_position, :widget_size, :mus_file_prescaler,
             :mus_prescaler, :mus_clipping, :mus_header_raw_defaults, :view_files_amp,
             :view_files_speed, :view_files_files, :view_files_selected_files,
             :view_files_speed_style, :view_files_amp_env]

Make_procs = [:make_all_pass, :make_asymmetric_fm, :make_snd2sample, :make_moving_average,
              :make_comb, :make_filtered_comb, :make_convolve, :make_delay, :make_env,
              :make_fft_window, :make_file2frame, :make_file2sample, :make_filter,
              :make_fir_filter, :make_formant, :make_frame, :make_frame2file, :make_granulate,
              :make_iir_filter, :make_locsig, :make_mixer, :make_notch, :make_one_pole,
              :make_one_zero, :make_oscil, :make_pulse_train, :make_rand, :make_rand_interp,
              :make_readin, :make_sample2file, :make_sawtooth_wave, :make_square_wave,
              :make_src, :make_table_lookup, :make_triangle_wave, :make_two_pole,
              :make_two_zero, :make_wave_train, :make_phase_vocoder, :make_ssb_am,
              :make_polyshape, :make_color, :make_player, :make_region, :make_scalar_mixer]

Keyargs = [:frequency, :initial_phase, :wave, :cosines, :amplitude, :ratio, :size,
           :a0, :a1, :a2, :b1, :b2, :input, :srate, :file, :channel, :start, :initial_contents,
           :initial_element, :scaler, :feedforward, :feedback, :max_size, :radius, :gain,
           :partials, :r, :a, :n, :fill_time, :order, :xcoeffs, :ycoeffs, :envelope, :base,
           :duration, :offset, :end, :direction, :degree, :distance, :reverb, :output, :fft_size,
           :expansion, :length, :hop, :ramp, :jitter, :type, :format, :comment, :channels, :filter,
           :revout, :width, :edit, :synthesize, :analyze, :interp, :overlap, :pitch, :distribution,
           :sines, :dur]

class Array
  # If body results in true, returns the rejected elements in a new
  # array and remove these elements from the original.
  def remove_if!
    ret = []
    self.map! do |x|
      if yield(x)
        ret.push(x)
        nil
      else
        x
      end
    end
    self.compact!
    ret
  end
end

Procs00  = Procs.remove_if! do |n| function?(n) and arity_ok(n, 0) end
Set_procs00 = Set_procs.remove_if! do |n| function?(n) and set_arity_ok(n, 1) end
Procs01  = Procs.remove_if! do |n| function?(n) and arity_ok(n, 1) end
Set_procs01 = Set_procs.remove_if! do |n| function?(n) and set_arity_ok(n, 2) end
Procs02  = Procs.remove_if! do |n| function?(n) and arity_ok(n, 2) end
Set_procs02 = Set_procs.remove_if! do |n| function?(n) and set_arity_ok(n, 3) end
Procs03  = Procs.remove_if! do |n| function?(n) and arity_ok(n, 3) end
Set_procs03 = Set_procs.remove_if! do |n| function?(n) and set_arity_ok(n, 4) end
Procs04  = Procs.remove_if! do |n| function?(n) and arity_ok(n, 4) end
Set_procs04 = Set_procs.remove_if! do |n| function?(n) and set_arity_ok(n, 5) end
Procs05  = Procs.remove_if! do |n| function?(n) and arity_ok(n, 5) end
Procs06  = Procs.remove_if! do |n| function?(n) and arity_ok(n, 6) end
Procs07  = Procs.remove_if! do |n| function?(n) and arity_ok(n, 7) end # not used
Procs08  = Procs.remove_if! do |n| function?(n) and arity_ok(n, 8) end
Procs10 = Procs.remove_if! do |n| function?(n) and arity_ok(n, 10) end

$delay_32 = make_oscil(440)
$color_95 = [1, 2, 3]
$vector_0 = make_comb(0.1, 3)
$vct_3    = make_vct(3)

def test_28_00
  procs1 =
    [:amp_control, :apply_controls, :comment,
     :contrast_control, :amp_control_bounds, :speed_control_bounds, :expand_control_bounds,
     :contrast_control_bounds, :reverb_control_length_bounds, :reverb_control_scale_bounds,
     :contrast_control_amp, :contrast_control?, :data_format, :data_location, :data_size,
     :expand_control, :expand_control_hop, :expand_control_jitter, :expand_control_length,
     :expand_control_ramp, :expand_control?, :filter_control_in_dB,
     :filter_control_in_hz, :filter_control_envelope, :filter_control_order, :filter_control?,
     :finish_progress_report, :frames, :header_type, :progress_report, :read_only,
     :reset_controls, :restore_controls, :reverb_control_decay, :reverb_control_feedback,
     :reverb_control_length, :reverb_control_lowpass, :reverb_control_scale, :reverb_control?,
     :save_controls, :select_sound, :short_file_name, :sound_loop_info, :soundfont_info,
     :speed_control, :speed_control_style, :speed_control_tones, :srate, :channel_style,
     :start_progress_report, :sync, :sound_properties, :sound_property, :swap_channels]
  procs1.each do |n|
    if (tag = Snd.catch do snd_func(n, integer2sound(123)) end).first != :no_such_sound
      snd_display("snd :no_such_sound %s: %s", n, tag)
    end
  end
  [sqrt(-1.0), 1.5, "hiho"].each do |arg|
    procs1.each do |n|
      next if n == :progress_report
      tag = Snd.catch do snd_func(n, arg) end
      if tag.first != :wrong_type_arg and
          tag.first != :mus_error and
          tag.first != :no_such_sound
        snd_display("snd :wrong_type_arg %s: %s %s", n, tag, arg)
      end
    end
  end
  progs2 =
    [:amp_control, :channels, :chans, :comment,
     :contrast_control, :amp_control_bounds, :speed_control_bounds, :expand_control_bounds,
     :contrast_control_bounds, :reverb_control_length_bounds, :reverb_control_scale_bounds,
     :contrast_control_amp, :contrast_control?, :data_format, :data_location, :data_size,
     :expand_control, :expand_control_hop, :expand_control_jitter, :expand_control_length,
     :expand_control_ramp, :expand_control?, :filter_control_in_dB,
     :filter_control_in_hz, :filter_control_envelope, :filter_control_order, :filter_control?,
     :frames, :header_type, :read_only,
     :reverb_control_decay, :reverb_control_feedback,
     :reverb_control_length, :reverb_control_lowpass, :reverb_control_scale, :reverb_control?,
     :sound_loop_info,
     :speed_control, :speed_control_style, :speed_control_tones, :srate, :channel_style, :sync]
  [sqrt(-1.0), 1.5, "hiho"].each do |arg|
    progs2.each_with_index do |n, i|
      if (tag = Snd.catch do
            case n
            when :channels, :chans, :data_format, :data_location, :data_size,
              :header_type, :srate, :comment
              # g_set_channels(snd, val)
              set_snd_func(n, arg, 0)
            else
              # g_set_amp_control(val, snd, chn)
              set_snd_func(n, 0, arg)
            end
          end).first != :wrong_type_arg
        snd_display("snd set :wrong_type_arg %s %s: %s %s", i, n, tag, arg)
      end
    end
  end
  progs3 =
    [:amp_control,
     :contrast_control, :amp_control_bounds, :speed_control_bounds, :expand_control_bounds,
     :contrast_control_bounds, :reverb_control_length_bounds, :reverb_control_scale_bounds,
     :contrast_control_amp, :contrast_control?,
     :expand_control, :expand_control_hop, :expand_control_jitter, :expand_control_length,
     :expand_control_ramp, :expand_control?, :filter_control_in_dB,
     :filter_control_in_hz, :filter_control_envelope, :filter_control_order, :filter_control?,
     :reverb_control_decay, :reverb_control_feedback,
     :reverb_control_length, :reverb_control_lowpass, :reverb_control_scale, :reverb_control?,
     :speed_control, :speed_control_style, :speed_control_tones, :channel_style, :sync]
  index = open_sound("obtest.snd")
  [sqrt(-1.0), "hiho"].each do |arg|
    progs3.each_with_index do |n, i|
      if (tag = Snd.catch do set_snd_func(n, arg, index) end).first != :wrong_type_arg
        snd_display("snd safe set :wrong_type_arg %s %s: %s %s", i, n, tag, arg)
      end
    end
  end
  close_sound(index)
  [Array.new(1), "hiho", sqrt(-1.0), 1.5, [1, 0], [0, 1]].each do |arg|
    [:make_vct, :vct_copy, :vct_length, :vct2list, :vct_peak].each do |n|
      if (tag = Snd.catch do snd_func(n, arg) end).first != :wrong_type_arg
        snd_display("vct 0 :wrong_type_arg %s: %s %s", n, tag, arg)
      end
    end
  end
  [Array.new(1), "hiho", sqrt(-1.0), 1.5, [1, 0], [0, 1]].each do |arg1|
    ["hiho", sqrt(-1.0), 1.5, [1, 0], [0, 1]].each do |arg2|
      [:vct_add!, :vct_subtract!, :vct_multiply!, :vct_ref, :vct_scale!, :vct_fill!].each do |n|
        case tag = (res = Snd.catch do snd_func(n, arg1, arg2) end).first
        when :wrong_type_arg, :wrong_number_of_args, :mus_error
          nil
        else
          snd_display("vct 1 :wrong_whatever %s: %s %s %s (%s)", n, tag, arg1, arg2, res)
        end
      end
    end
  end
  [Array.new(1), "hiho", sqrt(-1.0), [1, 0], [0, 1]].each do |arg|
    [:vct_add!, :vct_subtract!, :vct_multiply!, :vct_ref, :vct_scale!, :vct_fill!].each do |n|
      if (tag = Snd.catch do snd_func(n, $vct_3, arg) end).first != :wrong_type_arg
        snd_display("vct 2 :wrong_type_arg %s: %s %s", n, tag, arg)
      end
    end
  end
  if (tag = Snd.catch do make_vct(-23) end).first != :out_of_range
    snd_display("make_vct -23: %s", tag)
  end
  if (tag = Snd.catch do Vct.new(-23) end).first != :out_of_range
    snd_display("Vct.new -23: %s", tag)
  end
  v = $vct_3
  if (tag = Snd.catch do vct_ref(v, 12) end).first != :out_of_range
    snd_display("vct_ref 12: %s", tag)
  end
  if (tag = Snd.catch do v[12] end).first != :out_of_range
    snd_display("v[12]: %s", tag)
  end
  procs_p =
    [:all_pass?, :asymmetric_fm?, :comb?, :filtered_comb?, :convolve?, :delay?, :env?,
     :file2frame?, :file2sample?, :snd2sample?, :filter?, :fir_filter?, :formant?,
     :frame2file?, :frame?, :granulate?, :iir_filter?, :locsig?, :mixer?, :move_sound?, :mus_input?,
     :mus_output?, :notch?, :one_pole?, :one_zero?, :oscil?, :phase_vocoder?,
     :pulse_train?, :rand_interp?, :rand?, :readin?, :sample2file?, :sawtooth_wave?,
     :square_wave?, :src?,
     :table_lookup?, :triangle_wave?, :two_pole?, :two_zero?, :wave_train?,
     :color?, :mix_sampler?, :moving_average?, :ssb_am?, :sampler?,
     :region_sampler?, :vct?]
  [Array.new(1), "hiho", sqrt(-1.0), 1.5, [1, 0], [0, 1]].each do |arg|
    procs_p.each do |n|
      if (tag = Snd.catch do snd_func(n, arg) end).first.kind_of?(TrueClass)
        snd_display("?proc %s: %s %s", n, tag, arg)
      end
    end
  end
  procs_p.each do |n|
    next if n == :oscil?
    if (tag = Snd.catch do snd_func(n, make_oscil(440)) end).first.kind_of?(TrueClass)
      snd_display("oscil?proc %s: %s", n, tag)
    end
  end
  [:reverse_selection, :selection_position, :selection_frames, :smooth_selection,
   :scale_selection_to, :insert_selection, :delete_selection, :delete_selection_and_smooth,
   :mix_selection].each do |n|
    if (tag = Snd.catch do snd_func(n) end).first != :no_active_selection
      snd_display("selection %s: %s", n, tag)
    end
  end
  # FIXME (INFO)
  # Array.new(1): *partials_* functions return :bad_type (odd length partials list?)
  # We can't use arrays here like [:Pixel, ...] or [0, 1].
  [sqrt(-1.0)].each do |arg|
    [:all_pass, :asymmetric_fm, :clear_array, :comb, :filtered_comb, :convolve, :db2linear,
     :moving_average, :degrees2radians, :delay, :env, :formant, :frame2list, :granulate,
     :hz2radians, :linear2db, :make_all_pass, :make_asymmetric_fm, :make_comb, :make_filtered_comb,
     :make_convolve, :make_delay, :make_env, :make_file2frame, :make_file2sample,
     :make_filter, :make_fir_filter, :make_formant, :make_frame, :make_granulate,
     :make_iir_filter, :make_locsig, :make_notch, :make_one_pole, :make_one_zero,
     :make_oscil, :make_pulse_train, :make_rand, :make_rand_interp,
     :make_readin, :make_sawtooth_wave, :make_square_wave,
     :make_src, :make_table_lookup,
     :make_triangle_wave, :make_two_pole, :make_two_zero, :make_wave_train, :make_ssb_am,
     :mus_channel, :mus_channels, :make_polyshape,
     :mus_data, :mus_feedback, :mus_feedforward,
     :mus_frequency, :mus_hop, :mus_increment, :mus_length, :mus_file_name, :mus_location,
     :mus_order, :mus_phase, :mus_ramp, :mus_random, :mus_run, :mus_scaler, :mus_xcoeffs,
     :mus_ycoeffs, :notch, :one_pole, :one_zero, :make_moving_average, :seconds2samples,
     :samples2seconds, :oscil, :partials2polynomial, :partials2wave,
     :phase_partials2wave, :phase_vocoder, :pulse_train, :radians2degrees, :radians2hz,
     :rand, :rand_interp, :readin, :sawtooth_wave, :square_wave, :src,
     :table_lookup, :tap, :triangle_wave, :two_pole,
     :two_zero, :wave_train, :ssb_am].each_with_index do |n, i|
      case (tag = Snd.catch do snd_func(n, arg) end).first
      when :wrong_type_arg, :no_data, :bad_type, :arg_error
        next
      else
        snd_display("clm %s: tag %s, arg %s [%s]", n, tag, arg, i)
      end
    end
  end
  [:all_pass, :array_interp, :asymmetric_fm, :comb, :filtered_comb, :contrast_enhancement,
   :convolution, :convolve, :moving_average, :convolve_files, :delay, :dot_product, :env_interp,
   :file2frame,
   :file2sample, :snd2sample, :filter, :fir_filter, :formant, :firmant, :formant_bank,
   :frame_multiply, :frame_add, :frame2frame, :frame_ref, :frame2sample, :granulate,
   :iir_filter, :ina, :inb, :locsig_ref, :locsig_reverb_ref, :make_all_pass,
   :make_asymmetric_fm, :make_comb, :make_filtered_comb, :make_delay, :make_env, :make_fft_window,
   :make_filter, :make_fir_filter, :make_formant, :make_firmant, :make_frame, :make_granulate,
   :make_iir_filter, :make_locsig, :make_notch, :make_one_pole, :make_one_zero,
   :make_oscil, :make_phase_vocoder, :make_pulse_train, :make_rand,
   :make_rand_interp, :make_readin, :make_sawtooth_wave, :make_moving_average,
   :make_nrxysin, :make_nrxycos,
   :make_square_wave, :make_src, :make_ncos, :make_nsin,
   :make_table_lookup, :make_triangle_wave, :make_two_pole, :make_two_zero,
   :make_wave_train, :mixer_multiply, :mixer_add, :multiply_arrays, :notch, :one_pole, :one_zero,
   :oscil, :partials2polynomial, :partials2wave, :make_polyshape, :make_polywave,
   :phase_partials2wave, :phase_vocoder, :polynomial, :pulse_train, :rand, :rand_interp,
   :rectangular2polar, :rectangular2magnitudes,
   :ring_modulate, :sample2frame, :sawtooth_wave, :nrxysin, :nrxycos,
   :square_wave, :src, :ncos, :nsin, :table_lookup, :tap, :triangle_wave,
   :two_pole, :two_zero, :wave_train, :ssb_am, :make_ssb_am].each do |n|
    case tag = (res = Snd.catch do snd_func(n, make_oscil, $vct_3) end).first
    when :wrong_type_arg, :bad_arity, :mus_error
      next
    else
      snd_display("clm 1 %s: %s %s", n, tag, res)
    end
  end
  [:mus_data, :mus_feedback, :mus_feedforward,
   :mus_frequency, :mus_hop, :mus_increment, :mus_length, :mus_location,
   :mus_phase, :mus_ramp, :mus_scaler].each do |n|
    if (tag = Snd.catch do set_snd_func(n, make_oscil, $vector_0) end).first != :wrong_type_arg
      snd_display("mus_gen %s: %s", n, tag)
    end
  end
  mus_procs = [:mus_sound_samples, :mus_sound_frames, :mus_sound_duration, :mus_sound_datum_size,
               :mus_sound_data_location, :mus_sound_chans, :mus_sound_srate, :mus_sound_header_type,
               :mus_sound_data_format, :mus_sound_length, :mus_sound_type_specifier,
               :mus_header_type_name, :mus_data_format_name, :mus_sound_comment,
               :mus_sound_write_date, :mus_bytes_per_sample, :mus_sound_loop_info,
               :mus_sound_mark_info, :mus_sound_maxamp, :mus_sound_maxamp_exists?,
               :mus_header_type2string, :mus_data_format2string]
  mus_procs.each do |n|
    if (tag = Snd.catch do snd_func(n, $vct_3) end).first != :wrong_type_arg
      snd_display("mus_sound %s: %s", n, tag)
    end
  end
  mus_procs.each do |n|
    if (tag = Snd.catch do snd_func(n) end).first != :wrong_number_of_args
      snd_display("no arg mus_sound %s: %s", n, tag)
    end
  end
end

def test_28_01
  [:mus_sound_samples, :mus_sound_frames, :mus_sound_duration, :mus_sound_datum_size,
   :mus_sound_data_location, :mus_sound_chans, :mus_sound_srate, :mus_sound_header_type,
   :mus_sound_data_format, :mus_sound_length, :mus_sound_type_specifier,
   :mus_sound_comment, :mus_sound_write_date, :mus_sound_maxamp,
   :mus_sound_maxamp_exists?].each do |n|
    if (tag = Snd.catch do snd_func(n, "/bad/baddy") end).first != :mus_error
      snd_display("bad file mus_sound %s: %s", n, tag)
    end
  end
  mus_sound_forget("/bad/baddy")
  [:channel_widgets, :count_matches, :cursor, :channel_properties, :channel_property,
   :cursor_position, :cursor_size, :cursor_style, :tracking_cursor_style, :delete_sample,
   :display_edits, :dot_size, :draw_dots, :draw_lines, :edit_fragment, :edit_list2function,
   :edit_position, :edit_tree, :edits, :fft_window_alpha,
   :fft_window_beta, :fft_log_frequency, :fft_log_magnitude, :fft_with_phases, :transform_size,
   :transform_graph_type, :fft_window, :transform_graph?, :find_channel, :graph, :graph_style,
   :lisp_graph?, :insert_sound, :time_graph_style, :lisp_graph_style,
   :transform_graph_style, :left_sample, :make_graph_data, :map_chan, :max_transform_peaks,
   :maxamp_position, :min_dB, :mix_region, :transform_normalization,
   :peaks, :play, :position2x, :position2y, :reverse_sound,
   :revert_sound, :right_sample, :sample, :save_sound, :save_sound_as,
   :scan_chan, :select_channel, :show_axes, :show_transform_peaks, :show_marks,
   :show_mix_waveforms, :show_y_zero, :show_grid, :show_sonogram_cursor, :spectrum_end,
   :spectro_hop, :spectrum_start, :spectro_x_angle, :spectro_x_scale, :spectro_y_angle,
   :grid_density, :spectro_y_scale, :spectro_z_angle, :spectro_z_scale, :squelch_update,
   :transform_sample, :transform2vct, :transform_frames, :transform_type,
   :update_transform_graph, :update_time_graph, :update_lisp_graph, :update_sound,
   :wavelet_type, :time_graph?, :time_graph_type, :wavo_hop, :wavo_trace, :x_bounds,
   :x_position_slider, :x_zoom_slider, :x_axis_label, :y_axis_label, :y_bounds,
   :y_position_slider, :y_zoom_slider, :zero_pad].each_with_index do |n, i|
    case (tag = Snd.catch do snd_func(n, $vct_3) end).first
    when :wrong_type_arg, :no_such_sound
      next
    else
      snd_display("%s: chn (no snd) procs %s: %s", i, n, tag)
    end
  end
  [:channel_widgets, :count_matches, :cursor, :channel_properties, :channel_property,
   :combined_data_color, :cursor_position, :cursor_size, :cursor_style, :tracking_cursor_style,
   :delete_sample, :display_edits, :dot_size, :draw_dots, :draw_lines, :edit_fragment,
   :edit_position, :edit_tree, :edits, :fft_window_beta, :fft_window_alpha, :fft_with_phases,
   :fft_log_frequency, :fft_log_magnitude, :transform_size, :transform_graph_type, :fft_window,
   :transform_graph?, :find_channel, :graph, :graph_style, :lisp_graph?, :insert_region,
   :insert_sound, :left_sample, :time_graph_style, :lisp_graph_style, :transform_graph_style,
   :make_graph_data, :map_chan, :max_transform_peaks, :maxamp, :maxamp_position, :min_dB,
   :mix_region, :transform_normalization, :peaks, :position2x, :position2y, :reverse_sound,
   :right_sample, :sample, :save_sound_as, :scan_chan, :show_axes, :show_transform_peaks,
   :show_marks, :show_mix_waveforms, :show_y_zero, :show_grid, :show_sonogram_cursor, :spectrum_end,
   :spectro_hop, :spectrum_start, :spectro_x_angle, :spectro_x_scale, :spectro_y_angle,
   :spectro_y_scale, :spectro_z_angle, :spectro_z_scale, :squelch_update, :grid_density,
   :transform_sample, :transform2vct, :transform_frames, :transform_type,
   :update_transform_graph, :update_time_graph, :update_lisp_graph, :wavelet_type,
   :time_graph?, :time_graph_type, :wavo_hop, :wavo_trace, :x_bounds, :x_position_slider,
   :x_zoom_slider, :x_axis_label, :y_axis_label, :y_bounds, :y_position_slider,
   :y_zoom_slider, :zero_pad].each_with_index do |n, i|
    if (tag = Snd.catch do snd_func(n, 0, $vct_3) end).first != :wrong_type_arg
      snd_display("%s: chn (no chn) procs %s: %s", i, n, tag)
    end
  end
  idx = open_sound("oboe.snd")
  [:delete_sample, :edit_fragment, :graph_data, :position2x, :position2y, :redo_edit, :scale_by,
   :scale_to, :undo_edit, :x2position, :y2position].each_with_index do |n, i|
    tag = Snd.catch do snd_func(n, 0, idx, 1234) end
    if tag.first != :no_such_channel
      snd_display("%s: snd(1 1234) chn procs %s: %s", i, n, tag)
    end
  end
  close_sound(idx)
  idx = open_sound("oboe.snd")
  [:channel_widgets, :cursor, :cursor_position, :cursor_size, :cursor_style,
   :tracking_cursor_style, :display_edits, :dot_size, :edit_position, :edit_tree, :edits,
   :fft_window_alpha, :fft_window_beta, :fft_log_frequency, :fft_log_magnitude, :fft_with_phases,
   :transform_size, :transform_graph_type, :fft_window, :transform_graph?, :graph_style,
   :lisp_graph?, :left_sample, :time_graph_style, :lisp_graph_style, :transform_graph_style,
   :combined_data_color, :make_graph_data, :max_transform_peaks, :maxamp, :maxamp_position,
   :min_dB, :transform_normalization, :reverse_sound, :right_sample, :show_axes,
   :show_transform_peaks, :show_marks, :show_mix_waveforms, :show_y_zero, :show_grid,
   :show_sonogram_cursor, :grid_density, :spectrum_end, :spectro_hop, :spectrum_start,
   :spectro_x_angle, :spectro_x_scale, :spectro_y_angle, :spectro_y_scale, :spectro_z_angle,
   :spectro_z_scale, :squelch_update, :transform2vct, :transform_frames, :transform_type,
   :update_transform_graph, :update_time_graph, :update_lisp_graph, :wavelet_type, :time_graph?,
   :time_graph_type, :wavo_hop, :wavo_trace, :x_bounds, :x_position_slider, :x_axis_label,
   :x_zoom_slider, :y_bounds, :y_position_slider, :y_zoom_slider, :zero_pad,
   :channel_properties, :channel_property].each_with_index do |n, i|
    tag = Snd.catch do snd_func(n, idx, 1234) end
    if tag.first != :no_such_sound and tag.first != :no_such_channel
      snd_display("%s: chn procs %s: %s", i, n, tag)
    end
  end
  close_sound(idx)
  [:delete_sample, :edit_fragment, :graph_data, :graph_style,
   :position2x, :position2y, :redo_edit, :time_graph_style, :lisp_graph_style,
   :transform_graph_style, :scale_by, :scale_to, :undo_edit, :x2position, :y2position,
   :x_axis_label].each_with_index do |n, i|
    if (tag = Snd.catch do snd_func(n, 0, 1234) end).first != :no_such_sound
      snd_display("%s: snd(1) chn procs %s: %s", i, n, tag)
    end
  end
  idx = open_sound("oboe.snd")
  [:channel_widgets, :cursor, :cursor_position, :cursor_size, :cursor_style, :tracking_cursor_style,
   :display_edits, :dot_size, :edit_position, :edit_tree, :edits, :fft_window_beta,
   :fft_window_alpha, :fft_with_phases,
   :fft_log_frequency, :fft_log_magnitude, :transform_size, :transform_graph_type,
   :fft_window, :transform_graph?, :graph_style, :lisp_graph?, :left_sample,
   :time_graph_style, :lisp_graph_style, :transform_graph_style, :make_graph_data,
   :max_transform_peaks, :maxamp, :maxamp_position, :min_dB, :transform_normalization,
   :reverse_sound, :right_sample, :show_axes, :show_transform_peaks,
   :show_marks, :show_mix_waveforms, :show_y_zero, :show_grid, :show_sonogram_cursor,
   :grid_density, :spectrum_end, :spectro_hop, :spectrum_start, :spectro_x_angle,
   :spectro_x_scale, :spectro_y_angle, :spectro_y_scale, :spectro_z_angle,
   :spectro_z_scale, :squelch_update, :transform2vct, :transform_frames,
   :transform_type, :update_transform_graph, :update_time_graph, :update_lisp_graph,
   :wavelet_type, :time_graph?, :time_graph_type, :wavo_hop, :wavo_trace,
   :x_bounds, :x_position_slider, :x_axis_label, :x_zoom_slider, :y_bounds,
   :y_position_slider, :y_zoom_slider, :zero_pad,
   :channel_properties, :channel_property].each_with_index do |n, i|
    case (tag = Snd.catch do snd_func(n, idx, 1234) end).first
    when :no_such_sound, :no_such_channel
      next
    else
      snd_display("%s: chn (2) procs %s: %s", i, n, tag)
    end
  end
  [:channel_widgets, :cursor, :cursor_position, :display_edits, :dot_size,
   :edit_tree, :edits, :fft_window_beta, :fft_with_phases,
   :fft_window_alpha, :fft_log_frequency, :fft_log_magnitude,
   :transform_size, :transform_graph_type, :fft_window, :transform_graph?,
   :graph_style, :lisp_graph?, :left_sample, :make_graph_data,
   :max_transform_peaks, :maxamp, :maxamp_position, :time_graph_style,
   :lisp_graph_style, :transform_graph_style, :combined_data_color, :min_dB,
   :transform_normalization, :reverse_sound, :right_sample, :show_axes, :grid_density,
   :show_transform_peaks, :show_marks, :show_mix_waveforms, :show_y_zero,
   :show_grid, :show_sonogram_cursor, :spectrum_end, :spectro_hop, :spectrum_start,
   :spectro_x_angle, :spectro_x_scale, :spectro_y_angle, :spectro_y_scale,
   :spectro_z_angle, :spectro_z_scale, :squelch_update, :transform2vct,
   :transform_frames, :transform_type, :update_transform_graph, :update_time_graph,
   :update_lisp_graph, :wavelet_type, :time_graph?, :time_graph_type, :wavo_hop,
   :wavo_trace, :x_bounds, :x_position_slider, :x_zoom_slider, :y_bounds,
   :y_position_slider, :y_zoom_slider, :zero_pad, :x_axis_label].each_with_index do |n, i|
    tag = Snd.catch do set_snd_func(n, $vct_3, idx, 0) end
    if tag.first != :wrong_type_arg and tag.first != :no_method_error and tag.first != :name_error
      snd_display("%s: set chn procs %s: %s", i, n, tag)
    end
  end
  close_sound(idx)
  [:mix_amp, :mix_amp_env, :mix_length, :mix_name, :mix_position, :mix_home, :mix_speed,
   :mix_tag_y].each_with_index do |n, i|
    if (tag = Snd.catch do snd_func(n, $vct_3) end).first != :wrong_type_arg
      snd_display("%s: mix (1) procs %s: %s", i, n, tag)
    end
  end
  [:mix_amp, :mix_length, :mix_name, :mix_position, :mix_home, :mix_speed,
   :mix_tag_y].each_with_index do |n, i|
    if (tag = Snd.catch do snd_func(n, integer2mix(1234)) end).first != :no_such_mix
      snd_display("%s: mix (2) procs %s: %s", i, n, tag)
    end
  end
  [:mix_name, :mix_position, :mix_speed, :mix_tag_y].each_with_index do |n, i|
    tag = Snd.catch do set_snd_func(n, integer2mix(1234), $vct_3) end
    if tag.car != :wrong_type_arg and tag.car != :no_such_mix
      snd_display("%s: set mix (3) procs %s: %s", i, n, tag)
    end
  end
  index = open_sound("oboe.snd")
  id = mix_sound("oboe.snd", 10).car
  [:mix_name, :mix_position, :mix_speed, :mix_tag_y].each_with_index do |n, i|
    if (tag = Snd.catch do set_snd_func(n, id, $vct_3) end).first != :wrong_type_arg
      snd_display("%s: set mix (4) procs %s: %s", i, n, tag)
    end
  end
  close_sound(index)
  [:add_mark, :mark_name, :mark_sample, :mark_sync, :mark_home, :delete_mark,
   :delete_marks, :find_mark].each_with_index do |n, i|
    if (tag = Snd.catch do snd_func(n, $vct_3) end).first != :wrong_type_arg
      snd_display("%s: mark (1) procs %s: %s", i, n, tag)
    end
  end
  [:mark_name, :mark_sample, :mark_sync, :mark_home, :delete_mark].each_with_index do |n, i|
    if (tag = Snd.catch do snd_func(n, integer2mark(1234)) end).first != :no_such_mark
      snd_display("%s: mark (2) procs %s: %s", i, n, tag)
    end
  end
  index = open_sound("oboe.snd")
  id = add_mark(0, index, 0)
  [:mark_name, :mark_sample, :mark_sync].each_with_index do |n, i|
    if (tag = Snd.catch do set_snd_func(n, id, $vct_3) end).first != :wrong_type_arg
      snd_display("%s: set mark (3) procs %s: %s", i, n, tag)
    end
  end
  close_sound(index)
  [[0, 1], sqrt(-1.0), "hiho", [0, 1]].each do |arg|
    [:region_chans, :region_home, :region_frames, :region_position,
     :region_maxamp, :region_maxamp_position, :region_sample, :region2vct,
     :region_srate, :forget_region].each_with_index do |n, i|
      if (tag = Snd.catch do snd_func(n, arg) end).first != :wrong_type_arg
        snd_display("%s: region (1) procs %s: %s %s", i, n, tag, arg)
      end
    end
  end
  [:region_chans, :region_home, :region_frames, :region_position,
   :region_maxamp, :region_maxamp_position, :region_srate,
   :forget_region].each_with_index do |n, i|
    if (tag = Snd.catch do snd_func(n, integer2region(1234)) end).first != :no_such_region
      snd_display("%s: (no) region (2) procs %s: %s", i, n, tag)
    end
  end
  [:enved_filter_order, :enved_filter, :filter_control_waveform_color,
   :ask_before_overwrite, :auto_resize, :auto_update, :axis_label_font,
   :axis_numbers_font, :basic_color, :show_full_duration, :show_full_range, :initial_beg,
   :initial_dur, :channel_style, :color_cutoff, :color_inverted, :color_scale, :cursor_color,
   :dac_combines_channels, :dac_size, :clipping, :data_color,
   :default_output_chans, :default_output_data_format, :default_output_srate,
   :default_output_header_type, :enved_envelope, :enved_base, :enved_clip?,
   :enved_in_dB, :enved_style, :enved_power, :enved_target,
   :enved_waveform_color, :enved_wave?, :eps_file, :eps_left_margin,
   :eps_bottom_margin, :eps_size, :foreground_color, :graph_color, :graph_cursor,
   :highlight_color, :just_sounds, :listener_color, :listener_font,
   :listener_prompt, :listener_text_color, :max_regions,
   :minibuffer_history_length, :mix_waveform_height, :region_graph_style,
   :position_color, :time_graph_style, :lisp_graph_style, :transform_graph_style,
   :peaks_font, :bold_peaks_font, :view_files_sort, :print_length, :play_arrow_size,
   :sash_color, :ladspa_dir, :peak_env_dir, :save_dir, :save_state_file, :selected_channel,
   :selected_data_color, :selected_graph_color, :selected_sound,
   :selection_creates_region, :show_controls, :show_indices,
   :show_listener, :show_selection_transform, :sinc_width, :temp_dir,
   :text_focus_color, :tiny_font, :trap_segfault, :with_file_monitor,
   :with_verbose_cursor, :with_inset_graph, :with_interrupts, :with_pointer_focus,
   :window_height, :beats_per_measure, :with_smpte_label,
   :with_toolbar, :with_tooltips, :with_menu_icons, :remember_sound_state,
   :save_as_dialog_src, :save_as_dialog_auto_comment, :window_width, :window_x, :window_y, :with_gl,
   :with_mix_tags, :x_axis_style, :beats_per_minute, :zoom_color, :mix_tag_height,
   :mix_tag_width, :with_relative_panes, :clm_table_size,
   :mark_tag_width, :mark_tag_height].each do |n|
    tag = Snd.catch do set_snd_func(n, $vct_3) end
    if tag.first != :wrong_type_arg and tag.first != :mus_error
      snd_display("misc procs %s: %s", n, tag.inspect)
    end
  end
  [:after_graph_hook, :lisp_graph_hook, :before_transform_hook,
    :mix_release_hook, :save_hook, :before_save_as_hook, :after_save_as_hook,
    :save_state_hook, :new_sound_hook, :mus_error_hook, :mouse_enter_graph_hook,
    :mouse_leave_graph_hook, :open_raw_sound_hook, :select_channel_hook,
    :output_name_hook, :peak_env_hook, :after_open_hook, :close_hook, :draw_mark_hook,
    :draw_mix_hook, :mark_click_hook, :listener_click_hook, :mix_click_hook,
    :after_save_state_hook, :before_save_state_hook, :mark_hook, :mark_drag_hook,
    :mix_drag_hook, :name_click_hook, :after_apply_controls_hook,
    :open_hook, :output_comment_hook, :help_hook, :play_hook, :dac_hook,
    :new_widget_hook, :read_hook, :bad_header_hook, :snd_error_hook,
    :snd_warning_hook, :start_hook, :start_playing_hook, :stop_playing_hook,
    :mouse_enter_listener_hook, :mouse_leave_listener_hook,
    :select_sound_hook, :view_files_select_hook,
    :during_open_hook, :after_transform_hook, :mouse_enter_label_hook,
    :mouse_leave_label_hook, :initial_graph_hook, :graph_hook, :key_press_hook,
    :mouse_drag_hook, :mouse_press_hook, :mouse_click_hook, :enved_hook].each_with_index do |n, i|
    hook = eval("$#{n}")
    fnc = lambda do || 1 + 2 end
    if (tag = Snd.catch do hook.add_hook!("test28-1", &fnc) end).first != :wrong_type_arg
      snd_display("%s: hooks (1) %s: %s", i, n, tag)
    end
  end
  [:exit_hook, :stop_dac_hook, :stop_playing_selection_hook,
   :color_hook, :orientation_hook, :start_playing_selection_hook].each_with_index do |n, i|
    hook = eval("$#{n}")
    fnc = lambda do |a, b, c| a + b + c end
    if (tag = Snd.catch do hook.add_hook!("test28-2", &fnc) end).first != :wrong_type_arg
      snd_display("%s: hooks (2) %s: %s", i, n, tag)
    end
  end
end

def test_28_02
  not_an_env = nil # otherwise name_error: no such variable or function
  check_error_tag(:no_such_envelope) do set_enved_envelope("not_an_env") end
  check_error_tag(:cannot_save) do save_envelopes("/bad/baddy") end
  check_error_tag(:cannot_save) do save_macros("/bad/baddy") end
  check_error_tag(:cannot_save) do mus_sound_report_cache("/bad/baddy") end
  check_error_tag(:bad_arity) do set_search_procedure(lambda do |a, b, c| a end) end
  check_error_tag(:no_such_sound) do set_search_procedure(1234, lambda do |a| a end) end
  check_error_tag(:no_such_channel) do make_sampler(0, "oboe.snd", 1) end
  check_error_tag(:no_such_channel) do make_sampler(0, "oboe.snd", -1) end
  check_error_tag(:bad_arity) do
    bind_key(key_to_int(?p), 0, lambda do |a, b| play_often([1, a].max) end)
  end
  check_error_tag(:bad_arity) do set_zoom_focus_style(lambda do |a| 0 end) end
  check_error_tag(:wrong_type_arg) do
    mus_mix("oboe.snd", "pistol.snd", 0, 12, 0, make_mixer(1, 1.0), "a string")
  end
  check_error_tag(:bad_header) do mus_mix("oboe.snd", $sf_dir + "bad_chans.aifc") end
  check_error_tag(:mus_error) do mus_mix("oboe.snd", $sf_dir + "bad_length.aifc") end
  check_error_tag(:bad_header) do mus_mix($sf_dir + "bad_chans.aifc", "oboe.snd") end
  check_error_tag(:no_such_sound) do set_sound_loop_info(123, [0, 0, 1, 1]) end
  check_error_tag(:bad_header) do
    new_sound("fmv.snd", Mus_nist, Mus_bfloat, 22050, 2, "this is a comment")
  end
  check_error_tag(:wrong_type_arg) do player_home(123) end
  check_error_tag(:no_such_file) do set_temp_dir("/hiho") end
  check_error_tag(:no_such_file) do set_save_dir("/hiho") end
  check_error_tag(:out_of_range) do snd_transform(integer2transform(20), make_vct(4)) end
  check_error_tag(:bad_header) do mus_sound_maxamp($sf_dir + "bad_chans.snd") end
  check_error_tag(:bad_header) do set_mus_sound_maxamp($sf_dir + "bad_chans.snd", [0.0, 0.0]) end
  check_error_tag(:mus_error) do make_iir_filter(:order, 32, :ycoeffs, make_vct(4)) end
  check_error_tag(:mus_error) do make_iir_filter(:coeffs, make_vct(4), :ycoeffs, make_vct(4)) end
  check_error_tag(:mus_error) do make_iir_filter(:coeffs, make_vct(4), :xcoeffs, make_vct(4)) end
  check_error_tag(:out_of_range) do make_table_lookup(:size, 123456789) end
  check_error_tag(:out_of_range) do make_granulate(:ramp, -0.5) end
  check_error_tag(:out_of_range) do make_granulate(:ramp, 1.5) end
  check_error_tag(:mus_error) do make_granulate(:expansion, 32000.0) end
  check_error_tag(:out_of_range) do new_sound("test.snd", :channels, 0) end
  check_error_tag(:out_of_range) do new_sound("test.snd", :srate, 0) end
  check_error_tag(:out_of_range) do new_sound("test.snd", :size, -1) end
  check_error_tag(:out_of_range) do make_readin("oboe.snd", :size, 0) end
  check_error_tag(:out_of_range) do make_readin("oboe.snd", :size, -1) end
  check_error_tag(:out_of_range) do make_file2sample("oboe.snd", 0) end
  check_error_tag(:out_of_range) do make_file2sample("oboe.snd", -1) end
  check_error_tag(:out_of_range) do make_file2frame("oboe.snd", 0) end
  check_error_tag(:out_of_range) do make_file2frame("oboe.snd", -1) end
  check_error_tag(:out_of_range) do set_default_output_data_format(-1) end
  check_error_tag(:out_of_range) do set_default_output_header_type(Mus_soundfont) end
  check_error_tag(:mus_error) do mus_sound_chans($sf_dir + "bad_location.nist") end
  check_error_tag(:mus_error) do mus_sound_chans($sf_dir + "bad_field.nist") end
  if $with_test_motif
    check_error_tag(:no_such_widget) do widget_position([:Widget, 0]) end
    check_error_tag(:no_such_widget) do widget_size([:Widget, 0]) end
    check_error_tag(:no_such_widget) do widget_text([:Widget, 0]) end
    check_error_tag(:no_such_widget) do set_widget_position([:Widget, 0], [0, 0]) end
    check_error_tag(:no_such_widget) do set_widget_size([:Widget, 0], [10, 10]) end
    check_error_tag(:no_such_widget) do set_widget_text([:Widget, 0], "hiho") end
  end
  check_error_tag(:no_such_menu) do main_menu(-1) end
  check_error_tag(:no_such_menu) do main_menu(111) end
  check_error_tag(:out_of_range) do new_sound("hiho", 123) end
  check_error_tag(:out_of_range) do new_sound("hiho", Mus_nist, 123) end
  check_error_tag(:bad_header) do new_sound("hiho", Mus_nist, Mus_bfloat) end
  check_error_tag(:out_of_range) do make_sound_data(0, 1) end
  check_error_tag(:out_of_range) do make_sound_data(-2, 1) end
  check_error_tag(:out_of_range) do make_sound_data(1, -1) end
  check_error_tag(:out_of_range) do make_sound_data(1, 0) end
  check_error_tag(:out_of_range) do mus_sound_close_output(0, 1) end
  check_error_tag(:out_of_range) do mus_sound_close_output(1, 1) end
  check_error_tag(:out_of_range) do mus_sound_close_output(2, 1) end
  check_error_tag(:out_of_range) do mus_sound_close_input(0) end
  check_error_tag(:out_of_range) do mus_sound_close_input(1) end
  check_error_tag(:out_of_range) do mus_sound_close_input(2) end
  check_error_tag(:out_of_range) do set_mus_array_print_length(-1) end
  check_error_tag(:out_of_range) do set_print_length(-1) end
  check_error_tag(:out_of_range) do set_play_arrow_size(-1) end
  check_error_tag(:wrong_type_arg) do vector2vct(make_array(3, "hio")) end
  check_error_tag(:out_of_range) do set_enved_style(12) end
  check_error_tag(:out_of_range) do make_color(1.5, 0.0, 0.0) end
  check_error_tag(:out_of_range) do make_color(-0.5, 0.0, 0.0) end
  check_error_tag(:wrong_type_arg) do make_variable_graph(false) end
  check_error_tag(:cannot_print) do graph2ps end
  ind = open_sound("oboe.snd")
  set_selection_creates_region(true)
  select_all
  check_error_tag(:mus_error) do save_selection("sel0.snd", :not_a_key, 3) end
  check_error_tag(:wrong_type_arg) do read_only([ind]) end
  check_error_tag(:wrong_type_arg) do frames(ind, [0]) end
  check_error_tag(:wrong_type_arg) do smooth_sound(0, -10) end
  check_error_tag(:no_such_channel) do mix_selection(0, ind, 123) end
  check_error_tag(:no_such_channel) do insert_selection(0, ind, 123) end
  check_error_tag(:out_of_range) do set_channels(ind, 0) end
  check_error_tag(:out_of_range) do set_channels(ind, -1) end
  check_error_tag(:out_of_range) do set_channels(ind, 12340) end
  check_error_tag(:out_of_range) do set_data_format(ind, 12340) end
  check_error_tag(:out_of_range) do set_header_type(ind, 12340) end
  check_error_tag(:out_of_range) do set_srate(ind, 0) end
  check_error_tag(:out_of_range) do set_data_location(ind, -1) end
  check_error_tag(:out_of_range) do set_data_size(ind, -1) end
  check_error_tag(:no_such_sample) do set_sample(-1, -1) end
  check_error_tag(:no_such_sample) do sample(-1) end
  check_error_tag(:out_of_range) do set_frames(-10) end
  check_error_tag(:out_of_range) do set_min_dB(0.0) end
  check_error_tag(:out_of_range) do set_min_dB(0.0, ind, 0) end
  check_error_tag(:out_of_range) do start_playing(1, -22) end
  check_error_tag(:out_of_range) do start_playing(1, 0) end
  check_error_tag(:out_of_range) do
    set_filter_control_envelope([0.0, 1.0, 0.1, -0.1, 1.0, 0.0], ind)
  end
  check_error_tag(:out_of_range) do
    set_filter_control_envelope([0.0, 1.0, 0.1, 1.1, 1.0, 0.0], ind)
  end
  check_error_tag(:env_error) do filter_sound([0, 0, 0.1, 0.1, 0.05, 0.1, 1, 1], 32) end
  check_error_tag(:out_of_range) do apply_controls(ind, 123) end
  check_error_tag(:out_of_range) do set_speed_control_bounds([0.0, 2.0]) end
  check_error_tag(:out_of_range) do set_expand_control_bounds([0.0, 2.0]) end
  check_error_tag(:out_of_range) do set_speed_control_bounds([2.0, 0.0]) end
  check_error_tag(:out_of_range) do set_expand_control_bounds([2.0, 0.0]) end
  check_error_tag(:bad_header) do insert_sound($sf_dir + "bad_chans.snd") end
  check_error_tag(:io_error) do convolve_with($sf_dir + "bad_chans.snd") end
  check_error_tag(:cannot_save) do save_sound_as("hiho.snd", ind, -12) end
  check_error_tag(:cannot_save) do save_sound_as("hiho.snd", ind, Mus_next, -12) end
  check_error_tag(:cannot_save) do save_sound_as("test.snd", ind, Mus_nist, Mus_bdouble) end
  check_error_tag(:cannot_save) do save_sound_as("test.snd", ind, Mus_aifc, Mus_lfloat) end
  check_error_tag(:cannot_save) do save_sound_as("test.snd", ind, Mus_riff, Mus_bshort) end
  check_error_tag(:cannot_save) do save_sound_as("test.snd", ind, Mus_voc, Mus_bshort) end
  check_error_tag(:cannot_save) do save_selection("test.snd", Mus_riff, Mus_bshort) end
  check_error_tag(:cannot_save) do save_selection("test.snd", Mus_voc, Mus_bshort) end
  check_error_tag(:out_of_range) do src_channel(make_env([0, 0, 1, 1], :length, 11)) end
  check_error_tag(:out_of_range) do src_channel(make_env([0, 1, 1, 0], :length, 11)) end
  check_error_tag(:out_of_range) do src_channel(make_env([0, 1, 1, -1], :length, 11)) end
  check_error_tag(:out_of_range) do src_channel(make_env([0, -1, 1, 1], :length, 11)) end
  check_error_tag(:out_of_range) do src_sound(make_env([0, 0, 1, 1], :length, 11)) end
  check_error_tag(:out_of_range) do src_sound(make_env([0, 1, 1, 0], :length, 11)) end
  check_error_tag(:out_of_range) do src_sound(make_env([0, 1, 1, -1], :length, 11)) end
  check_error_tag(:out_of_range) do src_sound(make_env([0, -1, 1, 1], :length, 11)) end
  check_error_tag(:mus_error) do make_readin(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) end
  check_error_tag(:out_of_range) do filter_sound($vct_3, 32) end
  check_error_tag(:out_of_range) do filter_sound([0, 0, 1, 1], 0) end
  check_error_tag(:no_such_sound) do swap_channels(ind, 0, 12345, 0) end
  check_error_tag(:no_such_sample) do mix_vct(vct(0.1, 0.2, 0.3), -1, ind, 0, true) end
  check_error_tag(:out_of_range) do snd_spectrum(Vct.new(8), 0, -123) end
  check_error_tag(:out_of_range) do snd_spectrum(Vct.new(8), 0, 0) end
  check_error_tag(:no_such_file) do play("/baddy/hiho") end
  check_error_tag(:bad_format) do play($sf_dir + "nist-shortpack.wav") end
  check_error_tag(:no_such_sound) do play(123, 0) end
  check_error_tag(:no_such_channel) do make_player(ind, 123) end
  check_error_tag(:no_such_file) do mix("/baddy/hiho") end
  check_error_tag(:no_such_channel) do mix("oboe.snd", 0, 2) end
  check_error_tag(:no_such_file) do mix_sound("/baddy/hiho", 0) end
  check_error_tag(:no_such_file) do insert_sound("/baddy/hiho.snd") end
  check_error_tag(:no_such_file) do insert_samples(0, 10, "/baddy/hiho.snd") end
  check_error_tag(:no_data) do set_filter_control_envelope([], ind) end
  check_error_tag(:out_of_range) do set_data_format(ind, 123) end
  check_error_tag(:out_of_range) do set_header_type(ind, 123) end
  check_error_tag(:no_such_channel) do set_selected_channel(ind, 123) end
  check_error_tag(:bad_arity) do set_search_procedure(ind, lambda do |a, b, c| true end) end
  check_error_tag(:bad_arity) do map_chan(lambda do |a, b, c| 1.0 end) end
  check_error_tag(:bad_arity) do scan_chan(lambda do |a, b, c| 1.0 end) end
  check_error_tag(:bad_arity) do set_cursor_style(lambda do |a| 32 end, ind, 0) end
  check_error_tag(:bad_arity) do find_channel(lambda do | | 1.0 end) end
  check_error_tag(:bad_arity) do count_matches(lambda do | | 1.0 end) end
  check_error_tag(:no_such_graphics_context) do draw_line(0, 0, 1, 1, ind, 0, 1234) end
  check_error_tag(:no_such_graphics_context) do foreground_color(ind, 0, 1234) end
  check_error_tag(:no_such_graphics_context) do current_font(ind, 0, 1234) end
  check_error_tag(:no_such_graphics_context) do
    graph_data([$vct_3, $vct_3], ind, 0, 1234, 0, 1, 0)
  end
  check_error_tag(:no_such_axis) do position2x(100, ind, 0, 1234) end
  check_error_tag(:no_such_axis) do position2y(100, ind, 0, 1234) end
  check_error_tag(:no_such_axis) do x2position(100, ind, 0, 1234) end
  check_error_tag(:no_such_axis) do y2position(100, ind, 0, 1234) end
  check_error_tag(:no_such_axis) do axis_info(ind, 0, 1234) end
  check_error_tag(:out_of_range) do
    draw_axes(channel_widgets.car, snd_gcs.car, "hiho", 0.0, 1.0, -1.0, 1.0, X_axis_in_seconds,1234)
  end
  check_error_tag(:out_of_range) do
    draw_axes(channel_widgets.car, snd_gcs.car, "hiho", 0.0, 1.0, -1.0, 1.0, 1234)
  end
  check_error_tag(:no_such_channel) do axis_info(ind, 1234) end
  check_error_tag(:no_such_sound) do axis_info(1234) end
  set_time_graph_type(Graph_once)
  check_error_tag(:out_of_range) do set_x_bounds([0.1, -0.1]) end
  check_error_tag(:out_of_range) do make_region(100, 0) end
  check_error_tag(:no_such_sample) do delete_sample(-1) end
  check_error_tag(:no_such_sample) do delete_sample(2 * frames(ind)) end
  regions.empty? and make_region(0, 100)
  check_error_tag(:no_such_channel) do region_sample(regions.car, 0, 1234) end
  check_error_tag(:no_such_channel) do region_frames(regions.car, 1234) end
  check_error_tag(:no_such_channel) do region_position(regions.car, 1234) end
  check_error_tag(:no_such_channel) do region2vct(regions.car, 0, 1, 1234) end
  check_error_tag(:cannot_save) do save_sound_as("/bad/baddy.snd") end
  check_error_tag(:no_such_sound) do transform_sample(0, 1, 1234) end
  check_error_tag(:no_such_channel) do transform_sample(0, 1, ind, 1234) end
  check_error_tag(:no_such_sound) do graph(vct(0, 1), "hi", 0, 1, 0, 1, 1234) end
  check_error_tag(:no_such_channel) do graph(vct(0, 1), "hi", 0, 1, 0, 1, ind, 1234) end
  set_selection_member?(false, true)
  check_error_tag(:no_active_selection) do filter_selection(vct(0, 0, 1, 1), 4) end
  check_error_tag(:no_active_selection) do save_selection("/bad/baddy.snd") end
  check_error_tag(:no_active_selection) do env_selection([0, 0, 1, 1]) end
  check_error_tag(:no_such_region) do save_region(integer2region(1234), "/bad/baddy.snd") end
  make_region(0, 100, ind, 0)
  check_error_tag(:cannot_save) do save_selection("/bad/baddy.snd") end
  check_error_tag(:cannot_save) do save_region(regions.car, "/bad/baddy.snd") end
  check_error_tag(:no_such_mix) do make_mix_sampler(integer2mix(1234)) end
  check_error_tag(:no_such_sound) do make_region(0, 12, 1234, true) end
  set_read_only(true, ind)
  check_error_tag(:cannot_save) do set_sound_loop_info(ind, [0, 0, 1, 1]) end
  check_error_tag(:no_such_direction) do make_sampler(0, ind, 0, 123) end
  check_error_tag(:no_such_direction) do make_sampler(0, ind, 0, 0) end
  check_error_tag(:no_such_direction) do make_sampler(0, ind, 0, -2) end
  check_error_tag(:no_data) do scale_by([]) end
  check_error_tag(:no_data) do scale_to([]) end
  check_error_tag(:bad_arity) do prompt_in_minibuffer("hi", lambda do |x, y| x + y end) end
  check_error_tag(:no_such_sample) do set_selection_position(-999, ind, 0) end
  check_error_tag(:wrong_type_arg) do set_selection_frames(-999, ind, 0) end
  check_error_tag(:wrong_type_arg) do set_selection_frames(0, ind, 0) end
  check_error_tag(:no_such_edit) do edit_fragment(-1) end
  check_error_tag(:no_such_edit) do edit_fragment(101, ind, 0) end
  check_error_tag(:no_such_edit) do edit_tree(ind, 0, -2) end
  check_error_tag(:no_such_edit) do edit_tree(ind, 0, 101) end
  check_error_tag(:no_such_sample) do add_mark(-1) end
  check_error_tag(:no_such_sample) do add_mark(frames * 2) end
  check_error_tag(:no_such_file) do convolve_with("/bad/baddy") end
  check_error_tag(:no_such_file) do mix("/bad/baddy") end
  check_error_tag(:no_such_sound) do swap_channels(ind, 0, 123) end
  check_error_tag(:out_of_range) do set_show_axes(123, ind, 0) end
  check_error_tag(:out_of_range) do set_show_axes(-123, ind, 0) end
  check_error_tag(:out_of_range) do set_x_axis_style(123, ind, 0) end
  check_error_tag(:out_of_range) do set_x_axis_style(-123, ind, 0) end
  check_error_tag(:out_of_range) do set_graph_style(123, ind, 0) end
  check_error_tag(:out_of_range) do set_graph_style(-123, ind, 0) end
  check_error_tag(:out_of_range) do env_sound([0, 0, 1, 1], 0, false, -1.5) end
  check_error_tag(:out_of_range) do xramp_channel(0.0, 1.0, -1.6) end
  check_error_tag(:wrong_type_arg) do set_samples(0, 2, -1) end
  check_error_tag(:wrong_type_arg) do left_sample([0]) end
  check_error_tag(:wrong_type_arg) do amp_control([0]) end
  check_error_tag(:wrong_type_arg) do sound_loop_info([0]) end
  check_error_tag(:wrong_type_arg) do add_mark(123, [0]) end
  check_error_tag(:no_such_sound) do filter_channel([0, 0, 1, 1], 100, false, false, 1234, 0) end
  check_error_tag(:no_such_channel) do filter_channel([0, 0, 1, 1], 100, false, false, ind, 1) end
  check_error_tag(:no_such_channel) do filter_channel(vct(0, 0, 1, 1), 4, false, false, ind, 1) end
  check_error_tag(:out_of_range) do filter_sound(vct(0, 0, 1, 1), 0) end
  check_error_tag(:out_of_range) do filter_sound(vct(0, 0, 1, 1), 10) end
  check_error_tag(:out_of_range) do set_reverb_control_length_bounds([0.1, 0.01], ind) end
  check_error_tag(:out_of_range) do set_reverb_control_scale_bounds([0.1, 0.01], ind) end
  check_error_tag(:wrong_type_arg) do scale_by(false) end
  check_error_tag(:wrong_type_arg) do scale_by(make_mixer(2, 0.1, 0.1, 0.2, 0.2)) end
  check_error_tag(:wrong_type_arg) do src_sound(3.0, 1.0, true) end
  check_error_tag(:wrong_type_arg) do src_sound(3.0, 1.0, ind, true) end
  check_error_tag(:no_such_edit) do display_edits(ind, 0, 123) end
  check_error_tag(:no_such_edit) do marks(ind, 0, 123) end
  check_error_tag(:no_such_edit) do save_sound_as("test.snd", :edit_position, 123) end
  check_error_tag(:no_such_auto_delete_choice) do insert_sound("1a.snd", 0, 0, ind, 0, 0, 123) end
  close_sound(ind)
  check_error_tag(:bad_arity) do add_transform("hiho", "time", 0, 1, lambda do | | 1.0 end) end
  check_error_tag(:cannot_save) do save_state("/bad/baddy") end
  check_error_tag(:no_such_menu) do add_to_menu(1234, "hi", lambda do | | false end) end
  check_error_tag(:bad_arity) do add_to_main_menu("hi", lambda do |a, b| false end) end
  check_error_tag(:bad_arity) do add_to_menu(1, "hi", lambda do |a, b| false end) end
  check_error_tag(:wrong_type_arg) do set_transform_type(integer2transform(-1)) end
  check_error_tag(:out_of_range) do set_transform_type(integer2transform(123)) end
  check_error_tag(:wrong_type_arg) do help_dialog([0, 1], "hiho") end
  check_error_tag(:wrong_type_arg) do info_dialog([0, 1], "hiho") end
  check_error_tag(:no_such_sound) do edit_header_dialog(1234) end
  check_error_tag(:no_such_file) do open_sound("/bad/baddy.snd") end
  check_error_tag(:no_such_file) do open_raw_sound("/bad/baddy.snd", 1, 22050, Mus_lshort) end
  check_error_tag(:no_such_file) do view_sound("/bad/baddy.snd") end
  check_error_tag(:no_such_file) do make_sampler(0, "/bad/baddy.snd") end
  check_error_tag(:no_such_region) do make_region_sampler(integer2region(1234567), 0) end
  check_error_tag(:no_such_key) do bind_key(12345678, 0, false) end
  check_error_tag(:no_such_key) do bind_key(-1, 0, false) end
  check_error_tag(:no_such_key) do bind_key(12, 17, false) end
  check_error_tag(:no_such_key) do bind_key(12, -1, false) end
  check_error_tag(:no_such_key) do key_binding(12345678, 0) end
  check_error_tag(:no_such_key) do key_binding(-1, 0) end
  check_error_tag(:no_such_key) do key_binding(12, 17) end
  check_error_tag(:no_such_key) do key_binding(12, -1) end
  check_error_tag(:bad_header) do file2array($sf_dir + "bad_chans.snd", 0, 0, 123, Vct.new(123)) end
  check_error_tag(:bad_header) do make_readin($sf_dir + "bad_chans.snd") end
  check_error_tag(:mus_error) do make_iir_filter(30, Vct.new(3)) end
  check_error_tag(:out_of_range) do make_wave_train(:size, 2 ** 30) end
  check_error_tag(:out_of_range) do set_mus_srate(0.0) end
  check_error_tag(:out_of_range) do set_mus_srate(-1000) end
  check_error_tag(:out_of_range) do dot_product(Vct.new(3), Vct.new(3), -1) end
  check_error_tag(:out_of_range) do multiply_arrays(Vct.new(3), Vct.new(3), -1) end
  check_error_tag(:out_of_range) do
    make_delay(3, :initial_element, 0.0, :initial_contents, vct(0.1, 0.2, 0.3))
  end
  check_error_tag(:out_of_range) do
    make_delay(3, :max_size, 100, :initial_contents, vct(0.1, 0.2, 0.3))
  end
  check_error_tag(:out_of_range) do make_table_lookup(:size, 100, :wave, Vct.new(3)) end
  check_error_tag(:out_of_range) do make_wave_train(:size, 100, :wave, Vct.new(3)) end
  check_error_tag(:out_of_range) do make_ssb_am(100, 12345678) end
  check_error_tag(:mus_error) do make_rand(:envelope, [0, 0, 1, 1], :distribution, Vct.new(10)) end
  check_error_tag(:mus_error) do make_rand(:envelope, [0, 0, 1]) end
  check_error_tag(:out_of_range) do make_rand(:envelope, [0, 0, 1, 1], :size, -2) end
  check_error_tag(:out_of_range) do make_rand(:envelope, [0, 0, 1, 1], :size, 1234567890) end
  check_error_tag(:bad_arity) do
    grn = make_granulate
    granulate(grn, false, lambda do |a, s, d| false end)
  end
  check_error_tag(:bad_arity) do
    pv = make_phase_vocoder
    phase_vocoder(pv, false, lambda do | | false end)
  end
  check_error_tag(:bad_arity) do
    pv = make_phase_vocoder
    phase_vocoder(pv, false, false, lambda do | | false end)
  end
  check_error_tag(:bad_arity) do
    pv = make_phase_vocoder
    phase_vocoder(pv, false, false, false, lambda do | | false end)
  end
  check_error_tag(:mus_error) do
    f = make_filter(3, :xcoeffs, $vct_3, :ycoeffs, $vct_3)
    mus_xcoeff(f, 4)
  end
  check_error_tag(:mus_error) do
    f = make_filter(3, :xcoeffs, $vct_3, :ycoeffs, $vct_3)
    mus_ycoeff(f, 4)
  end
  check_error_tag(:mus_error) do
    f = make_filter(3, :xcoeffs, $vct_3, :ycoeffs, $vct_3)
    set_mus_xcoeff(f, 4, 1.0)
  end
  check_error_tag(:mus_error) do
    f = make_filter(3, :xcoeffs, $vct_3, :ycoeffs, $vct_3)
    set_mus_ycoeff(f, 4, 1.0)
  end
  check_error_tag(:mus_error) do make_filter(:ycoeffs, Vct.new(4), :order, 12) end
  check_error_tag(:mus_error) do
    hi = make_oscil
    set_mus_offset(hi, 1)
  end
  check_error_tag(:out_of_range) do make_locsig(:channels, 2 ** 30) end
  check_error_tag(:out_of_range) do make_src(:width, 3000) end
  check_error_tag(:out_of_range) do make_frame(-1) end
  check_error_tag(:mus_error) do
    hi = make_frame(2, 0.1, 0.2)
    frame_ref(hi, 3)
  end
  check_error_tag(:out_of_range) do make_scalar_mixer(0, 0.1) end
  check_error_tag(:mus_error) do
    m = make_mixer(2)
    mixer_ref(m, 3, 4)
  end
  check_error_tag(:bad_arity) do add_colormap("baddy", lambda do | | false end) end
  check_error_tag(:bad_arity) do add_colormap("baddy", lambda do |a, b, c| false end) end
  check_error_tag(:out_of_range) do
    sr = make_src(:input, lambda do |dir| 1.0 end)
    src(sr, 2000000.0)
  end
  check_error_tag(:out_of_range) do partials2polynomial([1, 1], -1) end
  check_error_tag(:out_of_range) do partials2polynomial([1, 1], 3) end
  check_error_tag(:out_of_range) do make_polyshape(440.0, :partials, [1, 1], :kind, -1) end
  check_error_tag(:out_of_range) do make_polyshape(440.0, :partials, [1, 1], :kind, 3) end
  check_error_tag(:wrong_type_arg) do set_mus_header_raw_defaults(1234) end
  check_error_tag(:wrong_type_arg) do set_mus_header_raw_defaults([44100, 2.123, "hi"]) end
  check_error_tag(:wrong_type_arg) do set_with_toolbar(123) end
  check_error_tag(:wrong_type_arg) do set_with_tooltips(123) end
  check_error_tag(:wrong_type_arg) do set_with_menu_icons(123) end
  check_error_tag(:wrong_type_arg) do set_save_as_dialog_src(123) end
  check_error_tag(:wrong_type_arg) do set_save_as_dialog_auto_comment(123) end
  check_error_tag(:wrong_type_arg) do set_with_smpte_label(123) end
  check_error_tag(:wrong_type_arg) do set_ask_about_unsaved_edits(123) end
  check_error_tag(:no_such_mix) do mix_properties(integer2mix(mix_sync_max + 1)) end
  check_error_tag(:no_such_mix) do set_mix_properties(integer2mix(mix_sync_max + 1), 1) end
  # 
  if $with_test_motif
    [:widget_position, :widget_size, :widget_text,
      :hide_widget, :show_widget, :focus_widget].each do |n|
      if (tag = Snd.catch do snd_func(n, [:Widget, 0]) end).first != :no_such_widget
        snd_display("%s of null widget: %s", n, tag)
      end
    end
  end
end

def test_28_03
  new_wave = Vct.new(1)
  [[:make_table_lookup, :table_lookup]].each do |mg, g|
    gen = snd_func(mg, :wave, new_wave)
    snd_func(g, gen, 1.0)
  end
  #
  old_srate = mus_srate
  old_clm_srate = $clm_srate
  [100, 1].each do |n|
    set_mus_srate(n)
    $clm_srate = n
    [:make_oscil,
     :make_asymmetric_fm,
     :make_triangle_wave,
     :make_square_wave,
     :make_pulse_train,
     :make_sawtooth_wave,
     :make_rand,
     :make_rand_interp].each do |g|
      if (res = Snd.catch do snd_func(g, :frequency, 440.0) end).first != :out_of_range
        snd_display("srate %s: %s -> %s", n, g, res.inspect)
      end
    end
  end
  set_mus_srate(old_srate)
  $clm_srate = old_clm_srate
  # now try everything! (all we care about here is that Snd keeps running)
  random_args = [1.5, [0, 1], 1234, true]
  main_args   = [1.5, [0, 1], 1234, $vct_3, $color_95, sqrt(-1.0), $delay_32, :feedback, false]
  few_args    = [1.5, [0, 1], 1234, sqrt(-1.0), $delay_32, true]
  fewer_args  = [1.5, $vct_3, sqrt(-1.0)]
  less_args   = $all_args ? main_args : few_args
  #
  # key args
  #
  if $VERBOSE
    snd_info("keyargs-2-args")
  end
  Keyargs.each do |arg1|
    random_args.each do |arg2|
      Make_procs.each do |n|
        Snd.catch do snd_func(n, arg1, arg2) end
      end
    end
  end
  dismiss_all_dialogs
  if $all_args
    if $VERBOSE
      snd_info("keyargs-3-args")
    end
    random_args.each do |arg1|
      Keyargs.each do |arg2|
        random_args.each do |arg3|
          Make_procs.each do |n|
            Snd.catch do snd_func(n, arg1, arg2, arg3) end
          end
        end
      end
    end
    dismiss_all_dialogs
    if $VERBOSE
      snd_info("keyargs-4-args")
    end
    random_args.each do |arg1|
      Keyargs.each do |arg2|
        random_args.each do |arg3|
          Keyargs.each do |arg4|
            Make_procs.each do |n|
              Snd.catch do snd_func(n, arg1, arg2, arg3, arg4) end
            end
          end
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # 0 Args
  #
  unless Procs00.empty?
    if $VERBOSE
      snd_info("no-args")
    end
    Procs00.each do |n|
      if (tag = Snd.catch do
            snd_func(n)
          end).first == :wrong_number_of_args
        snd_display("procs00: %s %s\n# %s", tag, n, snd_help(n))
      end
    end
    dismiss_all_dialogs
  end
  #
  # set no Args
  #
  unless Set_procs00.empty?
    if $VERBOSE
      snd_info("set-no-args")
    end
    main_args.each do |val|
      Set_procs00.each do |n|
        if (tag = Snd.catch do
              set_snd_func(n, val)
            end).first == :wrong_number_of_args
          snd_display("set_procs00: %s set_%s\n# %s", tag, n, snd_help(n))
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # 1 Arg
  #
  unless Procs01.empty?
    if $VERBOSE
      snd_info("1-arg")
    end
    main_args.each do |arg|
      Procs01.each do |n|
        if (tag = Snd.catch do
              snd_func(n, arg)
            end).first == :wrong_number_of_args
          snd_display("procs01: %s %s\n# %s", tag, n, snd_help(n))
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # set 1 Arg
  #
  unless Set_procs01.empty?
    if $VERBOSE
      snd_info("set-1-arg")
    end
    main_args.each do |arg1|
      main_args.each do |arg2|
        Set_procs01.each do |n|
          if (tag = Snd.catch do
                set_snd_func(n, arg1, arg2)
              end).first == :wrong_number_of_args
            snd_display("set_procs01: %s set_%s\n# %s", tag, n, snd_help(n))
          end
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # 2 Args
  #
  unless Procs02.empty?
    if $VERBOSE
      snd_info("2-args")
    end
    main_args.each do |arg1|
      main_args.each do |arg2|
        Procs02.each do |n|
          if (tag = Snd.catch do
                snd_func(n, arg1, arg2)
              end).first == :wrong_number_of_args
            snd_display("procs02: %s %s\n# %s", tag, n, snd_help(n))
          end
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # set 2 Args
  #
  unless Set_procs02.empty?
    if $VERBOSE
      snd_info("set-2-args")
    end
    less_args.each do |arg1|
      less_args.each do |arg2|
        less_args.each do |arg3|
          Set_procs02.each do |n|
            if (tag = Snd.catch do
                  set_snd_func(n, arg1, arg2, arg3)
                end).first == :wrong_number_of_args
              snd_display("set_procs02: %s set_%s\n# %s", tag, n, snd_help(n))
            end
          end
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # 3 Args
  #
  unless Procs03.empty?
    if $VERBOSE
      snd_info("3-args")
    end
    less_args.each do |arg1|
      less_args.each do |arg2|
        less_args.each do |arg3|
          Procs03.each do |n|
            if (tag = Snd.catch do
                  snd_func(n, arg1, arg2, arg3)
                end).first == :wrong_number_of_args
              snd_display("procs03: %s %s\n# %s", tag, n, snd_help(n))
            end
          end
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # set 3 Args
  #
  unless Set_procs03.empty?
    if $VERBOSE
      snd_info("set-3-args")
    end
    less_args.each do |arg1|
      less_args.each do |arg2|
        less_args.each do |arg3|
          less_args.each do |arg4|
            Set_procs03.each do |n|
              if (tag = Snd.catch do
                    set_snd_func(n, arg1, arg2, arg3, arg4)
                  end).first == :wrong_number_of_args
                snd_display("set_procs03: %s set_%s\n# %s", tag, n, snd_help(n))
              end
            end
          end
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # 4 Args
  #
  unless Procs04.empty?
    if $VERBOSE
      snd_info("4-args")
    end
    few_args.each do |arg1|
      few_args.each do |arg2|
        few_args.each do |arg3|
          few_args.each do |arg4|
            Procs04.each do |n|
              if (tag = Snd.catch do
                    snd_func(n, arg1, arg2, arg3, arg4)
                  end).first == :wrong_number_of_args
                snd_display("procs04: %s %s\n# %s", tag, n, snd_help(n))
              end
            end
          end
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # set 4 Args
  #
  unless Set_procs04.empty?
    if $VERBOSE
      snd_info("set-4-args")
    end
    few_args.each do |arg1|
      few_args.each do |arg2|
        few_args.each do |arg3|
          few_args.each do |arg4|
            few_args.each do |arg5|
              Set_procs04.each do |n|
                if (tag = Snd.catch do
                      set_snd_func(n, arg1, arg2, arg3, arg4, arg5)
                    end).first == :wrong_number_of_args
                  snd_display("set_procs04: %s set_%s\n# %s", tag, n, snd_help(n))
                end
              end
            end
          end
        end
      end
    end
    clear_sincs
    stop_playing
    dismiss_all_dialogs
  end
  #
  # 5 Args
  #
  unless Procs05.empty?
    if $VERBOSE
      snd_info("5-args")
    end
    fewer_args.each do |arg1|
      fewer_args.each do |arg2|
        fewer_args.each do |arg3|
          fewer_args.each do |arg4|
            fewer_args.each do |arg5|
              Procs05.each do |n|
                if (tag = Snd.catch do
                      snd_func(n, arg1, arg2, arg3, arg4, arg5)
                    end).first == :wrong_number_of_args
                  snd_display("procs05: %s %s\n# %s", tag, n, snd_help(n))
                end
              end
            end
          end
        end
      end
    end
    clear_sincs
    dismiss_all_dialogs
  end
  #
  # 6 Args
  #
  unless Procs06.empty?
    if $VERBOSE
      snd_info("6-args")
    end
    fewer_args.each do |arg1|
      fewer_args.each do |arg2|
        fewer_args.each do |arg3|
          fewer_args.each do |arg4|
            fewer_args.each do |arg5|
              fewer_args.each do |arg6|
                Procs06.each do |n|
                  if (tag = Snd.catch do
                        snd_func(n, arg1, arg2, arg3, arg4, arg5, arg6)
                      end).first == :wrong_number_of_args
                    snd_display("procs06: %s %s\n# %s", tag, n, snd_help(n))
                  end
                end
              end
            end
          end
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # 8 Args
  #
  unless Procs08.empty?
    if $VERBOSE
      snd_info("8-args")
    end
    fewer_args.each do |arg1|
      fewer_args.each do |arg2|
        fewer_args.each do |arg3|
          fewer_args.each do |arg4|
            fewer_args.each do |arg5|
              fewer_args.each do |arg6|
                fewer_args.each do |arg7|
                  fewer_args.each do |arg8|
                    Procs08.each do |n|
                      if (tag = Snd.catch do
                            snd_func(n, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
                          end).first == :wrong_number_of_args
                        snd_display("procs08: %s %s\n# %s", tag, n, snd_help(n))
                      end
                    end
                  end
                end
              end
            end
          end
        end
      end
    end
    dismiss_all_dialogs
  end
  #
  # 10 Args
  #
  unless Procs10.empty?
    if $VERBOSE
      snd_info("10-args")
    end
    fewer_args.each do |arg1|
      fewer_args.each do |arg2|
        fewer_args.each do |arg3|
          fewer_args.each do |arg4|
            fewer_args.each do |arg5|
              fewer_args.each do |arg6|
                fewer_args.each do |arg7|
                  fewer_args.each do |arg8|
                    fewer_args.each do |arg9|
                      fewer_args.each do |arg10|
                        Procs10.each do |n|
                          if (tag = Snd.catch do
                                snd_func(n, arg1, arg2, arg3, arg4, arg5,
                                         arg6, arg7, arg8, arg9, arg10)
                              end).first == :wrong_number_of_args
                            snd_display("procs10: %s %s\n# %s", tag, n, snd_help(n))
                          end
                        end
                      end
                    end
                  end
                end
              end
            end
          end
        end
      end
    end
    clear_sincs
    dismiss_all_dialogs
  end
  set_ask_about_unsaved_edits(false)
end

def test_28_04
  if defined? mus_audio_reinitialize
    mus_audio_reinitialize
  end
  File.exists?("test.snd") and File.chmod(0644, "test.snd") and File.unlink("test.snd")
  file_copy("oboe.snd", "test.snd")
  ind = open_sound("test.snd")
  delete_file("test.snd")
  if (tag = Snd.catch do update_sound(ind) end).first != :cant_update_file
    snd_display("update_sound after deletion: %s", tag)
  end
  delete_sample(10)
  if (tag = Snd.catch do save_sound(ind) end).first != :cannot_save
    snd_display("save file deleted: %s", tag)
  end
  close_sound(ind)
  # 
  file_copy("oboe.snd", "test.snd")
  ind = open_sound("test.snd")
  reg = select_all
  delete_file("test.snd")
  view_regions_dialog
  close_sound(ind)
  # 
  file_copy("oboe.snd", "test.snd")
  ind = open_sound("test.snd")
  File.chmod(0400, "test.snd")
  delete_sample(10)
  if (tag = Snd.catch do save_sound(ind) end).first != :cannot_save
    snd_display("save protected sound msg: %s", tag)
  end
  close_sound(ind)
  File.chmod(0644, "test.snd")
  delete_file("test.snd")
  # 
  res = Snd.catch do open_sound("test.snd") end
  if res.first != :no_such_file and res.first != :mus_error
    snd_display("open read_protected sound: %s", res)
    sound?(res.car) and close_sound(res.car)
  end
  # 
  file_copy("oboe.snd", "test.snd")
  File.chmod(0400, "test.snd")
  ind = open_sound("oboe.snd")
  delete_sample(10)
  if (res = Snd.catch do save_sound_as("test.snd") end).first != :cannot_save
    snd_display("save_as write_protected sound: %s", res)
  end
  close_sound(ind)
  File.chmod(0644, "test.snd")
  delete_file("test.snd")
end

def test_28
  set_with_background_processes(true)
  set_remember_sound_state(false)
  if $VERBOSE
    snd_info("procs  prcs/set-prcs")
    snd_info("====================")
    snd_info("proc00: %3d/%3d", Procs00.length, Set_procs00.length)
    snd_info("proc01: %3d/%3d", Procs01.length, Set_procs01.length)
    snd_info("proc02: %3d/%3d", Procs02.length, Set_procs02.length)
    snd_info("proc03: %3d/%3d", Procs03.length, Set_procs03.length)
    snd_info("proc04: %3d/%3d", Procs04.length, Set_procs04.length)
    snd_info("proc05: %3d", Procs05.length)
    snd_info("proc06: %3d", Procs06.length)
    snd_info("proc07: %3d", Procs07.length)
    snd_info("proc08: %3d", Procs08.length)
    snd_info("proc10: %3d", Procs10.length)
  end
  if $with_test_gui
    unless Procs.empty?
      snd_display("procs: %s?", Procs)
    end
    unless Set_procs.empty?
      snd_display("set_procs: %s?", Set_procs)
    end
  end
  reset_almost_all_hooks
  test_28_00
  if $with_test_gui
    test_28_01
    test_28_02
  end
  # FIXME (INFO)
  # with snd-gtk test_28_04 should come before test_28_03 because of:
  # (snd-ruby-xg:2371): Gdk-WARNING **: GdkWindow 0x160015b unexpectedly destroyed
  # ...
  test_28_04
  test_28_03
end

# ---------------- test all done

def test_30
  test_05_25
end

main_test

# snd-test.rb ends here
