class AudioPlayback::Device::Stream
Attributes
Public Class Methods
@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
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
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 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
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
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
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 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
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 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
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
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 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