native_audio 0.3.0 → 0.5.0

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/README.md +152 -0
  4. data/ext/audio/audio.c +541 -43
  5. data/ext/audio/audio.h +34 -0
  6. data/ext/audio/delay_node.c +185 -0
  7. data/ext/audio/delay_node.h +51 -0
  8. data/ext/audio/extconf.rb +12 -110
  9. data/ext/audio/miniaudio.h +95844 -0
  10. data/ext/audio/reverb_node.c +224 -0
  11. data/ext/audio/reverb_node.h +63 -0
  12. data/lib/dummy_audio.rb +94 -0
  13. data/lib/native_audio.rb +78 -7
  14. metadata +12 -132
  15. data/assets/include/SDL2/SDL.h +0 -233
  16. data/assets/include/SDL2/SDL_assert.h +0 -326
  17. data/assets/include/SDL2/SDL_atomic.h +0 -415
  18. data/assets/include/SDL2/SDL_audio.h +0 -1500
  19. data/assets/include/SDL2/SDL_bits.h +0 -126
  20. data/assets/include/SDL2/SDL_blendmode.h +0 -198
  21. data/assets/include/SDL2/SDL_clipboard.h +0 -141
  22. data/assets/include/SDL2/SDL_config.h +0 -61
  23. data/assets/include/SDL2/SDL_config_android.h +0 -194
  24. data/assets/include/SDL2/SDL_config_emscripten.h +0 -218
  25. data/assets/include/SDL2/SDL_config_iphoneos.h +0 -217
  26. data/assets/include/SDL2/SDL_config_macosx.h +0 -277
  27. data/assets/include/SDL2/SDL_config_minimal.h +0 -95
  28. data/assets/include/SDL2/SDL_config_ngage.h +0 -89
  29. data/assets/include/SDL2/SDL_config_os2.h +0 -207
  30. data/assets/include/SDL2/SDL_config_pandora.h +0 -141
  31. data/assets/include/SDL2/SDL_config_windows.h +0 -331
  32. data/assets/include/SDL2/SDL_config_wingdk.h +0 -253
  33. data/assets/include/SDL2/SDL_config_winrt.h +0 -220
  34. data/assets/include/SDL2/SDL_config_xbox.h +0 -235
  35. data/assets/include/SDL2/SDL_copying.h +0 -20
  36. data/assets/include/SDL2/SDL_cpuinfo.h +0 -594
  37. data/assets/include/SDL2/SDL_egl.h +0 -2352
  38. data/assets/include/SDL2/SDL_endian.h +0 -348
  39. data/assets/include/SDL2/SDL_error.h +0 -163
  40. data/assets/include/SDL2/SDL_events.h +0 -1166
  41. data/assets/include/SDL2/SDL_filesystem.h +0 -149
  42. data/assets/include/SDL2/SDL_gamecontroller.h +0 -1074
  43. data/assets/include/SDL2/SDL_gesture.h +0 -117
  44. data/assets/include/SDL2/SDL_guid.h +0 -100
  45. data/assets/include/SDL2/SDL_haptic.h +0 -1341
  46. data/assets/include/SDL2/SDL_hidapi.h +0 -451
  47. data/assets/include/SDL2/SDL_hints.h +0 -2569
  48. data/assets/include/SDL2/SDL_image.h +0 -2173
  49. data/assets/include/SDL2/SDL_joystick.h +0 -1066
  50. data/assets/include/SDL2/SDL_keyboard.h +0 -353
  51. data/assets/include/SDL2/SDL_keycode.h +0 -358
  52. data/assets/include/SDL2/SDL_loadso.h +0 -115
  53. data/assets/include/SDL2/SDL_locale.h +0 -103
  54. data/assets/include/SDL2/SDL_log.h +0 -404
  55. data/assets/include/SDL2/SDL_main.h +0 -275
  56. data/assets/include/SDL2/SDL_messagebox.h +0 -193
  57. data/assets/include/SDL2/SDL_metal.h +0 -113
  58. data/assets/include/SDL2/SDL_misc.h +0 -79
  59. data/assets/include/SDL2/SDL_mixer.h +0 -2784
  60. data/assets/include/SDL2/SDL_mouse.h +0 -465
  61. data/assets/include/SDL2/SDL_mutex.h +0 -471
  62. data/assets/include/SDL2/SDL_name.h +0 -33
  63. data/assets/include/SDL2/SDL_opengl.h +0 -2132
  64. data/assets/include/SDL2/SDL_opengl_glext.h +0 -13209
  65. data/assets/include/SDL2/SDL_opengles.h +0 -39
  66. data/assets/include/SDL2/SDL_opengles2.h +0 -52
  67. data/assets/include/SDL2/SDL_opengles2_gl2.h +0 -656
  68. data/assets/include/SDL2/SDL_opengles2_gl2ext.h +0 -4033
  69. data/assets/include/SDL2/SDL_opengles2_gl2platform.h +0 -27
  70. data/assets/include/SDL2/SDL_opengles2_khrplatform.h +0 -311
  71. data/assets/include/SDL2/SDL_pixels.h +0 -644
  72. data/assets/include/SDL2/SDL_platform.h +0 -261
  73. data/assets/include/SDL2/SDL_power.h +0 -88
  74. data/assets/include/SDL2/SDL_quit.h +0 -58
  75. data/assets/include/SDL2/SDL_rect.h +0 -376
  76. data/assets/include/SDL2/SDL_render.h +0 -1919
  77. data/assets/include/SDL2/SDL_revision.h +0 -6
  78. data/assets/include/SDL2/SDL_rwops.h +0 -841
  79. data/assets/include/SDL2/SDL_scancode.h +0 -438
  80. data/assets/include/SDL2/SDL_sensor.h +0 -322
  81. data/assets/include/SDL2/SDL_shape.h +0 -155
  82. data/assets/include/SDL2/SDL_stdinc.h +0 -830
  83. data/assets/include/SDL2/SDL_surface.h +0 -997
  84. data/assets/include/SDL2/SDL_system.h +0 -623
  85. data/assets/include/SDL2/SDL_syswm.h +0 -386
  86. data/assets/include/SDL2/SDL_test.h +0 -69
  87. data/assets/include/SDL2/SDL_test_assert.h +0 -105
  88. data/assets/include/SDL2/SDL_test_common.h +0 -236
  89. data/assets/include/SDL2/SDL_test_compare.h +0 -69
  90. data/assets/include/SDL2/SDL_test_crc32.h +0 -124
  91. data/assets/include/SDL2/SDL_test_font.h +0 -168
  92. data/assets/include/SDL2/SDL_test_fuzzer.h +0 -386
  93. data/assets/include/SDL2/SDL_test_harness.h +0 -134
  94. data/assets/include/SDL2/SDL_test_images.h +0 -78
  95. data/assets/include/SDL2/SDL_test_log.h +0 -67
  96. data/assets/include/SDL2/SDL_test_md5.h +0 -129
  97. data/assets/include/SDL2/SDL_test_memory.h +0 -63
  98. data/assets/include/SDL2/SDL_test_random.h +0 -115
  99. data/assets/include/SDL2/SDL_thread.h +0 -464
  100. data/assets/include/SDL2/SDL_timer.h +0 -222
  101. data/assets/include/SDL2/SDL_touch.h +0 -150
  102. data/assets/include/SDL2/SDL_ttf.h +0 -2316
  103. data/assets/include/SDL2/SDL_types.h +0 -29
  104. data/assets/include/SDL2/SDL_version.h +0 -204
  105. data/assets/include/SDL2/SDL_video.h +0 -2150
  106. data/assets/include/SDL2/SDL_vulkan.h +0 -215
  107. data/assets/include/SDL2/begin_code.h +0 -187
  108. data/assets/include/SDL2/close_code.h +0 -40
  109. data/assets/macos/universal/lib/libFLAC.a +0 -0
  110. data/assets/macos/universal/lib/libSDL2.a +0 -0
  111. data/assets/macos/universal/lib/libSDL2_mixer.a +0 -0
  112. data/assets/macos/universal/lib/libmodplug.a +0 -0
  113. data/assets/macos/universal/lib/libmpg123.a +0 -0
  114. data/assets/macos/universal/lib/libogg.a +0 -0
  115. data/assets/macos/universal/lib/libvorbis.a +0 -0
  116. data/assets/macos/universal/lib/libvorbisfile.a +0 -0
  117. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libFLAC.a +0 -0
  118. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2.a +0 -0
  119. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_mixer.a +0 -0
  120. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmodplug.a +0 -0
  121. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmpg123.a +0 -0
  122. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libogg.a +0 -0
  123. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopus.a +0 -0
  124. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopusfile.a +0 -0
  125. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libsndfile.a +0 -0
  126. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libstdc++.a +0 -0
  127. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbis.a +0 -0
  128. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbisfile.a +0 -0
  129. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libz.a +0 -0
  130. data/assets/windows/mingw-w64-x86_64/lib/libFLAC.a +0 -0
  131. data/assets/windows/mingw-w64-x86_64/lib/libSDL2.a +0 -0
  132. data/assets/windows/mingw-w64-x86_64/lib/libSDL2_mixer.a +0 -0
  133. data/assets/windows/mingw-w64-x86_64/lib/libmodplug.a +0 -0
  134. data/assets/windows/mingw-w64-x86_64/lib/libmpg123.a +0 -0
  135. data/assets/windows/mingw-w64-x86_64/lib/libogg.a +0 -0
  136. data/assets/windows/mingw-w64-x86_64/lib/libopus.a +0 -0
  137. data/assets/windows/mingw-w64-x86_64/lib/libopusfile.a +0 -0
  138. data/assets/windows/mingw-w64-x86_64/lib/libsndfile.a +0 -0
  139. data/assets/windows/mingw-w64-x86_64/lib/libstdc++.a +0 -0
  140. data/assets/windows/mingw-w64-x86_64/lib/libvorbis.a +0 -0
  141. data/assets/windows/mingw-w64-x86_64/lib/libvorbisfile.a +0 -0
  142. data/assets/windows/mingw-w64-x86_64/lib/libz.a +0 -0
  143. data/ext/audio/extconf.h +0 -3
