ruby-sfml 3.0.0.0 → 3.0.0.1
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 +4 -4
- data/CHANGELOG.md +130 -0
- data/README.md +34 -45
- data/lib/sfml/audio/internal.rb +41 -0
- data/lib/sfml/audio/listener.rb +31 -0
- data/lib/sfml/audio/music.rb +64 -0
- data/lib/sfml/audio/sound.rb +82 -0
- data/lib/sfml/audio/sound_cone.rb +56 -0
- data/lib/sfml/audio/sound_stream.rb +206 -0
- data/lib/sfml/c/audio.rb +78 -0
- data/lib/sfml/c/graphics.rb +51 -0
- data/lib/sfml/c/network.rb +104 -0
- data/lib/sfml/c/system.rb +10 -0
- data/lib/sfml/c/window.rb +42 -0
- data/lib/sfml/graphics/image.rb +23 -0
- data/lib/sfml/graphics/render_states.rb +5 -3
- data/lib/sfml/graphics/render_target.rb +23 -2
- data/lib/sfml/graphics/render_window.rb +55 -0
- data/lib/sfml/graphics/shader.rb +71 -11
- data/lib/sfml/graphics/stencil_mode.rb +93 -0
- data/lib/sfml/graphics/vertex_buffer.rb +127 -0
- data/lib/sfml/network/ftp.rb +184 -0
- data/lib/sfml/network/http.rb +117 -0
- data/lib/sfml/network/socket_selector.rb +93 -0
- data/lib/sfml/version.rb +1 -1
- data/lib/sfml/window/event.rb +14 -0
- data/lib/sfml/window/sensor.rb +50 -0
- data/lib/sfml/window/touch.rb +39 -0
- data/lib/sfml/window/window.rb +57 -0
- data/lib/sfml.rb +27 -2
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f6e44279278680f06163024e2dd2f1eecbc8e8624960d589ec74e591849c9b3
|
|
4
|
+
data.tar.gz: 23fc48fc06aa0f7e6807b681c2601784de0159d9429055617e87d1e7977d20fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d351b03bb583fb5bb7ddd469da17838d876d7f5c99cbdae5387c94dfbf933f7299362d70bc8360c71b1b5737c49622bb55795869f2bd49d1784aab910f16d32d
|
|
7
|
+
data.tar.gz: d5b91cdb38ee758ca2fc76daf52f2aba4c6172b7d6798d51783caa677a10684d394881dca6e01e704e8db9d1be372ef38035de43e4f49b3bab26a9066d5728f5
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,136 @@ ruby-sfml's own patch level.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [3.0.0.1] — 2026-05-07
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `Window#icon=` and `RenderWindow#icon=` — set the window's title-bar /
|
|
15
|
+
taskbar icon from any `SFML::Image`. Wraps `sfWindow_setIcon` /
|
|
16
|
+
`sfRenderWindow_setIcon`. New example
|
|
17
|
+
[21_window_icon](examples/21_window_icon/window_icon.rb) builds a
|
|
18
|
+
procedural 32×32 ruby-style icon to demo the API.
|
|
19
|
+
- `Image#save_to_memory(format)` — encode an image to a Ruby String of
|
|
20
|
+
bytes in the given format (`"png"`, `"jpg"`, `"bmp"`, `"tga"`),
|
|
21
|
+
without touching the disk. Useful for screenshots over the network,
|
|
22
|
+
data: URLs, or piping into other image-processing libraries. Wraps
|
|
23
|
+
`sfImage_saveToMemory` plus the `sfBuffer_*` helpers in
|
|
24
|
+
libcsfml-system.
|
|
25
|
+
- Shader array uniforms — `shader[:positions] = [[x, y], ...]` and
|
|
26
|
+
similar for vec3 / vec4 arrays; also accepts `Vector2` / `Vector3`
|
|
27
|
+
elements interchangeably. New explicit `Shader#set_float_array` for
|
|
28
|
+
`uniform float arr[N];` (which can't be inferred via `[]=` because
|
|
29
|
+
it'd collide with the vec3 case). Wraps the
|
|
30
|
+
`sfShader_set{Float,Vec2,Vec3,Vec4}UniformArray` family.
|
|
31
|
+
- `Window#minimum_size=` / `#maximum_size=` and the same on
|
|
32
|
+
`RenderWindow` — clamp how small or large the OS lets the user
|
|
33
|
+
drag the window. Accepts `[w, h]`, `Vector2`, or `nil` (clears the
|
|
34
|
+
limit). Wraps `sfWindow_setMinimumSize` / `setMaximumSize` and the
|
|
35
|
+
RenderWindow equivalents.
|
|
36
|
+
- `Listener.velocity` and `Listener.cone` — finish the 3D-audio
|
|
37
|
+
surface on the listener side. Velocity feeds the Doppler effect
|
|
38
|
+
for sources whose `doppler_factor` is non-zero; cone (a
|
|
39
|
+
`SoundCone`, or a Hash convertible to one) attenuates sources
|
|
40
|
+
outside a directional pickup pattern.
|
|
41
|
+
- 3D-audio polish for `Sound` and `Music` — now expose `velocity`,
|
|
42
|
+
`doppler_factor`, `direction`, and `cone` (via the new
|
|
43
|
+
`SFML::SoundCone` value class — `inner_angle`, `outer_angle`,
|
|
44
|
+
`outer_gain`). Cone setter accepts both a `SoundCone` and a
|
|
45
|
+
Hash. Plus `effect_processor=` for installing a real-time DSP
|
|
46
|
+
Ruby callable on the audio thread (`->(samples, channels) {
|
|
47
|
+
... }`); pass `nil` to remove it. The DSP path is documented as
|
|
48
|
+
Ruby+GVL-limited and best for very light effects only. Wraps
|
|
49
|
+
the corresponding `sfSound_*` and `sfMusic_*` setters/getters
|
|
50
|
+
plus `setEffectProcessor`.
|
|
51
|
+
- `SFML::VertexBuffer` — GPU-resident vertex buffer (VBO). Same
|
|
52
|
+
shape as `VertexArray` but vertices live on the GPU, so a draw
|
|
53
|
+
call ships only an OpenGL handle instead of re-uploading every
|
|
54
|
+
frame. `new(vertices, primitive_type:, usage:)` (one of `:stream`
|
|
55
|
+
/ `:dynamic` / `:static`), `update(vertices, offset:)` for
|
|
56
|
+
partial uploads, `draw_range_on(target, first, count)` to draw a
|
|
57
|
+
slice. `VertexBuffer.available?` reports whether the GPU
|
|
58
|
+
supports VBOs at all (fall back to `VertexArray` if it doesn't).
|
|
59
|
+
Wraps the `sfVertexBuffer_*` family plus the
|
|
60
|
+
`sfRender{Window,Texture}_drawVertexBuffer{,Range}` draw paths.
|
|
61
|
+
- `Window.from_handle` / `RenderWindow.from_handle` — wrap an
|
|
62
|
+
existing OS-level window (HWND, NSView*, X11 Window xid). The
|
|
63
|
+
outside framework owns the window's lifecycle; SFML just renders
|
|
64
|
+
into it. Pair with `#native_handle` to interop in the other
|
|
65
|
+
direction. Wraps `sfWindow_createFromHandle` /
|
|
66
|
+
`sfRenderWindow_createFromHandle` and the matching
|
|
67
|
+
`getNativeHandle` getters.
|
|
68
|
+
- `SFML::SoundStream` — procedural audio source. Subclass it and
|
|
69
|
+
override `#on_get_data` to return an Array of `Int16` PCM samples
|
|
70
|
+
(or `nil` to stop); optionally override `#on_seek(time)` to
|
|
71
|
+
support `playing_offset=`. Same playback / 3D-positional API as
|
|
72
|
+
`Sound` and `Music` (volume, pitch, looping, position,
|
|
73
|
+
attenuation, min_distance, relative_to_listener). Wraps the full
|
|
74
|
+
`sfSoundStream_*` family. Includes example
|
|
75
|
+
[23_sound_stream](examples/23_sound_stream/sound_stream.rb) — a
|
|
76
|
+
real-time sine synth with arrow-key pitch / volume control.
|
|
77
|
+
- `SFML::Network::SocketSelector` — multiplex many sockets onto one
|
|
78
|
+
blocking `wait`. `add` / `remove` / `clear` / `wait(timeout:)`
|
|
79
|
+
/ `ready?(socket)`. Polymorphic across `TcpListener`, `TcpSocket`,
|
|
80
|
+
`UdpSocket`. The `wait` call releases the GVL, so other Ruby
|
|
81
|
+
threads keep running during the syscall. Wraps the
|
|
82
|
+
`sfSocketSelector_*` family.
|
|
83
|
+
- `SFML::Network::Ftp` — CSFML's FTP client wrapped as idiomatic Ruby:
|
|
84
|
+
`connect`, `login` / `login_anonymous`, `working_directory`,
|
|
85
|
+
`directory_listing`, `change_directory`, `parent_directory`,
|
|
86
|
+
`create_directory`, `delete_directory`, `rename_file`,
|
|
87
|
+
`delete_file`, `download`, `upload`, `send_command`, `keep_alive`,
|
|
88
|
+
`disconnect`. Each call returns a `Response` (or
|
|
89
|
+
`DirectoryResponse` / `ListingResponse`) with `#ok?`, `#status`,
|
|
90
|
+
`#status_symbol`, `#message`, plus `#directory` or `#names`
|
|
91
|
+
where applicable. Network calls release the GVL.
|
|
92
|
+
Same caveat as Http — Ruby stdlib `Net::FTP` is the better choice
|
|
93
|
+
in production.
|
|
94
|
+
- `SFML::Network::Http` — CSFML's HTTP/1.x client in idiomatic Ruby
|
|
95
|
+
form. `Http.new(host, port:)` plus `#send_request(method:, uri:,
|
|
96
|
+
fields:, body:, http_version:, timeout:)` returns an `Http::Response`
|
|
97
|
+
with `#status` (Integer) / `#status_symbol` (`:ok`, `:not_found`,
|
|
98
|
+
`:connection_failed`, …), `#body`, `#field(name)`, `#http_version`.
|
|
99
|
+
Marked `blocking: true` so the GVL is released during the network
|
|
100
|
+
round-trip and concurrent Ruby threads can run. Wraps
|
|
101
|
+
`sfHttp_*`, `sfHttpRequest_*`, `sfHttpResponse_*`. Note: for any
|
|
102
|
+
non-trivial use (TLS, redirects, JSON, retries), Ruby's stdlib
|
|
103
|
+
`Net::HTTP` is the better tool — this binding exists for parity
|
|
104
|
+
with CSFML, not because we recommend it.
|
|
105
|
+
- `SFML::Sensor` polling module — `available?(type)`, `enable(type)`,
|
|
106
|
+
`disable(type)`, `value(type)` for the six sensor types
|
|
107
|
+
(`:accelerometer`, `:gyroscope`, `:magnetometer`, `:gravity`,
|
|
108
|
+
`:user_acceleration`, `:orientation`). The `:sensor_changed` event
|
|
109
|
+
variant now decodes its `sensor:` and `value:` payloads. Wraps
|
|
110
|
+
`sfSensor_isAvailable` / `sfSensor_setEnabled` /
|
|
111
|
+
`sfSensor_getValue`.
|
|
112
|
+
- `SFML::Touch` polling module — `down?(finger)` and
|
|
113
|
+
`position(finger, relative_to: window)`. Touch event variants
|
|
114
|
+
(`:touch_began`, `:touch_moved`, `:touch_ended`) now decode their
|
|
115
|
+
`finger:` and `position:` payloads (previously fell through to
|
|
116
|
+
empty data). Wraps `sfTouch_isDown` / `sfTouch_getPosition` /
|
|
117
|
+
`sfTouch_getPositionRenderWindow`.
|
|
118
|
+
- `Sound#playing_offset` / `playing_offset=` and `Music#playing_offset`
|
|
119
|
+
/ `playing_offset=` — read or seek the playback head as a
|
|
120
|
+
`SFML::Time`. Setter also accepts a Numeric (interpreted as
|
|
121
|
+
seconds). Wraps `sfSound_setPlayingOffset` /
|
|
122
|
+
`sfMusic_setPlayingOffset` and the matching getters.
|
|
123
|
+
- Stencil buffer support — new `SFML::StencilMode` value class with
|
|
124
|
+
symbolic comparisons (`:equal`, `:always`, etc.) and update
|
|
125
|
+
operations (`:replace`, `:keep`, etc.). Pass it via `stencil_mode:`
|
|
126
|
+
to `RenderTarget#draw` for two-pass mask/clip effects, and clear
|
|
127
|
+
the stencil with `target.clear(color, stencil: N)` or
|
|
128
|
+
`target.clear(stencil: N)`. Wraps `sfRenderWindow_clearStencil` /
|
|
129
|
+
`clearColorAndStencil` (and the RenderTexture twins) plus the
|
|
130
|
+
existing `stencil_mode` slot in `sfRenderStates`. New example
|
|
131
|
+
[22_stencil_mask](examples/22_stencil_mask/stencil_mask.rb)
|
|
132
|
+
demonstrates a cursor-following spotlight that clips an animated
|
|
133
|
+
rainbow background.
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
- `at_exit` hook now writes the unhandled exception (message, class,
|
|
137
|
+
backtrace) to stderr before calling `exit!`. Previously `exit!`
|
|
138
|
+
short-circuited Ruby's terminal exception reporter, so an error in
|
|
139
|
+
`setup` or any other top-level user code looked like a silent exit.
|
|
140
|
+
|
|
11
141
|
## [3.0.0.0] — initial release
|
|
12
142
|
|
|
13
143
|
First public cut. Targets **CSFML 3.0.0** (released March 2025) and
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Modern, idiomatic Ruby bindings for [SFML 3.x](https://www.sfml-dev.org/) via [CSFML](https://github.com/SFML/CSFML) and [Ruby FFI](https://github.com/ffi/ffi).
|
|
4
4
|
|
|
5
|
-
> **Status:** the API surface is complete for SFML 3.0 — system, window, graphics, audio, network, plus the higher-level `Game` and `Assets` helpers.
|
|
5
|
+
> **Status:** the API surface is complete for SFML 3.0 — system, window, graphics (incl. stencil buffer + VBOs), audio (incl. 3D positional + custom DSP + procedural streams), network (incl. HTTP / FTP / socket selector), input (keyboard, mouse, joystick, touch, sensors), plus the higher-level `Game` and `Assets` helpers. 387 RSpec examples, 23 runnable example folders. RBS signatures are the main remaining engineering item.
|
|
6
6
|
|
|
7
7
|
## Why
|
|
8
8
|
|
|
@@ -76,61 +76,47 @@ end
|
|
|
76
76
|
| Area | Classes |
|
|
77
77
|
| -------- | ------------------------------------------------------------ |
|
|
78
78
|
| System | `Vector2`, `Vector3`, `Rect`, `Time`, `Clock` |
|
|
79
|
-
| Window | `RenderWindow`, `Window` (bare, GL-only), `VideoMode`, `Event`, `Keyboard`, `Mouse`, `Joystick`, `Cursor`, `Clipboard` |
|
|
80
|
-
| Graphics | `Color`, `Image`, `Texture`, `RenderTexture`, `Sprite`, `CircleShape`, `RectangleShape`, `ConvexShape`, `Vertex`, `VertexArray`, `Font`, `Text`, `View`, `BlendMode`, `RenderStates`, `Shader`, `Transform` |
|
|
81
|
-
| Audio | `SoundBuffer`, `Sound`, `Music`, `Listener`, `SoundRecorder`, `SoundBufferRecorder` (3D positional
|
|
79
|
+
| Window | `RenderWindow`, `Window` (bare, GL-only), `VideoMode`, `Event`, `Keyboard`, `Mouse`, `Joystick`, `Touch`, `Sensor`, `Cursor`, `Clipboard` |
|
|
80
|
+
| Graphics | `Color`, `Image`, `Texture`, `RenderTexture`, `Sprite`, `CircleShape`, `RectangleShape`, `ConvexShape`, `Vertex`, `VertexArray`, `VertexBuffer`, `Font`, `Text`, `View`, `BlendMode`, `StencilMode`, `RenderStates`, `Shader`, `Transform` |
|
|
81
|
+
| Audio | `SoundBuffer`, `Sound`, `Music`, `Listener`, `SoundCone`, `SoundStream`, `SoundRecorder`, `SoundBufferRecorder` (3D positional + cones + Doppler + custom DSP via `effect_processor=`) |
|
|
82
82
|
| Helpers | `Assets` (search-path + cache), `Game` (lifecycle main loop) |
|
|
83
83
|
|
|
84
|
-
**Network**: `IpAddress`, `TcpSocket`, `TcpListener`, `UdpSocket` for stream / datagram networking.
|
|
84
|
+
**Network**: `IpAddress`, `TcpSocket`, `TcpListener`, `UdpSocket`, `SocketSelector` for stream / datagram networking, plus the niche `Http` and `Ftp` clients (use Ruby's `Net::HTTP` / `Net::FTP` if you have the choice — these exist for parity with CSFML).
|
|
85
85
|
|
|
86
86
|
## What's intentionally *not* wrapped
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
- `sf::
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
the common case; if you need static-mesh perf, open an issue
|
|
110
|
-
- Geometry shaders — only vertex and fragment stages on `SFML::Shader`
|
|
111
|
-
- `sf::Shader#setUniformArray` (bulk uniforms) — set elements one by one
|
|
112
|
-
- Stencil buffer ops (`clearStencil`, custom `StencilMode`) — accept
|
|
113
|
-
CSFML defaults
|
|
114
|
-
- `sf::Image#saveToMemory` — only `Image#save(path)` is wrapped
|
|
115
|
-
|
|
116
|
-
**Advanced audio features**
|
|
117
|
-
- Sound / Music cones, velocity, Doppler factor, custom DSP via
|
|
118
|
-
`setEffectProcessor` — basic 3D positional + attenuation is in;
|
|
119
|
-
the rest is rarely used in 2D gamedev
|
|
120
|
-
- `sf::Listener` cone — same reasoning
|
|
121
|
-
|
|
122
|
-
**Embedding / integration corners**
|
|
123
|
-
- `RenderWindow.createFromHandle` (embed in another framework's window)
|
|
124
|
-
- Custom `sf::InputStream` for loading assets from non-file sources
|
|
125
|
-
- Window icon, min/max size, native handle accessors on `SFML::Window`
|
|
88
|
+
A handful of CSFML 3 corners deliberately stay out:
|
|
89
|
+
|
|
90
|
+
- **Geometry shaders** — CSFML doesn't expose them at all (only vertex
|
|
91
|
+
and fragment stages); nothing for us to wrap.
|
|
92
|
+
- **Raw `sf::SoundRecorder`** (per-buffer callbacks on the audio
|
|
93
|
+
thread) — use `SFML::SoundBufferRecorder` for the common "record
|
|
94
|
+
into memory, save on stop" path. The raw callback variant fights
|
|
95
|
+
the GVL hard.
|
|
96
|
+
- **Custom `sf::InputStream`** for loading assets from non-file
|
|
97
|
+
sources — Ruby has `IO`, just read into memory and use the
|
|
98
|
+
byte-string constructors.
|
|
99
|
+
|
|
100
|
+
**An aside on `Http` / `Ftp` / `SocketSelector`** — these *are*
|
|
101
|
+
wrapped (matches CSFML for parity), but for any non-trivial use
|
|
102
|
+
Ruby's stdlib `Net::HTTP` / `Net::FTP` and `IO.select` are better
|
|
103
|
+
tools.
|
|
104
|
+
|
|
105
|
+
**An aside on `SoundStream` and `effect_processor=`** — both *are*
|
|
106
|
+
wrapped, but their callbacks run on the SFML audio thread and
|
|
107
|
+
must reacquire the GVL each invocation. Fine for trivial DSP;
|
|
108
|
+
expect glitches for anything heavier.
|
|
126
109
|
|
|
127
110
|
**Other Ruby bindings worth knowing about**
|
|
111
|
+
|
|
128
112
|
- SFML 2.x is *not* covered. The previous-generation gem
|
|
129
113
|
[rbSFML](https://github.com/Groogy/rbSFML) targets SFML 2; it's
|
|
130
114
|
unmaintained and only works with Ruby ≤ 2.2.
|
|
115
|
+
- **RubySFML3** (Andy P., 2026) — a thin FFI wrapper that exposes
|
|
116
|
+
the CSFML C API directly. If you want raw bindings (no idiomatic
|
|
117
|
+
Ruby layer, no auto-cleanup), check that project instead.
|
|
131
118
|
|
|
132
|
-
If anything
|
|
133
|
-
"niche" is just a default, not a closed door.
|
|
119
|
+
If anything missing here is blocking you, **open an issue**.
|
|
134
120
|
|
|
135
121
|
## Examples
|
|
136
122
|
|
|
@@ -164,6 +150,9 @@ bundle exec ruby examples/<NN_name>/<name>.rb
|
|
|
164
150
|
| 18 | [draw_primitives](examples/18_draw_primitives/draw_primitives.rb) | Raw `draw_primitives` — line burst rebuilt every frame |
|
|
165
151
|
| 19 | [udp_loopback](examples/19_udp_loopback/udp_loopback.rb) | UDP send/receive on localhost via `Network::UdpSocket` |
|
|
166
152
|
| 20 | [bare_window](examples/20_bare_window/bare_window.rb) | `SFML::Window` (no 2D batcher) — events for raw-OpenGL apps |
|
|
153
|
+
| 21 | [window_icon](examples/21_window_icon/window_icon.rb) | Procedural 32×32 icon set as the window/taskbar icon |
|
|
154
|
+
| 22 | [stencil_mask](examples/22_stencil_mask/stencil_mask.rb) | Two-pass `StencilMode` masking — cursor spotlight clip |
|
|
155
|
+
| 23 | [sound_stream](examples/23_sound_stream/sound_stream.rb) | Real-time sine synth via `SFML::SoundStream` subclass |
|
|
167
156
|
|
|
168
157
|
## Idioms baked in
|
|
169
158
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
module Audio
|
|
3
|
+
# Internal helpers shared between Sound, Music, and SoundStream.
|
|
4
|
+
# Not part of the public API.
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
# Build an FFI::Function that adapts CSFML's effect-processor
|
|
8
|
+
# signature (raw float buffers, in/out frame counts) to a Ruby
|
|
9
|
+
# callable taking `(samples, channels)` and returning samples.
|
|
10
|
+
#
|
|
11
|
+
# Returns the FFI::Function — callers must keep a strong Ruby
|
|
12
|
+
# reference to it for as long as it's installed (otherwise GC
|
|
13
|
+
# collects the closure and the audio thread crashes).
|
|
14
|
+
def _build_effect_processor(callable)
|
|
15
|
+
FFI::Function.new(
|
|
16
|
+
:void,
|
|
17
|
+
[:pointer, :pointer, :pointer, :pointer, :uint32, :pointer],
|
|
18
|
+
) do |in_ptr, in_cnt_ptr, out_ptr, out_cnt_ptr, channels, _user|
|
|
19
|
+
in_count = in_cnt_ptr.read_uint32
|
|
20
|
+
out_count = out_cnt_ptr.read_uint32
|
|
21
|
+
|
|
22
|
+
written = 0
|
|
23
|
+
if in_count > 0
|
|
24
|
+
input = in_ptr.read_array_of_float(in_count * channels)
|
|
25
|
+
output =
|
|
26
|
+
begin
|
|
27
|
+
callable.call(input, channels) || []
|
|
28
|
+
rescue => e
|
|
29
|
+
warn "ruby-sfml effect_processor raised: #{e.class}: #{e.message}"
|
|
30
|
+
[]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
written = [output.length / channels, out_count].min
|
|
34
|
+
out_ptr.write_array_of_float(output.first(written * channels)) if written.positive?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
out_cnt_ptr.write_uint32(written)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/sfml/audio/listener.rb
CHANGED
|
@@ -51,5 +51,36 @@ module SFML
|
|
|
51
51
|
vec = value.is_a?(Vector3) ? value : Vector3.new(*value)
|
|
52
52
|
C::Audio.sfListener_setUpVector(vec.to_native_f)
|
|
53
53
|
end
|
|
54
|
+
|
|
55
|
+
# Listener velocity in world units / second — used by the
|
|
56
|
+
# Doppler effect on sources whose `doppler_factor` is non-zero.
|
|
57
|
+
def velocity
|
|
58
|
+
Vector3.from_native(C::Audio.sfListener_getVelocity)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def velocity=(value)
|
|
62
|
+
vec = value.is_a?(Vector3) ? value : Vector3.new(*value)
|
|
63
|
+
C::Audio.sfListener_setVelocity(vec.to_native_f)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Directional cone for the listener — same shape as the cone on
|
|
67
|
+
# individual sound sources (see SFML::SoundCone). Used for
|
|
68
|
+
# cone-shaped attenuation when the listener faces a particular
|
|
69
|
+
# direction (think headphones turning to follow a character's
|
|
70
|
+
# head).
|
|
71
|
+
def cone
|
|
72
|
+
SoundCone.from_native(C::Audio.sfListener_getCone)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def cone=(value)
|
|
76
|
+
cone =
|
|
77
|
+
case value
|
|
78
|
+
when SoundCone then value
|
|
79
|
+
when Hash then SoundCone.new(**value)
|
|
80
|
+
else
|
|
81
|
+
raise ArgumentError, "Listener.cone= expects SoundCone or Hash; got #{value.class}"
|
|
82
|
+
end
|
|
83
|
+
C::Audio.sfListener_setCone(cone.to_native)
|
|
84
|
+
end
|
|
54
85
|
end
|
|
55
86
|
end
|
data/lib/sfml/audio/music.rb
CHANGED
|
@@ -29,6 +29,70 @@ module SFML
|
|
|
29
29
|
|
|
30
30
|
def duration = Time.from_native(C::Audio.sfMusic_getDuration(@handle))
|
|
31
31
|
|
|
32
|
+
# Current playback head as a SFML::Time. Reads from the underlying
|
|
33
|
+
# OpenAL source — only meaningful while the music is playing or
|
|
34
|
+
# paused (not after #stop).
|
|
35
|
+
def playing_offset
|
|
36
|
+
Time.from_native(C::Audio.sfMusic_getPlayingOffset(@handle))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Seek to `value` (a SFML::Time, or seconds as a Numeric). Works
|
|
40
|
+
# while the music is playing, paused, or stopped.
|
|
41
|
+
def playing_offset=(value)
|
|
42
|
+
t = value.is_a?(Time) ? value : Time.seconds(value.to_f)
|
|
43
|
+
C::Audio.sfMusic_setPlayingOffset(@handle, t.to_native)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 3D velocity, Doppler factor, direction, cone — see Sound for
|
|
47
|
+
# the same methods on the simpler buffered source.
|
|
48
|
+
def velocity
|
|
49
|
+
v = C::Audio.sfMusic_getVelocity(@handle)
|
|
50
|
+
Vector3.new(v[:x], v[:y], v[:z])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def velocity=(value)
|
|
54
|
+
vec = value.is_a?(Vector3) ? value : Vector3.new(*value)
|
|
55
|
+
packed = C::System::Vector3f.new
|
|
56
|
+
packed[:x] = vec.x.to_f; packed[:y] = vec.y.to_f; packed[:z] = vec.z.to_f
|
|
57
|
+
C::Audio.sfMusic_setVelocity(@handle, packed)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def doppler_factor = C::Audio.sfMusic_getDopplerFactor(@handle)
|
|
61
|
+
def doppler_factor=(v) C::Audio.sfMusic_setDopplerFactor(@handle, v.to_f); end
|
|
62
|
+
|
|
63
|
+
def direction
|
|
64
|
+
v = C::Audio.sfMusic_getDirection(@handle)
|
|
65
|
+
Vector3.new(v[:x], v[:y], v[:z])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def direction=(value)
|
|
69
|
+
vec = value.is_a?(Vector3) ? value : Vector3.new(*value)
|
|
70
|
+
packed = C::System::Vector3f.new
|
|
71
|
+
packed[:x] = vec.x.to_f; packed[:y] = vec.y.to_f; packed[:z] = vec.z.to_f
|
|
72
|
+
C::Audio.sfMusic_setDirection(@handle, packed)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def cone
|
|
76
|
+
SoundCone.from_native(C::Audio.sfMusic_getCone(@handle))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def cone=(value)
|
|
80
|
+
cone =
|
|
81
|
+
case value
|
|
82
|
+
when SoundCone then value
|
|
83
|
+
when Hash then SoundCone.new(**value)
|
|
84
|
+
else
|
|
85
|
+
raise ArgumentError, "Music#cone= expects SoundCone or Hash; got #{value.class}"
|
|
86
|
+
end
|
|
87
|
+
C::Audio.sfMusic_setCone(@handle, cone.to_native)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# See Sound#effect_processor= — same audio-thread DSP callback.
|
|
91
|
+
def effect_processor=(callable)
|
|
92
|
+
@effect_cb = callable.nil? ? nil : Audio._build_effect_processor(callable)
|
|
93
|
+
C::Audio.sfMusic_setEffectProcessor(@handle, @effect_cb, nil)
|
|
94
|
+
end
|
|
95
|
+
|
|
32
96
|
# Cached on the Ruby side; see Sound#looping? for the why.
|
|
33
97
|
def looping?
|
|
34
98
|
@looping
|
data/lib/sfml/audio/sound.rb
CHANGED
|
@@ -41,6 +41,88 @@ module SFML
|
|
|
41
41
|
def paused? = status == :paused
|
|
42
42
|
def stopped? = status == :stopped
|
|
43
43
|
|
|
44
|
+
# Current playback head as a SFML::Time. Reads from the underlying
|
|
45
|
+
# OpenAL source — only meaningful while the sound is playing or
|
|
46
|
+
# paused (not after #stop).
|
|
47
|
+
def playing_offset
|
|
48
|
+
Time.from_native(C::Audio.sfSound_getPlayingOffset(@handle))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Seek to `value` (a SFML::Time, or seconds as a Numeric). Works
|
|
52
|
+
# while the sound is playing, paused, or stopped — calling #play
|
|
53
|
+
# afterwards resumes from the new offset.
|
|
54
|
+
def playing_offset=(value)
|
|
55
|
+
t = value.is_a?(Time) ? value : Time.seconds(value.to_f)
|
|
56
|
+
C::Audio.sfSound_setPlayingOffset(@handle, t.to_native)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 3D velocity in world units / second — used by the Doppler
|
|
60
|
+
# effect to shift pitch as the source approaches or recedes from
|
|
61
|
+
# the listener.
|
|
62
|
+
def velocity
|
|
63
|
+
v = C::Audio.sfSound_getVelocity(@handle)
|
|
64
|
+
Vector3.new(v[:x], v[:y], v[:z])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def velocity=(value)
|
|
68
|
+
vec = value.is_a?(Vector3) ? value : Vector3.new(*value)
|
|
69
|
+
packed = C::System::Vector3f.new
|
|
70
|
+
packed[:x] = vec.x.to_f; packed[:y] = vec.y.to_f; packed[:z] = vec.z.to_f
|
|
71
|
+
C::Audio.sfSound_setVelocity(@handle, packed)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Per-source Doppler scale. 1.0 is realistic; bump it up for an
|
|
75
|
+
# exaggerated Doppler shift, drop to 0 to disable per-source.
|
|
76
|
+
def doppler_factor = C::Audio.sfSound_getDopplerFactor(@handle)
|
|
77
|
+
def doppler_factor=(v) C::Audio.sfSound_setDopplerFactor(@handle, v.to_f); end
|
|
78
|
+
|
|
79
|
+
# The direction the sound's cone points. Used together with
|
|
80
|
+
# #cone= for directional attenuation.
|
|
81
|
+
def direction
|
|
82
|
+
v = C::Audio.sfSound_getDirection(@handle)
|
|
83
|
+
Vector3.new(v[:x], v[:y], v[:z])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def direction=(value)
|
|
87
|
+
vec = value.is_a?(Vector3) ? value : Vector3.new(*value)
|
|
88
|
+
packed = C::System::Vector3f.new
|
|
89
|
+
packed[:x] = vec.x.to_f; packed[:y] = vec.y.to_f; packed[:z] = vec.z.to_f
|
|
90
|
+
C::Audio.sfSound_setDirection(@handle, packed)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Directional-attenuation cone — see SFML::SoundCone.
|
|
94
|
+
def cone
|
|
95
|
+
SoundCone.from_native(C::Audio.sfSound_getCone(@handle))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def cone=(value)
|
|
99
|
+
cone =
|
|
100
|
+
case value
|
|
101
|
+
when SoundCone then value
|
|
102
|
+
when Hash then SoundCone.new(**value)
|
|
103
|
+
else
|
|
104
|
+
raise ArgumentError, "Sound#cone= expects SoundCone or Hash; got #{value.class}"
|
|
105
|
+
end
|
|
106
|
+
C::Audio.sfSound_setCone(@handle, cone.to_native)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Install a real-time DSP filter. The callable is invoked from the
|
|
110
|
+
# CSFML audio thread once per audio frame batch with:
|
|
111
|
+
# * `input` — Array<Float> of interleaved samples (signed [-1, 1])
|
|
112
|
+
# * `channels` — Integer channel count (e.g. 2 for stereo)
|
|
113
|
+
# and should return an Array<Float> of the same length (or shorter
|
|
114
|
+
# if the effect produces fewer frames). Pass `nil` to remove an
|
|
115
|
+
# installed processor.
|
|
116
|
+
#
|
|
117
|
+
# CAVEAT: the callback runs on a real-time audio thread and is
|
|
118
|
+
# called every few milliseconds. Ruby + GVL is rarely fast enough
|
|
119
|
+
# for non-trivial DSP — expect glitches for anything heavier than
|
|
120
|
+
# a constant-gain or simple IIR. For real DSP, do it offline.
|
|
121
|
+
def effect_processor=(callable)
|
|
122
|
+
@effect_cb = callable.nil? ? nil : Audio._build_effect_processor(callable)
|
|
123
|
+
C::Audio.sfSound_setEffectProcessor(@handle, @effect_cb, nil)
|
|
124
|
+
end
|
|
125
|
+
|
|
44
126
|
def looping?
|
|
45
127
|
@looping
|
|
46
128
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Directional-attenuation cone for a 3D sound source. Inside the
|
|
3
|
+
# `inner_angle` cone the sound plays at full volume; outside the
|
|
4
|
+
# `outer_angle` cone it's attenuated by `outer_gain`; between the
|
|
5
|
+
# two it's smoothly interpolated.
|
|
6
|
+
#
|
|
7
|
+
# Angles are in degrees, measured from the source's `direction`
|
|
8
|
+
# axis. `outer_gain` is a scalar in [0, 1] (0 = silent outside).
|
|
9
|
+
#
|
|
10
|
+
# sound.direction = [0, 0, -1]
|
|
11
|
+
# sound.cone = SFML::SoundCone.new(
|
|
12
|
+
# inner_angle: 30, outer_angle: 90, outer_gain: 0.2,
|
|
13
|
+
# )
|
|
14
|
+
class SoundCone
|
|
15
|
+
attr_reader :inner_angle, :outer_angle, :outer_gain
|
|
16
|
+
|
|
17
|
+
def initialize(inner_angle:, outer_angle:, outer_gain:)
|
|
18
|
+
@inner_angle = inner_angle.to_f
|
|
19
|
+
@outer_angle = outer_angle.to_f
|
|
20
|
+
@outer_gain = outer_gain.to_f
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def ==(other)
|
|
25
|
+
other.is_a?(SoundCone) &&
|
|
26
|
+
inner_angle == other.inner_angle &&
|
|
27
|
+
outer_angle == other.outer_angle &&
|
|
28
|
+
outer_gain == other.outer_gain
|
|
29
|
+
end
|
|
30
|
+
alias eql? ==
|
|
31
|
+
def hash = [inner_angle, outer_angle, outer_gain].hash
|
|
32
|
+
|
|
33
|
+
def to_s
|
|
34
|
+
"SoundCone(inner=#{inner_angle}°, outer=#{outer_angle}°, outer_gain=#{outer_gain})"
|
|
35
|
+
end
|
|
36
|
+
alias inspect to_s
|
|
37
|
+
|
|
38
|
+
# @!visibility private
|
|
39
|
+
def to_native
|
|
40
|
+
s = C::Audio::SoundSourceCone.new
|
|
41
|
+
s[:inner_angle] = @inner_angle
|
|
42
|
+
s[:outer_angle] = @outer_angle
|
|
43
|
+
s[:outer_gain] = @outer_gain
|
|
44
|
+
s
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @!visibility private
|
|
48
|
+
def self.from_native(struct)
|
|
49
|
+
new(
|
|
50
|
+
inner_angle: struct[:inner_angle],
|
|
51
|
+
outer_angle: struct[:outer_angle],
|
|
52
|
+
outer_gain: struct[:outer_gain],
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|