ruby-sfml 3.0.0.4 → 3.0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02adbbc1dc50cc42127dc845ad9be68829ee07c100cb915e83f15d2e8deb8df7
4
- data.tar.gz: 59e6cadd1e7a325e70b7ad92b162f7ba3d73035fc482317e3ad0f28d8b7afcb6
3
+ metadata.gz: c20f8dbdc29ef33bf370ea4a7fbadf09f9652acc84834dc47f3ca9e325635d5d
4
+ data.tar.gz: f65b6a2c2799e01c45071e5801cb33056cdec961d3c52a1b4ffd209538868f51
5
5
  SHA512:
6
- metadata.gz: da0f1e034c77f9a78fb202e4e40e90099aafac7fbe5b2261e94bd693e51f937098c5113491f723eefc45acc731f983b39cc6a27e372c81e6391f8b2777aee99e
7
- data.tar.gz: a494f581e0cf1975c5233705838cc9f29f0f99fe88a793cd580478772c5170a0c440330c40f464947a689849d43b8e2c16b4b247c0cf4350eea5e35616d9ec90
6
+ metadata.gz: 4ef7eabaab2ffe015f2e2325926d4d41f6f941f49392d9d3d72570198d1e5df9873aeec8d4a82a32aeea3dddd2174010b0993ca0c9cfc4070eb6b6630394ec70
7
+ data.tar.gz: 0d8d30d9bae64f2c84ecef83acbaebf7eda423133c362e0d7b397c90a531ad72477bbe64c61d80f10cdfc1e8a316612a2d8ca71f14fd1fe4581b5eee66510218
data/CHANGELOG.md CHANGED
@@ -8,6 +8,105 @@ ruby-sfml's own patch level.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [3.0.0.5] — 2026-05-11
12
+
13
+ Round-trip release: closes every remaining CSFML 3.0 gap that's
14
+ useful from Ruby. The library now covers the surface area you'd
15
+ expect for porting a CSFML application straight across.
16
+
17
+ ### Added — graphics
18
+
19
+ - `Color#+`, `Color#-`, `Color#*` (alias `modulate`),
20
+ `Color#to_integer` / `Color.from_integer(value)` — channel-wise
21
+ saturating arithmetic, plus packed 0xRRGGBBAA round-trips.
22
+ - Texture binding + geometric introspection on the three concrete
23
+ shapes via the new `Graphics::ShapeInspectable` mixin:
24
+ `CircleShape`/`RectangleShape`/`ConvexShape` all gain
25
+ `texture` / `texture=` / `set_texture(tex, reset_rect:)`,
26
+ `texture_rect` / `texture_rect=`, `point(i)`, `geometric_center`,
27
+ `local_bounds`, `global_bounds`, `transform`, `inverse_transform`,
28
+ `dup` / `clone`. `RectangleShape` additionally exposes
29
+ `point_count`.
30
+ - `SFML::Shape` — callback-driven abstract shape. Subclass and
31
+ override `#point_count` / `#point(i)` to drive geometry from
32
+ live Ruby data; call `#update` after the source data changes.
33
+ - `SFML::TransformableObject` — standalone transform container
34
+ (CSFML's `sfTransformable*`). Useful as a base for custom
35
+ drawables that combine a transform with their own rendering.
36
+ - `VertexArray#dup` / `#clone` — independent deep copy.
37
+ - `VertexBuffer#bind` / `VertexBuffer.unbind` — bind a VBO as the
38
+ active GL vertex buffer (for mixing raw GL with SFML rendering).
39
+ - Texture sRGB variants via the `srgb:` kwarg on
40
+ `Texture.load`, `Texture.create`, `Texture.from_memory`,
41
+ `Texture.from_image`, and `Texture#resize`.
42
+ - `Shader#set_mat3` / `#set_mat4` / `#set_mat3_array` /
43
+ `#set_mat4_array` and `Shader#set_bvec(name, *components)` —
44
+ matrix and bool-vector uniforms.
45
+ - `Shader.from_stream(vertex:, geometry:, fragment:)`,
46
+ `Font.from_stream(io)`, `Image.from_stream(io)`,
47
+ `Texture.from_stream(io, srgb:, ...)` — load any of these from a
48
+ Ruby IO-like object (File, StringIO, network reader). Backed by
49
+ the new `SFML::InputStream` adapter that wraps a Ruby IO as
50
+ CSFML's `sfInputStream*`.
51
+
52
+ ### Added — audio
53
+
54
+ - Full 3D-audio surface on `SoundStream` (mirror of Sound / Music):
55
+ `pan`, `min_gain` / `max_gain`, `max_distance`,
56
+ `spatialization_enabled?`, `direction`, `cone`, `velocity`,
57
+ `doppler_factor`, `directional_attenuation_factor`,
58
+ `effect_processor=` (real-time DSP filter), `channel_map` — and
59
+ the `=` setters.
60
+ - `SoundRecorder#channel_map` — read the channel layout the
61
+ recorder is producing.
62
+ - `SFML::SoundRecorder` — callback-based mic capture. Subclass
63
+ and override `#on_start` / `#on_process_samples(samples,
64
+ channels)` / `#on_stop`. The pre-existing module-level helpers
65
+ (`SoundRecorder.available?` / `.devices` / `.default_device`)
66
+ are preserved as class methods.
67
+ - `Music.from_stream(io, **opts)` and
68
+ `SoundBuffer.from_stream(io)` — stream-backed audio loaders.
69
+
70
+ ### Added — network
71
+
72
+ - `SFML::Network::Packet` — wire-compatible with `sf::Packet`. Typed
73
+ read / write for `Bool`, `Int8`–`Int64`, `Uint8`–`Uint64`,
74
+ `Float`, `Double`, `String`; `#data` / `#size` / `#read_position`
75
+ / `#end_of_packet?` / `#ok?` / `#clear` / `#dup`.
76
+ - `TcpSocket#send_packet` / `#receive_packet` and
77
+ `UdpSocket#send_packet(packet, to:, port:)` /
78
+ `#receive_packet` — structured framing on top of the existing
79
+ raw byte send/receive.
80
+
81
+ ### Added — window
82
+
83
+ - `Keyboard::SCAN_CODES` — full layout-independent scancode table
84
+ (146 entries matching `sfScancode`).
85
+ - `Keyboard.scancode_pressed?(:scan_w)` — query the physical key
86
+ regardless of keyboard layout (the standard for WASD-style games).
87
+ - `Keyboard.localize(scancode)` / `.delocalize(key)` /
88
+ `.description(scancode)` — convert between physical scancodes
89
+ and logical keys under the current OS layout, plus the
90
+ human-readable description string.
91
+ - `Keyboard.virtual_keyboard_visible=` — on-screen keyboard toggle
92
+ for touchscreen / mobile builds (no-op on desktop).
93
+ - `VideoMode.fullscreen_modes` — all video modes the display
94
+ supports for true-fullscreen window creation, sorted from most
95
+ to least pixels.
96
+ - `VideoMode#valid?` — does the display actually support this mode
97
+ at fullscreen?
98
+ - `SFML::Context` — headless GL context. Activate it on a thread
99
+ to compile shaders / make raw GL calls without a window. Static
100
+ `Context.active_context_id`, `.extension_available?(name)`,
101
+ `.gl_function(name)`.
102
+
103
+ ### Added — system
104
+
105
+ - `SFML::InputStream(io)` — wraps a Ruby IO-like object (anything
106
+ answering `read` / `seek` / `pos` / `size`) as the
107
+ `sfInputStream*` argument CSFML loader functions take. Used
108
+ internally by every `*.from_stream` factory.
109
+
11
110
  ## [3.0.0.4] — 2026-05-09
12
111
 
13
112
  ### Added — graphics
@@ -29,6 +29,20 @@ module SFML
29
29
  m
30
30
  end
31
31
 
32
+ # Stream music straight from a Ruby IO-like object. CSFML reads
33
+ # the audio lazily on its decoding thread — keep the IO open
34
+ # until you stop the Music.
35
+ def self.from_stream(io, **opts)
36
+ stream = SFML::InputStream.new(io)
37
+ ptr = C::Audio.sfMusic_createFromStream(stream.to_ptr)
38
+ raise Error, "sfMusic_createFromStream returned NULL — unsupported format?" if ptr.null?
39
+
40
+ m = _wrap(ptr, opts)
41
+ m.instance_variable_set(:@_stream_pin, stream)
42
+ m.instance_variable_set(:@_io_pin, io)
43
+ m
44
+ end
45
+
32
46
  # Internal — finish initialising a Music from an already-built
33
47
  # CSFML pointer.
34
48
  def self._wrap(ptr, opts)
@@ -29,6 +29,19 @@ module SFML
29
29
  buf
30
30
  end
31
31
 
32
+ # Load fully-decoded audio from any Ruby IO-like object. The
33
+ # whole stream is decoded into RAM — for streaming playback,
34
+ # use `Music.from_stream` instead.
35
+ def self.from_stream(io)
36
+ stream = SFML::InputStream.new(io)
37
+ ptr = C::Audio.sfSoundBuffer_createFromStream(stream.to_ptr)
38
+ raise Error, "sfSoundBuffer_createFromStream returned NULL — unsupported format?" if ptr.null?
39
+
40
+ buf = allocate
41
+ buf.send(:_take_ownership, ptr)
42
+ buf
43
+ end
44
+
32
45
  # Default channel-layout map for the common 1- and 2-channel
33
46
  # cases — saves callers from spelling out the SoundChannel
34
47
  # enum just to build a mono blip or stereo waveform.
@@ -1,30 +1,143 @@
1
1
  module SFML
2
- # Static helpers for audio capture. Use SFML::SoundBufferRecorder
3
- # to actually record; these tell you what hardware is available.
2
+ # Callback-based audio-capture base class. Subclass and override
3
+ # `#on_start` (initialise capture state, return `true` to begin),
4
+ # `#on_process_samples(samples, channels)` (called every audio chunk
5
+ # with an Array<Integer> of interleaved int16 PCM, return `true` to
6
+ # keep recording or `false` to stop), and `#on_stop` (release any
7
+ # resources).
4
8
  #
5
- # SFML::SoundRecorder.available? #=> true / false
6
- # SFML::SoundRecorder.devices #=> ["alsa_input.pci-...", ...]
7
- # SFML::SoundRecorder.default_device #=> "alsa_input.pci-..."
8
- module SoundRecorder
9
- module_function
10
-
11
- # Is at least one audio input device present on the host?
12
- def available?
9
+ # The simpler "record to a SoundBuffer" path lives in
10
+ # SFML::SoundBufferRecorder — reach for SoundRecorder only when you
11
+ # need to stream samples somewhere else (file, socket, DSP pipeline).
12
+ #
13
+ # class LevelMeter < SFML::SoundRecorder
14
+ # def on_start
15
+ # @peak = 0
16
+ # true
17
+ # end
18
+ #
19
+ # def on_process_samples(samples, _channels)
20
+ # @peak = [@peak, *samples.map(&:abs)].max
21
+ # true
22
+ # end
23
+ #
24
+ # def on_stop
25
+ # puts "Peak: #{@peak}"
26
+ # end
27
+ # end
28
+ #
29
+ # meter = LevelMeter.new
30
+ # meter.start(sample_rate: 44_100)
31
+ # sleep 2
32
+ # meter.stop
33
+ #
34
+ # CAVEATS
35
+ # * All three callbacks run on CSFML's audio thread; heavy Ruby work
36
+ # on the audio thread will glitch the capture.
37
+ # * Always keep a reference to the SoundRecorder object — if the Ruby
38
+ # object is GC'd while CSFML is mid-capture, the process crashes.
39
+ class SoundRecorder
40
+ # ---- Static helpers (work without a recorder instance) ------------
41
+
42
+ def self.available?
13
43
  C::Audio.sfSoundRecorder_isAvailable
14
44
  end
15
45
 
16
- def default_device
46
+ def self.default_device
17
47
  C::Audio.sfSoundRecorder_getDefaultDevice
18
48
  end
19
49
 
20
50
  # All input devices the OS exposes to SFML, as an Array of String
21
- # names. Pass any of them to SoundBufferRecorder#device= to switch.
22
- def devices
51
+ # names. Pass any of them to SoundRecorder#device= or
52
+ # SoundBufferRecorder#device= to switch.
53
+ def self.devices
23
54
  count_buf = FFI::MemoryPointer.new(:size_t)
24
55
  array_ptr = C::Audio.sfSoundRecorder_getAvailableDevices(count_buf)
25
56
  n = count_buf.read(:size_t)
26
57
  return [] if array_ptr.null? || n.zero?
27
58
  array_ptr.read_array_of_pointer(n).map { |p| p.read_string }
28
59
  end
60
+
61
+ # ---- Instance API -------------------------------------------------
62
+
63
+ def initialize
64
+ # Strong refs so the GC doesn't disappear callbacks under CSFML.
65
+ @start_cb = FFI::Function.new(:bool, [:pointer]) do |_user|
66
+ on_start ? true : false
67
+ end
68
+ @process_cb = FFI::Function.new(:bool, [:pointer, :size_t, :pointer]) do |samples_ptr, count, _user|
69
+ samples = count.zero? ? [] : samples_ptr.read_array_of_int16(count)
70
+ on_process_samples(samples, channel_count) ? true : false
71
+ end
72
+ @stop_cb = FFI::Function.new(:void, [:pointer]) do |_user|
73
+ on_stop
74
+ nil
75
+ end
76
+
77
+ ptr = C::Audio.sfSoundRecorder_create(@start_cb, @process_cb, @stop_cb, nil)
78
+ raise Error, "sfSoundRecorder_create returned NULL" if ptr.null?
79
+
80
+ @handle = FFI::AutoPointer.new(ptr, C::Audio.method(:sfSoundRecorder_destroy))
81
+ end
82
+
83
+ # ---- Subclass hooks ----
84
+
85
+ # Called once before the capture starts. Return `true` to begin
86
+ # the capture or `false` to abort. Default does nothing and
87
+ # accepts the start.
88
+ def on_start
89
+ true
90
+ end
91
+
92
+ # Called with each chunk of captured audio. `samples` is an
93
+ # Array<Integer> of interleaved int16 PCM (length = frames *
94
+ # channels). Return `true` to keep capturing or `false` to stop.
95
+ # Default raises so subclasses must implement it.
96
+ def on_process_samples(_samples, _channels)
97
+ raise NoMethodError, "#{self.class} must override #on_process_samples"
98
+ end
99
+
100
+ # Called once after the capture is done. Default is a no-op.
101
+ def on_stop; end
102
+
103
+ # ---- Public recorder API ----
104
+
105
+ def start(sample_rate: 44_100)
106
+ C::Audio.sfSoundRecorder_start(@handle, Integer(sample_rate)) ||
107
+ raise(Error, "sfSoundRecorder_start failed (no input device or driver error)")
108
+ self
109
+ end
110
+
111
+ def stop
112
+ C::Audio.sfSoundRecorder_stop(@handle)
113
+ self
114
+ end
115
+
116
+ def sample_rate = C::Audio.sfSoundRecorder_getSampleRate(@handle)
117
+ def channel_count = C::Audio.sfSoundRecorder_getChannelCount(@handle)
118
+
119
+ def channel_count=(n)
120
+ C::Audio.sfSoundRecorder_setChannelCount(@handle, Integer(n))
121
+ end
122
+
123
+ def device = C::Audio.sfSoundRecorder_getDevice(@handle)
124
+
125
+ def device=(name)
126
+ C::Audio.sfSoundRecorder_setDevice(@handle, name.to_s) ||
127
+ raise(Error, "sfSoundRecorder_setDevice failed for #{name.inspect}")
128
+ end
129
+
130
+ # The channel layout the recorder is producing, as an Array of
131
+ # `sfSoundChannel` enum values (1 = Mono, 2 = FrontLeft,
132
+ # 3 = FrontRight, etc — see SoundBuffer::DEFAULT_CHANNEL_MAPS).
133
+ def channel_map
134
+ count_buf = FFI::MemoryPointer.new(:size_t)
135
+ ptr = C::Audio.sfSoundRecorder_getChannelMap(@handle, count_buf)
136
+ n = count_buf.read(:size_t)
137
+ return [] if ptr.null? || n.zero?
138
+ ptr.read_array_of_int32(n)
139
+ end
140
+
141
+ attr_reader :handle # :nodoc:
29
142
  end
30
143
  end
@@ -169,6 +169,109 @@ module SFML
169
169
  C::Audio.sfSoundStream_setRelativeToListener(@handle, !!value)
170
170
  end
171
171
 
172
+ # ---- 3D-audio surface (mirror of Sound / Music) -------------------
173
+
174
+ def pan = C::Audio.sfSoundStream_getPan(@handle)
175
+
176
+ def pan=(value)
177
+ C::Audio.sfSoundStream_setPan(@handle, value.to_f)
178
+ end
179
+
180
+ def max_distance = C::Audio.sfSoundStream_getMaxDistance(@handle)
181
+
182
+ def max_distance=(value)
183
+ C::Audio.sfSoundStream_setMaxDistance(@handle, value.to_f)
184
+ end
185
+
186
+ def min_gain = C::Audio.sfSoundStream_getMinGain(@handle)
187
+
188
+ def min_gain=(value)
189
+ C::Audio.sfSoundStream_setMinGain(@handle, value.to_f)
190
+ end
191
+
192
+ def max_gain = C::Audio.sfSoundStream_getMaxGain(@handle)
193
+
194
+ def max_gain=(value)
195
+ C::Audio.sfSoundStream_setMaxGain(@handle, value.to_f)
196
+ end
197
+
198
+ def spatialization_enabled? = C::Audio.sfSoundStream_isSpatializationEnabled(@handle)
199
+
200
+ def spatialization_enabled=(value)
201
+ C::Audio.sfSoundStream_setSpatializationEnabled(@handle, !!value)
202
+ end
203
+
204
+ def direction
205
+ v = C::Audio.sfSoundStream_getDirection(@handle)
206
+ Vector3.new(v[:x], v[:y], v[:z])
207
+ end
208
+
209
+ def direction=(value)
210
+ vec = value.is_a?(Vector3) ? value : Vector3.new(*value)
211
+ packed = C::System::Vector3f.new
212
+ packed[:x] = vec.x.to_f; packed[:y] = vec.y.to_f; packed[:z] = vec.z.to_f
213
+ C::Audio.sfSoundStream_setDirection(@handle, packed)
214
+ end
215
+
216
+ def cone
217
+ SoundCone.from_native(C::Audio.sfSoundStream_getCone(@handle))
218
+ end
219
+
220
+ def cone=(value)
221
+ cone =
222
+ case value
223
+ when SoundCone then value
224
+ when Hash then SoundCone.new(**value)
225
+ else
226
+ raise ArgumentError, "SoundStream#cone= expects SoundCone or Hash; got #{value.class}"
227
+ end
228
+ C::Audio.sfSoundStream_setCone(@handle, cone.to_native)
229
+ end
230
+
231
+ def velocity
232
+ v = C::Audio.sfSoundStream_getVelocity(@handle)
233
+ Vector3.new(v[:x], v[:y], v[:z])
234
+ end
235
+
236
+ def velocity=(value)
237
+ vec = value.is_a?(Vector3) ? value : Vector3.new(*value)
238
+ packed = C::System::Vector3f.new
239
+ packed[:x] = vec.x.to_f; packed[:y] = vec.y.to_f; packed[:z] = vec.z.to_f
240
+ C::Audio.sfSoundStream_setVelocity(@handle, packed)
241
+ end
242
+
243
+ def doppler_factor = C::Audio.sfSoundStream_getDopplerFactor(@handle)
244
+
245
+ def doppler_factor=(value)
246
+ C::Audio.sfSoundStream_setDopplerFactor(@handle, value.to_f)
247
+ end
248
+
249
+ def directional_attenuation_factor
250
+ C::Audio.sfSoundStream_getDirectionalAttenuationFactor(@handle)
251
+ end
252
+
253
+ def directional_attenuation_factor=(value)
254
+ C::Audio.sfSoundStream_setDirectionalAttenuationFactor(@handle, value.to_f)
255
+ end
256
+
257
+ # Install a real-time DSP filter (same contract as Sound /
258
+ # Music — see Sound#effect_processor=). Pass `nil` to remove.
259
+ def effect_processor=(callable)
260
+ @effect_cb = callable.nil? ? nil : Audio._build_effect_processor(callable)
261
+ C::Audio.sfSoundStream_setEffectProcessor(@handle, @effect_cb, nil)
262
+ end
263
+
264
+ # The channel layout the stream is producing, as an Array of
265
+ # `sfSoundChannel` enum values (1 = Mono, 2 = FrontLeft,
266
+ # 3 = FrontRight, etc — see SoundBuffer::DEFAULT_CHANNEL_MAPS).
267
+ def channel_map
268
+ count_buf = FFI::MemoryPointer.new(:size_t)
269
+ ptr = C::Audio.sfSoundStream_getChannelMap(@handle, count_buf)
270
+ n = count_buf.read(:size_t)
271
+ return [] if ptr.null? || n.zero?
272
+ ptr.read_array_of_int32(n)
273
+ end
274
+
172
275
  attr_reader :handle # :nodoc:
173
276
 
174
277
  private
data/lib/sfml/c/audio.rb CHANGED
@@ -28,6 +28,7 @@ module SFML
28
28
  attach_function :sfSoundBuffer_getChannelCount,[:sound_buffer_t], :uint32
29
29
  attach_function :sfSoundBuffer_copy, [:sound_buffer_t], :sound_buffer_t
30
30
  attach_function :sfSoundBuffer_createFromMemory, [:pointer, :size_t], :sound_buffer_t
31
+ attach_function :sfSoundBuffer_createFromStream, [:pointer], :sound_buffer_t
31
32
  attach_function :sfSoundBuffer_createFromSamples,
32
33
  [:pointer, :uint64, :uint32, :uint32, :pointer, :size_t], :sound_buffer_t
33
34
  attach_function :sfSoundBuffer_getSampleCount, [:sound_buffer_t], :uint64
@@ -94,6 +95,7 @@ module SFML
94
95
 
95
96
  attach_function :sfMusic_createFromFile, [:string], :music_t
96
97
  attach_function :sfMusic_createFromMemory, [:pointer, :size_t], :music_t
98
+ attach_function :sfMusic_createFromStream, [:pointer], :music_t
97
99
  attach_function :sfMusic_destroy, [:music_t], :void
98
100
  attach_function :sfMusic_getChannelCount, [:music_t], :uint32
99
101
  attach_function :sfMusic_getSampleRate, [:music_t], :uint32
@@ -187,6 +189,30 @@ module SFML
187
189
  attach_function :sfSoundStream_setRelativeToListener, [:sound_stream_t, :bool], :void
188
190
  attach_function :sfSoundStream_isRelativeToListener, [:sound_stream_t], :bool
189
191
 
192
+ # 3D-audio surface — symmetric with sfSound / sfMusic.
193
+ attach_function :sfSoundStream_setPan, [:sound_stream_t, :float], :void
194
+ attach_function :sfSoundStream_getPan, [:sound_stream_t], :float
195
+ attach_function :sfSoundStream_setMaxDistance, [:sound_stream_t, :float], :void
196
+ attach_function :sfSoundStream_getMaxDistance, [:sound_stream_t], :float
197
+ attach_function :sfSoundStream_setMinGain, [:sound_stream_t, :float], :void
198
+ attach_function :sfSoundStream_getMinGain, [:sound_stream_t], :float
199
+ attach_function :sfSoundStream_setMaxGain, [:sound_stream_t, :float], :void
200
+ attach_function :sfSoundStream_getMaxGain, [:sound_stream_t], :float
201
+ attach_function :sfSoundStream_setSpatializationEnabled, [:sound_stream_t, :bool], :void
202
+ attach_function :sfSoundStream_isSpatializationEnabled, [:sound_stream_t], :bool
203
+ attach_function :sfSoundStream_setDirection, [:sound_stream_t, System::Vector3f.by_value], :void
204
+ attach_function :sfSoundStream_getDirection, [:sound_stream_t], System::Vector3f.by_value
205
+ attach_function :sfSoundStream_setCone, [:sound_stream_t, SoundSourceCone.by_value], :void
206
+ attach_function :sfSoundStream_getCone, [:sound_stream_t], SoundSourceCone.by_value
207
+ attach_function :sfSoundStream_setVelocity, [:sound_stream_t, System::Vector3f.by_value], :void
208
+ attach_function :sfSoundStream_getVelocity, [:sound_stream_t], System::Vector3f.by_value
209
+ attach_function :sfSoundStream_setDopplerFactor, [:sound_stream_t, :float], :void
210
+ attach_function :sfSoundStream_getDopplerFactor, [:sound_stream_t], :float
211
+ attach_function :sfSoundStream_setDirectionalAttenuationFactor, [:sound_stream_t, :float], :void
212
+ attach_function :sfSoundStream_getDirectionalAttenuationFactor, [:sound_stream_t], :float
213
+ attach_function :sfSoundStream_setEffectProcessor, [:sound_stream_t, :sf_effect_processor, :pointer], :void
214
+ attach_function :sfSoundStream_getChannelMap, [:sound_stream_t, :pointer], :pointer
215
+
190
216
  # ---- SoundBufferRecorder ----
191
217
  # The simple "record into a SoundBuffer" path. Raw sfSoundRecorder
192
218
  # (callback-based) and sfSoundStream (custom audio source via
@@ -210,6 +236,27 @@ module SFML
210
236
  attach_function :sfSoundRecorder_getDefaultDevice, [], :string
211
237
  attach_function :sfSoundRecorder_getAvailableDevices, [:pointer], :pointer
212
238
 
239
+ # Callback-based recorder. The three callbacks run on CSFML's
240
+ # audio thread; the Ruby SoundRecorder must hold strong refs to
241
+ # the FFI::Function objects for the lifetime of the recorder.
242
+ typedef :pointer, :sound_recorder_t
243
+ callback :sound_recorder_start, [:pointer], :bool
244
+ callback :sound_recorder_process, [:pointer, :size_t, :pointer], :bool
245
+ callback :sound_recorder_stop, [:pointer], :void
246
+
247
+ attach_function :sfSoundRecorder_create,
248
+ [:sound_recorder_start, :sound_recorder_process, :sound_recorder_stop, :pointer],
249
+ :sound_recorder_t
250
+ attach_function :sfSoundRecorder_destroy, [:sound_recorder_t], :void
251
+ attach_function :sfSoundRecorder_start, [:sound_recorder_t, :uint32], :bool
252
+ attach_function :sfSoundRecorder_stop, [:sound_recorder_t], :void
253
+ attach_function :sfSoundRecorder_getSampleRate, [:sound_recorder_t], :uint32
254
+ attach_function :sfSoundRecorder_setDevice, [:sound_recorder_t, :string], :bool
255
+ attach_function :sfSoundRecorder_getDevice, [:sound_recorder_t], :string
256
+ attach_function :sfSoundRecorder_setChannelCount, [:sound_recorder_t, :uint32], :void
257
+ attach_function :sfSoundRecorder_getChannelCount, [:sound_recorder_t], :uint32
258
+ attach_function :sfSoundRecorder_getChannelMap, [:sound_recorder_t, :pointer], :pointer
259
+
213
260
  # ---- Listener (the "ear" — global, no handle) ----
214
261
  attach_function :sfListener_setGlobalVolume, [:float], :void
215
262
  attach_function :sfListener_getGlobalVolume, [], :float