1# dlocsig.rb -- CLM -> Snd/Ruby translation of dlocsig.lisp 2 3# Translator/Author: Michael Scholz <mi-scholz@users.sourceforge.net> 4# Copyright (c) 2003-2020 Michael Scholz <mi-scholz@users.sourceforge.net> 5# All rights reserved. 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions 9# are met: 10# 1. Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# 2. Redistributions in binary form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in the 14# documentation and/or other materials provided with the distribution. 15# 16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26# SUCH DAMAGE. 27 28# Original Copyright of Fernando Lopez Lezcano: 29 30# ;;; Copyright (c) 92, 93, 94, 98, 99, 2000, 2001 Fernando Lopez Lezcano. 31# ;;; All rights reserved. 32# ;;; Use and copying of this software and preparation of derivative works 33# ;;; based upon this software are permitted and may be copied as long as 34# ;;; no fees or compensation are charged for use, copying, or accessing 35# ;;; this software and all copies of this software include this copyright 36# ;;; notice. Suggestions, comments and bug reports are welcome. Please 37# ;;; address email to: nando@ccrma.stanford.edu 38# ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 39 40# ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 41# ;;; Dynamic multichannel three-dimentional signal locator 42# ;;; (wow that sound good! :-) 43# ;;; 44# ;;; by Fernando Lopez Lezcano 45# ;;; CCRMA, Stanford University 46# ;;; nando@ccrma.stanford.edu 47# ;;; 48# ;;; Thanks to Juan Pampin for help in the initial coding of the new version 49# ;;; and for prodding me to finish it. To Joseph L. Anderson and Marcelo Perticone 50# ;;; for insights into the Ambisonics coding and decoding process. 51# ;;; http://www.york.ac.uk/inst/mustech/3d_audio/ambison.htm for more details... 52 53# Tested with Snd 7.10, Motif 2.2.2, Ruby 1.6.6, 1.6.8 and 1.9.0. 54# 55# The code is a translation of the Lisp code of Fernando Lopez Lezcano 56# found in clm-2/dlocsig of the CLM distribution. An extensive 57# documentation of the purpose and usage of it can be found in 58# clm-2/dlocsig/dlocsig.html. 59# 60# Note: dlocsig.rb handles not more rev_channels than out_channels; 61# B_format_ambisonics handles only 4 out_channels and 0, 1, or 4 62# rev_channels. 63# 64# The simple example 65# 66# [[-10, 10], [0, 5], [10, 10]].to_path.snd_plot 67# 68# draws trajectory, velocity, doppler curve, and the acceleration in 69# Snd's lisp-graph. If you have gnuplot installed, the example 70# 71# [[-10, 10], [0, 5], [10, 10]].to_path.pplot 72# 73# draws all four curves in one gnuplot window. 74 75# DL.make_path 76# DL.make_polar_path 77# DL.make_closed_path 78# and to_path take the following options 79# 80# Open_bezier_path.new(path, *args) 81# :d3, true 82# :polar, false 83# :error, 0.01 84# :curvature, nil 85# :initial_direction, [0.0, 0.0, 0.0] 86# :final_direction, [0.0, 0.0, 0.0] 87# 88# Closed_bezier_path.new(path, *args) 89# :d3, true 90# :polar, false 91# :error, 0.01 92# :curvature, nil 93# 94# DL.make_literal_path 95# DL.make_literal_polar_path take the following options 96# 97# Literal_path.new(path, *args) 98# :d3, true 99# :polar, false 100# 101# DL.make_spiral_path takes these options 102# 103# Spiral_path.new :start_angle, 0 104# :turns, 2 105# 106# The make_locsig-replacement make_dlocsig takes these arguments: 107# 108# DL.make_dlocsig(startime, dur, *args) 109# :path, nil 110# :scaler, 1.0 111# :reverb_amount, 0.05 112# :rbm_output, $output 113# :rbm_reverb, $reverb 114# :output_power, 1.5 115# :reverb_power, 0.5 116# :render_using, :amplitude_panning 117# or :b_format_ambisonics 118# or :decoded_ambisonics 119# 120# Sample instruments (sinewave() and move() below) show how to replace 121# the usual make_locsig() and locsig() by DL.make_dlocsig() and 122# DL.dlocsig(). 123 124# Example functions at the end of the file: 125 126# class Instrument 127# sinewave(start, dur, freq, amp, path, amp_env, *dlocsig_args) 128# move(start, file, path, *dlocsig_args) 129# move_sound(path, *dlocsig_args) do ... end 130# 131# class With_sound 132# run_dlocsig(start, dur, *dlocsig_args) do |samp| ... end 133 134# Classes and Modules: 135 136# module Inject 137# inject(n) 138# sum(initial) 139# product(initial) 140# 141# class Array 142# to_trias 143# to_path(*args) 144# 145# class Sndplot 146# initialize(chns) 147# snd 148# open 149# close 150# 151# class Gnuplot 152# initialize 153# open 154# close 155# command(*args) 156# reset 157# set_autoscale 158# set_x_range(range) 159# set_y_range(range) 160# set_z_range(range) 161# set_grid 162# set_surface 163# set_parametric 164# set_ticslevel(level) 165# set_title(title) 166# set_label(label) 167# set_margins(margin) 168# set_border(border) 169# start_multiplot 170# end_multiplot 171# size(xorigin, yorigin, xsize, ysize) 172# data(data, *args) 173# plot_2d_curve(curve, *args) 174# plot_2d_curves(curves, *args) 175# plot_3d_curve(curve, *args) 176# 177# module DL 178# class Dlocsig < Dlocs 179# initialize(start, dur, *args) 180# one_turn 181# one_turn=(val) 182# speed_of_sound 183# speed_of_sound=(val) 184# run_beg 185# run_end 186# angles_in_degree 187# angles_in_radians 188# angles_in_turns 189# distance_in_meters 190# distance_in_feet 191# 192# class Path 193# initialize(path, *args) 194# path_x 195# path_y 196# path_z 197# path_time 198# scale_path(scaling) 199# translate_path(translation) 200# rotate_path(rotation, *args) 201# 202# path_trajectory 203# path_2d_trajectory 204# path_velocity 205# path_doppler 206# path_acceleration 207# 208# plot_open 209# plot_close 210# cmd(*args) 211# plot_trajectory(*args) 212# plot_velocity(reset) 213# plot_doppler(reset) 214# plot_acceleration(reset) 215# pplot(normalize) 216# 217# snd_open(chns) 218# snd_close 219# snd_trajectory(chn, label) 220# snd_velocity(chn, label) 221# snd_doppler(chn, label) 222# snd_acceleration(chn, label) 223# snd_plot 224# 225# DL.make_dlocsig(start, dur, *args) 226# DL.dlocsig(dl, loc, input) 227# 228# DL.make_path(path, *args) 229# DL.make_polar_path(path, *args) 230# DL.make_closed_path(path, *args) 231# DL.make_literal_path(path, *args) 232# DL.make_literal_polar_path(path, *args) 233# DL.make_spiral_path(*args) 234# 235# class Dlocsig_menu 236# initialize(label, snd_p) 237# post_dialog 238 239require "ws" 240require "matrix" 241include Math 242 243provided?(:snd_motif) and (not provided?(:xm)) and require("libxm.so") 244 245class DlocsigError < StandardError 246end 247 248Ruby_exceptions[:dlocsig_error] = DlocsigError 249 250def dl_error(*msg) 251 Snd.raise(:dlocsig_error, (msg.empty? ? "" : format(*msg))) 252end 253 254# module Inject, see Thomas, David, Hunt, Andrew: Programming Ruby -- 255# The Pragmatic Programmer's Guide, 2001 Addison-Wesley, page 102n 256 257module Inject 258 def inject(n) 259 each do |x| n = yield(n, x) end 260 n 261 end 262 263 def sum(initial = 0) 264 inject(initial) do |n, v| n + v end 265 end 266 267 def product(initial = 1) 268 inject(initial) do |n, v| n * v end 269 end 270end unless defined? Inject 271 272# used by plotting curves 273# to_trias: [0, 1, 2, 3, 4, 5] --> [[0, 1, 2], [3, 4, 5]] 274# to_path: [[-10, 10], [0, 5], [10, 10]].to_path <=> DL.make_path([[-10, 10], [0, 5], [10, 10]]) 275# uses the same options as DL.make_path() 276 277class Array 278 include Inject 279 280 def to_trias 281 ary = [] 282 unless self.length.divmod(3).last.nonzero? 283 0.step(self.length - 2, 3) do |i| 284 ary.push([self[i], self[i + 1], self[i + 2]]) 285 end 286 end 287 ary 288 end 289 290 def to_path(*args) 291 DL.make_path(self, *args) 292 end 293end 294 295class Sndplot 296 def initialize(chns = 1) 297 @chns = chns 298 @snd = open 299 end 300 attr_reader :snd 301 302 def inspect 303 format("#<%s: snd: %d, chns: %d>", self.class, @snd, @chns) 304 end 305 306 def open 307 if snds = sounds() 308 snds.each do |s| set_sound_property(:selected, false, s) end 309 set_sound_property(:selected, true, selected_sound) 310 end 311 if @snd = snds.detect do |s| channels(s) >= @chns end 312 set_sound_property(:dlocsig_created, false, @snd) 313 select_sound(@snd) 314 else 315 @snd = new_sound(snd_tempnam, @chns, default_output_srate, 316 default_output_sample_type, 317 default_output_header_type) 318 set_sound_property(:dlocsig_created, true, @snd) 319 end 320 channels(@snd).times do |chn| 321 set_channel_property(:time_graph, time_graph?(@snd, chn), @snd, chn) 322 set_channel_property(:transform_graph, transform_graph?(@snd, chn), @snd, chn) 323 set_channel_property(:lisp_graph, lisp_graph?(@snd, chn), @snd, chn) 324 set_time_graph?(false, @snd, chn) 325 set_transform_graph?(false, @snd, chn) 326 set_lisp_graph?(true, @snd, chn) 327 end 328 $exit_hook.add_hook!("dlocsig-hook") do | | 329 close 330 false 331 end 332 @snd 333 end 334 335 def close 336 if snds = sounds() 337 snds.each do |snd| 338 set_sound_property(:selected, false, snd) 339 unless sound_property(:dlocsig_created, snd).nil? 340 if sound_property(:dlocsig_created, snd) 341 close_sound_extend(snd) 342 else 343 channels(snd).times do |chn| 344 set_time_graph?(channel_property(:time_graph, snd, chn), snd, chn) 345 set_transform_graph?(channel_property(:transform_graph, snd, chn), snd, chn) 346 set_lisp_graph?(channel_property(:lisp_graph, snd, chn), snd, chn) 347 end 348 end 349 end 350 end 351 end 352 $exit_hook.remove_hook!("dlocsig-hook") 353 self 354 end 355end 356 357class Gnuplot 358 @@plot_stream = nil 359 360 def initialize 361 if (not @@plot_stream) or @@plot_stream.closed? 362 open 363 end 364 end 365 366 def inspect 367 format("#<%s: plot_stream: %s>", self.class, @@plot_stream.inspect) 368 end 369 370 def open 371 if (gnuplot = `which gnuplot`).empty? 372 dl_error("gnuplot not found?") 373 else 374 @@plot_stream = IO.popen(gnuplot, "w") 375 end 376 end 377 378 def close 379 unless @@plot_stream.closed? 380 @@plot_stream.puts("quit") 381 @@plot_stream.close 382 @@plot_stream = nil 383 end 384 end 385 386 def command(*args) 387 open if @@plot_stream.closed? 388 @@plot_stream.printf(*args) 389 format(*args).chomp 390 rescue 391 Snd.warning("%s#%s", self.class, get_func_name) 392 end 393 394 def reset 395 command "reset\n" 396 end 397 398 def set_autoscale 399 command "set autoscale\n" 400 end 401 402 def set_x_range(range = []) 403 command("set xrange [%f:%f]\n", range[0], range[1]) if range.length == 2 404 end 405 406 def set_y_range(range = []) 407 command("set yrange [%f:%f]\n", range[0], range[1]) if range.length == 2 408 end 409 410 def set_z_range(range = []) 411 command("set zrange [%f:%f]\n", range[0], range[1]) if range.length == 2 412 end 413 414 def set_grid 415 command "set grid xtics; set grid ytics; set grid ztics\n" 416 end 417 418 def set_surface 419 command "set surface\n" 420 end 421 422 def set_parametric 423 command "set parametric\n" 424 end 425 426 def set_ticslevel(level = 0) 427 command("set ticslevel %.2f\n", level) 428 end 429 430 def set_title(title = "") 431 command("set title \"%s\"\n", title) unless title.empty? 432 end 433 434 def set_label(label = "") 435 command("set label \"%s\"\n", label) unless label.empty? 436 end 437 438 def set_margins(margin = 1) 439 command("set tmargin %f\n", margin) 440 command("set lmargin %f\n", margin) 441 command("set rmargin %f\n", margin) 442 command("set bmargin %f\n", margin) 443 end 444 445 def set_border(border = nil) 446 command("set border %d\n", border.to_i) if border 447 end 448 449 def start_multiplot 450 command "set multiplot\n" 451 end 452 453 def end_multiplot 454 command "set nomultiplot\n" 455 end 456 457 def size(xorigin, yorigin, xsize, ysize) 458 command("set origin %f,%f\n", xorigin.to_f, yorigin.to_f) 459 command("set size %f,%f\n", xsize.to_f, ysize.to_f) 460 end 461 462 def data(data, *args) 463 style, label = nil 464 optkey(args, binding, 465 [:style, "linespoints"], 466 [:label, ""]) 467 command("plot '-' %s %s\n", label.empty? ? "" : "title \"#{label}\"", 468 style.empty? ? "" : "with #{style}") 469 data.each_with_index do |y, x| command("%f %f\n", x, y) end 470 command "e\n" 471 end 472 473 def plot_2d_curve(curve, *args) 474 style, label = nil 475 optkey(args, binding, 476 [:style, "linespoints"], 477 [:label, ""]) 478 set_grid() 479 command("plot '-' %s %s\n", label.empty? ? "" : "title \"#{label}\"", 480 style.empty? ? "" : "with #{style}") 481 curve.each_pair do |x, y| command("%.8f %.8f\n", x, y) end 482 command "e\n" 483 end 484 485 def plot_2d_curves(curves, *args) 486 styles, labels = nil 487 optkey(args, binding, 488 [:styles, "linespoints"], 489 [:labels, ""]) 490 set_grid() 491 styles = curves.map do |i| styles end unless array?(styles) 492 labels = curves.map do |i| labels end unless array?(labels) 493 command "plot" 494 curves.each_with_index do |x, i| 495 style = styles[i] 496 label = labels[i] 497 command " '-' " 498 command(" title \"%s\"", label) if label or (not label.empty?) 499 command(" with %s", style) if style or (not style.empty?) 500 command(", ") if i != (curves.length - 1) 501 end 502 command "\n" 503 curves.each do |curve| 504 curve.each_pair do |x, y| command("%.8f %.8f\n", x, y) end 505 command "e\n" 506 end 507 end 508 509 def plot_3d_curve(curve, *args) 510 style, label, zstyle, xrot, zrot, scale, zscale = nil 511 optkey(args, binding, 512 [:style, "linespoints"], 513 [:label, ""], 514 [:zstyle, "impulses"], 515 :xrot, 516 :zrot, 517 :scale, 518 :zscale) 519 set_border(127 + 256 + 512) 520 set_grid() 521 set_surface() 522 set_parametric() 523 set_ticslevel(0) 524 if xrot or zrot or scale or zscale 525 command("set view %s,%s,%s,%s\n", xrot, zrot, scale, zscale) 526 end 527 command "splot '-'" 528 command(" title \"%s\"", label) unless label.empty? 529 command(" with %s 1", style) unless style.empty? 530 command(", '-' notitle with %s 1", zstyle) unless zstyle.empty? 531 command "\n" 532 curve.to_trias.each do |x, y, z| command("%.8f %.8f %.8f\n", x, y, z) end 533 command "e\n" 534 if zstyle 535 curve.to_trias.each do |x, y, z| command("%.8f %.8f %.8f\n", x, y, z) end 536 command "e\n" 537 end 538 end 539end 540 541module DL 542 Path_maxcoeff = 8 543 Point707 = cos(TWO_PI / 8.0) 544 545 Amplitude_panning = 1 546 B_format_ambisonics = 2 547 Decoded_ambisonics = 3 548 def which_render(val) 549 case val 550 when :amplitude_panning, Amplitude_panning 551 Amplitude_panning 552 when :b_format_ambisonics, B_format_ambisonics 553 B_format_ambisonics 554 when :decoded_ambisonics, Decoded_ambisonics 555 Decoded_ambisonics 556 else 557 Amplitude_panning 558 end 559 end 560 561 def cis(r) 562 Complex(cos(r), sin(r)) 563 end 564 565 def distance(x, y, z) 566 sqrt(x * x + y * y + z * z) 567 end 568 569 def nearest_point(x0, y0, z0, x1, y1, z1, px, py, pz) 570 if same?(x0, y0, z0, px, py, pz) 571 [x0, y0, z0] 572 elsif same?(x1, y1, z1, px, py, pz) 573 [x1, y1, z1] 574 elsif same?(x0, y0, z0, x1, y1, z1) 575 [x0, y0, z0] 576 else 577 xm0 = x1 - x0 578 ym0 = y1 - y0 579 zm0 = z1 - z0 580 xm1 = px - x0 581 ym1 = py - y0 582 zm1 = pz - z0 583 d0 = distance(xm0, ym0, zm0) 584 d1 = distance(xm1, ym1, zm1) 585 p = d1 * ((xm0 * xm1 + ym0 * ym1 + zm0 * zm1) / (d0 * d1)) 586 ratio = p / d0 587 [x0 + xm0 * ratio, y0 + ym0 * ratio, z0 + zm0 * ratio] 588 end 589 end 590 591 def same?(a0, b0, c0, a1, b1, c1) 592 a0 == a1 and b0 == b1 and c0 == c1 593 end 594 595 def rotation_matrix(x, y, z, angle) 596 mag = distance(x, y, z) 597 dx = x / mag 598 dy = y / mag 599 dz = z / mag 600 ri = Matrix.I(3) 601 ra = Matrix.rows([[0.0, dz, -dy], [-dz, 0.0, dx], [dy, -dx, 0.0]]) 602 raa = ra * ra 603 sn = sin(-angle) 604 omcs = 1 - cos(-angle) 605 raa = raa.map do |xx| omcs * xx end 606 ra = ra.map do |xx| sn * xx end 607 (ri + ra + raa) 608 end 609 610 class Dlocsig_base 611 include DL 612 613 def initialize 614 @one_turn = 360.0 615 @speed_of_sound = 344.0 616 end 617 attr_accessor :one_turn, :speed_of_sound 618 619 def angles_in_degree 620 @one_turn = 360.0 621 end 622 623 def angles_in_radians 624 @one_turn = TWO_PI 625 end 626 627 def angles_in_turns 628 @one_turn = 1.0 629 end 630 631 def distances_in_meters 632 @speed_of_sound = 344.0 633 end 634 635 def distances_in_feet 636 @speed_of_sound = 1128.0 637 end 638 end 639 640 class Speaker_config < Dlocsig_base 641 Groups = Struct.new("Groups", :size, :vertices, :speakers, :matrix) 642 643 def initialize 644 super 645 @number = nil 646 @coords = nil 647 @groups = nil 648 end 649 650 protected 651 def set_speakers(channels, d3) 652 if channels.between?(1, 8) 653 d3 = false if channels < 4 654 arrange_speakers(unless d3 655 case channels 656 when 1 657 [[0]] 658 when 2 659 [[-60, 60]] 660 when 3 661 [[-45, 45, 180]] 662 when 4 663 [[-45, 45, 135, 225]] 664 when 5 665 [[-45, 0, 45, 135, -135]] 666 when 6 667 [[-60, 0, 60, 120, 180, 240]] 668 when 7 669 [[-45, 0, 45, 100, 140, -140, -100]] 670 when 8 671 [[-22.5, 22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5]] 672 end 673 else 674 case channels 675 when 4 676 [[[-60, 0], [60, 0], [180, 0], [0, 90]], 677 [[0, 1, 3], [1, 2, 3], [2, 0, 3], [0, 1, 2]]] 678 when 5 679 [[[-45, 0], [45, 0], [135, 0], [-135, 0], [0, 90]], 680 [[0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4], [0, 1, 2], [2, 3, 0]]] 681 when 6 682 [[[-45, 0], [45, 0], [135, 0], [-135, 0], [-90, 60], [90, 60]], 683 [[0, 1, 4], [1, 4, 5], [1, 2, 5], [2, 3, 5], 684 [3, 4, 5], [3, 0, 4], [0, 1, 2], [2, 3, 0]]] 685 when 7 686 [[[-45, 0], [45, 0], [135, 0], [-135, 0], 687 [-60, 60], [60, 60], [180, 60]], 688 [[0, 1, 4], [1, 4, 5], [1, 2, 5], [2, 6, 5], [2, 3, 6], 689 [3, 4, 6], [3, 0, 4], [4, 5, 6], [0, 1, 2], [2, 3, 0]]] 690 when 8 691 [[[-45, 10], [45, -10], [135, -10], [225, -10], 692 [-45, 45], [45, 45], [135, 45], [225, 45]], 693 [[0, 4, 5], [0, 5, 1], [5, 1, 2], [2, 6, 5], [6, 7, 2], [2, 3, 7], 694 [3, 7, 4], [3, 0, 4], [4, 7, 6], [6, 5, 4], [0, 1, 2], [2, 3, 0]]] 695 end 696 end) 697 else 698 dl_error(channels, "only 1 to 8 channels possible") 699 end 700 end 701 702 private 703 def arrange_speakers(args) 704 speakers = args.shift 705 groups = args.shift 706 @number = speakers.length 707 @coords = speakers.map do |s| 708 a = (array?(s) ? s[0] : s).to_f 709 e = (array?(s) ? s[1] : 0).to_f 710 evec = cis((e / @one_turn) * TWO_PI) 711 dxy = evec.real 712 avec = cis((a / @one_turn) * TWO_PI) 713 x = (dxy * avec.imag) 714 y = (dxy * avec.real) 715 z = evec.imag 716 mag = distance(x, y, z) 717 [x / mag, y / mag, z / mag] 718 end 719 unless groups 720 if @number == 1 721 groups = [[0]] 722 else 723 groups = make_array(@number) do |i| [i, i + 1] end 724 groups[-1][-1] = 0 725 end 726 end 727 @groups = groups.map do |group| 728 size = group.length 729 vertices = group.map do |vertice| @coords[vertice] end 730 matrix = case size 731 when 3 732 if (m = Matrix[vertices[0], vertices[1], vertices[2]]).regular? 733 m.inverse.to_a 734 else 735 nil 736 end 737 when 2 738 if (m = Matrix[vertices[0][0, 2], vertices[1][0, 2]]).regular? 739 m.inverse.to_a 740 else 741 nil 742 end 743 else 744 nil 745 end 746 Groups.new(size, vertices, group, matrix) 747 end 748 end 749 end 750 751 class Dlocsig < Speaker_config 752 def initialize 753 super 754 @render_using = Amplitude_panning 755 @output_power = 1.5 756 @reverb_power = 0.5 757 @rbm_output = nil 758 @rbm_reverb = nil 759 @out_channels = 4 760 @rev_channels = 1 761 @clm = true 762 @delay = [] 763 @prev_time = @prev_dist = @prev_group = @prev_x = @prev_y = @prev_z = false 764 @first_dist = @last_dist = 0.0 765 @min_dist = @max_dist = 0.0 766 @start = nil 767 @end = nil 768 @output_gains = nil 769 @reverb_gains = nil 770 @path = nil 771 @run_beg = @run_end = nil 772 end 773 attr_reader :run_beg, :run_end, :out_channels, :rev_channels 774 775 def inspect 776 format("#<%s: channels: %d, reverb: %s>", self.class, @out_channels, @rev_channels.inspect) 777 end 778 779 def each 780 (@run_beg...@run_end).each do |i| yield(i) end 781 end 782 alias run each 783 784 # general clm version 785 # dl.dlocsig(loc, val) 786 def dlocsig(loc, input) 787 if loc < @start 788 delay(@path, (loc >= @end ? 0.0 : input), 0.0) 789 @out_channels.times do |chn| out_any(loc, 0.0, chn, @rbm_output) end 790 else 791 sample = delay(@path, (loc >= @end ? 0.0 : input), env(@delays)) 792 @out_channels.times do |chn| 793 out_any(loc, sample * env(@output_gains[chn]), chn, @rbm_output) 794 end 795 @rev_channels.times do |chn| 796 out_any(loc, sample * env(@reverb_gains[chn]), chn, @rbm_reverb) 797 end 798 end 799 end 800 801 # dl.ws_dlocsig do |loc| ...; val; end 802 # @clm == true @rbm_output/@rbm_reverb: sample2files 803 # @clm == false @rbm_output/@rbm_reverb: sound index numbers 804 # With_sound#run_dlocsig below uses this method 805 def ws_dlocsig 806 len = @run_end - @run_beg 807 out_data = make_vct(len) 808 len.times do |i| 809 loc = i + @run_beg 810 input = yield(loc) 811 if loc < @start 812 delay(@path, (loc >= @end ? 0.0 : input), 0.0) 813 else 814 out_data[i] = delay(@path, (loc >= @end ? 0.0 : input), env(@delays)) 815 end 816 end 817 @out_channels.times do |chn| 818 if @clm 819 (@run_beg...@run_end).each do |i| 820 out_any(i, out_data[i - @run_beg] * env(@output_gains[chn]), chn, @rbm_output) 821 end 822 else 823 out = vct_multiply!(vct_copy(out_data), 824 Vct.new(len) do |x| env(@output_gains[chn]) end) 825 mix_vct(out, @run_beg, @rbm_output, chn, false) 826 end 827 end 828 @rev_channels.times do |chn| 829 if @clm 830 (@run_beg...@run_end).each do |i| 831 out_any(i, out_data[i - @run_beg] * env(@reverb_gains[chn]), chn, @rbm_reverb) 832 end 833 else 834 out = vct_multiply!(vct_copy(out_data), 835 Vct.new(len) do |x| env(@reverb_gains[chn]) end) 836 mix_vct(out, @run_beg, @rbm_reverb, chn, false) 837 end 838 end 839 end 840 841 # :amplitude_panning 842 # :b_format_ambisonics 843 # :decoded_ambisonics 844 def make_dlocsig(startime, dur, *args) 845 path, scaler, reverb_amount, output_power, reverb_power, render_using = nil 846 rbm_output, rbm_reverb, out_channels, rev_channels, clm = nil 847 optkey(args, binding, 848 :path, 849 [:scaler, 1.0], 850 [:reverb_amount, 0.05], 851 [:output_power, 1.5], 852 [:reverb_power, 0.5], 853 [:render_using, Amplitude_panning], 854 [:rbm_output, $output], 855 [:rbm_reverb, $reverb], 856 [:out_channels, 4], 857 [:rev_channels, 1], 858 [:clm, true]) 859 @output_power = output_power 860 @reverb_power = reverb_power 861 @render_using = which_render(render_using) 862 @rbm_output = rbm_output 863 @rbm_reverb = rbm_reverb 864 @out_channels = out_channels 865 @rev_channels = rev_channels 866 @clm = clm 867 if @render_using == B_format_ambisonics and @out_channels != 4 868 dl_error("B_format_ambisonics requires 4 output channels") 869 end 870 if @render_using == B_format_ambisonics and 871 @rev_channels.nonzero? and 872 (@rev_channels != 1 and @rev_channels != 4) 873 dl_error("B_format_ambisonics accepts only 0, 1 or 4 rev_channels") 874 end 875 if @rev_channels > @out_channels 876 dl_error("more rev_channels than out_channels") 877 end 878 if @render_using == B_format_ambisonics 879 scaler *= 0.8 880 end 881 unless path.kind_of?(Path) 882 if array?(path) and !path.empty? 883 path = make_path(path) 884 else 885 dl_error(path, "sorry, need a path") 886 end 887 end 888 xpoints = path.path_x 889 ypoints = path.path_y 890 zpoints = path.path_z 891 tpoints = path.path_time 892 @channel_gains = make_array(@out_channels) do [] end 893 @channel_rev_gains = make_array(@rev_channels) do [] end 894 self.set_speakers(@out_channels, (not zpoints.detect do |x| x.nonzero? end.nil?)) 895 @speed_limit = (@speed_of_sound * (tpoints[-1] - tpoints[0])) / dur 896 if xpoints.length == 1 897 walk_all_rooms(xpoints[0], ypoints[0], zpoints[0], tpoints[0]) 898 else 899 xb = yb = zb = tb = 0.0 900 (tpoints.length - 1).times do |i| 901 xa, xb = xpoints[i, 2] 902 ya, yb = ypoints[i, 2] 903 za, zb = zpoints[i, 2] 904 ta, tb = tpoints[i, 2] 905 minimum_segment_length(xa, ya, za, ta, xb, yb, zb, tb) 906 end 907 walk_all_rooms(xb, yb, zb, tb) 908 end 909 @start = dist2samples(@first_dist - @min_dist) 910 @end = seconds2samples(startime + dur) 911 min_delay = dist2samples(@min_dist) 912 @run_beg = seconds2samples(startime) 913 @run_end = @end + dist2samples(@last_dist) - min_delay 914 real_dur = dur + dist2seconds(@last_dist - @first_dist) 915 min_dist_unity = [@min_dist, 1.0].max 916 unity_gain = scaler * min_dist_unity ** @output_power 917 @output_gains = make_array(@number) do |i| 918 make_env(:envelope, @channel_gains[i], :scaler, unity_gain, :duration, real_dur) 919 end 920 if @rev_channels.nonzero? 921 unity_rev_gain = reverb_amount * scaler * min_dist_unity ** @reverb_power 922 @reverb_gains = make_array(@rev_channels) do |i| 923 make_env(:envelope, @channel_rev_gains[i], :scaler, unity_rev_gain, :duration, real_dur) 924 end 925 end 926 @delays = make_env(:envelope, @delay, :offset, -min_delay, :duration, real_dur) 927 @path = make_delay(:size, 1, :max_size, [1, dist2samples(@max_dist)].max) 928 self 929 end 930 931 private 932 def dist2samples(d) 933 (d * (mus_srate() / @speed_of_sound)).round 934 end 935 936 def dist2seconds(d) 937 d / @speed_of_sound.to_f 938 end 939 940 def transition_point_3(vert_a, vert_b, xa, ya, za, xb, yb, zb) 941 line_b = vct(xa, ya, za) 942 line_m = tr3_sub(vct(xb, yb, zb), line_b) 943 normal = tr3_cross(vert_a, vert_b) 944 if (denominator = tr3_dot(normal, line_m)).abs <= 0.000001 945 false 946 else 947 vct2list(tr3_add(line_b, tr3_scale(line_m, -tr3_dot(normal, line_b) / denominator))) 948 end 949 end 950 951 def tr3_cross(v1, v2) 952 vct(v1[1] * v2[2] - v1[2] * v2[1], 953 v1[2] * v2[0] - v1[0] * v2[2], 954 v1[0] * v2[1] - v1[1] * v2[0]) 955 end 956 957 def tr3_dot(v1, v2) 958 dot_product(v1, v2) 959 end 960 961 def tr3_sub(v1, v2) 962 vct_subtract!(vct_copy(v1), v2) 963 end 964 965 def tr3_add(v1, v2) 966 vct_add!(vct_copy(v1), v2) 967 end 968 969 def tr3_scale(v1, c) 970 vct_scale!(vct_copy(v1), c) 971 end 972 973 def transition_point_2(vert, xa, ya, xb, yb) 974 ax = vert[0] 975 bx = xa - xb 976 ay = vert[1] 977 by = ya - yb 978 cx = -xa 979 cy = -ya 980 d = by * cx - bx * cy 981 f = ay * bx - ax * by 982 if f.zero? 983 false 984 else 985 [(d * ax) / f, (d * ay) / f] 986 end 987 end 988 989 def calculate_gains(x, y, z, group) 990 zero_coord = 1e-10 991 zero_gain = 1e-10 992 size = group.size 993 if mat = group.matrix 994 if x.abs < zero_coord and y.abs < zero_coord and z.abs < zero_coord 995 [true, [1.0, 1.0, 1.0]] 996 else 997 case size 998 when 3 999 gain_a = mat[0][0] * x + mat[0][1] * y + mat[0][2] * z 1000 gain_b = mat[1][0] * x + mat[1][1] * y + mat[1][2] * z 1001 gain_c = mat[2][0] * x + mat[2][1] * y + mat[2][2] * z 1002 mag = distance(gain_a, gain_b, gain_c) 1003 if gain_a.abs < zero_gain then gain_a = 0.0 end 1004 if gain_b.abs < zero_gain then gain_b = 0.0 end 1005 if gain_c.abs < zero_gain then gain_c = 0.0 end 1006 [(gain_a >= 0 and gain_b >= 0 and gain_c >= 0), 1007 [gain_a / mag, gain_b / mag, gain_c / mag]] 1008 when 2 1009 gain_a = mat[0][0] * x + mat[0][1] * y 1010 gain_b = mat[1][0] * x + mat[1][1] * y 1011 mag = distance(gain_a, gain_b, 0.0) 1012 if gain_a.abs < zero_gain then gain_a = 0.0 end 1013 if gain_b.abs < zero_gain then gain_b = 0.0 end 1014 [(gain_a >= 0 and gain_b >= 0), [gain_a / mag, gain_b / mag]] 1015 when 1 1016 [true, [1.0]] 1017 end 1018 end 1019 else 1020 [true, [1.0, 1.0, 1.0]] 1021 end 1022 end 1023 1024 def find_group(x, y, z) 1025 grp = gns = false 1026 @groups.detect do |group| 1027 inside, gains = calculate_gains(x, y, z, group) 1028 if inside 1029 grp, gns = group, gains 1030 true 1031 end 1032 end 1033 [grp, gns] 1034 end 1035 1036 def push_zero_gains(time) 1037 @out_channels.times do |i| @channel_gains[i].push(time, 0.0) end 1038 @rev_channels.times do |i| @channel_rev_gains[i].push(time, 0.0) end 1039 end 1040 1041 def push_gains(group, gains, dist, time) 1042 outputs = make_vct(@out_channels) 1043 revputs = if @rev_channels > 0 1044 make_vct(@rev_channels) 1045 else 1046 false 1047 end 1048 if dist >= 1.0 1049 att = 1.0 / dist ** @output_power 1050 ratt = 1.0 / dist ** @reverb_power 1051 else 1052 att = 1.0 - dist ** (1.0 / @output_power) 1053 ratt = 1.0 - dist ** (1.0 / @reverb_power) 1054 end 1055 if dist >= 1.0 1056 group.speakers.each_with_index do |speaker, i| 1057 gain = gains[i] 1058 outputs[speaker] = gain * att 1059 if @rev_channels > 1 1060 revputs[speaker] = gain * ratt 1061 end 1062 end 1063 else 1064 @number.times do |speaker| 1065 if found = group.speakers.index(speaker) 1066 gain = gains[found] 1067 outputs[speaker] = gain + (1.0 - gain) * att 1068 if @rev_channels > 1 1069 revputs[speaker] = gain + (1.0 - gain) * ratt 1070 end 1071 else 1072 outputs[speaker] = att 1073 if @rev_channels > 1 1074 revputs[speaker] = ratt 1075 end 1076 end 1077 end 1078 end 1079 vct2list(outputs).each_with_index do |val, i| @channel_gains[i].push(time, val) end 1080 if @rev_channels == 1 1081 @channel_rev_gains[0].push(time, ratt) 1082 elsif @rev_channels > 1 1083 vct2list(revputs).each_with_index do |val, i| @channel_rev_gains[i].push(time, val) end 1084 end 1085 end 1086 1087 def amplitude_panning(x, y, z, dist, time) 1088 if @prev_group 1089 if time != @prev_time and ((dist - @prev_dist) / (time - @prev_time)) > @speed_limit 1090 Snd.display("%s#%s: supersonic radial movement", self.class, get_func_name) 1091 end 1092 inside, gains = calculate_gains(x, y, z, @prev_group) 1093 if inside 1094 push_gains(@prev_group, gains, dist, time) 1095 @prev_x, @prev_y, @prev_z = x, y, z 1096 else 1097 group, gains = find_group(x, y, z) 1098 if group 1099 edge = group.vertices & @prev_group.vertices 1100 if edge.length == 2 1101 if pint = transition_point_3(edge[0], edge[1], x, y, z, @prev_x, @prev_y, @prev_z) 1102 xi, yi, zi = pint 1103 di = distance(xi, yi, zi) 1104 ti = @prev_time + 1105 (distance(xi - @prev_x, yi - @prev_y, zi - @prev_z) / \ 1106 distance(x - @prev_x, y - @prev_y, z - @prev_z)) * (time - @prev_time) 1107 if ti < @prev_time 1108 inside, gains = calculate_gains(xi, yi, zi, @prev_group) 1109 if inside 1110 push_gains(@prev_group, gains, di, ti) 1111 else 1112 inside, gains = calculate_gains(xi, yi, zi, group) 1113 if inside 1114 push_gains(group, gains, di, ti) 1115 else 1116 dl_error("outside of both adjacent groups") 1117 end 1118 end 1119 else 1120 if $DEBUG 1121 Snd.warning("%s#%s: current time <= previous time", self.class, get_func_name) 1122 end 1123 end 1124 end 1125 elsif edge.length == 1 and group.size == 2 1126 if pint = transition_point_2(edge[0], x, y, @prev_x, @prev_y) 1127 xi, yi = pint 1128 di = distance(xi, yi, 0.0) 1129 ti = @prev_time + 1130 (distance(xi - @prev_x, yi - @prev_y, 0.0) / \ 1131 distance(x - @prev_x, y - @prev_y, 0.0)) * (time - @prev_time) 1132 if ti < @prev_time 1133 inside, gains = calculate_gains(xi, yi, 0.0, @prev_group) 1134 if inside 1135 push_gains(@prev_group, gains, di, ti) 1136 inside, gains = calculate_gains(xi, yi, 0.0, group) 1137 if inside 1138 push_gains(group, gains, di, ti) 1139 else 1140 dl_error("outside of both adjacent groups") 1141 end 1142 end 1143 else 1144 if $DEBUG 1145 Snd.warning("%s#%s: current time <= previous time", self.class, get_func_name) 1146 end 1147 end 1148 end 1149 elsif edge.length == 1 1150 Snd.display("%s#%s: only one point in common", self.class, get_func_name) 1151 elsif edge.length.zero? 1152 Snd.display("%s#%s: with no common points", self.class, get_func_name) 1153 end 1154 push_gains(group, gains, dist, time) 1155 @prev_group, @prev_x, @prev_y, @prev_z = group, x, y, z 1156 else 1157 push_zero_gains(time) 1158 @prev_group = false 1159 end 1160 end 1161 else 1162 group, gains = find_group(x, y, z) 1163 if group 1164 push_gains(group, gains, dist, time) 1165 @prev_group, @prev_x, @prev_y, @prev_z = group, x, y, z 1166 else 1167 push_zero_gains(time) 1168 @prev_group = false 1169 end 1170 end 1171 @prev_time = time 1172 @prev_dist = dist 1173 end 1174 1175 def b_format_ambisonics(x, y, z, dist, time) 1176 if dist > 1.0 1177 att = (1.0 / dist) ** @output_power 1178 @channel_gains[0].push(time, Point707 * att) 1179 @channel_gains[1].push(time, (y / dist) * att) 1180 @channel_gains[2].push(time, (-x / dist) * att) 1181 @channel_gains[3].push(time, (z / dist) * att) 1182 if @rev_channels == 1 1183 @channel_rev_gains[0].push(time, 1.0 / (dist ** @reverb_power)) 1184 elsif @rev_channels == 4 1185 ratt = (1.0 / dist) ** @reverb_power 1186 @channel_rev_gains[0].push(time, Point707 * ratt) 1187 @channel_rev_gains[1].push(time, (y / dist) * ratt) 1188 @channel_rev_gains[2].push(time, (-x / dist) * ratt) 1189 @channel_rev_gains[3].push(time, (z / dist) * ratt) 1190 end 1191 elsif dist.zero? 1192 @channel_gains[0].push(time, 1.0) 1193 (1..3).each do |i| @channel_gains[i].push(time, 0.0) end 1194 if @rev_channels >= 1 1195 @channel_rev_gains[0].push(time, 1.0) 1196 end 1197 if @rev_channels == 4 1198 (1..3).each do |i| @channel_rev_gains[i].push(time, 0.0) end 1199 end 1200 else 1201 att = dist ** (1.0 / @output_power) 1202 @channel_gains[0].push(time, 1.0 - (1.0 - Point707) * dist ** @output_power) 1203 @channel_gains[1].push(time, (y / dist) * att) 1204 @channel_gains[2].push(time, (-x / dist) * att) 1205 @channel_gains[3].push(time, (z / dist) * att) 1206 if @rev_channels == 1 1207 @channel_rev_gains[0].push(time, 1.0 - dist ** (1.0 / @reverb_power)) 1208 elsif @rev_channels == 4 1209 ratt = dist ** (1.0 / @reverb_power) 1210 @channel_rev_gains[0].push(time, 1.0 - (1.0 - Point707) * dist ** @reverb_power) 1211 @channel_rev_gains[1].push(time, (y / dist) * ratt) 1212 @channel_rev_gains[2].push(time, (-x / dist) * ratt) 1213 @channel_rev_gains[3].push(time, (z / dist) * ratt) 1214 end 1215 end 1216 end 1217 1218 def decoded_ambisonics(x, y, z, dist, time) 1219 if dist > 1.0 1220 att = (1.0 / dist) ** @output_power 1221 attw = Point707 * Point707 * att 1222 attx = att * (x / dist) 1223 atty = att * (y / dist) 1224 attz = att * (z / dist) 1225 @coords.each_with_index do |s, i| 1226 @channel_gains[i].push(time, Point707 * (attw + attx * s[0] + atty * s[1] + attz * s[2])) 1227 end 1228 if @rev_channels == 1 1229 @channel_rev_gains[0].push(time, 1.0 / (dist ** @reverb_power)) 1230 elsif @rev_channels == 4 1231 ratt = (1.0 / dist) ** @reverb_power 1232 rattw = Point707 * Point707 * ratt 1233 rattx = ratt * (x / dist) 1234 ratty = ratt * (y / dist) 1235 rattz = ratt * (z / dist) 1236 @rev_channels.times do |i| 1237 s = @coords[i] 1238 @channel_rev_gains[i].push(time, Point707 * \ 1239 (rattw + rattx * s[0] + ratty * s[1] + rattz * s[2])) 1240 end 1241 end 1242 elsif dist.zero? 1243 att = Point707 * Point707 1244 @coords.each_index do |i| @channel_gains[i].push(time, att) end 1245 if @rev_channels == 1 1246 @channel_rev_gains[0].push(time, 1.0) 1247 else 1248 @rev_channels.times do |i| @channel_rev_gains[i].push(time, att) end 1249 end 1250 else 1251 att = dist ** (1.0 / @output_power) 1252 attw = Point707 * (1.0 - (1.0 - Point707) * dist ** @output_power) 1253 attx = att * (x / dist) 1254 atty = att * (y / dist) 1255 attz = att * (z / dist) 1256 @coords.each_with_index do |s, i| 1257 @channel_gains[i].push(time, Point707 * (attw + attx * s[0] + atty * s[1] + attz * s[2])) 1258 end 1259 if @rev_channels == 1 1260 @channel_rev_gains[0].push(time, 1.0 - dist ** (1.0 / @reverb_power)) 1261 elsif @rev_channels == 4 1262 ratt = dist ** (1.0 / @reverb_power) 1263 rattw = Point707 * (1.0 - (1.0 - Point707) * dist ** @reverb_power) 1264 rattx = ratt * (x / dist) 1265 ratty = ratt * (y / dist) 1266 rattz = ratt * (z / dist) 1267 @rev_channels.times do |i| 1268 s = @coords[i] 1269 @channel_rev_gains[i].push(time, Point707 * \ 1270 (rattw + rattx * s[0] + ratty * s[1] + rattz * s[2])) 1271 end 1272 end 1273 end 1274 end 1275 1276 def walk_all_rooms(x, y, z, time) 1277 dist = distance(x, y, z) 1278 if @first_dist.zero? 1279 @first_dist = dist 1280 end 1281 @last_dist = dist 1282 if @min_dist.zero? or dist < @min_dist 1283 @min_dist = dist 1284 end 1285 if @max_dist.zero? or dist > @max_dist 1286 @max_dist = dist 1287 end 1288 @delay.push(time, dist2samples(dist)) 1289 case @render_using 1290 when Amplitude_panning 1291 amplitude_panning(x, y, z, dist, time) 1292 when B_format_ambisonics 1293 b_format_ambisonics(x, y, z, dist, time) 1294 when Decoded_ambisonics 1295 decoded_ambisonics(x, y, z, dist, time) 1296 end 1297 end 1298 1299 def change_direction(xa, ya, za, ta, xb, yb, zb, tb) 1300 walk_all_rooms(xa, ya, za, ta) 1301 if xa != xb or ya != yb or za != zb or ta != tb 1302 xi, yi, zi = nearest_point(xa, ya, za, xb, yb, zb, 0.0, 0.0, 0.0) 1303 if (((xa < xb) ? (xa <= xi and xi <= xb) : (xb <= xi and xi <= xa)) and 1304 ((ya < yb) ? (ya <= yi and yi <= yb) : (yb <= yi and yi <= ya)) and 1305 ((za < zb) ? (za <= zi and zi <= zb) : (zb <= zi and zi <= za))) 1306 walk_all_rooms(xi, yi, zi, 1307 tb + (ta - tb) * (distance(xb - xi, yb - yi, zb - zi) / \ 1308 distance(xb - xa, yb - ya, zb - za))) 1309 end 1310 end 1311 end 1312 1313 def intersects_inside_radius(xa, ya, za, ta, xb, yb, zb, tb) 1314 mag = distance(xb - xa, yb - ya, zb - za) 1315 vx = (xb - xa) / mag 1316 vy = (yb - ya) / mag 1317 vz = (zb - za) / mag 1318 bsq = xa * vx + ya * vy + za * vz 1319 disc = bsq * bsq - ((xa * xa + ya * ya + za * za) - 1.0) 1320 if disc >= 0.0 1321 root = sqrt(disc) 1322 rin = -bsq - root 1323 rout = -bsq + root 1324 xi = xo = nil 1325 if rin > 0 and rin < mag 1326 xi = xa + vx * rin 1327 yi = ya + vy * rin 1328 zi = za + vz * rin 1329 ti = tb + (ta - tb) * 1330 (distance(xb - xi, yb - yi, zb - zi) / distance(xb - xa, yb - ya, zb - za)) 1331 end 1332 if rout > 0 and rout.abs < mag 1333 xo = xa + vx * rout 1334 yo = ya + vy * rout 1335 zo = za + vz * rout 1336 to = tb + (ta - tb) * 1337 (distance(xb - xo, yb - yo, zb - zo) / distance(xb - xa, yb - ya, zb - za)) 1338 end 1339 if xi 1340 change_direction(xa, ya, za, ta, xi, yi, zi, ti) 1341 if xo 1342 change_direction(xi, yi, zi, ti, xo, yo, zo, to) 1343 change_direction(xo, yo, zo, to, xb, yb, zb, tb) 1344 else 1345 change_direction(xi, yi, zi, ti, xb, yb, zb, tb) 1346 end 1347 else 1348 if xo 1349 change_direction(xa, ya, za, ta, xo, yo, zo, to) 1350 change_direction(xo, yo, zo, to, xb, yb, zb, tb) 1351 else 1352 change_direction(xa, ya, za, ta, xb, yb, zb, tb) 1353 end 1354 end 1355 else 1356 change_direction(xa, ya, za, ta, xb, yb, zb, tb) 1357 end 1358 end 1359 1360 def minimum_segment_length(xa, ya, za, ta, xb, yb, zb, tb) 1361 if distance(xb - xa, yb - ya, zb - za) < 1.0 1362 intersects_inside_radius(xa, ya, za, ta, xb, yb, zb, tb) 1363 else 1364 xi = (xa + xb) * 0.5 1365 yi = (ya + yb) * 0.5 1366 zi = (za + zb) * 0.5 1367 ti = tb + (ta - tb) * 1368 (distance(xb - xi, yb - yi, zb - zi) / distance(xb - xa, yb - ya, zb - za)) 1369 minimum_segment_length(xa, ya, za, ta, xi, yi, zi, ti) 1370 minimum_segment_length(xi, yi, zi, ti, xb, yb, zb, tb) 1371 end 1372 end 1373 end 1374 1375 class Path < Dlocsig_base 1376 def initialize 1377 super 1378 @rx = @ry = @rz = @rv = @rt = @tx = @ty = @tz = @tt = nil 1379 @gnuplot = @sndplot = nil 1380 end 1381 1382 def path_x 1383 (@tx or (@rx or (render_path(); @rx))) 1384 end 1385 1386 def path_y 1387 (@ty or (@ry or (render_path(); @ry))) 1388 end 1389 1390 def path_z 1391 (@tz or (@rz or (render_path(); @rz))) 1392 end 1393 1394 def path_time 1395 (@tt or (@rt or (render_path(); @rt))) 1396 end 1397 1398 def scale_path(scaling) 1399 assert_type((number?(scaling) or array?(scaling)), 0, scaling, "a number or an array") 1400 if number?(scaling) then scaling = [scaling, scaling, scaling] end 1401 transform_path(:scaling, scaling) 1402 end 1403 1404 def translate_path(translation) 1405 assert_type((number?(translation) or array?(translation)), 1406 0, translation, "a number or an array") 1407 if number?(translation) then translation = [translation, translation, translation] end 1408 transform_path(:translation, translation) 1409 end 1410 1411 def rotate_path(rotation, *args) 1412 assert_type(number?(rotation), 0, rotation, "a number") 1413 rotation_center, rotation_axis = nil 1414 optkey(args, binding, 1415 :rotation_center, 1416 [:rotation_axis, [0.0, 0.0, 1.0]]) 1417 transform_path(:rotation, rotation, 1418 :rotation_center, rotation_center, 1419 :rotation_axis, rotation_axis) 1420 end 1421 1422 def path_trajectory 1423 path_x.map_with_index do |d, i| [d, path_y[i], path_z[i]] end.flatten 1424 end 1425 1426 def path_2d_trajectory 1427 path_x.map_with_index do |d, i| [d, path_y[i]] end.flatten 1428 end 1429 1430 # if velocity is zero, ti == tf 1431 Secure_distance = 0.0001 1432 def path_velocity 1433 xp, yp, zp, tp = path_x, path_y, path_z, path_time 1434 (0...(tp.length - 1)).map do |i| 1435 xi, xf = xp[i, 2] 1436 yi, yf = yp[i, 2] 1437 zi, zf = zp[i, 2] 1438 ti, tf = tp[i, 2] 1439 if tf == ti 1440 tf += Secure_distance 1441 end 1442 [(ti + tf) / 2.0, distance(xf - xi, yf - yi, zf - zi) / (tf - ti)] 1443 end.flatten 1444 end 1445 1446 def path_doppler 1447 xp, yp, zp, tp = path_x, path_y, path_z, path_time 1448 (0...(tp.length - 1)).map do |i| 1449 xi, xf = xp[i, 2] 1450 yi, yf = yp[i, 2] 1451 zi, zf = zp[i, 2] 1452 ti, tf = tp[i, 2] 1453 if tf == ti 1454 tf += Secure_distance 1455 end 1456 [(tf + ti) / 2.0, -((distance(xf, yf, zf) - distance(xi, yi, zi)) / (tf - ti))] 1457 end.flatten 1458 end 1459 1460 def path_acceleration 1461 v = path_velocity() 1462 result = [] 1463 0.step(v.length - 3, 2) do |i| 1464 ti, vi, tf, vf = v[i, 4] 1465 if tf == ti 1466 tf += Secure_distance 1467 end 1468 am = (vf - vi) / (tf - ti) 1469 result << ti << am << tf << am 1470 end 1471 result 1472 end 1473 1474 # Gnuplot 1475 def plot_open 1476 if @gnuplot.kind_of?(Gnuplot) 1477 @gnuplot 1478 else 1479 @gnuplot = Gnuplot.new 1480 end 1481 end 1482 1483 def plot_close 1484 @gnuplot.close if @gnuplot.kind_of?(Gnuplot) 1485 end 1486 1487 def cmd(*args) 1488 @gnuplot = plot_open 1489 @gnuplot.command(format(*args) << "\n") 1490 end 1491 1492 def plot_trajectory(*args) 1493 @gnuplot = plot_open 1494 label, reset = nil 1495 optkey(args, binding, 1496 [:label, "trajectory"], 1497 [:reset, true]) 1498 @gnuplot.reset() if reset 1499 @gnuplot.set_autoscale() 1500 if path_z.detect do |z| z.nonzero? end 1501 @gnuplot.plot_3d_curve(path_trajectory(), :label, label, *args) 1502 else 1503 @gnuplot.plot_2d_curve(path_2d_trajectory(), :label, label, *args) 1504 end 1505 end 1506 1507 def plot_velocity(reset = true) 1508 @gnuplot = plot_open 1509 @gnuplot.reset() if reset 1510 @gnuplot.set_autoscale() 1511 @gnuplot.plot_2d_curve(path_velocity(), :label, "velocity", :style, "steps") 1512 end 1513 1514 def plot_doppler(reset = true) 1515 @gnuplot = plot_open 1516 @gnuplot.reset() if reset 1517 @gnuplot.set_autoscale() 1518 @gnuplot.plot_2d_curve(path_doppler(), :label, "doppler", :style, "steps") 1519 end 1520 1521 def plot_acceleration(reset = true) 1522 @gnuplot = plot_open 1523 @gnuplot.reset() if reset 1524 @gnuplot.set_autoscale() 1525 @gnuplot.plot_2d_curve(path_acceleration(), :label, "acceleration", :style, "steps") 1526 end 1527 1528 def pplot(normalize = true) 1529 @gnuplot = plot_open 1530 norm = lambda do |env, nrm| 1531 unless nrm 1532 env 1533 else 1534 mx = env.each_pair do |x, y| y end.max 1535 if mx.zero? 1536 env 1537 else 1538 env.each_pair do |x, y| [x, y / mx.to_f] end.flatten 1539 end 1540 end 1541 end 1542 @gnuplot.reset() 1543 @gnuplot.size(0, 0, 1, 1) 1544 @gnuplot.start_multiplot() 1545 @gnuplot.size(0.0, 0.333, 1.0, 0.667) 1546 plot_trajectory(:reset, false) 1547 @gnuplot.size(0.0, 0.0, 1.0, 0.333) 1548 @gnuplot.plot_2d_curves([norm.call(path_velocity(), normalize), 1549 norm.call(path_acceleration(), normalize), 1550 norm.call(path_doppler(), normalize)], 1551 :labels, ["velocity", "acceleration", "doppler"], 1552 :styles, ["steps", "steps", "steps"]) 1553 @gnuplot.end_multiplot() 1554 end 1555 1556 # Sndplot 1557 def snd_open(chns = 1) 1558 if @sndplot.kind_of?(Sndplot) 1559 @sndplot 1560 else 1561 @sndplot = Sndplot.new(chns) 1562 end 1563 end 1564 1565 def snd_close 1566 @sndplot.close if @sndplot.kind_of?(Sndplot) 1567 end 1568 1569 def snd_trajectory(chn = 0, label = "trajectory") 1570 @sndplot = snd_open 1571 graph(path_2d_trajectory(), label, false, false, false, false, @sndplot.snd, chn) 1572 @sndplot 1573 end 1574 1575 def snd_velocity(chn = 0, label = "velocity") 1576 @sndplot = snd_open 1577 graph(path_velocity(), label, false, false, false, false, @sndplot.snd, chn) 1578 @sndplot 1579 end 1580 1581 def snd_doppler(chn = 0, label = "doppler") 1582 @sndplot = snd_open 1583 graph(path_doppler(), label, false, false, false, false, @sndplot.snd, chn) 1584 @sndplot 1585 end 1586 1587 def snd_acceleration(chn = 0, label = "acceleration") 1588 @sndplot = snd_open 1589 graph(path_acceleration(), label, false, false, false, false, @sndplot.snd, chn) 1590 @sndplot 1591 end 1592 1593 def snd_plot 1594 @sndplot = snd_open(4) 1595 snd_trajectory(0) 1596 snd_velocity(1) 1597 snd_acceleration(2) 1598 snd_doppler(3) 1599 @sndplot 1600 end 1601 1602 private 1603 def transform_path(*args) 1604 scaling, translation, rotation, rotation_center, rotation_axis = nil 1605 optkey(args, binding, 1606 :scaling, 1607 :translation, 1608 :rotation, 1609 :rotation_center, 1610 [:rotation_axis, [0.0, 0.0, 1.0]]) 1611 render_path() if @rx.nil? 1612 if scaling or translation or rotation 1613 rotation = TWO_PI * (rotation / @one_turn) if rotation 1614 if rotation_axis and (rotation_axis.length != 3) 1615 dl_error(rotation_axis, "rotation axis has to have all three coordinates") 1616 end 1617 matrix = if rotation 1618 rotation_matrix(rotation_axis[0], rotation_axis[1], rotation_axis[2], rotation) 1619 end 1620 xc = path_x() 1621 yc = path_y() 1622 zc = path_z() 1623 if rotation_center and (rotation_center.length != 3) 1624 dl_error(rotation_center, "rotation center has to have all three coordinates") 1625 end 1626 xtr = [] 1627 ytr = [] 1628 ztr = [] 1629 xc.each_with_index do |x, i| 1630 y = yc[i] 1631 z = zc[i] 1632 xw, yw, zw = x, y, z 1633 if rotation_center and rotation 1634 xw -= rotation_center[0] 1635 yw -= rotation_center[1] 1636 zw -= rotation_center[2] 1637 end 1638 if rotation 1639 mc = [xw, yw, zw] 1640 xv, yv, zv = matrix.column_vectors 1641 xr = xv.to_a.map_with_index do |xx, ii| xx * mc[ii] end.to_a.sum 1642 yr = yv.to_a.map_with_index do |xx, ii| xx * mc[ii] end.to_a.sum 1643 zr = zv.to_a.map_with_index do |xx, ii| xx * mc[ii] end.to_a.sum 1644 xw, yw, zw = xr, yr, zr 1645 end 1646 if rotation_center and rotation 1647 xw += rotation_center[0] 1648 yw += rotation_center[1] 1649 zw += rotation_center[2] 1650 end 1651 if scaling 1652 xw *= scaling[0] 1653 yw *= scaling[1] if scaling[1] 1654 zw *= scaling[2] if scaling[2] 1655 end 1656 if translation 1657 xw += translation[0] 1658 yw += translation[1] if translation[1] 1659 zw += translation[2] if translation[2] 1660 end 1661 xtr << xw 1662 ytr << yw 1663 ztr << zw 1664 end 1665 @tx, @ty, @tz = xtr, ytr, ztr 1666 else 1667 @tt = @rt.dup 1668 @tx = @rx.dup 1669 @ty = @ry.dup 1670 @tz = @rz.dup 1671 end 1672 end 1673 1674 def reset_transformation 1675 @tt = @tx = @ty = @tz = nil 1676 end 1677 1678 def reset_rendering 1679 @rt = @rv = @rx = @ry = @rz = nil 1680 reset_transformation() 1681 end 1682 1683 def parse_cartesian_coordinates(points, d3) 1684 if array?(points[0]) 1685 x = points.map do |p| p[0] end 1686 y = points.map do |p| p[1] end 1687 z = points.map do |p| d3 ? (p[2] or 0.0) : 0.0 end 1688 v = points.map do |p| d3 ? p[3] : p[2] end 1689 [x, y, z, v] 1690 else 1691 if d3 1692 x = [] 1693 y = [] 1694 z = [] 1695 0.step(points.length - 3, 3) do |i| 1696 x += [points[i]] 1697 y += [points[i + 1]] 1698 z += [points[i + 2]] 1699 end 1700 [x, y, z, x.map do |i| nil end] 1701 else 1702 x = [] 1703 y = [] 1704 0.step(points.length - 2, 2) do |i| 1705 x += [points[i]] 1706 y += [points[i + 1]] 1707 end 1708 [x, y, x.map do |i| 0.0 end, x.map do |i| nil end] 1709 end 1710 end 1711 end 1712 1713 def parse_polar_coordinates(points, d3) 1714 if array?(points[0]) 1715 x = [] 1716 y = [] 1717 z = [] 1718 v = [] 1719 points.each do |p| 1720 d = p[0] 1721 a = p[1] 1722 e = (d3 ? (p[2] or 0.0) : 0.0) 1723 evec = cis((e / @one_turn) * TWO_PI) 1724 dxy = d * evec.real 1725 avec = cis((a / @one_turn) * TWO_PI) 1726 z << (d * evec.imag) 1727 x << (dxy * avec.imag) 1728 y << (dxy * avec.real) 1729 v << (d3 ? p[3] : p[2]) 1730 end 1731 [x, y, z, v] 1732 else 1733 if d3 1734 x = [] 1735 y = [] 1736 z = [] 1737 0.step(points.length - 1, 3) do |i| 1738 d, a, e = points[i, 3] 1739 evec = cis((e / @one_turn) * TWO_PI) 1740 dxy = (d * evec.real) 1741 avec = cis((a / @one_turn) * TWO_PI) 1742 z << (d * evec.imag) 1743 x << (dxy * avec.imag) 1744 y << (dxy * avec.real) 1745 end 1746 [x, y, z, x.map do |i| nil end] 1747 else 1748 x = [] 1749 y = [] 1750 0.step(points.length - 1, 2) do |i| 1751 d, a = points[i, 2] 1752 avec = cis((a / @one_turn) * TWO_PI) 1753 x << (d * avec.imag) 1754 y << (d * avec.real) 1755 end 1756 [x, y, x.map do |i| 0.0 end, x.map do |i| nil end] 1757 end 1758 end 1759 end 1760 end 1761 1762 class Bezier_path < Path 1763 def initialize(path, *args) 1764 @path = path 1765 if (not @path) or (array?(@path) and @path.empty?) 1766 dl_error("can't define a path with no points in it") 1767 end 1768 super() 1769 d3, polar, error, curvature = nil 1770 optkey(args, binding, 1771 [:d3, true], 1772 [:polar, false], 1773 [:error, 0.01], 1774 :curvature) 1775 @d3 = d3 1776 @polar = polar 1777 @error = error 1778 @curvature = curvature 1779 @x = @y = @z = @v = @bx = @by = @bz = nil 1780 # for ac() and a() 1781 @path_ak_even = nil 1782 @path_ak_odd = nil 1783 @path_gtab = nil 1784 @path_ftab = nil 1785 end 1786 1787 private 1788 def parse_path 1789 if @polar 1790 @x, @y, @z, @v = parse_polar_coordinates(@path, @d3) 1791 else 1792 @x, @y, @z, @v = parse_cartesian_coordinates(@path, @d3) 1793 end 1794 if @v[0] and @v.min < 0.1 1795 if @v.min < 0.0 1796 Snd.warning("%s#%s: velocities must be all positive, corrected", 1797 self.class, get_func_name) 1798 end 1799 @v.map! do |x| [0.1, x].max end 1800 end 1801 @bx = @by = @bz = nil 1802 reset_rendering() 1803 end 1804 1805 def fit_path 1806 parse_path() if @x.nil? 1807 end 1808 1809 def bezier_point(u, c) 1810 u1 = 1.0 - u 1811 cr = make_array(3) do |i| make_array(3) do |j| u1 * c[i][j] + u * c[i][j + 1] end end 1812 1.downto(0) do |i| 1813 0.upto(i) do |j| 1814 3.times do |k| cr[k][j] = u1 * cr[k][j] + u * cr[k][j + 1] end 1815 end 1816 end 1817 [cr[0][0], cr[1][0], cr[2][0]] 1818 end 1819 1820 def berny(xl, yl, zl, xh, yh, zh, ul, u, uh, c) 1821 x, y, z = bezier_point(u, c) 1822 xn, yn, zn = nearest_point(xl, yl, zl, xh, yh, zh, x, y, z) 1823 if distance(xn - x, yn - y, zn - z) > @error 1824 xi, yi, zi = berny(xl, yl, zl, x, y, z, ul, (ul + u) / 2.0, u, c) 1825 xj, yj, zj = berny(x, y, z, xh, yh, zh, u, (u + uh) / 2.0, uh, c) 1826 [xi + [x] + xj, yi + [y] + yj, zi + [z] + zj] 1827 else 1828 [[], [], []] 1829 end 1830 end 1831 1832 def render_path 1833 fit_path() if @bx.nil? 1834 rx = [] 1835 ry = [] 1836 rz = [] 1837 rv = [] 1838 if (not @v[0]) or @v[0].zero? 1839 @v[0] = 1.0 1840 @v[-1] = 1.0 1841 end 1842 if @x.length == 1 1843 @rx = @x 1844 @ry = @y 1845 @rz = @z 1846 @rt = [0.0] 1847 return 1848 end 1849 xf_bz = yf_bz = zf_bz = vf_bz = 0.0 1850 (@v.length - 1).times do |i| 1851 x_bz = @bx[i] 1852 y_bz = @by[i] 1853 z_bz = @bz[i] 1854 vi_bz, vf_bz = @v[i, 2] 1855 xi_bz = x_bz[0] 1856 xf_bz = x_bz[-1] 1857 yi_bz = y_bz[0] 1858 yf_bz = y_bz[-1] 1859 zi_bz = z_bz[0] 1860 zf_bz = z_bz[-1] 1861 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]) 1862 rx += [xi_bz] + xs 1863 ry += [yi_bz] + ys 1864 rz += [zi_bz] + zs 1865 rv += [vi_bz] + xs.map do nil end 1866 end 1867 rx << xf_bz 1868 ry << yf_bz 1869 rz << zf_bz 1870 rv << vf_bz 1871 xseg = [rx[0]] 1872 yseg = [ry[0]] 1873 zseg = [rz[0]] 1874 vi = rv[0] 1875 ti = 0.0 1876 times = [ti] 1877 (1...rx.length).each do |i| 1878 x = rx[i] 1879 y = ry[i] 1880 z = rz[i] 1881 v = rv[i] 1882 xseg << x 1883 yseg << y 1884 zseg << z 1885 if v 1886 sofar = 0.0 1887 dseg = (0...xseg.length - 1).map do |j| 1888 xsi, xsf = xseg[j, 2] 1889 ysi, ysf = yseg[j, 2] 1890 zsi, zsf = zseg[j, 2] 1891 sofar += distance(xsf - xsi, ysf - ysi, zsf - zsi) 1892 end 1893 df = dseg[-1] 1894 vf = v 1895 aa = ((vf - vi) * (vf + vi)) / (df * 4.0) 1896 tseg = dseg.map do |d| 1897 ti + (if vi and vi.nonzero? and vf == vi 1898 d / vi 1899 elsif aa.nonzero? 1900 ((vi * vi + 4.0 * aa * d) ** 0.5 - vi) / (2.0 * aa) 1901 else 1902 0.0 1903 end) 1904 end 1905 times += tseg 1906 xseg = [x] 1907 yseg = [y] 1908 zseg = [z] 1909 vi = v 1910 ti = tseg[-1] 1911 end 1912 end 1913 @rx = rx 1914 @ry = ry 1915 @rz = rz 1916 tf = times[-1] 1917 @rt = times.map do |tii| tii / tf end 1918 reset_transformation() 1919 end 1920 1921 # called in Closed_bezier_path#calculate_fit 1922 def a(k, n) 1923 if ([Path_maxcoeff * 2.0 + 1, n].min).odd? 1924 make_a_odd() unless @path_ak_odd 1925 @path_ak_odd[(n - 3) / 2][k - 1] 1926 else 1927 make_a_even() unless @path_ak_even 1928 @path_ak_even[(n - 4) / 2][k - 1] 1929 end 1930 end 1931 1932 # called in Open_bezier_path#calculate_fit 1933 def ac(k, n) 1934 n = [n, Path_maxcoeff].min 1935 make_a_even() unless @path_ak_even 1936 @path_ak_even[n - 2][k - 1] 1937 end 1938 1939 def make_a_even 1940 g = lambda do |m| 1941 @path_gtab = make_array(Path_maxcoeff) unless @path_gtab 1942 @path_gtab[0] = 1.0 1943 @path_gtab[1] = -4.0 1944 (2...Path_maxcoeff).each do |i| 1945 @path_gtab[i] = -4.0 * @path_gtab[i - 1] - @path_gtab[i - 2] 1946 end 1947 @path_gtab[m] 1948 end 1949 @path_ak_even = make_array(Path_maxcoeff - 1) 1950 (1...Path_maxcoeff).each do |m| 1951 @path_ak_even[m - 1] = make_array(m) 1952 (1..m).each do |k| 1953 @path_ak_even[m - 1][k - 1] = (-g.call(m - k) / g.call(m)).to_f 1954 end 1955 end 1956 end 1957 1958 def make_a_odd 1959 f = lambda do |m| 1960 @path_ftab = make_array(Path_maxcoeff) unless @path_ftab 1961 @path_ftab[0] = 1.0 1962 @path_ftab[1] = -3.0 1963 (2...Path_maxcoeff).each do |i| 1964 @path_ftab[i] = -4.0 * @path_ftab[i - 1] - @path_ftab[i - 2] 1965 end 1966 @path_ftab[m] 1967 end 1968 @path_ak_odd = make_array(Path_maxcoeff - 1) 1969 (1...Path_maxcoeff).each do |m| 1970 @path_ak_odd[m - 1] = make_array(m) 1971 (1..m).each do |k| 1972 @path_ak_odd[m - 1][k - 1] = (-f.call(m - k) / f.call(m)).to_f 1973 end 1974 end 1975 end 1976 end 1977 1978 class Open_bezier_path < Bezier_path 1979 def initialize(path, *args) 1980 super 1981 initial_direction, final_direction = nil 1982 optkey(args, binding, 1983 [:initial_direction, [0.0, 0.0, 0.0]], 1984 [:final_direction, [0.0, 0.0, 0.0]]) 1985 @initial_direction = initial_direction 1986 @final_direction = final_direction 1987 end 1988 1989 private 1990 def calculate_fit 1991 n = @x.length - 1 1992 m = n - 1 1993 p = [@x, @y, @z] 1994 d = make_array(3) do make_array(n + 1, 0.0) end 1995 d = Matrix[d[0], d[1], d[2]].to_a 1996 ref = lambda do |z, j, i| 1997 if i > n 1998 z[j][i - n] 1999 elsif i < 0 2000 z[j][i + n] 2001 elsif i == n 2002 z[j][n] - d[j][n] 2003 elsif i == 0 2004 z[j][0] + d[j][0] 2005 else 2006 z[j][i] 2007 end 2008 end 2009 d[0][0] = (@initial_direction[0] or 0.0) 2010 d[1][0] = (@initial_direction[1] or 0.0) 2011 d[2][0] = (@initial_direction[2] or 0.0) 2012 d[0][n] = (@final_direction[0] or 0.0) 2013 d[1][n] = (@final_direction[1] or 0.0) 2014 d[2][n] = (@final_direction[2] or 0.0) 2015 (1...n).each do |i| 2016 (1..[Path_maxcoeff - 1, m].min).each do |j| 2017 3.times do |k| 2018 d[k][i] = d[k][i] + ac(j, n) * (ref.call(p, k, i + j) - ref.call(p, k, i - j)) 2019 end 2020 end 2021 end 2022 [n, p, d] 2023 end 2024 2025 def fit_path 2026 parse_path() if @x.nil? 2027 case points = @x.length 2028 when 1 2029 @bx = @by = @bz = nil 2030 when 2 2031 x1, x2 = @x[0, 2] 2032 y1, y2 = @y[0, 2] 2033 z1, z2 = @z[0, 2] 2034 @bx = [[x1, x1, x2, x2]] 2035 @by = [[y1, y1, y2, y2]] 2036 @bz = [[z1, z1, z2, z2]] 2037 else 2038 n, p, d = calculate_fit() 2039 c = @curvature 2040 cs = make_array(n) 2041 if c.kind_of?(NilClass) or (array?(c) and c.empty?) 2042 n.times do |i| cs[i] = [1.0, 1.0] end 2043 elsif number?(c) 2044 n.times do |i| cs[i] = [c, c] end 2045 elsif array?(c) and c.length == n 2046 c.each_with_index do |ci, i| 2047 cs[i] = if array?(ci) 2048 if ci.length != 2 2049 dl_error(ci, "curvature sublist must have two elements") 2050 else 2051 ci 2052 end 2053 else 2054 [ci, ci] 2055 end 2056 end 2057 else 2058 dl_error(c, "bad curvature argument to path, need #{n} elements") 2059 end 2060 @bx = (0...n).map do |i| 2061 [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]] 2062 end 2063 @by = (0...n).map do |i| 2064 [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]] 2065 end 2066 @bz = (0...n).map do |i| 2067 [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]] 2068 end 2069 end 2070 reset_rendering() 2071 end 2072 end 2073 2074 class Closed_bezier_path < Bezier_path 2075 def initialize(path, *args) 2076 super 2077 end 2078 2079 private 2080 def calculate_fit 2081 n = @x.length - 1 2082 m = (n - (n.odd? ? 3 : 4)) / 2 2083 p = [@x, @y, @z] 2084 d = make_array(3) do make_array(n, 0.0) end 2085 ref = lambda do |z, j, i| 2086 if i > (n - 1) 2087 z[j][i - n] 2088 elsif i < 0 2089 z[j][i + n] 2090 else 2091 z[j][i] 2092 end 2093 end 2094 n.times do |i| 2095 (1..m).each do |j| 2096 3.times do |k| 2097 d[k][i] = d[k][i] + a(j, n) * (ref.call(p, k, i + j) - ref.call(p, k, i - j)) 2098 end 2099 end 2100 end 2101 if @curvature 2102 n.times do |i| 2103 curve = @curvature[i] 2104 d[0][i] *= curve 2105 d[1][i] *= curve 2106 d[2][i] *= curve 2107 end 2108 end 2109 [n - 1, p, d] 2110 end 2111 2112 def fit_path 2113 parse_path() if @x.nil? 2114 if @x.length > 4 2115 n, p, d = calculate_fit() 2116 xc = (0...n).map do |i| 2117 [p[0][i], p[0][i] + d[0][i], p[0][i + 1] - d[0][i + 1], p[0][i + 1]] 2118 end 2119 yc = (0...n).map do |i| 2120 [p[1][i], p[1][i] + d[1][i], p[1][i + 1] - d[1][i + 1], p[1][i + 1]] 2121 end 2122 zc = (0...n).map do |i| 2123 [p[2][i], p[2][i] + d[2][i], p[2][i + 1] - d[2][i + 1], p[2][i + 1]] 2124 end 2125 @bx = xc + [[p[0][n], p[0][n] + d[0][n], p[0][0] - d[0][0], p[0][0]]] 2126 @by = yc + [[p[1][n], p[1][n] + d[1][n], p[1][0] - d[1][0], p[1][0]]] 2127 @bz = zc + [[p[2][n], p[2][n] + d[2][n], p[2][0] - d[2][0], p[2][0]]] 2128 else 2129 xc = [] 2130 yc = [] 2131 zc = [] 2132 (@x.length - 1).times do |i| 2133 x1, x2 = @x[i, 2] 2134 y1, y2 = @y[i, 2] 2135 z1, z2 = @z[i, 2] 2136 xc << [x1, x1, x2, x2] 2137 yc << [y1, y1, y2, y2] 2138 zc << [z1, z1, z2, z2] 2139 end 2140 @bx = xc 2141 @by = yc 2142 @bz = zc 2143 end 2144 reset_rendering() 2145 end 2146 end 2147 2148 class Literal_path < Path 2149 def initialize(path, *args) 2150 @path = path 2151 if (not @path) or (array?(@path) and @path.empty?) 2152 dl_error("can't define a path with no points in it") 2153 end 2154 super() 2155 d3, polar = nil 2156 optkey(args, binding, 2157 [:d3, true], 2158 [:polar, false]) 2159 @d3 = d3 2160 @polar = polar 2161 end 2162 2163 private 2164 def render_path 2165 if @polar 2166 @rx, @ry, @rz, @rv = parse_polar_coordinates(@path, @d3) 2167 else 2168 @rx, @ry, @rz, @rv = parse_cartesian_coordinates(@path, @d3) 2169 end 2170 if (not @rv[0]) or @rv[0].zero? 2171 @rv[0] = 1.0 2172 @rv[-1] = 1.0 2173 end 2174 if @rx.length == 1 2175 @rt = [0.0] 2176 return 2177 end 2178 rx = @rx 2179 ry = @ry 2180 rz = @rz 2181 rv = @rv 2182 xseg = [rx[0]] 2183 yseg = [ry[0]] 2184 zseg = [rz[0]] 2185 vi = rv[0] 2186 ti = 0.0 2187 times = [ti] 2188 (1...rx.length).each do |i| 2189 x = rx[i] 2190 y = ry[i] 2191 z = rz[i] 2192 v = rv[i] 2193 xseg << x 2194 yseg << y 2195 zseg << z 2196 if v 2197 sofar = 0.0 2198 dseg = (0...xseg.length - 1).map do |j| 2199 xsi, xsf = xseg[j, 2] 2200 ysi, ysf = yseg[j, 2] 2201 zsi, zsf = zseg[j, 2] 2202 sofar += distance(xsf - xsi, ysf - ysi, zsf - zsi) 2203 end 2204 df = dseg[-1] 2205 vf = v 2206 aa = ((vf - vi) * (vf + vi)) / (df * 4.0) 2207 tseg = dseg.map do |d| 2208 ti + (if vi and vi.nonzero? and vf == vi 2209 d / vi 2210 elsif aa.nonzero? 2211 ((vi * vi + 4.0 * aa * d) ** 0.5 - vi) / (2.0 * aa) 2212 else 2213 0.0 2214 end) 2215 end 2216 times += tseg 2217 xseg = [x] 2218 yseg = [y] 2219 zseg = [z] 2220 vi = v 2221 ti = tseg[-1] 2222 end 2223 end 2224 tf = times[-1] 2225 @rt = times.map do |tii| tii / tf end 2226 reset_transformation() 2227 end 2228 end 2229 2230 class Spiral_path < Literal_path 2231 def initialize(*args) 2232 # to fool Literal_path.new 2233 super([nil]) 2234 start_angle, turns = nil 2235 optkey(args, binding, 2236 [:start_angle, 0], 2237 [:turns, 2]) 2238 @start_angle = start_angle 2239 @turns = turns 2240 end 2241 2242 private 2243 def render_path 2244 start = (@start_angle / @one_turn.to_f) * TWO_PI 2245 total = (@turns.zero? ? TWO_PI : (@turns * TWO_PI)) 2246 step_angle = @one_turn / 100.0 2247 steps = (total / ((step_angle / @one_turn.to_f) * TWO_PI)).abs 2248 step = total / (steps.ceil * (step_angle < 0 ? -1 : 1)) 2249 x = [] 2250 y = [] 2251 z = [] 2252 (total / step).round.abs.times do 2253 xy = cis(start) 2254 x << (10.0 * xy.imag) 2255 y << (10.0 * xy.real) 2256 z << 0.0 2257 start += step 2258 end 2259 sofar = 0.0 2260 dp = (0...x.length - 1).map do |i| 2261 xi, xf = x[i, 2] 2262 yi, yf = y[i, 2] 2263 zi, zf = z[i, 2] 2264 sofar += distance(xf - xi, yf - yi, zf - zi) 2265 end 2266 td = 0.0 2267 times = (0...dp.length - 1).map do |i| 2268 di, df = dp[i, 2] 2269 td = td + (df - di) / 4.0 2270 end 2271 @rx = x 2272 @ry = y 2273 @rz = z 2274 tf = times[-1] 2275 @rt = times.map do |ti| ti / tf end 2276 reset_transformation() 2277 end 2278 end 2279 2280 module_function 2281 def make_dlocsig(start, dur, *args) 2282 dl = Dlocsig.new 2283 dl.make_dlocsig(start, dur, *args) 2284 dl 2285 end 2286 2287 def dlocsig(dl, dloc, input) 2288 dl.dlocsig(dloc, input) 2289 end 2290 2291 def make_path(path, *args) 2292 Open_bezier_path.new(path, *args) 2293 end 2294 2295 def make_polar_path(path, *args) 2296 Open_bezier_path.new(path, :polar, true, *args) 2297 end 2298 2299 def make_closed_path(path, *args) 2300 d3 = nil 2301 optkey(args, binding, [:d3, true]) 2302 len = d3 ? 3 : 2 2303 unless path[0][0, len] == path[-1][0, len] 2304 path += [path[0]] 2305 end 2306 Closed_bezier_path.new(path, *args) 2307 end 2308 2309 def make_literal_path(path, *args) 2310 Literal_path.new(path, *args) 2311 end 2312 2313 def make_literal_polar_path(path, *args) 2314 Literal_path.new(path, :polar, true, *args) 2315 end 2316 2317 def make_spiral_path(*args) 2318 Spiral_path.new(*args) 2319 end 2320end 2321 2322# example functions (see clm-2/dlocsig/move-sound.ins and 2323# clm-2/dlocsig/dlocsig.html) 2324 2325class Instrument 2326 def sinewave(start, dur, freq, amp, path, amp_env = [0, 1, 1, 1], *dlocsig_args) 2327 os = make_oscil(:frequency, freq) 2328 en = make_env(:envelope, amp_env, :scaler, amp, :duration, dur) 2329 run_dlocsig(start, dur, :path, path, *dlocsig_args) do 2330 env(en) * oscil(os) 2331 end 2332 end 2333 2334 def move(start, file, path, *dlocsig_args) 2335 dl_error(path, "need a path") unless path.kind_of?(DL::Path) 2336 dur = ws_duration(file) 2337 chns = ws_channels(file) 2338 rds = make_array(chns) do |chn| make_ws_reader(file, :start, start, :channel, chn) end 2339 run_dlocsig(start, dur, :path, path, *dlocsig_args) do 2340 rds.map do |rd| ws_readin(rd) end.sum / chns 2341 end 2342 end 2343 2344 add_help(:move_sound, 2345 "move_sound(path, *args) do ... end 2346 sound_let-args: 2347 :channels, 1 # channels to move 2348 start time in output file: 2349 :startime, 0 2350 rest args: make_dlocsig") 2351 def move_sound(path, *args, &body) 2352 chns = get_shift_args(args, :channels, 1) 2353 start = get_shift_args(args, :startime, 0) 2354 sound_let([:channels, chns, body]) do |to_move| 2355 if @verbose 2356 Snd.display("%s: moving sound on %d channel%s", get_func_name, chns, (chns > 1 ? "s" : "")) 2357 end 2358 move(0, to_move, path, *args) 2359 rbm_mix(to_move, :output_frame, seconds2samples(start)) 2360 end 2361 end 2362end 2363 2364class With_sound 2365 def run_dlocsig(start, dur, *dlocsig_args, &body) 2366 with_sound_info(get_func_name(2), start, dur) 2367 dl = DL.make_dlocsig(start, dur, 2368 :clm, @clm, 2369 :rbm_output, @ws_output, 2370 :rbm_reverb, @ws_reverb, 2371 :out_channels, @channels, 2372 :rev_channels, @reverb_channels, 2373 *dlocsig_args) 2374 dl.ws_dlocsig(&body) 2375 end 2376end 2377 2378# Dlocsig menu 2379# 2380# require 'snd-xm' 2381# 2382# make_snd_menu("Dlocsig") do 2383# cascade("Dlocsig (Snd)") do 2384# entry(Dlocsig_bezier, "Bezier path (Snd)", true) 2385# entry(Dlocsig_spiral, "Spiral path (Snd)", true) 2386# end 2387# cascade("Dlocsig (CLM)") do 2388# entry(Dlocsig_bezier, "Bezier path (CLM)", false) 2389# entry(Dlocsig_spiral, "Spiral path (CLM)", false) 2390# end 2391# end 2392 2393if provided? :snd_motif 2394 class Dlocsig_menu 2395 require "snd-xm" 2396 include Snd_XM 2397 require "xm-enved" 2398 include DL 2399 2400 def initialize(label, snd_p) 2401 @label = label 2402 @snd_p = snd_p 2403 @dialog = nil 2404 @out_chans = 4 2405 @rev_chans = 1 2406 @path = nil 2407 @sliders = [] 2408 @init_out_chans = 4 2409 @output_power = @init_power = 1.5 2410 @reverb_power = @init_rev_power = 0.5 2411 @render_using = Amplitude_panning 2412 end 2413 2414 private 2415 def with_sound_target(*comment_args) 2416 if @render_using == B_format_ambisonics 2417 set_scale_value(@sliders[0].scale, @out_chans = 4) 2418 end 2419 comment_string = if string?($clm_comment) and !$clm_comment.empty? 2420 format("%s; %s", $clm_comment, format(*comment_args)) 2421 else 2422 format(*comment_args) 2423 end 2424 snd_path, snd_name = File.split(file_name(selected_sound)) 2425 snd_name = snd_name.split("moved-").last 2426 snd_to_move = format("%s/%s", snd_path, snd_name) 2427 snd_moved = format("%s/moved-%s", snd_path, snd_name) 2428 path = @path 2429 output_power = @output_power 2430 reverb_power = @reverb_power 2431 render_using = @render_using 2432 f = with_sound(:clm, (not @snd_p), 2433 :output, snd_moved, 2434 :channels, @out_chans, 2435 :reverb_channels, @rev_chans, 2436 :comment, comment_string, 2437 :info, (not $clm_notehook), 2438 :statistics, true, 2439 :play, true) do 2440 move(0, 2441 snd_to_move, 2442 path, 2443 :output_power, output_power, 2444 :reverb_power, reverb_power, 2445 :render_using, render_using) 2446 end 2447 Snd.display(f.output.inspect) 2448 rescue 2449 Snd.warning("%s#%s: %s", self.class, get_func_name, comment_string) 2450 end 2451 2452 def add_with_sound_sliders(parent = @dialog.parent) 2453 @sliders << @dialog.add_slider("output channels", 2454 2, @init_out_chans, 8, 1, :linear, parent) do |w, c, i| 2455 @out_chans = get_scale_value(w, i).round 2456 end 2457 @sliders << @dialog.add_slider("output power", 2458 0, @init_power, 10, 100, :linear, parent) do |w, c, i| 2459 @output_power = get_scale_value(w, i, 100.0) 2460 end 2461 @sliders << @dialog.add_slider("reverb power", 2462 0, @init_rev_power, 10, 100, :linear, parent) do |w, c, i| 2463 @reverb_power = get_scale_value(w, i, 100.0) 2464 end 2465 end 2466 2467 def reset_with_sound_sliders(reverb_p = true) 2468 set_scale_value(@sliders[0].scale, @out_chans = @init_out_chans) 2469 @output_power = @init_power 2470 set_scale_value(@sliders[1].scale, @output_power, 100.0) 2471 @reverb_power = @init_rev_power 2472 set_scale_value(@sliders[2].scale, @reverb_power, 100.0) 2473 end 2474 2475 def add_with_sound_targets 2476 @dialog.add_target([["amplitude panning", :amplitude, true], 2477 ["b format ambisonics", :b_format, false], 2478 ["decoded ambisonics", :decoded, false]]) do |val| 2479 @render_using = case val 2480 when :amplitude 2481 Amplitude_panning 2482 when :b_format 2483 set_scale_value(@sliders[0].scale, @out_chans = 4) 2484 B_format_ambisonics 2485 when :decoded 2486 Decoded_ambisonics 2487 end 2488 end 2489 @dialog.add_target([["no reverb", :no_reverb, false], 2490 ["1 rev chan", :one_rev_chan, true], 2491 ["4 rev chans", :four_rev_chans, false]]) do |val| 2492 case val 2493 when :no_reverb 2494 @rev_chans = 0 2495 when :one_rev_chan 2496 @rev_chans = 1 2497 when :four_rev_chans 2498 @rev_chans = 4 2499 end 2500 end 2501 end 2502 2503 def set_xm_enveds_hooks(*enveds) 2504 enveds.each do |e| 2505 e.before_enved_hook.reset_hook! # to prevent running $enved_hook 2506 e.before_enved_hook.add_hook!("dlocsig-hook") do |pos, x, y, reason| 2507 if reason == Enved_move_point 2508 if e.in_range?(x) 2509 old_x = e.point(pos).first 2510 e.stretch!(old_x, x) 2511 e.point(pos, :y, y) 2512 else 2513 false 2514 end 2515 else 2516 false 2517 end 2518 end 2519 e.after_enved_hook.add_hook!("dlocsig-hook") do |pos, reason| show_values end 2520 end 2521 end 2522 2523 # comment string 2524 def dlocsig_strings 2525 dlstr = ["", :amplitude_panning, :b_format_ambisonics, :decoded_ambisonics] 2526 format("%s, output_power: %1.2f, reverb_power: %1.2f", 2527 dlstr[@render_using], 2528 @output_power, 2529 @reverb_power) 2530 end 2531 2532 def help_cb 2533 help_dialog(@label, 2534 "\ 2535The current sound will be moved through the chosen path. You can set \ 2536the reverberator via the global with-sound-variable $clm_reverb \ 2537(#{$clm_reverb.inspect}). If you want four reverb channels, you \ 2538may try freeverb from freeverb.rb. 2539 2540reverb reverb-channels output-channels source 2541 2542jc_reverb 1 4 examp.rb 2543jl_reverb 1 2 clm-ins.rb 2544nrev 1 4 clm-ins.rb 2545freeverb 4 > 4 freeverb.rb 2546 2547Amplitude-panning: generates amplitude panning between adjacent speakers. 2548 2549B-format-ambisonics: generates a four channel first order b-format encoded soundfile. 2550 2551Decoded-ambisonics: the ambisonics encoded information is decoded to the number of selected speakers. 2552 2553Note: reverb on spiral path generates noise if turns is less than 2.6 2554 2555For detailed information see clm-2/dlocsig.html.", 2556 ["{Libxm}: graphics module", 2557 "{Ruby}: extension language", 2558 "{Motif}: Motif extensions via Libxm", 2559 "{dlocsig}: Fernando Lopez Lezcano's multichannel locator"]) 2560 end 2561 end 2562 2563 class Dlocsig_bezier < Dlocsig_menu 2564 require "xm-enved" 2565 2566 def initialize(label, snd_p = false) 2567 super 2568 @target = :with_sound 2569 @which_path = :open_bezier_path 2570 @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]] 2571 @trajectory = nil 2572 @z_value = nil 2573 @velocity = nil 2574 @label_list = [] 2575 end 2576 2577 def inspect 2578 str = @snd_path.inspect 2579 new_str = "" 2580 [str.length, 20].min.times do |i| new_str << str[i] end 2581 new_str << "..." if str.length > 20 2582 format("%s (%s)", @label, new_str) 2583 end 2584 2585 def post_dialog 2586 unless @dialog.kind_of?(Dialog) and widget?(@dialog.dialog) 2587 init_traj = [0, 1, 0.5, 0.5, 1, 1] 2588 init_z_traj = [0, 0, 0.5, 0.1, 1, 0] 2589 init_vel = [0, 0.5, 1, 0.5] 2590 @dialog = make_dialog(@label, 2591 :help_cb, lambda do |w, c, i| 2592 help_cb() 2593 end, :clear_cb, lambda do |w, c, i| 2594 create_path 2595 @path.pplot 2596 end, :reset_cb, lambda do |w, c, i| 2597 reset_with_sound_sliders 2598 @trajectory.envelope = init_traj 2599 @z_value.envelope = init_z_traj 2600 @velocity.envelope = init_vel 2601 show_values 2602 end) do |w, c, i| 2603 create_path 2604 with_sound_target("%s: %s, path: %s", @which_path, dlocsig_strings, @snd_path.inspect) 2605 end 2606 frame_args = [RXmNshadowThickness, 4, 2607 RXmNshadowType, RXmSHADOW_ETCHED_OUT, 2608 RXmNbackground, basic_color, 2609 RXmNheight, 170, 2610 RXmNwidth, 400] 2611 pane = RXtCreateManagedWidget("pane", RxmPanedWindowWidgetClass, @dialog.parent, 2612 [RXmNsashHeight, 1, RXmNsashWidth, 1, 2613 RXmNorientation, RXmHORIZONTAL, 2614 RXmNbackground, basic_color]) 2615 xepane = RXtCreateManagedWidget("xepane", RxmPanedWindowWidgetClass, pane, 2616 [RXmNsashHeight, 1, RXmNsashWidth, 1, 2617 RXmNorientation, RXmVERTICAL, 2618 RXmNbackground, basic_color]) 2619 trfr = RXtCreateManagedWidget("trfr", RxmFrameWidgetClass, xepane, frame_args) 2620 zfr = RXtCreateManagedWidget("zfr", RxmFrameWidgetClass, xepane, frame_args) 2621 vefr = RXtCreateManagedWidget("vefr", RxmFrameWidgetClass, xepane, frame_args) 2622 vepane = RXtCreateManagedWidget("vpane", RxmPanedWindowWidgetClass, pane, 2623 [RXmNsashHeight, 1, RXmNsashWidth, 1, 2624 RXmNseparatorOn, true, 2625 RXmNorientation, RXmVERTICAL, 2626 RXmNbackground, basic_color]) 2627 add_with_sound_sliders(vepane) 2628 rc = RXtCreateManagedWidget("form", RxmRowColumnWidgetClass, vepane, 2629 [RXmNorientation, RXmVERTICAL, 2630 RXmNalignment, RXmALIGNMENT_CENTER]) 2631 @label_list = make_array(8) do |i| 2632 RXtCreateManagedWidget("W" * 30, RxmLabelWidgetClass, rc, []) 2633 end 2634 add_with_sound_targets 2635 @dialog.add_target([["open bezier", :open_bezier_path, true], 2636 ["closed bezier", :closed_bezier_path, false], 2637 ["literal", :literal_path, false]]) do |val| 2638 @which_path = val 2639 create_path 2640 end 2641 activate_dialog(@dialog.dialog) 2642 @trajectory = make_xenved("x, y", trfr, 2643 :envelope, init_traj, 2644 :axis_bounds, [0.0, 1.0, 0.0, 1.0], 2645 :axis_label, [-10.0, 10.0, 0.0, 10.0]) 2646 @z_value = make_xenved("z", zfr, 2647 :envelope, init_z_traj, 2648 :axis_bounds, [0.0, 1.0, 0.0, 1.0], 2649 :axis_label, [-10.0, 10.0, 0.0, 10.0]) 2650 @velocity = make_xenved("velocity v", vefr, 2651 :envelope, init_vel, 2652 :axis_bounds, [0.0, 1.0, 0.05, 1.0], 2653 :axis_label, [-10.0, 10.0, 0.0, 2.0]) 2654 set_xm_enveds_hooks(@trajectory, @z_value, @velocity) 2655 @dialog.clear_string("Gnuplot") 2656 @dialog.doit_string((@snd_p ? "With_Snd" : "With_Sound")) 2657 show_values 2658 else 2659 activate_dialog(@dialog.dialog) 2660 end 2661 end 2662 2663 private 2664 def create_path 2665 test_path 2666 @path = case @which_path 2667 when :open_bezier_path 2668 make_path(@snd_path) 2669 when :closed_bezier_path 2670 make_closed_path(@snd_path) 2671 when :literal_path 2672 make_literal_path(@snd_path) 2673 end 2674 end 2675 2676 def test_path 2677 if @snd_path.length == 2 2678 Snd.display("%s#%s: path has only two points, one added", self.class, get_func_name) 2679 @snd_path.insert(1, [0.0, @snd_path[0][1] - 0.1, 0.0, @snd_path[0][3] + 0.1]) 2680 end 2681 unless @snd_path.detect do |pnt| pnt[1].nonzero? end 2682 Snd.display("%s#%s: y-values are all zero, changed to mid-point 0.1", 2683 self.class, get_func_name) 2684 @snd_path[@snd_path.length / 2][1] = 0.1 2685 end 2686 end 2687 2688 def points_to_path 2689 @snd_path = [] 2690 @trajectory.each do |x, y| 2691 z = @z_value.interp(x) 2692 vel = @velocity.interp(x) 2693 @snd_path.push([x * 20.0 - 10.0, y * 10.0, z * 10.0, vel * 2.0]) 2694 end 2695 end 2696 2697 def show_values 2698 points_to_path 2699 @label_list.each_with_index do |w, i| 2700 if i.zero? 2701 change_label(w, format("%6s %6s %6s %6s", "x", "y", "z", "v")) 2702 else 2703 x, y, z, v = @snd_path[i - 1] 2704 if x 2705 change_label(w, format("%s %s %s %s", 2706 to_f_str(x), to_f_str(y), to_f_str(z), to_f_str(v))) 2707 else 2708 change_label(w, "") 2709 end 2710 end 2711 end 2712 end 2713 2714 def to_f_str(val) 2715 "%6s" % ("% 3.1f" % val) 2716 end 2717 end 2718 2719 class Dlocsig_spiral < Dlocsig_menu 2720 def initialize(label, snd_p = false) 2721 super 2722 @start = 0 2723 @turns = 3.0 2724 end 2725 2726 def inspect 2727 format("%s (%d %1.1f)", @label, @start, @turns) 2728 end 2729 2730 def post_dialog 2731 unless @dialog.kind_of?(Dialog) and widget?(@dialog.dialog) 2732 sliders = [] 2733 init_start = 0 2734 init_turns = 3.0 2735 @dialog = make_dialog(@label, 2736 :help_cb, lambda do |w, c, i| 2737 help_cb 2738 end, :clear_cb, lambda do |w, c, i| 2739 make_spiral_path(:start_angle, @start, :turns, @turns).pplot 2740 end, :reset_cb, lambda do |w, c, i| 2741 reset_with_sound_sliders 2742 set_scale_value(sliders[0].scale, @start = init_start) 2743 @turns = init_turns 2744 set_scale_value(sliders[1].scale, init_turns, 10.0) 2745 end) do |w, c, i| 2746 @path = make_spiral_path(:start_angle, @start, :turns, @turns) 2747 with_sound_target("spiral_path: %s, start: %d, turns: %1.1f", 2748 dlocsig_strings, @start, @turns) 2749 end 2750 add_with_sound_sliders(if provided? :xg 2751 @dialog.dialog 2752 else 2753 @dialog.parent 2754 end) 2755 sliders << @dialog.add_slider("start angle", 0, init_start, 360) do |w, c, i| 2756 @start = get_scale_value(w, i) 2757 end 2758 # turns below 2.6 together with reverb create noise 2759 sliders << @dialog.add_slider("turns", 2.6, init_turns, 10.0, 10) do |w, c, i| 2760 @turns = get_scale_value(w, i, 10.0) 2761 end 2762 add_with_sound_targets 2763 @dialog.clear_string("Gnuplot") 2764 @dialog.doit_string((@snd_p ? "With_Snd" : "With_Sound")) 2765 end 2766 activate_dialog(@dialog.dialog) 2767 end 2768 end 2769 2770 unless defined? $__private_dlocsig_menu__ and $__private_dlocsig_menu__ 2771 snd_main = make_snd_menu("Dlocsig") do 2772 cascade("Dlocsig (Snd)") do 2773 entry(Dlocsig_bezier, "Bezier path (Snd)", true) 2774 entry(Dlocsig_spiral, "Spiral path (Snd)", true) 2775 end 2776 cascade("Dlocsig (CLM)") do 2777 entry(Dlocsig_bezier, "Bezier path (CLM)", false) 2778 entry(Dlocsig_spiral, "Spiral path (CLM)", false) 2779 end 2780 end 2781 2782 if provided? :xm 2783 set_label_sensitive(menu_widgets[Top_menu_bar], "Dlocsig", ((sounds() or []).length > 1)) 2784 else 2785 set_sensitive(snd_main.menu, ((sounds() or []).length > 1)) 2786 end 2787 2788 unless $open_hook.member?("dlocsig-menu-hook") 2789 $open_hook.add_hook!("dlocsig-menu-hook") do |snd| 2790 if provided? :xm 2791 set_label_sensitive(menu_widgets[Top_menu_bar], "Dlocsig", true) 2792 else 2793 set_sensitive(snd_main.menu, true) 2794 end 2795 false 2796 end 2797 2798 $close_hook.add_hook!("dlocsig-menu-hook") do |snd| 2799 if provided? :xm 2800 set_label_sensitive(menu_widgets[Top_menu_bar], "Dlocsig", ((sounds() or []).length > 1)) 2801 else 2802 set_sensitive(snd_main.menu, ((sounds() or []).length > 1)) 2803 end 2804 false 2805 end 2806 end 2807 end 2808end 2809 2810# JC_REVERB (examp.rb) default options 2811# 2812# :low_pass, false 2813# :volume, 1.0 2814# :amp_env, false 2815# :delay1, 0.013 2816# :delay2, 0.011 2817# :delay3, 0.015 2818# :delay4, 0.017 2819# :double, false 2820# 2821# $clm_reverb = :jc_reverb_rb 2822# $clm_reverb_data = [:low_pass, false, :volume, 1.0, :amp_env, false, 2823# :delay1, 0.013, :delay2, 0.011, :delay3, 0.015, :delay4, 0.017] 2824 2825# require "clm-ins" 2826# 2827# JL_REVERB has no options 2828# 2829# $clm_reverb = :jl_reverb 2830# $clm_reverb_data = [] 2831 2832# NREV default options 2833# 2834# :reverb_factor, 1.09 2835# :lp_coeff, 0.7 2836# :volume, 1.0 2837# 2838# $clm_reverb = :nrev_rb 2839# $clm_reverb_data = [:volume, 1.0, :lp_coeff, 0.7] 2840 2841# INTERN or N_REV default options (only with_snd) 2842# 2843# :amount, 0.1 2844# :filter, 0.5 2845# :feedback, 1.09 2846# 2847# $clm_reverb = :intern 2848# $clm_reverb_data = [:amount, 0.1, :filter, 0.5, :feedback, 1.09] 2849 2850# require "freeverb" 2851# 2852# FREEVERB default options 2853# 2854# :room_decay, 0.5, 2855# :damping, 0.5, 2856# :global, 0.3, 2857# :predelay, 0.03, 2858# :output_gain, 1.0, 2859# :output_mixer, nil, 2860# :scale_room_decay, 0.28, 2861# :offset_room_decay, 0.7, 2862# :combtuning, [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617], 2863# :allpasstuning, [556, 441, 341, 225], 2864# :scale_damping, 0.4, 2865# :stereo_spread, 23, 2866# 2867# $clm_reverb = :freeverb 2868# $clm_reverb_data = [:room_decay, 0.5, :damping, 0.5, :global, 0.3, :predelay, 0.03, 2869# :output_gain, 1.0, :output_mixer, nil, :scale_room_decay, 0.7, 2870# :scale_damping, 0.4, :stereo_spread, 23] 2871 2872=begin 2873# (snd-ruby-mode) 2874# Examples: 2875 2876(with_sound(:channels, 4, :output, "rdloc04.snd") do 2877 sinewave(0, 1, 440, 0.5, [[-10, 10], [0, 5], [10, 10]].to_path) 2878 end) 2879 2880(with_sound(:channels, 4, :output, "rdlocspiral.snd") do 2881 move(0, "/usr/gnu/sound/SFiles/bell.snd", DL.make_spiral_path(:start_angle, 180, :turns, 3.5)) 2882 end) 2883 2884([[-10, 10, 0, 0], [0, 5, 0, 1], [10, 10, 0, 0]].to_path(:error, 0.01).plot_velocity) 2885 2886(with_sound(:channels, 4, :output, "rdlocmove.snd") do 2887 move_sound(DL.make_path([[-10, 10], [0.1, 0.1], [10, -10]])) do 2888 fm_violin_rb(0, 1, 440, 0.1) 2889 fm_violin_rb(0.3, 2, 1020, 0.05) 2890 end 2891 end) 2892=end 2893 2894# dlocsig.rb ends here 2895