data/ext/audio/audio.c CHANGED
@@ -1,80 +1,578 @@
1
+ // ============================================================================
2
+ // audio.c - Main entry point and Ruby bindings for native_audio
3
+ // ============================================================================
4
+
1
5
  #include <ruby.h>
2
- #include "extconf.h"
3
- #include <SDL2/SDL.h>
4
- #include <SDL2/SDL_mixer.h>
6
+ #include <math.h>
7
+ #include <stdlib.h>
8
+ #include <string.h>
9
+
10
+ #define MINIAUDIO_IMPLEMENTATION
11
+ #include "miniaudio.h"
12
+ #include "audio.h"
13
+
14
+ // ============================================================================
15
+ // Global Definitions
16
+ // ============================================================================
5
17
 
6
- Mix_Chunk *sounds[1024];
18
+ ma_engine engine;
19
+ ma_context context;
20
+ ma_sound *sounds[MAX_SOUNDS];
21
+ ma_sound *channels[MAX_CHANNELS];
22
+ multi_tap_delay_node *delay_nodes[MAX_CHANNELS];
23
+ reverb_node *reverb_nodes[MAX_CHANNELS];
7
24
  int sound_count = 0;
25
+ int engine_initialized = 0;
26
+ int context_initialized = 0;
27
+ int using_null_backend = 0;
8
28
 
