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.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +9 -0
  3. data/CHANGELOG.md +67 -16
  4. data/README.md +123 -32
  5. data/TODO.md +18 -11
  6. data/ext/audio/audio_enums.c +58 -20
  7. data/ext/audio/listener.c +89 -6
  8. data/ext/audio/music.c +212 -5
  9. data/ext/audio/sound.c +218 -9
  10. data/ext/audio/sound_buffer.c +197 -47
  11. data/ext/audio/sound_buffer_recorder.c +90 -18
  12. data/ext/audio/sound_recorder.c +188 -58
  13. data/ext/audio/sound_source_cone.c +127 -33
  14. data/ext/audio/sound_stream.c +204 -21
  15. data/ext/core/exceptions.c +10 -8
  16. data/ext/core/unicode.c +12 -2
  17. data/ext/ext.c +8 -0
  18. data/ext/graphics/blend_mode.c +218 -60
  19. data/ext/graphics/circle.c +300 -16
  20. data/ext/graphics/color.c +253 -94
  21. data/ext/graphics/drawable.c +16 -4
  22. data/ext/graphics/font.c +144 -25
  23. data/ext/graphics/glyph.c +38 -8
  24. data/ext/graphics/image.c +205 -41
  25. data/ext/graphics/polygon.c +317 -30
  26. data/ext/graphics/rect.c +225 -60
  27. data/ext/graphics/rectangle.c +307 -35
  28. data/ext/graphics/render_state.c +124 -12
  29. data/ext/graphics/render_texture.c +191 -14
  30. data/ext/graphics/render_window.c +55 -0
  31. data/ext/graphics/render_window.h +10 -0
  32. data/ext/graphics/shader.c +289 -17
  33. data/ext/graphics/shape.c +265 -32
  34. data/ext/graphics/sprite.c +245 -30
  35. data/ext/graphics/stencil_mode.c +163 -46
  36. data/ext/graphics/target.c +121 -43
  37. data/ext/graphics/target.h +31 -17
  38. data/ext/graphics/text.c +339 -19
  39. data/ext/graphics/texture.c +291 -6
  40. data/ext/graphics/transform.c +178 -5
  41. data/ext/graphics/transformable.c +127 -12
  42. data/ext/graphics/vertex.c +98 -6
  43. data/ext/graphics/vertex_array.c +142 -21
  44. data/ext/graphics/vertex_buffer.c +154 -12
  45. data/ext/graphics/view.c +140 -6
  46. data/ext/network/ftp.c +278 -12
  47. data/ext/network/http.c +179 -44
  48. data/ext/network/ip_address.c +127 -35
  49. data/ext/network/network_enums.c +248 -141
  50. data/ext/network/packet.c +280 -41
  51. data/ext/network/socket_selector.c +98 -21
  52. data/ext/network/tcp_listener.c +82 -18
  53. data/ext/network/tcp_socket.c +135 -27
  54. data/ext/network/udp_socket.c +129 -28
  55. data/ext/ports.rb +7 -0
  56. data/ext/system/buffer.c +47 -16
  57. data/ext/system/clock.c +77 -12
  58. data/ext/system/input_stream.c +113 -51
  59. data/ext/system/sleep.c +10 -2
  60. data/ext/system/time.c +182 -40
  61. data/ext/system/vec2.c +166 -39
  62. data/ext/system/vec3.c +177 -39
  63. data/ext/window/clipboard.c +47 -17
  64. data/ext/window/context.c +66 -13
  65. data/ext/window/context_settings.c +132 -42
  66. data/ext/window/cursor.c +45 -9
  67. data/ext/window/event.c +164 -53
  68. data/ext/window/joystick.c +69 -3
  69. data/ext/window/keyboard.c +168 -116
  70. data/ext/window/mouse.c +64 -13
  71. data/ext/window/sensor.c +28 -2
  72. data/ext/window/touch.c +32 -12
  73. data/ext/window/video_mode.c +93 -31
  74. data/ext/window/vulkan.c +34 -6
  75. data/ext/window/window.c +228 -229
  76. data/ext/window/window.h +4 -4
  77. data/ext/window/window_base.c +251 -0
  78. data/ext/window/window_base.h +43 -0
  79. data/ext/window/window_base.inc +236 -0
  80. data/lib/sfml/version.rb +1 -1
  81. data/sig/audio/audio_enums.rbs +30 -0
  82. data/sig/audio/listener.rbs +16 -0
  83. data/sig/audio/music.rbs +56 -0
  84. data/sig/audio/sound.rbs +51 -0
  85. data/sig/audio/sound_buffer.rbs +17 -0
  86. data/sig/audio/sound_buffer_recorder.rbs +14 -0
  87. data/sig/audio/sound_recorder.rbs +17 -0
  88. data/sig/audio/sound_source_cone.rbs +14 -0
  89. data/sig/audio/sound_stream.rbs +51 -0
  90. data/sig/graphics/blend_mode.rbs +29 -0
  91. data/sig/graphics/circle.rbs +44 -0
  92. data/sig/graphics/color.rbs +39 -0
  93. data/sig/graphics/drawable.rbs +5 -0
  94. data/sig/graphics/font.rbs +20 -0
  95. data/sig/graphics/glyph.rbs +7 -0
  96. data/sig/graphics/image.rbs +22 -0
  97. data/sig/graphics/polygon.rbs +42 -0
  98. data/sig/graphics/rect.rbs +33 -0
  99. data/sig/graphics/rectangle.rbs +42 -0
  100. data/sig/graphics/render_state.rbs +21 -0
  101. data/sig/graphics/render_target.rbs +18 -0
  102. data/sig/graphics/render_texture.rbs +39 -0
  103. data/sig/graphics/render_window.rbs +7 -0
  104. data/sig/graphics/shader.rbs +39 -0
  105. data/sig/graphics/shape.rbs +38 -0
  106. data/sig/graphics/sprite.rbs +34 -0
  107. data/sig/graphics/stencil_mode.rbs +18 -0
  108. data/sig/graphics/target.rbs +8 -0
  109. data/sig/graphics/text.rbs +48 -0
  110. data/sig/graphics/texture.rbs +34 -0
  111. data/sig/graphics/transform.rbs +37 -0
  112. data/sig/graphics/transformable.rbs +25 -0
  113. data/sig/graphics/vertex.rbs +17 -0
  114. data/sig/graphics/vertex_array.rbs +17 -0
  115. data/sig/graphics/vertex_buffer.rbs +22 -0
  116. data/sig/graphics/view.rbs +23 -0
  117. data/sig/network/ftp.rbs +46 -0
  118. data/sig/network/http.rbs +27 -0
  119. data/sig/network/ip_address.rbs +22 -0
  120. data/sig/network/network_enums.rbs +95 -0
  121. data/sig/network/packet.rbs +40 -0
  122. data/sig/network/socket_selector.rbs +14 -0
  123. data/sig/network/tcp_listener.rbs +13 -0
  124. data/sig/network/tcp_socket.rbs +18 -0
  125. data/sig/network/udp_socket.rbs +17 -0
  126. data/sig/sfml/version.rbs +3 -0
  127. data/sig/system/buffer.rbs +11 -0
  128. data/sig/system/clock.rbs +13 -0
  129. data/sig/system/input_stream.rbs +7 -0
  130. data/sig/system/sleep.rbs +3 -0
  131. data/sig/system/time.rbs +28 -0
  132. data/sig/system/vec2.rbs +26 -0
  133. data/sig/system/vec3.rbs +27 -0
  134. data/sig/window/clipboard.rbs +8 -0
  135. data/sig/window/context.rbs +11 -0
  136. data/sig/window/context_settings.rbs +20 -0
  137. data/sig/window/cursor.rbs +6 -0
  138. data/sig/window/event.rbs +20 -0
  139. data/sig/window/joystick.rbs +15 -0
  140. data/sig/window/keyboard.rbs +10 -0
  141. data/sig/window/mouse.rbs +9 -0
  142. data/sig/window/sensor.rbs +8 -0
  143. data/sig/window/touch.rbs +6 -0
  144. data/sig/window/video_mode.rbs +16 -0
  145. data/sig/window/vulkan.rbs +7 -0
  146. data/sig/window/window.rbs +34 -0
  147. data/sig/window/window_base.rbs +33 -0
  148. metadata +76 -3
