# dlocsig.rb -- CLM -> Snd/Ruby translation of dlocsig.lisp -*- snd-ruby -*-
# Copyright (C) 2003--2005 Michael Scholz
# Translator/Author: Michael Scholz <scholz-micha@gmx.de>
# Created: Tue Mar 25 23:21:37 CET 2003
# Last: Thu May 19 19:43:43 CEST 2005
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free
# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307 USA
# Original Copyright of Fernando Lopez Lezcano:
# ;;; Copyright (c) 92, 93, 94, 98, 99, 2000, 2001 Fernando Lopez Lezcano.
# ;;; All rights reserved.
# ;;; Use and copying of this software and preparation of derivative works
# ;;; based upon this software are permitted and may be copied as long as
# ;;; no fees or compensation are charged for use, copying, or accessing
# ;;; this software and all copies of this software include this copyright
# ;;; notice. Suggestions, comments and bug reports are welcome. Please
# ;;; address email to: nando@ccrma.stanford.edu
# ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
# ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
# ;;; Dynamic multichannel three-dimentional signal locator
# ;;; (wow that sound good! :-)
# ;;;
# ;;; by Fernando Lopez Lezcano
# ;;; CCRMA, Stanford University
# ;;; nando@ccrma.stanford.edu
# ;;;
# ;;; Thanks to Juan Pampin for help in the initial coding of the new version
# ;;; and for prodding me to finish it. To Joseph L. Anderson and Marcelo Perticone
# ;;; for insights into the Ambisonics coding and decoding process.
# ;;; http://www.york.ac.uk/inst/mustech/3d_audio/ambison.htm for more details...
# Commentary:
# Tested with Snd 7.10, Motif 2.2.2, Gtk+ 2.2.1, Ruby 1.6.6, 1.6.8 and 1.9.0.
#
# The code is a translation of the Lisp code of Fernando Lopez Lezcano
# found in clm-2/dlocsig of the CLM distribution. An extensive
# documentation of the purpose and usage of it can be found in
# clm-2/dlocsig/dlocsig.html.
#
# Note: dlocsig.rb handles not more rev_channels than out_channels;
# B_format_ambisonics handles only 4 out_channels and 0, 1, or 4
# rev_channels.
#
# The simple example
#
# [[-10, 10], [0, 5], [10, 10]].to_path.snd_plot
#
# draws trajectory, velocity, doppler curve, and the acceleration in
# Snd's lisp-graph. If you have gnuplot installed, the example
#
# [[-10, 10], [0, 5], [10, 10]].to_path.pplot
#
# draws all four curves in one gnuplot window.
# DL.make_path
# DL.make_polar_path
# DL.make_closed_path
# and to_path take the following options
#
# Open_bezier_path.new(path, *args)
# :d3, true
# :polar, false
# :error, 0.01
# :curvature, nil
# :initial_direction, [0.0, 0.0, 0.0]
# :final_direction, [0.0, 0.0, 0.0]
#
# Closed_bezier_path.new(path, *args)
# :d3, true
# :polar, false
# :error, 0.01
# :curvature, nil
#
# DL.make_literal_path
# DL.make_literal_polar_path take the following options
#
# Literal_path.new(path, *args)
# :d3, true
# :polar, false
#
# DL.make_spiral_path takes these options
#
# Spiral_path.new :start_angle, 0
# :turns, 2
#
# The make_locsig-replacement make_dlocsig takes these arguments:
#
# DL.make_dlocsig(startime, dur, *args)
# :path, nil
# :scaler, 1.0
# :reverb_amount, 0.05
# :rbm_output, $output
# :rbm_reverb, $reverb
# :output_power, 1.5
# :reverb_power, 0.5
# :render_using, :amplitude_panning
# or :b_format_ambisonics
# or :decoded_ambisonics
#
# Sample instruments (sinewave() and move() below) show how to replace
# the usual make_locsig() and locsig() by DL.make_dlocsig() and
# DL.dlocsig().
# Example functions at the end of the file:
# class Instrument
# sinewave(start, dur, freq, amp, path, amp_env, *dlocsig_args)
# move(start, file, path, *dlocsig_args)
# move_sound(path, *dlocsig_args) do ... end
#
# class With_sound
# run_dlocsig(start, dur, *dlocsig_args) do |samp| ... end
# Classes and Modules:
# module Inject
# inject(n)
# sum(initial)
# product(initial)
#
# class Array
# to_trias
# to_path(*args)
#
# class Sndplot
# initialize(chns)
# snd
# open
# close
#
# class Gnuplot
# initialize
# open
# close
# command(*args)
# reset
# set_autoscale
# set_x_range(range)
# set_y_range(range)
# set_z_range(range)
# set_grid
# set_surface
# set_parametric
# set_ticslevel(level)
# set_title(title)
# set_label(label)
# set_margins(margin)
# set_border(border)
# start_multiplot
# end_multiplot
# size(xorigin, yorigin, xsize, ysize)
# data(data, *args)
# plot_2d_curve(curve, *args)
# plot_2d_curves(curves, *args)
# plot_3d_curve(curve, *args)
#
# module DL
# class Dlocsig < Dlocs
# initialize(start, dur, *args)
# one_turn
# one_turn=(val)
# speed_of_sound
# speed_of_sound=(val)
# run_beg
# run_end
# angles_in_degree
# angles_in_radians
# angles_in_turns
# distance_in_meters
# distance_in_feet
#
# class Path
# initialize(path, *args)
# path_x
# path_y
# path_z
# path_time
# scale_path(scaling)
# translate_path(translation)
# rotate_path(rotation, *args)
#
# path_trajectory
# path_2d_trajectory
# path_velocity
# path_doppler
# path_acceleration
#
# plot_open
# plot_close
# cmd(*args)
# plot_trajectory(*args)
# plot_velocity(reset)
# plot_doppler(reset)
# plot_acceleration(reset)
# pplot(normalize)
#
# snd_open(chns)
# snd_close
# snd_trajectory(chn, label)
# snd_velocity(chn, label)
# snd_doppler(chn, label)
# snd_acceleration(chn, label)
# snd_plot
#
# DL.make_dlocsig(start, dur, *args)
# DL.dlocsig(dl, loc, input)
#
# DL.make_path(path, *args)
# DL.make_polar_path(path, *args)
# DL.make_closed_path(path, *args)
# DL.make_literal_path(path, *args)
# DL.make_literal_polar_path(path, *args)
# DL.make_spiral_path(*args)
#
# class Dlocsig_menu
# initialize(label, snd_p)
# post_dialog
# Code:
require "examp"
require "ws"
require "complex"
require "matrix"
include Math
provided?(:snd_motif) and (not provided?(:xm)) and require("libxm.so")
provided?(:snd_gtk) and (not provided?(:xg)) and require("libxg.so")
class DlocsigError < StandardError
end
Ruby_exceptions[:dlocsig_error] = DlocsigError
def dl_error(*msg)
Snd.raise(:dlocsig_error, (msg.empty? ? "" : format(*msg)))
end
# module Inject, see Thomas, David, Hunt, Andrew: Programming Ruby --
# The Pragmatic Programmer's Guide, 2001 Addison-Wesley, page 102n
module Inject
def inject(n)
each do |x| n = yield(n, x) end
n
end
def sum(initial = 0)
inject(initial) do |n, v| n + v end
end
def product(initial = 1)
inject(initial) do |n, v| n * v end
end
end unless defined? Inject
# used by plotting curves
# to_trias: [0, 1, 2, 3, 4, 5] --> [[0, 1, 2], [3, 4, 5]]
# to_path: [[-10, 10], [0, 5], [10, 10]].to_path <=> DL.make_path([[-10, 10], [0, 5], [10, 10]])
# uses the same options as DL.make_path()
class Array
include Inject
def to_trias
ary = []
unless self.length.divmod(3).last.nonzero?
0.step(self.length - 2, 3) do |i|
ary.push([self[i], self[i + 1], self[i + 2]])
end
end
ary
end
def to_path(*args)
DL.make_path(self, *args)
end
end
class Sndplot
def initialize(chns = 1)
@chns = chns
@snd = open
end
attr_reader :snd
def inspect
format("#<%s: snd: %d, chns: %d>", self.class, @snd, @chns)
end
def open
if snds = sounds()
snds.each do |s| set_sound_property(:selected, false, s) end
set_sound_property(:selected, true, selected_sound)
end
if @snd = snds.detect do |s| channels(s) >= @chns end
set_sound_property(:dlocsig_created, false, @snd)
select_sound(@snd)
else
@snd = new_sound(snd_tempnam, default_output_header_type, default_output_data_format,
default_output_srate, @chns)
set_sound_property(:dlocsig_created, true, @snd)
end
channels(@snd).times do |chn|
set_channel_property(:time_graph, time_graph?(@snd, chn), @snd, chn)
set_channel_property(:transform_graph, transform_graph?(@snd, chn), @snd, chn)
set_channel_property(:lisp_graph, lisp_graph?(@snd, chn), @snd, chn)
set_time_graph?(false, @snd, chn)
set_transform_graph?(false, @snd, chn)
set_lisp_graph?(true, @snd, chn)
end
$exit_hook.add_hook!("dlocsig-hook") do | |
close
false
end
@snd
end
def close
if snds = sounds()
snds.each do |snd|
set_sound_property(:selected, false, snd)
unless sound_property(:dlocsig_created, snd).nil?
if sound_property(:dlocsig_created, snd)
close_sound_extend(snd)
else
channels(snd).times do |chn|
set_time_graph?(channel_property(:time_graph, snd, chn), snd, chn)
set_transform_graph?(channel_property(:transform_graph, snd, chn), snd, chn)
set_lisp_graph?(channel_property(:lisp_graph, snd, chn), snd, chn)
end
end
end
end
end
$exit_hook.remove_hook!("dlocsig-hook")
self
end
end
class Gnuplot
@@plot_stream = nil
def initialize
if (not @@plot_stream) or @@plot_stream.closed?
open
end
end
def inspect
format("#<%s: plot_stream: %s>", self.class, @@plot_stream.inspect)
end
def open
if (gnuplot = `which gnuplot`).empty?
dl_error("gnuplot not found?")
else
@@plot_stream = IO.popen(gnuplot, "w")
end
end
def close
unless @@plot_stream.closed?
@@plot_stream.puts("quit")
@@plot_stream.close
@@plot_stream = nil
end
end
def command(*args)
open if @@plot_stream.closed?
@@plot_stream.printf(*args)
format(*args).chomp
rescue
Snd.warning("%s#%s", self.class, get_func_name)
end
def reset
command "reset\n"
end
def set_autoscale
command "set autoscale\n"
end
def set_x_range(range = [])
command("set xrange [%f:%f]\n", range[0], range[1]) if range.length == 2
end
def set_y_range(range = [])
command("set yrange [%f:%f]\n", range[0], range[1]) if range.length == 2
end
def set_z_range(range = [])
command("set zrange [%f:%f]\n", range[0], range[1]) if range.length == 2
end
def set_grid
command "set grid xtics; set grid ytics; set grid ztics\n"
end
def set_surface
command "set surface\n"
end
def set_parametric
command "set parametric\n"
end
def set_ticslevel(level = 0)
command("set ticslevel %.2f\n", level)
end
def set_title(title = "")
command("set title \"%s\"\n", title) unless title.empty?
end
def set_label(label = "")
command("set label \"%s\"\n", label) unless label.empty?
end
def set_margins(margin = 1)
command("set tmargin %f\n", margin)
command("set lmargin %f\n", margin)
command("set rmargin %f\n", margin)
command("set bmargin %f\n", margin)
end
def set_border(border = nil)
command("set border %d\n", border.to_i) if border
end
def start_multiplot
command "set multiplot\n"
end
def end_multiplot
command "set nomultiplot\n"
end
def size(xorigin, yorigin, xsize, ysize)
command("set origin %f,%f\n", xorigin.to_f, yorigin.to_f)
command("set size %f,%f\n", xsize.to_f, ysize.to_f)
end
def data(data, *args)
style, label = nil
optkey(args, binding,
[:style, "linespoints"],
[:label, ""])
command("plot '-' %s %s\n", label.empty? ? "" : "title \"#{label}\"",
style.empty? ? "" : "with #{style}")
data.each_with_index do |y, x| command("%f %f\n", x, y) end
command "e\n"
end
def plot_2d_curve(curve, *args)
style, label = nil
optkey(args, binding,
[:style, "linespoints"],
[:label, ""])
set_grid()
command("plot '-' %s %s\n", label.empty? ? "" : "title \"#{label}\"",
style.empty? ? "" : "with #{style}")
curve.each_pair do |x, y| command("%.8f %.8f\n", x, y) end
command "e\n"
end
def plot_2d_curves(curves, *args)
styles, labels = nil
optkey(args, binding,
[:styles, "linespoints"],
[:labels, ""])
set_grid()
styles = curves.map do |i| styles end unless array?(styles)
labels = curves.map do |i| labels end unless array?(labels)
command "plot"
curves.each_with_index do |x, i|
style = styles[i]
label = labels[i]
command " '-' "
command(" title \"%s\"", label) if label or (not label.empty?)
command(" with %s", style) if style or (not style.empty?)
command(", ") if i != (curves.length - 1)
end
command "\n"
curves.each do |curve|
curve.each_pair do |x, y| command("%.8f %.8f\n", x, y) end
command "e\n"
end
end
def plot_3d_curve(curve, *args)
style, label, zstyle, xrot, zrot, scale, zscale = nil
optkey(args, binding,
[:style, "linespoints"],
[:label, ""],
[:zstyle, "impulses"],
:xrot,
:zrot,
:scale,
:zscale)
set_border(127 + 256 + 512)
set_grid()
set_surface()
set_parametric()
set_ticslevel(0)
if xrot or zrot or scale or zscale
command("set view %s,%s,%s,%s\n", xrot, zrot, scale, zscale)
end
command "splot '-'"
command(" title \"%s\"", label) unless label.empty?
command(" with %s 1", style) unless style.empty?
command(", '-' notitle with %s 1", zstyle) unless zstyle.empty?
command "\n"
curve.to_trias.each do |x, y, z| command("%.8f %.8f %.8f\n", x, y, z) end
command "e\n"
if zstyle
curve.to_trias.each do |x, y, z| command("%.8f %.8f %.8f\n", x, y, z) end
command "e\n"
end
end
end
module DL
Path_maxcoeff = 8
Point707 = cos(TWO_PI / 8.0)
Amplitude_panning = 1
B_format_ambisonics = 2
Decoded_ambisonics = 3
def which_render(val)
case val
when :amplitude_panning, Amplitude_panning
Amplitude_panning
when :b_format_ambisonics, B_format_ambisonics
B_format_ambisonics
when :decoded_ambisonics, Decoded_ambisonics
Decoded_ambisonics
else
Amplitude_panning
end
end
def cis(r)
Complex(cos(r), sin(r))
end
def distance(x, y, z)
sqrt(x * x + y * y + z * z)
end
def nearest_point(x0, y0, z0, x1, y1, z1, px, py, pz)
if same?(x0, y0, z0, px, py, pz)
[x0, y0, z0]
elsif same?(x1, y1, z1, px, py, pz)
[x1, y1, z1]
elsif same?(x0, y0, z0, x1, y1, z1)
[x0, y0, z0]
else
xm0 = x1 - x0
ym0 = y1 - y0
zm0 = z1 - z0
xm1 = px - x0
ym1 = py - y0
zm1 = pz - z0
d0 = distance(xm0, ym0, zm0)
d1 = distance(xm1, ym1, zm1)
p = d1 * ((xm0 * xm1 + ym0 * ym1 + zm0 * zm1) / (d0 * d1))
ratio = p / d0
[x0 + xm0 * ratio, y0 + ym0 * ratio, z0 + zm0 * ratio]
end
end
def same?(a0, b0, c0, a1, b1, c1)
a0 == a1 and b0 == b1 and c0 == c1
end
def rotation_matrix(x, y, z, angle)
mag = distance(x, y, z)
dx = x / mag
dy = y / mag
dz = z / mag
ri = Matrix.I(3)
ra = Matrix.rows([[0.0, dz, -dy], [-dz, 0.0, dx], [dy, -dx, 0.0]])
raa = ra * ra
sn = sin(-angle)
omcs = 1 - cos(-angle)
raa = raa.map do |x| omcs * x end
ra = ra.map do |x| sn * x end
(ri + ra + raa)
end
class Dlocsig_base
include DL
def initialize
@one_turn = 360.0
@speed_of_sound = 344.0
end
attr_accessor :one_turn, :speed_of_sound
def angles_in_degree
@one_turn = 360.0
end
def angles_in_radians
@one_turn = TWO_PI
end
def angles_in_turns
@one_turn = 1.0
end
def distances_in_meters
@speed_of_sound = 344.0
end
def distances_in_feet
@speed_of_sound = 1128.0
end
end
class Speaker_config < Dlocsig_base
Groups = Struct.new("Groups", :size, :vertices, :speakers, :matrix)
def initialize
super
@number = nil
@coords = nil
@groups = nil
end
protected
def set_speakers(channels, d3)
if channels.between?(1, 8)
d3 = false if channels < 4
arrange_speakers(unless d3
case channels
when 1
[[0]]
when 2
[[-60, 60]]
when 3
[[-45, 45, 180]]
when 4
[[-45, 45, 135, 225]]
when 5
[[-45, 0, 45, 135, -135]]
when 6
[[-60, 0, 60, 120, 180, 240]]
when 7
[[-45, 0, 45, 100, 140, -140, -100]]
when 8
[[-22.5, 22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5]]
end
else
case channels
when 4
[[[-60, 0], [60, 0], [180, 0], [0, 90]],
[[0, 1, 3], [1, 2, 3], [2, 0, 3], [0, 1, 2]]]
when 5
[[[-45, 0], [45, 0], [135, 0], [-135, 0], [0, 90]],
[[0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4], [0, 1, 2], [2, 3, 0]]]
when 6
[[[-45, 0], [45, 0], [135, 0], [-135, 0], [-90, 60], [90, 60]],
[[0, 1, 4], [1, 4, 5], [1, 2, 5], [2, 3, 5],
[3, 4, 5], [3, 0, 4], [0, 1, 2], [2, 3, 0]]]
when 7
[[[-45, 0], [45, 0], [135, 0], [-135, 0],
[-60, 60], [60, 60], [180, 60]],
[[0, 1, 4], [1, 4, 5], [1, 2, 5], [2, 6, 5], [2, 3, 6],
[3, 4, 6], [3, 0, 4], [4, 5, 6], [0, 1, 2], [2, 3, 0]]]
when 8
[[[-45, 10], [45, -10], [135, -10], [225, -10],
[-45, 45], [45, 45], [135, 45], [225, 45]],
[[0, 4, 5], [0, 5, 1], [5, 1, 2], [2, 6, 5], [6, 7, 2], [2, 3, 7],
[3, 7, 4], [3, 0, 4], [4, 7, 6], [6, 5, 4], [0, 1, 2], [2, 3, 0]]]
end
end)
else
dl_error(channels, "only 1 to 8 channels possible")
end
end
private
def arrange_speakers(args)
speakers = args.shift
groups = args.shift
@number = speakers.length
@coords = speakers.map do |s|
a = (array?(s) ? s[0] : s).to_f
e = (array?(s) ? s[1] : 0).to_f
evec = cis((e / @one_turn) * TWO_PI)
dxy = evec.real
avec = cis((a / @one_turn) * TWO_PI)
x = (dxy * avec.image)
y = (dxy * avec.real)
z = evec.image
mag = distance(x, y, z)
[x / mag, y / mag, z / mag]
end
unless groups
if @number == 1
groups = [[0]]
else
groups = make_array(@number) do |i| [i, i + 1] end
groups[-1][-1] = 0
end
end
@groups = groups.map do |group|
size = group.length
vertices = group.map do |vertice| @coords[vertice] end
matrix = case size
when 3
if (m = Matrix[vertices[0], vertices[1], vertices[2]]).regular?
m.inverse.to_a
else
nil
end
when 2
if (m = Matrix[vertices[0][0, 2], vertices[1][0, 2]]).regular?
m.inverse.to_a
else
nil
end
else
nil
end
Groups.new(size, vertices, group, matrix)
end
end
end
class Dlocsig < Speaker_config
def initialize
super
@render_using = Amplitude_panning
@output_power = 1.5
@reverb_power = 0.5
@rbm_output = nil
@rbm_reverb = nil
@out_channels = 4
@rev_channels = 1
@clm = true
@delay = []
@prev_time = @prev_dist = @prev_group = @prev_x = @prev_y = @prev_z = false
@first_dist = @last_dist = 0.0
@min_dist = @max_dist = 0.0
@start = nil
@end = nil
@output_gains = nil
@reverb_gains = nil
@path = nil
@run_beg = @run_end = nil
end
attr_reader :run_beg, :run_end, :out_channels, :rev_channels
def inspect
format("#<%s: channels: %d, reverb: %s>", self.class, @out_channels, @rev_channels.inspect)
end
def each
(@run_beg...@run_end).each do |i| yield(i) end
end
alias run each
# general clm version
# dl.dlocsig(loc, val)
def dlocsig(loc, input)
if loc < @start
delay(@path, (loc >= @end ? 0.0 : input), 0.0)
@out_channels.times do |chn| out_any(loc, 0.0, chn, @rbm_output) end
else
sample = delay(@path, (loc >= @end ? 0.0 : input), env(@delays))
@out_channels.times do |chn|
out_any(loc, sample * env(@output_gains[chn]), chn, @rbm_output)
end
@rev_channels.times do |chn|
out_any(loc, sample * env(@reverb_gains[chn]), chn, @rbm_reverb)
end
end
end
# dl.ws_dlocsig do |loc| ...; val; end
# @clm == true @rbm_output/@rbm_reverb: sample2files
# @clm == false @rbm_output/@rbm_reverb: sound index numbers
# With_sound#run_dlocsig below uses this method
def ws_dlocsig
len = @run_end - @run_beg
out_data = make_vct(len)
len.times do |i|
loc = i + @run_beg
input = yield(loc)
if loc < @start
delay(@path, (loc >= @end ? 0.0 : input), 0.0)
else
out_data[i] = delay(@path, (loc >= @end ? 0.0 : input), env(@delays))
end
end
@out_channels.times do |chn|
if @clm
(@run_beg...@run_end).each do |i|
out_any(i, out_data[i - @run_beg] * env(@output_gains[chn]), chn, @rbm_output)
end
else
out = vct_multiply!(vct_copy(out_data),
vct_map!(make_vct(len), lambda do | | env(@output_gains[chn]) end))
mix_vct(out, @run_beg, @rbm_output, chn, false)
end
end
@rev_channels.times do |chn|
if @clm
(@run_beg...@run_end).each do |i|
out_any(i, out_data[i - @run_beg] * env(@reverb_gains[chn]), chn, @rbm_reverb)
end
else
out = vct_multiply!(vct_copy(out_data),
vct_map!(make_vct(len), lambda do | | env(@reverb_gains[chn]) end))
mix_vct(out, @run_beg, @rbm_reverb, chn, false)
end
end
end
# :amplitude_panning
# :b_format_ambisonics
# :decoded_ambisonics
def make_dlocsig(startime, dur, *args)
path, scaler, reverb_amount, output_power, reverb_power, render_using = nil
rbm_output, rbm_reverb, out_channels, rev_channels, clm = nil
optkey(args, binding,
:path,
[:scaler, 1.0],
[:reverb_amount, 0.05],
[:output_power, 1.5],
[:reverb_power, 0.5],
[:render_using, Amplitude_panning],
[:rbm_output, $output],
[:rbm_reverb, $reverb],
[:out_channels, 4],
[:rev_channels, 1],
[:clm, true])
@output_power = output_power
@reverb_power = reverb_power
@render_using = which_render(render_using)
@rbm_output = rbm_output
@rbm_reverb = rbm_reverb
@out_channels = out_channels
@rev_channels = rev_channels
@clm = clm
if @render_using == B_format_ambisonics and @out_channels != 4
dl_error("B_format_ambisonics requires 4 output channels")
end
if @render_using == B_format_ambisonics and
@rev_channels.nonzero? and
(not (@rev_channels == 1 or @rev_channels == 4))
dl_error("B_format_ambisonics accepts only 0, 1 or 4 rev_channels")
end
if @rev_channels > @out_channels
dl_error("more rev_channels than out_channels")
end
if @render_using == B_format_ambisonics
scaler *= 0.8
end
unless path.kind_of?(Path)
if array?(path) and !path.empty?
path = make_path(path)
else
dl_error(path, "sorry, need a path")
end
end
xpoints = path.path_x
ypoints = path.path_y
zpoints = path.path_z
tpoints = path.path_time
@channel_gains = make_array(@out_channels) do [] end
@channel_rev_gains = make_array(@rev_channels) do [] end
self.set_speakers(@out_channels, (not zpoints.detect do |x| x.nonzero? end.nil?))
@speed_limit = (@speed_of_sound * (tpoints[-1] - tpoints[0])) / dur
if xpoints.length == 1
walk_all_rooms(xpoints[0], ypoints[0], zpoints[0], tpoints[0])
else
xb = yb = zb = tb = 0.0
(tpoints.length - 1).times do |i|
xa, xb = xpoints[i, 2]
ya, yb = ypoints[i, 2]
za, zb = zpoints[i, 2]
ta, tb = tpoints[i, 2]
minimum_segment_length(xa, ya, za, ta, xb, yb, zb, tb)
end
walk_all_rooms(xb, yb, zb, tb)
end
@start = dist2samples(@first_dist - @min_dist)
@end = seconds2samples(startime + dur)
min_delay = dist2samples(@min_dist)
@run_beg = seconds2samples(startime)
@run_end = @end + dist2samples(@last_dist) - min_delay
real_dur = dur + dist2seconds(@last_dist - @first_dist)
min_dist_unity = [@min_dist, 1.0].max
unity_gain = scaler * min_dist_unity ** @output_power
@output_gains = make_array(@number) do |i|
make_env(:envelope, @channel_gains[i], :scaler, unity_gain, :duration, real_dur)
end
if @rev_channels.nonzero?
unity_rev_gain = reverb_amount * scaler * min_dist_unity ** @reverb_power
@reverb_gains = make_array(@rev_channels) do |i|
make_env(:envelope, @channel_rev_gains[i], :scaler, unity_rev_gain, :duration, real_dur)
end
end
@delays = make_env(:envelope, @delay, :offset, -min_delay, :duration, real_dur)
@path = make_delay(:size, 1, :max_size, [1, dist2samples(@max_dist)].max)
self
end
private
def dist2samples(d)
(d * (mus_srate() / @speed_of_sound)).round
end
def dist2seconds(d)
d / @speed_of_sound.to_f
end
def transition_point_3(vert_a, vert_b, xa, ya, za, xb, yb, zb)
line_b = vct(xa, ya, za)
line_m = tr3_sub(vct(xb, yb, zb), line_b)
normal = tr3_cross(vert_a, vert_b)
if (denominator = tr3_dot(normal, line_m)).abs <= 0.000001
false
else
vct2list(tr3_add(line_b, tr3_scale(line_m, -tr3_dot(normal, line_b) / denominator)))
end
end
def tr3_cross(v1, v2)
vct(v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0])
end
def tr3_dot(v1, v2)
dot_product(v1, v2)
end
def tr3_sub(v1, v2)
vct_subtract!(vct_copy(v1), v2)
end
def tr3_add(v1, v2)
vct_add!(vct_copy(v1), v2)
end
def tr3_scale(v1, c)
vct_scale!(vct_copy(v1), c)
end
def transition_point_2(vert, xa, ya, xb, yb)
ax = vert[0]
bx = xa - xb
ay = vert[1]
by = ya - yb
cx = -xa
cy = -ya
d = by * cx - bx * cy
f = ay * bx - ax * by
if f.zero?
false
else
[(d * ax) / f, (d * ay) / f]
end
end
def calculate_gains(x, y, z, group)
zero_coord = 1e-10
zero_gain = 1e-10
size = group.size
if mat = group.matrix
if x.abs < zero_coord and y.abs < zero_coord and z.abs < zero_coord
[true, [1.0, 1.0, 1.0]]
else
case size
when 3
gain_a = mat[0][0] * x + mat[0][1] * y + mat[0][2] * z
gain_b = mat[1][0] * x + mat[1][1] * y + mat[1][2] * z
gain_c = mat[2][0] * x + mat[2][1] * y + mat[2][2] * z
mag = distance(gain_a, gain_b, gain_c)
if gain_a.abs < zero_gain then gain_a = 0.0 end
if gain_b.abs < zero_gain then gain_b = 0.0 end
if gain_c.abs < zero_gain then gain_c = 0.0 end
[(gain_a >= 0 and gain_b >= 0 and gain_c >= 0),
[gain_a / mag, gain_b / mag, gain_c / mag]]
when 2
gain_a = mat[0][0] * x + mat[0][1] * y
gain_b = mat[1][0] * x + mat[1][1] * y
mag = distance(gain_a, gain_b, 0.0)
if gain_a.abs < zero_gain then gain_a = 0.0 end
if gain_b.abs < zero_gain then gain_b = 0.0 end
[(gain_a >= 0 and gain_b >= 0), [gain_a / mag, gain_b / mag]]
when 1
[true, [1.0]]
end
end
else
[true, [1.0, 1.0, 1.0]]
end
end
def find_group(x, y, z)
grp = gns = false
@groups.detect do |group|
inside, gains = calculate_gains(x, y, z, group)
if inside
grp, gns = group, gains
true
end
end
[grp, gns]
end
def push_zero_gains(time)
@out_channels.times do |i| @channel_gains[i].push(time, 0.0) end
@rev_channels.times do |i| @channel_rev_gains[i].push(time, 0.0) end
end
def push_gains(group, gains, dist, time)
outputs = make_vct(@out_channels)
revputs = if @rev_channels > 0
make_vct(@rev_channels)
else
false
end
if dist >= 1.0
att = 1.0 / dist ** @output_power
ratt = 1.0 / dist ** @reverb_power
else
att = 1.0 - dist ** (1.0 / @output_power)
ratt = 1.0 - dist ** (1.0 / @reverb_power)
end
if dist >= 1.0
group.speakers.each_with_index do |speaker, i|
gain = gains[i]
outputs[speaker] = gain * att
if @rev_channels > 1
revputs[speaker] = gain * ratt
end
end
else
@number.times do |speaker|
if found = group.speakers.index(speaker)
gain = gains[found]
outputs[speaker] = gain + (1.0 - gain) * att
if @rev_channels > 1
revputs[speaker] = gain + (1.0 - gain) * ratt
end
else
outputs[speaker] = att
if @rev_channels > 1
revputs[speaker] = ratt
end
end
end
end
vct2list(outputs).each_with_index do |val, i| @channel_gains[i].push(time, val) end
if @rev_channels == 1
@channel_rev_gains[0].push(time, ratt)
elsif @rev_channels > 1
vct2list(revputs).each_with_index do |val, i| @channel_rev_gains[i].push(time, val) end
end
end
def amplitude_panning(x, y, z, dist, time)
if @prev_group
if time != @prev_time and ((dist - @prev_dist) / (time - @prev_time)) > @speed_limit
Snd.display("%s#%s: supersonic radial movement", self.class, get_func_name)
end
inside, gains = calculate_gains(x, y, z, @prev_group)
if inside
push_gains(@prev_group, gains, dist, time)
@prev_x, @prev_y, @prev_z = x, y, z
else
group, gains = find_group(x, y, z)
if group
edge = group.vertices & @prev_group.vertices
if edge.length == 2
if pint = transition_point_3(edge[0], edge[1], x, y, z, @prev_x, @prev_y, @prev_z)
xi, yi, zi = pint
di = distance(xi, yi, zi)
ti = @prev_time +
(distance(xi - @prev_x, yi - @prev_y, zi - @prev_z) / \
distance(x - @prev_x, y - @prev_y, z - @prev_z)) * (time - @prev_time)
if ti < @prev_time
inside, gains = calculate_gains(xi, yi, zi, @prev_group)
if inside
push_gains(@prev_group, gains, di, ti)
else
inside, gains = calculate_gains(xi, yi, zi, group)
if inside
push_gains(group, gains, di, ti)
else
dl_error("outside of both adjacent groups")
end
end
else
if $DEBUG
Snd.warning("%s#%s: current time <= previous time", self.class, get_func_name)
end
end
end
elsif edge.length == 1 and group.size == 2
if pint = transition_point_2(edge[0], x, y, @prev_x, @prev_y)
xi, yi = pint
di = distance(xi, yi, 0.0)
ti = @prev_time +
(distance(xi - @prev_x, yi - @prev_y, 0.0) / \
distance(x - @prev_x, y - @prev_y, 0.0)) * (time - @prev_time)
if ti < @prev_time
inside, gains = calculate_gains(xi, yi, 0.0, @prev_group)
if inside
push_gains(@prev_group, gains, di, ti)
inside, gains = calculate_gains(xi, yi, 0.0, group)
if inside
push_gains(group, gains, di, ti)
else
dl_error("outside of both adjacent groups")
end
end
else
if $DEBUG
Snd.warning("%s#%s: current time <= previous time", self.class, get_func_name)
end
end
end
elsif edge.length == 1
Snd.display("%s#%s: only one point in common", self.class, get_func_name)
elsif edge.length.zero?
Snd.display("%s#%s: with no common points", self.class, get_func_name)
end
push_gains(group, gains, dist, time)
@prev_group, @prev_x, @prev_y, @prev_z = group, x, y, z
else
push_zero_gains(time)
@prev_group = false
end
end
else
group, gains = find_group(x, y, z)
if group
push_gains(group, gains, dist, time)
@prev_group, @prev_x, @prev_y, @prev_z = group, x, y, z
else
push_zero_gains(time)
@prev_group = false
end
end
@prev_time = time
@prev_dist = dist
end
def b_format_ambisonics(x, y, z, dist, time)
if dist > 1.0
att = (1.0 / dist) ** @output_power
@channel_gains[0].push(time, Point707 * att)
@channel_gains[1].push(time, (y / dist) * att)
@channel_gains[2].push(time, (-x / dist) * att)
@channel_gains[3].push(time, (z / dist) * att)
if @rev_channels == 1
@channel_rev_gains[0].push(time, 1.0 / (dist ** @reverb_power))
elsif @rev_channels == 4
ratt = (1.0 / dist) ** @reverb_power
@channel_rev_gains[0].push(time, Point707 * ratt)
@channel_rev_gains[1].push(time, (y / dist) * ratt)
@channel_rev_gains[2].push(time, (-x / dist) * ratt)
@channel_rev_gains[3].push(time, (z / dist) * ratt)
end
elsif dist.zero?
@channel_gains[0].push(time, 1.0)
(1..3).each do |i| @channel_gains[i].push(time, 0.0) end
if @rev_channels >= 1
@channel_rev_gains[0].push(time, 1.0)
end
if @rev_channels == 4
(1..3).each do |i| @channel_rev_gains[i].push(time, 0.0) end
end
else
att = dist ** (1.0 / @output_power)
@channel_gains[0].push(time, 1.0 - (1.0 - Point707) * dist ** @output_power)
@channel_gains[1].push(time, (y / dist) * att)
@channel_gains[2].push(time, (-x / dist) * att)
@channel_gains[3].push(time, (z / dist) * att)
if @rev_channels == 1
@channel_rev_gains[0].push(time, 1.0 - dist ** (1.0 / @reverb_power))
elsif @rev_channels == 4
ratt = dist ** (1.0 / @reverb_power)
@channel_rev_gains[0].push(time, 1.0 - (1.0 - Point707) * dist ** @reverb_power)
@channel_rev_gains[1].push(time, (y / dist) * ratt)
@channel_rev_gains[2].push(time, (-x / dist) * ratt)
@channel_rev_gains[3].push(time, (z / dist) * ratt)
end
end
end
def decoded_ambisonics(x, y, z, dist, time)
if dist > 1.0
att = (1.0 / dist) ** @output_power
attw = Point707 * Point707 * att
attx = att * (x / dist)
atty = att * (y / dist)
attz = att * (z / dist)
@coords.each_with_index do |s, i|
@channel_gains[i].push(time, Point707 * (attw + attx * s[0] + atty * s[1] + attz * s[2]))
end
if @rev_channels == 1
@channel_rev_gains[0].push(time, 1.0 / (dist ** @reverb_power))
elsif @rev_channels == 4
ratt = (1.0 / dist) ** @reverb_power
rattw = Point707 * Point707 * ratt
rattx = ratt * (x / dist)
ratty = ratt * (y / dist)
rattz = ratt * (z / dist)
@rev_channels.times do |i|
s = @coords[i]
@channel_rev_gains[i].push(time, Point707 * \
(rattw + rattx * s[0] + ratty * s[1] + rattz * s[2]))
end
end
elsif dist.zero?
att = Point707 * Point707
@coords.each_index do |i| @channel_gains[i].push(time, att) end
if @rev_channels == 1
@channel_rev_gains[0].push(time, 1.0)
else
@rev_channels.times do |i| @channel_rev_gains[i].push(time, att) end
end
else
att = dist ** (1.0 / @output_power)
attw = Point707 * (1.0 - (1.0 - Point707) * dist ** @output_power)
attx = att * (x / dist)
atty = att * (y / dist)
attz = att * (z / dist)
@coords.each_with_index do |s, i|
@channel_gains[i].push(time, Point707 * (attw + attx * s[0] + atty * s[1] + attz * s[2]))
end
if @rev_channels == 1
@channel_rev_gains[0].push(time, 1.0 - dist ** (1.0 / @reverb_power))
elsif @rev_channels == 4
ratt = dist ** (1.0 / @reverb_power)
rattw = Point707 * (1.0 - (1.0 - Point707) * dist ** @reverb_power)
rattx = ratt * (x / dist)
ratty = ratt * (y / dist)
rattz = ratt * (z / dist)
@rev_channels.times do |i|
s = @coords[i]
@channel_rev_gains[i].push(time, Point707 * \
(rattw + rattx * s[0] + ratty * s[1] + rattz * s[2]))
end
end
end
end
def walk_all_rooms(x, y, z, time)
dist = distance(x, y, z)
if @first_dist.zero?
@first_dist = dist
end
@last_dist = dist
if @min_dist.zero? or dist < @min_dist
@min_dist = dist
end
if @max_dist.zero? or dist > @max_dist
@max_dist = dist
end
@delay.push(time, dist2samples(dist))
case @render_using
when Amplitude_panning
amplitude_panning(x, y, z, dist, time)
when B_format_ambisonics
b_format_ambisonics(x, y, z, dist, time)
when Decoded_ambisonics
decoded_ambisonics(x, y, z, dist, time)
end
end
def change_direction(xa, ya, za, ta, xb, yb, zb, tb)
walk_all_rooms(xa, ya, za, ta)
if xa != xb or ya != yb or za != zb or ta != tb
xi, yi, zi = nearest_point(xa, ya, za, xb, yb, zb, 0.0, 0.0, 0.0)
if (((xa < xb) ? (xa <= xi and xi <= xb) : (xb <= xi and xi <= xa)) and
((ya < yb) ? (ya <= yi and yi <= yb) : (yb <= yi and yi <= ya)) and
((za < zb) ? (za <= zi and zi <= zb) : (zb <= zi and zi <= za)))
walk_all_rooms(xi, yi, zi,
tb + (ta - tb) * (distance(xb - xi, yb - yi, zb - zi) / \
distance(xb - xa, yb - ya, zb - za)))
end
end
end
def intersects_inside_radius(xa, ya, za, ta, xb, yb, zb, tb)
mag = distance(xb - xa, yb - ya, zb - za)
vx = (xb - xa) / mag
vy = (yb - ya) / mag
vz = (zb - za) / mag
bsq = xa * vx + ya * vy + za * vz
disc = bsq * bsq - ((xa * xa + ya * ya + za * za) - 1.0)
if disc >= 0.0
root = sqrt(disc)
rin = -bsq - root
rout = -bsq + root
xi = xo = nil
if rin > 0 and rin < mag
xi = xa + vx * rin
yi = ya + vy * rin
zi = za + vz * rin
ti = tb + (ta - tb) *
(distance(xb - xi, yb - yi, zb - zi) / distance(xb - xa, yb - ya, zb - za))
end
if rout > 0 and rout.abs < mag
xo = xa + vx * rout
yo = ya + vy * rout
zo = za + vz * rout
to = tb + (ta - tb) *
(distance(xb - xo, yb - yo, zb - zo) / distance(xb - xa, yb - ya, zb - za))
end
if xi
change_direction(xa, ya, za, ta, xi, yi, zi, ti)
if xo
change_direction(xi, yi, zi, ti, xo, yo, zo, to)
change_direction(xo, yo, zo, to, xb, yb, zb, tb)
else
change_direction(xi, yi, zi, ti, xb, yb, zb, tb)
end
else
if xo
change_direction(xa, ya, za, ta, xo, yo, zo, to)
change_direction(xo, yo, zo, to, xb, yb, zb, tb)
else
change_direction(xa, ya, za, ta, xb, yb, zb, tb)
end
end
else
change_direction(xa, ya, za, ta, xb, yb, zb, tb)
end
end
def minimum_segment_length(xa, ya, za, ta, xb, yb, zb, tb)
if distance(xb - xa, yb - ya, zb - za) < 1.0
intersects_inside_radius(xa, ya, za, ta, xb, yb, zb, tb)
else
xi = (xa + xb) * 0.5
yi = (ya + yb) * 0.5
zi = (za + zb) * 0.5
ti = tb + (ta - tb) *
(distance(xb - xi, yb - yi, zb - zi) / distance(xb - xa, yb - ya, zb - za))
minimum_segment_length(xa, ya, za, ta, xi, yi, zi, ti)
minimum_segment_length(xi, yi, zi, ti, xb, yb, zb, tb)
end
end
end
class Path < Dlocsig_base
def initialize
super
@rx = @ry = @rz = @rv = @rt = @tx = @ty = @tz = @tt = nil
@gnuplot = @sndplot = nil
end
def path_x
(@tx or (@rx or (render_path(); @rx)))
end
def path_y
(@ty or (@ry or (render_path(); @ry)))
end
def path_z
(@tz or (@rz or (render_path(); @rz)))
end
def path_time
(@tt or (@rt or (render_path(); @rt)))
end
def scale_path(scaling)
assert_type((number?(scaling) or array?(scaling)), 0, scaling, "a number or an array")
if number?(scaling) then scaling = [scaling, scaling, scaling] end
transform_path(:scaling, scaling)
end
def translate_path(translation)
assert_type((number?(translation) or array?(translation)),
0, translation, "a number or an array")
if number?(translation) then translation = [translation, translation, translation] end
transform_path(:translation, translation)
end
def rotate_path(rotation, *args)
assert_type(number?(rotation), 0, rotation, "a number")
rotation_center, rotation_axis = nil
optkey(args, binding,
:rotation_center,
[:rotation_axis, [0.0, 0.0, 1.0]])
transform_path(:rotation, rotation,
:rotation_center, rotation_center,
:rotation_axis, rotation_axis)
end
def path_trajectory
path_x.map_with_index do |d, i| [d, path_y[i], path_z[i]] end.flatten
end
def path_2d_trajectory
path_x.map_with_index do |d, i| [d, path_y[i]] end.flatten
end
# if velocity is zero, ti == tf
Secure_distance = 0.0001
def path_velocity
xp, yp, zp, tp = path_x, path_y, path_z, path_time
(0...(tp.length - 1)).map do |i|
xi, xf = xp[i, 2]
yi, yf = yp[i, 2]
zi, zf = zp[i, 2]
ti, tf = tp[i, 2]
if tf == ti
tf += Secure_distance
end
[(ti + tf) / 2.0, distance(xf - xi, yf - yi, zf - zi) / (tf - ti)]
end.flatten
end
def path_doppler
xp, yp, zp, tp = path_x, path_y, path_z, path_time
(0...(tp.length - 1)).map do |i|
xi, xf = xp[i, 2]
yi, yf = yp[i, 2]
zi, zf = zp[i, 2]
ti, tf = tp[i, 2]
if tf == ti
tf += Secure_distance
end
[(tf + ti) / 2.0, -((distance(xf, yf, zf) - distance(xi, yi, zi)) / (tf - ti))]
end.flatten
end
def path_acceleration
v = path_velocity()
result = []
0.step(v.length - 3, 2) do |i|
ti, vi, tf, vf = v[i, 4]
if tf == ti
tf += Secure_distance
end
am = (vf - vi) / (tf - ti)
result << ti << am << tf << am
end
result
end
# Gnuplot
def plot_open
if @gnuplot.kind_of?(Gnuplot)
@gnuplot
else
@gnuplot = Gnuplot.new
end
end
def plot_close
@gnuplot.close if @gnuplot.kind_of?(Gnuplot)
end
def cmd(*args)
@gnuplot = plot_open
@gnuplot.command(format(*args) << "\n")
end
def plot_trajectory(*args)
@gnuplot = plot_open
label, reset = nil
optkey(args, binding,
[:label, "trajectory"],
[:reset, true])
@gnuplot.reset() if reset
@gnuplot.set_autoscale()
if path_z.detect do |z| z.nonzero? end
@gnuplot.plot_3d_curve(path_trajectory(), :label, label, *args)
else
@gnuplot.plot_2d_curve(path_2d_trajectory(), :label, label, *args)
end
end
def plot_velocity(reset = true)
@gnuplot = plot_open
@gnuplot.reset() if reset
@gnuplot.set_autoscale()
@gnuplot.plot_2d_curve(path_velocity(), :label, "velocity", :style, "steps")
end
def plot_doppler(reset = true)
@gnuplot = plot_open
@gnuplot.reset() if reset
@gnuplot.set_autoscale()
@gnuplot.plot_2d_curve(path_doppler(), :label, "doppler", :style, "steps")
end
def plot_acceleration(reset = true)
@gnuplot = plot_open
@gnuplot.reset() if reset
@gnuplot.set_autoscale()
@gnuplot.plot_2d_curve(path_acceleration(), :label, "acceleration", :style, "steps")
end
def pplot(normalize = true)
@gnuplot = plot_open
norm = lambda do |env, nrm|
unless nrm
env
else
mx = env.each_pair do |x, y| y end.max
if mx.zero?
env
else
env.each_pair do |x, y| [x, y / mx.to_f] end.flatten
end
end
end
@gnuplot.reset()
@gnuplot.size(0, 0, 1, 1)
@gnuplot.start_multiplot()
@gnuplot.size(0.0, 0.333, 1.0, 0.667)
plot_trajectory(:reset, false)
@gnuplot.size(0.0, 0.0, 1.0, 0.333)
@gnuplot.plot_2d_curves([norm.call(path_velocity(), normalize),
norm.call(path_acceleration(), normalize),
norm.call(path_doppler(), normalize)],
:labels, ["velocity", "acceleration", "doppler"],
:styles, ["steps", "steps", "steps"])
@gnuplot.end_multiplot()
end
# Sndplot
def snd_open(chns = 1)
if @sndplot.kind_of?(Sndplot)
@sndplot
else
@sndplot = Sndplot.new(chns)
end
end
def snd_close
@sndplot.close if @sndplot.kind_of?(Sndplot)
end
def snd_trajectory(chn = 0, label = "trajectory")
@sndplot = snd_open
graph(path_2d_trajectory(), label, false, false, false, false, @sndplot.snd, chn)
@sndplot
end
def snd_velocity(chn = 0, label = "velocity")
@sndplot = snd_open
graph(path_velocity(), label, false, false, false, false, @sndplot.snd, chn)
@sndplot
end
def snd_doppler(chn = 0, label = "doppler")
@sndplot = snd_open
graph(path_doppler(), label, false, false, false, false, @sndplot.snd, chn)
@sndplot
end
def snd_acceleration(chn = 0, label = "acceleration")
@sndplot = snd_open
graph(path_acceleration(), label, false, false, false, false, @sndplot.snd, chn)
@sndplot
end
def snd_plot
@sndplot = snd_open(4)
snd_trajectory(0)
snd_velocity(1)
snd_acceleration(2)
snd_doppler(3)
@sndplot
end
private
def transform_path(*args)
scaling, translation, rotation, rotation_center, rotation_axis = nil
optkey(args, binding,
:scaling,
:translation,
:rotation,
:rotation_center,
[:rotation_axis, [0.0, 0.0, 1.0]])
render_path() if @rx.nil?
if scaling or translation or rotation
rotation = TWO_PI * (rotation / @one_turn) if rotation
if rotation_axis and (rotation_axis.length != 3)
dl_error(rotation_axis, "rotation axis has to have all three coordinates")
end
matrix = if rotation
rotation_matrix(rotation_axis[0], rotation_axis[1], rotation_axis[2], rotation)
end
xc = path_x()
yc = path_y()
zc = path_z()
if rotation_center and (rotation_center.length != 3)
dl_error(rotation_center, "rotation center has to have all three coordinates")
end
xtr = []
ytr = []
ztr = []
xc.each_with_index do |x, i|
y = yc[i]
z = zc[i]
xw, yw, zw = x, y, z
if rotation_center and rotation
xw -= rotation_center[0]
yw -= rotation_center[1]
zw -= rotation_center[2]
end
if rotation
mc = [xw, yw, zw]
xv, yv, zv = matrix.column_vectors
xr = xv.to_a.map_with_index do |xx, ii| xx * mc[ii] end.to_a.sum
yr = yv.to_a.map_with_index do |xx, ii| xx * mc[ii] end.to_a.sum
zr = zv.to_a.map_with_index do |xx, ii| xx * mc[ii] end.to_a.sum
xw, yw, zw = xr, yr, zr
end
if rotation_center and rotation
xw += rotation_center[0]
yw += rotation_center[1]
zw += rotation_center[2]
end
if scaling
xw *= scaling[0]
yw *= scaling[1] if scaling[1]
zw *= scaling[2] if scaling[2]
end
if translation
xw += translation[0]
yw += translation[1] if translation[1]
zw += translation[2] if translation[2]
end
xtr << xw
ytr << yw
ztr << zw
end
@tx, @ty, @tz = xtr, ytr, ztr
else
@tt = @rt.dup
@tx = @rx.dup
@ty = @ry.dup
@tz = @rz.dup
end
end
def reset_transformation
@tt = @tx = @ty = @tz = nil
end
def reset_rendering
@rt = @rv = @rx = @ry = @rz = nil
reset_transformation()
end
def parse_cartesian_coordinates(points, d3)
if array?(points[0])
x = points.map do |p| p[0] end
y = points.map do |p| p[1] end
z = points.map do |p| d3 ? (p[2] or 0.0) : 0.0 end
v = points.map do |p| d3 ? p[3] : p[2] end
[x, y, z, v]
else
if d3
x = []
y = []
z = []
0.step(points.length - 3, 3) do |i|
x += [points[i]]
y += [points[i + 1]]
z += [points[i + 2]]
end
[x, y, z, x.map do |i| nil end]
else
x = []
y = []
0.step(points.length - 2, 2) do |i|
x += [points[i]]
y += [points[i + 1]]
end
[x, y, x.map do |i| 0.0 end, x.map do |i| nil end]
end
end
end
def parse_polar_coordinates(points, d3)
if array?(points[0])
x = []
y = []
z = []
v = []
points.each do |p|
d = p[0]
a = p[1]
e = (d3 ? (p[2] or 0.0) : 0.0)
evec = cis((e / @one_turn) * TWO_PI)
dxy = d * evec.real
avec = cis((a / @one_turn) * TWO_PI)
z << (d * evec.image)
x << (dxy * avec.image)
y << (dxy * avec.real)
v << (d3 ? p[3] : p[2])
end
[x, y, z, v]
else
if d3
x = []
y = []
z = []
0.step(points.length - 1, 3) do |i|
d, a, e = points[i, 3]
evec = cis((e / @one_turn) * TWO_PI)
dxy = (d * evec.real)
avec = cis((a / @one_turn) * TWO_PI)
z << (d * evec.image)
x << (dxy * avec.image)
y << (dxy * avec.real)
end
[x, y, z, x.map do |i| nil end]
else
x = []
y = []
0.step(points.length - 1, 2) do |i|
d, a = points[i, 2]
avec = cis((a / @one_turn) * TWO_PI)
x << (d * avec.image)
y << (d * avec.real)
end
[x, y, x.map do |i| 0.0 end, x.map do |i| nil end]
end
end
end
end
class Bezier_path < Path
def initialize(path, *args)
@path = path
if (not @path) or (array?(@path) and @path.empty?)
dl_error("can't define a path with no points in it")
end
super()
d3, polar, error, curvature = nil
optkey(args, binding,
[:d3, true],
[:polar, false],
[:error, 0.01],
:curvature)
@d3 = d3
@polar = polar
@error = error
@curvature = curvature
@x = @y = @z = @v = @bx = @by = @bz = nil
# for ac() and a()
@path_ak_even = nil
@path_ak_odd = nil
@path_gtab = nil
@path_ftab = nil
end
private
def parse_path
if @polar
@x, @y, @z, @v = parse_polar_coordinates(@path, @d3)
else
@x, @y, @z, @v = parse_cartesian_coordinates(@path, @d3)
end
if @v[0] and @v.min < 0.1
if @v.min < 0.0
Snd.warning("%s#%s: velocities must be all positive, corrected",
self.class, get_func_name)
end
@v.map! do |x| [0.1, x].max end
end
@bx = @by = @bz = nil
reset_rendering()
end
def fit_path
parse_path() if @x.nil?
end
def bezier_point(u, c)
u1 = 1.0 - u
cr = make_array(3) do |i| make_array(3) do |j| u1 * c[i][j] + u * c[i][j + 1] end end
1.downto(0) do |i|
0.upto(i) do |j|
3.times do |k| cr[k][j] = u1 * cr[k][j] + u * cr[k][j + 1] end
end
end
[cr[0][0], cr[1][0], cr[2][0]]
end
def berny(xl, yl, zl, xh, yh, zh, ul, u, uh, c)
x, y, z = bezier_point(u, c)
xn, yn, zn = nearest_point(xl, yl, zl, xh, yh, zh, x, y, z)
if distance(xn - x, yn - y, zn - z) > @error
xi, yi, zi = berny(xl, yl, zl, x, y, z, ul, (ul + u) / 2.0, u, c)
xj, yj, zj = berny(x, y, z, xh, yh, zh, u, (u + uh) / 2.0, uh, c)
[xi + [x] + xj, yi + [y] + yj, zi + [z] + zj]
else
[[], [], []]
end
end
def render_path
fit_path() if @bx.nil?
rx = []
ry = []
rz = []
rv = []
if (not @v[0]) or @v[0].zero?
@v[0] = 1.0
@v[-1] = 1.0
end
if @x.length == 1
@rx = @x
@ry = @y
@rz = @z
@rt = [0.0]
return
end
xf_bz = yf_bz = zf_bz = vf_bz = 0.0
(@v.length - 1).times do |i|
x_bz = @bx[i]
y_bz = @by[i]
z_bz = @bz[i]
vi_bz, vf_bz = @v[i, 2]
xi_bz = x_bz[0]
xf_bz = x_bz[-1]
yi_bz = y_bz[0]
yf_bz = y_bz[-1]
zi_bz = z_bz[0]
zf_bz = z_bz[-1]
xs, ys, zs = berny(xi_bz, yi_bz, zi_bz, xf_bz, yf_bz, zf_bz, 0, 0.5, 1, [x_bz, y_bz, z_bz])
rx += [xi_bz] + xs
ry += [yi_bz] + ys
rz += [zi_bz] + zs
rv += [vi_bz] + xs.map do nil end
end
rx << xf_bz
ry << yf_bz
rz << zf_bz
rv << vf_bz
xseg = [rx[0]]
yseg = [ry[0]]
zseg = [rz[0]]
vi = rv[0]
ti = 0.0
times = [ti]
(1...rx.length).each do |i|
x = rx[i]
y = ry[i]
z = rz[i]
v = rv[i]
xseg << x
yseg << y
zseg << z
if v
sofar = 0.0
dseg = (0...xseg.length - 1).map do |j|
xsi, xsf = xseg[j, 2]
ysi, ysf = yseg[j, 2]
zsi, zsf = zseg[j, 2]
sofar += distance(xsf - xsi, ysf - ysi, zsf - zsi)
end
df = dseg[-1]
vf = v
aa = ((vf - vi) * (vf + vi)) / (df * 4.0)
tseg = dseg.map do |d|
ti + (if vi and vi.nonzero? and vf == vi
d / vi
elsif aa.nonzero?
((vi * vi + 4.0 * aa * d) ** 0.5 - vi) / (2.0 * aa)
else
0.0
end)
end
times += tseg
xseg = [x]
yseg = [y]
zseg = [z]
vi = v
ti = tseg[-1]
end
end
@rx = rx
@ry = ry
@rz = rz
tf = times[-1]
@rt = times.map do |ti| ti / tf end
reset_transformation()
end
# called in Closed_bezier_path#calculate_fit
def a(k, n)
if ([Path_maxcoeff * 2.0 + 1, n].min).odd?
make_a_odd() unless @path_ak_odd
@path_ak_odd[(n - 3) / 2][k - 1]
else
make_a_even() unless @path_ak_even
@path_ak_even[(n - 4) / 2][k - 1]
end
end
# called in Open_bezier_path#calculate_fit
def ac(k, n)
n = [n, Path_maxcoeff].min
make_a_even() unless @path_ak_even
@path_ak_even[n - 2][k - 1]
end
def make_a_even
g = lambda do |m|
@path_gtab = make_array(Path_maxcoeff) unless @path_gtab
@path_gtab[0] = 1.0
@path_gtab[1] = -4.0
(2...Path_maxcoeff).each do |i|
@path_gtab[i] = -4.0 * @path_gtab[i - 1] - @path_gtab[i - 2]
end
@path_gtab[m]
end
@path_ak_even = make_array(Path_maxcoeff - 1)
(1...Path_maxcoeff).each do |m|
@path_ak_even[m - 1] = make_array(m)
(1..m).each do |k|
@path_ak_even[m - 1][k - 1] = (-g.call(m - k) / g.call(m)).to_f
end
end
end
def make_a_odd
f = lambda do |m|
@path_ftab = make_array(Path_maxcoeff) unless @path_ftab
@path_ftab[0] = 1.0
@path_ftab[1] = -3.0
(2...Path_maxcoeff).each do |i|
@path_ftab[i] = -4.0 * @path_ftab[i - 1] - @path_ftab[i - 2]
end
@path_ftab[m]
end
@path_ak_odd = make_array(Path_maxcoeff - 1)
(1...Path_maxcoeff).each do |m|
@path_ak_odd[m - 1] = make_array(m)
(1..m).each do |k|
@path_ak_odd[m - 1][k - 1] = (-f.call(m - k) / f.call(m)).to_f
end
end
end
end
class Open_bezier_path < Bezier_path
def initialize(path, *args)
super
initial_direction, final_direction = nil
optkey(args, binding,
[:initial_direction, [0.0, 0.0, 0.0]],
[:final_direction, [0.0, 0.0, 0.0]])
@initial_direction = initial_direction
@final_direction = final_direction
end
private
def calculate_fit
n = @x.length - 1
m = n - 1
p = [@x, @y, @z]
d = make_array(3) do make_array(n + 1, 0.0) end
d = Matrix[d[0], d[1], d[2]].to_a
ref = lambda do |z, j, i|
if i > n
z[j][i - n]
elsif i < 0
z[j][i + n]
elsif i == n
z[j][n] - d[j][n]
elsif i == 0
z[j][0] + d[j][0]
else
z[j][i]
end
end
d[0][0] = (@initial_direction[0] or 0.0)
d[1][0] = (@initial_direction[1] or 0.0)
d[2][0] = (@initial_direction[2] or 0.0)
d[0][n] = (@final_direction[0] or 0.0)
d[1][n] = (@final_direction[1] or 0.0)
d[2][n] = (@final_direction[2] or 0.0)
(1...n).each do |i|
(1..[Path_maxcoeff - 1, m].min).each do |j|
3.times do |k|
d[k][i] += ac(j, n) * (ref.call(p, k, i + j) - ref.call(p, k, i - j))
end
end
end
[n, p, d]
end
def fit_path
parse_path() if @x.nil?
case points = @x.length
when 1
@bx = @by = @bz = nil
when 2
x1, x2 = @x[0, 2]
y1, y2 = @y[0, 2]
z1, z2 = @z[0, 2]
@bx = [[x1, x1, x2, x2]]
@by = [[y1, y1, y2, y2]]
@bz = [[z1, z1, z2, z2]]
else
n, p, d = calculate_fit()
c = @curvature
cs = make_array(n)
if c.kind_of?(NilClass) or (array?(c) and c.empty?)
n.times do |i| cs[i] = [1.0, 1.0] end
elsif number?(c)
n.times do |i| cs[i] = [c, c] end
elsif array?(c) and c.length == n
c.each_with_index do |ci, i|
cs[i] = if array?(ci)
if ci.length != 2
dl_error(ci, "curvature sublist must have two elements")
else
ci
end
else
[ci, ci]
end
end
else
dl_error(c, "bad curvature argument to path, need #{n} elements")
end
@bx = (0...n).map do |i|
[p[0][i], p[0][i] + d[0][i] * cs[i][0], p[0][i + 1] - d[0][i + 1] * cs[i][1], p[0][i + 1]]
end
@by = (0...n).map do |i|
[p[1][i], p[1][i] + d[1][i] * cs[i][0], p[1][i + 1] - d[1][i + 1] * cs[i][1], p[1][i + 1]]
end
@bz = (0...n).map do |i|
[p[2][i], p[2][i] + d[2][i] * cs[i][0], p[2][i + 1] - d[2][i + 1] * cs[i][1], p[2][i + 1]]
end
end
reset_rendering()
end
end
class Closed_bezier_path < Bezier_path
def initialize(path, *args)
super
end
private
def calculate_fit
n = @x.length - 1
m = (n - (n.odd? ? 3 : 4)) / 2
p = [@x, @y, @z]
d = make_array(3) do make_array(n, 0.0) end
ref = lambda do |z, j, i|
if i > (n - 1)
z[j][i - n]
elsif i < 0
z[j][i + n]
else
z[j][i]
end
end
n.times do |i|
(1..m).each do |j|
3.times do |k|
d[k][i] += a(j, n) * (ref.call(p, k, i + j) - ref.call(p, k, i - j))
end
end
end
if @curvature
n.times do |i|
curve = @curvature[i]
d[0][i] *= curve
d[1][i] *= curve
d[2][i] *= curve
end
end
[n - 1, p, d]
end
def fit_path
parse_path() if @x.nil?
if @x.length > 4
n, p, d = calculate_fit()
xc = (0...n).map do |i|
[p[0][i], p[0][i] + d[0][i], p[0][i + 1] - d[0][i + 1], p[0][i + 1]]
end
yc = (0...n).map do |i|
[p[1][i], p[1][i] + d[1][i], p[1][i + 1] - d[1][i + 1], p[1][i + 1]]
end
zc = (0...n).map do |i|
[p[2][i], p[2][i] + d[2][i], p[2][i + 1] - d[2][i + 1], p[2][i + 1]]
end
@bx = xc + [[p[0][n], p[0][n] + d[0][n], p[0][0] - d[0][0], p[0][0]]]
@by = yc + [[p[1][n], p[1][n] + d[1][n], p[1][0] - d[1][0], p[1][0]]]
@bz = zc + [[p[2][n], p[2][n] + d[2][n], p[2][0] - d[2][0], p[2][0]]]
else
xc = []
yc = []
zc = []
(@x.length - 1).times do |i|
x1, x2 = @x[i, 2]
y1, y2 = @y[i, 2]
z1, z2 = @z[i, 2]
xc << [x1, x1, x2, x2]
yc << [y1, y1, y2, y2]
zc << [z1, z1, z2, z2]
end
@bx = xc
@by = yc
@bz = zc
end
reset_rendering()
end
end
class Literal_path < Path
def initialize(path, *args)
@path = path
if (not @path) or (array?(@path) and @path.empty?)
dl_error("can't define a path with no points in it")
end
super()
d3, polar = nil
optkey(args, binding,
[:d3, true],
[:polar, false])
@d3 = d3
@polar = polar
end
private
def render_path
if @polar
@rx, @ry, @rz, @rv = parse_polar_coordinates(@path, @d3)
else
@rx, @ry, @rz, @rv = parse_cartesian_coordinates(@path, @d3)
end
if (not @rv[0]) or @rv[0].zero?
@rv[0] = 1.0
@rv[-1] = 1.0
end
if @rx.length == 1
@rt = [0.0]
return
end
rx = @rx
ry = @ry
rz = @rz
rv = @rv
xseg = [rx[0]]
yseg = [ry[0]]
zseg = [rz[0]]
vi = rv[0]
ti = 0.0
times = [ti]
(1...rx.length).each do |i|
x = rx[i]
y = ry[i]
z = rz[i]
v = rv[i]
xseg << x
yseg << y
zseg << z
if v
sofar = 0.0
dseg = (0...xseg.length - 1).map do |j|
xsi, xsf = xseg[j, 2]
ysi, ysf = yseg[j, 2]
zsi, zsf = zseg[j, 2]
sofar += distance(xsf - xsi, ysf - ysi, zsf - zsi)
end
df = dseg[-1]
vf = v
aa = ((vf - vi) * (vf + vi)) / (df * 4.0)
tseg = dseg.map do |d|
ti + (if vi and vi.nonzero? and vf == vi
d / vi
elsif aa.nonzero?
((vi * vi + 4.0 * aa * d) ** 0.5 - vi) / (2.0 * aa)
else
0.0
end)
end
times += tseg
xseg = [x]
yseg = [y]
zseg = [z]
vi = v
ti = tseg[-1]
end
end
tf = times[-1]
@rt = times.map do |ti| ti / tf end
reset_transformation()
end
end
class Spiral_path < Literal_path
def initialize(*args)
# to fool Literal_path.new
super([nil])
start_angle, turns = nil
optkey(args, binding,
[:start_angle, 0],
[:turns, 2])
@start_angle = start_angle
@turns = turns
end
private
def render_path
start = (@start_angle / @one_turn.to_f) * TWO_PI
total = (@turns.zero? ? TWO_PI : (@turns * TWO_PI))
step_angle = @one_turn / 100.0
steps = (total / ((step_angle / @one_turn.to_f) * TWO_PI)).abs
step = total / (steps.ceil * (step_angle < 0 ? -1 : 1))
x = []
y = []
z = []
(total / step).round.abs.times do
xy = cis(start)
x << (10.0 * xy.image)
y << (10.0 * xy.real)
z << 0.0
start += step
end
sofar = 0.0
dp = (0...x.length - 1).map do |i|
xi, xf = x[i, 2]
yi, yf = y[i, 2]
zi, zf = z[i, 2]
sofar += distance(xf - xi, yf - yi, zf - zi)
end
td = 0.0
times = (0...dp.length - 1).map do |i|
di, df = dp[i, 2]
td += (df - di) / 4.0
end
@rx = x
@ry = y
@rz = z
tf = times[-1]
@rt = times.map do |ti| ti / tf end
reset_transformation()
end
end
module_function
def make_dlocsig(start, dur, *args)
dl = Dlocsig.new
dl.make_dlocsig(start, dur, *args)
dl
end
def dlocsig(dl, dloc, input)
dl.dlocsig(dloc, input)
end
def make_path(path, *args)
Open_bezier_path.new(path, *args)
end
def make_polar_path(path, *args)
Open_bezier_path.new(path, :polar, true, *args)
end
def make_closed_path(path, *args)
d3 = nil
optkey(args, binding, [:d3, true])
len = d3 ? 3 : 2
unless path[0][0, len] == path[-1][0, len]
path += [path[0]]
end
Closed_bezier_path.new(path, *args)
end
def make_literal_path(path, *args)
Literal_path.new(path, *args)
end
def make_literal_polar_path(path, *args)
Literal_path.new(path, :polar, true, *args)
end
def make_spiral_path(*args)
Spiral_path.new(*args)
end
end
# example functions (see clm-2/dlocsig/move-sound.ins and
# clm-2/dlocsig/dlocsig.html)
class Instrument
def sinewave(start, dur, freq, amp, path, amp_env = [0, 1, 1, 1], *dlocsig_args)
os = make_oscil(:frequency, freq)
en = make_env(:envelope, amp_env, :scaler, amp, :duration, dur)
run_dlocsig(start, dur, :path, path, *dlocsig_args) do
env(en) * oscil(os)
end
end
def move(start, file, path, *dlocsig_args)
dl_error(path, "need a path") unless path.kind_of?(DL::Path)
dur = ws_duration(file)
chns = ws_channels(file)
rds = make_array(chns) do |chn| make_ws_reader(file, :start, start, :channel, chn) end
run_dlocsig(start, dur, :path, path, *dlocsig_args) do
rds.map do |rd| ws_readin(rd) end.sum / chns
end
end
add_help(:move_sound,
"move_sound(path, *args) do ... end
sound_let-args:
:channels, 1 # channels to move
start time in output file:
:startime, 0
rest args: make_dlocsig")
def move_sound(path, *args, &body)
chns = get_shift_args(args, :channels, 1)
start = get_shift_args(args, :startime, 0)
sound_let([:channels, chns, body]) do |to_move|
if @verbose
Snd.display("%s: moving sound on %d channel%s", get_func_name, chns, (chns > 1 ? "s" : ""))
end
move(0, to_move, path, *args)
rbm_mix(to_move, :output_frame, seconds2samples(start))
end
end
end
class With_sound
def run_dlocsig(start, dur, *dlocsig_args, &body)
with_sound_info(get_func_name(2), start, dur)
dl = DL.make_dlocsig(start, dur,
:clm, @clm,
:rbm_output, @ws_output,
:rbm_reverb, @ws_reverb,
:out_channels, @channels,
:rev_channels, @reverb_channels,
*dlocsig_args)
ws_interrupt?
dl.ws_dlocsig(&body)
end
end
# Dlocsig menu
#
# require 'snd-xm'
#
# make_snd_menu("Dlocsig") do
# cascade("Dlocsig (Snd)") do
# entry(Dlocsig_bezier, "Bezier path (Snd)", true)
# entry(Dlocsig_spiral, "Spiral path (Snd)", true)
# end
# cascade("Dlocsig (CLM)") do
# entry(Dlocsig_bezier, "Bezier path (CLM)", false)
# entry(Dlocsig_spiral, "Spiral path (CLM)", false)
# end
# end
if provided? :snd_motif or provided? :snd_gtk
class Dlocsig_menu
require "snd-xm"
include Snd_XM
require "xm-enved"
include DL
def initialize(label, snd_p)
@label = label
@snd_p = snd_p
@dialog = nil
@out_chans = 4
@rev_chans = 1
@path = nil
@sliders = []
@init_out_chans = 4
@output_power = @init_power = 1.5
@reverb_power = @init_rev_power = 0.5
@render_using = Amplitude_panning
end
private
def with_sound_target(*comment_args)
if @render_using == B_format_ambisonics
set_scale_value(@sliders[0].scale, @out_chans = 4)
end
comment_string = if string?($clm_comment) and !$clm_comment.empty?
format("%s; %s", $clm_comment, format(*comment_args))
else
format(*comment_args)
end
snd_path, snd_name = File.split(file_name(selected_sound))
snd_name = snd_name.split("moved-").last
snd_to_move = format("%s/%s", snd_path, snd_name)
snd_moved = format("%s/moved-%s", snd_path, snd_name)
path = @path
output_power = @output_power
reverb_power = @reverb_power
render_using = @render_using
f = with_sound(:clm, (not @snd_p),
:output, snd_moved,
:channels, @out_chans,
:reverb_channels, @rev_chans,
:comment, comment_string,
:info, (not $clm_notehook),
:statistics, true,
:play, true) do
move(0,
snd_to_move,
path,
:output_power, output_power,
:reverb_power, reverb_power,
:render_using, render_using)
end
Snd.display(f.output.inspect)
rescue
Snd.warning("%s#%s: %s", self.class, get_func_name, comment_string)
end
def add_with_sound_sliders(parent = @dialog.parent)
@sliders << @dialog.add_slider("output channels",
2, @init_out_chans, 8, 1, :linear, parent) do |w, c, i|
@out_chans = get_scale_value(w, i).round
end
@sliders << @dialog.add_slider("output power",
0, @init_power, 10, 100, :linear, parent) do |w, c, i|
@output_power = get_scale_value(w, i, 100.0)
end
@sliders << @dialog.add_slider("reverb power",
0, @init_rev_power, 10, 100, :linear, parent) do |w, c, i|
@reverb_power = get_scale_value(w, i, 100.0)
end
end
def reset_with_sound_sliders(reverb_p = true)
set_scale_value(@sliders[0].scale, @out_chans = @init_out_chans)
@output_power = @init_power
set_scale_value(@sliders[1].scale, @output_power, 100.0)
@reverb_power = @init_rev_power
set_scale_value(@sliders[2].scale, @reverb_power, 100.0)
end
def add_with_sound_targets
@dialog.add_target([["amplitude panning", :amplitude, true],
["b format ambisonics", :b_format, false],
["decoded ambisonics", :decoded, false]]) do |val|
@render_using = case val
when :amplitude
Amplitude_panning
when :b_format
set_scale_value(@sliders[0].scale, @out_chans = 4)
B_format_ambisonics
when :decoded
Decoded_ambisonics
end
end
@dialog.add_target([["no reverb", :no_reverb, false],
["1 rev chan", :one_rev_chan, true],
["4 rev chans", :four_rev_chans, false]]) do |val|
case val
when :no_reverb
@rev_chans = 0
when :one_rev_chan
@rev_chans = 1
when :four_rev_chans
@rev_chans = 4
end
end
end
def set_xm_enveds_hooks(*enveds)
enveds.each do |e|
e.before_enved_hook.reset_hook! # to prevent running $enved_hook
e.before_enved_hook.add_hook!("dlocsig-hook") do |pos, x, y, reason|
if reason == Enved_move_point
if e.in_range?(x)
old_x = e.point(pos).first
e.stretch!(old_x, x)
e.point(pos, :y, y)
else
false
end
else
false
end
end
e.after_enved_hook.add_hook!("dlocsig-hook") do |pos, reason| show_values end
end
end
# comment string
def dlocsig_strings
dlstr = ["", :amplitude_panning, :b_format_ambisonics, :decoded_ambisonics]
format("%s, output_power: %1.2f, reverb_power: %1.2f",
dlstr[@render_using],
@output_power,
@reverb_power)
end
def help_cb
help_dialog(@label,
"\
The current sound will be moved through the chosen path. You can set \
the reverberator via the global with-sound-variable $clm_reverb \
(#{$clm_reverb.inspect}). If you want four reverb channels, you \
may try freeverb from freeverb.rb.
reverb reverb-channels output-channels source
jc_reverb 1 4 examp.rb
jl_reverb 1 2 clm-ins.rb
nrev 1 4 clm-ins.rb
freeverb 4 > 4 freeverb.rb
Amplitude-panning: generates amplitude panning between adjacent speakers.
B-format-ambisonics: generates a four channel first order b-format encoded soundfile.
Decoded-ambisonics: the ambisonics encoded information is decoded to the number of selected speakers.
Note: reverb on spiral path generates noise if turns is less than 2.6
For detailed information see clm-2/dlocsig.html.",
["{Libxm}: graphics module",
"{Ruby}: extension language",
"{Motif}: Motif extensions via Libxm",
"{dlocsig}: Fernando Lopez Lezcano's multichannel locator"])
end
end
class Dlocsig_bezier < Dlocsig_menu
require "xm-enved"
def initialize(label, snd_p = false)
super
@target = :with_sound
@which_path = :open_bezier_path
@snd_path = [[-10.0, 10.0, 0.0, 1.0], [0.0, 5.0, 1.0, 1.0], [10.0, 10.0, 0.0, 1.0]]
@trajectory = nil
@z_value = nil
@velocity = nil
@label_list = []
end
def inspect
str = @snd_path.inspect
new_str = ""
[str.length, 20].min.times do |i| new_str << str[i] end
new_str << "..." if str.length > 20
format("%s (%s)", @label, new_str)
end
def post_dialog
unless @dialog.kind_of?(Dialog) and widget?(@dialog.dialog)
init_traj = [0, 1, 0.5, 0.5, 1, 1]
init_z_traj = [0, 0, 0.5, 0.1, 1, 0]
init_vel = [0, 0.5, 1, 0.5]
@dialog = make_dialog(@label,
:help_cb, lambda do |w, c, i|
help_cb()
end, :clear_cb, lambda do |w, c, i|
create_path
@path.pplot
end, :reset_cb, lambda do |w, c, i|
reset_with_sound_sliders
@trajectory.envelope = init_traj
@z_value.envelope = init_z_traj
@velocity.envelope = init_vel
show_values
end) do |w, c, i|
create_path
with_sound_target("%s: %s, path: %s", @which_path, dlocsig_strings, @snd_path.inspect)
end
if provided? :xm
frame_args = [RXmNshadowThickness, 4,
RXmNshadowType, RXmSHADOW_ETCHED_OUT,
RXmNbackground, basic_color,
RXmNheight, 170,
RXmNwidth, 400]
pane = RXtCreateManagedWidget("pane", RxmPanedWindowWidgetClass, @dialog.parent,
[RXmNsashHeight, 1, RXmNsashWidth, 1,
RXmNorientation, RXmHORIZONTAL,
RXmNbackground, basic_color])
xepane = RXtCreateManagedWidget("xepane", RxmPanedWindowWidgetClass, pane,
[RXmNsashHeight, 1, RXmNsashWidth, 1,
RXmNorientation, RXmVERTICAL,
RXmNbackground, basic_color])
trfr = RXtCreateManagedWidget("trfr", RxmFrameWidgetClass, xepane, frame_args)
zfr = RXtCreateManagedWidget("zfr", RxmFrameWidgetClass, xepane, frame_args)
vefr = RXtCreateManagedWidget("vefr", RxmFrameWidgetClass, xepane, frame_args)
vepane = RXtCreateManagedWidget("vpane", RxmPanedWindowWidgetClass, pane,
[RXmNsashHeight, 1, RXmNsashWidth, 1,
RXmNseparatorOn, true,
RXmNorientation, RXmVERTICAL,
RXmNbackground, basic_color])
add_with_sound_sliders(vepane)
rc = RXtCreateManagedWidget("form", RxmRowColumnWidgetClass, vepane,
[RXmNorientation, RXmVERTICAL,
RXmNalignment, RXmALIGNMENT_CENTER,
RXmNbackground, help_button_color])
@label_list = make_array(8) do |i|
RXtCreateManagedWidget("W" * 30, RxmLabelWidgetClass, rc,
[RXmNbackground, help_button_color])
end
add_with_sound_targets
@dialog.add_target([["open bezier", :open_bezier_path, true],
["closed bezier", :closed_bezier_path, false],
["literal", :literal_path, false]]) do |val|
@which_path = val
create_path
end
activate_dialog(@dialog.dialog)
@trajectory = make_xenved("x, y", trfr,
:envelope, init_traj,
:axis_bounds, [0.0, 1.0, 0.0, 1.0],
:axis_label, [-10.0, 10.0, 0.0, 10.0])
@z_value = make_xenved("z", zfr,
:envelope, init_z_traj,
:axis_bounds, [0.0, 1.0, 0.0, 1.0],
:axis_label, [-10.0, 10.0, 0.0, 10.0])
@velocity = make_xenved("velocity v", vefr,
:envelope, init_vel,
:axis_bounds, [0.0, 1.0, 0.05, 1.0],
:axis_label, [-10.0, 10.0, 0.0, 2.0])
else
pane = Rgtk_hbox_new(false, 0)
Rgtk_box_pack_start(RGTK_BOX(@dialog.parent), pane, false, false, 4)
Rgtk_widget_show(pane)
xepane = Rgtk_vbox_new(true, 0)
Rgtk_box_pack_start(RGTK_BOX(pane), xepane, true, true, 4)
Rgtk_widget_show(xepane)
activate_dialog(@dialog.dialog)
@trajectory = make_xenved("x, y", xepane,
:envelope, init_traj,
:axis_bounds, [0.0, 1.0, 0.0, 1.0],
:axis_label, [-10.0, 10.0, 0.0, 10.0])
@z_value = make_xenved("z", xepane,
:envelope, init_z_traj,
:axis_bounds, [0.0, 1.0, 0.0, 1.0],
:axis_label, [-10.0, 10.0, 0.0, 10.0])
@velocity = make_xenved("velocity v", xepane,
:envelope, init_vel,
:axis_bounds, [0.0, 1.0, 0.05, 1.0],
:axis_label, [-10.0, 10.0, 0.0, 2.0])
vepane = Rgtk_vbox_new(false, 0)
Rgtk_box_pack_start(RGTK_BOX(pane), vepane, false, false, 4)
Rgtk_widget_show(vepane)
add_with_sound_sliders(vepane)
@label_list = make_array(8) do |i|
lab = Rgtk_label_new("W" * 30)
Rgtk_box_pack_start(RGTK_BOX(vepane), lab, false, false, 4)
Rgtk_widget_show(lab)
lab
end
add_with_sound_targets
@dialog.add_target([["open bezier", :open_bezier_path, true],
["closed bezier", :closed_bezier_path, false],
["literal", :literal_path, false]]) do |val|
@which_path = val
create_path
end
end
set_xm_enveds_hooks(@trajectory, @z_value, @velocity)
@dialog.clear_string("Gnuplot")
@dialog.doit_string((@snd_p ? "With_Snd" : "With_Sound"))
show_values
else
activate_dialog(@dialog.dialog)
end
end
private
def create_path
test_path
@path = case @which_path
when :open_bezier_path
make_path(@snd_path)
when :closed_bezier_path
make_closed_path(@snd_path)
when :literal_path
make_literal_path(@snd_path)
end
end
def test_path
if @snd_path.length == 2
Snd.display("%s#%s: path has only two points, one added", self.class, get_func_name)
@snd_path.insert(1, [0.0, @snd_path[0][1] - 0.1, 0.0, @snd_path[0][3] + 0.1])
end
unless @snd_path.detect do |pnt| pnt[1].nonzero? end
Snd.display("%s#%s: y-values are all zero, changed to mid-point 0.1",
self.class, get_func_name)
@snd_path[@snd_path.length / 2][1] = 0.1
end
end
def points_to_path
@snd_path = []
@trajectory.each do |x, y|
z = @z_value.interp(x)
vel = @velocity.interp(x)
@snd_path.push([x * 20.0 - 10.0, y * 10.0, z * 10.0, vel * 2.0])
end
end
def show_values
points_to_path
@label_list.each_with_index do |w, i|
if i.zero?
change_label(w, format("%6s %6s %6s %6s", "x", "y", "z", "v"))
else
x, y, z, v = @snd_path[i - 1]
if x
change_label(w, format("%s %s %s %s",
to_f_str(x), to_f_str(y), to_f_str(z), to_f_str(v)))
else
change_label(w, "")
end
end
end
end
def to_f_str(val)
"%6s" % ("% 3.1f" % val)
end
end
class Dlocsig_spiral < Dlocsig_menu
def initialize(label, snd_p = false)
super
@start = 0
@turns = 3.0
end
def inspect
format("%s (%d %1.1f)", @label, @start, @turns)
end
def post_dialog
unless @dialog.kind_of?(Dialog) and widget?(@dialog.dialog)
sliders = []
init_start = 0
init_turns = 3.0
@dialog = make_dialog(@label,
:help_cb, lambda do |w, c, i|
help_cb
end, :clear_cb, lambda do |w, c, i|
make_spiral_path(:start_angle, @start, :turns, @turns).pplot
end, :reset_cb, lambda do |w, c, i|
reset_with_sound_sliders
set_scale_value(sliders[0].scale, @start = init_start)
@turns = init_turns
set_scale_value(sliders[1].scale, init_turns, 10.0)
end) do |w, c, i|
@path = make_spiral_path(:start_angle, @start, :turns, @turns)
with_sound_target("spiral_path: %s, start: %d, turns: %1.1f",
dlocsig_strings, @start, @turns)
end
add_with_sound_sliders(if provided? :xg
@dialog.dialog
else
@dialog.parent
end)
sliders << @dialog.add_slider("start angle", 0, init_start, 360) do |w, c, i|
@start = get_scale_value(w, i)
end
# turns below 2.6 together with reverb create noise
sliders << @dialog.add_slider("turns", 2.6, init_turns, 10.0, 10) do |w, c, i|
@turns = get_scale_value(w, i, 10.0)
end
add_with_sound_targets
@dialog.clear_string("Gnuplot")
@dialog.doit_string((@snd_p ? "With_Snd" : "With_Sound"))
end
activate_dialog(@dialog.dialog)
end
end
unless defined? $__private_dlocsig_menu__ and $__private_dlocsig_menu__
snd_main = make_snd_menu("Dlocsig") do
cascade("Dlocsig (Snd)") do
entry(Dlocsig_bezier, "Bezier path (Snd)", true)
entry(Dlocsig_spiral, "Spiral path (Snd)", true)
end
cascade("Dlocsig (CLM)") do
entry(Dlocsig_bezier, "Bezier path (CLM)", false)
entry(Dlocsig_spiral, "Spiral path (CLM)", false)
end
end
if provided? :xm
set_label_sensitive(menu_widgets[Top_menu_bar], "Dlocsig", ((sounds() or []).length > 1))
else
set_sensitive(snd_main.menu, ((sounds() or []).length > 1))
end
unless $open_hook.member?("dlocsig-menu-hook")
$open_hook.add_hook!("dlocsig-menu-hook") do |snd|
if provided? :xm
set_label_sensitive(menu_widgets[Top_menu_bar], "Dlocsig", true)
else
set_sensitive(snd_main.menu, true)
end
false
end
$close_hook.add_hook!("dlocsig-menu-hook") do |snd|
if provided? :xm
set_label_sensitive(menu_widgets[Top_menu_bar], "Dlocsig", ((sounds() or []).length > 1))
else
set_sensitive(snd_main.menu, ((sounds() or []).length > 1))
end
false
end
end
end
end
# JC_REVERB (examp.rb) default options
#
# :low_pass, false
# :volume, 1.0
# :amp_env, false
# :delay1, 0.013
# :delay2, 0.011
# :delay3, 0.015
# :delay4, 0.017
# :double, false
#
# $clm_reverb = :jc_reverb_rb
# $clm_reverb_data = [:low_pass, false, :volume, 1.0, :amp_env, false,
# :delay1, 0.013, :delay2, 0.011, :delay3, 0.015, :delay4, 0.017]
# require "clm-ins"
#
# JL_REVERB has no options
#
# $clm_reverb = :jl_reverb
# $clm_reverb_data = []
# NREV default options
#
# :reverb_factor, 1.09
# :lp_coeff, 0.7
# :volume, 1.0
#
# $clm_reverb = :nrev_rb
# $clm_reverb_data = [:volume, 1.0, :lp_coeff, 0.7]
# INTERN or N_REV default options (only with_snd)
#
# :amount, 0.1
# :filter, 0.5
# :feedback, 1.09
#
# $clm_reverb = :intern
# $clm_reverb_data = [:amount, 0.1, :filter, 0.5, :feedback, 1.09]
# require "freeverb"
#
# FREEVERB default options
#
# :room_decay, 0.5,
# :damping, 0.5,
# :global, 0.3,
# :predelay, 0.03,
# :output_gain, 1.0,
# :output_mixer, nil,
# :scale_room_decay, 0.28,
# :offset_room_decay, 0.7,
# :combtuning, [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617],
# :allpasstuning, [556, 441, 341, 225],
# :scale_damping, 0.4,
# :stereo_spread, 23,
#
# $clm_reverb = :freeverb
# $clm_reverb_data = [:room_decay, 0.5, :damping, 0.5, :global, 0.3, :predelay, 0.03,
# :output_gain, 1.0, :output_mixer, nil, :scale_room_decay, 0.7,
# :scale_damping, 0.4, :stereo_spread, 23]
=begin
# (snd-ruby-mode)
# Examples:
(with_sound(:channels, 4, :output, "rdloc04.snd") do
sinewave(0, 1, 440, 0.5, [[-10, 10], [0, 5], [10, 10]].to_path)
end)
(with_sound(:channels, 4, :output, "rdlocspiral.snd") do
move(0, "/usr/gnu/sound/SFiles/bell.snd", DL.make_spiral_path(:start_angle, 180, :turns, 3.5))
end)
([[-10, 10, 0, 0], [0, 5, 0, 1], [10, 10, 0, 0]].to_path(:error, 0.01).plot_velocity)
(with_sound(:channels, 4, :output, "rdlocmove.snd") do
move_sound(DL.make_path([[-10, 10], [0.1, 0.1], [10, -10]])) do
fm_violin_rb(0, 1, 440, 0.1)
fm_violin_rb(0.3, 2, 1020, 0.05)
end
end)
=end
# dlocsig.rb ends here