9
- VALUE audio_play(VALUE self, VALUE channel_id, VALUE clip)
29
+ // ============================================================================
30
+ // Cleanup (called on Ruby exit)
31
+ // ============================================================================
32
+
33
+ static void cleanup_audio(VALUE unused)
10
34
  {
11
- Mix_Chunk *sound = sounds[NUM2INT(clip)];
12
- int channel = Mix_PlayChannel(NUM2INT(channel_id), sound, 0);
13
- return rb_int2inum(channel);
35
+ (void)unused;
36
+
37
+ if (!engine_initialized) {
38
+ return;
39
+ }
40
+
41
+ for (int i = 0; i < MAX_CHANNELS; i++) {
42
+ if (channels[i] != NULL) {
43
+ ma_sound_stop(channels[i]);
44
+ ma_sound_uninit(channels[i]);
45
+ free(channels[i]);
46
+ channels[i] = NULL;
47
+ }
48
+
49
+ if (delay_nodes[i] != NULL) {
50
+ multi_tap_delay_uninit(delay_nodes[i]);
51
+ free(delay_nodes[i]);
52
+ delay_nodes[i] = NULL;
53
+ }
54
+
55
+ if (reverb_nodes[i] != NULL) {
56
+ reverb_uninit(reverb_nodes[i]);
57
+ free(reverb_nodes[i]);
58
+ reverb_nodes[i] = NULL;
59
+ }
60
+ }
61
+
62
+ for (int i = 0; i < sound_count; i++) {
63
+ if (sounds[i] != NULL) {
64
+ ma_sound_stop(sounds[i]);
65
+ ma_sound_uninit(sounds[i]);
66
+ free(sounds[i]);
67
+ sounds[i] = NULL;
68
+ }
69
+ }
70
+
71
+ #ifdef _WIN32
72
+ if (using_null_backend) {
73
+ engine_initialized = 0;
74
+ context_initialized = 0;
75
+ return;
76
+ }
77
+ #endif
78
+
79
+ ma_engine_uninit(&engine);
80
+ engine_initialized = 0;
81
+
82
+ if (context_initialized) {
83
+ ma_context_uninit(&context);
84
+ context_initialized = 0;
85
+ }
14
86
  }
15
87
 
