# snd-test.rb: Snd Ruby code and tests -*- snd-ruby -*-
#
# 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 24: user-interface
# test 28: errors
# test all done
# snd -noinit -load snd-test.rb # all tests
# snd -noinit -load snd-test.rb 3 7 20 # test 3, 7, 20
require "examp"
require "ws"
require "hooks"
require "extensions"
require "mix"
require "marks"
require "pvoc"
require "bird"
require "v"
require "poly"
require "rubber"
include Rubber
unless provided? :snd_nogui
require "rgb"
provided?(:snd_motif) and (not provided?(:xm)) and require("libxm")
provided?(:snd_gtk) and (not provided?(:xg)) and require("libxg")
require "snd-xm"
include Snd_XM
require "popup"
require "xm-enved"
require "draw"
require "musglyphs"
require "effects"
end
$my_snd_error_hook = nil
$original_save_dir = (save_dir or "/zap/snd")
$original_temp_dir = (temp_dir or "/zap/tmp")
$original_prompt = listener_prompt
$sample_reader_tests = 300
$default_file_buffer_size = 65536
$home_dir = ENV["HOME"]
$audio_amp_zero = 0.0
$with_backtrace = false
$sf_dir = "/home/bil/sf1/"
$with_exit = true
$full_test = true
$with_big_file = false
$bigger_snd = "/home/bil/zap/sounds/bigger.snd"
$all_args = false
$test00 = true
$test01 = true
$test02 = true
$test03 = true
$test04 = true
$test05 = true
$test06 = true
$test07 = true
$test08 = true
$test09 = true
$test10 = true
$test11 = true
$test12 = true
$test13 = true
$test14 = true
$test15 = true
$test16 = true
$test17 = true
$test18 = true
$test19 = true
$test20 = true
$test21 = true
$test22 = true
$test23 = true
$test24 = true
$test25 = true
$test26 = true
$test27 = true
$test28 = true
reset_all_hooks
# global variables may be overridden in `pwd`/.sndtestrc or ~/.sndtestrc
load_init_file(".sndtestrc")
# 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)
def main_test
test00
test01
test02
test03
test04
test05
test06
test07
test08
test09
test10
test11
test12
test13
test14
test15
test16
test17
test18
test19
test20
test21
# test 22: run skipped
test23
test24
# test25
# test26
# test27
test28
end
def snd_info(*args)
args[0] = String(args[0])
str = format(*args)
if provided? :snd_nogui
clm_print("# %s\n", str)
else
clm_print("\n# %s", str)
$stderr.printf("# %s\n", str)
end
nil
end
if $with_backtrace
def snd_display(*args)
snd_info(*args)
$stderr.printf("%s\n", verbose_message_string(true, "# "))
nil
end
else
alias snd_display snd_info
end
# $snd_error_hook
def snd_display_error(*args)
snd_info(*args)
$stderr.printf("%s\n", verbose_message_string(true, "# "))
nil
end
# command line args: the last arg(s) may be zero or many test numbers
# snd -noinit -load snd-test.rb 0 17 18 28
if script_arg.positive?
if script_args[script_arg - 1] !~ /^-l.*/
Snd.display("script_args[%d]: %s %s", script_arg - 1, script_args[script_arg - 1], script_args)
end
if script_args[script_arg] !~ /snd-test/
Snd.display("script_args[%d]: %s %s", script_arg, script_args[script_arg], script_args)
end
args = script_args[script_arg..-1].map do |arg|
Snd.catch(:all, nil) do Integer(arg) end.first
end.compact
set_script_arg(script_args.length - 1)
if args.length.positive?
29.times do |i| eval(format("$test%02d = false", i)) end
args.each do |arg|
if arg.between?(0, 28)
eval(format("$test%02d = true", arg))
$with_exit = true
$full_test = false
end
end
end
end
def fneq_err(f1, f2, err = 0.001)
(Float(f1) - Float(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
# 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 cneq(a, b)
if complex?(a) and complex?(b)
fneq(a.real, b.real) or fneq(a.image, b.image)
else
true
end
end
def rs(val)
random(1.0) < val
end
def log0(try_it = true)
try_it ? log(0) : 0.0
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
rargs == args
else
rargs.abs <= args
end
else
false
end
end
def set_arity_ok(func, args)
arity_ok("set_#{func}", args)
end
# let proc $snd_error_hook("sndtestrc") untouched
# perhaps defined in .sndtestrc
def reset_almost_all_hooks
reset_all_hooks
if proc? $my_snd_error_hook then $snd_error_hook.add_hook!("sndtestrc", &$my_snd_error_hook) end
end
def dismiss_all_dialogs
if provided? :xm or provided? :xg
dialog_widgets.each do |dialog|
if array?(dialog)
if symbol?(dialog.car)
if provided? :snd_motif
if RXtIsManaged(dialog) then RXtUnmanageChild(dialog) end
elsif provided? :snd_gtk
Rgtk_widget_hide(dialog)
end
else
dialog.each do |d|
if symbol?(d.car)
if provided? :snd_motif
if RXtIsManaged(d) then RXtUnmanageChild(d) end
elsif provided? :snd_gtk
Rgtk_widget_hide(d)
end
end
end
end
end
end
end
end
set_mus_file_buffer_size($default_file_buffer_size)
set_with_background_processes(false)
set_show_backtrace(true)
make_proc_with_setter(:mus_audio_playback_amp,
lambda do
vals = make_vct(32)
mus_audio_mixer_read(Mus_audio_default, Mus_audio_amp, 0, vals)
ch0_amp = vals[0]
mus_audio_mixer_read(Mus_audio_default, Mus_audio_amp, 1, vals)
ch1_amp = vals[0]
[ch0_amp, ch1_amp]
end,
lambda do |val|
vals = make_vct(32)
vals[0] = val
mus_audio_mixer_write(Mus_audio_default, Mus_audio_amp, 0, vals)
mus_audio_mixer_write(Mus_audio_default, Mus_audio_amp, 1, vals)
val
end)
$orig_audio_amp = mus_audio_playback_amp.first
set_mus_audio_playback_amp($audio_amp_zero)
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)
File.exists?(f1) and system(format("cp %s %s", f1, f2))
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 = true, &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
ret
end
def snd_error_test
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
end
snd_error_test if $full_test
show_listener
set_window_x(600)
set_window_y(10)
file_copy(ENV["HOME"] + "/.snd", ENV["HOME"] + "/dot-snd")
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 inspect
format("#<%s: real: %8.3f, utime: %8.3f, stime: %8.3f>", self.class, @real, @utime, @stime)
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
def display(msg)
str = if msg
" (" + msg + ")"
else
""
end
snd_info("real: %1.3f, utime: %1.3f, stime: %1.3f%s", @real, @utime, @stime, str)
end
def run(msg, &body)
start
body.call
stop
if msg then display(msg) end
[self.real, self.utime, self.stime]
end
end
def with_time(msg = nil, &body)
Snd_test_time.new.run(msg, &body)
end
def finish_snd_test
$overall_start_time.stop
Snd.regions.apply(:forget_region)
Snd.tracks.apply(:free_track)
set_view_files_sort(0)
clear_sincs
stop_playing
reset_almost_all_hooks
snd_info("all done!")
snd_info
unless $timings.empty?
$timings.each do |tst| snd_info("test %2d %s", tst.first, tst.last.inspect) end
end
snd_info("total %s\n", $overall_start_time.inspect)
show_listener
save_listener("test.output")
show_listener
dot_snd = ENV["HOME"] + "/dot-snd"
File.exists?(dot_snd) and File.rename(dot_snd, ENV["HOME"] + "/.snd")
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_display("%s temporary file%s deleted", fs.zero? ? "no" : fs, fs.between?(0, 1) ? "" : "s")
mus_sound_prune
["aaa.eps",
"envs.save",
"fmv.snd",
"fmv.wav",
"fmv0.snd",
"fmv1.snd",
"fmv2.snd",
"fmv3.snd",
"fmv4.reverb",
"fmv4.snd",
"hiho.marks",
"hiho.snd",
"hiho.snd",
"hiho.tmp",
"hiho.wave",
"ho",
"new.snd",
"oboe.marks",
"obtest.snd.stereo",
"snd.eps",
"test-1.snd",
"test-2.snd",
"test-macros.scm",
"test.aiff",
"test.data",
"test.rev",
"test.reverb",
"test.snd",
"test.snd.snd",
"test.wav",
"test.xpm",
"test2.snd",
"test3.snd",
"tmp.snd",
"with-mix.snd",
"1",
"gtk-errors",
"accelmap"].each do |file|
delete_file(file)
end
["mus10.snd.snd",
"ieee-text-16.snd.snd",
"trumps22.adp.snd",
"oki.wav.snd",
"nasahal.avi.snd",
"hcom-16.snd.snd",
"ce-c3.w02.snd",
"oboe.g723_24.snd",
"oboe.g723_40.snd",
"oboe.g721.snd",
"wood.sds.snd",
"o2_dvi.wave.snd",
"nist-shortpack.wav.snd"].each do |file|
with_file(file, false) do delete_file(file) end
end
set_mus_audio_playback_amp($orig_audio_amp)
end
# map_chan* procedure
$init_channel = lambda do |y| 1.0 end
$timings = Array.new(0)
make_hook("$before_test_hook", 1, "snd-test") do |n|
$timings.push([n, Snd_test_time.new])
snd_info("test %d", n)
set_show_backtrace(false)
end
make_hook("$after_test_hook", 1, "snd-test") do |n|
$timings.last.last.stop
if sounds
snd_info("test %d: open sounds: %s", n, short_file_name(true))
Snd.sounds.apply(:close_sound)
end
dismiss_all_dialogs
snd_info("test %d done\n#", n)
end
unless hook? $before_test_hook
snd_display("$before_test_hook not a hook: %s?", $before_test_hook.inspect)
end
unless hook? $after_test_hook
snd_display("$after_test_hook not a hook: %s?", $after_test_hook.inspect)
end
snd_info("=== Snd version: %s", snd_version)
snd_info("=== Ruby version: %s (%s) [%s]", RUBY_VERSION, RUBY_RELEASE_DATE, RUBY_PLATFORM)
snd_info
snd_info("%s\n#", Time.now.localtime.strftime("%a %d-%b-%Y %H:%M %Z"))
$overall_start_time = Snd_test_time.new
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]))
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? then 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]))
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? then 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? then 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
RXtCallCallbacks(button, RXmNactivateCallback,
let(RXmPushButtonCallbackStruct()) do |but|
Rset_click_count(but, 0)
Rset_event(but, let(RXEvent(RButtonPress)) do |e|
Rset_state(e, (bits or 0))
e
end)
but
end)
else
snd_display("pushbutton %s has no active callbacks", RXtName(button))
end
else
if RXmIsToggleButton(button) or RXmIsToggleButtonGadget(button)
if RXtHasCallbacks(button, RXmNvalueChangedCallback) == RXtCallbackHasSome
RXtCallCallbacks(button, RXmNvalueChangedCallback,
let(RXmToggleButtonCallbackStruct()) do |tgl|
Rset_set(tgl, value)
Rset_event(tgl, let(RXEvent(RButtonPress)) do |e|
Rset_state(e, (bits or 0))
e
end)
tgl
end)
else
snd_display("togglebutton %s has no valueChanged callbacks", RXtName(button))
end
else
if RXmIsArrowButton(button)
if RXtHasCallbacks(button, RXmNactivateCallback) == RXtCallbackHasSome
RXtCallCallbacks(button, RXmNactivateCallback,
let(RXmArrowButtonCallbackStruct()) do |arr|
Rset_click_count(arr, 0)
Rset_event(arr, let(RXEvent(RButtonPress)) do |e|
Rset_state(e, (bits or 0))
e
end)
arr
end)
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)
i