1# examp.rb -- something from examp.scm 2 3# Translator/Author: Michael Scholz <mi-scholz@users.sourceforge.net> 4# Created: 02/09/04 18:34:00 5# Changed: 17/12/24 22:01:05 6 7# module Examp (examp.scm) 8# selection_rms 9# region_rms(n) 10# window_samples(snd, chn) 11# display_energy(snd, chn) 12# display_db(snd, chn) 13# window_rms 14# fft_peak(snd, chn, scale) 15# finfo(file) 16# display_correlate(snd, chn, y0, y1) 17# 18# zoom_spectrum(snd, chn, y0, y1) 19# zoom_fft(snd, chn, y0, y1) 20# superimpose_ffts(snd, chn, y0, y1) 21# locate_zero(limit) 22# shell(cmd, *rest) 23# 24# mpg(mpgfile, rawfile) 25# read_ogg(filename) 26# write_ogg(snd) 27# read_speex(filename) 28# write_speex(snd) 29# read_flac(filename) 30# write_flac(snd) 31# read_ascii(in_filename, out_filename, out_type, out_format, out_srate) 32# auto_dot(snd, chn, y0, y1) 33# 34# first_mark_in_window_at_left 35# flash_selected_data(interval) 36# mark_loops 37# do_all_chans(origin) do |y| ... end 38# update_graphs 39# do_chans(*origin) do |y| ... end 40# do_sound_chans(*origin) do |y| ... end 41# every_sample? do |y| ... end 42# sort_samples(nbins) 43# place_sound(mono_snd, stereo_snd, pan_env) 44# 45# fft_edit(bottom, top, snd, chn) 46# fft_squelch(squelch, snd, chn) 47# fft_cancel(lo_freq, hi_freq, snd, chn) 48# 49# class Ramp < Musgen 50# initialize(size) 51# run_func(up, dummy) 52# run(up) 53# 54# make_ramp(size) 55# ramp(gen, up) 56# squelch_vowels(snd, chn) 57# fft_env_data(fft_env, snd, chn) 58# fft_env_edit(fft_env, snd, chn) 59# fft_env_interp(env1, env2, interp, snd, chn) 60# fft_smoother(cutoff, start, samps, snd, chn) 61# 62# comb_filter(scaler, size) 63# comb_chord(scaler, size, amp, interval_one, interval_two) 64# zcomb(scaler, size, pm) 65# notch_filter(scaler, size) 66# formant_filter(radius, freq) 67# formants(r1, f1, r2, f2, r3, f3) 68# moving_formant(radius, move) 69# osc_formants(radius, bases, amounts, freqs) 70# echo(scaler, secs) 71# zecho(scaler, secs, freq, amp) 72# flecho(scaler, secs) 73# ring_mod(freq, gliss_env) 74# am(freq) 75# vibro(speed, depth) 76# hello_dentist(freq, amp, snd, chn) 77# fp(sr, osamp, osfreq, snd, chn) 78# compand() 79# expsrc(rate, snd, chn) 80# expsnd(gr_env, snd, chn) 81# cross_synthesis(cross_snd, amp, fftsize, r) 82# voiced2unvoiced(amp, fftsize, r, temp, snd, chn) 83# pulse_voice(cosin, freq, amp, fftsize, r, snd, chn) 84# cnvtest(snd0, snd1, amp) 85# 86# swap_selection_channels 87# make_sound_interp(start, snd, chn) 88# sound_interp(func, loc) 89# sound_via_sound(snd1, snd2) 90# env_sound_interp(envelope, time_scale, snd, chn) 91# granulated_sound_interp(en, time_scale, grain_len, grain_env, out_hop, s, c) 92# filtered_env(en, snd, chn) 93# 94# class Mouse 95# initialize 96# press(snd, chn, button, state, x, y) 97# drag(snd, chn, button, state, x, y) 98# 99# files_popup_buffer(type, position, name) 100# 101# find_click(loc) 102# remove_clicks 103# search_for_click 104# zero_plus 105# next_peak 106# find_pitch(pitch) 107# file2vct(file) 108# add_notes(notes, snd, chn) 109# region_play_list(data) 110# region_play_sequence(data) 111# replace_with_selection 112# explode_sf2 113# 114# class Next_file 115# initialize 116# open_next_file_in_directory 117# click_middle_button_to_open_next_file_in_directory 118# 119# chain_dsps(start, dur, *dsps) 120# 121# scramble_channels(*new_order) 122# scramble_channel(silence) 123# 124# reverse_by_blocks(block_len, snd, chn) 125# reverse_within_blocks(block_len, snd, chn) 126# sound2segment_data(main_dir, output_file) 127# channel_clipped?(snd, chn) 128# scan_sound(func, beg, dur, snd) 129# or scan_sound_rb(beg, dur, snd) do |y, chn| ... end 130# 131# class Moog_filter < Musgen (moog.scm) 132# initialize(freq, q) 133# frequency=(freq) 134# filter(insig) 135# 136# module Moog 137# make_moog_filter(freq, q) 138# moog_filter(moog, insig) 139# moog(freq, q) 140 141require "clm" 142 143module Examp 144 # (ext)snd.html examples made harder to break 145 # 146 # this mainly involves keeping track of the current sound/channel 147 148 add_help(:selection_rms, 149 "selection_rms() \ 150Returns rms of selection data using samplers.") 151 def selection_rms 152 if selection? 153 reader = make_sampler(selection_position, false, false) 154 len = selection_framples() 155 sum = 0.0 156 len.times do 157 val = next_sample(reader) 158 sum = sum + val * val 159 end 160 free_sampler(reader) 161 sqrt(sum / len) 162 else 163 Snd.raise(:no_active_selection) 164 end 165 end 166 167 add_help(:region_rms, 168 "region_rms(n=0) \ 169Returns rms of region N's data (chan 0).") 170 def region_rms(n = 0) 171 if region?(n) 172 data = region2vct(n, 0, 0) 173 sqrt(dot_product(data, data) / data.length) 174 else 175 Snd.raise(:no_such_region) 176 end 177 end 178 179 add_help(:window_samples, 180 "window_samples(snd=false, chn=false) \ 181Returns samples in snd channel chn in current graph window.") 182 def window_samples(snd = false, chn = false) 183 wl = left_sample(snd, chn) 184 wr = right_sample(snd, chn) 185 channel2vct(wl, 1 + (wr - wl), snd, chn) 186 end 187 188 add_help(:display_energy, 189 "display_energy(snd, chn) \ 190Is a $lisp_graph_hook function to display the time domain \ 191data as energy (squared): 192$lisp_graph_hook.add_hook!(\"display-energy\", \ 193&method(:display_energy).to_proc)") 194 def display_energy(snd, chn) 195 ls = left_sample(snd, chn) 196 rs = right_sample(snd, chn) 197 datal = make_graph_data(snd, chn) 198 data = vct?(datal) ? datal : datal[1] 199 sr = srate(snd) 200 y_max = y_zoom_slider(snd, chn) 201 if data and ls and rs 202 vct_multiply!(data, data) 203 graph(data, "energy", ls / sr, rs / sr, 0.0, y_max * y_max, snd, chn) 204 end 205 end 206 207 add_help(:display_db, 208 "display_db(snd, chn) \ 209Is a $lisp_graph_hook function to display the time domain data in dB: 210$lisp_graph_hook.add_hook!(\"display-db\", &method(:display_db).to_proc)") 211 def display_db(snd, chn) 212 if datal = make_graph_data(snd, chn) 213 dB = lambda do |val| 214 if val < 0.001 215 -60.0 216 else 217 20.0 * log10(val) 218 end 219 end 220 data = vct?(datal) ? datal : datal[1] 221 sr = srate(snd) 222 ls = left_sample(snd, chn) 223 rs = right_sample(snd, chn) 224 data.map! do |val| 60.0 + dB.call(val.abs) end 225 graph(data, "dB", ls / sr, rs / sr, 0.0, 60.0, snd, chn) 226 end 227 end 228 229 add_help(:window_rms, 230 "window_rms() \ 231Returns rms of data in currently selected graph window.") 232 def window_rms 233 ls = left_sample 234 rs = right_sample 235 data = channel2vct(ls, 1 + (rs - ls)) 236 sqrt(dot_product(data, data), data.length) 237 end 238 239 add_help(:fft_peak, 240 "fft_peak(snd, chn, scale) \ 241Returns the peak spectral magnitude: 242$after_transform_hook.add_hook!(\"fft-peak\") do |snd, chn, scale| 243 fft_peak(snd, chn, scale) 244end") 245 def fft_peak(snd, chn, scale) 246 if transform_graph? and transform_graph_type == Graph_once 247 pk = (2.0 * vct_peak(transform2vct(snd, chn))) / transform_size 248 status_report(pk.to_s, snd) 249 pk 250 else 251 false 252 end 253 end 254 255 # 'info' from extsnd.html using format 256 257 add_help(:finfo, 258 "finfo(file) \ 259Returns description (as a string) of FILE.") 260 def finfo(file) 261 chans = mus_sound_chans(file) 262 sr = mus_sound_srate(file) 263 format("%s: chans: %d, srate: %d, %s, %s, len: %1.3f", 264 file, chans, sr, 265 mus_header_type_name(mus_sound_header_type(file)), 266 mus_sample_type_name(mus_sound_sample_type(file)), 267 mus_sound_samples(file).to_f / (chans * sr.to_f)) 268 end 269 270 # Correlation 271 # 272 # correlation of channels in a stereo sound 273 274 add_help(:display_correlate, 275 "display_correlate(snd, chn, y0, y1) \ 276Returns the correlation of SND's 2 channels (intended for use with $graph_hook): 277$graph_hook.add_hook!(\"display_correlate\") do |snd, chn, y0, y1| 278 display_correlate(snd, chn, y0, y1) 279end") 280 281 def display_correlate(snd, chn, y0, y1) 282 if channels(snd) == 2 and framples(snd, 0) > 1 and framples(snd, 1) > 1 283 ls = left_sample(snd, 0) 284 rs = right_sample(snd, 0) 285 ilen = 1 + (rs - ls) 286 pow2 = (log(ilen) / log(2)).ceil 287 fftlen = (2 ** pow2).to_i 288 fftlen2 = fftlen / 2 289 fftscale = 1.0 / fftlen 290 rl1 = channel2vct(ls, fftlen, snd, 0) 291 rl2 = channel2vct(ls, fftlen, snd, 1) 292 im1 = make_vct(fftlen) 293 im2 = make_vct(fftlen) 294 fft(rl1, im1, 1) 295 fft(rl2, im2, 1) 296 tmprl = vct_copy(rl1) 297 tmpim = vct_copy(im1) 298 data3 = make_vct(fftlen2) 299 vct_multiply!(tmprl, rl2) 300 vct_multiply!(tmpim, im2) 301 vct_multiply!(im2, rl1) 302 vct_multiply!(rl2, im1) 303 vct_add!(tmprl, tmpim) 304 vct_subtract!(im2, rl2) 305 fft(tmprl, im2, -1) 306 vct_add!(data3, tmprl) 307 vct_scale!(data3, fftscale) 308 graph(data3, "lag time", 0, fftlen2) 309 else 310 snd_print("correlate wants stereo input") 311 end 312 end 313 314 # set transform-size based on current time domain window size 315 # 316 # also zoom spectrum based on y-axis zoom slider 317 318 add_help(:zoom_spectrum, 319 "zoom_spectrum(snd, chn, y0, y1) \ 320Sets the transform size to correspond to the \ 321time-domain window size (use with $graph_hook): 322$graph_hook.add_hook!(\"zoom-spectrum\") do |snd, chn, y0, y1| 323 zoom_spectrum(snd, chn, y0, y1) 324end") 325 def zoom_spectrum(snd, chn, y0, y1) 326 if transform_graph?(snd, chn) and 327 transform_graph_type(snd, chn) == Graph_once 328 set_transform_size((2 ** 329 (log(right_sample(snd, chn) - 330 left_sample(snd, chn))/log(2.0))).to_i, snd, chn) 331 set_spectrum_end(y_zoom_slider(snd, chn), snd, chn) 332 end 333 false 334 end 335 336 add_help(:zoom_fft, 337 "zoom_fft(snd, chn, y0, y1) \ 338Sets the transform size if the time domain is \ 339not displayed (use with $graph_hook). \ 340It also sets the spectrum display start point \ 341based on the x position slider---this can be confusing \ 342if fft normalization is on (the default): 343$graph_hook.add_hook!(\"zoom-fft\") do |snd, chn, y0, y1| 344 zoom_fft(snd, chn, y0, y1) 345end") 346 def zoom_fft(snd, chn, y0, y1) 347 if transform_graph?(snd, chn) and 348 (not time_graph?(snd, chn)) and 349 transform_graph_type(snd, chn) == Graph_once 350 set_transform_size(2 ** 351 (log(right_sample(snd, chn) - 352 left_sample(snd, chn)) / log(2.0)).ceil, snd, chn) 353 set_spectrum_start(x_position_slider(snd, chn), snd, chn) 354 set_spectrum_end(y_zoom_slider(snd, chn), snd, chn) 355 end 356 false 357 end 358 359 # superimpose spectra of sycn'd sounds 360 361 add_help(:superimpose_ffts, 362 "superimpose_ffts(snd, chn, y0, y1) \ 363Superimposes ffts of multiple (syncd) sounds (use with $graph_hook): 364$graph_hook.add_hook!(\"superimpose-ffts\") do |snd, chn, y0, y1| 365 superimpose_ffts(snd, chn, y0, y1) 366end") 367 def superimpose_ffts(snd, chn, y0, y1) 368 maxsync = Snd.sounds.map do |s| sync(s) end.max 369 if sync(snd) > 0 and 370 snd == Snd.sounds.map do |s| 371 sync(snd) == sync(s) ? sound2integer(s) : (maxsync + 1) 372 end.min 373 ls = left_sample(snd, chn) 374 rs = right_sample(snd, chn) 375 pow2 = (log(rs - ls) / log(2)).ceil 376 fftlen = (2 ** pow2).to_i 377 if pow2 > 2 378 ffts = [] 379 Snd.sounds.each do |s| 380 if sync(snd) == sync(s) and channels(s) > chn 381 fdr = channel2vct(ls, fftlen, s, chn) 382 fdi = make_vct(fftlen) 383 spectr = make_vct(fftlen / 2) 384 ffts.push(spectr.add(spectrum(fdr, fdi, false, 2))) 385 end 386 end 387 graph(ffts, "spectra", 0.0, 0.5, y0, y1, snd, chn) 388 end 389 end 390 false 391 end 392 393 # c-g? example (Anders Vinjar) 394 395 add_help(:locate_zero, 396 "locate_zero(limit) \ 397Looks for successive samples that sum to less than LIMIT, \ 398moving the cursor if successful.") 399 def locate_zero(limit) 400 start = cursor 401 sf = make_sampler(start, false, false) 402 val0 = sf.call.abs 403 val1 = sf.call.abs 404 n = start 405 until sampler_at_end?(sf) or val0 + val1 < limit 406 val0, val1 = val1, sf.call.abs 407 n += 1 408 end 409 free_sampler(sf) 410 set_cursor(n) 411 end 412 413 # make a system call from irb or snd listener 414 # 415 # shell("df") for example 416 # or to play a sound whenever a file is closed: 417 # $close-hook.add_hook!() do |snd| shell("sndplay wood16.wav"); false end 418 419 add_help(:shell, 420 "shell(cmd, *rest) \ 421Sends CMD to a shell (executes it as a shell command) \ 422and returns the result string.") 423 def shell(cmd, *rest) 424 str = "" 425 unless cmd.null? 426 IO.popen(format(cmd, *rest), "r") do |f| str = f.readlines.join end 427 end 428 str 429 end 430 431 # translate mpeg input to 16-bit linear and read into Snd 432 # 433 # mpg123 with the -s switch sends the 16-bit (mono or stereo) representation 434 # of an mpeg file to stdout. There's also apparently a switch to write 435 # 'wave' output. 436 437 add_help(:mpg, 438 "mpg(file, tmpname) \ 439Converts file from MPEG to raw 16-bit samples using mpg123: \ 440mpg(\"mpeg.mpg\", \"mpeg.raw\")") 441 def mpg(mpgfile, rawfile) 442 b0 = b1 = b2 = b3 = 0 443 File.open(mpgfile, "r") do |fd| 444 b0 = fd.readchar 445 b1 = fd.readchar 446 b2 = fd.readchar 447 b3 = fd.readchar 448 end 449 if b0 != 255 or (b1 & 0b11100000) != 0b11100000 450 Snd.display("%s is not an MPEG file (first 11 bytes: %b %b", 451 mpgfile.inspect, b0, b1 & 0b11100000) 452 else 453 id = (b1 & 0b11000) >> 3 454 layer = (b1 & 0b110) >> 1 455 srate_index = (b2 & 0b1100) >> 2 456 channel_mode = (b3 & 0b11000000) >> 6 457 if id == 1 458 Snd.display("odd: %s is using a reserved Version ID", mpgfile) 459 end 460 if layer == 0 461 Snd.display("odd: %s is using a reserved layer description", mpgfile) 462 end 463 chans = channel_mode == 3 ? 1 : 2 464 mpegnum = id.zero? ? 4 : (id == 2 ? 2 : 1) 465 mpeg_layer = layer == 3 ? 1 : (layer == 2 ? 2 : 3) 466 srate = [44100, 48000, 32000, 0][srate_index] / mpegnum 467 Snd.display("%s: %s Hz, %s, MPEG-%s", 468 mpgfile, srate, chans == 1 ? "mono": "stereo", mpeg_layer) 469 system(format("mpg123 -s %s > %s", mpgfile, rawfile)) 470 open_raw_sound(rawfile, chans, srate, 471 little_endian? ? Mus_lshort : Mus_bshort) 472 end 473 end 474 475 # read and write OGG files 476 477 add_help(:read_ogg, 478 "read_ogg(filename) \ 479Read OGG files: 480$open_hook.add_hook!(\"read-ogg\") do |filename| 481 if mus_sound_header_type(filename) == Mus_raw 482 read_ogg(filename) 483 else 484 false 485 end 486end") 487 def read_ogg(filename) 488 flag = false 489 File.open(filename, "r") do |fd| 490 flag = fd.readchar == ?O and 491 fd.readchar == ?g and 492 fd.readchar == ?g and 493 fd.readchar == ?S 494 end 495 if flag 496 aufile = filename + ".au" 497 File.unlink(aufile) if File.exist?(aufile) 498 system(format("ogg123 -d au -f %s %s", aufile, filename)) 499 aufile 500 else 501 false 502 end 503 end 504 505 def write_ogg(snd) 506 if edits(snd)[0] > 0 or header_type(snd) != Mus_riff 507 file = file_name(snd) + ".tmp" 508 save_sound_as(file, snd, :header_type, Mus_riff) 509 system("oggenc " + file) 510 File.unlink(file) 511 else 512 system("oggenc " + file_name(snd)) 513 end 514 end 515 516 # read and write Speex files 517 518 def read_speex(filename) 519 wavfile = filename + ".wav" 520 File.unlink(wavfile) if File.exist?(wavfile) 521 system(format("speexdec %s %s", filename, wavfile)) 522 wavfile 523 end 524 525 def write_speex(snd) 526 if edits(snd)[0] > 0 or header_type(snd) != Mus_riff 527 file = file_name(snd) + ".wav" 528 spxfile = file_name(snd) + "spx" 529 save_sound_as(file, snd, :header_type, Mus_riff) 530 system(format("speexenc %s %s", file, spxfile)) 531 File.unlink(file) 532 else 533 system(format("speexenc %s %s", file_name(snd), spxfile)) 534 end 535 end 536 537 # read and write FLAC files 538 539 def read_flac(filename) 540 system(format("flac -d %s", filename)) 541 end 542 543 def write_flac(snd) 544 if edits(snd)[0] > 0 or header_type(snd) != Mus_riff 545 file = file_name(snd) + ".wav" 546 save_sound_as(file, snd, :header_type, Mus_riff) 547 system(format("flac %s", file)) 548 File.unlink(file) 549 else 550 system(format("flac %s ", file_name(snd))) 551 end 552 end 553 554 # read ASCII files 555 # 556 # these are used by Octave (WaveLab) -- each line has one integer, 557 # apparently a signed short. 558 559 def read_ascii(in_filename, 560 out_filename = "test.snd", 561 out_type = Mus_next, 562 out_format = Mus_bshort, 563 out_srate = 44100) 564 in_buffer = IO.readlines(in_filename) # array of strings 565 com = format("created by %s: %s", get_func_name, in_filename) 566 out_snd = new_sound(out_filename, 1, out_srate, out_format, out_type, com) 567 bufsize = 512 568 data = make_vct(bufsize) 569 loc = 0 570 frame = 0 571 short2float = 1.0 / 32768.0 572 as_one_edit_rb do | | 573 in_buffer.each do |line| 574 line.split.each do |str_val| 575 val = eval(str_val) 576 data[loc] = val * short2float 577 loc += 1 578 if loc == bufsize 579 vct2channel(data, frame, bufsize, out_snd, 0) 580 frame += bufsize 581 loc = 0 582 end 583 end 584 end 585 if loc > 0 586 vct2channel(data, frame, loc, out_snd, 0) 587 end 588 end 589 out_snd 590 end 591 592 # make dot size dependent on number of samples being displayed 593 # 594 # this could be extended to set time_graph_style to Graph_lines if 595 # many samples are displayed, etc 596 597 add_help(:auto_dot, 598 "auto_dot(snd, chn, y0, y1) \ 599Sets the dot size depending on the number \ 600of samples being displayed (use with $graph_hook): 601$graph_hook.add_hook!(\"auto-dot\") do |snd, chn, y0, y1| 602 auto_dot(snd, chn, y0, y1) 603end") 604 def auto_dot(snd, chn, y0, y1) 605 dots = right_sample(snd, chn) - left_sample(snd, chn) 606 case dots 607 when 100 608 set_dot_size(1, snd, chn) 609 when 50 610 set_dot_size(2, snd, chn) 611 when 25 612 set_dot_size(3, snd, chn) 613 else 614 set_dot_size(5, snd, chn) 615 end 616 false 617 end 618 619 # move window left edge to mark upon 'm' 620 # 621 # in large sounds, it can be pain to get the left edge of the window 622 # aligned with a specific spot in the sound. In this code, we 623 # assume the desired left edge has a mark, and the 'm' key (without 624 # control) will move the window left edge to that mark. 625 626 add_help(:first_mark_in_window_at_left, 627 "first_mark_in_window_at_left() \ 628Moves the graph so that the leftmost visible mark is at the left edge: 629bind_key(?m, 0, lambda do | | first_mark_in_window_at_left end)") 630 def first_mark_in_window_at_left 631 keysnd = Snd.snd 632 keychn = Snd.chn 633 current_left_sample = left_sample(keysnd, keychn) 634 chan_marks = marks(keysnd, keychn) 635 if chan_marks.null? 636 snd_print("no marks!") 637 else 638 leftmost = chan_marks.map do |m| 639 mark_sample(m) 640 end.detect do |m| 641 m > current_left_sample 642 end 643 if leftmost.null? 644 snd_print("no mark in window") 645 else 646 set_left_sample(leftmost, keysnd, keychn) 647 Keyboard_no_action 648 end 649 end 650 end 651 652 # flash selected data red and green 653 654 add_help(:flash_selected_data, 655 "flash_selected_data(millisecs) \ 656Causes the selected data to flash red and green.") 657 def flash_selected_data(interval) 658 if selected_sound 659 set_selected_data_color(selected_data_color == Red ? Green : Red) 660 call_in(interval, lambda do | | flash_selected_data(interval) end) 661 end 662 end 663 664 # use loop info (if any) to set marks at loop points 665 666 add_help(:mark_loops, 667 "mark_loops() \ 668Places marks at loop points found in the selected sound's header.") 669 def mark_loops 670 loops = (sound_loop_info or mus_sound_loop_info(file_name)) 671 if loops and !loops.empty? 672 unless loops[0].zero? and loops[1].zero? 673 add_mark(loops[0]) 674 add_mark(loops[1]) 675 unless loops[2].zero? and loops[3].zero? 676 add_mark(loops[2]) 677 add_mark(loops[3]) 678 end 679 end 680 else 681 Snd.display("%s has no loop info", short_file_name.inspect) 682 end 683 end 684 685 # mapping extensions (map arbitrary single-channel function over 686 # various channel collections) 687 688 add_help(:do_all_chans, 689 "do_all_chans(edhist) do |y| ... end \ 690Applies func to all active channels, \ 691using EDHIST as the edit history indication: \ 692do_all_chans(\"double all samples\", do |val| 2.0 * val end)") 693 def do_all_chans(origin, &func) 694 Snd.sounds.each do |snd| 695 channels(snd).times do |chn| 696 map_channel(func, 0, false, snd, chn, false, origin) 697 end 698 end 699 end 700 701 add_help(:update_graphs, 702 "update_graphs() \ 703Updates (redraws) all graphs.") 704 def update_graphs 705 Snd.sounds.each do |snd| 706 channels(snd).times do |chn| 707 update_time_graph(snd, chn) 708 end 709 end 710 end 711 712 add_help(:do_chans, 713 "do_chans(edhist) do |y| ... end \ 714Applies func to all sync'd channels using EDHIST \ 715as the edit history indication.") 716 def do_chans(*origin, &func) 717 snc = sync 718 if snc > 0 719 Snd.sounds.each do |snd| 720 channels(snd).times do |chn| 721 if sync(snd) == snc 722 map_channel(func, 0, false, snd, chn, false, 723 (origin.empty? ? "" : format(*origin))) 724 end 725 end 726 end 727 else 728 snd_warning("sync not set") 729 end 730 end 731 732 add_help(:do_sound_chans, 733 "do_sound_chans(edhist) do |y| ... end \ 734Applies func to all selected channels using EDHIST \ 735as the edit history indication.") 736 def do_sound_chans(*origin, &func) 737 if snd = selected_sound 738 channels(snd).times do |chn| 739 map_channel(func, 0, false, snd, chn, false, 740 (origin.empty? ? "" : format(*origin))) 741 end 742 else 743 snd_warning("no selected sound") 744 end 745 end 746 747 add_help(:every_sample?, 748 "every_sample? do |y| ... end \ 749Returns true if func is not false for all samples in the current channel, \ 750otherwise it moves the cursor to the first offending sample.") 751 def every_sample?(&func) 752 snd = Snd.snd 753 chn = Snd.chn 754 if baddy = scan_channel(lambda do |y| 755 (not func.call(y)) 756 end, 0, framples(snd, chn), snd, chn) 757 set_cursor(baddy[1]) 758 end 759 (not baddy) 760 end 761 762 add_help(:sort_samples, 763 "sort_samples(bins) \ 764Provides a histogram in BINS bins.") 765 def sort_samples(nbins) 766 bins = make_array(nbins, 0) 767 scan_channel(lambda do |y| 768 bin = (y.abs * nbins).floor 769 bins[bin] += 1 770 false 771 end) 772 bins 773 end 774 775 # mix mono sound into stereo sound panning according to env 776 777 add_help(:place_sound, 778 "place_sound(mono_snd, stereo_snd, pan_env) \ 779Mixes a mono sound into a stereo sound, splitting it into two copies \ 780whose amplitudes depend on the envelope PAN_ENV. \ 781If PAN_ENV is a number, \ 782the sound is split such that 0 is all in channel 0 \ 783and 90 is all in channel 1.") 784 def place_sound(mono_snd, stereo_snd, pan_env) 785 len = framples(mono_snd) 786 if number?(pan_env) 787 pos = pan_env / 90.0 788 rd0 = make_sampler(0, mono_snd, false) 789 rd1 = make_sampler(0, mono_snd, false) 790 map_channel(lambda do |y| y + pos * read_sample(rd1) end, 791 0, len, stereo_snd, 1) 792 map_channel(lambda do |y| y + (1.0 - pos) * read_sample(rd0) end, 793 0, len, stereo_snd, 0) 794 else 795 e0 = make_env(:envelope, pan_env, :length, len) 796 e1 = make_env(:envelope, pan_env, :length, len) 797 rd0 = make_sampler(0, mono_snd, false) 798 rd1 = make_sampler(0, mono_snd, false) 799 map_channel(lambda do |y| y + env(e1) * read_sample(rd1) end, 800 0, len, stereo_snd, 1) 801 map_channel(lambda do |y| y + (1.0 - env(e0)) * read_sample(rd0) end, 802 0, len, stereo_snd, 0) 803 end 804 end 805 806 # FFT-based editing 807 808 add_help(:fft_edit, 809 "fft_edit(low_Hz, high_Hz) \ 810Ffts an entire sound, removes all energy below low-Hz and all above high-Hz, \ 811then inverse ffts.") 812 def fft_edit(bottom, top, snd = false, chn = false) 813 sr = srate(snd).to_f 814 len = framples(snd, chn) 815 fsize = (2.0 ** (log(len) / log(2.0)).ceil).to_i 816 rdata = channel2vct(0, fsize, snd, chn) 817 idata = make_vct(fsize) 818 lo = (bottom / (sr / fsize)).round 819 hi = (top / (sr / fsize)).round 820 fft(rdata, idata, 1) 821 j = fsize - 1 822 lo.times do |i| 823 rdata[i] = rdata[j] = 0.0 824 rdata[i] = rdata[j] = 0.0 825 j -= 1 826 end 827 j = fsize - hi 828 (hi..(fsize / 2)).each do |i| 829 rdata[i] = rdata[j] = 0.0 830 rdata[i] = rdata[j] = 0.0 831 j -= 1 832 end 833 fft(rdata, idata, -1) 834 vct_scale!(rdata, 1.0 / fsize) 835 vct2channel(rdata, 0, len - 1, snd, chn, false, 836 format("%s(%s, %s", get_func_name, bottom, top)) 837 end 838 839 add_help(:fft_squelch, 840 "fft_squelch(squelch, snd=false, chn=false) \ 841Ffts an entire sound, sets all bins to 0.0 whose energy is below squelch, \ 842then inverse ffts.") 843 def fft_squelch(squelch, snd = false, chn = false) 844 len = framples(snd, chn) 845 fsize = (2.0 ** (log(len) / log(2.0)).ceil).to_i 846 rdata = channel2vct(0, fsize, snd, chn) 847 idata = make_vct(fsize) 848 fsize2 = fsize / 2 849 fft(rdata, idata, 1) 850 vr = vct_copy(rdata) 851 vi = vct_copy(idata) 852 rectangular2polar(vr, vi) 853 scaler = vct_peak(vr) 854 scl_squelch = squelch * scaler 855 j = fsize - 1 856 fsize2.times do |i| 857 if sqrt(rdata[i] * rdata[i] + idata[i] * idata[i]) < scl_squelch 858 rdata[i] = rdata[j] = 0.0 859 rdata[i] = rdata[j] = 0.0 860 end 861 j -= 1 862 end 863 fft(rdata, idata, -1) 864 vct_scale!(rdata, 1.0 / fsize) 865 vct2channel(rdata, 0, len - 1, snd, chn, false, 866 format("%s(%s", get_func_name, squelch)) 867 scaler 868 end 869 870 add_help(:fft_cancel, 871 "fft_cancel(lo_freq, hi_freq, snd=false, chn=false) \ 872Ffts an entire sound, sets the bin(s) representing lo_freq to hi_freq to 0.0, \ 873then inverse ffts.") 874 def fft_cancel(lo_freq, hi_freq, snd = false, chn = false) 875 sr = srate(snd).to_f 876 len = framples(snd, chn) 877 fsize = (2.0 ** (log(len) / log(2.0)).ceil).to_i 878 rdata = channel2vct(0, fsize, snd, chn) 879 idata = make_vct(fsize) 880 fsize2 = fsize / 2 881 fft(rdata, idata, 1) 882 hz_bin = sr / fsize 883 lo_bin = (lo_freq / hz_bin).round 884 hi_bin = (hi_freq / hz_bin).round 885 j = fsize - lo_bin - 1 886 fsize2.times do |i| 887 if i > hi_bin 888 rdata[i] = rdata[j] = 0.0 889 rdata[i] = rdata[j] = 0.0 890 end 891 j -= 1 892 end 893 fft(rdata, idata, -1) 894 vct_scale!(rdata, 1.0 / fsize) 895 vct2channel(rdata, 0, len - 1, snd, chn, false, 896 format("%s(%s, %s", get_func_name, lo_freq, hi_freq)) 897 end 898 899 # same idea but used to distinguish vowels (steady-state) from consonants 900 901 class Ramp < Musgen 902 def initialize(size = 128) 903 super() 904 @val = 0.0 905 @size = size 906 @incr = 1.0 / size 907 @up = true 908 end 909 910 def inspect 911 format("%s.new(%d)", self.class, @size) 912 end 913 914 def to_s 915 format("#<%s size: %d, val: %1.3f, incr: %1.3f, up: %s>", 916 self.class, @size, @val, @incr, @up) 917 end 918 919 def run_func(up = true, dummy = false) 920 @up = up 921 @val += (@up ? @incr : -@incr) 922 @val = [1.0, [0.0, @val].max].min 923 end 924 925 def run(up = true) 926 self.run_func(up, false) 927 end 928 end 929 930 add_help(:make_ramp, 931 "make_ramp(size=128) \ 932Returns a ramp generator.") 933 def make_ramp(size = 128) 934 Ramp.new(size) 935 end 936 937 add_help(:ramp, 938 "ramp(gen, up) \ 939Is a kind of CLM generator that produces a ramp of a given length, \ 940then sticks at 0.0 or 1.0 until the UP argument changes.") 941 def ramp(gen, up) 942 gen.run_func(up, false) 943 end 944 945 add_help(:squelch_vowels, 946 "squelch_vowels(snd=false, chn=false) \ 947Suppresses portions of a sound that look like steady-state.") 948 def squelch_vowels(snd = false, chn = false) 949 fft_size = 32 950 fft_mid = (fft_size / 2.0).floor 951 ramper = Ramp.new(256) 952 peak = maxamp(snd, chn) / fft_mid 953 read_ahead = make_sampler(0, snd, chn) 954 rl = Vct.new(fft_size) do 955 read_sample(read_ahead) 956 end 957 im = Vct.new(fft_size, 0.0) 958 ctr = fft_size - 1 959 in_vowel = false 960 map_channel(lambda do |y| 961 ctr += 1 962 if ctr == fft_size 963 ctr = 0 964 fft(rl, im, 1) 965 rl.multiply!(rl) 966 im.multiply!(im) 967 rl.add!(im) 968 im.fill(0.0) 969 in_vowel = (rl[0] + rl[1] + rl[2] + rl[3]) > peak 970 rl.map! do 971 read_sample(read_ahead) 972 end 973 end 974 y * (1.0 - ramper.run(in_vowel)) 975 end, 0, false, snd, chn, false, "squelch_vowels(") 976 end 977 978 add_help(:fft_env_data, 979 "fft_env_data(fft-env, snd=false, chn=false) \ 980Applies fft_env as spectral env to current sound, returning vct of new data.") 981 def fft_env_data(fft_env, snd = false, chn = false) 982 len = framples(snd, chn) 983 fsize = (2 ** (log(len) / log(2.0)).ceil).to_i 984 rdata = channel2vct(0, fsize, snd, chn) 985 idata = make_vct(fsize) 986 fsize2 = fsize / 2 987 en = make_env(:envelope, fft_env, :length, fsize2) 988 fft(rdata, idata, 1) 989 j = fsize - 1 990 fsize2.times do |i| 991 val = env(en) 992 rdata[i] *= val 993 idata[i] *= val 994 rdata[j] *= val 995 idata[j] *= val 996 j -= 1 997 end 998 fft(rdata, idata, -1) 999 vct_scale!(rdata, 1.0 / fsize) 1000 end 1001 1002 add_help(:fft_env_edit, 1003 "fft_env_edit(fft-env, snd=false, chn=false) \ 1004Edits (filters) current chan using fft_env.") 1005 def fft_env_edit(fft_env, snd = false, chn = false) 1006 vct2channel(fft_env_data(fft_env, snd, chn), 0, framples(snd, chn) - 1, 1007 snd, chn, false, 1008 format("%s(%s", get_func_name, fft_env.inspect)) 1009 end 1010 1011 add_help(:fft_env_interp, 1012 "fft_env_interp(env1, env2, interp, snd=false, chn=false) \ 1013Interpolates between two fft-filtered \ 1014versions (env1 and env2 are the spectral envelopes) \ 1015following interp (an env between 0 and 1).") 1016 def fft_env_interp(env1, env2, interp, snd = false, chn = false) 1017 data1 = fft_env_data(env1, snd, chn) 1018 data2 = fft_env_data(env2, snd, chn) 1019 len = framples(snd, chn) 1020 en = make_env(:envelope, interp, :length, len) 1021 new_data = make_vct!(len) do |i| 1022 pan = env(en) 1023 (1.0 - pan) * data1[i] + pan * data2[i] 1024 end 1025 vct2channel(new_data, 0, len - 1, snd, chn, false, 1026 format("%s(%s, %s, %s", get_func_name, 1027 env1.inspect, env2.inspect, interp.inspect)) 1028 end 1029 1030 add_help(:fft_smoother, 1031 "fft_smoother(cutoff, start, samps, snd=false, chn=false) \ 1032Uses fft-filtering to smooth a section: \ 1033vct2channel(fft_smoother(0.1, cursor, 400, 0, 0), cursor, 400)") 1034 def fft_smoother(cutoff, start, samps, snd = false, chn = false) 1035 fftpts = (2 ** (log(samps + 1) / log(2.0)).ceil).to_i 1036 rl = channel2vct(start, samps, snd, chn) 1037 im = make_vct(fftpts) 1038 top = (fftpts * cutoff.to_f).floor 1039 old0 = rl[0] 1040 old1 = rl[samps - 1] 1041 oldmax = vct_peak(rl) 1042 fft(rl, im, 1) 1043 (top...fftpts).each do |i| rl[i] = im[i] = 0.0 end 1044 fft(rl, im, -1) 1045 vct_scale!(rl, 1.0 / fftpts) 1046 newmax = vct_peak(rl) 1047 if newmax.zero? 1048 rl 1049 else 1050 if (scl = oldmax / newmax) > 1.5 1051 vct_scale!(rl, scl) 1052 end 1053 new0 = rl[0] 1054 new1 = rl[samps - 1] 1055 offset0 = old0 - new0 1056 offset1 = old1 - new1 1057 incr = offset0 == offset1 ? 0.0 : ((offset1 - offset0) / samps) 1058 trend = offset0 - incr 1059 rl.map do |val| 1060 trend += incr 1061 val += trend 1062 end 1063 end 1064 end 1065 1066 # comb-filter 1067 1068 add_help(:comb_filter, 1069 "comb_filter(scaler, size) \ 1070Returns a comb-filter ready for map_channel etc: \ 1071map_channel(comb_filter(0.8, 32)) \ 1072If you're in a hurry use: clm_channel(make_comb(0.8, 32)) instead.") 1073 def comb_filter(scaler, size) 1074 cmb = make_comb(scaler, size) 1075 lambda do |inval| comb(cmb, inval) end 1076 end 1077 1078 # by using filters at harmonically related sizes, we can get chords: 1079 1080 add_help(:comb_chord, 1081 "comb_chord(scl, size, amp, interval_one=0.75, interval_two=1.2) \ 1082Returns a set of harmonically-related comb filters: \ 1083map_channel(comb_chord(0.95, 100, 0.3))") 1084 def comb_chord(scaler, size, amp, interval_one = 0.75, interval_two = 1.2) 1085 c1 = make_comb(scaler, size.to_i) 1086 c2 = make_comb(scaler, (interval_one * size).to_i) 1087 c3 = make_comb(scaler, (interval_two * size).to_i) 1088 lambda do |inval| 1089 amp * (comb(c1, inval) + comb(c2, inval) + comb(c3, inval)) 1090 end 1091 end 1092 1093 # or change the comb length via an envelope: 1094 1095 add_help(:zcomb, 1096 "zcomb(scaler, size, pm) \ 1097Returns a comb filter whose length varies according to an envelope: \ 1098map_channel(zcomb(0.8, 32, [0, 0, 1, 10]))") 1099 def zcomb(scaler, size, pm) 1100 max_envelope_1 = lambda do |en, mx| 1101 1.step(en.length - 1, 2) do |i| mx = [mx, en[i]].max.to_f end 1102 mx 1103 end 1104 cmb = make_comb(scaler, size, 1105 :max_size, (max_envelope_1.call(pm, 0.0) + size + 1).to_i) 1106 penv = make_env(:envelope, pm, :length, framples()) 1107 lambda do |inval| comb(cmb, inval, env(penv)) end 1108 end 1109 1110 add_help(:notch_filter, 1111 "notch_filter(scaler, size) \ 1112Returns a notch-filter: map_channel(notch_filter(0.8, 32))") 1113 def notch_filter(scaler, size) 1114 gen = make_notch(scaler, size) 1115 lambda do |inval| notch(gen, inval) end 1116 end 1117 1118 add_help(:formant_filter, 1119 "formant_filter(radius, frequency) \ 1120Returns a formant generator: map_channel(formant_filter(0.99, 2400)) \ 1121Faster is: filter_sound(make_formant(2400, 0.99))") 1122 def formant_filter(radius, freq) 1123 frm = make_formant(radius, freq) 1124 lambda do |inval| formant(frm, inval) end 1125 end 1126 1127 # to impose several formants, just add them in parallel: 1128 1129 add_help(:formants, 1130 "formants(r1, f1, r2, f2, r3, f3) \ 1131Returns 3 formant filters in parallel: \ 1132map_channel(formants(0.99, 900, 0.98, 1800, 0.99 2700))") 1133 def formants(r1, f1, r2, f2, r3, f3) 1134 fr1 = make_formant(f1, r1) 1135 fr2 = make_formant(f2, r2) 1136 fr3 = make_formant(f3, r3) 1137 lambda do |inval| 1138 formant(fr1, inval) + formant(fr2, inval) + formant(fr3, inval) 1139 end 1140 end 1141 1142 add_help(:moving_formant, 1143 "moving_formant(radius, move) \ 1144Returns a time-varying (in frequency) formant filter: \ 1145map_channel(moving_formant(0.99, [0, 1200, 1, 2400]))") 1146 def moving_formant(radius, move) 1147 frm = make_formant(move[1], radius) 1148 menv = make_env(:envelope, move, :length, framples()) 1149 lambda do |inval| 1150 val = formant(frm, inval) 1151 frm.frequency = env(menv) 1152 val 1153 end 1154 end 1155 1156 add_help(:osc_formants, 1157 "osc_formants(radius, bases, amounts, freqs) \ 1158Set up any number of independently oscillating formants, \ 1159then calls map_channel: \ 1160osc_formants(0.99, vct(400, 800, 1200), vct(400, 800, 1200), vct(4, 2, 3))") 1161 def osc_formants(radius, bases, amounts, freqs) 1162 len = bases.length 1163 frms = make_array(len) do |i| make_formant(bases[i], radius) end 1164 oscs = make_array(len) do |i| make_oscil(freqs[i]) end 1165 map_channel_rb() do |x| 1166 val = 0.0 1167 frms.each_with_index do |frm, i| 1168 val += formant(frm, x) 1169 set_mus_frequency(frm, bases[i] + amounts[i] * oscil(oscs[i])) 1170 end 1171 val 1172 end 1173 end 1174 1175 # echo 1176 1177 add_help(:echo, 1178 "echo(scaler, secs) \ 1179Returns an echo maker: map_channel(echo(0.5, 0.5), 0 44100)") 1180 def echo(scaler, secs) 1181 del = make_delay((secs * srate()).round) 1182 lambda do |inval| 1183 inval + delay(del, scaler * (tap(del) + inval)) 1184 end 1185 end 1186 1187 add_help(:zecho, 1188 "zecho(scaler, secs, freq, amp) \ 1189Returns a modulated echo maker: \ 1190map_channel(zecho(0.5, 0.75, 6, 10.0), 0, 65000)") 1191 def zecho(scaler, secs, freq, amp) 1192 os = make_oscil(freq) 1193 len = (secs * srate()).round 1194 del = make_delay(len, :max_size, (len + amp + 1).to_i) 1195 lambda do |inval| 1196 inval + delay(del, scaler * (tap(del) + inval), amp * oscil(os)) 1197 end 1198 end 1199 1200 add_help(:flecho, 1201 "flecho(scaler, secs) \ 1202Returns a low-pass filtered echo maker: \ 1203map_channel(flecho(0.5, 0.9), 0, 75000)" ) 1204 def flecho(scaler, secs) 1205 flt = make_fir_filter(:order, 4, :xcoeffs, vct(0.125, 0.25, 0.25, 0.125)) 1206 del = make_delay((secs * srate()).round) 1207 lambda do |inval| 1208 inval + delay(del, fir_filter(flt, scaler * (tap(del) + inval))) 1209 end 1210 end 1211 1212 # ring-mod and am 1213 # 1214 # CLM instrument is ring-modulate.ins 1215 1216 add_help(:ring_mod, 1217 "ring_mod(freq, gliss_env) \ 1218Returns a time-varying ring-modulation filter: \ 1219map_channel(ring_mod(10, [0, 0, 1, hz2radians(100)]))") 1220 def ring_mod(freq, gliss_env) 1221 os = make_oscil(:frequency, freq) 1222 len = framples() 1223 genv = make_env(:envelope, gliss_env, :length, len) 1224 lambda do |inval| oscil(os, env(genv)) * inval end 1225 end 1226 1227 add_help(:am, 1228 "am(freq) \ 1229returns an amplitude-modulator: map_channel(am(440))") 1230 def am(freq) 1231 os = make_oscil(freq) 1232 lambda do |inval| amplitude_modulate(1.0, inval, oscil(os)) end 1233 end 1234 1235 def vibro(speed, depth) 1236 sine = make_oscil(speed) 1237 scl = 0.5 * depth 1238 offset = 1.0 - scl 1239 lambda do |inval| inval * (offset + scl * oscil(sine)) end 1240 end 1241 1242 # hello-dentist 1243 # 1244 # CLM instrument version is in clm.html 1245 1246 add_help(:hello_dentist, 1247 "hello_dentist(frq, amp, snd=false, chn=false) \ 1248Varies the sampling rate randomly, \ 1249making a voice sound quavery: hello_dentist(40.0, 0.1)") 1250 def hello_dentist(freq, amp, snd = false, chn = false) 1251 rn = make_rand_interp(:frequency, freq, :amplitude, amp) 1252 i = 0 1253 len = framples() 1254 in_data = channel2vct(0, len, snd, chn) 1255 out_len = (len * (1.0 + 2.0 * amp)).to_i 1256 out_data = make_vct(out_len) 1257 rd = make_src(:srate, 1.0, 1258 :input, lambda do |dir| 1259 val = i.between?(0, len - 1) ? in_data[i] : 0.0 1260 i += dir 1261 val 1262 end) 1263 vct2channel(out_data.map do |x| src(rd, rand_interp(rn)) end, 1264 0, len, snd, chn, false, 1265 format("%s(%s, %s", get_func_name, freq, amp)) 1266 end 1267 1268 # a very similar function uses oscil instead of rand-interp, giving 1269 # various "Forbidden Planet" sound effects: 1270 1271 add_help(:fp, 1272 "fp(sr, osamp, osfrq, snd=false, chn=false) \ 1273Varies the sampling rate via an oscil: fp(1.0, 0.3, 20)") 1274 def fp(sr, osamp, osfreq, snd = false, chn = false) 1275 os = make_oscil(:frequency, osfreq) 1276 s = make_src(:srate, sr) 1277 len = framples(snd, chn) 1278 sf = make_sampler(0, snd, chn) 1279 out_data = make_vct!(len) do 1280 src(s, osamp * oscil(os), 1281 lambda do |dir| 1282 dir > 0 ? next_sample(sf) : previous_sample(sf) 1283 end) 1284 end 1285 free_sampler(sf) 1286 vct2channel(out_data, 0, len, snd, chn, false, 1287 format("%s(%s, %s, %s", get_func_name, sr, osamp, osfreq)) 1288 end 1289 1290 # compand 1291 1292 Compand_table = vct(-1.0, -0.96, -0.9, -0.82, -0.72, -0.6, -0.45, -0.25, 1293 0.0, 0.25, 0.45, 0.6, 0.72, 0.82, 0.9, 0.96, 1.0) 1294 add_help(:compand, 1295 "compand() \ 1296Returns a compander: map_channel(compand())") 1297 def compand() 1298 lambda do |inval| 1299 array_interp(Compand_table, 8.0 + 8.0 * inval, Compand_table.length) 1300 end 1301 end 1302 1303 # shift pitch keeping duration constant 1304 # 1305 # both src and granulate take a function argument to get input 1306 # whenever it is needed. in this case, src calls granulate which 1307 # reads the currently selected file. CLM version is in expsrc.ins 1308 1309 add_help(:expsrc, 1310 "expsrc(rate, snd=false, chn=false) \ 1311Uses sampling-rate conversion and granular synthesis to produce a sound \ 1312at a new pitch but at the original tempo. \ 1313It returns a function for map_chan.") 1314 def expsrc(rate, snd = false, chn = false) 1315 gr = make_granulate(:expansion, rate) 1316 sr = make_src(:srate, rate) 1317 vsize = 1024 1318 vbeg = 0 1319 v = channel2vct(0, vsize) 1320 inctr = 0 1321 lambda do |inval| 1322 src(sr, 0.0, lambda do |dir| 1323 granulate(gr, lambda do |dr| 1324 val = v[inctr] 1325 inctr += dr 1326 if inctr >= vsize 1327 vbeg += inctr 1328 inctr = 0 1329 v = channel2vct(vbeg, vsize, snd, chn) 1330 end 1331 val 1332 end) 1333 end) 1334 end 1335 end 1336 1337 # the next (expsnd) changes the tempo according to an envelope; the 1338 # new duration will depend on the expansion envelope -- we integrate 1339 # it to get the overall expansion, then use that to decide the new 1340 # length. 1341 1342 add_help(:expsnd, 1343 "expsnd(gr_env, snd=false, chn=false) \ 1344Uses the granulate generator to change tempo \ 1345according to an envelope: expsnd([0, 0.5, 2, 2.0])") 1346 def expsnd(gr_env, snd = false, chn = false) 1347 dur = ((framples(snd, chn) / srate(snd)) * 1348 integrate_envelope(gr_env) / envelope_last_x(gr_env)) 1349 gr = make_granulate(:expansion, gr_env[1], :jitter, 0) 1350 ge = make_env(:envelope, gr_env, :duration, dur) 1351 sound_len = (srate(snd) * dur).to_i 1352 len = [sound_len, framples(snd, chn)].max 1353 sf = make_sampler(0, snd, chn) 1354 out_data = make_vct!(len) do 1355 val = granulate(gr, lambda do |dir| next_sample(sf) end) 1356 gr.increment = env(ge) 1357 val 1358 end 1359 free_sampler(sf) 1360 vct2channel(out_data, 0, len, snd, chn, false, 1361 format("%s(%s", get_func_name, gr_env.inspect)) 1362 end 1363 1364 # cross-synthesis 1365 # 1366 # CLM version is in clm.html 1367 1368 add_help(:cross_synthesis, 1369 "cross_synthesis(cross_snd, amp, fftsize, r) \ 1370Does cross-synthesis between CROSS_SND (a sound index) \ 1371and the currently selected sound: \ 1372map_channel(cross_synthesis(1, 0.5, 128, 6.0))") 1373 def cross_synthesis(cross_snd, amp, fftsize, r) 1374 freq_inc = fftsize / 2 1375 fdr = make_vct(fftsize) 1376 fdi = make_vct(fftsize) 1377 spectr = make_vct(freq_inc) 1378 inctr = 0 1379 ctr = freq_inc 1380 radius = 1.0 - r / fftsize.to_f 1381 osr = mus_srate() 1382 csr = srate() 1383 bin = csr / fftsize 1384 # The comment from examp.scm applies of course here as well: 1385 # ;; if mus-srate is 44.1k and srate is 48k, make-formant 1386 # ;; thinks we're trying to go past srate/2 1387 # ;; and in any case it's setting its formants incorrectly 1388 # ;; for the actual output srate 1389 # begin of temporary mus-srate 1390 set_mus_srate(csr) 1391 fmts = make_array(freq_inc) do |i| 1392 make_formant(i * bin, radius) 1393 end 1394 set_mus_srate(osr) 1395 # end of temporary mus-srate 1396 formants = make_formant_bank(fmts, spectr) 1397 lambda do |inval| 1398 if ctr == freq_inc 1399 fdr = channel2vct(inctr, fftsize, cross_snd, 0) 1400 inctr += freq_inc 1401 spectrum(fdr, fdi, false, 2) 1402 vct_subtract!(fdr, spectr) 1403 vct_scale!(fdr, 1.0 / freq_inc) 1404 ctr = 0 1405 end 1406 ctr += 1 1407 vct_add!(spectr, fdr) 1408 amp * formant_bank(formants, inval) 1409 end 1410 end 1411 1412 # similar ideas can be used for spectral cross-fades, etc -- for example: 1413 1414 add_help(:voiced2unvoiced, 1415 "voiced2unvoiced(amp, fftsize, r, tempo, snd=false, chn=false) \ 1416Turns a vocal sound into whispering: voiced2unvoiced(1.0, 256, 2.0, 2.0)") 1417 def voiced2unvoiced(amp, fftsize, r, tempo, snd = false, chn = false) 1418 freq_inc = fftsize / 2 1419 fdr = make_vct(fftsize) 1420 fdi = make_vct(fftsize) 1421 spectr = make_vct(freq_inc) 1422 noi = make_rand(:frequency, srate(snd) / 3.0) 1423 inctr = 0 1424 ctr = freq_inc 1425 osr = mus_srate() 1426 csr = srate() 1427 # See cross_synthesis above. 1428 # begin of temporary mus-srate 1429 set_mus_srate(csr) 1430 radius = 1.0 - r.to_f / fftsize 1431 bin = csr / fftsize 1432 len = framples(snd, chn) 1433 outlen = (len / tempo).floor 1434 hop = (freq_inc * tempo).floor 1435 out_data = make_vct([len, outlen].max) 1436 fmts = make_array(freq_inc) do |i| 1437 make_formant(i * bin, radius) 1438 end 1439 set_mus_srate(osr) 1440 # end of temporary mus-srate 1441 formants = make_formant_bank(fmts, spectr) 1442 old_peak_amp = new_peak_amp = 0.0 1443 outlen.times do |i| 1444 if ctr == freq_inc 1445 fdr = channel2vct(inctr, fftsize, snd, chn) 1446 if (pk = vct_peak(fdr)) > old_peak_amp 1447 old_peak_amp = pk 1448 end 1449 spectrum(fdr, fdi, false, 2) 1450 inctr += hop 1451 vct_subtract!(fdr, spectr) 1452 vct_scale!(fdr, 1.0 / freq_inc) 1453 ctr = 0 1454 end 1455 ctr += 1 1456 vct_add!(spectr, fdr) 1457 if (outval = formant_bank(formants, rand(noi))).abs > new_peak_amp 1458 new_peak_amp = outval.abs 1459 end 1460 out_data[i] = outval 1461 end 1462 vct_scale!(out_data, amp * (old_peak_amp / new_peak_amp)) 1463 vct2channel(out_data, 0, [len, outlen].max, snd, chn, false, 1464 format("%s(%s, %s, %s, %s", get_func_name, 1465 amp, fftsize, r, tempo)) 1466 end 1467 1468 # very similar but use sum-of-cosines (glottal pulse train?) 1469 # instead of white noise 1470 1471 add_help(:pulse_voice, 1472 "pulse_voice(cosin, freq=440.0, amp=1.0, fftsize=256, r=2.0, \ 1473snd=false, chn=false) \ 1474Use ncos to manipulate speech sounds.") 1475 def pulse_voice(cosin, freq = 440.0, amp = 1.0, 1476 fftsize = 256, r = 2.0, snd = false, chn = false) 1477 freq_inc = fftsize / 2 1478 spectr = make_vct(freq_inc) 1479 len = framples(snd, chn) 1480 osr = mus_srate() 1481 csr = srate(snd) 1482 # See cross_synthesis above. 1483 # begin of temporary mus-srate 1484 set_mus_srate(csr) 1485 bin = csr / fftsize 1486 radius = 1.0 - (r.to_f / fftsize) 1487 fmts = make_array(freq_inc) do |i| 1488 make_formant(:radius, radius, :frequency, i * bin) 1489 end 1490 formants = make_formant_bank(fmts, spectr) 1491 set_mus_srate(osr) 1492 # end of temporary mus-srate 1493 old_peak_amp = 0.0 1494 pulse = make_ncos(freq, cosin) 1495 fdr = nil 1496 inctr = 0 1497 fdi = make_vct(fftsize) 1498 inv_freq_inc = 1.0 / freq_inc 1499 out_data = make_vct!(len) do |i| 1500 if i.modulo(freq_inc) == 0 1501 fdr = channel2vct(inctr, fftsize, snd, chn) 1502 old_peak_amp = [vct_peak(fdr), old_peak_amp].max 1503 spectrum(fdr, fdi, false, 2) 1504 vct_subtract!(fdr, spectr) 1505 vct_scale!(fdr, inv_freq_inc) 1506 inctr += freq_inc 1507 end 1508 vct_add!(spectr, fdr) 1509 formant_bank(formants, ncos(pulse)) 1510 end 1511 vct_scale!(out_data, (old_peak_amp / vct_peak(out_data)) * amp) 1512 vct2channel(out_data, 0, len, snd, chn) 1513 end 1514 # pulse_voice(80, 20.0, 1.0, 1024, 0.01) 1515 # pulse_voice(80, 120.0, 1.0, 1024, 0.2) 1516 # pulse_voice(30, 240.0, 1.0, 1024, 0.1) 1517 # pulse_voice(30, 240.0, 1.0, 2048) 1518 # pulse_voice( 6, 1000.0, 1.0, 512) 1519 1520 # convolution example 1521 1522 add_help(:cnvtest, 1523 "cnvtest(snd0, snd1, amp) \ 1524Convolves snd0 and snd1, scaling by amp, \ 1525returns new max amp: cnvtest(0, 1, 0.1)") 1526 def cnvtest(snd0, snd1, amp) 1527 flt_len = framples(snd0) 1528 total_len = flt_len + framples(snd1) 1529 cnv = make_convolve(:filter, channel2vct(0, flt_len, snd0)) 1530 sf = make_sampler(0, snd1, false) 1531 out_data = make_vct!(total_len) do 1532 convolve(cnv, lambda do |dir| next_sample(sf) end) 1533 end 1534 free_sampler(sf) 1535 vct_scale!(out_data, amp) 1536 max_samp = vct_peak(out_data) 1537 vct2channel(out_data, 0, total_len, snd1) 1538 if max_samp > 1.0 1539 set_y_bounds(snd1, [-max_samp, max_samp]) 1540 end 1541 max_samp 1542 end 1543 1544 # swap selection chans 1545 1546 add_help(:swap_selection_channels, 1547 "swap_selection_channels() \ 1548Swaps the currently selected data's channels.") 1549 def swap_selection_channels 1550 if (not selection?) 1551 Snd.raise(:no_active_selection) 1552 end 1553 if selection_chans != 2 1554 Snd.raise(:wrong_number_of_channels, "need a stereo selection") 1555 end 1556 beg = selection_position() 1557 len = selection_framples() 1558 snd_chn0 = snd_chn1 = nil 1559 Snd.sounds.each do |snd| 1560 channels(snd).times do |chn| 1561 if selection_member?(snd, chn) 1562 if snd_chn0.nil? 1563 snd_chn0 = [snd, chn] 1564 elsif snd_chn1.nil? 1565 snd_chn1 = [snd, chn] 1566 break 1567 end 1568 end 1569 end 1570 end 1571 if snd_chn1.nil? 1572 Snd.raise(:wrong_number_of_channels, "need two channels to swap") 1573 end 1574 swap_channels(snd_chn0[0], snd_chn0[1], snd_chn1[0], snd_chn1[1], beg, len) 1575 end 1576 1577 # sound interp 1578 # 1579 # make-sound-interp sets up a sound reader that reads a channel at 1580 # an arbitary location, interpolating between samples if necessary, 1581 # the corresponding "generator" is sound-interp 1582 1583 add_help(:make_sound_interp, 1584 "make_sound_interp(start, snd=false, chn=false) \ 1585An interpolating reader for SND's channel CHN.") 1586 def make_sound_interp(start, snd = false, chn = false) 1587 data = channel2vct(0, false, snd, chn) 1588 size = data.length 1589 lambda do |loc| 1590 array_interp(data, loc, size) 1591 end 1592 end 1593 1594 add_help(:sound_interp, 1595 "sound_interp(func, loc) \ 1596Sample at LOC (interpolated if necessary) from FUNC \ 1597created by make_sound_interp.") 1598 def sound_interp(func, loc) 1599 func.call(loc) 1600 end 1601 1602 def sound_via_sound(snd1, snd2) 1603 intrp = make_sound_interp(0, snd1, 0) 1604 len = framples(snd1, 0) - 1 1605 rd = make_sampler(0, snd2, 0) 1606 mx = maxamp(snd2, 0) 1607 map_channel(lambda do |val| 1608 sound_interp(intrp, 1609 (len * 0.5 * 1610 (1.0 + (read_sample(rd) / mx))).floor) 1611 end) 1612 end 1613 1614 # env_sound_interp takes an envelope that goes between 0 and 1 1615 # (y-axis), and a time-scaler (1.0 = original length) and returns a 1616 # new version of the data in the specified channel that follows that 1617 # envelope (that is, when the envelope is 0 we get sample 0, when 1618 # the envelope is 1 we get the last sample, envelope = 0.5 we get 1619 # the middle sample of the sound and so on. env_sound_interp([0, 0, 1620 # 1, 1]) will return a copy of the current sound; 1621 # env_sound_interp([0, 0, 1, 1, 2, 0], 2.0) will return a new sound 1622 # with the sound copied first in normal order, then reversed. 1623 # src_sound with an envelope could be used for this effect, but it 1624 # is much more direct to apply the envelope to sound sample 1625 # positions. 1626 1627 add_help(:env_sound_interp, 1628 "env_sound_interp(env, time_scale=1.0, snd=false, chn=false) \ 1629Reads SND's channel CHN according to ENV and TIME_SCALE.") 1630 def env_sound_interp(envelope, time_scale = 1.0, snd = false, chn = false) 1631 len = framples(snd, chn) 1632 newlen = (time_scale.to_f * len).floor 1633 read_env = make_env(:envelope, envelope, :length, newlen + 1, :scaler, len) 1634 data = channel2vct(0, false, snd, chn) 1635 new_snd = Vct.new(newlen) do 1636 array_interp(data, env(read_env), len) 1637 end 1638 set_samples(0, newlen, new_snd, snd, chn, true, 1639 format("%s(%p, %s", get_func_name, envelope, time_scale), 1640 0, Current_edit_position, true) 1641 end 1642 # env_sound_interp([0, 0, 1, 1, 2, 0], 2.0) 1643 1644 add_help(:granulated_sound_interp, 1645 "granulated_sound_interp(env, time_scale=1.0, grain_len=0.1, \ 1646grain_env=[0, 0, 1, 1, 2, 1, 3, 0], out_hop=0.05, snd=false, chn=false) \ 1647Reads the given channel following ENV (as in env_sound_interp), \ 1648using grains to create the re-tempo'd read.") 1649 def granulated_sound_interp(envelope, 1650 time_scale = 1.0, 1651 grain_length = 0.1, 1652 grain_envelope = [0, 0, 1, 1, 2, 1, 3, 0], 1653 output_hop = 0.05, 1654 snd = false, 1655 chn = false) 1656 len = framples(snd, chn) 1657 newlen = (time_scale.to_f * len).floor 1658 read_env = make_env(envelope, :length, newlen, :scaler, len) 1659 sr = srate(snd).to_f 1660 grain_frames = (grain_length * sr).to_i 1661 hop_frames = (output_hop * sr).to_i 1662 num_readers = (grain_length.to_f / output_hop).ceil 1663 cur_readers = 0 1664 next_reader = 0 1665 jitter = sr * 0.005 1666 readers = Array.new(num_readers, false) 1667 grain_envs = Array.new(num_readers) do 1668 make_env(grain_envelope, :length, grain_frames) 1669 end 1670 new_snd = Vct.new(newlen, 0.0) 1671 0.step(newlen, hop_frames) do |i| 1672 stop = [newlen, hop_frames + i].min 1673 read_env.location = i 1674 position_in_original = env(read_env) 1675 mx = [0, (position_in_original + mus_random(jitter)).round].max 1676 readers[next_reader] = make_sampler(mx, snd, chn) 1677 grain_envs[next_reader].reset 1678 next_reader += 1 1679 next_reader %= num_readers 1680 if cur_readers < next_reader 1681 cur_readers = next_reader 1682 end 1683 (0...cur_readers).each do |j| 1684 en = grain_envs[j] 1685 rd = readers[j] 1686 (i...stop).each do |k| 1687 new_snd[k] = env(en) * rd.call() 1688 end 1689 end 1690 end 1691 set_samples(0, newlen, new_snd, snd, chn, true, 1692 format("%s(%p, %s, %s, %p, %s", 1693 get_func_name, 1694 envelope, 1695 time_scale, 1696 grain_length, 1697 grain_envelope, 1698 output_hop), 0, Current_edit_position, true) 1699 end 1700 # granulated_sound_interp([0, 0, 1, 0.1, 2, 1], 1.0, 0.2, [0, 0, 1, 1, 2, 0]) 1701 # granulated_sound_interp([0, 0, 1, 1], 2.0) 1702 # granulated_sound_interp([0, 0, 1, 0.1, 2, 1], 1.0, 0.2, [0, 0, 1, 1, 2, 0], 1703 # 0.02) 1704 1705 # filtered-env 1706 1707 add_help(:filtered_env, 1708 "filtered_env(env, snd=false, chn=false) \ 1709Is a time-varying one-pole filter: when ENV is at 1.0, no filtering, \ 1710as ENV moves to 0.0, low-pass gets more intense; \ 1711amplitude and low-pass amount move together.") 1712 def filtered_env(en, snd = false, chn = false) 1713 flt = make_one_pole(1.0, 0.0) 1714 amp_env = make_env(:envelope, en, :length, framples()) 1715 map_channel(lambda do |val| 1716 env_val = env(amp_env) 1717 set_mus_xcoeff(flt, 0, env_val) 1718 set_mus_ycoeff(flt, 1, env_val - 1.0) 1719 one_pole(flt, env_val * val) 1720 end, 0, false, snd, chn, false, 1721 format("%s(%s", get_func_name, en.inspect)) 1722 end 1723 1724 # lisp graph with draggable x axis 1725 1726 class Mouse 1727 def initialize 1728 @down = 0 1729 @pos = 0.0 1730 @x1 = 1.0 1731 end 1732 1733 def press(snd, chn, button, state, x, y) 1734 @pos = x / @x1 1735 @down = @x1 1736 end 1737 1738 def drag(snd, chn, button, state, x, y) 1739 xnew = x / @x1 1740 @x1 = [1.0, [0.1, @down + (@pos - xnew)].max].min 1741 graph(make_vct!((100 * @x1).floor) do |i| i * 0.01 end, "ramp", 0.0, @x1) 1742 end 1743 end 1744 1745=begin 1746 let(Mouse.new) do |mouse| 1747 $mouse_drag_hook.add_hook!("Mouse") do |snd, chn, button, state, x, y| 1748 mouse.drag(snd, chn, button, state, x, y) 1749 end 1750 $mouse_press_hook.add_hook!("Mouse") do |snd, chn, button, state, x, y| 1751 mouse.press(snd, chn, button, state, x, y) 1752 end 1753 end 1754=end 1755 1756 # pointer focus within Snd 1757 # 1758 # $mouse_enter_graph_hook.add_hook!("focus") do |snd, chn| 1759 # focus_widget(channel_widgets(snd, chn)[0]) 1760 # end 1761 # $mouse_enter_listener_hook.add_hook!("focus") do |widget| 1762 # focus_widget(widget) 1763 # end 1764 # $mouse_enter_text_hook.add_hook!("focus") do |widget| 1765 # focus_widget(widget) 1766 # end 1767 1768 # View: Files dialog chooses which sound is displayed 1769 # 1770 # by Anders Vinjar 1771 1772 add_help(:files_popup_buffer, 1773 "files_popup_buffer(type, position, name) \ 1774Hides all sounds but the one the mouse touched in the current files list. \ 1775Use with $mouse_enter_label_hook. 1776$mouse_enter_label_hook.add_hook!(\"files-popup\") do |type, position, name| 1777 files_popup_buffer(type, position, name) 1778end") 1779 def files_popup_buffer(type, position, name) 1780 if snd = find_sound(name) 1781 curr_buffer = Snd.snd 1782 vals = widget_size(sound_widgets(curr_buffer)[0]) 1783 height = vals[1] 1784 Snd.sounds.each do |s| hide_widget(sound_widgets(s)[0]) end 1785 show_widget(sound_widgets(snd)[0]) 1786 set_widget_size(sound_widgets(snd)[0], [widht, height]) 1787 select_sound(snd) 1788 end 1789 end 1790 1791 # remove-clicks 1792 1793 add_help(:find_click, 1794 "find_click(loc) \ 1795Finds the next click starting at LOC.") 1796 def find_click(loc) 1797 reader = make_sampler(loc, false, false) 1798 samp0 = samp1 = samp2 = 0.0 1799 samps = make_vct(10) 1800 len = framples() 1801 samps_ctr = 0 1802 (loc...len).each do |ctr| 1803 samp0, samp1, samp2 = samp1, samp2, next_sample(reader) 1804 samps[samps_ctr] = samp0 1805 if samps_ctr < 9 1806 samps_ctr += 1 1807 else 1808 samps_ctr = 0 1809 end 1810 local_max = [0.1, vct_peak(samps)].max 1811 if ((samp0 - samp1).abs > local_max) and 1812 ((samp1 - samp2).abs > local_max) and 1813 ((samp0 - samp2).abs < (local_max / 2)) 1814 return ctr - 1 1815 end 1816 end 1817 false 1818 end 1819 1820 add_help(:remove_clicks, 1821 "remove_clicks() \ 1822Tries to find and smooth-over clicks.") 1823 def remove_clicks 1824 loc = 0 1825 while (click = find_click(loc)) 1826 smooth_sound(click - 2, 4) 1827 loc = click + 2 1828 end 1829 end 1830 1831 # searching examples (zero+, next-peak) 1832 1833 add_help(:search_for_click, 1834 "search_for_click() \ 1835Looks for the next click (for use with C-s).") 1836 def search_for_click 1837 samp0 = samp1 = samp2 = 0.0 1838 samps = Vct.new(10) 1839 sctr = 0 1840 lambda do |val| 1841 samp0, samp1, samp2 = samp1, samp2, val 1842 samps[sctr] = val 1843 sctr += 1 1844 if sctr >= 10 then sctr = 0 end 1845 local_max = [0.1, samps.peak].max 1846 if ((samp0 - samp1).abs >= local_max) and 1847 ((samp1 - samp2).abs >= local_max) and 1848 ((samp0 - samp2).abs <= (local_max / 2)) 1849 -1 1850 else 1851 false 1852 end 1853 end 1854 end 1855 1856 add_help(:zero_plus, 1857 "zero_plus() \ 1858Finds the next positive-going \ 1859zero crossing (if searching forward) (for use with C-s).") 1860 def zero_plus 1861 lastn = 0.0 1862 lambda do |n| 1863 rtn = lastn < 0.0 and n >= 0.0 and -1 1864 lastn = n 1865 rtn 1866 end 1867 end 1868 1869 add_help(:next_peak, 1870 "next_peak() \ 1871Finds the next max or min point \ 1872in the time-domain waveform (for use with C-s).") 1873 def next_peak 1874 last0 = last1 = false 1875 lambda do |n| 1876 rtn = number?(last0) and 1877 ((last0 < last1 and last1 > n) or (last0 > last1 and last1 < n)) and -1 1878 last0, last1 = last1, n 1879 rtn 1880 end 1881 end 1882 1883 add_help(:find_pitch, 1884 "find_pitch(pitch) \ 1885Finds the point in the current sound where PITCH (in Hz) \ 1886predominates -- C-s find_pitch(300). \ 1887In most cases, \ 1888this will be slightly offset from the true beginning of the note.") 1889 def find_pitch(pitch) 1890 interpolated_peak_offset = lambda do |la, ca, ra| 1891 pk = 0.001 + [la, ca, ra].max 1892 logla = log([la, 0.0000001].max / pk) / log(10) 1893 logca = log([ca, 0.0000001].max / pk) / log(10) 1894 logra = log([ra, 0.0000001].max / pk) / log(10) 1895 0.5 * (logla - logra) / ((logla + logra) - 2 * logca) 1896 end 1897 data = make_vct(transform_size) 1898 data_loc = 0 1899 lambda do |n| 1900 data[data_loc] = n 1901 data_loc += 1 1902 rtn = false 1903 if data_loc == transform_size 1904 data_loc = 0 1905 if vct_peak(data) > 0.001 1906 spectr = snd_spectrum(data, Rectangular_window, transform_size) 1907 pk = 0.0 1908 pkloc = 0 1909 (transform_size / 2).times do |i| 1910 if spectr[i] > pk 1911 pk = spectr[i] 1912 pkloc = i 1913 end 1914 end 1915 pit = (pkloc + (pkloc > 0 ? 1916 interpolated_peak_offset.call(*spectr[pkloc - 1, 3]) : 1917 0.0) * srate()) / transform_size 1918 if (pitch - pit).abs < srate / (2 * transform_size) 1919 rtn = -(transform_size / 2) 1920 end 1921 end 1922 vct_fill!(data, 0.0) 1923 end 1924 rtn 1925 end 1926 end 1927 1928 # file2vct and a sort of cue-list, I think 1929 1930 add_help(:file2vct, 1931 "file2vct(file) \ 1932Returns a vct with FILE's data.") 1933 def file2vct(file) 1934 len = mus_sound_framples(file) 1935 reader = make_sampler(0, file) 1936 data = make_vct!(len) do next_sample(reader) end 1937 free_sampler(reader) 1938 data 1939 end 1940 1941 add_help(:add_notes, 1942 "add_notes(notes, snd=false, chn=false) \ 1943Adds (mixes) NOTES which is a list of lists of the form: \ 1944[file, offset=0.0, amp=1.0] starting at the cursor in the \ 1945currently selected channel: \ 1946add_notes([[\"oboe.snd\"], [\"pistol.snd\", 1.0, 2.0]])") 1947 def add_notes(notes, snd = false, chn = false) 1948 start = cursor(snd, chn) 1949 as_one_edit_rb("%s(%s", get_func_name, notes.inspect) do 1950 (notes or []).each do |note| 1951 file, offset, amp = note 1952 beg = start + (srate(snd) * (offset or 0.0)).floor 1953 if amp and amp != 1.0 1954 mix_vct(vct_scale!(file2vct(file), amp), beg, snd, chn, false, 1955 format("%s(%s", get_func_name, notes.inspect)) 1956 else 1957 mix(file, beg, 0, snd, chn, false) 1958 end 1959 end 1960 end 1961 end 1962 1963 add_help(:region_play_list, 1964 "region_play_list(data) \ 1965DATA is list of lists [[time, reg], ...], time in secs, \ 1966setting up a sort of play list: \ 1967region_play_list([[0.0, 0], [0.5, 1], [1.0, 2], [1.0, 0]])") 1968 def region_play_list(data) 1969 (data or []).each do |tm, rg| 1970 tm = (1000.0 * tm).floor 1971 if region?(rg) 1972 call_in(tm, lambda do | | play(rg) end) 1973 end 1974 end 1975 end 1976 1977 add_help(:region_play_sequence, 1978 "region_play_sequence(data) \ 1979DATA is list of region ids which will be played one after the other: \ 1980region_play_sequence([0, 2, 1])") 1981 def region_play_sequence(data) 1982 time = 0.0 1983 region_play_list(data.map do |id| 1984 cur = time 1985 time = time + region_framples(id) / region_srate(id) 1986 [cur, id] 1987 end) 1988 end 1989 1990 # replace-with-selection 1991 1992 add_help(:replace_with_selection, 1993 "replace_with_selection() \ 1994Replaces the samples from the cursor with the current selection.") 1995 def replace_with_selection 1996 beg = cursor 1997 len = selection_framples() 1998 delete_samples(beg, len) 1999 insert_selection(beg) 2000 end 2001 2002 # explode-sf2 2003 2004 add_help(:explode_sf2, 2005 "explode_sf2() \ 2006Turns the currently selected soundfont file into \ 2007a bunch of files of the form sample-name.aif.") 2008 def explode_sf2 2009 (soundfont_info() or []).each do |name, start, loop_start, loop_end| 2010 filename = name + ".aif" 2011 if selection? 2012 set_selection_member?(false, true) 2013 end 2014 set_selection_member?(true) 2015 set_selection_position(start) 2016 set_selection_framples(framples() - start) 2017 save_selection(filename, selection_srate(), Mus_bshort, Mus_aifc) 2018 temp = open_sound(filename) 2019 set_sound_loop_info([loop_start, loop_end], temp) 2020 close_sound(temp) 2021 end 2022 end 2023 2024 # open-next-file-in-directory 2025 2026 class Next_file 2027 def initialize 2028 @last_file_opened = "" 2029 @current_directory = "" 2030 @current_sorted_files = [] 2031 end 2032 2033 def open_next_file_in_directory 2034 unless $open_hook.member?("open-next-file-in-directory") 2035 $open_hook.add_hook!("open-next-file-in-directory") do |fname| 2036 self.get_current_directory(fname) 2037 end 2038 end 2039 if @last_file_opened.empty? and sounds 2040 @last_file_opened = file_name(Snd.snd) 2041 end 2042 if @current_directory.empty? 2043 unless sounds 2044 get_current_files(Dir.pwd) 2045 else 2046 get_current_files(File.split(@last_file_opened).first) 2047 end 2048 end 2049 if @current_sorted_files.empty? 2050 Snd.raise(:no_such_file) 2051 else 2052 next_file = find_next_file 2053 if find_sound(next_file) 2054 Snd.raise(:file_already_open, next_file) 2055 else 2056 sounds and close_sound(Snd.snd) 2057 open_sound(next_file) 2058 end 2059 end 2060 true 2061 end 2062 2063 private 2064 def find_next_file 2065 choose_next = @last_file_opened.empty? 2066 just_filename = File.basename(@last_file_opened) 2067 f = callcc do |ret| 2068 @current_sorted_files.each do |file| 2069 ret.call(file) if choose_next 2070 if file == just_filename 2071 choose_next = true 2072 end 2073 end 2074 @current_sorted_files[0] # wrapped around 2075 end 2076 @current_directory + "/" + f 2077 end 2078 2079 def get_current_files(dir) 2080 @current_directory = dir 2081 @current_sorted_files = sound_files_in_directory(dir).sort 2082 end 2083 2084 def get_current_directory(filename) 2085 Snd.display(@last_file_opened = filename) 2086 new_path = File.split(mus_expand_filename(filename)).first 2087 if @current_directory.empty? or @current_directory != new_path 2088 get_current_files(new_path) 2089 end 2090 false 2091 end 2092 end 2093 2094 def click_middle_button_to_open_next_file_in_directory 2095 nf = Next_file.new 2096 $mouse_click_hook.add_hook!("next-file") do |s, c, button, st, x, y, ax| 2097 if button == 2 2098 nf.open_next_file_in_directory 2099 end 2100 end 2101 end 2102 2103 # chain-dsps 2104 2105 def chain_dsps(start, dur, *dsps) 2106 dsp_chain = dsps.map do |gen| 2107 if array?(gen) 2108 make_env(:envelope, gen, :duration, dur) 2109 else 2110 gen 2111 end 2112 end 2113 run_instrument(start, dur) do 2114 val = 0.0 2115 dsp_chain.each do |gen| 2116 if env?(gen) 2117 val *= gen.run 2118 elsif readin?(gen) 2119 val += gen.run 2120 else 2121 val = gen.run(val) 2122 end 2123 end 2124 val 2125 end 2126 end 2127 2128=begin 2129 with_sound() do 2130 chain_dsps(0, 1.0, [0, 0, 1, 1, 2, 0], 2131 make_oscil(:frequency, 440)) 2132 chain_dsps(0, 1.0, [0, 0, 1, 1, 2, 0], 2133 make_one_pole(0.5), make_readin("oboe.snd")) 2134 chain_dsps(0, 1.0, [0, 0, 1, 1, 2, 0], 2135 let(make_oscil(:frequency, 220), 2136 make_oscil(:frequency, 440)) do |osc1, osc2| 2137 lambda do |val| 2138 osc1.run(val) + osc2.run(2.0 * val) 2139 end 2140 end) 2141 end 2142=end 2143 2144 # re-order channels 2145 2146 def scramble_channels(*new_order) 2147 len = new_order.length 2148 swap_once = lambda do |current, desired, n| 2149 if n != len 2150 cur_orig, cur_cur = current[n][0, 2] 2151 dst = desired[n] 2152 if cur_orig != dst 2153 swap_channels(false, cur_cur, false, dst) 2154 current[dst][0] = cur_orig 2155 end 2156 swape_once.call(current, desired, n + 1) 2157 end 2158 end 2159 swap_once.call(make_array(len) do |i| [i, i] end, new_order, 0) 2160 end 2161 2162 def scramble_channel(silence) 2163 buffer = make_moving_average(128) 2164 silence = silence / 128.0 2165 edges = [] 2166 in_silence = true 2167 old_max = max_regions 2168 old_tags = with_mix_tags 2169 set_max_regions(1024) 2170 set_with_mix_tags(false) 2171 rd = make_sampler() 2172 framples().times do |i| 2173 y = next_sample(rd) 2174 sum_of_squares = moving_average(buffer, y * y) 2175 now_silent = (sum_of_squares < silence) 2176 if now_silent != in_silence 2177 edges.push(i) 2178 end 2179 in_silence = now_silent 2180 end 2181 edges.push(framples()) 2182 len = edges.length 2183 start = 0 2184 pieces = Array.new(len) do |i| 2185 en = edges[i] 2186 re = make_region(start, en) 2187 start = en 2188 re 2189 end 2190 start = 0 2191 fnc = lambda do | | 2192 scale_by(0.0) 2193 len.times do |i| 2194 this = random(len) 2195 reg = pieces[this] 2196 pieces[this] = false 2197 unless reg 2198 (this + 1).upto(len - 1) do |j| 2199 reg = pieces[j] 2200 if reg 2201 pieces[j] = false 2202 break 2203 end 2204 end 2205 unless reg 2206 (this - 1).downto(0) do |j| 2207 reg = pieces[j] 2208 if reg 2209 pieces[j] = false 2210 break 2211 end 2212 end 2213 end 2214 end 2215 mix_region(reg, start) 2216 start += framples(reg) 2217 forget_region(reg) 2218 end 2219 end 2220 as_one_edit(fnc) 2221 set_max_regions(old_max) 2222 set_with_mix_tags(old_tags) 2223 end 2224 # scramble_channel(0.01) 2225 2226 # reorder blocks within channel 2227 2228 add_help(:reverse_by_blocks, 2229 "reverse_by_blocks(block_len, snd=false, chn=false) \ 2230Divide sound into block-len blocks, recombine blocks in reverse order.") 2231 def reverse_by_blocks(block_len, snd = false, chn = false) 2232 len = framples(snd, chn) 2233 num_blocks = (len / (srate(snd).to_f * block_len)).floor 2234 if num_blocks > 1 2235 actual_block_len = len / num_blocks 2236 rd = make_sampler(len - actual_block_len, snd, chn) 2237 beg = 0 2238 ctr = 1 2239 map_channel(lambda do |y| 2240 val = read_sample(rd) 2241 if beg < 10 2242 val = val * beg * 0.1 2243 else 2244 if beg > actual_block_len - 10 2245 val = val * (actual_block_len - beg) * 0.1 2246 end 2247 end 2248 beg += 1 2249 if beg == actual_block_len 2250 ctr += 1 2251 beg = 0 2252 rd = make_sampler([len - ctr * actual_block_len, 0].max, 2253 snd, chn) 2254 end 2255 val 2256 end, 0, false, 2257 snd, chn, false, format("%s(%s", get_func_name, block_len)) 2258 end 2259 end 2260 2261 add_help(:reverse_within_blocks, 2262 "reverse_within_blocks(block_len, snd=false, chn=false) \ 2263Divide sound into blocks, recombine in order, \ 2264but each block internally reversed.") 2265 def reverse_within_blocks(block_len, snd = false, chn = false) 2266 len = framples(snd, chn) 2267 num_blocks = (len / (srate(snd).to_f * block_len)).floor 2268 if num_blocks > 1 2269 actual_block_len = len / num_blocks 2270 no_clicks_env = [0.0, 0.0, 0.01, 1.0, 0.99, 1.0, 1.0, 0.0] 2271 as_one_edit(lambda do | | 2272 0.step(len, actual_block_len) do |beg| 2273 reverse_channel(beg, actual_block_len, snd, chn) 2274 env_channel(no_clicks_env, beg, actual_block_len, 2275 snd, chn) 2276 end 2277 end, format("%s(%s", get_func_name, block_len)) 2278 else 2279 reverse_channel(0, false, snd, chn) 2280 end 2281 end 2282 2283 def segment_maxamp(name, beg, dur) 2284 mx = 0.0 2285 rd = make_sampler(beg, name) 2286 dur.times do mx = [mx, next_sample(rd).abs].max end 2287 free_sampler(rd) 2288 mx 2289 end 2290 2291 def segment_sound(name, high, low) 2292 len = mus_sound_framples(name) 2293 reader = make_sampler(0, name) 2294 avg = make_moving_average(:size, 128) 2295 lavg = make_moving_average(:size, 2048) 2296 segments = Vct.new(100) 2297 segctr = 0 2298 possible_end = 0 2299 in_sound = false 2300 len.times do |i| 2301 samp = next_sample(reader).abs 2302 val = moving_average(avg, samp) 2303 lval = moving_average(lavg, samp) 2304 if in_sound 2305 if val < low 2306 possible_end = i 2307 if lval < low 2308 segments[segctr] = possible_end + 128 2309 segctr += 1 2310 in_sound = false 2311 end 2312 else 2313 if val > high 2314 segments[segctr] = i - 128 2315 segctr += 1 2316 in_sound = true 2317 end 2318 end 2319 end 2320 end 2321 free_sampler(reader) 2322 if in_sound 2323 segments[segctr] = len 2324 [segctr + 1, segments] 2325 else 2326 [segctr, segments] 2327 end 2328 end 2329 2330 def do_one_directory(fd, dir_name, ins_name, high = 0.01, low = 0.001) 2331 snd_print("# #{dir_name}") 2332 sound_files_in_directory(dir_name).each do |sound| 2333 sound_name = dir_name + "/" + sound 2334 boundary_data = segment_sound(sound_name, high, low) 2335 segments, boundaries = boundary_data 2336 fd.printf("\n\n# ", sound) 2337 fd.printf("(%s %s", ins_name, sound_name.inspect) 2338 0.step(segments, 2) do |bnd| 2339 segbeg = boundaries[bnd].to_i 2340 segbeg = boundaries[bnd + 1].to_i 2341 fd.printf("(%s %s %s)", segbeg, segdur, 2342 segment_maxamp(sound_name, segbeg, segdur)) 2343 fd.printf(")") 2344 end 2345 mus_sound_forget(sound_name) 2346 end 2347 end 2348 2349 def sound2segment_data(main_dir, output_file = "sounds.data") 2350 File.open(output_file, "w") do |fd| 2351 old_fam = with_file_monitor 2352 set_with_file_monitor(false) 2353 fd.printf("# sound data from %s", main_dir.inspect) 2354 if main_dir[-1] != "/" then main_dir += "/" end 2355 Dir[main_dir].each do |dir| 2356 ins_name = dir.downcase.tr(" ", "") 2357 fd.printf("\n\n# ---------------- %s ----------------", dir) 2358 if dir == "Piano" 2359 Dir[main_dir + dir].each do |inner_dir| 2360 do_one_directory(fd, main_dir + dir + "/" + inner_dir, 2361 ins_name, 0.001, 0.0001) 2362 end 2363 else 2364 do_one_directory(fd, main_dir + dir, ins_name) 2365 end 2366 end 2367 set_with_file_monitor(old_fam) 2368 end 2369 end 2370 # sounds2segment_data(ENV['HOME'] + "/.snd.d/iowa/sounds/", "iowa.data") 2371 2372 add_help(:channel_clipped?, 2373 "channel_clipped?(snd=false, chn=false) \ 2374Returns true and a sample number if it finds clipping.") 2375 def channel_clipped?(snd = false, chn = false) 2376 last_y = 0.0 2377 scan_channel(lambda do |y| 2378 result = (y.abs >= 0.9999 and last_y.abs >= 0.9999) 2379 last_y = y 2380 result 2381 end, 0, false, snd, chn) 2382 end 2383 2384 # scan-sound 2385 2386 def scan_sound(func, beg = 0, dur = false, snd = false) 2387 if sound?(index = Snd.snd(snd)) 2388 if (chns = channels(index)) == 1 2389 scan_channel(lambda do |y| func.call(y, 0) end, beg, dur, index, 0) 2390 else 2391 len = framples(index) 2392 fin = (dur ? [len, beg + dur].min : len) 2393 readers = make_array(chns) do |chn| make_sampler(beg, index, chn) end 2394 result = false 2395 beg.upto(fin) do |i| 2396 local_result = true 2397 readers.each_with_index do |rd, chn| 2398 local_result = (func.call(rd.call, chn) and local_result) 2399 end 2400 if local_result 2401 result = [true, i] 2402 break 2403 end 2404 end 2405 result 2406 end 2407 else 2408 Snd.raise(:no_such_sound, get_func_name, snd) 2409 end 2410 end 2411 2412 def scan_sound_rb(beg = 0, dur = false, snd = false, &func) 2413 scan_sound(func, beg, dur, snd) 2414 end 2415end 2416 2417include Examp 2418 2419module Moog 2420 class Moog_filter < Musgen 2421 Gaintable = vct(0.999969, 0.990082, 0.980347, 0.970764, 0.961304, 0.951996, 2422 0.94281, 0.933777, 0.924866, 0.916077, 0.90741, 0.898865, 2423 0.890442, 0.882141, 0.873962, 0.865906, 0.857941, 0.850067, 2424 0.842346, 0.834686, 0.827148, 0.819733, 0.812378, 0.805145, 2425 0.798004, 0.790955, 0.783997, 0.77713, 0.770355, 0.763672, 2426 0.75708, 0.75058, 0.744141, 0.737793, 0.731537, 0.725342, 2427 0.719238, 0.713196, 0.707245, 0.701355, 0.695557, 0.689819, 2428 0.684174, 0.678558, 0.673035, 0.667572, 0.66217, 0.65686, 2429 0.651581, 0.646393, 0.641235, 0.636169, 0.631134, 0.62619, 2430 0.621277, 0.616425, 0.611633, 0.606903, 0.602234, 0.597626, 2431 0.593048, 0.588531, 0.584045, 0.579651, 0.575287, 0.570953, 2432 0.566681, 0.562469, 0.558289, 0.554169, 0.550079, 0.546051, 2433 0.542053, 0.538116, 0.53421, 0.530334, 0.52652, 0.522736, 2434 0.518982, 0.515289, 0.511627, 0.507996, 0.504425, 0.500885, 2435 0.497375, 0.493896, 0.490448, 0.487061, 0.483704, 0.480377, 2436 0.477081, 0.473816, 0.470581, 0.467377, 0.464203, 0.46109, 2437 0.457977, 0.454926, 0.451874, 0.448883, 0.445892, 0.442932, 2438 0.440033, 0.437134, 0.434265, 0.431427, 0.428619, 0.425842, 2439 0.423096, 0.42038, 0.417664, 0.415009, 0.412354, 0.409729, 2440 0.407135, 0.404572, 0.402008, 0.399506, 0.397003, 0.394501, 2441 0.392059, 0.389618, 0.387207, 0.384827, 0.382477, 0.380127, 2442 0.377808, 0.375488, 0.37323, 0.370972, 0.368713, 0.366516, 2443 0.364319, 0.362122, 0.359985, 0.357849, 0.355713, 0.353607, 2444 0.351532, 0.349457, 0.347412, 0.345398, 0.343384, 0.34137, 2445 0.339417, 0.337463, 0.33551, 0.333588, 0.331665, 0.329773, 2446 0.327911, 0.32605, 0.324188, 0.322357, 0.320557, 0.318756, 2447 0.316986, 0.315216, 0.313446, 0.311707, 0.309998, 0.308289, 2448 0.30658, 0.304901, 0.303223, 0.301575, 0.299927, 0.298309, 2449 0.296692, 0.295074, 0.293488, 0.291931, 0.290375, 0.288818, 2450 0.287262, 0.285736, 0.284241, 0.282715, 0.28125, 0.279755, 2451 0.27829, 0.276825, 0.275391, 0.273956, 0.272552, 0.271118, 2452 0.269745, 0.268341, 0.266968, 0.265594, 0.264252, 0.262909, 2453 0.261566, 0.260223, 0.258911, 0.257599, 0.256317, 0.255035, 2454 0.25375) 2455 Freqtable = [0, -1, 2456 0.03311111, -0.9, 2457 0.06457143, -0.8, 2458 0.0960272, -0.7, 2459 0.127483, -0.6, 2460 0.1605941, -0.5, 2461 0.1920544, -0.4, 2462 0.22682086, -0.3, 2463 0.2615873, -0.2, 2464 0.29801363, -0.1, 2465 0.33278003, -0.0, 2466 0.37086168, 0.1, 2467 0.40893877, 0.2, 2468 0.4536417, 0.3, 2469 0.5, 0.4, 2470 0.5463583, 0.5, 2471 0.5943719, 0.6, 2472 0.6556281, 0.7, 2473 0.72185487, 0.8, 2474 0.8096009, 0.9, 2475 0.87913835, 0.95, 2476 0.9933787, 1, 2477 1, 1] 2478 2479 def initialize(freq, q) 2480 super() 2481 @frequency = freq 2482 @Q = q 2483 @state = make_vct(4) 2484 @A = 0.0 2485 @freqtable = envelope_interp(freq / (srate() * 0.5), Freqtable) 2486 end 2487 attr_reader :frequency, :state, :freqtable, :A 2488 attr_accessor :Q 2489 2490 def inspect 2491 format("%s.new(%s, %s)", self.class, @frequency, @Q) 2492 end 2493 2494 def to_s 2495 format("#<%s freq: %1.3f, Q: %s>", self.class, @frequency, @Q) 2496 end 2497 2498 def run_func(val1 = 0.0, val2 = 0.0) 2499 filter(val1) 2500 end 2501 2502 def frequency=(freq) 2503 @freqtable = envelope_interp(freq / (srate() * 0.5), Freqtable) 2504 @frequency = freq 2505 end 2506 2507 def filter(insig) 2508 a = 0.25 * (insig - @A) 2509 @state.map! do |st| 2510 new_a = saturate(a + @freqtable * (a - st)) 2511 a = saturate(new_a + st) 2512 new_a 2513 end 2514 ix = @freqtable * 99.0 2515 ixint = ix.floor 2516 ixfrac = ix - ixint 2517 @A = a * @Q * 2518 ((1.0 - ixfrac) * Gaintable[ixint + 99] + 2519 ixfrac * Gaintable[ixint + 100]) 2520 a 2521 end 2522 2523 private 2524 def saturate(x) 2525 [[x, -0.95].max, 0.95].min 2526 end 2527 end 2528 2529 add_help(:make_moog_filter, 2530 "make_moog_filter(freq=440.0, Q=0) \ 2531Makes a new moog_filter generator. \ 2532FREQUENCY is the cutoff in Hz, \ 2533Q sets the resonance: 0 = no resonance, 1: oscillates at FREQUENCY.") 2534 def make_moog_filter(freq = 440.0, q = 0) 2535 Moog_filter.new(freq, q) 2536 end 2537 2538 add_help(:moog_filter, 2539 "moog_filter(moog, insig=0.0) \ 2540Is the generator associated with make_moog_filter.") 2541 def moog_filter(mg, insig = 0.0) 2542 mg.filter(insig) 2543 end 2544 2545 def moog(freq, q) 2546 mg = Moog_filter.new(freq, q) 2547 lambda do |inval| mg.filter(inval) end 2548 end 2549end 2550 2551include Moog 2552 2553# examp.rb ends here 2554