88
+ // ============================================================================
89
+ // Engine Initialization
90
+ // ============================================================================
91
+
92
+ VALUE audio_init(VALUE self)
93
+ {
94
+ if (engine_initialized) {
95
+ return Qnil;
96
+ }
97
+
98
+ const char *driver = getenv("NATIVE_AUDIO_DRIVER");
99
+ int use_null = (driver != NULL && strcmp(driver, "null") == 0);
100
+
101
+ ma_engine_config config = ma_engine_config_init();
102
+ config.listenerCount = 1;
103
+
104
+ if (use_null) {
105
+ ma_backend backends[] = { ma_backend_null };
106
+ ma_result ctx_result = ma_context_init(backends, 1, NULL, &context);
107
+ if (ctx_result != MA_SUCCESS) {
108
+ rb_raise(rb_eRuntimeError, "Failed to initialize null audio context");
109
+ return Qnil;
110
+ }
111
+ context_initialized = 1;
112
+ using_null_backend = 1;
113
+ config.pContext = &context;
114
+ }
115
+
116
+ ma_result result = ma_engine_init(&config, &engine);
117
+
118
+ if (result != MA_SUCCESS) {
119
+ if (context_initialized) {
120
+ ma_context_uninit(&context);
121
+ context_initialized = 0;
122
+ }
123
+ rb_raise(rb_eRuntimeError, "Failed to initialize audio engine");
124
+ return Qnil;
125
+ }
126
+
127
+ engine_initialized = 1;
128
+ rb_set_end_proc(cleanup_audio, Qnil);
129
+
130
+ return Qnil;
131
+ }
132
+
133
+ // ============================================================================
134
+ // Audio Loading
135
+ // ============================================================================
136
+
16
137
  VALUE audio_load(VALUE self, VALUE file)
17
138
  {
18
- Mix_Chunk *sound = NULL;
19
- sound = Mix_LoadWAV(StringValueCStr(file));
20
- if(sound == NULL)
21
- {
22
- fprintf(stderr, "Unable to load WAV file: %s\n", Mix_GetError());
23
- }
139
+ const char *path = StringValueCStr(file);
140
+
141
+ ma_sound *sound = (ma_sound *)malloc(sizeof(ma_sound));
142
+ if (sound == NULL) {
143
+ rb_raise(rb_eRuntimeError, "Failed to allocate memory for sound");
144
+ return Qnil;
145
+ }
146
+
147
+ ma_result result = ma_sound_init_from_file(&engine, path, MA_SOUND_FLAG_DECODE, NULL, NULL, sound);
148
+ if (result != MA_SUCCESS) {
149
+ free(sound);
150
+ rb_raise(rb_eRuntimeError, "Failed to load audio file: %s", path);
151
+ return Qnil;
152
+ }
24
153
 
25
- sounds[0] = sound;
26
- sound_count++;
154
+ int id = sound_count;
155
+ sounds[id] = sound;
156
+ sound_count++;
27
157
 
28
- return rb_int2inum(sound_count - 1);
158
+ return rb_int2inum(id);
29
159
  }
30
160
 
