class Musicality::SuperCollider::Conductor

Constants

BASE_FPATH_PLACEHOLDER
COMPLETION_MSG
POST_PID_MSG
SC_HEADER
SYNTHDEFS_PLACEHOLDER

Public Class Methods

new(score) click to toggle source
# File lib/musicality/performance/supercollider/conductor.rb, line 5
def initialize score
  unless score.is_a?(Score::Timed)
    raise ArgumentError, "The given score is not a Score::Timed. \
    Convert it first using ScoreConverter."
  end

  parts = score.collated? ? score.parts : ScoreCollator.new(score).collate_parts
  @performers = Hash[ parts.map do |name, part|
    [name, Performer.new(part)]
  end]
end

Public Instance Methods

bundles(selected_parts = @performers.keys, lead_time) click to toggle source
# File lib/musicality/performance/supercollider/conductor.rb, line 24
def bundles selected_parts = @performers.keys, lead_time
  Node.reset_id_counter

  default_group = Group.default(nil)
  aux_audio_bus = 16
  volume_control_bus = 0
  bundle_kwargs = {
    :parent_group => default_group,
    :aux_audio_bus => aux_audio_bus,
    :volume_control_bus => volume_control_bus,
    :lead_time => lead_time
  }

  all_bundles = [ default_group.bundle_queue(0.0) ]
  selected_parts.each do |name|
    bundles = @performers[name].bundles(**bundle_kwargs)
    bundle_kwargs[:aux_audio_bus] += 2
    bundle_kwargs[:volume_control_bus] += 1
    all_bundles.concat bundles
  end
  all_bundles = all_bundles.sort_by {|b| b.time }
  default_group.free_all
  all_bundles.push(default_group.bundle_queue(lead_time+all_bundles.last.time))

  coalesced_bundles = []
  all_bundles.each do |bundle|
    if coalesced_bundles.any? && coalesced_bundles.last.time == bundle.time
      coalesced_bundles.last.messages.concat bundle.messages
    else
      coalesced_bundles.push bundle
    end
  end

  return coalesced_bundles
end
perform(base_fpath, selected_parts: @performers.keys, verbose: false, lead_time: 0.1, keep_code: false) click to toggle source
# File lib/musicality/performance/supercollider/conductor.rb, line 17
def perform base_fpath, selected_parts: @performers.keys, verbose: false, lead_time: 0.1, keep_code: false
  bundles = bundles(selected_parts, lead_time)
  fpath = write_sc_code bundles, base_fpath
  exec_sc_code fpath, bundles.last.time, verbose
  File.delete(fpath) unless keep_code
end

Private Instance Methods

exec_sc_code(fpath, last_time, verbose) click to toggle source
# File lib/musicality/performance/supercollider/conductor.rb, line 105
def exec_sc_code fpath, last_time, verbose
  post_sclang_pid = Regexp.new(POST_PID_MSG + " ([0-9]+)")

  sclang_pid = nil
  IO.popen("sclang \"#{fpath}\"") do |io|
    pid = io.pid      
    while response = io.gets
      puts response if verbose

      case response.chomp
      when post_sclang_pid
        sclang_pid = $1.to_i
      when COMPLETION_MSG
        if OS.windows?
          Process.kill 9, sclang_pid
        else
          Process.kill "INT", sclang_pid
        end
      end
    end
  end
end
write_sc_code(bundles, base_fpath) click to toggle source
# File lib/musicality/performance/supercollider/conductor.rb, line 93
def write_sc_code bundles, base_fpath
  fpath = "#{base_fpath}.scd"
  File.open(fpath, "w") do |f|
    synthdefs = [ SynthDefs::VOLUME_CONTROL, SynthDefs::VOLUME_CHANGE ] + @performers.values.map {|p| p.settings.synthdef }

    f.write SC_HEADER.gsub(SYNTHDEFS_PLACEHOLDER, synthdefs.map {|s| s.to_sclang }.join("\n\n"))
    f.write bundles.map {|b| "    " + b.to_sclang }.join(",\n") + "\n"
    f.write SC_FOOTER.gsub(BASE_FPATH_PLACEHOLDER, base_fpath)
  end
  fpath
end