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.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +152 -0
- data/ext/audio/audio.c +541 -43
- data/ext/audio/audio.h +34 -0
- data/ext/audio/delay_node.c +185 -0
- data/ext/audio/delay_node.h +51 -0
- data/ext/audio/extconf.rb +12 -110
- data/ext/audio/miniaudio.h +95844 -0
- data/ext/audio/reverb_node.c +224 -0
- data/ext/audio/reverb_node.h +63 -0
- data/lib/dummy_audio.rb +94 -0
- data/lib/native_audio.rb +78 -7
- metadata +12 -132
- data/assets/include/SDL2/SDL.h +0 -233
- data/assets/include/SDL2/SDL_assert.h +0 -326
- data/assets/include/SDL2/SDL_atomic.h +0 -415
- data/assets/include/SDL2/SDL_audio.h +0 -1500
- data/assets/include/SDL2/SDL_bits.h +0 -126
- data/assets/include/SDL2/SDL_blendmode.h +0 -198
- data/assets/include/SDL2/SDL_clipboard.h +0 -141
- data/assets/include/SDL2/SDL_config.h +0 -61
- data/assets/include/SDL2/SDL_config_android.h +0 -194
- data/assets/include/SDL2/SDL_config_emscripten.h +0 -218
- data/assets/include/SDL2/SDL_config_iphoneos.h +0 -217
- data/assets/include/SDL2/SDL_config_macosx.h +0 -277
- data/assets/include/SDL2/SDL_config_minimal.h +0 -95
- data/assets/include/SDL2/SDL_config_ngage.h +0 -89
- data/assets/include/SDL2/SDL_config_os2.h +0 -207
- data/assets/include/SDL2/SDL_config_pandora.h +0 -141
- data/assets/include/SDL2/SDL_config_windows.h +0 -331
- data/assets/include/SDL2/SDL_config_wingdk.h +0 -253
- data/assets/include/SDL2/SDL_config_winrt.h +0 -220
- data/assets/include/SDL2/SDL_config_xbox.h +0 -235
- data/assets/include/SDL2/SDL_copying.h +0 -20
- data/assets/include/SDL2/SDL_cpuinfo.h +0 -594
- data/assets/include/SDL2/SDL_egl.h +0 -2352
- data/assets/include/SDL2/SDL_endian.h +0 -348
- data/assets/include/SDL2/SDL_error.h +0 -163
- data/assets/include/SDL2/SDL_events.h +0 -1166
- data/assets/include/SDL2/SDL_filesystem.h +0 -149
- data/assets/include/SDL2/SDL_gamecontroller.h +0 -1074
- data/assets/include/SDL2/SDL_gesture.h +0 -117
- data/assets/include/SDL2/SDL_guid.h +0 -100
- data/assets/include/SDL2/SDL_haptic.h +0 -1341
- data/assets/include/SDL2/SDL_hidapi.h +0 -451
- data/assets/include/SDL2/SDL_hints.h +0 -2569
- data/assets/include/SDL2/SDL_image.h +0 -2173
- data/assets/include/SDL2/SDL_joystick.h +0 -1066
- data/assets/include/SDL2/SDL_keyboard.h +0 -353
- data/assets/include/SDL2/SDL_keycode.h +0 -358
- data/assets/include/SDL2/SDL_loadso.h +0 -115
- data/assets/include/SDL2/SDL_locale.h +0 -103
- data/assets/include/SDL2/SDL_log.h +0 -404
- data/assets/include/SDL2/SDL_main.h +0 -275
- data/assets/include/SDL2/SDL_messagebox.h +0 -193
- data/assets/include/SDL2/SDL_metal.h +0 -113
- data/assets/include/SDL2/SDL_misc.h +0 -79
- data/assets/include/SDL2/SDL_mixer.h +0 -2784
- data/assets/include/SDL2/SDL_mouse.h +0 -465
- data/assets/include/SDL2/SDL_mutex.h +0 -471
- data/assets/include/SDL2/SDL_name.h +0 -33
- data/assets/include/SDL2/SDL_opengl.h +0 -2132
- data/assets/include/SDL2/SDL_opengl_glext.h +0 -13209
- data/assets/include/SDL2/SDL_opengles.h +0 -39
- data/assets/include/SDL2/SDL_opengles2.h +0 -52
- data/assets/include/SDL2/SDL_opengles2_gl2.h +0 -656
- data/assets/include/SDL2/SDL_opengles2_gl2ext.h +0 -4033
- data/assets/include/SDL2/SDL_opengles2_gl2platform.h +0 -27
- data/assets/include/SDL2/SDL_opengles2_khrplatform.h +0 -311
- data/assets/include/SDL2/SDL_pixels.h +0 -644
- data/assets/include/SDL2/SDL_platform.h +0 -261
- data/assets/include/SDL2/SDL_power.h +0 -88
- data/assets/include/SDL2/SDL_quit.h +0 -58
- data/assets/include/SDL2/SDL_rect.h +0 -376
- data/assets/include/SDL2/SDL_render.h +0 -1919
- data/assets/include/SDL2/SDL_revision.h +0 -6
- data/assets/include/SDL2/SDL_rwops.h +0 -841
- data/assets/include/SDL2/SDL_scancode.h +0 -438
- data/assets/include/SDL2/SDL_sensor.h +0 -322
- data/assets/include/SDL2/SDL_shape.h +0 -155
- data/assets/include/SDL2/SDL_stdinc.h +0 -830
- data/assets/include/SDL2/SDL_surface.h +0 -997
- data/assets/include/SDL2/SDL_system.h +0 -623
- data/assets/include/SDL2/SDL_syswm.h +0 -386
- data/assets/include/SDL2/SDL_test.h +0 -69
- data/assets/include/SDL2/SDL_test_assert.h +0 -105
- data/assets/include/SDL2/SDL_test_common.h +0 -236
- data/assets/include/SDL2/SDL_test_compare.h +0 -69
- data/assets/include/SDL2/SDL_test_crc32.h +0 -124
- data/assets/include/SDL2/SDL_test_font.h +0 -168
- data/assets/include/SDL2/SDL_test_fuzzer.h +0 -386
- data/assets/include/SDL2/SDL_test_harness.h +0 -134
- data/assets/include/SDL2/SDL_test_images.h +0 -78
- data/assets/include/SDL2/SDL_test_log.h +0 -67
- data/assets/include/SDL2/SDL_test_md5.h +0 -129
- data/assets/include/SDL2/SDL_test_memory.h +0 -63
- data/assets/include/SDL2/SDL_test_random.h +0 -115
- data/assets/include/SDL2/SDL_thread.h +0 -464
- data/assets/include/SDL2/SDL_timer.h +0 -222
- data/assets/include/SDL2/SDL_touch.h +0 -150
- data/assets/include/SDL2/SDL_ttf.h +0 -2316
- data/assets/include/SDL2/SDL_types.h +0 -29
- data/assets/include/SDL2/SDL_version.h +0 -204
- data/assets/include/SDL2/SDL_video.h +0 -2150
- data/assets/include/SDL2/SDL_vulkan.h +0 -215
- data/assets/include/SDL2/begin_code.h +0 -187
- data/assets/include/SDL2/close_code.h +0 -40
- data/assets/macos/universal/lib/libFLAC.a +0 -0
- data/assets/macos/universal/lib/libSDL2.a +0 -0
- data/assets/macos/universal/lib/libSDL2_mixer.a +0 -0
- data/assets/macos/universal/lib/libmodplug.a +0 -0
- data/assets/macos/universal/lib/libmpg123.a +0 -0
- data/assets/macos/universal/lib/libogg.a +0 -0
- data/assets/macos/universal/lib/libvorbis.a +0 -0
- data/assets/macos/universal/lib/libvorbisfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libFLAC.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_mixer.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmodplug.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmpg123.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libogg.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopus.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopusfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libsndfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libstdc++.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbis.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbisfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libz.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libFLAC.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2_mixer.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libmodplug.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libmpg123.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libogg.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libopus.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libopusfile.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libsndfile.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libstdc++.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libvorbis.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libvorbisfile.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libz.a +0 -0
- 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
|
|
3
|
-
#include <
|
|
4
|
-
#include <
|
|
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
|
-
|
|
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
|
-
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Cleanup (called on Ruby exit)
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
static void cleanup_audio(VALUE unused)
|
|
10
34
|
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
154
|
+
int id = sound_count;
|
|
155
|
+
sounds[id] = sound;
|
|
156
|
+
sound_count++;
|
|
27
157
|
|
|
28
|
-
|
|
158
|
+
return rb_int2inum(id);
|
|
29
159
|
}
|
|
30
160
|
|
|
31
|
-
VALUE
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
493
|
+
VALUE audio_set_reverb_damping(VALUE self, VALUE channel_id, VALUE damp)
|
|
62
494
|
{
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|