31
- VALUE audio_set_pos(VALUE self, VALUE channel_id, VALUE angle, VALUE distance)
161
+ VALUE audio_duration(VALUE self, VALUE clip)
162
+ {
163
+ int clip_id = NUM2INT(clip);
164
+
165
+ if (clip_id < 0 || clip_id >= sound_count || sounds[clip_id] == NULL) {
166
+ rb_raise(rb_eArgError, "Invalid clip ID: %d", clip_id);
167
+ return Qnil;
168
+ }
169
+
170
+ float length;
171
+ ma_result result = ma_sound_get_length_in_seconds(sounds[clip_id], &length);
172
+ if (result != MA_SUCCESS) {
173
+ return Qnil;
174
+ }
175
+
176
+ return rb_float_new(length);
177
+ }
178
+
179
+ // ============================================================================
180
+ // Playback Controls
181
+ // ============================================================================
182
+
183
+ VALUE audio_play(VALUE self, VALUE channel_id, VALUE clip)
32
184
  {
33
- Mix_SetPosition(NUM2INT(channel_id), NUM2INT(angle), NUM2INT(distance));
34
- return Qnil;
185
+ int channel = NUM2INT(channel_id);
186
+ int clip_id = NUM2INT(clip);
187
+
188
+ if (clip_id < 0 || clip_id >= sound_count || sounds[clip_id] == NULL) {
189
+ rb_raise(rb_eArgError, "Invalid clip ID: %d", clip_id);
190
+ return Qnil;
191
+ }
192
+
193
+ if (channel < 0 || channel >= MAX_CHANNELS) {
194
+ rb_raise(rb_eArgError, "Invalid channel ID: %d", channel);
195
+ return Qnil;
196
+ }
197
+
198
+ // Clean up existing resources on this channel
199
+ if (channels[channel] != NULL) {
200
+ ma_sound_stop(channels[channel]);
201
+ ma_sound_uninit(channels[channel]);
202
+ free(channels[channel]);
203
+ channels[channel] = NULL;
204
+ }
205
+
206
+ if (delay_nodes[channel] != NULL) {
207
+ multi_tap_delay_uninit(delay_nodes[channel]);
208
+ free(delay_nodes[channel]);
209
+ delay_nodes[channel] = NULL;
210
+ }
211
+
212
+ if (reverb_nodes[channel] != NULL) {
213
+ reverb_uninit(reverb_nodes[channel]);
214
+ free(reverb_nodes[channel]);
215
+ reverb_nodes[channel] = NULL;
216
+ }
217
+
218
+ // Create sound copy for playback
219
+ ma_sound *playback = (ma_sound *)malloc(sizeof(ma_sound));
220
+ if (playback == NULL) {
221
+ rb_raise(rb_eRuntimeError, "Failed to allocate memory for playback");
222
+ return Qnil;
223
+ }
224
+
225
+ ma_result result = ma_sound_init_copy(&engine, sounds[clip_id], MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT, NULL, playback);
226
+ if (result != MA_SUCCESS) {
227
+ free(playback);
228
+ rb_raise(rb_eRuntimeError, "Failed to create sound copy for playback");
229
+ return Qnil;
230
+ }
231
+
232
+ // Create delay node
233
+ ma_uint32 sampleRate = ma_engine_get_sample_rate(&engine);
234
+ ma_uint32 numChannels = ma_engine_get_channels(&engine);
235
+
236
+ multi_tap_delay_node *delayNode = (multi_tap_delay_node *)malloc(sizeof(multi_tap_delay_node));
237
+ if (delayNode == NULL) {
238
+ ma_sound_uninit(playback);
239
+ free(playback);
240
+ rb_raise(rb_eRuntimeError, "Failed to allocate memory for delay node");
241
+ return Qnil;
242
+ }
243
+
244
+ result = multi_tap_delay_init(delayNode, ma_engine_get_node_graph(&engine), sampleRate, numChannels);
245
+ if (result != MA_SUCCESS) {
246
+ free(delayNode);
247
+ ma_sound_uninit(playback);
248
+ free(playback);
249
+ rb_raise(rb_eRuntimeError, "Failed to initialize delay node");
250
+ return Qnil;
251
+ }
252
+
253
+ // Create reverb node
254
+ reverb_node *reverbNode = (reverb_node *)malloc(sizeof(reverb_node));
255
+ if (reverbNode == NULL) {
256
+ multi_tap_delay_uninit(delayNode);
257
+ free(delayNode);
258
+ ma_sound_uninit(playback);
259
+ free(playback);
260
+ rb_raise(rb_eRuntimeError, "Failed to allocate memory for reverb node");
261
+ return Qnil;
262
+ }
263
+
264
+ result = reverb_init(reverbNode, ma_engine_get_node_graph(&engine), sampleRate, numChannels);
265
+ if (result != MA_SUCCESS) {
266
+ free(reverbNode);
267
+ multi_tap_delay_uninit(delayNode);
268
+ free(delayNode);
269
+ ma_sound_uninit(playback);
270
+ free(playback);
271
+ rb_raise(rb_eRuntimeError, "Failed to initialize reverb node");
272
+ return Qnil;
273
+ }
274
+
275
+ // Route: sound -> delay_node -> reverb_node -> endpoint
276
+ ma_node *endpoint = ma_engine_get_endpoint(&engine);
277
+ ma_node_attach_output_bus(&reverbNode->base, 0, endpoint, 0);
278
+ ma_node_attach_output_bus(&delayNode->base, 0, &reverbNode->base, 0);
279
+ ma_node_attach_output_bus((ma_node *)playback, 0, &delayNode->base, 0);
280
+
281
+ delay_nodes[channel] = delayNode;
282
+ reverb_nodes[channel] = reverbNode;
283
+ channels[channel] = playback;
284
+ ma_sound_start(playback);
285
+
286
+ return rb_int2inum(channel);
35
287
  }
36
288
 
37
289
  VALUE audio_stop(VALUE self, VALUE channel_id)
38
290
  {
39
- Mix_HaltChannel(NUM2INT(channel_id));
40
- return Qnil;
291
+ int channel = NUM2INT(channel_id);
292
+
293
+ if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
294
+ return Qnil;
295
+ }
296
+
297
+ ma_sound_stop(channels[channel]);
298
+ ma_sound_seek_to_pcm_frame(channels[channel], 0);
299
+
300
+ return Qnil;
41
301
  }
42
302
 
