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
data/ext/audio/sound.c CHANGED
@@ -33,15 +33,29 @@ static void* Sound_destroy_without_gvl(void* handle) {
33
33
  static void Sound_free(void* ptr) {
34
34
  Sound* sound = ptr;
35
35
 
36
+ /* Destroy (which blocks until CSFML guarantees no in-flight audio-thread
37
+ callback still references this source) before releasing the effect
38
+ slot, not after: releasing first opens a window where another Ruby
39
+ thread's effect_processor_acquire could reuse the slot number for an
40
+ unrelated new source while this source's callback might still be
41
+ executing against it. */
42
+ if (sound->source.handle != NULL) {
43
+ rb_thread_call_without_gvl(Sound_destroy_without_gvl, sound->source.handle, RUBY_UBF_IO,
44
+ NULL);
45
+ }
46
+
36
47
  effect_processor_release(sound->source.effect_slot);
37
- rb_thread_call_without_gvl(Sound_destroy_without_gvl, sound->source.handle, RUBY_UBF_IO, NULL);
38
48
  free(sound);
39
49
  }
40
50
 
51
+ /* Deliberately no RUBY_TYPED_FREE_IMMEDIATELY: Sound_free releases the GVL for
52
+ sfSound_destroy, and freeing during GC (which the flag requests) would let
53
+ another thread allocate while GC is mid-cycle, which Ruby aborts on as
54
+ "object allocation during garbage collection phase". Deferred finalization
55
+ runs the same free with the GVL held and no GC in progress. */
41
56
  static const rb_data_type_t Sound_data_type = {
42
57
  .wrap_struct_name = "SFML::Sound",
43
- .function = {.dmark = Sound_mark, .dfree = Sound_free, .dsize = NULL},
44
- .flags = RUBY_TYPED_FREE_IMMEDIATELY};
58
+ .function = {.dmark = Sound_mark, .dfree = Sound_free, .dsize = NULL}};
45
59
 
