Class: SFML::SoundStream
- Inherits:
-
Object
- Object
- SFML::SoundStream
- Defined in:
- lib/sfml/audio/sound_stream.rb
Overview
Procedural audio source. Subclass it and override ‘#on_get_data` to fill each chunk on demand — CSFML invokes the callback from its audio thread whenever the sound queue runs low.
‘#on_get_data` returns either:
* an Array (or anything responding to `#to_a`) of Int16 PCM
samples — they're packed into a buffer and handed to CSFML;
* `nil` to stop the stream.
Override ‘#on_seek(time)` to support `playing_offset=`. Default is a no-op (the next callback continues from wherever the subclass internal state happens to be).
class SineWave < SFML::SoundStream
def initialize(freq:, sample_rate: 44_100)
super(channel_count: 1, sample_rate: sample_rate)
@freq, @sample_rate = freq, sample_rate
@phase = 0.0
end
def on_get_data
n = @sample_rate / 10 # ~100ms chunks
step = 2 * Math::PI * @freq / @sample_rate
Array.new(n) do
s = (Math.sin(@phase) * 30_000).to_i
@phase = (@phase + step) % (2 * Math::PI)
s
end
end
def on_seek(time)
@phase = 0.0
end
end
CAVEATS
-
The callback runs on the SFML audio thread; doing heavy work there will glitch the audio. Generate samples and return.
-
Ruby threads still need the GVL — your callback acquires it each invocation. Long Ruby work on the main thread can starve the audio thread and produce dropouts.
-
Always keep a reference to the SoundStream object (assign to a variable, store in an instance var). If the Ruby object is GC’d while CSFML is still calling callbacks, the process crashes.
Constant Summary collapse
- DEFAULT_CHUNK_FRAMES =
4096
Instance Attribute Summary collapse
-
#handle ⇒ Object
readonly
:nodoc:.
Instance Method Summary collapse
- #attenuation ⇒ Object
- #attenuation=(value) ⇒ Object
- #channel_count ⇒ Object
-
#initialize(channel_count:, sample_rate:) ⇒ SoundStream
constructor
A new instance of SoundStream.
- #looping=(value) ⇒ Object
-
#looping? ⇒ Boolean
Cached on the Ruby side; some OpenAL backends (notably the headless null sink we get on Linux CI) don’t reliably read the loop flag back through CSFML once it’s been set.
- #min_distance ⇒ Object
- #min_distance=(value) ⇒ Object
-
#on_get_data ⇒ Object
Return an Array of Int16 PCM samples (interleaved if multi-channel), or ‘nil` to stop the stream.
-
#on_seek(_time) ⇒ Object
Called when the user changes the playing offset.
- #pause ⇒ Object
- #paused? ⇒ Boolean
- #pitch ⇒ Object
- #pitch=(value) ⇒ Object
-
#play ⇒ Object
—- Public playback API ——————————————.
- #playing? ⇒ Boolean
- #playing_offset ⇒ Object
- #playing_offset=(value) ⇒ Object
- #position ⇒ Object
- #position=(value) ⇒ Object
- #relative_to_listener=(value) ⇒ Object
- #relative_to_listener? ⇒ Boolean
- #sample_rate ⇒ Object
- #status ⇒ Object
- #stop ⇒ Object
- #stopped? ⇒ Boolean
- #volume ⇒ Object
- #volume=(value) ⇒ Object
Constructor Details
#initialize(channel_count:, sample_rate:) ⇒ SoundStream
Returns a new instance of SoundStream.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/sfml/audio/sound_stream.rb', line 49 def initialize(channel_count:, sample_rate:) raise ArgumentError, "channel_count must be >= 1" if channel_count < 1 raise ArgumentError, "sample_rate must be > 0" if sample_rate < 1 # Hold strong refs so neither GC nor reload disposes the # callbacks while CSFML is still calling them. @get_data_cb = FFI::Function.new(:bool, [:pointer, :pointer]) do |chunk_ptr, _user| _on_get_data_callback(chunk_ptr) end @seek_cb = FFI::Function.new(:void, [C::System::Time.by_value, :pointer]) do |time, _user| on_seek(Time.from_native(time)) nil end ptr = C::Audio.sfSoundStream_create( @get_data_cb, @seek_cb, Integer(channel_count), Integer(sample_rate), nil, 0, nil, ) raise Error, "sfSoundStream_create returned NULL" if ptr.null? @handle = FFI::AutoPointer.new(ptr, C::Audio.method(:sfSoundStream_destroy)) # We re-use a single MemoryPointer for the sample buffer, # growing it on demand. CSFML reads from the pointer between # callbacks, then asks for the next chunk — by the time we # overwrite, CSFML is done with the previous data. @sample_buffer = nil @sample_buffer_capacity = 0 end |
Instance Attribute Details
#handle ⇒ Object (readonly)
:nodoc:
172 173 174 |
# File 'lib/sfml/audio/sound_stream.rb', line 172 def handle @handle end |
Instance Method Details
#attenuation ⇒ Object
154 |
# File 'lib/sfml/audio/sound_stream.rb', line 154 def attenuation = C::Audio.sfSoundStream_getAttenuation(@handle) |
#attenuation=(value) ⇒ Object
156 157 158 |
# File 'lib/sfml/audio/sound_stream.rb', line 156 def attenuation=(value) C::Audio.sfSoundStream_setAttenuation(@handle, value.to_f) end |
#channel_count ⇒ Object
106 |
# File 'lib/sfml/audio/sound_stream.rb', line 106 def channel_count = C::Audio.sfSoundStream_getChannelCount(@handle) |
#looping=(value) ⇒ Object
116 117 118 119 |
# File 'lib/sfml/audio/sound_stream.rb', line 116 def looping=(value) @looping = !!value C::Audio.sfSoundStream_setLooping(@handle, @looping) end |
#looping? ⇒ Boolean
Cached on the Ruby side; some OpenAL backends (notably the headless null sink we get on Linux CI) don’t reliably read the loop flag back through CSFML once it’s been set.
112 113 114 |
# File 'lib/sfml/audio/sound_stream.rb', line 112 def looping? @looping == true end |
#min_distance ⇒ Object
160 |
# File 'lib/sfml/audio/sound_stream.rb', line 160 def min_distance = C::Audio.sfSoundStream_getMinDistance(@handle) |
#min_distance=(value) ⇒ Object
162 163 164 |
# File 'lib/sfml/audio/sound_stream.rb', line 162 def min_distance=(value) C::Audio.sfSoundStream_setMinDistance(@handle, value.to_f) end |
#on_get_data ⇒ Object
Return an Array of Int16 PCM samples (interleaved if multi-channel), or ‘nil` to stop the stream. Default raises so subclasses must implement it.
86 87 88 |
# File 'lib/sfml/audio/sound_stream.rb', line 86 def on_get_data raise NoMethodError, "#{self.class} must override #on_get_data" end |
#on_seek(_time) ⇒ Object
Called when the user changes the playing offset. Default is a no-op — override if your stream tracks position internally (counters, file offsets, etc.).
93 |
# File 'lib/sfml/audio/sound_stream.rb', line 93 def on_seek(_time); end |
#pause ⇒ Object
98 |
# File 'lib/sfml/audio/sound_stream.rb', line 98 def pause = (C::Audio.sfSoundStream_pause(@handle); self) |
#paused? ⇒ Boolean
103 |
# File 'lib/sfml/audio/sound_stream.rb', line 103 def paused? = status == :paused |
#pitch ⇒ Object
127 |
# File 'lib/sfml/audio/sound_stream.rb', line 127 def pitch = C::Audio.sfSoundStream_getPitch(@handle) |
#pitch=(value) ⇒ Object
129 130 131 |
# File 'lib/sfml/audio/sound_stream.rb', line 129 def pitch=(value) C::Audio.sfSoundStream_setPitch(@handle, value.to_f) end |
#play ⇒ Object
—- Public playback API ——————————————
97 |
# File 'lib/sfml/audio/sound_stream.rb', line 97 def play = (C::Audio.sfSoundStream_play(@handle); self) |
#playing? ⇒ Boolean
102 |
# File 'lib/sfml/audio/sound_stream.rb', line 102 def = status == :playing |
#playing_offset ⇒ Object
133 134 135 |
# File 'lib/sfml/audio/sound_stream.rb', line 133 def Time.from_native(C::Audio.(@handle)) end |
#playing_offset=(value) ⇒ Object
137 138 139 140 |
# File 'lib/sfml/audio/sound_stream.rb', line 137 def (value) t = value.is_a?(Time) ? value : Time.seconds(value.to_f) C::Audio.(@handle, t.to_native) end |
#position ⇒ Object
142 143 144 145 |
# File 'lib/sfml/audio/sound_stream.rb', line 142 def position v = C::Audio.sfSoundStream_getPosition(@handle) Vector3.new(v[:x], v[:y], v[:z]) end |
#position=(value) ⇒ Object
147 148 149 150 151 152 |
# File 'lib/sfml/audio/sound_stream.rb', line 147 def position=(value) v = value.is_a?(Vector3) ? value : Vector3.new(*value) packed = C::System::Vector3f.new packed[:x] = v.x.to_f; packed[:y] = v.y.to_f; packed[:z] = v.z.to_f C::Audio.sfSoundStream_setPosition(@handle, packed) end |
#relative_to_listener=(value) ⇒ Object
168 169 170 |
# File 'lib/sfml/audio/sound_stream.rb', line 168 def relative_to_listener=(value) C::Audio.sfSoundStream_setRelativeToListener(@handle, !!value) end |
#relative_to_listener? ⇒ Boolean
166 |
# File 'lib/sfml/audio/sound_stream.rb', line 166 def relative_to_listener? = C::Audio.sfSoundStream_isRelativeToListener(@handle) |
#sample_rate ⇒ Object
107 |
# File 'lib/sfml/audio/sound_stream.rb', line 107 def sample_rate = C::Audio.sfSoundStream_getSampleRate(@handle) |
#status ⇒ Object
101 |
# File 'lib/sfml/audio/sound_stream.rb', line 101 def status = C::Audio::STATUSES[C::Audio.sfSoundStream_getStatus(@handle)] |
#stop ⇒ Object
99 |
# File 'lib/sfml/audio/sound_stream.rb', line 99 def stop = (C::Audio.sfSoundStream_stop(@handle); self) |
#stopped? ⇒ Boolean
104 |
# File 'lib/sfml/audio/sound_stream.rb', line 104 def stopped? = status == :stopped |