@@ -8,13 +8,13 @@
8
8
  #include "core/macros.h"
9
9
 
10
10
  typedef struct {
11
- sfSoundBufferRecorder *handle;
11
+ sfSoundBufferRecorder* handle;
12
12
  } SoundBufferRecorder;
13
13
 
14
14
  static VALUE rb_cSoundBufferRecorder;
15
15
 
16
- static void SoundBufferRecorder_free(void *ptr) {
17
- SoundBufferRecorder *recorder = ptr;
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 *handle) {
33
- SoundBufferRecorder *ptr;
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 SoundBufferRecorder_new(VALUE klass) {
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) NUM2INT(rb_sample_rate)));
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(sfSoundBufferRecorder_getBuffer(Get_SoundBufferRecorder_Struct(self)));
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) NUM2INT(rb_count));
146
+ (unsigned int)NUM2INT(rb_count));
83
147
  return rb_count;
84
148
  }
85
149
 
86
- void Init_SoundBufferRecorder(VALUE rb_module) {
87
- rb_cSoundBufferRecorder = rb_define_class_under(rb_module, "SoundBufferRecorder", rb_cObject);
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
- rb_define_singleton_method(rb_cSoundBufferRecorder, "new", SoundBufferRecorder_new, 0);
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, 0);
98
- rb_define_method(rb_cSoundBufferRecorder, "channel_count=", SoundBufferRecorder_set_channel_count, 1);
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 *Get_SoundBufferRecorder_Struct(VALUE self) {
106
- SoundBufferRecorder *ptr;
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
  }