46
60
  static VALUE Sound_wrap(VALUE klass, sfSound* handle, VALUE rb_buffer) {
47
61
  Sound* ptr;
@@ -58,14 +72,61 @@ static VALUE Sound_wrap(VALUE klass, sfSound* handle, VALUE rb_buffer) {
58
72
  return TypedData_Wrap_Struct(klass, &Sound_data_type, ptr);
59
73
  }
60
74
 
61
- static VALUE Sound_new(VALUE klass, VALUE rb_buffer) {
75
+ static VALUE Sound_initialize_copy(VALUE self, VALUE other) {
76
+ (void)other;
77
+ rb_raise(rb_eTypeError, "can't copy a %s", rb_obj_classname(self));
78
+ }
79
+
80
+ static VALUE Sound_alloc(VALUE klass) {
81
+ Sound* ptr = malloc(sizeof(Sound));
82
+
83
+ if (ptr == NULL) {
84
+ rb_raise(rb_eNoMemError, "failed to allocate sound");
85
+ }
86
+
87
+ ptr->source.handle = NULL;
88
+ ptr->source.effect_slot = -1;
89
+ ptr->rb_buffer = Qnil;
90
+
91
+ return TypedData_Wrap_Struct(klass, &Sound_data_type, ptr);
92
+ }
93
+
94
+ /* call-seq:
95
+ * Sound.new(buffer) -> Sound
96
+ *
97
+ * Creates a sound that plays the given buffer.
98
+ *
99
+ * @return [Sound]
100
+ * @raise [ArgumentError] if +buffer+ is not a SoundBuffer
101
+ */
102
+ static VALUE Sound_initialize(VALUE self, VALUE rb_buffer) {
103
+ Sound* sound;
104
+ sfSound* handle;
105
+
62
106
  if (!rb_obj_is_kind_of(rb_buffer, Get_Klass_SoundBuffer())) {
63
107
  raise_invalid_argument_class(Get_Klass_SoundBuffer());
64
108
  }
65
109
 
66
- return Sound_wrap(klass, sfSound_create(Get_SoundBuffer_Struct(rb_buffer)), rb_buffer);
110
+ sound = (Sound*)Get_Sound_Struct(self);
111
+
112
+ handle = sfSound_create(Get_SoundBuffer_Struct(rb_buffer));
113
+
114
+ if (handle == NULL) {
115
+ rb_raise(rb_eRuntimeError, "failed to create sound");
116
+ }
117
+
118
+ sound->source.handle = handle;
119
+ sound->rb_buffer = rb_buffer;
120
+
121
+ return self;
67
122
  }
68
123
 
124
+ /* call-seq: copy -> Sound
125
+ *
126
+ * Creates an independent copy of the sound that shares its buffer.
127
+ *
128
+ * @return [Sound] an independent copy that shares the same SoundBuffer
129
+ */
69
130
  static VALUE Sound_copy(VALUE self) {
70
131
  Sound* sound = (Sound*)Get_Sound_Struct(self);
71
132
 
@@ -73,6 +134,12 @@ static VALUE Sound_copy(VALUE self) {
73
134
  sound->rb_buffer);
74
135
  }
75
136
 
137
+ /* call-seq: buffer -> SoundBuffer
138
+ *
139
+ * Returns the buffer currently attached to this sound.
140
+ *
141
+ * @return [SoundBuffer] the buffer currently attached to this sound
142
+ */
76
143
  static VALUE Sound_get_buffer(VALUE self) {
77
144
  return ((Sound*)Get_Sound_Struct(self))->rb_buffer;
78
145
  }
@@ -93,6 +160,16 @@ static void* Sound_set_buffer_without_gvl(void* raw) {
93
160
  return NULL;
94
161
  }
95
162
 
163
+ /* call-seq:
164
+ * buffer=(value) -> SoundBuffer
165
+ *
166
+ * Attaches a new SoundBuffer. If a buffer is already attached and the sound
167
+ * is playing, it is stopped first, which may briefly block the calling
168
+ * thread (see #stop).
169
+ *
170
+ * @return [SoundBuffer] +value+
171
+ * @raise [ArgumentError] if +value+ is not a SoundBuffer
172
+ */
96
173
  static VALUE Sound_set_buffer(VALUE self, VALUE rb_buffer) {
97
174
  Sound* sound = (Sound*)Get_Sound_Struct(self);
98
175
  SoundSetBufferArgs args;
@@ -116,10 +193,142 @@ static VALUE Sound_set_buffer(VALUE self, VALUE rb_buffer) {
116
193
  #undef SS_FN
117
194
  #undef SS_METHOD
118
195
 
119
- void Init_Sound(VALUE rb_module) {
120
- rb_cSound = rb_define_class_under(rb_module, "Sound", rb_cObject);
121
-
122
- rb_define_singleton_method(rb_cSound, "new", Sound_new, 1);
196
+ /* Document-class: SFML::Sound
197
+ * A sound playing directly from a SoundBuffer held fully in memory. Suited
198
+ * to short effects; for long files prefer Music, which streams instead.
199
+ *
200
+ * @!method play
201
+ * Starts playback, or resumes it when paused.
202
+ * @return [self]
203
+ * @!method pause
204
+ * Pauses playback, keeping the current playing offset.
205
+ * @return [self]
206
+ * @!method stop
207
+ * Stops playback and rewinds to the beginning. May briefly block the
208
+ * calling thread if an audio-thread callback for this source is in
209
+ * flight.
210
+ * @return [self]
211
+ * @!method status
212
+ * Returns the current playback status.
213
+ * @return [Symbol] one of +:stopped+, +:paused+, +:playing+
214
+ * @!method looping?
215
+ * Returns +true+ if playback loops back to the start on completion.
216
+ * @return [Boolean]
217
+ * @!method looping=(value)
218
+ * Enables or disables looping.
219
+ * @return [Boolean]
220
+ * @!method pitch
221
+ * Returns the pitch scaling factor.
222
+ * @return [Float]
223
+ * @!method pitch=(value)
224
+ * Sets the pitch scaling factor.
225
+ * @return [Float]
226
+ * @!method pan
227
+ * Returns the source's stereo pan.
228
+ * @return [Float] stereo pan, -1 (left) to 1 (right)
229
+ * @!method pan=(value)
230
+ * Sets the source's stereo pan.
231
+ * @return [Float]
232
+ * @!method volume
233
+ * Returns the source's volume.
234
+ * @return [Float] 0 to 100
235
+ * @!method volume=(value)
236
+ * Sets the source's volume.
237
+ * @return [Float]
238
+ * @!method spatialization_enabled?
239
+ * Returns +true+ if 3D spatialization is enabled.
240
+ * @return [Boolean]
241
+ * @!method spatialization_enabled=(value)
242
+ * Enables or disables 3D spatialization.
243
+ * @return [Boolean]
244
+ * @!method position
245
+ * Returns the source's position in 3D space.
246
+ * @return [Vector3]
247
+ * @!method position=(value)
248
+ * Sets the source's position in 3D space.
249
+ * @return [Vector3]
250
+ * @!method direction
251
+ * Returns the direction the source is facing.
252
+ * @return [Vector3]
253
+ * @!method direction=(value)
254
+ * Sets the direction the source is facing.
255
+ * @return [Vector3]
256
+ * @!method velocity
257
+ * Returns the source's velocity, used for Doppler calculations.
258
+ * @return [Vector3]
259
+ * @!method velocity=(value)
260
+ * Sets the source's velocity for Doppler calculations.
261
+ * @return [Vector3]
262
+ * @!method cone
263
+ * Returns the source's directional attenuation cone.
264
+ * @return [SoundSourceCone]
265
+ * @!method cone=(value)
266
+ * Sets the source's directional attenuation cone.
267
+ * @return [SoundSourceCone]
268
+ * @!method doppler_factor
269
+ * Returns the factor by which the Doppler effect is scaled.
270
+ * @return [Float]
271
+ * @!method doppler_factor=(value)
272
+ * Sets the factor by which the Doppler effect is scaled.
273
+ * @return [Float]
274
+ * @!method directional_attenuation_factor
275
+ * Returns the factor controlling directional attenuation.
276
+ * @return [Float]
277
+ * @!method directional_attenuation_factor=(value)
278
+ * Sets the factor controlling directional attenuation.
279
+ * @return [Float]
280
+ * @!method relative_to_listener?
281
+ * Returns +true+ if the source is positioned relative to the listener.
282
+ * @return [Boolean]
283
+ * @!method relative_to_listener=(value)
284
+ * Makes the source relative to, or independent of, the listener.
285
+ * @return [Boolean]
286
+ * @!method min_distance
287
+ * Returns the minimum distance of the distance-attenuation model.
288
+ * @return [Float]
289
+ * @!method min_distance=(value)
290
+ * Sets the minimum distance of the distance-attenuation model.
291
+ * @return [Float]
292
+ * @!method max_distance
293
+ * Returns the maximum distance of the distance-attenuation model.
294
+ * @return [Float]
295
+ * @!method max_distance=(value)
296
+ * Sets the maximum distance of the distance-attenuation model.
297
+ * @return [Float]
298
+ * @!method min_gain
299
+ * Returns the minimum gain of the distance-attenuation model.
300
+ * @return [Float]
301
+ * @!method min_gain=(value)
302
+ * Sets the minimum gain of the distance-attenuation model.
303
+ * @return [Float]
304
+ * @!method max_gain
305
+ * Returns the maximum gain of the distance-attenuation model.
306
+ * @return [Float]
307
+ * @!method max_gain=(value)
308
+ * Sets the maximum gain of the distance-attenuation model.
309
+ * @return [Float]
310
+ * @!method attenuation
311
+ * Returns the distance-attenuation factor.
312
+ * @return [Float]
313
+ * @!method attenuation=(value)
314
+ * Sets the distance-attenuation factor.
315
+ * @return [Float]
316
+ * @!method playing_offset
317
+ * Returns the current playing offset.
318
+ * @return [Time]
319
+ * @!method playing_offset=(value)
320
+ * Seeks to the given playing offset.
321
+ * @return [Time]
322
+ * @!method effect_processor=(proc)
323
+ * Installs a Proc that post-processes this source's audio in real time.
324
+ * @return [Proc] +proc+
325
+ */
326
+ void Init_Sound(VALUE rb_mSFML) {
327
+ rb_cSound = rb_define_class_under(rb_mSFML, "Sound", rb_cObject);
328
+
329
+ rb_define_alloc_func(rb_cSound, Sound_alloc);
330
+ rb_define_method(rb_cSound, "initialize", Sound_initialize, 1);
331
+ rb_define_private_method(rb_cSound, "initialize_copy", Sound_initialize_copy, 1);
123
332
 
124
333
  rb_define_method(rb_cSound, "copy", Sound_copy, 0);
125
334
  rb_define_method(rb_cSound, "buffer", Sound_get_buffer, 0);
@@ -12,14 +12,14 @@
12
12
  #include "system/time.h"
13
13
 
14
14
  typedef struct {
15
- sfSoundBuffer *buffer;
15
+ sfSoundBuffer* buffer;
16
16
  bool owns;
17
17
  } SoundBuffer;
18
18
 
19
19
  static VALUE rb_cSoundBuffer;
20
20
 
21
- static void SoundBuffer_free(void *ptr) {
22
- SoundBuffer *sound_buffer = ptr;
21
+ static void SoundBuffer_free(void* ptr) {
22
+ SoundBuffer* sound_buffer = ptr;
23
23
 
24
24
  if (sound_buffer->owns) {
25
25
  sfSoundBuffer_destroy(sound_buffer->buffer);
@@ -31,11 +31,10 @@ static void SoundBuffer_free(void *ptr) {
31
31
  static const rb_data_type_t SoundBuffer_data_type = {
32
32
  .wrap_struct_name = "SFML::SoundBuffer",
33
33
  .function = {.dmark = NULL, .dfree = SoundBuffer_free, .dsize = NULL},
34
- .flags = RUBY_TYPED_FREE_IMMEDIATELY
35
- };
34
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY};
36
35
 
37
- static VALUE SoundBuffer_wrap(VALUE klass, sfSoundBuffer *buffer) {
38
- SoundBuffer *ptr;
36
+ static VALUE SoundBuffer_wrap(VALUE klass, sfSoundBuffer* buffer) {
37
+ SoundBuffer* ptr;
39
38
 
40
39
  if (buffer == NULL) {
41
40
  rb_raise(rb_eRuntimeError, "failed to create sound buffer");
@@ -48,36 +47,60 @@ static VALUE SoundBuffer_wrap(VALUE klass, sfSoundBuffer *buffer) {
48
47
  return TypedData_Wrap_Struct(klass, &SoundBuffer_data_type, ptr);
49
48
  }
50
49
 
51
- VALUE sound_buffer_from_borrowed(const sfSoundBuffer *buffer) {
52
- SoundBuffer *ptr;
50
+ VALUE sound_buffer_from_borrowed(const sfSoundBuffer* buffer) {
51
+ SoundBuffer* ptr;
53
52
 
54
53
  if (buffer == NULL) {
55
54
  return Qnil;
56
55
  }
57
56
 
58
57
  ptr = malloc(sizeof(SoundBuffer));
59
- ptr->buffer = (sfSoundBuffer *) buffer;
58
+ ptr->buffer = (sfSoundBuffer*)buffer;
60
59
  ptr->owns = false;
61
60
 
62
61
  return TypedData_Wrap_Struct(rb_cSoundBuffer, &SoundBuffer_data_type, ptr);
63
62
  }
64
63
 
64
+ /* call-seq:
65
+ * SoundBuffer.from_file(path) -> SoundBuffer
66
+ *
67
+ * Loads and decodes an audio file into a buffer.
68
+ *
69
+ * @return [SoundBuffer]
70
+ * @raise [RuntimeError] if the file cannot be opened or decoded
71
+ */
65
72
  static VALUE SoundBuffer_from_file(VALUE klass, VALUE rb_path) {
66
73
  return SoundBuffer_wrap(klass, sfSoundBuffer_createFromFile(StringValueCStr(rb_path)));
67
74
  }
68
75
 
76
+ /* call-seq:
77
+ * SoundBuffer.from_memory(data) -> SoundBuffer
78
+ *
79
+ * Decodes audio held in a String into a buffer.
80
+ *
81
+ * @return [SoundBuffer]
82
+ * @raise [RuntimeError] if +data+ cannot be decoded
83
+ */
69
84
  static VALUE SoundBuffer_from_memory(VALUE klass, VALUE rb_data) {
70
85
  StringValue(rb_data);
71
86
 
72
- return SoundBuffer_wrap(klass, sfSoundBuffer_createFromMemory(RSTRING_PTR(rb_data),
73
- (size_t) RSTRING_LEN(rb_data)));
87
+ return SoundBuffer_wrap(
88
+ klass, sfSoundBuffer_createFromMemory(RSTRING_PTR(rb_data), (size_t)RSTRING_LEN(rb_data)));
74
89
  }
75
90
 
91
+ /* call-seq:
92
+ * SoundBuffer.from_stream(stream) -> SoundBuffer
93
+ *
94
+ * Decodes audio read from an SFML InputStream into a buffer.
95
+ *
96
+ * @return [SoundBuffer]
97
+ * @raise [RuntimeError] if the stream cannot be decoded
98
+ */
76
99
  static VALUE SoundBuffer_from_stream(VALUE klass, VALUE rb_stream) {
77
100
  VALUE holder = Qnil;
78
- sfInputStream *stream = input_stream_from_rb(rb_stream, &holder);
101
+ sfInputStream* stream = input_stream_from_rb(rb_stream, &holder);
79
102
 
80
- (void) holder;
103
+ (void)holder;
81
104
 
82
105
  return SoundBuffer_wrap(klass, sfSoundBuffer_createFromStream(stream));
83
106
  }
@@ -85,12 +108,13 @@ static VALUE SoundBuffer_from_stream(VALUE klass, VALUE rb_stream) {
85
108
  /* SFML rejects a buffer whose channel map does not have exactly one entry per
86
109
  channel, so an omitted map is filled with the conventional layout for 1 and
87
110
  2 channels and left Unspecified beyond that. */
88
- static void SoundBuffer_default_channel_map(unsigned int channel_count, sfSoundChannel *map) {
111
+ static void SoundBuffer_default_channel_map(unsigned int channel_count, sfSoundChannel* map) {
89
112
  static const sfSoundChannel surround[] = {
90
- sfSoundChannelFrontLeft, sfSoundChannelFrontRight, sfSoundChannelFrontCenter,
91
- sfSoundChannelLowFrequencyEffects, sfSoundChannelBackLeft, sfSoundChannelBackRight,
92
- sfSoundChannelSideLeft, sfSoundChannelSideRight, sfSoundChannelTopCenter
93
- };
113
+ sfSoundChannelFrontLeft, sfSoundChannelFrontRight,
114
+ sfSoundChannelFrontCenter, sfSoundChannelLowFrequencyEffects,
115
+ sfSoundChannelBackLeft, sfSoundChannelBackRight,
116
+ sfSoundChannelSideLeft, sfSoundChannelSideRight,
117
+ sfSoundChannelTopCenter};
94
118
  unsigned int i;
95
119
 
96
120
  for (i = 0; i < channel_count; i++) {
@@ -106,28 +130,76 @@ static void SoundBuffer_default_channel_map(unsigned int channel_count, sfSoundC
106
130
  }
107
131
  }
108
132
 
133
+ typedef struct {
134
+ sfSoundChannel* channel_map;
135
+ VALUE rb_channel_map;
136
+ unsigned int channel_count;
137
+ } ChannelMapFillContext;
138
+
139
+ /* sound_channel_from_rb raises ArgumentError on an unrecognized entry; run
140
+ through rb_protect so the caller can free the samples/channel_map buffers
141
+ (already allocated by this point) before that exception propagates,
142
+ instead of leaking them. */
143
+ static VALUE SoundBuffer_fill_channel_map(VALUE raw) {
144
+ ChannelMapFillContext* ctx = (ChannelMapFillContext*)raw;
145
+ long i;
146
+
147
+ for (i = 0; i < (long)ctx->channel_count; i++) {
148
+ ctx->channel_map[i] = sound_channel_from_rb(rb_ary_entry(ctx->rb_channel_map, i));
149
+ }
150
+
151
+ return Qnil;
152
+ }
153
+
109
154
  /* Copies the samples into a contiguous int16 buffer: an Array is converted
110
- element by element, a String is memcpy'd out of (possibly unaligned) Ruby
111
- storage. The channel map, when given, is translated symbol by symbol. */
112
- static VALUE SoundBuffer_from_samples(int argc, VALUE *argv, VALUE klass) {
155
+ element by element, and a String's packed int16 samples are copied out of
156
+ the (possibly unaligned) Ruby storage. The channel map, when given, is
157
+ translated symbol by symbol. */
158
+ /* call-seq:
159
+ * SoundBuffer.from_samples(samples, channel_count, sample_rate) -> SoundBuffer
160
+ * SoundBuffer.from_samples(samples, channel_count, sample_rate, channel_map) -> SoundBuffer
161
+ *
162
+ * +samples+ is an Array of Integer samples or a packed String of int16
163
+ * samples, interleaved by channel. +channel_map+, if given, is an Array of
164
+ * channel Symbols (see SoundChannel), one per channel; if omitted, a
165
+ * conventional layout is assumed for 1 and 2 channels.
166
+ *
167
+ * @return [SoundBuffer]
168
+ * @raise [ArgumentError] if +channel_count+ is 0, +samples+ is neither an
169
+ * Array nor a String, or +channel_map+ doesn't have exactly one entry per
170
+ * channel
171
+ */
172
+ static VALUE SoundBuffer_from_samples(int argc, VALUE* argv, VALUE klass) {
113
173
  VALUE rb_samples, rb_channel_count, rb_sample_rate, rb_channel_map;
114
- int16_t *samples;
115
- sfSoundChannel *channel_map;
174
+ int16_t* samples;
175
+ sfSoundChannel* channel_map;
116
176
  unsigned int channel_count;
117
177
  uint64_t sample_count;
118
- sfSoundBuffer *buffer;
178
+ sfSoundBuffer* buffer;
119
179
 
120
180
  rb_scan_args(argc, argv, "31", &rb_samples, &rb_channel_count, &rb_sample_rate,
121
181
  &rb_channel_map);
122
182
 
123
- channel_count = (unsigned int) NUM2INT(rb_channel_count);
183
+ channel_count = (unsigned int)NUM2INT(rb_channel_count);
124
184
 
125
185
  if (channel_count == 0) {
126
186
  rb_raise(rb_eArgError, "channel count must be positive");
127
187
  }
128
188
 
189
+ /* channel_map's malloc below (sizeof(sfSoundChannel) * channel_count) can
190
+ wrap on the 32-bit targets this gem ships if channel_count is large
191
+ enough, allocating far less than the subsequent write loop assumes.
192
+ Check before any allocation happens, so there's nothing to clean up on
193
+ the raise path. (Written with channel_count as the divisor, not
194
+ compared directly against a constant bound, so this isn't optimized
195
+ away as tautological on 64-bit hosts where it can't actually trigger
196
+ -- it still does real work on the 32-bit targets.) */
197
+ if (SIZE_MAX / channel_count < sizeof(sfSoundChannel)) {
198
+ rb_raise(rb_eArgError, "channel count too large");
199
+ }
200
+
129
201
  if (RB_TYPE_P(rb_samples, T_STRING)) {
130
- size_t bytes = (size_t) RSTRING_LEN(rb_samples);
202
+ size_t bytes = (size_t)RSTRING_LEN(rb_samples);
131
203
 
132
204
  if (bytes % sizeof(int16_t) != 0) {
133
205
  rb_raise(rb_eArgError, "sample string length must be a multiple of 2 bytes");
@@ -137,16 +209,21 @@ static VALUE SoundBuffer_from_samples(int argc, VALUE *argv, VALUE klass) {
137
209
  samples = malloc(bytes > 0 ? bytes : 1);
138
210
 
139
211
  if (bytes > 0) {
140
- memcpy(samples, RSTRING_PTR(rb_samples), bytes);
212
+ const int16_t* src = (const int16_t*)RSTRING_PTR(rb_samples);
213
+ size_t i;
214
+
215
+ for (i = 0; i < sample_count; i++) {
216
+ samples[i] = src[i];
217
+ }
141
218
  }
142
219
  } else if (RB_TYPE_P(rb_samples, T_ARRAY)) {
143
220
  long i;
144
221
 
145
- sample_count = (uint64_t) RARRAY_LEN(rb_samples);
146
- samples = malloc(sizeof(int16_t) * ((size_t) sample_count + 1));
222
+ sample_count = (uint64_t)RARRAY_LEN(rb_samples);
223
+ samples = malloc(sizeof(int16_t) * ((size_t)sample_count + 1));
147
224
 
148
- for (i = 0; i < (long) sample_count; i++) {
149
- samples[i] = (int16_t) NUM2INT(rb_ary_entry(rb_samples, i));
225
+ for (i = 0; i < (long)sample_count; i++) {
226
+ samples[i] = (int16_t)NUM2INT(rb_ary_entry(rb_samples, i));
150
227
  }
151
228
  } else {
152
229
  rb_raise(rb_eArgError, "samples must be an Array of integers or a String");
@@ -158,23 +235,30 @@ static VALUE SoundBuffer_from_samples(int argc, VALUE *argv, VALUE klass) {
158
235
  if (NIL_P(rb_channel_map)) {
159
236
  SoundBuffer_default_channel_map(channel_count, channel_map);
160
237
  } else {
161
- long i;
238
+ ChannelMapFillContext ctx = {.channel_map = channel_map,
239
+ .rb_channel_map = rb_channel_map,
240
+ .channel_count = channel_count};
241
+ int state = 0;
162
242
 
163
243
  if (!RB_TYPE_P(rb_channel_map, T_ARRAY) ||
164
- (unsigned long) RARRAY_LEN(rb_channel_map) != channel_count) {
244
+ (unsigned long)RARRAY_LEN(rb_channel_map) != channel_count) {
165
245
  free(samples);
166
246
  free(channel_map);
167
247
  rb_raise(rb_eArgError, "channel map must have exactly one entry per channel");
168
248
  }
169
249
 
170
- for (i = 0; i < (long) channel_count; i++) {
171
- channel_map[i] = sound_channel_from_rb(rb_ary_entry(rb_channel_map, i));
250
+ rb_protect(SoundBuffer_fill_channel_map, (VALUE)&ctx, &state);
251
+
252
+ if (state) {
253
+ free(samples);
254
+ free(channel_map);
255
+ rb_jump_tag(state);
172
256
  }
173
257
  }
174
258
 
175
259
  buffer = sfSoundBuffer_createFromSamples(samples, sample_count, channel_count,
176
- (unsigned int) NUM2INT(rb_sample_rate), channel_map,
177
- channel_count);
260
+ (unsigned int)NUM2INT(rb_sample_rate), channel_map,
261
+ channel_count);
178
262
 
179
263
  free(samples);
180
264
  free(channel_map);
@@ -182,17 +266,38 @@ static VALUE SoundBuffer_from_samples(int argc, VALUE *argv, VALUE klass) {
182
266
  return SoundBuffer_wrap(klass, buffer);
183
267
  }
184
268
 
269
+ /* call-seq: copy -> SoundBuffer
270
+ *
271
+ * Creates an independent copy of the buffer.
272
+ *
273
+ * @return [SoundBuffer] an independent copy
274
+ */
185
275
  static VALUE SoundBuffer_copy(VALUE self) {
186
276
  return SoundBuffer_wrap(Get_Klass_SoundBuffer(),
187
277
  sfSoundBuffer_copy(Get_SoundBuffer_Struct(self)));
188
278
  }
189
279
 
280
+ /* call-seq:
281
+ * save_to_file(path) -> true or false
282
+ *
283
+ * Writes the buffer to an audio file.
284
+ *
285
+ * @return [Boolean] whether the file was written successfully
286
+ */
190
287
  static VALUE SoundBuffer_save_to_file(VALUE self, VALUE rb_path) {
191
- return BOOL2RB(sfSoundBuffer_saveToFile(Get_SoundBuffer_Struct(self), StringValueCStr(rb_path)));
288
+ return BOOL2RB(
289
+ sfSoundBuffer_saveToFile(Get_SoundBuffer_Struct(self), StringValueCStr(rb_path)));
192
290
  }
193
291
 
292
+ /* call-seq: samples -> Array<Integer>
293
+ *
294
+ * Returns the buffer's raw audio samples as Integers.
295
+ *
296
+ * @return [Array<Integer>] the raw int16 audio samples, interleaved by
297
+ * channel
298
+ */
194
299
  static VALUE SoundBuffer_samples(VALUE self) {
195
- const int16_t *samples = sfSoundBuffer_getSamples(Get_SoundBuffer_Struct(self));
300
+ const int16_t* samples = sfSoundBuffer_getSamples(Get_SoundBuffer_Struct(self));
196
301
  uint64_t count = sfSoundBuffer_getSampleCount(Get_SoundBuffer_Struct(self));
197
302
  VALUE rb_array;
198
303
  uint64_t i;
@@ -201,7 +306,7 @@ static VALUE SoundBuffer_samples(VALUE self) {
201
306
  return rb_ary_new();
202
307
  }
203
308
 
204
- rb_array = rb_ary_new_capa((long) count);
309
+ rb_array = rb_ary_new_capa((long)count);
205
310
 
206
311
  for (i = 0; i < count; i++) {
207
312
  rb_ary_push(rb_array, INT2NUM(samples[i]));
@@ -210,21 +315,46 @@ static VALUE SoundBuffer_samples(VALUE self) {
210
315
  return rb_array;
211
316
  }
212
317
 
318
+ /* call-seq: sample_count -> Integer
319
+ *
320
+ * Returns the total number of samples stored in the buffer.
321
+ *
322
+ * @return [Integer] total number of int16 samples, across all channels
323
+ */
213
324
  static VALUE SoundBuffer_sample_count(VALUE self) {
214
325
  return ULL2NUM(sfSoundBuffer_getSampleCount(Get_SoundBuffer_Struct(self)));
215
326
  }
216
327
 
328
+ /* call-seq: sample_rate -> Integer
329
+ *
330
+ * Returns the buffer's sample rate in samples per second.
331
+ *
332
+ * @return [Integer]
333
+ */
217
334
  static VALUE SoundBuffer_sample_rate(VALUE self) {
218
335
  return UINT2NUM(sfSoundBuffer_getSampleRate(Get_SoundBuffer_Struct(self)));
219
336
  }
220
337
 
338
+ /* call-seq: channel_count -> Integer
339
+ *
340
+ * Returns the number of audio channels in the buffer.
341
+ *
342
+ * @return [Integer]
343
+ */
221
344
  static VALUE SoundBuffer_channel_count(VALUE self) {
222
345
  return UINT2NUM(sfSoundBuffer_getChannelCount(Get_SoundBuffer_Struct(self)));
223
346
  }
224
347
 
348
+ /* call-seq: channel_map -> Array<Symbol>
349
+ *
350
+ * Returns the channel layout of the buffer.
351
+ *
352
+ * @return [Array<Symbol>] one entry per channel, e.g.
353
+ * +[:front_left, :front_right]+
354
+ */
225
355
  static VALUE SoundBuffer_channel_map(VALUE self) {
226
356
  size_t count = 0;
227
- sfSoundChannel *map = sfSoundBuffer_getChannelMap(Get_SoundBuffer_Struct(self), &count);
357
+ sfSoundChannel* map = sfSoundBuffer_getChannelMap(Get_SoundBuffer_Struct(self), &count);
228
358
  VALUE rb_array;
229
359
  size_t i;
230
360
 
@@ -232,7 +362,7 @@ static VALUE SoundBuffer_channel_map(VALUE self) {
232
362
  return rb_ary_new();
233
363
  }
234
364
 
235
- rb_array = rb_ary_new_capa((long) count);
365
+ rb_array = rb_ary_new_capa((long)count);
236
366
 
237
367
  for (i = 0; i < count; i++) {
238
368
  rb_ary_push(rb_array, ID2SYM(rb_intern(sound_channel_name(map[i]))));
@@ -241,12 +371,32 @@ static VALUE SoundBuffer_channel_map(VALUE self) {
241
371
  return rb_array;
242
372
  }
243
373
 
374
+ /* call-seq: duration -> Time
375
+ *
376
+ * Returns the buffer's total playing duration.
377
+ *
378
+ * @return [Time]
379
+ */
244
380
  static VALUE SoundBuffer_duration(VALUE self) {
245
381
  return time_to_rb(sfSoundBuffer_getDuration(Get_SoundBuffer_Struct(self)));
246
382
  }
247
383
 
248
- void Init_SoundBuffer(VALUE rb_module) {
249
- rb_cSoundBuffer = rb_define_class_under(rb_module, "SoundBuffer", rb_cObject);
384
+ static VALUE SoundBuffer_alloc(VALUE klass) {
385
+ (void)klass;
386
+ rb_raise(rb_eNotImpError,
387
+ "use SoundBuffer.from_file, SoundBuffer.from_memory, SoundBuffer.from_stream or "
388
+ "SoundBuffer.from_samples");
389
+ }
390
+
391
+ /* Document-class: SFML::SoundBuffer
392
+ * Audio samples held fully decoded in memory, ready to be played through one
393
+ * or more Sound instances (a single buffer may back several simultaneous
394
+ * Sounds). For long audio, prefer Music, which streams instead of loading
395
+ * everything up front.
396
+ */
397
+ void Init_SoundBuffer(VALUE rb_mSFML) {
398
+ rb_cSoundBuffer = rb_define_class_under(rb_mSFML, "SoundBuffer", rb_cObject);
399
+ rb_define_alloc_func(rb_cSoundBuffer, SoundBuffer_alloc);
250
400
 
251
401
  rb_define_singleton_method(rb_cSoundBuffer, "from_file", SoundBuffer_from_file, 1);
252
402
  rb_define_singleton_method(rb_cSoundBuffer, "from_memory", SoundBuffer_from_memory, 1);
@@ -267,8 +417,8 @@ VALUE Get_Klass_SoundBuffer(void) {
267
417
  return rb_cSoundBuffer;
268
418
  }
269
419
 
270
- void *Get_SoundBuffer_Struct(VALUE self) {
271
- SoundBuffer *ptr;
420
+ void* Get_SoundBuffer_Struct(VALUE self) {
421
+ SoundBuffer* ptr;
272
422
  TypedData_Get_Struct(self, SoundBuffer, &SoundBuffer_data_type, ptr);
273
423
  return ptr->buffer;
274
424
  }