# examp.rb -- Guile -> Ruby translation -*- snd-ruby -*-
# Translator/Author: Michael Scholz <scholz-micha@gmx.de>
# Created: Wed Sep 04 18:34:00 CEST 2002
# Changed: Mon Jan 09 02:50:48 CET 2006
# Commentary:
#
# Extensions to Ruby:
#
# provided?(feature)
# provide(feature)
# features(all = nil)
#
# array?(obj) alias list?(obj)
# hash?(obj)
# string?(obj)
# regexp?(obj)
# symbol?(obj)
# number?(obj)
# integer?(obj)
# float?(obj)
# rational?(obj)
# complex?(obj)
# boolean?(obj)
# proc?(obj)
# thunk?(obj)
# method?(obj)
# func?(obj)
# mus?(obj)
# get_func_name(n)
# assert_type(condition, obj, pos, msg)
# identity(arg)
# ignore(*rest)
# enum(*names)
#
# class Object
# null?
# function?(obj)
# snd_func(name, *rest, &body)
# set_snd_func(name, val, *rest, &body)
# snd_apropos(str_or_sym)
#
# NilClass(arg)
# Fixnum(arg)
#
# class NilClass
# each
# apply(func, *rest, &body)
# empty?
# zero?
# nonzero?
# to_vct
# to_vector
# to_poly
# +(other)
# -(other)
# *(other)
#
# backward compatibility methods:
# String#to_sym, Symbol#to_sym
# make_array(len, init, &body)
# Array#insert
# Float#step
# Range#step
# Enumerable#each_index
# Enumerable#zip
#
# class Array
# to_pairs
# each_pair do |x, y| ... end
# to_string(len)
# first=(val)
# last=(val)
# pick
# rand
# rand!
# add(other)
# add!(other)
# subtract(other)
# subtract!(other)
# multiply(other)
# multiply!(other)
# offset(scl)
# offset!(scl)
# scale(scl)
# scale!(scl)
# to_vector
# car
# car=
# cadr
# cadr=
# caddr
# caddr=
# cadddr
# cadddr=
# caddddr
# caddddr=
# cdr
# step(n)
# apply(func, *rest, &body)
# remove_fi(&body)
#
# class Vec < Array
# Vec[]
# initialize(len, init, &body)
# inspect
# to_s
# to_vector
# +(other)
# -(other)
# *(other)
#
# Vec(obj)
# make_vector(len, init, &body)
# vector?(obj)
# vector(*args)
#
# class String
# to_vector
# to_vct
#
# Vct(obj)
# make_vct!(len, init) do |i| ... end
#
# class Vct
# Vct[]
# to_sound_data(chn, chns)
# to_vct
# to_vector
# apply(func, *rest, &body)
# +(other) handles self.offset (Numeric) and self.add (Array, Vec, Vct)
# -(other) handles self.offset (Numeric) and self.subtract (Array, Vec, Vct)
# *(other) handles self.scale (Numeric) and self.multiply (Array, Vec, Vct)
# step(n)
# [](idx, size)
#
# class Fixnum
# +(other) handles other.offset on Vct, Array, and Vec
# *(other) handles other.scale on Vct, Array, and Vec
#
# class Float
# +(other) handles other.offset on Vct, Array, and Vec
# *(other) handles other.scale on Vct, Array, and Vec
#
# SoundData(ary) can be used to reread evaled output from sound_data2string
# sound_data2string(sd) produces a string which can be evaled and reread with SoundData
#
# class SoundData
# to_vct(chn)
# to_a
# inspect
# length
# each(chn)
# each_with_index(chn)
# map(chn)
# map!(chn)
# maxamp(chn)
#
# mus_a0(gen)
# set_mus_a0(gen, val)
# mus_a1(gen)
# set_mus_a1(gen, val)
# mus_a2(gen)
# set_mus_a2(gen, val)
# mus_b1(gen)
# set_mus_b1(gen, val)
# mus_b2(gen)
# set_mus_b2(gen, val)
#
# class Mus
# run(arg1 = 0.0, arg2 = 0.0)
# apply(*rest)
# inspect
# close
# xcoeff=(index, val)
# ycoeff=(index, val)
# a0 a0=(val)
# a1 a1=(val)
# a2 a2=(val)
# b1 b1=(val)
# b2 b2=(val)
#
# class Musgen base class for generators written in Ruby
# initialize
# inspect
# to_s
# run(val1 = 0.0, val2 = 0.0)
# apply(*rest)
# eql?(other)
# reset
#
# class Numeric
# positive?
# negative?
#
# class Integer
# even?
# odd?
# prime?
#
# module Enumerable
# map_with_index do |x, i| ... end
# map_with_index! do |x, i| ... end
# cycle
# cycle=(val)
#
# as_one_edit_rb(*origin, &body)
# map_channel_rb(beg, dur, snd, chn, edpos, edname, &body)
# map_chan_rb(beg, dur, edpos, snd, chn, &body)
#
# with_silence(exception) do |old_verbose, old_debug| ... end
#
# module Info
# description=(text)
# description
#
# class Proc
# to_method(name, klass)
# to_str
# to_body
# source
# source=
#
# make_proc2method(name, prc)
# make_proc_with_setter(name, getter, setter)
# make_proc_with_source(string, bind)
# proc_source(prc) set_proc_source(prc, val)
#
# Multi-line input to the Snd listener and Emacs/inf-snd.el
#
# $emacs_eval_hook.call(line)
# run_emacs_eval_hook(line)
#
# class Snd_eval
# Snd_eval.count_level(line)
#
# class Snd_prompt
# initialize(level)
# inspect
# update(level)
# reset
#
# start_emacs_eval(file)
# start_listener_eval(file)
# stop_emacs_eval
# stop_listener_eval
#
# Debugging resp. inspecting local variables
#
# debug_properties(name) set_debug_properties(name, val)
# debug_property(key, name) set_debug_property(key, val, name)
# debug_binding(name) set_debug_binding(bind, name)
# display_all_variables(name)
# each_variables(&body)
#
# let(*rest) do |*rest| ... end
#
# Utilities:
#
# close_sound_extend(snd)
# times2samples(start, dur)
# random(n)
# logn(r, b)
# car(v), cadr(v), caddr(v), cdr(v)
# warning(*args), die(*args), error(*args)
# clm_message(*args), message(*args), debug(*args), debug_trace(*args)
#
# class Snd
# Snd.add_sound_path(path)
# Snd.open_from_path(fname)
# Snd.find_from_path(fname)
# Snd.fullname(fname)
# Snd.load_path
# Snd.message(*args)
# Snd.display(*args)
# Snd.warning(*args)
# Snd.die(*args)
# Snd.error(*args)
# Snd.debug(*args)
# Snd.debug_trace(*args)
# Snd.sounds
# Snd.regions
# Snd.tracks
# Snd.marks(snd, chn)
# Snd.snd(snd)
# Snd.chn(chn)
# Snd.catch(tag, retval)
# Snd.throw(tag, *rest)
# Snd.raise(tag, *rest)
#
# snd_catch(tag, retval)
# snd_throw(tag, *rest)
# snd_raise(tag, *rest)
#
# gloop(*args) do |args| ... end
# get_args(args, key, default)
# get_shift_args(args, key, default)
# get_class_or_key(args, klass, key, default)
# optkey(args, *rest)
# load_init_file(file)
#
# edit_list_proc_counter
# set_edit_list_proc_counter
#
# module Examp (examp.scm)
# selection_rms
# region_rms(n = 0)
# window_samples(snd = false, chn = false)
# display_energy(snd = false, chn = false)
# display_db(snd = false, chn = false)
# window_rms
# fft_peak(snd, chn, scale)
# finfo(file)
# correlate(snd, chn, y0, y1)
#
# zoom_spectrum(snd, chn, y0, y1)
# zoom_fft(snd, chn, y0, y1)
# superimpose_ffts(snd, chn, y0, y1)
# locate_zero(limit)
# shell(*cmd)
#
# mpg(mpgfile, rawfile)
# read_ogg(filename)
# write_ogg(snd)
# read_speex(filename)
# write_speex(snd)
# read_flac(filename)
# write_flac(snd)
# read_ascii(in_filename, out_filename, out_type, out_format, out_srate)
# auto_dot(snd, chn, y0, y1)
#
# first_mark_in_window_at_left
# flash_selected_data(interval)
# mark_loops
# do_all_chans(origin, &func)
# update_graphs
# do_chans(*origin, &func)
# do_sound_chans(*origin, &func)
# every_sample?(&func)
# sort_samples(nbins)
# place_sound(mono_snd, stereo_snd, pan_env)
#
# fft_edit(bottom, top, snd = false, chn = false)
# fft_squelch(squelch, snd = false, chn = false)
# fft_cancel(lo_freq, hi_freq, snd = false, chn = false)
# ramp(gen, up)
# make_ramp(size = 128)
# squelch_vowels(snd = false, chn = false)
# fft_env_data(fft_env, snd = false, chn = false)
# fft_env_edit(fft_env, snd = false, chn = false)
# fft_env_interp(env1, env2, interp, snd = false, chn = false)
# fft_smoother(cutoff, start, samps, snd = false, chn = false)
#
# comb_filter(scaler, size)
# comb_chord(scaler, size, amp, interval_one = 0.75, interval_two = 1.2)
# zcomb(scaler, size, pm)
# notch_filter(scaler, size)
# formant_filter(radius, freq)
# formants(r1, f1, r2, f2, r3, f3)
# moving_formant(radius, move)
# osc_formants(radius, bases, amounts, freqs)
# echo(scaler, secs)
# zecho(scaler, secs, freq, amp)
# flecho(scaler, secs)
# ring_mod(freq, gliss_env)
# am(freq)
# vibro(speed, depth)
# hello_dentist(freq, amp, snd = false, chn = false)
# fp(sr, osamp, osfreq, snd = false, chn = false)
# compand(doc = false)
# compand_channel(beg = 0, dur = false, snd = false, chn = false, edpos = false)
# expsrc(rate, snd = false, chn = false)
# expsnd(gr_env, snd = false, chn = false)
# cross_synthesis(cross_snd, amp, fftsize, r)
# voiced2unvoiced(amp, fftsize, r, temp, snd = false, chn = false)
# pulse_voice(cosin, freq, amp, fftsize, r, snd, chn)
# cnvtest(snd0, snd1, amp)
#
# swap_selection_channels
# make_sound_interp(start, snd = false, chn = false)
# sound_interp(func, loc)
# sound_via_sound(snd1, snd2)
# env_sound_interp(envelope, time_scale = 1.0, snd = false, chn = false)
# title_with_date
# filtered_env(en, snd = false, chn = false)
#
# class Mouse
# initialize
# press(snd, chn, button, state, x, y)
# drag(snd, chn, button, state, x, y)
#
# files_popup_buffer(type, position, name)
#
# class Snd_buffers
# initialize
# switch_to_buffer
# open(snd)
# close(snd)
#
# find_click(loc)
# remove_clicks
# search_for_click
# zero_plus
# next_peak
# find_pitch(pitch)
# file2vct(file)
# add_notes(notes, snd = false, chn = false)
# region_play_list(data)
# region_play_sequence(data)
# replace_with_selection
# explode_sf2
#
# class Next_file
# initialize
# open_next_file_in_directory
# click_middle_button_to_open_next_file_in_directory
#
# chain_dsps(start, dur, *dsps)
#
# module Cursor_follows_play
# local_dac_func(data)
# local_start_playing_func(snd)
# local_stop_playing_func(snd)
# current_cursor(snd, chn)
# set_current_cursor(val, snd, chn)
# original_cursor(snd, chn)
# set_original_cursor(val, snd, chn)
# if_cursor_follows_play_it_stays_where_play_stopped(enable = true)
#
# smooth_channel_via_ptree(beg = 0, dur = false, snd = false, chn = false, edpos = false)
# ring_modulate_channel(freq, beg = 0, dur = false, snd = false, chn = false, edpos = false)
# scramble_channels(*new_order)
# scramble_channel(silence)
#
# reverse_by_blocks(block_len, snd, chn)
# reverse_with_blocks(block_len, snd, chn)
#
# class Moog_filter < Musgen (moog.scm)
# initialize(freq, q)
# frequency=(freq)
# filter(insig)
#
# module Moog
# make_moog_filter(freq = 440.0, q = 0)
# moog_filter(moog, insig = 0.0)
# moog(freq, q)
#
# Code:
unless defined? $LOADED_FEATURES then alias $LOADED_FEATURES $" end
def provided?(feature)
assert_type((symbol?(feature) or string?(feature)), feature, 0, "a symbol or a string")
$LOADED_FEATURES.map do |f| File.basename(f) end.member?(feature.to_s.tr("_", "-"))
end
def provide(feature)
assert_type((symbol?(feature) or string?(feature)), feature, 0, "a symbol or a string")
$LOADED_FEATURES.push(feature.to_s)
end
def features(all = nil)
if all
$LOADED_FEATURES.map do |f| File.basename(f) end
else
$LOADED_FEATURES.map do |f|
next if f.include?("/") or f.include?(".")
f
end.compact
end
end
def make_polar(r, theta)
Complex.new(cos(theta) * r, sin(theta) * r)
end
def make_rectangular(re, im)
Complex.new(re, im)
end
def array?(obj)
obj.kind_of?(Array)
end
alias list? array?
def hash?(obj)
obj.kind_of?(Hash)
end
def string?(obj)
obj.kind_of?(String)
end
def regexp?(obj)
obj.kind_of?(Regexp)
end
def symbol?(obj)
obj.kind_of?(Symbol)
end
def number?(obj)
obj.kind_of?(Numeric)
end
def integer?(obj)
obj.kind_of?(Fixnum)
end
def float?(obj)
obj.kind_of?(Float)
end
def rational?(obj)
obj.kind_of?(Rational)
end
def complex?(obj)
obj.kind_of?(Complex)
end
def boolean?(obj)
obj.kind_of?(TrueClass) or obj.kind_of?(FalseClass)
end
def proc?(obj)
obj.kind_of?(Proc)
end
def thunk?(obj)
obj.kind_of?(Proc) and obj.arity.zero?
end
def method?(obj)
obj.kind_of?(Method)
end
def func?(obj)
obj.kind_of?(String) or obj.kind_of?(Symbol)
end
def mus?(obj)
obj.kind_of?(Mus)
end
def binding?(obj)
obj.kind_of?(Binding)
end
def get_func_name(n = 1)
if ca = caller(n)[0].scan(/^.*:in `(.*)'/).first
ca.first
else
"top_level"
end
end
def assert_type(condition, obj, pos, msg)
condition or raise(TypeError, format("%s: wrong type arg %d, %s, wanted %s",
get_func_name(2), pos, obj.inspect, msg))
end
def identity(arg)
arg
end
def ignore(*rest)
nil
end
# backward compatibility aliases and constants (mostly from snd7.scm)
if provided? :snd
alias save_options save_state
alias delete_samples_with_origin delete_samples
alias default_output_type default_output_header_type
alias default_output_format default_output_data_format
alias previous_files_sort view_files_sort
alias preload_directory add_directory_to_view_files_list
alias preload_file add_file_to_view_files_list
alias $previous_files_select_hook $view_files_select_hook
alias recorder_in_format recorder_in_data_format
alias recorder_out_format recorder_out_data_format
alias recorder_out_type recorder_out_header_type
Sort_files_by_name = 0
Sort_files_by_date = 2
Sort_files_by_size = 4
Sort_files_by_entry = -1
end
# enum("foo", :bar, "FOO_BAR")
# produces three constants
# Foo == 0
# Bar == 1
# FOO_BAR == 2
def enum(*names)
names.flatten.map_with_index do |name, i|
const_name = name.to_s
if const_name[0].between?(?a, ?z)
const_name[0] += ?A - ?a
end
Object.const_set(const_name, i)
const_name
end
end
class Object
def null?
self.nil? or
(self.respond_to?(:zero?) and self.zero?) or
(self.respond_to?(:empty?) and self.empty?) or
(self.respond_to?(:length) and self.length.zero?)
end
def function?(obj)
func?(obj) and self.method(obj)
rescue
false
end
# Float(nil) ==> 0.0 like Integer(nil) ==> 0
def new_Float(numb)
if numb.kind_of?(NilClass)
0.0
else
old_Float(numb)
end
end
alias old_Float Float
alias Float new_Float
def snd_func(name, *rest, &body)
assert_type(func?(name), name, 0, "a string or a symbol")
send(name.to_s, *rest, &body)
end
def set_snd_func(name, val, *rest, &body)
assert_type(func?(name), name, 0, "a string or a symbol")
send(format("set_%s", name.to_s), val, *rest, &body)
end
# snd_apropos(str_or_sym)
# if `str_or_sym' is a symbol, returns snd_help result,
# if `str_or_sym' is a string or regexp it looks in
# self.public_methods,
# self.protected_methods,
# self.private_methods,
# Object.constants, and
# Kernel.global_variables and returns an array of strings or nil.
#
# [].snd_apropos(/^apply/) ==> ["apply", "apply_controls"]
# vct(0).snd_apropos("subseq") ==> ["subseq", "vct_subseq"]
# snd_apropos(/^mus_sound/) ==> ["mus_sound_...", ...]
def snd_apropos(str_or_sym)
case str_or_sym
when Symbol
snd_help(str_or_sym)
when String, Regexp
res = []
[self.public_methods,
self.protected_methods,
self.private_methods,
Object.constants,
Kernel.global_variables].each do |m| res += m.grep(/#{str_or_sym}/) end
res
else
nil
end
end
end
def NilClass(arg)
nil
end
alias Fixnum Integer
class NilClass
# FIXME (dangerous)
# def method_missing(id, *args, &body)
# nil
# end
def each
nil
end
def apply(func, *rest, &body)
nil
end
def empty?
true
end
# Integer(nil) ==> 0
def zero?
true
end
def nonzero?
false
end
def to_vct
vector2vct([])
end
def to_vector
vector()
end
def to_poly
poly()
end
def +(other)
other
end
def -(other)
other
end
def *(other)
snd_func(other.class.name, nil)
end
end
# If $DEBUG = true, on older Ruby versions warnings occur about
# missing NilClass#to_str and Symbol#to_str
if $DEBUG and RUBY_VERSION < "1.8.0"
class Object
def method_missing(id, *args)
if id == :to_str
self.class.class_eval do define_method(id, lambda do self.to_s end) end
id.id2name
else
raise(NameError, format("[version %s] undefined method `%s'", RUBY_VERSION, id.id2name))
end
end
end
end
class String
def to_sym
self.intern
end unless defined? "a".to_sym
end
class Symbol
def to_sym
self
end unless defined? :a.to_sym
end
alias object_id __id__ unless defined? object_id
# with_silence(exception) do |old_verbose, old_debug| ... end
#
# subpress debug messages (mostly on older Ruby versions)
#
# with_silence do $global_var ||= value end
# with_silence(LoadError) do require("nonexistent.file") end
def with_silence(exception = StandardError)
old_verbose = $VERBOSE
old_debug = $DEBUG
$VERBOSE = false
$DEBUG = false
ret = if block_given?
begin
yield(old_verbose, old_debug)
rescue exception
false
end
else
false
end
$VERBOSE = old_verbose
$DEBUG = old_debug
ret
end
# Provides descriptions of instances of classes, see nb.rb,
# xm-enved.rb, etc.
#
# m = lambda do |*args| puts args end
# m.info = "my description"
# puts m.info
module Info
def description=(val)
@description = val.to_s
end
alias info= description=
def description
if defined?(@description) and string?(@description) and (not @description.empty?)
@description
else
"no description available"
end
end
alias info description
end
require "ws"
alias snd_help get_help unless defined? snd_help
$array_print_length = 10
def print_length
$array_print_length
end unless defined? print_length
def set_print_length(val)
$array_print_length = val
end unless defined? set_print_length
# Older Ruby versions lack Array.new(10) do |i| ... end
# make_array
# make_array(10)
# make_array(10, 1.0)
# make_array(10) do |i| ... end
def make_array(len = 0, init = nil)
assert_type((number?(len) and len >= 0), len, 0, "a number")
len = Integer(len)
if block_given?
Array.new(len, init).map_with_index do |x, i| yield(i) end
else
Array.new(len, init)
end
end
class Array
def insert(pos, *args)
unless args.empty?
if pos < 0
pos = self.length - (pos.abs - 1)
end
tmp = self.dup
self[pos, args.length] = args
self[pos + args.length..-1] = tmp[pos..-1]
end
self
end unless defined? [].insert
# [0.0, 0.0, 0.5, 0.2, 1.0, 1.0].to_pairs --> [[0.0, 0.0], [0.5, 0.2], [1.0, 1.0]]
def to_pairs
ary = []
if self.length.even?
0.step(self.length - 1, 2) do |i|
ary.push([self[i], self[i + 1]])
end
end
ary
end
# [0.0, 0.0, 0.5, 0.2, 1.0, 1.0].each_pair do |x, y| print x, " ", y, "\n" end
# --> 0.0 0.0
# 0.5 0.2
# 1.0 1.0
def each_pair
ary = []
if self.length.even?
0.step(self.length - 1, 2) do |i|
ary.push(yield([self[i], self[i + 1]]))
end
end
ary
end
# prints flat float array more prettily
def to_string(len = print_length)
ary = self.flatten
str = "["
ary.each_with_index do |val, i|
if i < len
str += "%1.3f, " % val.to_f
else
break
end
end
if ary.length > len
str += "..."
else
str.chop!.chop!
end
str += "]"
end
alias old_to_s to_s
alias to_s inspect
def first=(val)
self[0] = val
end
def last=(val)
self[-1] = val
end
# ary.pick ==> random value
# ary.pick(3) ==> [x, y, z]
# ary.pick(true) ==> whole ary randomized
def array_pick(n = 1)
n = self.length if n == true
if n == 1
self[kernel_rand(self.length)]
else
(0...n).map do |i| self[kernel_rand(self.length)] end
end
end
alias pick array_pick
def array_rand
tmp = self.dup
tmp.each_index do |i|
r = kernel_rand(tmp.length)
tmp[r], tmp[i] = tmp[i], tmp[r]
end
tmp
end
alias rand array_rand
def array_rand!
self.each_index do |i|
r = kernel_rand(self.length)
self[r], self[i] = self[i], self[r]
end
self
end
alias rand! array_rand!
def add(other)
assert_type((array?(other) or vct?(other)), 0, other, "an array, a vector or a vct")
new_ary = self.dup
[self.length, other.length].min.times do |i| new_ary[i] += other[i] end
new_ary
end
def add!(other)
assert_type((array?(other) or vct?(other)), 0, other, "an array, a vector or a vct")
[self.length, other.length].min.times do |i| self[i] += other[i] end
self
end
def subtract(other)
assert_type((array?(other) or vct?(other)), 0, other, "an array, a vector or a vct")
new_ary = self.dup
[self.length, other.length].min.times do |i| new_ary[i] -= other[i] end
new_ary
end
def subtract!(other)
assert_type((array?(other) or vct?(other)), 0, other, "an array, a vector or a vct")
[self.length, other.length].min.times do |i| self[i] -= other[i] end
self
end
def multiply(other)
assert_type((array?(other) or vct?(other)), 0, other, "an array, a vector or a vct")
new_ary = self.dup
[self.length, other.length].min.times do |i| new_ary[i] *= other[i] end
new_ary
end
def multiply!(other)
assert_type((array?(other) or vct?(other)), 0, other, "an array, a vector or a vct")
[self.length, other.length].min.times do |i| self[i] *= other[i] end
self
end
def offset(scl)
assert_type(number?(scl), 0, scl, "a number")
scl = Float(scl)
self.class.new(self.length) do |i| self[i] + scl end
end
def offset!(scl)
assert_type(number?(scl), 0, scl, "a number")
scl = Float(scl)
self.map! do |val| val += scl end
end
def scale(scl)
assert_type(number?(scl), 0, scl, "a number")
scl = Float(scl)
self.class.new(self.length) do |i| self[i] * scl end
end
def scale!(scl)
assert_type(number?(scl), 0, scl, "a number")
scl = Float(scl)
self.map! do |val| val *= scl end
end
def to_vector
Vec.new(self.length) do |i| Float(self[i]) end
end
def car
self[0]
end
def car=(val)
self[0] = val
end
def cadr
self[1]
end
def cadr=(val)
self[1] = val
end
def caddr
self[2]
end
def caddr=(val)
self[2] = val
end
def cadddr
self[3]
end
def cadddr=(val)
self[3] = val
end
def caddddr
self[4]
end
def caddddr=(val)
self[4] = val
end
def cdr
self[1..-1]
end
def step(n = 1)
0.step(self.length - n, n) do |i| yield(*self[i, n]) end
end
add_help(:apply,
"Array#apply([:func,] *rest, &body)
applies function or procedure with possible rest args \
to each element of Array or subclasses of Array.
[0, 1, 2].apply(\"a: %d\\n\") do |fmt, a| printf(fmt, a) end
[0, 1, 2].apply(:printf, \"a: %d\\n\")
both produce
a: 0
a: 1
a: 2
[1, 2, 3, 4].apply(:+) # ==> 10
%w(snd sndplay with_sound).apply(:length) # ==> [3, 7, 10]
[[1, 2, 3, 4], [1, 2, 3], [1, 2]].apply(:max) # ==> [4, 3, 2]
[vct(0.1, 0.2, 0.3), vct(-0.1, -0.2, -0.3)].apply(:peak) # ==> [0.3, 0.3]
sounds.apply(:map) do |s| puts s end
sounds.apply(:close_sound)")
def apply(func, *rest, &body)
if block_given? and (not symbol?(func))
rest.unshift(func)
self.map do |item| yield(*rest + [item]) end
else
assert_type((func?(func) or proc?(func) or method?(func)),
func, 0, "a function (string or symbol), a method or a proc")
case func
when Proc, Method
self.map do |item| func.call(*rest + [item]) end
when Symbol, String
if body and self.methods.member?(func.to_s)
# map, each, ...
self.send(func, *rest, &body)
else
receiver = self.compact.first
if receiver and receiver.methods.member?(func.to_s)
# methods
case func.to_sym
when :+, :-, :*
res = receiver
self[1..-1].compact.map do |item| res = res.send(func, *rest + [item]) end
res
else
len = rest.length + ((array?(receiver) and receiver.length) or 1)
if receiver.method(func).arity.abs == len
# remove_file (String(WS) in ws.rb)
self.map do |item| send(func, *rest + [item]) end
else
# length, max, min, ...
self.map do |item| item.send(func, *rest) end
end
end
else
# functions
self.map do |item| send(func, *rest + [item]) end
end
end
end
end
end
# original operands +, -, and * can now handle nil and numberic (offset, multiply)
#
# [].+(ary) concatenate arrays
# [].+(number) [].add(number)
unless defined? [].ary_plus
alias old_ary_plus +
def ary_plus(other)
case other
when Numeric
self.offset(other)
when NilClass
self
else
self.old_ary_plus(other)
end
end
alias + ary_plus
end
# [].-(ary) intersection
# [1, 2, 3, 4] - [2, 3] ==> [1, 4]
# [] - number [].offset
unless defined? [].ary_minus
alias old_ary_minus -
def ary_minus(other)
case other
when Numeric
self.offset(other)
when NilClass
self
else
self.old_ary_minus(other)
end
end
alias - ary_minus
end
# [].*(n) repetition or [].join(n)
# [5] * 3 ==> [5, 5, 5]
# ["foo", "bar"] * "-" ==> "foo-bar"
unless defined? [].ary_times
alias old_ary_times *
def ary_times(other)
case other
when NilClass
nil.to_a
else
self.old_ary_times(other)
end
end
alias * ary_times
end
# removes self's items where block is true and returns removed items
# in a newly created array (similar to Scheme's remove-if, I hope)
def remove_if
result = []
self.each_with_index do |x, i|
if yield(x)
result.push(x)
self.delete_at(i)
end
end
result
end
end
# name Vector is in use (lib/ruby/1.9/matrix.rb)
class Vec < Array
def self.[](*ary)
self.new(ary.length) do |i| ary[i] end
end
def initialize(len, init = 0.0)
assert_type((number?(len) and len >= 0), len, 0, "a number")
@name = "vector"
len = Integer(len)
if block_given?
super(len, init).map_with_index! do |val, i| yield(i) end
else
super(len, init)
end
test = self.detect do |x| (not number?(x)) end
assert_type((not test), test, 0, "only numeric elements")
end
def inspect
str = "%s(" % @name
self.each do |val| str += "%s, " % val.inspect end
if self.length > 0 then str.chop!.chop! end
str += ")"
str
end
def to_s
if self.length > 0
vals = ":"
self.map do |val| vals += " %s" % val end
else
vals = ""
end
format("#<%s[%d]%s>", self.class, self.length, vals)
end
def +(other)
case other
when Numeric
self.offset(other).to_vector
when Array, Vec, Vct
self.add(other.to_vector)
when NilClass
self
end
end
def -(other)
case other
when Numeric
self.offset(-other).to_vector
when Array, Vec, Vct
self.subtract(other.to_vector)
when NilClass
self
end
end
def *(other)
case other
when Numeric
self.scale(other).to_vector
when Array, Vec, Vct
self.multiply(other.to_vector)
when NilClass
nil.to_vector
end
end
end
def Vec(obj)
if obj.nil? then obj = [] end
assert_type(obj.respond_to?(:to_vector), obj, 0,
"an object containing method 'to_vector' (Vct, String, Array and subclasses)")
obj.to_vector
end
def make_vector(len, init = 0.0, &body)
Vec.new(len, init, &body)
end
def vector?(obj)
obj.kind_of?(Vec)
end
def vector(*args)
args.to_vector
end
class String
def to_vector
if self.scan(/^vector\([-+,.)\d\s]+/).null?
nil
else
eval(self)
end
end
def to_vct
if self.scan(/^vct\([-+,.)\d\s]+/).null?
nil
else
eval(self)
end
end
end
def Vct(obj)
if obj.nil? then obj = [] end
assert_type(obj.respond_to?(:to_vct), obj, 0,
"an object containing method 'to_vct' (Vct, String, Array and subclasses)")
obj.to_vct
end
def make_vct!(len, init = 0.0, &body)
if block_given?
Vct.new(len, &body)
else
Vct.new(len, init)
end
end
class Vct
def self.[](*ary)
self.new(ary.length) do |i| ary[i] end
end
# inspect in base classes like Array or Hash use elements' inspect
# to show them or write them down. Vct's to_str seems to be the
# best candidate for Vct's inspect.
alias vct_inspect inspect
alias inspect to_str
def to_sound_data(chn = 0, sd = 1)
assert_type(integer?(chn), chn, 0, "an integer (channel)")
assert_type((sound_data?(sd) or integer?(sd)), sd, 1, "a SoundData object or an integer")
case sd
when Integer
vct2sound_data(self, SoundData.new(sd, self.length), chn)
when SoundData
vct2sound_data(self, sd, chn)
end
end
def to_vct
self
end
def to_vector
Vec.new(self.length) do |i| self[i] end
end
def apply(*rest, &body)
self.to_a.apply(*rest, &body)
end
def +(other)
assert_type((number?(other) or vct?(other) or array?(other) or other.nil?), other, 0,
"a number, an array, a vector or a vct")
case other
when Numeric
self.offset(other)
when Array, Vec, Vct
self.add(other.to_vct)
when NilClass
self
end
end
def -(other)
assert_type((number?(other) or vct?(other) or array?(other) or other.nil?), other, 0,
"a number, an array, a vector or a vct")
case other
when Numeric
self.offset(-other)
when Array, Vec, Vct
self.subtract(other.to_vct)
when NilClass
self
end
end
def *(other)
assert_type((number?(other) or vct?(other) or array?(other) or other.nil?), other, 0,
"a number, an array, a vector or a vct")
case other
when Numeric
self.scale(other)
when Array, Vec, Vct
self.multiply(other.to_vct)
when NilClass
nil.to_vct
end
end
def step(n = 1, &body)
self.to_a.step(n, &body)
end
# v = vct(0, 1, 2, 3, 4)
# v[2..4] ==> vct(2.000, 3.000, 4.000)
# v[2...4] ==> vct(2.000, 3.000)
# v[3, 4] ==> vct(3.000, 4.000)
# v[-1] ==> 4.0
def vct_ref_extend(idx, size = nil)
case idx
when Fixnum
if idx < 0 then idx += self.length end
if idx < 0 then Snd.raise(:out_of_range, "index < 0", idx) end
if integer?(size)
size += idx - 1
if size >= self.length then size = self.length - 1 end
if size.between?(0, self.length - 1) and size >= idx
self.subseq(idx, size)
else
nil.to_vct # i.e. false
end
else
vct_ref(self, idx)
end
when Range
beg = idx.first
len = idx.last
if beg < 0 then beg += self.length end
if len < 0 then len += self.length end
if len >= self.length then len = self.length - 1 end
# exclude_end?: (1..2) ==> false
# (1...2) ==> true
if idx.exclude_end? then len -= 1 end
if beg.between?(0, self.length - 1) and len >= beg
self.subseq(beg, len)
else
nil.to_vct # i.e. false
end
end
end
# alias [] vct_ref_extend
end
class Fixnum
# no reloading (load "examp.rb")
unless defined? 0.new_int_plus
alias int_plus +
def new_int_plus(other)
case other
when Vct, Array, Vec
other.offset(Float(self))
when NilClass
self
else
self.int_plus(other)
end
end
alias + new_int_plus
end
unless defined? 0.new_int_times
alias int_times *
def new_int_times(other)
case other
when Vct, Array, Vec
other.scale(self)
when NilClass
0
else
self.int_times(other)
end
end
alias * new_int_times
end
end
class Float
# no reloading (load "examp.rb")
unless defined? 0.0.new_float_plus
alias float_plus +
def new_float_plus(other)
case other
when Vct, Array, Vec
other.offset(self)
when NilClass
self
else
self.float_plus(other)
end
end
alias + new_float_plus
end
unless defined? 0.0.new_float_times
alias float_times *
def new_float_times(other)
case other
when Vct, Array, Vec
other.scale(self)
when NilClass
0.0
else
self.float_times(other)
end
end
alias * new_float_times
end
end
def SoundData(ary)
assert_type((array?(ary) and vct?(ary.first)), ary, 0, "an array of vcts")
sd = SoundData.new(ary.length, ary.first.length)
ary.each_with_index do |v, chn| vct2sound_data(v, sd, chn) end
sd
end
def sound_data2string(sd)
sd.to_a.to_s
end
class SoundData
def to_vct(chn = 0)
sound_data2vct(self, chn, Vct.new(self.length))
end
# returns an array of sd.chans vcts
def to_a
make_array(self.chans) do |chn| sound_data2vct(self, chn, Vct.new(self.length)) end
end
alias sd_inspect inspect
def inspect
format("%s(%s)", self.class, self.to_a.to_s)
end
alias sd_length length
def length
self.size / self.chans
end
alias sd_each each
def each(chn = nil)
if chn
self.length.times do |i| yield(self[chn, i]) end
else
sd_each do |val| yield(val) end
end
end
def each_with_index(chn = nil)
if chn
self.length.times do |i| yield(self[chn, i], i) end
else
self.length.times do |i|
self.chans.times do |j| yield(self[j, i], i) end
end
end
end
def map(chn = nil)
sd = nil
if chn
sd = self.dup
self.each_with_index(chn) do |val, i| sd[chn, i] = yield(val) end
else
sd = SoundData.new(self.chans, self.length)
self.chans.times do |j|
self.each_with_index(j) do |val, i| sd[j, i] = yield(val) end
end
end
sd
end
def map!(chn = nil)
if chn
self.each_with_index(chn) do |val, i| self[chn, i] = yield(val) end
else
self.chans.times do |j|
self.each_with_index(j) do |val, i| self[j, i] = yield(val) end
end
end
self
end
def maxamp(chn = 0)
case chn
when TrueClass
sound_data_maxamp(self)
when FalseClass
sound_data_maxamp(self)[Snd.chn]
when Numeric
sound_data_maxamp(self)[chn]
else
nil
end
end
end
def mus_a0(gen)
mus_xcoeff(gen, 0)
end
def set_mus_a0(gen, val)
set_mus_xcoeff(gen, 0, val)
end
def mus_a1(gen)
mus_xcoeff(gen, 1)
end
def set_mus_a1(gen, val)
set_mus_xcoeff(gen, 1, val)
end
def mus_a2(gen)
mus_xcoeff(gen, 2)
end
def set_mus_a2(gen, val)
set_mus_xcoeff(gen, 2, val)
end
def mus_b1(gen)
mus_ycoeff(gen, 1)
end
def set_mus_b1(gen, val)
set_mus_ycoeff(gen, 1, val)
end
def mus_b2(gen)
mus_ycoeff(gen, 2)
end
def set_mus_b2(gen, val)
set_mus_ycoeff(gen, 2, val)
end
class Mus
# clm_gen.call(a1, a2) requires 2 arguments but clm_gen.run([a1, [a2]])
# 0, 1 or 2.
#
# clm_gen.run([arg1, [arg2]])
def run(arg1 = 0.0, arg2 = 0.0)
mus_run(self, arg1, arg2)
end
def apply(*rest)
mus_apply(self, *rest)
end
alias mus_inspect inspect
def inspect
"#<" + mus_describe(self) + ">"
end
def close
mus_close(self)
end
# gen.xcoeff = 0, 0.4
# set_mus_xcoeff(gen, index, val)
def xcoeff=(args)
set_mus_xcoeff(self, *args.flatten[0, 2])
end
# gen.ycoeff = 0, 0.4
# set_mus_ycoeff(gen, index, val)
def ycoeff=(args)
set_mus_ycoeff(self, *args.flatten[0, 2])
end
def a0
mus_xcoeff(self, 0)
end
def a0=(val)
set_mus_xcoeff(self, 0, val)
end
def a1
mus_xcoeff(self, 1)
end
def a1=(val)
set_mus_xcoeff(self, 1, val)
end
def a2
mus_xcoeff(self, 2)
end
def a2=(val)
set_mus_xcoeff(self, 2, val)
end
def b1
mus_ycoeff(self, 1)
end
def b1=(val)
set_mus_ycoeff(self, 1, val)
end
def b2
mus_ycoeff(self, 2)
end
def b2=(val)
set_mus_ycoeff(self, 2, val)
end
end
# base class for generators written in Ruby
class Musgen
def initialize
@frequency = 440.0
@phase = 0.0
@scaler = 1.0
@data = nil
@increment = 0
@interp_type = -1
@file_name = ""
end
attr_accessor :frequency
attr_accessor :phase
attr_accessor :scaler
attr_accessor :data
attr_accessor :increment
attr_reader :interp_type
attr_reader :file_name
def inspect
format("%s.new()", self.class)
end
def to_s
format("#<%s>", self.class)
end
def run(val1 = 0.0, val2 = 0.0)
self.run_func(val1, val2)
end
alias call run
def apply(*rest)
self.run_func(*rest)
end
def eql?(other)
self == other
end
def reset
@frequency = 440.0
@phase = 0.0
@scaler = 1.0
@increment = 0
self
end
end
class Numeric
def positive?
self > 0
end
def negative?
self < 0
end
end
class Integer
def even?
self.modulo(2) == 0
end
def odd?
self.modulo(2) != 0
end
def prime?
(self == 2) or
(self.odd? and 3.step(sqrt(self), 2) do |i| return false if self.modulo(i) == 0 end)
end
end
class Float
# step accepts floats as arguments (still implemented in newer versions)
def step(upto, step)
counter = self
while counter < upto
yield(counter)
counter += step
end
counter
end unless 1.1.respond_to?(:step)
end
class Range
def step(n = 1, &body)
self.to_a.step(n, &body)
end unless defined? Range.new(0, 1).step
end
module Enumerable
def map_with_index
i = -1
self.map do |x| yield(x, i += 1) end
end
def map_with_index!
i = -1
self.map! do |x| yield(x, i += 1) end
end
def cycle
unless defined? @cycle_index then @cycle_index = 0 end
val = self[@cycle_index % self.length]
@cycle_index += 1
if @cycle_index == self.length then @cycle_index = 0 end
val
end
def cycle=(val)
unless defined? @cycle_index then @cycle_index = 0 end
self[@cycle_index % self.length] = val
@cycle_index += 1
if @cycle_index == self.length then @cycle_index = 0 end
val
end
attr_accessor :cycle_index
# backward compatibility methods
def each_index
self.each_with_index do |val, i| yield(i) end
end unless vct(0).respond_to?(:each_index)
# Enumerable#zip, new in ruby core since 19-Nov-2002.
# a = [4, 5, 6]
# b = [7, 8, 9]
# [1, 2, 3].zip(a, b) --> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# [1, 2].zip(a, b) --> [[1, 4, 7], [2, 5, 8]]
# a.zip([1, 2],[8]) --> [[4, 1, 8], [5, 2, nil], [6, nil, nil]]
def zip(*objs)
args = objs.map do |obj| obj.to_a end
res = self.to_a
res.each_with_index do |val, i|
ary = [val]
args.each do |obj| ary.push(obj[i]) end
if block_given?
yield(*ary)
else
res[i] = ary
end
end
res
end unless vct(0).respond_to?(:zip)
end
def as_one_edit_rb(*origin, &body)
# ruby compatibility:
# ruby pre 1.9: lambda do end.arity != lambda do | | end.arity
# ruby 1.9: they are even (0)
as_one_edit(lambda do | | body.call end, origin.empty? ? "" : format(*origin))
end
def map_channel_rb(beg = 0, dur = false,
snd = false, chn = false, edpos = false, edname = false, &func)
map_channel(func, beg, dur, snd, chn, edpos, edname)
end
add_help(:map_chan_rb,
"map_chan(func,[start=0,[end=false,[edname=false,[snd=false,[chn=false,[edpos=false]]]]]])\
map_chan applies func to samples in the specified channel.\
It is the old (\"irregular\") version of map_channel.")
def map_chan_rb(beg = 0, dur = false, ednam = false, snd = false, chn = false, edpos = false, &func)
map_chan(func, beg, dur, ednam, snd, chn, edpos)
end
class Proc
include Info
alias run call
add_help(:to_method,
"Proc#to_method(name, [klass=Object]) \
converts a Proc to a Method 'name' in the given class, default Object. \
'name' can be a string or a symbol.
m = lambda do |*args| p args end
m.to_method(:func)
func(1, 2, 3) ==> [1, 2, 3]
lambda do |x| p x end.to_method(:foo); foo(\"text1\") ==> \"text1\"
lambda do |x| p x end.to_method(\"bar\"); bar(\"text2\") ==> \"text2\"")
def to_method(name, klass = Object)
assert_type((symbol?(name) or string?(name)), name, 0, "a symbol or a string")
assert_type((klass.kind_of?(Class) and klass.class == Class), name, 1, "a class, e.g. Object")
name = case name
when String
name.intern
when Symbol
name
end
body = self
klass.class_eval do define_method(name, body) end
end
# Important:
# The following works only with newer ruby versions (I assume >=
# 1.8.x). Proc#inspect must return #<Proc:0x80c96a0@xxx:x> to
# locate the source file of the procedure, not only #<Proc:0x80c96a0>!
# Functions to_str and to_body try to search the procedure source
# code in a file determined by to_s. It is only a simple scanner
# which doesn't look for the whole Ruby syntax. ;-)
#
# It doesn't work if no source file exists, i.e, if the code is
# eval'ed by the Snd listener (or in Emacs). You must load the file
# instead.
#
# with_sound(:notehook, lambda do |name| snd_print(name) if name =~ /viol/ end) do
# fm_violin(0, 1, 440, 0.3)
# end
#
# $clm_notehook = lambda do |name| clm_print(name) if name =~ /viol/ end
#
# with_sound do
# fm_violin(0, 1, 440, 0.3)
# end
#
# with_sound(:save_body, true) do
# ...
# end
# returns something like 'lambda do ... end'
def to_str
if body = self.source
return body
end
file, line = self.to_s.sub(/>/, "").split(/@/).last.split(/:/)
if file[0] == ?( and file[-1] == ?)
if $VERBOSE
warning("%s#%s: no file found for procedure %s", self.class, get_func_name, self.inspect)
end
body = ""
elsif (not File.exists?(file))
if $VERBOSE
warning("%s#%s: Sorry, you need a higher ruby version to use Proc#to_str.
It works only with newer ruby versions (I assume >= 1.8.x).
Proc#inspect must return #<Proc:0x01234567@xxx:x> not only %s!",
self.class, get_func_name, self.inspect)
end
body = ""
else
lineno = line.to_i
body = ""
blck = i = 0
first_line = true
File.foreach(file) do |line|
i += 1
next if i < lineno
body << line
if first_line
if (line.scan(/\s*do\b|\{/).length - line.scan(/\s*end\b|\}/).length).zero? and
(line.scan(/\(/).length - line.scan(/\)/).length).zero?
break
else
first_line = false
blck = 1
next
end
end
next if /\s*\S+\s*(if|unless|while|until)+/ =~ line
break if (blck += Snd_eval.count_level(line)).zero?
break if blck.negative?
end
end
unless self.source then self.source = body end
body
end
# returns the inner body without 'lambda do end'
def to_body
if (body = self.to_str).null?
""
elsif body.split(/\n/).length == 1
body.chomp.sub(/^(?:\s*\w+(?:\(.*\))??\s*(?:do\s+|\{\s*))(.*)\s*(?:end|\})$/, '\1').strip
else
body.split(/\n/)[1..-2].join("\n")
end
end
# property set in g_edit_list_to_function (snd-edits.c)
def source
property(self.object_id, :proc_source)
end
def source=(val)
set_property(self.object_id, :proc_source, val)
end
end
def make_proc2method(name, prc)
assert_type(func?(name), name, 0, "a symbol or a string")
assert_type(proc?(prc), prc, 1, "a proc")
prc.to_method(name)
end
# produces two new functions: NAME and SET_NAME
# val = 10
# make_proc_with_setter(:foo, lambda { puts val }, lambda { |a| val = a })
# foo ==> 10
# set_foo(12)
# foo ==> 12
def make_proc_with_setter(name, getter, setter)
make_proc2method(name, getter)
make_proc2method(format("set_%s", name).intern, setter)
end
# prc = make_proc_with_source(%(lambda do |a, b, c| puts a, b, c end))
# prc.call(1, 2, 3)
# prc.source ==> "lambda do |a, b, c| puts a, b, c end"
#
# With the second argument BIND one can use local variables known in
# the current (or other) environment in the proc body:
#
# os = make_oscil(:frequency, 330)
# prc = make_proc_with_source(%(lambda do 10.times do |i| p os.run end end), binding)
# puts prc.source ==> lambda do 10.times do |i| p os.run end end
# prc.call ==> ..., 0.748837699712728
# puts
# prc.call ==> ..., 0.97679449812022
def make_proc_with_source(string, bind = binding)
assert_type(string?(string), string, 0, "a string")
assert_type(binding?(bind), bind, 1, "a binding object")
if proc?(prc = (res = Snd.catch(:all) do eval(string, bind) end).first)
prc.source = string
prc
else
Snd.raise(:runtime_error, res, prc, string)
end
end
make_proc_with_setter(:proc_source,
lambda { |prc|
assert_type(proc?(prc), prc, 0, "a proc")
prc.source
},
lambda { |prc, val|
assert_type(proc?(prc), prc, 0, "a proc")
assert_type(string?(val), val, 1, "a string")
prc.source = val
})
# Multi-line input to the Snd listener and Emacs/inf-snd.el.
# A simple parser collects multi-line input, e.g.
#
# with_sound do
# fm_violin(0.0, 0.1, 330, 0.1)
# fm_violin(0.1, 0.1, 660, 0.1)
# end
#
# and evals it.
#
# ~/.snd
# set_listener_prompt("snd> ") # optional
# start_listener_eval # installs read-hook for snd-listener input
# start_emacs_eval # installs emacs-eval-hook
make_hook("$emacs_eval_hook", 1, "\
emacs_eval_hook(line): called each time inf-snd.el sends a line to the Snd process. \
The hook functions may do their best to deal with multi-line input; \
they can collect multi-line input and eval it by itself. \
One example is install_eval_hooks(file, retval, input, hook, &reset_cursor) in examp.rb.")
# inf-snd.el calls this function each time a line was sent to the
# emacs buffer.
def run_emacs_eval_hook(line)
if $emacs_eval_hook.empty?
# without emacs-eval-hook only single line eval
file = "(emacs-eval-hook)"
set_snd_input(:emacs)
begin
Snd.display(eval(line, TOPLEVEL_BINDING, file, 1).inspect)
rescue Interrupt, ScriptError, StandardError
Snd.display(verbose_message_string(true, "# ", file))
end
set_snd_input(:snd)
nil
else
$emacs_eval_hook.call(line)
end
end
class Snd_eval
class << Snd_eval
Open_token = %w(class module def do { while until if unless case begin for)
Close_token = %w(end })
def count_level(line)
eval_level = 0
# skip strings and symbols which may contain reserved words
line.gsub(/(:\w+|".+")/, "").split(/\b/).each do |s|
case s
when *Open_token
eval_level += 1
when *Close_token
eval_level -= 1
end
end
eval_level
end
end
end
class Snd_prompt
# level number inserted into original prompt
# ">" --> "(0)>"
# "snd> " --> "snd(0)> "
def initialize(level)
@listener_prompt = listener_prompt
@base_prompt = listener_prompt.split(/(\(\d+\))?(>)?\s*$/).car.to_s
@rest_prompt = listener_prompt.scan(/>\s*$/).car.to_s
update(level)
end
def inspect
format("#<%s %s(0)%s>", self.class, @base_prompt, @rest_prompt)
end
def update(level)
set_listener_prompt(format("%s(%d)%s", @base_prompt, level, @rest_prompt))
end
def reset
set_listener_prompt(@listener_prompt)
end
end
def install_eval_hooks(file, retval, input, hook, &reset_cursor)
eval_level = 0
eval_line = ""
prompt = Snd_prompt.new(eval_level)
reset_cursor and reset_cursor.call
$exit_hook.add_hook!(file) do prompt.reset end
hook.add_hook!(file) do |line|
eval_line << line << "\n"
eval_level += Snd_eval.count_level(line)
if eval_level.negative?
eval_level = 0
eval_line = ""
end
prompt.update(eval_level)
if eval_level.zero?
set_snd_input(input)
begin
Snd.display(eval(eval_line, TOPLEVEL_BINDING, file, 1).inspect)
rescue Interrupt, ScriptError, StandardError
Snd.display(verbose_message_string(true, "# ", file))
ensure
eval_line = ""
end
end
reset_cursor and reset_cursor.call
retval
end
end
# installs the emacs-eval-hook
def start_emacs_eval(name = "(emacs)")
install_eval_hooks(name, nil, :emacs, $emacs_eval_hook) do
$stdout.print(listener_prompt)
$stdout.flush
end
end
# installs the read-hook
def start_listener_eval(name = "(snd)")
set_show_listener(true)
install_eval_hooks(name, true, :snd, $read_hook) do
reset_listener_cursor
clm_print("\n%s", listener_prompt)
end
end
def stop_emacs_eval(name = "(emacs)")
$emacs_eval_hook.remove_hook!(name)
$exit_hook.run_hook_by_name(name)
$exit_hook.remove_hook!(name)
end
def stop_listener_eval(name = "(snd)")
$read_hook.remove_hook!(name)
$exit_hook.run_hook_by_name(name)
$exit_hook.remove_hook!(name)
reset_listener_cursor
clm_print("\n%s", listener_prompt)
end
# Debugging resp. inspecting local variables
make_proc_with_setter(:debug_properties,
lambda { |name|
property(name, :debug_property)
},
lambda { |name, val|
set_property(name, :debug_property, val)
})
make_proc_with_setter(:debug_property,
lambda { |key, name|
hash?(h = debug_properties(name)) and h[key]
},
lambda { |key, val, name|
unless hash?(h = debug_properties(name)) and h.store(key, [val] + h[key])
unless array?(a = property(:debug, :names)) and a.push(name)
set_property(:debug, :names, [name])
end
set_debug_properties(name, {key, [val]})
end
})
make_proc_with_setter(:debug_binding,
lambda { |name|
debug_property(:binding, name)
},
lambda { |bind, *name|
assert_type(binding?(bind), bind, 0, "a binding object")
name = (name.car or get_func_name(3))
set_debug_property(:binding, bind, name)
})
# shows all local variables of functions prepared by set_debug_binding(binding)
#
# def function1
# [...]
# set_debug_binding(binding)
# end
#
# def function2
# [...]
# set_debug_binding(binding)
# end
# [...]
#
# display_all_variables
def display_all_variables(name = nil)
if name
[name]
else
(property(:debug, :names) or [])
end.each do |name|
debug_binding(name).each do |bind|
Snd.message("=== %s ===", name)
Snd.message()
eval("local_variables", bind).each do |var|
Snd.message("%s = %s", var, eval(var, bind).inspect)
end
Snd.message()
end
end
end
# each_variables provides all local variable names and their values in
# the given proc context
#
# def function
# [...]
# each_variables do |k, v|
# Snd.display("%s = %s", k, v)
# end
# end
def each_variables(&prc)
eval("local_variables", prc).each do |var| yield(var, eval(var, prc)) end
end
# let(8, :foo, "bar") do |a, b, c|
# printf("a: %d, b: %s, c: %s\n", a, b, c)
# end
#
# Simulates a save local variable environment and restores old
# variables to their original values.
def let(*args, &prc)
locals = Hash.new
eval("local_variables", prc).each do |name| locals[name] = eval(name, prc) end
yield(*args)
rescue Interrupt, ScriptError, StandardError
raise
ensure
@locals = locals
locals.each_key do |name| eval("#{name} = @locals[#{name.inspect}]", prc) end
remove_instance_variable("@locals")
end
include Math
TWO_PI = PI * 2.0 unless defined? TWO_PI
HALF_PI = PI * 0.5 unless defined? HALF_PI
# for irb (rgb.rb)
def make_color(r, g, b)
[:Pixel, 0]
end unless defined? make_color
def doc(*rest)
# dummy for old Kernel.doc
end
require "env"
require "dsp"
require "rgb"
##
## Utilities
##
if provided? :snd_nogui
alias close_sound_extend close_sound
else
def close_sound_extend(snd)
# 5 == Notebook
if main_widgets[5] and selected_sound and selected_sound <= snd
idx = 0
snds = sounds() and idx = snds.index(snd)
close_sound(snd)
snds = sounds() and set_selected_sound(snds[idx < snds.length ? idx : -1])
else
close_sound(snd)
end
end
end
add_help(:times2samples,
"times2samples(start, dur) \
START and DUR are in seconds; returns array [beg, len] in samples")
def times2samples(start, dur)
beg = seconds2samples(start)
[beg, beg + seconds2samples(dur)]
end
def random(val)
if val.zero?
val
else
case val
when Fixnum
kernel_rand(val)
when Float
val.negative? ? -mus_random(val).abs : mus_random(val).abs
end
end
end
def logn(r, b = 10)
if r <= 0 then Snd.raise(:ruby_error, r, "r must be > 0") end
if b <= 0 or b == 1 then Snd.raise(:ruby_error, b, "b must be > 0 and != 1") end
log(r) / log(b)
end
def car(v)
v[0]
end
def cadr(v)
v[1]
end
def caddr(v)
v[2]
end
def cdr(v)
v[1..-1]
end
def verbose_message_string(stack_p, remark, *args)
fmt_remark = format("\n%s", remark)
args.to_a[0] = remark.to_s + args.to_a.car.to_s
str = if args.length < 2
format(args.car)
else
format(*args)
end.split(/\n/).join(fmt_remark)
if $!
unless str.length == remark.to_s.length then str += ": " end
str += format("[%s] %s (%s)", rb_error_to_mus_tag.inspect, snd_error_to_message, $!.class)
if stack_p
str += format("\n%s%s", remark, $!.backtrace.join(fmt_remark))
end
else
if stack_p and caller(2)
str += format("\n%s%s", remark, caller(2).join(fmt_remark))
end
end
str
end
def warning(*args)
str = "Warning: " << verbose_message_string($VERBOSE, nil, *args)
if provided? :snd
snd_warning(str)
nil
else
clm_message(str)
end
end
def die(*args)
message(verbose_message_string(true, nil, *args))
exit(1) unless provided? :snd
end
def error(*args)
Snd.raise(:runtime_error, verbose_message_string(true, nil, *args))
end
make_proc_with_setter(:snd_input,
lambda { property(:snd_input, :snd_listener) },
lambda { |val| set_property(:snd_input, :snd_listener, val) })
# like clm_print(fmt, *args)
def clm_message(*args)
msg = if args.null?
""
elsif args.length == 1
String(args.car)
else
format(*args)
end
if provided? :snd and (not ENV["EMACS"] or provided? :snd_nogui)
clm_print("\n%s", msg)
nil
else
print(msg, "\n")
end
end
# like clm_print(*args), in emacs it prepends msg with a comment sign
if ENV["EMACS"]
def message(*args)
clm_message(verbose_message_string(false, "# ", format(*args)))
end
else
alias message clm_message
end
# debug(var1, var2) --> #<DEBUG: ClassName: value1, ClassName: value2>
def debug(*args)
fmt = ""
args.each do |arg|
fmt += format("%s: %s", arg.class, arg.inspect)
fmt += ", "
end
message("#<DEBUG: %s>", fmt.chomp(", "))
end
def debug_trace(*args)
debug(*args)
clm_message(verbose_message_string(true, "# "))
end
if provided? :snd then set_snd_input(:snd) end
class Snd
class << Snd
Snd_path = Array.new
if provided? :snd
def add_sound_path(path)
Snd_path.push(path)
add_directory_to_view_files_list(path)
end
def open_from_path(fname)
find_sound(snd_file = Snd.fullname(fname)) or open_sound(snd_file)
end
def find_from_path(fname)
find_sound(Snd.fullname(fname))
end
else
def add_sound_path(path)
Snd_path.push(path)
end
end
def fullname(fname)
if File.exists?(fname)
fname
else
f = File.basename(fname)
callcc do |brk|
Snd_path.each do |path|
File.exists?(path + "/" + f) and brk.call(path + "/" + f)
end
Snd.raise(:no_such_file, fname)
end
end
end
def load_path
Snd_path
end
def message(*args)
Snd.display(verbose_message_string(false, "# ", *args))
end
def display(*args)
args[0] = String(args[0])
msg = format(*args)
if snd_input == :snd
snd_print("\n" + msg)
if $VERBOSE then $stdout.print(msg, "\n") end
nil
else
$stdout.print(msg, "\n")
end
end
def warning(*args)
if provided? :snd
snd_warning(verbose_message_string($VERBOSE, nil, *args))
nil
else
args[0] = "Warning: " + String(args[0])
Snd.display(verbose_message_string($VERBOSE, "# ", *args))
end
end
def die(*args)
Snd.display(verbose_message_string(true, nil, *args))
exit(1) unless provided? :snd
end
def error(*args)
Snd.raise(:runtime_error, verbose_message_string(true, nil, *args))
end
def debug(*args)
fmt = ""
args.each do |arg|
fmt += format("%s: %s", arg.class, arg.inspect)
fmt += ", "
end
Snd.message("#<DEBUG: %s>", fmt.chomp(", "))
end
def debug_trace(*args)
Snd.debug(*args)
Snd.display(verbose_message_string(true, "# "))
end
def sounds
(Kernel.sounds or []).reverse
end
def regions
(Kernel.regions or []).reverse
end
def tracks
(Kernel.tracks or []).reverse
end
def marks(snd = false, chn = false)
(Kernel.marks(snd, chn) or [])
end
def snd(sn = false)
sn or selected_sound or Snd.sounds.car
end
def chn(ch = false)
ch or selected_channel or 0
end
def catch(tag = :all, retval = :undefined)
old_debug = $DEBUG
$DEBUG = false
val = Kernel.catch(tag) do yield end
# catch/throw part
if array?(val) and val.car == :snd_throw # [:snd_throw, tag, get_func_name(2), *rest]
if retval != :undefined
if proc?(retval)
retval.call(val.cdr)
else
[retval]
end
else
val.cdr
end
else
[val]
end
rescue Interrupt, ScriptError, StandardError
# raise part
if (tag == (mus_tag = rb_error_to_mus_tag) or tag == :all)
if retval != :undefined
if proc?(retval)
retval.call(mus_tag, snd_error_to_message)
else
[retval]
end
else
[mus_tag, snd_error_to_message]
end
else
raise
end
ensure
$DEBUG = old_debug
end
def throw(tag, *rest)
Kernel.throw(tag, [:snd_throw, tag, get_func_name(2), *rest])
end
def raise(tag, *rest)
msg = format("%s: %s:", get_func_name(2), tag)
rest.each do |s| msg += format(" %s,", s) end
msg.chomp!(",")
exception = case tag
when :out_of_range
RangeError
when :wrong_type_arg
TypeError
when *Snd_error_tags
StandardError
else
Ruby_exceptions[tag] or RuntimeError
end
Kernel.raise(exception, msg, caller(1))
end
end
end
# nearly all are instance of StandardError
Snd_error_tags = [
# snd-0.h
:no_such_mix,
:no_such_track,
:no_such_region,
:no_such_envelope,
:no_such_sample,
:no_such_edit,
:cannot_save,
:cannot_print,
:no_such_axis,
:no_such_widget,
:no_such_graphics_context,
:no_such_player,
:no_such_direction,
:no_such_key,
:no_such_plugin,
:plugin_error,
:no_such_mark,
:no_such_menu,
:cannot_parse,
:no_active_selection,
:bad_arity,
:no_such_color,
:no_such_sound,
:gsl_error,
:no_such_colormap,
:cant_open_file,
:cant_close_file,
:cant_delete_file,
:cant_update_file,
# snd-error.c
:snd_error,
# snd-run.c/xen.c
:wrong_number_of_args,
# snd-snd.c
:cannot_apply_controls,
# sndlib2xen.h
:no_such_channel,
:no_such_file,
:mus_error,
:bad_type,
:no_data,
:bad_header,
# xen.h
:out_of_range, # RangeError
:wrong_type_arg] # TypeError
def rb_error_to_mus_tag
# to_s and string error-names intended here
# otherwise e.g. NameError goes to case StandardError!
case $!.class.to_s
# case 1
# No_such_file: file->array /baddy/hiho No such file or directory
# case 2
# insert_region: No_such_region: 1004
# case 3 (mus_error)
# mus_ycoeff__invalid_index_123__order___3?: Mus_error
when "StandardError"
err = $!.message.split(/\n/).first.downcase.split(/:/).map do |e| e.strip.chomp(">") end
Snd_error_tags.detect do |tag| err.member?(tag.to_s) end or :standard_error
when "RangeError"
:out_of_range
when "TypeError"
:wrong_type_arg
when "ArgumentError"
:wrong_number_of_args
else
# converts ruby exceptions to symbols: NoMethodError --> :no_method_error
$!.class.to_s.gsub(/([A-Z])/) do |c| "_" + c.tr("A-Z", "a-z") end[1..-1].intern
end
end
def snd_error_to_message
err = $!.message.split(/:/).map do |e| e.strip.chomp("\n") end
str = err.join(": ")
if err.length > 1 and (len = str.scan(/~A/).length).positive?
str.gsub!(/~A/, "%s")
str = if $!.class == RangeError
format(str.strip, if string?(s = err.cadr.split(/,/)[1..-2].join(","))
eval(s)
else
0
end)
else
format(str.strip, *if string?(s = str.slice!(str.index("[")..str.index("]")))
eval(s)
else
[0] * len
end)
end
end
str.gsub(rb_error_to_mus_tag.to_s.capitalize + ": ", "")
rescue Interrupt, ScriptError, StandardError
if $DEBUG
$stderr.printf("# Warning (%s)\n", get_func_name)
each_variables do |k, v| $stderr.printf("# %s = %s\n", k, v.inspect) end
end
str
end
add_help(:snd_catch,
"snd_catch([tag=:all, [retval=:undefined]]) \
catchs snd_throw and exceptions and \
returns body's last value wrapped in an array if all goes well. \
If a snd_throw tag meets snd_catch's, returns an array with the tag name, \
the function name from where was thrown and optional arguments given to snd_throw. \
If an exception was risen and the exception name meets tag name, \
returns an array with tag name and the exception message, otherwise reraises exception. \
If retval is given and tag matches exception or snd_throw tag, returns retval. \
If retval is a procedure, calls retval with tag name and message.
res = snd_catch do 10 + 2 end
puts res ==> [12]
res = Snd.catch(:no_such_file) do
open_sound(\"unknown-file.snd\")
end
puts res ==> [:no_such_file,
\"open_sound: no_such_file: Unknown_file.snd No such file or directory\"]
res = Snd.catch(:finish) do
10.times do |i|
if i == 8 then snd_throw(:finish, i) end
end
end
puts res ==> [:finish, \"top_level\", 8]
res = Snd.catch(:all, lambda do |tag, msg| Snd.display([tag, msg]) end) do
set_listener_prompt(17)
end
==> [:wrong_type_arg, \"set_listener-prompt: wrong type arg 0, 17, wanted a string\"]
puts res ==> nil
The lambda function handles the error in the last case.")
def snd_catch(tag = :all, retval = :undefined, &body)
Snd.catch(tag, retval, &body)
end
add_help(:snd_throw,
"snd_throw(tag, *rest) \
jumps to the corresponding snd_catch('tag') and returns an array \
with tag, function name and possible *rest strings or values.")
def snd_throw(tag, *rest)
Snd.throw(tag, *rest)
end
class Break < StandardError
end
Ruby_exceptions = {
:script_error, ScriptError,
:load_error, LoadError,
:name_error, NameError,
:not_implemented_error, NotImplementedError,
:syntax_error, SyntaxError,
:interrupt, Interrupt,
:system_exit, SystemExit,
:standard_error, StandardError,
:argument_error, ArgumentError,
:float_domain_error, FloatDomainError,
:index_error, IndexError,
:io_error, IOError,
:eof_error, EOFError,
:local_jump_error, LocalJumpError,
:no_memory_error, NoMemoryError,
:range_error, RangeError,
:regexp_error, RegexpError,
:runtime_error, RuntimeError,
:security_error, SecurityError,
:system_call_error, SystemCallError,
:system_stack_error, SystemStackError,
:thread_error, ThreadError,
:type_error, TypeError,
:zero_division_error, ZeroDivisionError,
:break, Break}
add_help(:snd_raise,
"snd_raise(tag, *rest) \
raises an exception 'tag' with an error message \
containing function name, tag and possible *rest strings or values. \
'tag' is a symbol, \
a Ruby exception looks like :local_jump_error instead of LocalJumpError, \
a Snd error tag looks like :no_such_sound.")
def snd_raise(tag, *rest)
Snd.raise(tag, *rest)
end
# for irb
def c_g?
false
end unless defined? c_g?
def srate
mus_srate
end unless defined? srate
# general purpose loop
add_help(:gloop,
"gloop(*args) { |args| ... }
:step = 1
:before = nil (thunk)
:after = nil (thunk)
args[0]: Range (each)
Hash(s) (each)
Array(s) (each_with_index) [args.last == Fixnum --> step]
Fixnum (times)
Fixnum [args[1] == :step --> step]
A general purpose loop, handling Range, Hash, Array, Vec, Vct, Fixnum,
with optional step. Returns the result of body as array like map.
Examples:
Range
gloop(0..3) do |i| puts i end
Hash (loops over all Hashs consecutively)
gloop({1, :a, 2, :b}, {11, :aa, 22, :bb}) do |k, v|
print('key: ', k, ' value: ', v)
puts
end
Array, Vec, Vct
gloop([0, 1]) do |x, i|
print(i, ': ', x)
puts end
Arrays with step (mixes all Arrays)
gloop([0, 1, 2, 3], [:a, :b, :c, :d], [55, 66, 77, 88, 99], 2) do |x, i|
print(i, ': ', x.inspect)
puts
end
Numeric (like Integer#times)
gloop(3) do |i| puts i end
Numeric with step (like Integer#step)
gloop(6, 2) do |i| puts i end
a simple body call
gloop do puts 'empty' end")
def gloop(*args, &body)
step = get_shift_args(args, :step, 1)
before = get_shift_args(args, :before)
after = get_shift_args(args, :after)
do_extra = lambda do |thunk| thunk?(thunk) ? thunk.call : snd_func(thunk) end
result = []
case args[0]
when Range
args[0].step(step) do |i|
do_extra.call(before) if before
result << body.call(i)
do_extra.call(after) if after
end
when Array, Vec, Vct
lmax = args.map do |x| x.length end.max
0.step(lmax - 1, step.round) do |i|
do_extra.call(before) if before
result << body.call(*args.map do |x| x[i] end << i)
do_extra.call(after) if after
end
when Hash
args.each do |x| x.each do |k, v|
do_extra.call(before) if before
result << body.call(k, v)
do_extra.call(after) if after
end
end
when Numeric
0.step(args[0], number?(args[1]) ? args[1] : step) do |i|
do_extra.call(before) if before
result << body.call(i)
do_extra.call(after) if after
end
else
do_extra.call(before) if before
result << body.call
do_extra.call(after) if after
end
result
end
# get_args(args, key, default = nil)
#
# returns value, whether DEFAULT or value of KEY found in ARGS
def get_args(args, key, default = nil)
if args.member?(key)
arg = args[args.index(key) + 1]
default = arg.nil? ? default : arg
end
default
end
def get_shift_args(args, key, default = nil)
default = get_args(args, key, default)
if args.member?(key)
i = args.index(key)
2.times do args.delete_at(i) end
end
default
end
# var = get_class_or_key(args, Klass, :key, default = nil)
def get_class_or_key(args, klass, key, default = nil)
if (not symbol?(args.first)) and args.first.kind_of?(klass)
args.shift
else
get_shift_args(args, key, default)
end
end
# var1, var2, var3, var4 = optkey(args, [:key, default],
# [:number, 1],
# [Array, :list, [0, 1, 2, 3]],
# :var_w/o_default_value)
#
# Key-default pairs must be included in brackets while keys alone can
# be included in brackets or not, see last key
# ":var_w/o_default_value" above. If no default value is specified,
# nil is used.
def optkey(args, *rest)
args_1 = args.dup
bind = binding?(rest.car) ? rest.shift : nil
@locals = nil
vals = rest.map do |keys|
val = if array?(keys)
case keys.length
when 1
name = keys.car.to_s
get_class_or_key(args_1, Object, keys.car, nil)
when 2
name = keys.car.to_s
get_class_or_key(args_1, keys.cadr.class, *keys)
when 3
name = keys.cadr.to_s
get_class_or_key(args_1, *keys)
else
assert_type(keys.length.between?(1, 3), keys, 1,
"an array of one to three elements [class, :key, default]")
end
else
name = keys.to_s
get_class_or_key(args_1, Object, keys, nil)
end
@locals = val
eval("#{name} = @locals", bind)
val
end
remove_instance_variable("@locals")
if vals.length == 1
vals.first
else
vals
end
end
add_help(:load_init_file,
"load_init_file(file) \
Returns false if file doesn't exist, otherwise loads it. \
File may reside in current working dir or in $HOME dir.")
def load_init_file(file)
if File.exists?(file)
Snd.catch do load(file) end
elsif File.exists?(f = ENV["HOME"] + "/" + file)
Snd.catch do load(f) end
else
false
end
end
let(-1) do |count|
# see rotate_phase(func, snd, chn) in dsp.rb
# it's necessary to produce a uniq method name
make_proc_with_setter(:edit_list_proc_counter, lambda { count }, lambda { count += 1 })
end
module Examp
# (ext)snd.html examples made harder to break
#
# this mainly involves keeping track of the current sound/channel
add_help(:selection_rms, "selection_rms() -> rms of selection data using sample readers")
def selection_rms
if selection?
reader = make_sample_reader(selection_position)
len = selection_frames
sum = 0.0
len.times do
val = next_sample(reader)
sum += val * val
end
free_sample_reader(reader)
sqrt(sum / len)
else
Snd.raise(:no_active_selection)
end
end
add_help(:region_rms, "region_rms([n=0]) -> rms of region n's data (chan 0)")
def region_rms(n = 0)
if region?(n)
data = region2vct(0, 0, n)
sqrt(dot_product(data, data) / data.length)
else
Snd.raise(:no_such_region)
end
end
add_help(:window_samples, "window_samples([snd=false, [chn=false]]) \
-> samples in snd channel chn in current graph window")
def window_samples(snd = false, chn = false)
wl = left_sample(snd, chn)
wr = right_sample(snd, chn)
channel2vct(wl, 1 + (wr - wl), snd, chn)
end
add_help(:display_energy,
"display_energy(snd, chn) \
is a $lisp_graph_hook function to display the time domain data as energy (squared).
$lisp_graph_hook.add_hook!(\"display-energy\", &method(:display_energy).to_proc)")
def display_energy(snd, chn)
ls = left_sample(snd, chn)
rs = right_sample(snd, chn)
datal = make_graph_data(snd, chn)
data = vct?(datal) ? datal : datal[1]
sr = srate(snd)
y_max = y_zoom_slider(snd, chn)
if data and ls and rs
vct_multiply!(data, data)
graph(data, "energy", ls / sr, rs / sr, 0.0, y_max * y_max, snd, chn)
end
end
add_help(:display_db,
"display_db(snd, chn) \
is a lisp-graph-hook function to display the time domain data in dB.
$lisp_graph_hook.add_hook!(\"display-db\", &method(:display_db).to_proc)")
def display_db(snd, chn)
if datal = make_graph_data(snd, chn)
dB = lambda do |val|
if val < 0.001
-60.0
else
20.0 * log10(val)
end
end
data = vct?(datal) ? datal : datal[1]
sr = srate(snd)
ls = left_sample(snd, chn)
rs = right_sample(snd, chn)
data.map! do |val| 60.0 + dB.call(val.abs) end
graph(data, "dB", ls / sr, rs / sr, 0.0, 60.0, snd, chn)
end
end
add_help(:window_rms, "window_rms() -> rms of data in currently selected graph window")
def window_rms
ls = left_sample
rs = right_sample
data = channel2vct(ls, 1 + (rs - ls))
sqrt(dot_product(data, data), data.length)
end
add_help(:fft_peak,
"fft_peak(snd, chn, scale) returns the peak spectral magnitude
$after_transform_hook.add_hook!(\"fft-peak\") do |snd, chn, scale|
fft_peak(snd, chn, scale)
end")
def fft_peak(snd, chn, scale)
if transform_graph? and transform_graph_type == Graph_once
report_in_minibuffer(((2.0 * vct_peak(transform2vct(snd, chn))) / transform_size).to_s, snd)
else
false
end
end
# 'info' from extsnd.html using format
add_help(:finfo, "finfo(file) -> description (as a string) of file")
def finfo(file)
chans = mus_sound_chans(file)
sr = mus_sound_srate(file)
format("%s: chans: %d, srate: %d, %s, %s, len: %1.3f",
file, chans, sr,
mus_header_type_name(mus_sound_header_type(file)),
mus_data_format_name(mus_sound_data_format(file)),
mus_sound_samples(file).to_f / (chans * sr.to_f))
end
# Correlation
#
# correlation of channels in a stereo sound
add_help(:correlate,
"correlate(snd, chn, y0, y1) \
returns the correlation of snd's 2 channels (intended for use with $graph_hook)
$graph_hook.add_hook!(\"correlate\") do |snd, chn, y0, y1|
correlate(snd, chn, y0, y1)
end")
def correlate(snd, chn, y0, y1)
if channels(snd) == 2 and frames(snd, 0) > 1 and frames(snd, 1) > 1
ls = left_sample(snd, 0)
rs = right_sample(snd, 0)
ilen = 1 + (rs - ls)
pow2 = (log(ilen) / log(2)).ceil
fftlen = (2 ** pow2).to_i
fftlen2 = fftlen / 2
fftscale = 1.0 / fftlen
rl1 = channel2vct(ls, fftlen, snd, 0)
rl2 = channel2vct(ls, fftlen, snd, 1)
im1 = make_vct(fftlen)
im2 = make_vct(fftlen)
fft(rl1, im1, 1)
fft(rl2, im2, 1)
tmprl = vct_copy(rl1)
tmpim = vct_copy(im1)
data3 = make_vct(fftlen2)
vct_multiply!(tmprl, rl2)
vct_multiply!(tmpim, im2)
vct_multiply!(im2, rl1)
vct_multiply!(rl2, im1)
vct_add!(tmprl, tmpim)
vct_subtract!(im2, rl2)
fft(tmprl, im2, -1)
vct_add!(data3, tmprl)
vct_scale!(data3, fftscale)
graph(data3, "lag time", 0, fftlen2)
else
report_in_minibuffer("correlate wants stereo input")
end
end
# set transform-size based on current time domain window size
#
# also zoom spectrum based on y-axis zoom slider
add_help(:zoom_spectrum,
"zoom_spectrum(snd, chn, y0, y1) \
sets the transform size to correspond to the time-domain window size (use with $graph_hook)
$graph_hook.add_hook!(\"zoom-spectrum\") do |snd, chn, y0, y1|
zoom_spectrum(snd, chn, y0, y1)
end")
def zoom_spectrum(snd, chn, y0, y1)
if transform_graph?(snd, chn) and transform_graph_type(snd, chn) == Graph_once
set_transform_size((log(right_sample(snd, chn) - left_sample(snd, chn)) / log(2.0)).ceil,
snd, chn)
set_spectro_cutoff(y_zoom_slider(snd, chn), snd, chn)
end
false
end
add_help(:zoom_fft,
"zoom_fft(snd, chn, y0, y1) \
sets the transform size if the time domain is not displayed (use with $graph_hook)
It also sets the spectrum display start point based on the x position slider---\
this can be confusing if fft normalization is on (the default)
$graph_hook.add_hook!(\"zoom-fft\") do |snd, chn, y0, y1|
zoom_fft(snd, chn, y0, y1)
end")
def zoom_fft(snd, chn, y0, y1)
if transform_graph?(snd, chn) and
(not time_graph?(snd, chn)) and
transform_graph_type(snd, chn) == Graph_once
set_transform_size(2 ** (log(right_sample(snd, chn) - left_sample(snd, chn)) / log(2.0)).ceil,
snd, chn)
set_spectro_start(x_position_slider(snd, chn), snd, chn)
set_spectro_cutoff(y_zoom_slider(snd, chn), snd, chn)
end
false
end
# superimpose spectra of sycn'd sounds
add_help(:superimpose_ffts,
"superimpose_ffts(snd, chn, y0, y1) \
superimposes ffts of multiple (syncd) sounds (use with $graph_hook)
$graph_hook.add_hook!(\"superimpose-ffts\") do |snd, chn, y0, y1|
superimpose_ffts(snd, chn, y0, y1)
end")
def superimpose_ffts(snd, chn, y0, y1)
maxsync = Snd.sounds.map do |s| sync(s) end.max
if sync(snd) > 0 and
snd == Snd.sounds.map do |s| sync(snd) == sync(s) ? s : maxsync + 1 end.min
ls = left_sample(snd, chn)
rs = right_sample(snd, chn)
pow2 = (log(rs - ls) / log(2)).ceil
fftlen = (2 ** pow2).to_i
if pow2 > 2
ffts = []
Snd.sounds.each do |s|
if sync(snd) == sync(s) and channels(s) > chn
fdr = channel2vct(ls, fftlen, s, chn)
fdi = make_vct(fftlen)
spectr = make_vct(fftlen / 2)
ffts.push(spectr.add(spectrum(fdr, fdi, false, 2)))
end
end
graph(ffts, "spectra", 0.0, 0.5, false, false, snd, chn)
end
end
false
end
# c-g? example (Anders Vinjar)
add_help(:locate_zero,
"locate_zero(limit) \
looks for successive samples that sum to less than 'limit', moving the cursor if successful")
def locate_zero(limit)
start = cursor
sf = make_sample_reader(start)
val0 = sf.call.abs
val1 = sf.call.abs
n = start
until sample_reader_at_end?(sf) or c_g? or val0 + val1 < limit
val0, val1 = val1, sf.call.abs
n += 1
end
free_sample_reader(sf)
set_cursor(n)
end
# make a system call from the listener
#
# shell("df") for example
# or to play a sound whenever a file is closed:
# $close-hook.add_hook!() do |snd| shell("sndplay wood16.wav"); false end
add_help(:shell, "shell(*cmd) \
sends 'cmd' to a shell (executes it as a shell command) and returns the result string.")
def shell(*cmd)
if !string?(cmd.first) or cmd.first.empty?
""
else
str = ""
IO.popen(format(*cmd)) do |f|
until f.eof?
str << f.gets
end
end
str
end
end
# translate mpeg input to 16-bit linear and read into Snd
#
# mpg123 with the -s switch sends the 16-bit (mono or stereo) representation of
# an mpeg file to stdout. There's also apparently a switch to write 'wave' output.
add_help(:mpg, "mpg(file, tmpname) converts file from MPEG to raw 16-bit samples using mpg123
mpg(\"mpeg.mpg\", \"mpeg.raw\")")
def mpg(mpgfile, rawfile)
b0 = b1 = b2 = b3 = 0
File.open(mpgfile, "r") do |fd|
b0 = fd.readchar
b1 = fd.readchar
b2 = fd.readchar
b3 = fd.readchar
end
if b0 != 255 or (b1 & 0b11100000) != 0b11100000
Snd.display("%s is not an MPEG file (first 11 bytes: %b %b",
mpgfile.inspect, b0, b1 & 0b11100000)
else
id = (b1 & 0b11000) >> 3
layer = (b1 & 0b110) >> 1
srate_index = (b2 & 0b1100) >> 2
channel_mode = (b3 & 0b11000000) >> 6
if id == 1
Snd.display("odd: %s is using a reserved Version ID", mpgfile.inspect)
end
if layer == 0
Snd.display("odd: %s is using a reserved layer description", mpgfile.inspect)
end
chans = channel_mode == 3 ? 1 : 2
mpegnum = id.zero? ? 4 : (id == 2 ? 2 : 1)
mpeg_layer = layer == 3 ? 1 : (layer == 2 ? 2 : 3)
srate = [44100, 48000, 32000, 0][srate_index] / mpegnum
Snd.display("%s: %s Hz, %s, MPEG-%s",
mpgfile.inspect, srate, chans == 1 ? "mono": "stereo", mpeg_layer)
system(format("mpg123 -s %s > %s", mpgfile, rawfile))
open_raw_sound(rawfile, chans, srate, little_endian? ? Mus_lshort : Mus_bshort)
end
end
# read and write OGG files
add_help(:read_ogg,
"read_ogg(filename) read OGG files
$open_hook.add_hook!(\"read-ogg\") do |filename|
if mus_sound_header_type(filename) == Mus_raw
read_ogg(filename)
else
false
end
end")
def read_ogg(filename)
flag = false
File.open(filename, "r") do |fd|
flag = fd.readchar == ?O and fd.readchar == ?g and fd.readchar == ?g and fd.readchar == ?S
end
if flag
aufile = filename + ".au"
File.unlink(aufile) if File.exists?(aufile)
system(format("ogg123 -d au -f %s %s", aufile, filename))
aufile
else
false
end
end
def write_ogg(snd)
if edits(snd)[0] > 0 or header_type(snd) != Mus_riff
file = file_name(snd) + ".tmp"
save_sound_as(file, snd, Mus_riff)
system("oggenc " + file)
File.unlink(file)
else
system("oggenc " + file_name(snd))
end
end
# read and write Speex files
def read_speex(filename)
wavfile = filename + ".wav"
File.unlink(wavfile) if File.exists?(wavfile)
system(format("speexdec %s %s", filename, wavfile))
wavfile
end
def write_speex(snd)
if edits(snd)[0] > 0 or header_type(snd) != Mus_riff
file = file_name(snd) + ".wav"
spxfile = file_name(snd) + "spx"
save_sound_as(file, snd, Mus_riff)
system(format("speexenc %s %s", file, spxfile))
File.unlink(file)
else
system(format("speexenc %s %s", file_name(snd), spxfile))
end
end
# read and write FLAC files
def read_flac(filename)
system(format("flac -d %s", filename))
end
def write_flac(snd)
if edits(snd)[0] > 0 or header_type(snd) != Mus_riff
file = file_name(snd) + ".wav"
save_sound_as(file, snd, Mus_riff)
system(format("flac %s", file))
File.unlink(file)
else
system(format("flac %s ", file_name(snd)))
end
end
# read ASCII files
#
# these are used by Octave (WaveLab) -- each line has one integer,
# apparently a signed short.
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
end
# make dot size dependent on number of samples being displayed
#
# this could be extended to set time_graph_style to Graph_lines if
# many samples are displayed, etc
add_help(:auto_dot,
"auto_dot(snd, chn, y0, y1) \
sets the dot size depending on the number of samples being displayed (use with $graph_hook)
$graph_hook.add_hook!(\"auto-dot\") do |snd, chn, y0, y1|
auto_dot(snd, chn, y0, y1)
end")
def auto_dot(snd, chn, y0, y1)
dots = right_sample(snd, chn) - left_sample(snd, chn)
case dots
when 100
set_dot_size(1, snd, chn)
when 50
set_dot_size(2, snd, chn)
when 25
set_dot_size(3, snd, chn)
else
set_dot_size(5, snd, chn)
end
false
end
# move window left edge to mark upon 'm'
#
# in large sounds, it can be pain to get the left edge of the window
# aligned with a specific spot in the sound. In this code, we
# assume the desired left edge has a mark, and the 'm' key (without
# control) will move the window left edge to that mark.
add_help(:first_mark_in_window_at_left,
"first_mark_in_window_at_left() \
moves the graph so that the leftmost visible mark is at the left edge
bind_key(?m, 0, lambda do | | first_mark_in_window_at_left end)")
def first_mark_in_window_at_left
keysnd = Snd.snd
keychn = Snd.chn
current_left_sample = left_sample(keysnd, keychn)
chan_marks = marks(keysnd, keychn)
if chan_marks.null?
report_in_minibuffer("no marks!")
else
leftmost = chan_marks.map do |m| mark_sample(m) end.detect do |m| m > current_left_sample end
if leftmost.null?
report_in_minibuffer("no mark in window")
else
set_left_sample(leftmost, keysnd, keychn)
Keyboard_no_action
end
end
end
# flash selected data red and green
add_help(:flash_selected_data,
"flash_selected_data(millisecs) causes the selected data to flash red and green")
def flash_selected_data(interval)
if selected_sound
set_selected_data_color(selected_data_color == Red ? Green : Red)
call_in(interval, lambda do | | flash_selected_data(interval) end)
end
end
# use loop info (if any) to set marks at loop points
add_help(:mark_loops,
"mark_loops() places marks at loop points found in the selected sound's header")
def mark_loops
loops = (sound_loop_info or mus_sound_loop_info(file_name))
if loops and !loops.empty?
unless loops[0].zero? and loops[1].zero?
add_mark(loops[0])
add_mark(loops[1])
unless loops[2].zero? and loops[3].zero?
add_mark(loops[2])
add_mark(loops[3])
end
end
else
Snd.display("%s has no loop info", short_file_name.inspect)
end
end
# mapping extensions (map arbitrary single-channel function over
# various channel collections)
add_help(:do_all_chans,
"do_all_chans(edhist, &func) \
applies func to all active channels, using edhist as the edit history indication:
do_all_chans(\"double all samples\", do |val| 2.0 * val end)")
def do_all_chans(origin, &func)
Snd.sounds.each do |snd|
channels(snd).times do |chn|
map_channel(func, 0, false, snd, chn, false, origin)
end
end
end
add_help(:update_graphs, "update_graphs() updates (redraws) all graphs")
def update_graphs
Snd.sounds.each do |snd|
channels(snd).times do |chn|
update_time_graph(snd, chn)
end
end
end
add_help(:do_chans,
"do_chans(edhist, &func) \
applies func to all sync'd channels using edhist as the edit history indication")
def do_chans(*origin, &func)
snc = sync
if snc > 0
Snd.sounds.each do |snd|
channels(snd).times do |chn|
if sync(snd) == snc
map_channel(func, 0, false, snd, chn, false, (origin.empty? ? "" : format(*origin)))
end
end
end
else
snd_warning("sync not set")
end
end
add_help(:do_sound_chans,
"do_sound_chans(edhist, &func) \
applies func to all selected channels using edhist as the edit history indication")
def do_sound_chans(*origin, &func)
if snd = selected_sound
channels(snd).times do |chn|
map_channel(func, 0, false, snd, chn, false, (origin.empty? ? "" : format(*origin)))
end
else
snd_warning("no selected sound")
end
end
add_help(:every_sample?,
"every_sample?(&func) \
-> true if func is not false for all samples in the current channel, \
otherwise it moves the cursor to the first offending sample")
def every_sample?(&func)
snd = Snd.snd
chn = Snd.chn
if baddy = scan_channel(lambda do |y| (not func.call(y)) end, 0, frames(snd, chn), snd, chn)
set_cursor(baddy[1])
end
(not baddy)
end
add_help(:sort_samples, "sort_samples(bins) provides a histogram in 'bins' bins")
def sort_samples(nbins)
bins = make_array(nbins, 0)
scan_channel(lambda do |y|
bin = (y.abs * nbins).floor
bins[bin] += 1
false
end)
bins
end
# mix mono sound into stereo sound panning according to env
add_help(:place_sound,
"place_sound(mono_snd, stereo_snd, pan_env) \
mixes a mono sound into a stereo sound, splitting it into two copies \
whose amplitudes depend on the envelope 'pan-env'. \
If 'pan-env' is a number, the sound is split such that 0 is all in channel 0 \
and 90 is all in channel 1.")
def place_sound(mono_snd, stereo_snd, pan_env)
len = frames(mono_snd)
if number?(pan_env)
pos = pan_env / 90.0
rd0 = make_sample_reader(0, mono_snd)
rd1 = make_sample_reader(0, mono_snd)
map_channel(lambda do |y| y + pos * read_sample(rd1) end, 0, len, stereo_snd, 1)
map_channel(lambda do |y| y + (1.0 - pos) * read_sample(rd0) end, 0, len, stereo_snd, 0)
else
e0 = make_env(:envelope, pan_env, :end, len - 1)
e1 = make_env(:envelope, pan_env, :end, len - 1)
rd0 = make_sample_reader(0, mono_snd)
rd1 = make_sample_reader(0, mono_snd)
map_channel(lambda do |y| y + env(e1) * read_sample(rd1) end, 0, len, stereo_snd, 1)
map_channel(lambda do |y| y + (1.0 - env(e0)) * read_sample(rd0) end, 0, len, stereo_snd, 0)
end
end
# FFT-based editing
add_help(:fft_edit,
"fft_edit(low_Hz, high_Hz) \
ffts an entire sound, removes all energy below low-Hz and all above high-Hz, then inverse ffts.")
def fft_edit(bottom, top, snd = false, chn = false)
sr = srate(snd).to_f
len = frames(snd, chn)
fsize = (2.0 ** (log(len) / log(2.0)).ceil).to_i
rdata = channel2vct(0, fsize, snd, chn)
idata = make_vct(fsize)
lo = (bottom / (sr / fsize)).round
hi = (top / (sr / fsize)).round
fft(rdata, idata, 1)
j = fsize - 1
lo.times do |i|
rdata[i] = rdata[j] = 0.0
rdata[i] = rdata[j] = 0.0
j -= 1
end
j = fsize - hi
(hi..(fsize / 2)).each do |i|
rdata[i] = rdata[j] = 0.0
rdata[i] = rdata[j] = 0.0
j -= 1
end
fft(rdata, idata, -1)
vct_scale!(rdata, 1.0 / fsize)
vct2channel(rdata, 0, len - 1, snd, chn, false, format("%s(%s, %s", get_func_name, bottom, top))
end
add_help(:fft_squelch,
"fft_squelch(squelch, [snd=false, [chn=false]]) \
ffts an entire sound, sets all bins to 0.0 whose energy is below squelch, then inverse ffts")
def fft_squelch(squelch, snd = false, chn = false)
sr = srate(snd).to_f
len = frames(snd, chn)
fsize = (2.0 ** (log(len) / log(2.0)).ceil).to_i
rdata = channel2vct(0, fsize, snd, chn)
idata = make_vct(fsize)
fsize2 = fsize / 2
fft(rdata, idata, 1)
vr = vct_copy(rdata)
vi = vct_copy(idata)
rectangular2polar(vr, vi)
scaler = vct_peak(vr)
scl_squelch = squelch * scaler
j = fsize - 1
fsize2.times do |i|
if sqrt(rdata[i] * rdata[i] + idata[i] * idata[i]) < scl_squelch
rdata[i] = rdata[j] = 0.0
rdata[i] = rdata[j] = 0.0
end
j -= 1
end
fft(rdata, idata, -1)
vct_scale!(rdata, 1.0 / fsize)
vct2channel(rdata, 0, len - 1, snd, chn, false, format("%s(%s", get_func_name, squelch))
scaler
end
add_help(:fft_cancel,
"fft_cancel(lo_freq, hi_freq, [snd=false, [chn=false]]) \
ffts an entire sound, sets the bin(s) representing lo_freq to hi_freq to 0.0, then inverse ffts")
def fft_cancel(lo_freq, hi_freq, snd = false, chn = false)
sr = srate(snd).to_f
len = frames(snd, chn)
fsize = (2.0 ** (log(len) / log(2.0)).ceil).to_i
rdata = channel2vct(0, fsize, snd, chn)
idata = make_vct(fsize)
fsize2 = fsize / 2
fft(rdata, idata, 1)
hz_bin = sr / fsize
lo_bin = (lo_freq / hz_bin).round
hi_bin = (hi_freq / hz_bin).round
j = fsize - lo_bin - 1
fsize2.times do |i|
if i > hi_bin
rdata[i] = rdata[j] = 0.0
rdata[i] = rdata[j] = 0.0
end
j -= 1
end
fft(rdata, idata, -1)
vct_scale!(rdata, 1.0 / fsize)
vct2channel(rdata, 0, len - 1, snd, chn, false,
format("%s(%s, %s", get_func_name, lo_freq, hi_freq))
end
# same idea but used to distinguish vowels (steady-state) from consonants
add_help(:ramp,
"ramp(gen, up) \
is a kind of CLM generator that produces a ramp of a given length, \
then sticks at 0.0 or 1.0 until the 'up' argument changes")
def ramp(gen, up)
ctr, size = gen[0, 2]
val = ctr / size
gen[0] = [size, [0, ctr + (up ? 1 : -1)].max].min
val
end
add_help(:make_ramp, "make_ramp([size=128]) returns a ramp generator")
def make_ramp(size = 128)
[0, size]
end
add_help(:squelch_vowels,
"squelch_vowels([snd=false, [chn=false]]) \
suppresses portions of a sound that look like steady-state")
def squelch_vowels(snd = false, chn = false)
fft_size = 32
fft_mid = fft_size / 2
rl = make_vct(fft_size)
im = make_vct(fft_size)
ramper = make_ramp(256)
peak = maxamp / fft_mid
read_ahead = make_sample_reader(0, snd, chn)
ctr = fft_size - 1
ctr.times do |i| rl[i] = read_sample(read_ahead) end
in_vowel = false
map_channel(lambda do |y|
rl[ctr] = read_sample(read_ahead)
ctr += 1
if ctr == fft_size
fft(rl, im, 1)
vct_multiply!(rl, rl)
vct_multiply!(im, im)
vct_add!(rl, im)
in_vowel = (rl[0] + rl[1] + rl[2] + rl[3]) > peak
vct_fill!(im, 0.0)
ctr = 0
end
y * (1.0 - ramp(ramper, in_vowel))
end, 0, false, snd, chn, false, "squelch_vowels(")
end
add_help(:fft_env_data,
"fft_env_data(fft-env, [snd=false, [chn=false]]) \
applies fft_env as spectral env to current sound, returning vct of new data")
def fft_env_data(fft_env, snd = false, chn = false)
sr = srate(snd)
len = frames(snd, chn)
fsize = (2 ** (log(len) / log(2.0)).ceil).to_i
rdata = channel2vct(0, fsize, snd, chn)
idata = make_vct(fsize)
fsize2 = fsize / 2
en = make_env(:envelope, fft_env, :end, fsize2 - 1)
fft(rdata, idata, 1)
j = fsize - 1
fsize2.times do |i|
val = env(en)
rdata[i] *= val
idata[i] *= val
rdata[j] *= val
idata[j] *= val
j -= 1
end
fft(rdata, idata, -1)
vct_scale!(rdata, 1.0 / fsize)
end
add_help(:fft_env_edit,
"fft_env_edit(fft-env, [snd=false, [chn=false]]) \
edits (filters) current chan using fft_env")
def fft_env_edit(fft_env, snd = false, chn = false)
vct2channel(fft_env_data(fft_env, snd, chn), 0, frames(snd, chn) - 1, snd, chn, false,
format("%s(%s", get_func_name, fft_env.inspect))
end
add_help(:fft_env_interp,
"fft_env_interp(env1, env2, interp, [snd=false, [chn=false]]) \
interpolates between two fft-filtered versions (env1 and env2 are the spectral envelopes) \
following interp (an env between 0 and 1)")
def fft_env_interp(env1, env2, interp, snd = false, chn = false)
data1 = fft_env_data(env1, snd, chn)
data2 = fft_env_data(env2, snd, chn)
len = frames(snd, chn)
en = make_env(:envelope, interp, :end, len - 1)
new_data = make_vct!(len) do |i|
pan = env(en)
(1.0 - pan) * data1[i] + pan * data2[i]
end
vct2channel(new_data, 0, len - 1, snd, chn, false,
format("%s(%s, %s, %s", get_func_name, env1.inspect, env2.inspect, interp.inspect))
end
add_help(:fft_smoother,
"fft_smoother(cutoff, start, samps, [snd=false, [chn=false]]) \
uses fft-filtering to smooth a section:
vct2channel(fft_smoother(0.1, cursor, 400, 0, 0), cursor, 400)")
def fft_smoother(cutoff, start, samps, snd = false, chn = false)
fftpts = (2 ** (log(samps + 1) / log(2.0)).ceil).to_i
rl = channel2vct(start, samps, snd, chn)
im = make_vct(fftpts)
top = (fftpts * cutoff.to_f).floor
old0 = rl[0]
old1 = rl[samps - 1]
oldmax = vct_peak(rl)
fft(rl, im, 1)
(top...fftpts).each do |i| rl[i] = im[i] = 0.0 end
fft(rl, im, -1)
vct_scale!(rl, 1.0 / fftpts)
newmax = vct_peak(rl)
if newmax.zero?
rl
else
if (scl = oldmax / newmax) > 1.5
vct_scale!(rl, scl)
end
new0 = rl[0]
new1 = rl[samps - 1]
offset0 = old0 - new0
offset1 = old1 - new1
incr = offset0 == offset1 ? 0.0 : ((offset1 - offset0) / samps)
trend = offset0 - incr
rl.map do |val|
trend += incr
val += trend
end
end
end
# comb-filter
add_help(:comb_filter,
"comb_filter(scaler, size) \
returns a comb-filter ready for map_channel etc: map_channel(comb_filter(0.8, 32)). \
If you're in a hurry use: clm_channel(make_comb(0.8, 32)) instead")
def comb_filter(scaler, size)
cmb = make_comb(scaler, size)
lambda do |inval| comb(cmb, inval) end
end
# by using filters at harmonically related sizes, we can get chords:
add_help(:comb_chord,
"comb_chord(scaler, size, amp, [interval_one=0.75, [interval_two=1.2]]) \
returns a set of harmonically-related comb filters: map_channel(comb_chord(0.95, 100, 0.3))")
def comb_chord(scaler, size, amp, interval_one = 0.75, interval_two = 1.2)
c1 = make_comb(scaler, size.to_i)
c2 = make_comb(scaler, (interval_one * size).to_i)
c3 = make_comb(scaler, (interval_two * size).to_i)
lambda do |inval| amp * (comb(c1, inval) + comb(c2, inval) + comb(c3, inval)) end
end
# or change the comb length via an envelope:
add_help(:zcomb,
"zcomb(scaler, size, pm) \
returns a comb filter whose length varies according to an envelope: \
map_channel(zcomb(0.8, 32, [0, 0, 1, 10]))")
def zcomb(scaler, size, pm)
max_envelope_1 = lambda do |en, mx|
1.step(en.length - 1, 2) do |i| mx = [mx, en[i]].max.to_f end
mx
end
cmb = make_comb(scaler, size, :max_size, (max_envelope_1.call(pm, 0.0) + size + 1).to_i)
penv = make_env(:envelope, pm, :end, frames)
lambda do |inval| comb(cmb, inval, env(penv)) end
end
add_help(:notch_filter,
"notch_filter(scaler, size) returns a notch-filter: map_channel(notch_filter(0.8, 32))")
def notch_filter(scaler, size)
gen = make_notch(scaler, size)
lambda do |inval| notch(gen, inval) end
end
add_help(:formant_filter,
"formant_filter(radius, frequency) \
returns a formant generator: map_channel(formant_filter(0.99, 2400)). \
Faster is: filter_sound(make_formant(0.99, 2400))")
def formant_filter(radius, freq)
frm = make_formant(radius, freq)
lambda do |inval| formant(frm, inval) end
end
# to impose several formants, just add them in parallel:
add_help(:formants,
"formants(r1, f1, r2, f2, r3, f3) \
returns 3 formant filters in parallel: map_channel(formants(0.99, 900, 0.98, 1800, 0.99 2700))")
def formants(r1, f1, r2, f2, r3, f3)
fr1 = make_formant(r1, f1)
fr2 = make_formant(r2, f2)
fr3 = make_formant(r3, f3)
lambda do |inval| formant(fr1, inval) + formant(fr1, inval) + formant(fr1, inval) end
end
add_help(:moving_formant,
"moving_formant(radius, move) \
returns a time-varying (in frequency) formant filter: \
map_channel(moving_formant(0.99, [0, 1200, 1, 2400]))")
def moving_formant(radius, move)
frm = make_formant(radius, move[1])
menv = make_env(:envelope, move, :end, frames)
lambda do |inval|
val = formant(frm, inval)
frm.frequency = env(menv)
val
end
end
add_help(:osc_formants,
"osc_formants(radius, bases, amounts, freqs) \
set up any number of independently oscillating formants, then calls map_channel: \
osc_formants(0.99, vct(400, 800, 1200), vct(400, 800, 1200), vct(4, 2, 3))")
def osc_formants(radius, bases, amounts, freqs)
len = bases.length
frms = make_array(len) do |i| make_formant(radius, bases[i]) end
oscs = make_array(len) do |i| make_oscil(freqs[i]) end
map_channel_rb() do |x|
val = 0.0
frms.each_with_index do |frm, i|
val += formant(frm, x)
set_mus_frequency(frm, bases[i] + amounts[i] * oscil(oscs[i]))
end
val
end
end
# echo
add_help(:echo, "echo(scaler, secs) returns an echo maker: map_channel(echo