43
303
  VALUE audio_pause(VALUE self, VALUE channel_id)
44
304
  {
45
- Mix_Pause(NUM2INT(channel_id));
46
- return Qnil;
305
+ int channel = NUM2INT(channel_id);
306
+
307
+ if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
308
+ return Qnil;
309
+ }
310
+
311
+ ma_sound_stop(channels[channel]);
312
+
313
+ return Qnil;
47
314
  }
48
315
 
49
316
  VALUE audio_resume(VALUE self, VALUE channel_id)
50
317
  {
51
- Mix_Resume(NUM2INT(channel_id));
52
- return Qnil;
318
+ int channel = NUM2INT(channel_id);
319
+
320
+ if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
321
+ return Qnil;
322
+ }
323
+
324
+ ma_sound_start(channels[channel]);
325
+
326
+ return Qnil;
53
327
  }
54
328
 
329
+ // ============================================================================
330
+ // Sound Effects
331
+ // ============================================================================
332
+
55
333
  VALUE audio_set_volume(VALUE self, VALUE channel_id, VALUE volume)
56
334
  {
57
- Mix_Volume(NUM2INT(channel_id), NUM2INT(volume));
58
- return Qnil;
335
+ int channel = NUM2INT(channel_id);
336
+ int vol = NUM2INT(volume);
337
+
338
+ if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
339
+ return Qnil;
340
+ }
341
+
342
+ ma_sound_set_volume(channels[channel], vol / 128.0f);
343
+
344
+ return Qnil;
345
+ }
346
+
347
+ VALUE audio_set_pitch(VALUE self, VALUE channel_id, VALUE pitch)
348
+ {
349
+ int channel = NUM2INT(channel_id);
350
+ float p = (float)NUM2DBL(pitch);
351
+
352
+ if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
353
+ return Qnil;
354
+ }
355
+
356
+ ma_sound_set_pitch(channels[channel], p);
357
+
358
+ return Qnil;
359
+ }
360
+
361
+ VALUE audio_set_pos(VALUE self, VALUE channel_id, VALUE angle, VALUE distance)
362
+ {
363
+ int channel = NUM2INT(channel_id);
364
+ int ang = NUM2INT(angle);
365
+ int dist = NUM2INT(distance);
366
+
367
+ if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
368
+ return Qnil;
369
+ }
370
+
371
+ float rad = ang * (MA_PI / 180.0f);
372
+ float normalized_dist = dist / 255.0f;
373
+ float x = normalized_dist * sinf(rad);
374
+ float z = -normalized_dist * cosf(rad);
375
+
376
+ ma_sound_set_position(channels[channel], x, 0.0f, z);
377
+
378
+ return Qnil;
379
+ }
380
+
381
+ VALUE audio_set_looping(VALUE self, VALUE channel_id, VALUE looping)
382
+ {
383
+ int channel = NUM2INT(channel_id);
384
+ ma_bool32 loop = RTEST(looping) ? MA_TRUE : MA_FALSE;
385
+
386
+ if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
387
+ return Qnil;
388
+ }
389
+
390
+ ma_sound_set_looping(channels[channel], loop);
391
+
392
+ return Qnil;
393
+ }
394
+
395
+ // ============================================================================
396
+ // Delay Tap Controls
397
+ // ============================================================================
398
+
399
+ VALUE audio_add_delay_tap(VALUE self, VALUE channel_id, VALUE time_ms, VALUE volume)
400
+ {
401
+ int channel = NUM2INT(channel_id);
402
+ float ms = (float)NUM2DBL(time_ms);
403
+ float vol = (float)NUM2DBL(volume);
404
+
405
+ if (channel < 0 || channel >= MAX_CHANNELS || delay_nodes[channel] == NULL) {
406
+ rb_raise(rb_eArgError, "Invalid channel or no delay node: %d", channel);
407
+ return Qnil;
408
+ }
409
+
410
+ int tap_id = multi_tap_delay_add_tap(delay_nodes[channel], ms, vol);
411
+ if (tap_id < 0) {
412
+ rb_raise(rb_eRuntimeError, "Failed to add delay tap (max taps reached)");
413
+ return Qnil;
414
+ }
415
+
416
+ return rb_int2inum(tap_id);
417
+ }
418
+
419
+ VALUE audio_remove_delay_tap(VALUE self, VALUE channel_id, VALUE tap_id)
420
+ {
421
+ int channel = NUM2INT(channel_id);
422
+ int tap = NUM2INT(tap_id);
423
+
424
+ if (channel < 0 || channel >= MAX_CHANNELS || delay_nodes[channel] == NULL) {
425
+ return Qnil;
426
+ }
427
+
428
+ multi_tap_delay_remove_tap(delay_nodes[channel], tap);
429
+
430
+ return Qnil;
431
+ }
432
+
433
+ VALUE audio_set_delay_tap_volume(VALUE self, VALUE channel_id, VALUE tap_id, VALUE volume)
434
+ {
435
+ int channel = NUM2INT(channel_id);
436
+ int tap = NUM2INT(tap_id);
437
+ float vol = (float)NUM2DBL(volume);
438
+
439
+ if (channel < 0 || channel >= MAX_CHANNELS || delay_nodes[channel] == NULL) {
440
+ return Qnil;
441
+ }
442
+
443
+ multi_tap_delay_set_volume(delay_nodes[channel], tap, vol);
444
+
445
+ return Qnil;
446
+ }
447
+
448
+ VALUE audio_set_delay_tap_time(VALUE self, VALUE channel_id, VALUE tap_id, VALUE time_ms)
449
+ {
450
+ int channel = NUM2INT(channel_id);
451
+ int tap = NUM2INT(tap_id);
452
+ float ms = (float)NUM2DBL(time_ms);
453
+
454
+ if (channel < 0 || channel >= MAX_CHANNELS || delay_nodes[channel] == NULL) {
455
+ return Qnil;
456
+ }
457
+
458
+ multi_tap_delay_set_time(delay_nodes[channel], tap, ms);
459
+
460
+ return Qnil;
461
+ }
462
+
463
+ // ============================================================================
464
+ // Reverb Controls
465
+ // ============================================================================
466
+
467
+ VALUE audio_enable_reverb(VALUE self, VALUE channel_id, VALUE enabled)
468
+ {
469
+ int channel = NUM2INT(channel_id);
470
+ ma_bool32 en = RTEST(enabled) ? MA_TRUE : MA_FALSE;
471
+
472
+ if (channel < 0 || channel >= MAX_CHANNELS || reverb_nodes[channel] == NULL) {
473
+ return Qnil;
474
+ }
475
+
476
+ reverb_set_enabled(reverb_nodes[channel], en);
477
+ return Qnil;
478
+ }
479
+
480
+ VALUE audio_set_reverb_room_size(VALUE self, VALUE channel_id, VALUE size)
481
+ {
482
+ int channel = NUM2INT(channel_id);
483
+ float s = (float)NUM2DBL(size);
484
+
485
+ if (channel < 0 || channel >= MAX_CHANNELS || reverb_nodes[channel] == NULL) {
486
+ return Qnil;
487
+ }
488
+
489
+ reverb_set_room_size(reverb_nodes[channel], s);
490
+ return Qnil;
59
491
  }
