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
-
#channel_map ⇒ Object
The channel layout the stream is producing, as an Array of ‘sfSoundChannel` enum values (1 = Mono, 2 = FrontLeft, 3 = FrontRight, etc — see SoundBuffer::DEFAULT_CHANNEL_MAPS).
- #cone ⇒ Object
- #cone=(value) ⇒ Object
- #direction ⇒ Object
- #direction=(value) ⇒ Object
- #directional_attenuation_factor ⇒ Object
- #directional_attenuation_factor=(value) ⇒ Object
- #doppler_factor ⇒ Object
- #doppler_factor=(value) ⇒ Object
-
#effect_processor=(callable) ⇒ Object
Install a real-time DSP filter (same contract as Sound / Music — see Sound#effect_processor=).
-
#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.
- #max_distance ⇒ Object
- #max_distance=(value) ⇒ Object
- #max_gain ⇒ Object
- #max_gain=(value) ⇒ Object
- #min_distance ⇒ Object
- #min_distance=(value) ⇒ Object
- #min_gain ⇒ Object
- #min_gain=(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.
-
#pan ⇒ Object
—- 3D-audio surface (mirror of Sound / Music) ——————-.
- #pan=(value) ⇒ Object
- #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
- #spatialization_enabled=(value) ⇒ Object
- #spatialization_enabled? ⇒ Boolean
- #status ⇒ Object
- #stop ⇒ Object
- #stopped? ⇒ Boolean
- #velocity ⇒ Object
- #velocity=(value) ⇒ Object
- #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:
275 276 277 |
# File 'lib/sfml/audio/sound_stream.rb', line 275 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) |
#channel_map ⇒ Object
The channel layout the stream is producing, as an Array of ‘sfSoundChannel` enum values (1 = Mono, 2 = FrontLeft, 3 = FrontRight, etc — see SoundBuffer::DEFAULT_CHANNEL_MAPS).
267 268 269 270 271 272 273 |
# File 'lib/sfml/audio/sound_stream.rb', line 267 def channel_map count_buf = FFI::MemoryPointer.new(:size_t) ptr = C::Audio.sfSoundStream_getChannelMap(@handle, count_buf) n = count_buf.read(:size_t) return [] if ptr.null? || n.zero? ptr.read_array_of_int32(n) end |
#cone ⇒ Object
216 217 218 |
# File 'lib/sfml/audio/sound_stream.rb', line 216 def cone SoundCone.from_native(C::Audio.sfSoundStream_getCone(@handle)) end |
#cone=(value) ⇒ Object
220 221 222 223 224 225 226 227 228 229 |
# File 'lib/sfml/audio/sound_stream.rb', line 220 def cone=(value) cone = case value when SoundCone then value when Hash then SoundCone.new(**value) else raise ArgumentError, "SoundStream#cone= expects SoundCone or Hash; got #{value.class}" end C::Audio.sfSoundStream_setCone(@handle, cone.to_native) end |
#direction ⇒ Object
204 205 206 207 |
# File 'lib/sfml/audio/sound_stream.rb', line 204 def direction v = C::Audio.sfSoundStream_getDirection(@handle) Vector3.new(v[:x], v[:y], v[:z]) end |
#direction=(value) ⇒ Object
209 210 211 212 213 214 |
# File 'lib/sfml/audio/sound_stream.rb', line 209 def direction=(value) vec = value.is_a?(Vector3) ? value : Vector3.new(*value) packed = C::System::Vector3f.new packed[:x] = vec.x.to_f; packed[:y] = vec.y.to_f; packed[:z] = vec.z.to_f C::Audio.sfSoundStream_setDirection(@handle, packed) end |
#directional_attenuation_factor ⇒ Object
249 250 251 |
# File 'lib/sfml/audio/sound_stream.rb', line 249 def directional_attenuation_factor C::Audio.sfSoundStream_getDirectionalAttenuationFactor(@handle) end |
#directional_attenuation_factor=(value) ⇒ Object
253 254 255 |
# File 'lib/sfml/audio/sound_stream.rb', line 253 def directional_attenuation_factor=(value) C::Audio.sfSoundStream_setDirectionalAttenuationFactor(@handle, value.to_f) end |
#doppler_factor ⇒ Object
243 |
# File 'lib/sfml/audio/sound_stream.rb', line 243 def doppler_factor = C::Audio.sfSoundStream_getDopplerFactor(@handle) |
#doppler_factor=(value) ⇒ Object
245 246 247 |
# File 'lib/sfml/audio/sound_stream.rb', line 245 def doppler_factor=(value) C::Audio.sfSoundStream_setDopplerFactor(@handle, value.to_f) end |
#effect_processor=(callable) ⇒ Object
Install a real-time DSP filter (same contract as Sound / Music — see Sound#effect_processor=). Pass ‘nil` to remove.
259 260 261 262 |
# File 'lib/sfml/audio/sound_stream.rb', line 259 def effect_processor=(callable) @effect_cb = callable.nil? ? nil : Audio._build_effect_processor(callable) C::Audio.sfSoundStream_setEffectProcessor(@handle, @effect_cb, nil) end |
#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 |
#max_distance ⇒ Object
180 |
# File 'lib/sfml/audio/sound_stream.rb', line 180 def max_distance = C::Audio.sfSoundStream_getMaxDistance(@handle) |
#max_distance=(value) ⇒ Object
182 183 184 |
# File 'lib/sfml/audio/sound_stream.rb', line 182 def max_distance=(value) C::Audio.sfSoundStream_setMaxDistance(@handle, value.to_f) end |
#max_gain ⇒ Object
192 |
# File 'lib/sfml/audio/sound_stream.rb', line 192 def max_gain = C::Audio.sfSoundStream_getMaxGain(@handle) |
#max_gain=(value) ⇒ Object
194 195 196 |
# File 'lib/sfml/audio/sound_stream.rb', line 194 def max_gain=(value) C::Audio.sfSoundStream_setMaxGain(@handle, value.to_f) 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 |
#min_gain ⇒ Object
186 |
# File 'lib/sfml/audio/sound_stream.rb', line 186 def min_gain = C::Audio.sfSoundStream_getMinGain(@handle) |
#min_gain=(value) ⇒ Object
188 189 190 |
# File 'lib/sfml/audio/sound_stream.rb', line 188 def min_gain=(value) C::Audio.sfSoundStream_setMinGain(@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 |
#pan ⇒ Object
—- 3D-audio surface (mirror of Sound / Music) ——————-
174 |
# File 'lib/sfml/audio/sound_stream.rb', line 174 def pan = C::Audio.sfSoundStream_getPan(@handle) |
#pan=(value) ⇒ Object
176 177 178 |
# File 'lib/sfml/audio/sound_stream.rb', line 176 def pan=(value) C::Audio.sfSoundStream_setPan(@handle, value.to_f) 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) |
#spatialization_enabled=(value) ⇒ Object
200 201 202 |
# File 'lib/sfml/audio/sound_stream.rb', line 200 def spatialization_enabled=(value) C::Audio.sfSoundStream_setSpatializationEnabled(@handle, !!value) end |
#spatialization_enabled? ⇒ Boolean
198 |
# File 'lib/sfml/audio/sound_stream.rb', line 198 def spatialization_enabled? = C::Audio.sfSoundStream_isSpatializationEnabled(@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 |
#velocity ⇒ Object
231 232 233 234 |
# File 'lib/sfml/audio/sound_stream.rb', line 231 def velocity v = C::Audio.sfSoundStream_getVelocity(@handle) Vector3.new(v[:x], v[:y], v[:z]) end |
#velocity=(value) ⇒ Object
236 237 238 239 240 241 |
# File 'lib/sfml/audio/sound_stream.rb', line 236 def velocity=(value) vec = value.is_a?(Vector3) ? value : Vector3.new(*value) packed = C::System::Vector3f.new packed[:x] = vec.x.to_f; packed[:y] = vec.y.to_f; packed[:z] = vec.z.to_f C::Audio.sfSoundStream_setVelocity(@handle, packed) end |