sfml3-rb 0.2.2 → 0.3.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/.yardopts +9 -0
- data/CHANGELOG.md +67 -16
- data/README.md +123 -32
- data/TODO.md +18 -11
- data/ext/audio/audio_enums.c +58 -20
- data/ext/audio/listener.c +89 -6
- data/ext/audio/music.c +212 -5
- data/ext/audio/sound.c +218 -9
- data/ext/audio/sound_buffer.c +197 -47
- data/ext/audio/sound_buffer_recorder.c +90 -18
- data/ext/audio/sound_recorder.c +188 -58
- data/ext/audio/sound_source_cone.c +127 -33
- data/ext/audio/sound_stream.c +204 -21
- data/ext/core/exceptions.c +10 -8
- data/ext/core/unicode.c +12 -2
- data/ext/ext.c +8 -0
- data/ext/graphics/blend_mode.c +218 -60
- data/ext/graphics/circle.c +300 -16
- data/ext/graphics/color.c +253 -94
- data/ext/graphics/drawable.c +16 -4
- data/ext/graphics/font.c +144 -25
- data/ext/graphics/glyph.c +38 -8
- data/ext/graphics/image.c +205 -41
- data/ext/graphics/polygon.c +317 -30
- data/ext/graphics/rect.c +225 -60
- data/ext/graphics/rectangle.c +307 -35
- data/ext/graphics/render_state.c +124 -12
- data/ext/graphics/render_texture.c +191 -14
- data/ext/graphics/render_window.c +55 -0
- data/ext/graphics/render_window.h +10 -0
- data/ext/graphics/shader.c +289 -17
- data/ext/graphics/shape.c +265 -32
- data/ext/graphics/sprite.c +245 -30
- data/ext/graphics/stencil_mode.c +163 -46
- data/ext/graphics/target.c +121 -43
- data/ext/graphics/target.h +31 -17
- data/ext/graphics/text.c +339 -19
- data/ext/graphics/texture.c +291 -6
- data/ext/graphics/transform.c +178 -5
- data/ext/graphics/transformable.c +127 -12
- data/ext/graphics/vertex.c +98 -6
- data/ext/graphics/vertex_array.c +142 -21
- data/ext/graphics/vertex_buffer.c +154 -12
- data/ext/graphics/view.c +140 -6
- data/ext/network/ftp.c +278 -12
- data/ext/network/http.c +179 -44
- data/ext/network/ip_address.c +127 -35
- data/ext/network/network_enums.c +248 -141
- data/ext/network/packet.c +280 -41
- data/ext/network/socket_selector.c +98 -21
- data/ext/network/tcp_listener.c +82 -18
- data/ext/network/tcp_socket.c +135 -27
- data/ext/network/udp_socket.c +129 -28
- data/ext/ports.rb +7 -0
- data/ext/system/buffer.c +47 -16
- data/ext/system/clock.c +77 -12
- data/ext/system/input_stream.c +113 -51
- data/ext/system/sleep.c +10 -2
- data/ext/system/time.c +182 -40
- data/ext/system/vec2.c +166 -39
- data/ext/system/vec3.c +177 -39
- data/ext/window/clipboard.c +47 -17
- data/ext/window/context.c +66 -13
- data/ext/window/context_settings.c +132 -42
- data/ext/window/cursor.c +45 -9
- data/ext/window/event.c +164 -53
- data/ext/window/joystick.c +69 -3
- data/ext/window/keyboard.c +168 -116
- data/ext/window/mouse.c +64 -13
- data/ext/window/sensor.c +28 -2
- data/ext/window/touch.c +32 -12
- data/ext/window/video_mode.c +93 -31
- data/ext/window/vulkan.c +34 -6
- data/ext/window/window.c +228 -229
- data/ext/window/window.h +4 -4
- data/ext/window/window_base.c +251 -0
- data/ext/window/window_base.h +43 -0
- data/ext/window/window_base.inc +236 -0
- data/lib/sfml/version.rb +1 -1
- data/sig/audio/audio_enums.rbs +30 -0
- data/sig/audio/listener.rbs +16 -0
- data/sig/audio/music.rbs +56 -0
- data/sig/audio/sound.rbs +51 -0
- data/sig/audio/sound_buffer.rbs +17 -0
- data/sig/audio/sound_buffer_recorder.rbs +14 -0
- data/sig/audio/sound_recorder.rbs +17 -0
- data/sig/audio/sound_source_cone.rbs +14 -0
- data/sig/audio/sound_stream.rbs +51 -0
- data/sig/graphics/blend_mode.rbs +29 -0
- data/sig/graphics/circle.rbs +44 -0
- data/sig/graphics/color.rbs +39 -0
- data/sig/graphics/drawable.rbs +5 -0
- data/sig/graphics/font.rbs +20 -0
- data/sig/graphics/glyph.rbs +7 -0
- data/sig/graphics/image.rbs +22 -0
- data/sig/graphics/polygon.rbs +42 -0
- data/sig/graphics/rect.rbs +33 -0
- data/sig/graphics/rectangle.rbs +42 -0
- data/sig/graphics/render_state.rbs +21 -0
- data/sig/graphics/render_target.rbs +18 -0
- data/sig/graphics/render_texture.rbs +39 -0
- data/sig/graphics/render_window.rbs +7 -0
- data/sig/graphics/shader.rbs +39 -0
- data/sig/graphics/shape.rbs +38 -0
- data/sig/graphics/sprite.rbs +34 -0
- data/sig/graphics/stencil_mode.rbs +18 -0
- data/sig/graphics/target.rbs +8 -0
- data/sig/graphics/text.rbs +48 -0
- data/sig/graphics/texture.rbs +34 -0
- data/sig/graphics/transform.rbs +37 -0
- data/sig/graphics/transformable.rbs +25 -0
- data/sig/graphics/vertex.rbs +17 -0
- data/sig/graphics/vertex_array.rbs +17 -0
- data/sig/graphics/vertex_buffer.rbs +22 -0
- data/sig/graphics/view.rbs +23 -0
- data/sig/network/ftp.rbs +46 -0
- data/sig/network/http.rbs +27 -0
- data/sig/network/ip_address.rbs +22 -0
- data/sig/network/network_enums.rbs +95 -0
- data/sig/network/packet.rbs +40 -0
- data/sig/network/socket_selector.rbs +14 -0
- data/sig/network/tcp_listener.rbs +13 -0
- data/sig/network/tcp_socket.rbs +18 -0
- data/sig/network/udp_socket.rbs +17 -0
- data/sig/sfml/version.rbs +3 -0
- data/sig/system/buffer.rbs +11 -0
- data/sig/system/clock.rbs +13 -0
- data/sig/system/input_stream.rbs +7 -0
- data/sig/system/sleep.rbs +3 -0
- data/sig/system/time.rbs +28 -0
- data/sig/system/vec2.rbs +26 -0
- data/sig/system/vec3.rbs +27 -0
- data/sig/window/clipboard.rbs +8 -0
- data/sig/window/context.rbs +11 -0
- data/sig/window/context_settings.rbs +20 -0
- data/sig/window/cursor.rbs +6 -0
- data/sig/window/event.rbs +20 -0
- data/sig/window/joystick.rbs +15 -0
- data/sig/window/keyboard.rbs +10 -0
- data/sig/window/mouse.rbs +9 -0
- data/sig/window/sensor.rbs +8 -0
- data/sig/window/touch.rbs +6 -0
- data/sig/window/video_mode.rbs +16 -0
- data/sig/window/vulkan.rbs +7 -0
- data/sig/window/window.rbs +34 -0
- data/sig/window/window_base.rbs +33 -0
- metadata +76 -3
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
#include "core/macros.h"
|
|
9
9
|
|
|
10
10
|
typedef struct {
|
|
11
|
-
sfSoundBufferRecorder
|
|
11
|
+
sfSoundBufferRecorder* handle;
|
|
12
12
|
} SoundBufferRecorder;
|
|
13
13
|
|
|
14
14
|
static VALUE rb_cSoundBufferRecorder;
|
|
15
15
|
|
|
16
|
-
static void SoundBufferRecorder_free(void
|
|
17
|
-
SoundBufferRecorder
|
|
16
|
+
static void SoundBufferRecorder_free(void* ptr) {
|
|
17
|
+
SoundBufferRecorder* recorder = ptr;
|
|
18
18
|
|
|
19
19
|
if (recorder->handle != NULL) {
|
|
20
20
|
sfSoundBufferRecorder_destroy(recorder->handle);
|
|
@@ -26,11 +26,10 @@ static void SoundBufferRecorder_free(void *ptr) {
|
|
|
26
26
|
static const rb_data_type_t SoundBufferRecorder_data_type = {
|
|
27
27
|
.wrap_struct_name = "SFML::SoundBufferRecorder",
|
|
28
28
|
.function = {.dmark = NULL, .dfree = SoundBufferRecorder_free, .dsize = NULL},
|
|
29
|
-
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
|
30
|
-
};
|
|
29
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY};
|
|
31
30
|
|
|
32
|
-
static VALUE SoundBufferRecorder_wrap(VALUE klass, sfSoundBufferRecorder
|
|
33
|
-
SoundBufferRecorder
|
|
31
|
+
static VALUE SoundBufferRecorder_wrap(VALUE klass, sfSoundBufferRecorder* handle) {
|
|
32
|
+
SoundBufferRecorder* ptr;
|
|
34
33
|
|
|
35
34
|
if (handle == NULL) {
|
|
36
35
|
rb_raise(rb_eRuntimeError, "failed to create sound buffer recorder (no capture device?)");
|
|
@@ -42,51 +41,122 @@ static VALUE SoundBufferRecorder_wrap(VALUE klass, sfSoundBufferRecorder *handle
|
|
|
42
41
|
return TypedData_Wrap_Struct(klass, &SoundBufferRecorder_data_type, ptr);
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
static VALUE
|
|
44
|
+
static VALUE SoundBufferRecorder_alloc(VALUE klass) {
|
|
46
45
|
return SoundBufferRecorder_wrap(klass, sfSoundBufferRecorder_create());
|
|
47
46
|
}
|
|
48
47
|
|
|
48
|
+
/* call-seq:
|
|
49
|
+
* SoundBufferRecorder.new -> SoundBufferRecorder
|
|
50
|
+
*
|
|
51
|
+
* Creates a recorder that captures into a SoundBuffer.
|
|
52
|
+
*
|
|
53
|
+
* @return [SoundBufferRecorder]
|
|
54
|
+
* @raise [RuntimeError] if no capture device is available
|
|
55
|
+
*/
|
|
56
|
+
static VALUE SoundBufferRecorder_initialize(VALUE self) {
|
|
57
|
+
return self;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* call-seq:
|
|
61
|
+
* start(sample_rate) -> true or false
|
|
62
|
+
*
|
|
63
|
+
* Starts capturing audio at the given sample rate.
|
|
64
|
+
*
|
|
65
|
+
* @return [Boolean] whether recording started successfully
|
|
66
|
+
*/
|
|
49
67
|
static VALUE SoundBufferRecorder_start(VALUE self, VALUE rb_sample_rate) {
|
|
50
68
|
return BOOL2RB(sfSoundBufferRecorder_start(Get_SoundBufferRecorder_Struct(self),
|
|
51
|
-
(unsigned int)
|
|
69
|
+
(unsigned int)NUM2INT(rb_sample_rate)));
|
|
52
70
|
}
|
|
53
71
|
|
|
72
|
+
/* call-seq: stop -> self
|
|
73
|
+
*
|
|
74
|
+
* Stops capturing audio.
|
|
75
|
+
*
|
|
76
|
+
* @return [self]
|
|
77
|
+
*/
|
|
54
78
|
static VALUE SoundBufferRecorder_stop(VALUE self) {
|
|
55
79
|
sfSoundBufferRecorder_stop(Get_SoundBufferRecorder_Struct(self));
|
|
56
80
|
return self;
|
|
57
81
|
}
|
|
58
82
|
|
|
83
|
+
/* call-seq: sample_rate -> Integer
|
|
84
|
+
*
|
|
85
|
+
* Returns the sample rate used for capture.
|
|
86
|
+
*
|
|
87
|
+
* @return [Integer]
|
|
88
|
+
*/
|
|
59
89
|
static VALUE SoundBufferRecorder_sample_rate(VALUE self) {
|
|
60
90
|
return UINT2NUM(sfSoundBufferRecorder_getSampleRate(Get_SoundBufferRecorder_Struct(self)));
|
|
61
91
|
}
|
|
62
92
|
|
|
93
|
+
/* call-seq: buffer -> SoundBuffer
|
|
94
|
+
*
|
|
95
|
+
* Returns the buffer holding the audio recorded so far.
|
|
96
|
+
*
|
|
97
|
+
* @return [SoundBuffer] the buffer containing the audio recorded so far
|
|
98
|
+
*/
|
|
63
99
|
static VALUE SoundBufferRecorder_buffer(VALUE self) {
|
|
64
|
-
return sound_buffer_from_borrowed(
|
|
100
|
+
return sound_buffer_from_borrowed(
|
|
101
|
+
sfSoundBufferRecorder_getBuffer(Get_SoundBufferRecorder_Struct(self)));
|
|
65
102
|
}
|
|
66
103
|
|
|
104
|
+
/* call-seq: device -> String
|
|
105
|
+
*
|
|
106
|
+
* Returns the name of the capture device in use.
|
|
107
|
+
*
|
|
108
|
+
* @return [String] the name of the capture device in use
|
|
109
|
+
*/
|
|
67
110
|
static VALUE SoundBufferRecorder_device(VALUE self) {
|
|
68
111
|
return rb_str_new_cstr(sfSoundBufferRecorder_getDevice(Get_SoundBufferRecorder_Struct(self)));
|
|
69
112
|
}
|
|
70
113
|
|
|
114
|
+
/* call-seq:
|
|
115
|
+
* device=(value) -> true or false
|
|
116
|
+
*
|
|
117
|
+
* Must be called while not recording. Get available names from
|
|
118
|
+
* SoundRecorder.available_devices.
|
|
119
|
+
*
|
|
120
|
+
* @return [Boolean] whether the device was set successfully
|
|
121
|
+
*/
|
|
71
122
|
static VALUE SoundBufferRecorder_set_device(VALUE self, VALUE rb_name) {
|
|
72
123
|
return BOOL2RB(sfSoundBufferRecorder_setDevice(Get_SoundBufferRecorder_Struct(self),
|
|
73
124
|
StringValueCStr(rb_name)));
|
|
74
125
|
}
|
|
75
126
|
|
|
127
|
+
/* call-seq: channel_count -> Integer
|
|
128
|
+
*
|
|
129
|
+
* Returns the number of capture channels.
|
|
130
|
+
*
|
|
131
|
+
* @return [Integer]
|
|
132
|
+
*/
|
|
76
133
|
static VALUE SoundBufferRecorder_channel_count(VALUE self) {
|
|
77
134
|
return UINT2NUM(sfSoundBufferRecorder_getChannelCount(Get_SoundBufferRecorder_Struct(self)));
|
|
78
135
|
}
|
|
79
136
|
|
|
137
|
+
/* call-seq:
|
|
138
|
+
* channel_count=(value) -> Integer
|
|
139
|
+
*
|
|
140
|
+
* Must be called while not recording.
|
|
141
|
+
*
|
|
142
|
+
* @return [Integer] +value+
|
|
143
|
+
*/
|
|
80
144
|
static VALUE SoundBufferRecorder_set_channel_count(VALUE self, VALUE rb_count) {
|
|
81
145
|
sfSoundBufferRecorder_setChannelCount(Get_SoundBufferRecorder_Struct(self),
|
|
82
|
-
(unsigned int)
|
|
146
|
+
(unsigned int)NUM2INT(rb_count));
|
|
83
147
|
return rb_count;
|
|
84
148
|
}
|
|
85
149
|
|
|
86
|
-
|
|
87
|
-
|
|
150
|
+
/* Document-class: SFML::SoundBufferRecorder
|
|
151
|
+
* Records audio from a capture device directly into a SoundBuffer. For
|
|
152
|
+
* custom processing of captured samples as they arrive, subclass
|
|
153
|
+
* SoundRecorder instead.
|
|
154
|
+
*/
|
|
155
|
+
void Init_SoundBufferRecorder(VALUE rb_mSFML) {
|
|
156
|
+
rb_cSoundBufferRecorder = rb_define_class_under(rb_mSFML, "SoundBufferRecorder", rb_cObject);
|
|
88
157
|
|
|
89
|
-
|
|
158
|
+
rb_define_alloc_func(rb_cSoundBufferRecorder, SoundBufferRecorder_alloc);
|
|
159
|
+
rb_define_method(rb_cSoundBufferRecorder, "initialize", SoundBufferRecorder_initialize, 0);
|
|
90
160
|
|
|
91
161
|
rb_define_method(rb_cSoundBufferRecorder, "start", SoundBufferRecorder_start, 1);
|
|
92
162
|
rb_define_method(rb_cSoundBufferRecorder, "stop", SoundBufferRecorder_stop, 0);
|
|
@@ -94,16 +164,18 @@ void Init_SoundBufferRecorder(VALUE rb_module) {
|
|
|
94
164
|
rb_define_method(rb_cSoundBufferRecorder, "buffer", SoundBufferRecorder_buffer, 0);
|
|
95
165
|
rb_define_method(rb_cSoundBufferRecorder, "device", SoundBufferRecorder_device, 0);
|
|
96
166
|
rb_define_method(rb_cSoundBufferRecorder, "device=", SoundBufferRecorder_set_device, 1);
|
|
97
|
-
rb_define_method(rb_cSoundBufferRecorder, "channel_count", SoundBufferRecorder_channel_count,
|
|
98
|
-
|
|
167
|
+
rb_define_method(rb_cSoundBufferRecorder, "channel_count", SoundBufferRecorder_channel_count,
|
|
168
|
+
0);
|
|
169
|
+
rb_define_method(rb_cSoundBufferRecorder,
|
|
170
|
+
"channel_count=", SoundBufferRecorder_set_channel_count, 1);
|
|
99
171
|
}
|
|
100
172
|
|
|
101
173
|
VALUE Get_Klass_SoundBufferRecorder(void) {
|
|
102
174
|
return rb_cSoundBufferRecorder;
|
|
103
175
|
}
|
|
104
176
|
|
|
105
|
-
void
|
|
106
|
-
SoundBufferRecorder
|
|
177
|
+
void* Get_SoundBufferRecorder_Struct(VALUE self) {
|
|
178
|
+
SoundBufferRecorder* ptr;
|
|
107
179
|
TypedData_Get_Struct(self, SoundBufferRecorder, &SoundBufferRecorder_data_type, ptr);
|
|
108
180
|
return ptr->handle;
|
|
109
181
|
}
|
data/ext/audio/sound_recorder.c
CHANGED
|
@@ -1,33 +1,48 @@
|
|
|
1
1
|
#include "audio/sound_recorder.h"
|
|
2
2
|
|
|
3
3
|
#include <ruby.h>
|
|
4
|
-
#include <ruby/thread.h>
|
|
5
4
|
#include <stdint.h>
|
|
6
5
|
#include <stdlib.h>
|
|
7
6
|
|
|
8
7
|
#include "audio/audio_enums.h"
|
|
8
|
+
#include "core/foreign_thread.h"
|
|
9
9
|
#include "core/macros.h"
|
|
10
10
|
|
|
11
|
-
/* A subclassable recorder. The capture callbacks arrive on SFML's
|
|
12
|
-
thread
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
/* A subclassable recorder. The capture callbacks arrive on SFML's capture
|
|
12
|
+
thread -- a real OS thread never created by Ruby, exactly like the audio
|
|
13
|
+
playback thread effect_processor.c/sound_stream.c are built to handle --
|
|
14
|
+
so re-entering Ruby directly (e.g. via rb_thread_call_with_gvl, as this
|
|
15
|
+
file previously did for #on_process, or via rb_protect with no GVL
|
|
16
|
+
handling at all, as it previously did for #on_start/#on_stop) is a fatal
|
|
17
|
+
VM error. Every callback here instead posts its work to the shared
|
|
18
|
+
foreign-thread worker (core/foreign_thread.h), the same mechanism the
|
|
19
|
+
other two files use, and treats an exception as "stop capturing" rather
|
|
20
|
+
than unwinding into the audio engine. A subclass only has to implement
|
|
21
|
+
#on_process; #on_start and #on_stop are optional and default to
|
|
22
|
+
continuing. */
|
|
23
|
+
|
|
24
|
+
#define SOUND_RECORDER_JOB_TIMEOUT_MS 50
|
|
16
25
|
typedef struct {
|
|
17
26
|
VALUE rb_self;
|
|
18
|
-
sfSoundRecorder
|
|
27
|
+
sfSoundRecorder* handle;
|
|
19
28
|
} SoundRecorder;
|
|
20
29
|
|
|
21
30
|
static VALUE rb_cSoundRecorder;
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
/* CSFML reports "no such device" as NULL; that is an absent name, not an
|
|
33
|
+
error, so it maps to nil rather than raising out of rb_str_new_cstr. */
|
|
34
|
+
static VALUE SoundRecorder_nullable_string(const char* value) {
|
|
35
|
+
return value == NULL ? Qnil : rb_str_new_cstr(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static void SoundRecorder_mark(void* ptr) {
|
|
39
|
+
SoundRecorder* recorder = ptr;
|
|
25
40
|
|
|
26
41
|
rb_gc_mark(recorder->rb_self);
|
|
27
42
|
}
|
|
28
43
|
|
|
29
|
-
static void SoundRecorder_free(void
|
|
30
|
-
SoundRecorder
|
|
44
|
+
static void SoundRecorder_free(void* ptr) {
|
|
45
|
+
SoundRecorder* recorder = ptr;
|
|
31
46
|
|
|
32
47
|
if (recorder->handle != NULL) {
|
|
33
48
|
sfSoundRecorder_destroy(recorder->handle);
|
|
@@ -39,8 +54,7 @@ static void SoundRecorder_free(void *ptr) {
|
|
|
39
54
|
static const rb_data_type_t SoundRecorder_data_type = {
|
|
40
55
|
.wrap_struct_name = "SFML::SoundRecorder",
|
|
41
56
|
.function = {.dmark = SoundRecorder_mark, .dfree = SoundRecorder_free, .dsize = NULL},
|
|
42
|
-
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
|
43
|
-
};
|
|
57
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY};
|
|
44
58
|
|
|
45
59
|
typedef struct {
|
|
46
60
|
VALUE self;
|
|
@@ -48,25 +62,31 @@ typedef struct {
|
|
|
48
62
|
} InvokeContext;
|
|
49
63
|
|
|
50
64
|
static VALUE SoundRecorder_invoke_body(VALUE raw) {
|
|
51
|
-
InvokeContext
|
|
65
|
+
InvokeContext* ctx = (InvokeContext*)raw;
|
|
52
66
|
|
|
53
67
|
return rb_funcall(ctx->self, ctx->method, 0);
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
/*
|
|
57
|
-
static void
|
|
58
|
-
InvokeContext ctx = {.self = self, .method = method};
|
|
70
|
+
/* Runs on the shared foreign-thread worker, which holds the GVL. */
|
|
71
|
+
static void SoundRecorder_invoke_run(void* raw) {
|
|
59
72
|
int state = 0;
|
|
60
73
|
|
|
61
|
-
rb_protect(SoundRecorder_invoke_body, (VALUE)
|
|
74
|
+
rb_protect(SoundRecorder_invoke_body, (VALUE)raw, &state);
|
|
62
75
|
|
|
63
76
|
if (state) {
|
|
64
77
|
rb_set_errinfo(Qnil);
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
/* Calls a zero-argument Ruby method, swallowing any exception it raises. */
|
|
82
|
+
static void SoundRecorder_invoke(VALUE self, ID method) {
|
|
83
|
+
InvokeContext ctx = {.self = self, .method = method};
|
|
84
|
+
|
|
85
|
+
run_on_ruby_thread(SoundRecorder_invoke_run, &ctx, SOUND_RECORDER_JOB_TIMEOUT_MS);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static bool SoundRecorder_on_start(void* userData) {
|
|
89
|
+
SoundRecorder* recorder = userData;
|
|
70
90
|
|
|
71
91
|
if (rb_respond_to(recorder->rb_self, rb_intern("on_start"))) {
|
|
72
92
|
SoundRecorder_invoke(recorder->rb_self, rb_intern("on_start"));
|
|
@@ -76,15 +96,15 @@ static bool SoundRecorder_on_start(void *userData) {
|
|
|
76
96
|
}
|
|
77
97
|
|
|
78
98
|
typedef struct {
|
|
79
|
-
SoundRecorder
|
|
80
|
-
const int16_t
|
|
99
|
+
SoundRecorder* recorder;
|
|
100
|
+
const int16_t* samples;
|
|
81
101
|
size_t sample_count;
|
|
82
102
|
int ok;
|
|
83
103
|
} ProcessContext;
|
|
84
104
|
|
|
85
105
|
static VALUE SoundRecorder_process_body(VALUE raw) {
|
|
86
|
-
ProcessContext
|
|
87
|
-
VALUE rb_samples = rb_ary_new_capa((long)
|
|
106
|
+
ProcessContext* ctx = (ProcessContext*)raw;
|
|
107
|
+
VALUE rb_samples = rb_ary_new_capa((long)ctx->sample_count);
|
|
88
108
|
VALUE result;
|
|
89
109
|
size_t i;
|
|
90
110
|
|
|
@@ -98,53 +118,79 @@ static VALUE SoundRecorder_process_body(VALUE raw) {
|
|
|
98
118
|
return Qnil;
|
|
99
119
|
}
|
|
100
120
|
|
|
101
|
-
|
|
121
|
+
/* Runs on the shared foreign-thread worker, which holds the GVL. */
|
|
122
|
+
static void SoundRecorder_process_run(void* raw) {
|
|
102
123
|
int state = 0;
|
|
103
124
|
|
|
104
|
-
rb_protect(SoundRecorder_process_body, (VALUE)
|
|
125
|
+
rb_protect(SoundRecorder_process_body, (VALUE)raw, &state);
|
|
105
126
|
|
|
106
127
|
if (state) {
|
|
107
128
|
rb_set_errinfo(Qnil);
|
|
108
|
-
((ProcessContext
|
|
129
|
+
((ProcessContext*)raw)->ok = 0;
|
|
109
130
|
}
|
|
110
|
-
|
|
111
|
-
return NULL;
|
|
112
131
|
}
|
|
113
132
|
|
|
114
|
-
static bool SoundRecorder_on_process(const int16_t
|
|
115
|
-
SoundRecorder
|
|
133
|
+
static bool SoundRecorder_on_process(const int16_t* samples, size_t sample_count, void* userData) {
|
|
134
|
+
SoundRecorder* recorder = userData;
|
|
116
135
|
ProcessContext ctx = {
|
|
117
|
-
.recorder = recorder, .samples = samples, .sample_count = sample_count, .ok = 0
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
.recorder = recorder, .samples = samples, .sample_count = sample_count, .ok = 0};
|
|
137
|
+
|
|
138
|
+
if (!run_on_ruby_thread(SoundRecorder_process_run, &ctx, SOUND_RECORDER_JOB_TIMEOUT_MS)) {
|
|
139
|
+
/* Timed out before the worker even claimed the job (e.g. GVL
|
|
140
|
+
contention): drop this one chunk rather than block the capture
|
|
141
|
+
thread indefinitely, but keep recording rather than aborting the
|
|
142
|
+
whole session over one missed callback. */
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
121
145
|
|
|
122
146
|
return ctx.ok ? true : false;
|
|
123
147
|
}
|
|
124
148
|
|
|
125
|
-
static void SoundRecorder_on_stop(void
|
|
126
|
-
SoundRecorder
|
|
149
|
+
static void SoundRecorder_on_stop(void* userData) {
|
|
150
|
+
SoundRecorder* recorder = userData;
|
|
127
151
|
|
|
128
152
|
if (rb_respond_to(recorder->rb_self, rb_intern("on_stop"))) {
|
|
129
153
|
SoundRecorder_invoke(recorder->rb_self, rb_intern("on_stop"));
|
|
130
154
|
}
|
|
131
155
|
}
|
|
132
156
|
|
|
133
|
-
static VALUE
|
|
134
|
-
SoundRecorder
|
|
135
|
-
sfSoundRecorder *handle;
|
|
157
|
+
static VALUE SoundRecorder_alloc(VALUE klass) {
|
|
158
|
+
SoundRecorder* ptr = malloc(sizeof(SoundRecorder));
|
|
136
159
|
VALUE self;
|
|
137
160
|
|
|
138
|
-
ptr
|
|
161
|
+
if (ptr == NULL) {
|
|
162
|
+
rb_raise(rb_eNoMemError, "failed to allocate sound recorder");
|
|
163
|
+
}
|
|
164
|
+
|
|
139
165
|
ptr->rb_self = Qnil;
|
|
140
166
|
ptr->handle = NULL;
|
|
141
167
|
|
|
142
168
|
self = TypedData_Wrap_Struct(klass, &SoundRecorder_data_type, ptr);
|
|
143
169
|
ptr->rb_self = self;
|
|
144
170
|
|
|
171
|
+
return self;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* call-seq:
|
|
175
|
+
* SoundRecorder.new -> SoundRecorder
|
|
176
|
+
*
|
|
177
|
+
* SoundRecorder must be subclassed: the subclass is required to implement
|
|
178
|
+
* +#on_process(samples)+, called from the audio thread with an Array of
|
|
179
|
+
* captured Integer samples whenever a chunk is ready (return a truthy value
|
|
180
|
+
* to keep recording, falsy to stop), and may optionally implement
|
|
181
|
+
* +#on_start+ and +#on_stop+.
|
|
182
|
+
*
|
|
183
|
+
* @return [SoundRecorder]
|
|
184
|
+
* @raise [NotImplementedError] if the subclass does not define #on_process
|
|
185
|
+
* @raise [RuntimeError] if no capture device is available
|
|
186
|
+
*/
|
|
187
|
+
static VALUE SoundRecorder_initialize(VALUE self) {
|
|
188
|
+
SoundRecorder* ptr;
|
|
189
|
+
sfSoundRecorder* handle;
|
|
190
|
+
|
|
191
|
+
TypedData_Get_Struct(self, SoundRecorder, &SoundRecorder_data_type, ptr);
|
|
192
|
+
|
|
145
193
|
if (!rb_respond_to(self, rb_intern("on_process"))) {
|
|
146
|
-
/* Ruby already owns the wrapper; SoundRecorder_free handles the NULL
|
|
147
|
-
handle, so let GC clean up after the raise. */
|
|
148
194
|
rb_raise(rb_eNotImpError, "subclass must define #on_process");
|
|
149
195
|
}
|
|
150
196
|
|
|
@@ -160,27 +206,59 @@ static VALUE SoundRecorder_new(VALUE klass) {
|
|
|
160
206
|
return self;
|
|
161
207
|
}
|
|
162
208
|
|
|
209
|
+
/* call-seq:
|
|
210
|
+
* start(sample_rate) -> true or false
|
|
211
|
+
*
|
|
212
|
+
* Starts capturing audio at the given sample rate.
|
|
213
|
+
*
|
|
214
|
+
* @return [Boolean] whether recording started successfully
|
|
215
|
+
*/
|
|
163
216
|
static VALUE SoundRecorder_start(VALUE self, VALUE rb_sample_rate) {
|
|
164
217
|
return BOOL2RB(sfSoundRecorder_start(Get_SoundRecorder_Struct(self),
|
|
165
|
-
(unsigned int)
|
|
218
|
+
(unsigned int)NUM2INT(rb_sample_rate)));
|
|
166
219
|
}
|
|
167
220
|
|
|
221
|
+
/* call-seq: stop -> self
|
|
222
|
+
*
|
|
223
|
+
* Stops capturing audio.
|
|
224
|
+
*
|
|
225
|
+
* @return [self]
|
|
226
|
+
*/
|
|
168
227
|
static VALUE SoundRecorder_stop(VALUE self) {
|
|
169
228
|
sfSoundRecorder_stop(Get_SoundRecorder_Struct(self));
|
|
170
229
|
return self;
|
|
171
230
|
}
|
|
172
231
|
|
|
232
|
+
/* call-seq: sample_rate -> Integer
|
|
233
|
+
*
|
|
234
|
+
* Returns the sample rate used for capture.
|
|
235
|
+
*
|
|
236
|
+
* @return [Integer]
|
|
237
|
+
*/
|
|
173
238
|
static VALUE SoundRecorder_sample_rate(VALUE self) {
|
|
174
239
|
return UINT2NUM(sfSoundRecorder_getSampleRate(Get_SoundRecorder_Struct(self)));
|
|
175
240
|
}
|
|
176
241
|
|
|
242
|
+
/* call-seq: SoundRecorder.available? -> true or false
|
|
243
|
+
*
|
|
244
|
+
* Returns +true+ if the audio backend supports capture.
|
|
245
|
+
*
|
|
246
|
+
* @return [Boolean] whether the audio backend supports capture at all
|
|
247
|
+
*/
|
|
177
248
|
static VALUE SoundRecorder_available(VALUE klass) {
|
|
178
249
|
return BOOL2RB(sfSoundRecorder_isAvailable());
|
|
179
250
|
}
|
|
180
251
|
|
|
252
|
+
/* call-seq: SoundRecorder.available_devices -> Array<String>
|
|
253
|
+
*
|
|
254
|
+
* Returns the names of the capture devices available on this system.
|
|
255
|
+
*
|
|
256
|
+
* @return [Array<String>] names of the capture devices available on this
|
|
257
|
+
* system
|
|
258
|
+
*/
|
|
181
259
|
static VALUE SoundRecorder_available_devices(VALUE klass) {
|
|
182
260
|
size_t count = 0;
|
|
183
|
-
const char *
|
|
261
|
+
const char* const* devices = sfSoundRecorder_getAvailableDevices(&count);
|
|
184
262
|
VALUE rb_array;
|
|
185
263
|
size_t i;
|
|
186
264
|
|
|
@@ -188,7 +266,7 @@ static VALUE SoundRecorder_available_devices(VALUE klass) {
|
|
|
188
266
|
return rb_ary_new();
|
|
189
267
|
}
|
|
190
268
|
|
|
191
|
-
rb_array = rb_ary_new_capa((long)
|
|
269
|
+
rb_array = rb_ary_new_capa((long)count);
|
|
192
270
|
|
|
193
271
|
for (i = 0; i < count; i++) {
|
|
194
272
|
rb_ary_push(rb_array, rb_str_new_cstr(devices[i]));
|
|
@@ -197,31 +275,74 @@ static VALUE SoundRecorder_available_devices(VALUE klass) {
|
|
|
197
275
|
return rb_array;
|
|
198
276
|
}
|
|
199
277
|
|
|
278
|
+
/* call-seq: SoundRecorder.default_device -> String or nil
|
|
279
|
+
*
|
|
280
|
+
* Returns the name of the system's default capture device.
|
|
281
|
+
*
|
|
282
|
+
* @return [String, nil] the name of the system's default capture device, or
|
|
283
|
+
* +nil+ when the system reports none
|
|
284
|
+
*/
|
|
200
285
|
static VALUE SoundRecorder_default_device(VALUE klass) {
|
|
201
|
-
return
|
|
286
|
+
return SoundRecorder_nullable_string(sfSoundRecorder_getDefaultDevice());
|
|
202
287
|
}
|
|
203
288
|
|
|
289
|
+
/* call-seq: device -> String or nil
|
|
290
|
+
*
|
|
291
|
+
* Returns the name of the capture device in use.
|
|
292
|
+
*
|
|
293
|
+
* @return [String, nil] the name of the capture device in use, or +nil+ when
|
|
294
|
+
* the recorder has none
|
|
295
|
+
*/
|
|
204
296
|
static VALUE SoundRecorder_device(VALUE self) {
|
|
205
|
-
return
|
|
297
|
+
return SoundRecorder_nullable_string(sfSoundRecorder_getDevice(Get_SoundRecorder_Struct(self)));
|
|
206
298
|
}
|
|
207
299
|
|
|
300
|
+
/* call-seq:
|
|
301
|
+
* device=(value) -> true or false
|
|
302
|
+
*
|
|
303
|
+
* Must be called while not recording. Get available names from
|
|
304
|
+
* .available_devices.
|
|
305
|
+
*
|
|
306
|
+
* @return [Boolean] whether the device was set successfully
|
|
307
|
+
*/
|
|
208
308
|
static VALUE SoundRecorder_set_device(VALUE self, VALUE rb_name) {
|
|
209
309
|
return BOOL2RB(
|
|
210
310
|
sfSoundRecorder_setDevice(Get_SoundRecorder_Struct(self), StringValueCStr(rb_name)));
|
|
211
311
|
}
|
|
212
312
|
|
|
313
|
+
/* call-seq: channel_count -> Integer
|
|
314
|
+
*
|
|
315
|
+
* Returns the number of capture channels.
|
|
316
|
+
*
|
|
317
|
+
* @return [Integer]
|
|
318
|
+
*/
|
|
213
319
|
static VALUE SoundRecorder_channel_count(VALUE self) {
|
|
214
320
|
return UINT2NUM(sfSoundRecorder_getChannelCount(Get_SoundRecorder_Struct(self)));
|
|
215
321
|
}
|
|
216
322
|
|
|
323
|
+
/* call-seq:
|
|
324
|
+
* channel_count=(value) -> Integer
|
|
325
|
+
*
|
|
326
|
+
* Must be called while not recording.
|
|
327
|
+
*
|
|
328
|
+
* @return [Integer] +value+
|
|
329
|
+
*/
|
|
217
330
|
static VALUE SoundRecorder_set_channel_count(VALUE self, VALUE rb_count) {
|
|
218
|
-
sfSoundRecorder_setChannelCount(Get_SoundRecorder_Struct(self),
|
|
331
|
+
sfSoundRecorder_setChannelCount(Get_SoundRecorder_Struct(self),
|
|
332
|
+
(unsigned int)NUM2INT(rb_count));
|
|
219
333
|
return rb_count;
|
|
220
334
|
}
|
|
221
335
|
|
|
336
|
+
/* call-seq: channel_map -> Array<Symbol>
|
|
337
|
+
*
|
|
338
|
+
* Returns the channel layout of the recorder.
|
|
339
|
+
*
|
|
340
|
+
* @return [Array<Symbol>] one entry per channel, e.g.
|
|
341
|
+
* +[:front_left, :front_right]+
|
|
342
|
+
*/
|
|
222
343
|
static VALUE SoundRecorder_channel_map(VALUE self) {
|
|
223
344
|
size_t count = 0;
|
|
224
|
-
sfSoundChannel
|
|
345
|
+
sfSoundChannel* map = sfSoundRecorder_getChannelMap(Get_SoundRecorder_Struct(self), &count);
|
|
225
346
|
VALUE rb_array;
|
|
226
347
|
size_t i;
|
|
227
348
|
|
|
@@ -229,7 +350,7 @@ static VALUE SoundRecorder_channel_map(VALUE self) {
|
|
|
229
350
|
return rb_ary_new();
|
|
230
351
|
}
|
|
231
352
|
|
|
232
|
-
rb_array = rb_ary_new_capa((long)
|
|
353
|
+
rb_array = rb_ary_new_capa((long)count);
|
|
233
354
|
|
|
234
355
|
for (i = 0; i < count; i++) {
|
|
235
356
|
rb_ary_push(rb_array, ID2SYM(rb_intern(sound_channel_name(map[i]))));
|
|
@@ -238,14 +359,23 @@ static VALUE SoundRecorder_channel_map(VALUE self) {
|
|
|
238
359
|
return rb_array;
|
|
239
360
|
}
|
|
240
361
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
362
|
+
/* Document-class: SFML::SoundRecorder
|
|
363
|
+
* Base class for a custom audio capture consumer. Subclasses must implement
|
|
364
|
+
* +#on_process(samples)+, called from the audio thread whenever a chunk of
|
|
365
|
+
* captured samples is ready, and may implement +#on_start+/+#on_stop+ to
|
|
366
|
+
* react to recording starting/stopping. For simply capturing into a
|
|
367
|
+
* SoundBuffer without custom processing, use SoundBufferRecorder instead.
|
|
368
|
+
*/
|
|
369
|
+
void Init_SoundRecorder(VALUE rb_mSFML) {
|
|
370
|
+
rb_cSoundRecorder = rb_define_class_under(rb_mSFML, "SoundRecorder", rb_cObject);
|
|
371
|
+
|
|
372
|
+
rb_define_alloc_func(rb_cSoundRecorder, SoundRecorder_alloc);
|
|
373
|
+
rb_define_method(rb_cSoundRecorder, "initialize", SoundRecorder_initialize, 0);
|
|
245
374
|
rb_define_singleton_method(rb_cSoundRecorder, "available?", SoundRecorder_available, 0);
|
|
246
375
|
rb_define_singleton_method(rb_cSoundRecorder, "available_devices",
|
|
247
376
|
SoundRecorder_available_devices, 0);
|
|
248
|
-
rb_define_singleton_method(rb_cSoundRecorder, "default_device", SoundRecorder_default_device,
|
|
377
|
+
rb_define_singleton_method(rb_cSoundRecorder, "default_device", SoundRecorder_default_device,
|
|
378
|
+
0);
|
|
249
379
|
|
|
250
380
|
rb_define_method(rb_cSoundRecorder, "start", SoundRecorder_start, 1);
|
|
251
381
|
rb_define_method(rb_cSoundRecorder, "stop", SoundRecorder_stop, 0);
|
|
@@ -261,8 +391,8 @@ VALUE Get_Klass_SoundRecorder(void) {
|
|
|
261
391
|
return rb_cSoundRecorder;
|
|
262
392
|
}
|
|
263
393
|
|
|
264
|
-
void
|
|
265
|
-
SoundRecorder
|
|
394
|
+
void* Get_SoundRecorder_Struct(VALUE self) {
|
|
395
|
+
SoundRecorder* ptr;
|
|
266
396
|
TypedData_Get_Struct(self, SoundRecorder, &SoundRecorder_data_type, ptr);
|
|
267
397
|
return ptr->handle;
|
|
268
398
|
}
|