60
492
 
61
- void Init_audio()
493
+ VALUE audio_set_reverb_damping(VALUE self, VALUE channel_id, VALUE damp)
62
494
  {
63
- if (SDL_Init(SDL_INIT_AUDIO) != 0) { exit(1); }
495
+ int channel = NUM2INT(channel_id);
496
+ float d = (float)NUM2DBL(damp);
497
+
498
+ if (channel < 0 || channel >= MAX_CHANNELS || reverb_nodes[channel] == NULL) {
499
+ return Qnil;
500
+ }
501
+
502
+ reverb_set_damping(reverb_nodes[channel], d);
503
+ return Qnil;
504
+ }
505
+
506
+ VALUE audio_set_reverb_wet(VALUE self, VALUE channel_id, VALUE wet)
507
+ {
508
+ int channel = NUM2INT(channel_id);
509
+ float w = (float)NUM2DBL(wet);
510
+
511
+ if (channel < 0 || channel >= MAX_CHANNELS || reverb_nodes[channel] == NULL) {
512
+ return Qnil;
513
+ }
514
+
515
+ reverb_set_wet(reverb_nodes[channel], w);
516
+ return Qnil;
517
+ }
518
+
519
+ VALUE audio_set_reverb_dry(VALUE self, VALUE channel_id, VALUE dry)
520
+ {
521
+ int channel = NUM2INT(channel_id);
522
+ float d = (float)NUM2DBL(dry);
523
+
524
+ if (channel < 0 || channel >= MAX_CHANNELS || reverb_nodes[channel] == NULL) {
525
+ return Qnil;
526
+ }
527
+
528
+ reverb_set_dry(reverb_nodes[channel], d);
529
+ return Qnil;
530
+ }
531
+
532
+ // ============================================================================
533
+ // Ruby Module Setup
534
+ // ============================================================================
535
+
536
+ void Init_audio(void)
537
+ {
538
+ for (int i = 0; i < MAX_SOUNDS; i++) sounds[i] = NULL;
539
+ for (int i = 0; i < MAX_CHANNELS; i++) {
540
+ channels[i] = NULL;
541
+ delay_nodes[i] = NULL;
542
+ reverb_nodes[i] = NULL;
543
+ }
544
+
545
+ VALUE mAudio = rb_define_module("Audio");
546
+
547
+ // Initialization
548
+ rb_define_singleton_method(mAudio, "init", audio_init, 0);
549
+
550
+ // Loading
551
+ rb_define_singleton_method(mAudio, "load", audio_load, 1);
552
+ rb_define_singleton_method(mAudio, "duration", audio_duration, 1);
553
+
554
+ // Playback
555
+ rb_define_singleton_method(mAudio, "play", audio_play, 2);
556
+ rb_define_singleton_method(mAudio, "stop", audio_stop, 1);
557
+ rb_define_singleton_method(mAudio, "pause", audio_pause, 1);
558
+ rb_define_singleton_method(mAudio, "resume", audio_resume, 1);
64
559
 
65
- int audio_rate = 22050;
66
- Uint16 audio_format = AUDIO_S16SYS;
67
- int audio_channels = 2;
68
- int audio_buffers = 4096;
560
+ // Effects
561
+ rb_define_singleton_method(mAudio, "set_volume", audio_set_volume, 2);
562
+ rb_define_singleton_method(mAudio, "set_pitch", audio_set_pitch, 2);
563
+ rb_define_singleton_method(mAudio, "set_pos", audio_set_pos, 3);
564
+ rb_define_singleton_method(mAudio, "set_looping", audio_set_looping, 2);
69
565
 
70
- if(Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers) != 0) { exit(1); }
566
+ // Delay taps
567
+ rb_define_singleton_method(mAudio, "add_delay_tap", audio_add_delay_tap, 3);
568
+ rb_define_singleton_method(mAudio, "remove_delay_tap", audio_remove_delay_tap, 2);
569
+ rb_define_singleton_method(mAudio, "set_delay_tap_volume", audio_set_delay_tap_volume, 3);
570
+ rb_define_singleton_method(mAudio, "set_delay_tap_time", audio_set_delay_tap_time, 3);
71
571
 
