#!/usr/bin/env ruby -w # grani.rb -- grani.ins CL --> Ruby # Translator: Michael Scholz # Created: 05/02/01 00:47:00 # Changed: 14/11/13 05:03:02 # Original header: # ;;; grani: a granular synthesis instrument # ;;; by Fernando Lopez-Lezcano # ;;; http://ccrma.stanford.edu/~nando/clm/grani/ # ;;; # ;;; Original grani.ins instrument written for the 220a Course by # ;;; Fernando Lopez-Lezcano & Juan Pampin, November 6 1996 # ;;; # ;;; Mar 21 1997: working with hop and grain-dur envelopes # ;;; Mar 22 1997: working with src envelope (grain wise) & src spread # ;;; Jan 26 1998: started work on new version # ;;; Nov 7 1998: input soundfile duration calculation wrong # ;;; Nov 10 1998: bug in in-samples (thanks to Kristopher D. Giesing # ;;; for this one) # ;;; Dec 20 1998: added standard locsig code # ;;; Feb 19 1999: added "nil" as default value of where to avoid warning # ;;; (by bill) # ;;; Jan 10 2000: added input-channel to select which channel of the input # ;;; file to process. # ;;; added grain-start-in-seconds to be able to specify input # ;;; file locations in seconds for the grain-start envelope # ;;; May 06 2002: fixed array passing of where-bins in clisp # ;;; (reported by Charles Nichols and jennifer l doering) # ;;; Mar 27 2003: added option for sending grains to all channels # ;;; (requested by Oded Ben-Tal) require "ws" require "env" # ;;; calculate a random spread around a center of 0 def random_spread(spread) spread.nonzero? ? (random(spread) - spread / 2.0) : 0.0 end # ;;; create a constant envelope if argument is a number def envelope_or_number(val) case val when Numeric [0, val, 1, val] when Vct vct2list(val) when Array val else error("%s: Number, Array, or Vct required", get_func_name()) end end # ;;; create a vct from an envelope def make_gr_env(env, length = 512) length_1 = (length - 1).to_f make_vct!(length) do |i| envelope_interp(i / length_1, env) end end # ;;; Grain envelopes def raised_cosine(*args) duty_cycle = get_args(args, :duty_cycle, 100) length = get_args(args, :length, 128) active = length * duty_cycle.to_f * 0.01 incr = PI / (active - 1.0) start = (length - active) / 2.0 fin = (length + active) / 2.0 s = -1 make_vct!(length) do |i| sine = if i >= start and i < fin s += 1 sin(s * incr) else 0.0 end sine * sine end end # ;;;========================================================================= # ;;; Granular synthesis instrument # ;;;========================================================================= # # ;;; input-channel: # ;;; from which channel in the input file are samples read # ;;; amp-envelope: # ;;; amplitude envelope for the note # ;;; grain-envelope: # ;;; grain-envelope-end: # ;;; envelopes for each individual grain. The envelope applied in the result # ;;; of interpolating both envelopes. The interpolation is controlled by # ;;; grain-envelope-trasition. If "grain-envelope-end" is nil interpolation # ;;; is turned off and only grain-envelope is applied to the grains. # ;;; grain-envelope-trasition: # ;;; an enveloper that controls the interpolation between the two # ;;; grain envelopes # ;;; 0 -> selects "grain-envelope" # ;;; 1 -> selects "grain-envelope-end" # ;;; grain-envelope-array-size # ;;; size of the array passed to make-table-lookup # ;;; grain-duration: # ;;; envelope that controls grain duration in seconds # ;;; srate-linear: # ;;; t -> sample rate envelope is linear # ;;; nil -> sample rate envelope is exponential # ;;; srate: # ;;; envelope that controls sample rate conversion. The envelope is an # ;;; exponential envelope, the base and error bound of the conversion # ;;; are controlled by "srate-base" and "srate-error". # ;;; srate-spread: # ;;; random spread of sample rate conversion around "srate" # ;;; srate-base: # ;;; base for the exponential conversion # ;;; for example: base = (expt 2 (/ 12)) creates a semitone envelope # ;;; srate-error: # ;;; error bound for the exponential conversion. # ;;; grain-start: # ;;; envelope that determines the starting point of the current grain in # ;;; the input file. "y"->0 starts the grain at the beginning of the input # ;;; file. "y"->1 starts the grain at the end of the input file. # ;;; grain-start-spread: # ;;; random spread around the value of "grain-start" # ;;; grain-start-in-seconds: # ;;; nil -> grain-start y envelope expressed in percent of the duration # ;;; of the input file # ;;; t -> grain-start y envelope expressed in seconds # ;;; grain-density: # ;;; envelope that controls the number of grains per second generated # ;;; in the output file # ;;; grain-density-spread: Grani_to_locsig = 0 Grani_to_grain_duration = 1 Grani_to_grain_start = 2 Grani_to_grain_sample_rate = 3 Grani_to_grain_random = 4 Grani_to_grain_allchans = 5 def grani(start, dur, amp, file, *args) input_channel = get_args(args, :input_channel, 0) grains = get_args(args, :grains, 0) amp_envelope = get_args(args, :amp_envelope, [0, 0, 0.3, 1, 0.7, 1, 1, 0]) grain_envelope = get_args(args, :grain_envelope, [0, 0, 0.3, 1, 0.7, 1, 1, 0]) grain_envelope_end = get_args(args, :grain_envelope_end, false) grain_envelope_transition = get_args(args, :grain_envelope_transition, [0, 0, 1, 1]) grain_envelope_array_size = get_args(args, :grain_envelope_array_size, 512) grain_duration = get_args(args, :grain_duration, 0.1) grain_duration_spread = get_args(args, :grain_spread, 0.0) grain_duration_limit = get_args(args, :grain_limit, 0.002) srate = get_args(args, :srate, 0.0) srate_spread = get_args(args, :srate_spread, 0.0) srate_linear = get_args(args, :srate_linear, false) srate_base = get_args(args, :srate_base, 2.0 ** (1.0 / 12)) srate_error = get_args(args, :srate_error, 0.01) grain_start = get_args(args, :grain_start, [0, 0, 1, 1]) grain_start_spread = get_args(args, :grain_start_spread, 0.0) grain_start_in_seconds = get_args(args, :grain_start_in_seconds, false) grain_density = get_args(args, :grain_density, 10.0) grain_density_spread = get_args(args, :grain_density_spread, 0.0) reverb_amount = get_args(args, :reverb_amount, 0.01) reverse = get_args(args, :reverse, false) where_to = get_args(args, :where_to, 0) where_bins = get_args(args, :where_bins, []) grain_distance = get_args(args, :grain_distance, 1.0) grain_distance_spread = get_args(args, :grain_distance_spread, 0.0) grain_degree = get_args(args, :grain_degree, 45.0) grain_degree_spread = get_args(args, :grain_degree_spread, 0.0) beg, fin = times2samples(start, dur) in_file_channels = mus_sound_chans(file) in_file_sr = mus_sound_srate(file).to_f in_file_dur = mus_sound_framples(file) / in_file_sr in_file_reader = make_readin(:file, file, :channel, [input_channel, in_file_channels - 1].min) last_in_sample = (in_file_dur * in_file_sr).round srate_ratio = in_file_sr / mus_srate() sr_env = make_env(:envelope, if srate_linear envelope_or_number(srate) else exp_envelope(envelope_or_number(srate), :base, srate_base, :error, srate_error) end, :scaler, srate_ratio, :duration, dur) sr_spread_env = make_env(:envelope, envelope_or_number(srate_spread), :duration, dur) amp_env = make_env(:envelope, amp_envelope, :scaler, amp, :duration, dur) gr_dur = make_env(:envelope, envelope_or_number(grain_duration), :duration, dur) gr_dur_spread = make_env(:envelope, envelope_or_number(grain_duration_spread), :duration, dur) gr_start_scaler = (grain_start_in_seconds ? 1.0 : in_file_dur) gr_start = make_env(:envelope, envelope_or_number(grain_start), :duration, dur) gr_start_spread = make_env(:envelope, envelope_or_number(grain_start_spread), :duration, dur) gr_dens_env = make_env(:envelope, envelope_or_number(grain_density), :duration, dur) gr_dens_spread_env = make_env(:envelope, envelope_or_number(grain_density_spread), :duration, dur) gr_env = make_table_lookup(:frequency, 1.0, "initial-phase".intern, 0.0, :wave, if vct?(grain_envelope) grain_envelope else make_gr_env(grain_envelope, grain_envelope_array_size) end) gr_env_end = make_table_lookup(:frequency, 1.0, "initial-phase".intern, 0.0, :wave, if grain_envelope_end if vct?(grain_envelope_end) grain_envelope_end else make_gr_env(grain_envelope_end, grain_envelope_array_size) end else make_vct(512) end) gr_int_env = make_env(:envelope, envelope_or_number(grain_envelope_transition), :duration, dur) interp_gr_envs = grain_envelope_end gr_dist = make_env(:envelope, envelope_or_number(grain_distance), :duration, dur) gr_dist_spread = make_env(:envelope, envelope_or_number(grain_distance_spread), :duration, dur) gr_degree = make_env(:envelope, envelope_or_number(grain_degree), :duration, dur) gr_degree_spread = make_env(:envelope, envelope_or_number(grain_degree_spread), :duration, dur) loc = make_locsig(:degree, 45.0, :distance, 1.0, :output, @ws_output, :revout, @ws_reverb, :channels, @channels) gr_start_sample = beg gr_samples = 0 gr_offset = 1 gr_dens = 0.0 gr_dens_spread = 0.0 grain_counter = 0 samples = 0 first_grain = true set_mus_increment(in_file_reader, -1) if reverse loop do if gr_offset < gr_samples gr_where = env(gr_int_env) if interp_gr_envs val = if interp_gr_envs (1 - gr_where) * table_lookup(gr_env) + gr_where * table_lookup(gr_env_end) else table_lookup(gr_env) end locsig(loc, gr_start_sample + gr_offset, val * env(amp_env) * readin(in_file_reader)) gr_offset += 1 else if first_grain first_grain = false gr_start_sample = beg else gr_start_sample += seconds2samples(1.0 / (gr_dens + gr_dens_spread)) if (gr_start_sample > fin) or (grains.nonzero? and (grain_counter >= grains)) break end end gr_offset = 0 gr_from_beg = gr_start_sample - beg set_mus_location(amp_env, gr_from_beg) set_mus_location(gr_dur, gr_from_beg) set_mus_location(gr_dur_spread, gr_from_beg) set_mus_location(sr_env, gr_from_beg) set_mus_location(sr_spread_env, gr_from_beg) set_mus_location(gr_start, gr_from_beg) set_mus_location(gr_start_spread, gr_from_beg) set_mus_location(gr_dens_env, gr_from_beg) set_mus_location(gr_dens_spread_env, gr_from_beg) in_start_value = env(gr_start) * gr_start_scaler + random_spread(env(gr_start_spread) * gr_start_scaler) in_start = (in_start_value * in_file_sr).round gr_duration = [grain_duration_limit, env(gr_dur) + random_spread(env(gr_dur_spread))].max gr_samples = seconds2samples(gr_duration) gr_srate = if srate_linear env(sr_env) + random_spread(env(sr_spread_env)) else env(sr_env) * srate_base ** random_spread(env(sr_spread_env)) end set_mus_increment(in_file_reader, gr_srate) in_samples = gr_samples / (1.0 / srate_ratio) set_mus_phase(gr_env, 0.0) set_mus_phase(gr_env_end, 0.0) set_mus_frequency(gr_env, 1.0 / gr_duration) set_mus_frequency(gr_env_end, 1.0 / gr_duration) gr_dens = env(gr_dens_env) gr_dens_spread = random_spread(env(gr_dens_spread_env)) samples += gr_samples grain_counter += 1 where = case where_to when Grani_to_grain_duration gr_duration when Grani_to_grain_start in_start_value when Grani_to_grain_sample_rate gr_srate when Grani_to_grain_random random(1.0) else Grani_to_locsig end if where.nonzero? and where_bins.length > 1 (where_bins.length - 1).times do |chn| locsig_set!(loc, chn, ((where_bins[chn] < where and where < where_bins[chn + 1]) ? 1.0 : 0.0)) end else if where_to == Grani_to_grain_allchans @channels.times do |chn| locsig_set!(loc, chn, 1.0) end else set_mus_location(gr_dist, gr_from_beg) set_mus_location(gr_dist_spread, gr_from_beg) set_mus_location(gr_degree, gr_from_beg) set_mus_location(gr_degree_spread, gr_from_beg) deg = env(gr_degree) + random_spread(env(gr_degree_spread)) dist = env(gr_dist) + random_spread(env(gr_dist_spread)) dist_scl = 1.0 / [dist, 1.0].max if @ws_reverb locsig_reverb_set!(loc, 0, reverb_amount * (1.0 / sqrt([dist, 1.0].max))) end if @channels == 1 locsig_set!(loc, 0, dist_scl) else if @channels == 2 frac = [90.0, [0.0, deg].max].min / 90.0 locsig_set!(loc, 0, dist_scl * (1.0 - frac)) locsig_set!(loc, 1, dist_scl * frac) else if @channels > 2 locsig_set!(loc, 0, if 0 <= deg and deg <= 90 dist_scl * ((90.0 - deg) / 90.0) else if 270 <= deg and deg <= 360 dist_scl * ((deg - 270.0) / 90.0) else 0.0 end end) locsig_set!(loc, 1, if 90 <= deg and deg <= 180 dist_scl * (180.0 - deg) / 90.0 else if 0 <= deg and deg <= 90 dist_scl * (deg / 90.0) else 0.0 end end) locsig_set!(loc, 2, if 180 <= deg and deg <= 270 dist_scl * (270.0 - deg) / 90.0 else if 90 <= deg and deg <= 180 dist_scl * (deg - 90.0) / 90.0 else 0.0 end end) if @channels > 3 locsig_set!(loc, 3, if 270 <= deg and deg <= 360 dist_scl * (360.0 - deg) / 90.0 else if 180 <= deg and deg <= 270 dist_scl * (deg - 180.0) / 90.0 else 0.0 end end) end end end end end end in_start = if (in_start + in_samples) > last_in_sample last_in_sample - in_samples else [in_start, 0].max end set_mus_location(in_file_reader, in_start) end end mus_close(in_file_reader) end =begin with_sound(:play, 1, :statistics, true, :channels, 1, :reverb, nil) do grani(0.0, 2.0, 5.0, "oboe.snd", :grain_envelope, raised_cosine()) end =end # grani.rb ends here