|
- # dlocsig.rb -- CLM -> Snd/Ruby translation of dlocsig.lisp
-
- # Translator/Author: Michael Scholz <mi-scholz@users.sourceforge.net>
- # Copyright (c) 2003-2012 Michael Scholz <mi-scholz@users.sourceforge.net>
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions
- # are met:
- # 1. Redistributions of source code must retain the above copyright
- # notice, this list of conditions and the following disclaimer.
- # 2. Redistributions in binary form must reproduce the above copyright
- # notice, this list of conditions and the following disclaimer in the
- # documentation and/or other materials provided with the distribution.
- #
- # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- # SUCH DAMAGE.
-
- # 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 "ws"
- 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, @chns, default_output_srate,
- default_output_sample_type,
- default_output_header_type)
- 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 |xx| omcs * xx end
- ra = ra.map do |xx| sn * xx 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.imag)
- y = (dxy * avec.real)
- z = evec.imag
- 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.new(len) do |x| 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.new(len) do |x| 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
- (@rev_channels != 1 and @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.imag)
- x << (dxy * avec.imag)
- 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.imag)
- x << (dxy * avec.imag)
- 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.imag)
- 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 |tii| tii / 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] = 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] = 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 |tii| tii / 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.imag)
- 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 = 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)
- 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])
- @label_list = make_array(8) do |i|
- RXtCreateManagedWidget("W" * 30, RxmLabelWidgetClass, rc, [])
- 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
|