72
- VALUE mAudio = rb_define_module("Audio");
73
- rb_define_singleton_method(mAudio, "load", audio_load, 1);
74
- rb_define_singleton_method(mAudio, "play", audio_play, 2);
75
- rb_define_singleton_method(mAudio, "set_pos", audio_set_pos, 3);
76
- rb_define_singleton_method(mAudio, "stop", audio_stop, 1);
77
- rb_define_singleton_method(mAudio, "pause", audio_pause, 1);
78
- rb_define_singleton_method(mAudio, "resume", audio_resume, 1);
79
- rb_define_singleton_method(mAudio, "set_volume", audio_set_volume, 2);
572
+ // Reverb
573
+ rb_define_singleton_method(mAudio, "enable_reverb", audio_enable_reverb, 2);
574
+ rb_define_singleton_method(mAudio, "set_reverb_room_size", audio_set_reverb_room_size, 2);
575
+ rb_define_singleton_method(mAudio, "set_reverb_damping", audio_set_reverb_damping, 2);
576
+ rb_define_singleton_method(mAudio, "set_reverb_wet", audio_set_reverb_wet, 2);
577
+ rb_define_singleton_method(mAudio, "set_reverb_dry", audio_set_reverb_dry, 2);
80
578
  }
data/ext/audio/audio.h ADDED
@@ -0,0 +1,34 @@
1
+ // ============================================================================
2
+ // audio.h - Shared types, constants, and globals for native_audio
3
+ // ============================================================================
4
+
5
+ #ifndef NATIVE_AUDIO_H
6
+ #define NATIVE_AUDIO_H
7
+
8
+ #include "miniaudio.h"
9
+ #include "delay_node.h"
10
+ #include "reverb_node.h"
11
+
12
+ // ============================================================================
13
+ // Constants
14
+ // ============================================================================
15
+
16
+ #define MAX_SOUNDS 1024
17
+ #define MAX_CHANNELS 1024
18
+
19
+ // ============================================================================
20
+ // Globals (defined in audio.c)
21
+ // ============================================================================
22
+
23
+ extern ma_engine engine;
24
+ extern ma_context context;
25
+ extern ma_sound *sounds[MAX_SOUNDS];
26
+ extern ma_sound *channels[MAX_CHANNELS];
27
+ extern multi_tap_delay_node *delay_nodes[MAX_CHANNELS];
28
+ extern reverb_node *reverb_nodes[MAX_CHANNELS];
29
+ extern int sound_count;
30
+ extern int engine_initialized;
31
+ extern int context_initialized;
32
+ extern int using_null_backend;
33
+
34
+ #endif // NATIVE_AUDIO_H