@@ -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 audio
12
- thread, so each re-enters Ruby through rb_thread_call_with_gvl and treats an
13
- exception as "stop capturing" instead of unwinding into the audio engine.
14
- A subclass only has to implement #on_process; #on_start and #on_stop are
15
- optional and default to continuing. */
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 *handle;
27
+ sfSoundRecorder* handle;
19
28
  } SoundRecorder;
20
29
 
21
30
  static VALUE rb_cSoundRecorder;
22
31
 
23
- static void SoundRecorder_mark(void *ptr) {
24
- SoundRecorder *recorder = ptr;
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 *ptr) {
30
- SoundRecorder *recorder = ptr;
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 *ctx = (InvokeContext *) raw;
65
+ InvokeContext* ctx = (InvokeContext*)raw;
52
66
 
53
67
  return rb_funcall(ctx->self, ctx->method, 0);
54
68
  }
55
69
 
56
- /* Calls a zero-argument Ruby method, swallowing any exception it raises. */
57
- static void SoundRecorder_invoke(VALUE self, ID method) {
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) &ctx, &state);
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
- static bool SoundRecorder_on_start(void *userData) {
69
- SoundRecorder *recorder = userData;
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 *recorder;
80
- const int16_t *samples;
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 *ctx = (ProcessContext *) raw;
87
- VALUE rb_samples = rb_ary_new_capa((long) ctx->sample_count);
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
- static void *SoundRecorder_process_gvl(void *raw) {
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) raw, &state);
125
+ rb_protect(SoundRecorder_process_body, (VALUE)raw, &state);
105
126
 
106
127
  if (state) {
107
128
  rb_set_errinfo(Qnil);
108
- ((ProcessContext *) raw)->ok = 0;
129
+ ((ProcessContext*)raw)->ok = 0;
109
130
  }
110
-
111
- return NULL;
112
131
  }
113
132
 
114
- static bool SoundRecorder_on_process(const int16_t *samples, size_t sample_count, void *userData) {
115
- SoundRecorder *recorder = userData;
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
- rb_thread_call_with_gvl(SoundRecorder_process_gvl, &ctx);
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 *userData) {
126
- SoundRecorder *recorder = userData;
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 SoundRecorder_new(VALUE klass) {
134
- SoundRecorder *ptr;
135
- sfSoundRecorder *handle;
157
+ static VALUE SoundRecorder_alloc(VALUE klass) {
158
+ SoundRecorder* ptr = malloc(sizeof(SoundRecorder));
136
159
  VALUE self;
137
160
 
138
- ptr = malloc(sizeof(SoundRecorder));
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) NUM2INT(rb_sample_rate)));
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 *const *devices = sfSoundRecorder_getAvailableDevices(&count);
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) count);
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 rb_str_new_cstr(sfSoundRecorder_getDefaultDevice());
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 rb_str_new_cstr(sfSoundRecorder_getDevice(Get_SoundRecorder_Struct(self)));
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), (unsigned int) NUM2INT(rb_count));
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 *map = sfSoundRecorder_getChannelMap(Get_SoundRecorder_Struct(self), &count);
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) count);
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
- void Init_SoundRecorder(VALUE rb_module) {
242
- rb_cSoundRecorder = rb_define_class_under(rb_module, "SoundRecorder", rb_cObject);
243
-
244
- rb_define_singleton_method(rb_cSoundRecorder, "new", SoundRecorder_new, 0);
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, 0);
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 *Get_SoundRecorder_Struct(VALUE self) {
265
- SoundRecorder *ptr;
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
  }