class AudioPlayback::Device::Stream

Attributes

is_playing[R]
playing?[R]

Public Class Methods

new(output, options = {}) click to toggle source

@param [Output] output @param [Hash] options @option options [IO] logger

# File lib/audio-playback/device/stream.rb, line 19
def initialize(output, options = {})
  @is_playing = false
  @is_muted = false
  @gain = 1.0
  @input = nil
  @output = output.resource
  initialize_exit_callback(:logger => options[:logger])
  Stream.streams << self
end
streams() click to toggle source

Keep track of all streams @return [Array<Stream>]

# File lib/audio-playback/device/stream.rb, line 12
def self.streams
  @streams ||= []
end

Public Instance Methods

active?() click to toggle source

Is the stream active? @return [Boolean]

# File lib/audio-playback/device/stream.rb, line 48
def active?
  FFI::PortAudio::API.Pa_IsStreamActive(@stream.read_pointer) == 1
end
block() click to toggle source

Block process until the current playback finishes @return [Boolean]

# File lib/audio-playback/device/stream.rb, line 54
def block
  begin
    while active?
      sleep(0.001)
    end
    while FFI::PortAudio::API.Pa_IsStreamActive(@stream.read_pointer) != :paNoError
      sleep(0.1)
    end
  rescue SystemExit, Interrupt
    # Control-C
    @is_playing = false
    exit
  end
  @is_playing = false
  true
end
play(playback, options = {}) click to toggle source

Perform the given playback @param [Playback] playback @param [Hash] options @option options [IO] logger @return [Stream]

# File lib/audio-playback/device/stream.rb, line 34
def play(playback, options = {})
  @is_playing = true
  report(playback, options[:logger]) if options[:logger]
  if @stream.nil?
    open_playback(playback)
  else
    continue(playback)
  end
  start
  self
end

Private Instance Methods

continue(playback) click to toggle source

Reset the stream and continue playback @param [Playback] playback @return [Boolean]

# File lib/audio-playback/device/stream.rb, line 85
def continue(playback)
  playback.reset
  open_stream(playback)
  true
end
exit_callback(options = {}) click to toggle source

Callback that’s fired when the stream exits @return [Boolean]

# File lib/audio-playback/device/stream.rb, line 100
def exit_callback(options = {})
  logger = options[:logger]
  logger.puts("Exit") if logger
  unless @stream.nil?
    #close
    FFI::PortAudio::API.Pa_Terminate
  end
  @is_playing = false
  true
end
initialize_exit_callback(options = {}) click to toggle source

Initialize the callback that’s fired when the stream exits @return [Stream]

# File lib/audio-playback/device/stream.rb, line 93
def initialize_exit_callback(options = {})
  at_exit { exit_callback(options) }
  self
end
open_playback(playback) click to toggle source

Initialize the stream for playback @param [Playback] playback @return [Boolean]

# File lib/audio-playback/device/stream.rb, line 114
def open_playback(playback)
  populate_stream_playback(playback)
  open_stream(playback)
  true
end
open_stream(playback) click to toggle source

Open the stream resource for playback @param [Playback] playback @return [Boolean]

# File lib/audio-playback/device/stream.rb, line 76
def open_stream(playback)
  @userdata ||= playback.data.to_pointer
  FFI::PortAudio::API.Pa_OpenStream(@stream, @input, @output, @freq, @frames, @flags, @method, @userdata)
  true
end
populate_stream_playback(playback) click to toggle source

Initialize the stream’s playback properties @param [Playback] playback @return [Boolean]

# File lib/audio-playback/device/stream.rb, line 123
def populate_stream_playback(playback)
  @freq ||= playback.sample_rate.to_i
  @frames ||= playback.buffer_size
  @flags ||= FFI::PortAudio::API::NoFlag
  @stream ||= FFI::Buffer.new(:pointer)
  @method ||= method(:process)
  true
end
process(input, output, frames_per_buffer, time_info, status_flags, user_data) click to toggle source

Portaudio stream callback

# File lib/audio-playback/device/stream.rb, line 141
def process(input, output, frames_per_buffer, time_info, status_flags, user_data)
  #puts "--"
  #puts "Entering callback at #{Time.now.to_f}"
  counter = user_data.get_float32(Playback::METADATA.index(:pointer) * Playback::FRAME_SIZE).to_i
  #puts "Frame: #{counter}"
  audio_data_size = user_data.get_float32(Playback::METADATA.index(:size) * Playback::FRAME_SIZE).to_i
  #puts "Sample size: #{audio_data_size}"
  num_channels = user_data.get_float32(Playback::METADATA.index(:num_channels) * Playback::FRAME_SIZE).to_i
  #puts "Num Channels: #{num_channels}"
  start_frame = user_data.get_float32(Playback::METADATA.index(:start_frame) * Playback::FRAME_SIZE).to_i
  #puts "Start point: #{start_frame}"
  end_frame = user_data.get_float32(Playback::METADATA.index(:end_frame) * Playback::FRAME_SIZE).to_i
  #puts "Duration: #{duration}"
  is_looping = user_data.get_float32(Playback::METADATA.index(:is_looping) * Playback::FRAME_SIZE).to_i > 0
  #puts "Is looping: #{is_looping}"
  end_frame = [end_frame, audio_data_size].min
  is_eof = false
  end_window = end_frame - frames_per_buffer
  if counter >= end_window
    if counter == end_frame
      is_eof = true
    elsif counter < end_frame
      buffer_size = end_frame.divmod(frames_per_buffer).last
      #puts "Truncated buffer size: #{buffer_size}"
      difference = frames_per_buffer - buffer_size
      #puts "Adding #{difference} frames of null audio"
      extra_data = [0] * difference * num_channels
      is_eof = true
    else
      # p "Aborting (counter: #{counter}, end_frame: #{end_frame})"
      return :paAbort
    end
  end
  buffer_size ||= frames_per_buffer
  #puts "Size per buffer per channel: #{frames_per_buffer}"
  offset = (((counter + start_frame) * num_channels) + Playback::METADATA.count) * Playback::FRAME_SIZE
  #puts "Starting at location: #{offset}"
  data = user_data.get_array_of_float32(offset, buffer_size * num_channels)
  data += extra_data unless extra_data.nil?
  #puts "This buffer size: #{data.size}"
  #puts "Writing to output"
  output.write_array_of_float(data)
  next_counter = counter + frames_per_buffer
  if is_eof
    if is_looping
      #puts "Looping to beginning"
      next_counter = start_frame
      :paContinue
    else
      #puts "Marking eof"
      user_data.put_float32(Playback::METADATA.index(:is_eof) * Playback::FRAME_SIZE, 1.0) # mark eof
      :paComplete
    end
  else
    :paContinue
  end
  user_data.put_float32(Playback::METADATA.index(:pointer) * Playback::FRAME_SIZE, next_counter.to_f) # update counter
  #puts "Exiting callback at #{Time.now.to_f}"
  result
end
report(playback, logger) click to toggle source

Report about the stream @param [Playback] playback @param [IO] logger @return [Stream]

# File lib/audio-playback/device/stream.rb, line 136
def report(playback, logger)
  self
end