Class: Plaything
- Inherits:
-
Object
- Object
- Plaything
- Defined in:
- lib/plaything.rb,
lib/plaything/openal.rb,
lib/plaything/version.rb,
lib/plaything/objects/buffer.rb,
lib/plaything/objects/device.rb,
lib/plaything/objects/source.rb,
lib/plaything/objects/context.rb,
lib/plaything/support/paramable.rb,
lib/plaything/support/type_class.rb,
lib/plaything/support/managed_pointer.rb
Overview
Plaything is tiny API wrapper around OpenAL, and makes it easy to play raw (PCM) streaming audio through your speakers.
API consist of a few key methods available on the Plaything instance.
-
#play, #pause, #stop — controls source playback state. If the source runs out of audio to play, it will forcefully stop playback.
-
#position, can be used to retrieve playback position.
-
#queue_size, #drops, #starved? — status information; should be used by the streaming source to improve playback experience.
-
#format= — allows you to change format, even during playback.
Internally, Plaything will queue and unqueue buffers as they are played during streaming. When a sufficient amount of audio has been fed into plaything, the audio will be queued on the source and plaything can accept additional audio.
Plaything is considered thread-safe.
Defined Under Namespace
Modules: OpenAL
Constant Summary collapse
- Error =
Class.new(StandardError)
- Formats =
{ [ :int16, 1 ] => :mono16, [ :int16, 2 ] => :stereo16, }
- VERSION =
"1.1.1"
Instance Attribute Summary collapse
-
#source ⇒ Plaything::OpenAL::Source
readonly
The back-end audio source.
Instance Method Summary collapse
-
#<<(frames) ⇒ Integer
Queue audio frames for playback.
-
#drops ⇒ Integer
If audio is starved, and it has not been previously seen as starved, it will return 1.
-
#format ⇒ Hash
Current audio format in the queues.
-
#format=(format) ⇒ Object
Change the format.
-
#initialize(format = { sample_rate: 44100, sample_type: :int16, channels: 2 }) ⇒ Plaything
constructor
Open the default output device and prepare it for playback.
-
#pause ⇒ Object
Pause playback of queued audio.
-
#play ⇒ Object
Start playback of queued audio.
-
#position ⇒ Rational
How many seconds of audio that has been played.
-
#queue_size ⇒ Integer
Total size of current play queue.
-
#starved? ⇒ Boolean
True if audio stream has starved.
-
#stop ⇒ Object
Stop playback and clear any queued audio.
-
#stream(frames, frame_format) ⇒ Integer
Queue audio frames for playback.
Constructor Details
#initialize(format = { sample_rate: 44100, sample_type: :int16, channels: 2 }) ⇒ Plaything
Open the default output device and prepare it for playback.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/plaything.rb', line 35 def initialize(format = { sample_rate: 44100, sample_type: :int16, channels: 2 }) @device = OpenAL.open_device(nil) raise Error, "Failed to open device" if @device.null? @context = OpenAL.create_context(@device, nil) OpenAL.make_context_current(@context) OpenAL.distance_model(:none) OpenAL.listenerf(:gain, 1.0) FFI::MemoryPointer.new(OpenAL::Source, 1) do |ptr| OpenAL.gen_sources(ptr.count, ptr) @source = OpenAL::Source.new(ptr.read_uint) end FFI::MemoryPointer.new(OpenAL::Buffer, 3) do |ptr| OpenAL.gen_buffers(ptr.count, ptr) @buffers = OpenAL::Buffer.extract(ptr, ptr.count) end @free_buffers = @buffers.clone @queued_buffers = [] @queued_frames = [] @starved = false @total_buffers_processed = 0 @monitor = Monitor.new self.format = format end |
Instance Attribute Details
#source ⇒ Plaything::OpenAL::Source (readonly)
Returns the back-end audio source.
67 68 69 |
# File 'lib/plaything.rb', line 67 def source @source end |
Instance Method Details
#<<(frames) ⇒ Integer
this method is here for backwards-compatibility, and does not support changing format automatically. You should use #stream instead.
Queue audio frames for playback.
189 190 191 |
# File 'lib/plaything.rb', line 189 def <<(frames) stream(frames, format) end |
#drops ⇒ Integer
If audio is starved, and it has not been previously seen as starved, it will return 1. However, if audio is starved and #drops has already reported it as starved, it will return 0. Finally, if audio is not starved, it always returns 0.
119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/plaything.rb', line 119 def drops if starved? if @starved_toggle 0 else @starved_toggle = true 1 end else @starved_toggle = false 0 end end |
#format ⇒ Hash
Returns current audio format in the queues.
139 140 141 142 143 144 145 146 147 |
# File 'lib/plaything.rb', line 139 def format synchronize do { sample_rate: @sample_rate, sample_type: @sample_type, channels: @channels, } end end |
#format=(format) ⇒ Object
if there is any queued audio it will be cleared, and the playback will be stopped.
Change the format.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/plaything.rb', line 158 def format=(format) synchronize do if @source. stop # clear audio buffers @starved = true end @sample_type = format.fetch(:sample_type) @sample_rate = Integer(format.fetch(:sample_rate)) @channels = Integer(format.fetch(:channels)) @sample_format = Formats.fetch([@sample_type, @channels]) do raise TypeError, "unknown sample format for type [#{@sample_type}, #{@channels}]" end # 44100 int16s = 22050 frames = 0.5s (1 frame * 2 channels = 2 int16 = 1 sample = 1/44100 s) @buffer_size = @sample_rate * @channels * 1.0 # how many samples there are in each buffer, irrespective of channels @buffer_length = @buffer_size / @channels # buffer_duration = buffer_length / sample_rate end end |
#pause ⇒ Object
Pause playback of queued audio. Playback will resume from current position when #play is called.
80 81 82 |
# File 'lib/plaything.rb', line 80 def pause synchronize { @source.pause } end |
#play ⇒ Object
You must continue to supply audio, or playback will cease.
Start playback of queued audio.
72 73 74 75 76 77 |
# File 'lib/plaything.rb', line 72 def play synchronize do @starved = false @source.play end end |
#position ⇒ Rational
Returns how many seconds of audio that has been played.
99 100 101 102 103 104 |
# File 'lib/plaything.rb', line 99 def position synchronize do total_samples_processed = @total_buffers_processed * @buffer_length Rational(total_samples_processed + @source.sample_offset, @sample_rate) end end |
#queue_size ⇒ Integer
Returns total size of current play queue.
107 108 109 110 111 |
# File 'lib/plaything.rb', line 107 def queue_size synchronize do @source.buffers_queued * @buffer_length - @source.sample_offset end end |
#starved? ⇒ Boolean
Returns true if audio stream has starved.
134 135 136 |
# File 'lib/plaything.rb', line 134 def starved? synchronize { @starved or @source.starved? } end |
#stop ⇒ Object
All audio queues are completely cleared, and #position is reset.
Stop playback and clear any queued audio.
87 88 89 90 91 92 93 94 95 96 |
# File 'lib/plaything.rb', line 87 def stop synchronize do @source.stop @source.detach_buffers @free_buffers.concat(@queued_buffers) @queued_buffers.clear @queued_frames.clear @total_buffers_processed = 0 end end |
#stream(frames, frame_format) ⇒ Integer
Queue audio frames for playback.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/plaything.rb', line 201 def stream(frames, frame_format) synchronize do if buffers_processed > 0 FFI::MemoryPointer.new(OpenAL::Buffer, buffers_processed) do |ptr| OpenAL.source_unqueue_buffers(@source, ptr.count, ptr) @total_buffers_processed += ptr.count @free_buffers.concat OpenAL::Buffer.extract(ptr, ptr.count) @queued_buffers.delete_if { |buffer| @free_buffers.include?(buffer) } end end self.format = frame_format if frame_format != format wanted_size = (@buffer_size - @queued_frames.length).div(@channels) * @channels consumed_frames = frames.take(wanted_size) @queued_frames.concat(consumed_frames) if @queued_frames.length >= @buffer_size and @free_buffers.any? current_buffer = @free_buffers.shift FFI::MemoryPointer.new(@sample_type, @queued_frames.length) do |frames| frames.public_send(:"write_array_of_#{@sample_type}", @queued_frames) # stereo16 = 2 int16s (1 frame) = 1 sample OpenAL.buffer_data(current_buffer, @sample_format, frames, frames.size, @sample_rate) @queued_frames.clear end FFI::MemoryPointer.new(OpenAL::Buffer, 1) do |buffers| buffers.write_uint(current_buffer.to_native) OpenAL.source_queue_buffers(@source, buffers.count, buffers) end @queued_buffers.push(current_buffer) end consumed_frames.length / @channels end end |