Module: Audio

Defined in:
ext/audio/audio.c

Class Method Summary collapse

Class Method Details

.duration(clip) ⇒ Object

Audio.duration(clip) - Get duration of clip in seconds



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'ext/audio/audio.c', line 110

VALUE audio_duration(VALUE self, VALUE clip)
{
    int clip_id = NUM2INT(clip);

    if (clip_id < 0 || clip_id >= sound_count || sounds[clip_id] == NULL) {
        rb_raise(rb_eArgError, "Invalid clip ID: %d", clip_id);
        return Qnil;
    }

    float length;
    ma_result result = ma_sound_get_length_in_seconds(sounds[clip_id], &length);
    if (result != MA_SUCCESS) {
        return Qnil;
    }

    return rb_float_new(length);
}

.initObject

Audio.init - Initialize the audio engine



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'ext/audio/audio.c', line 283

VALUE audio_init(VALUE self)
{
    if (engine_initialized) {
        return Qnil;
    }

    // Check for null driver (for CI environments without audio devices)
    // Usage: NATIVE_AUDIO_DRIVER=null ruby script.rb
    const char *driver = getenv("NATIVE_AUDIO_DRIVER");
    int use_null = (driver != NULL && strcmp(driver, "null") == 0);

    ma_engine_config config = ma_engine_config_init();
    config.listenerCount = 1;

    if (use_null) {
        ma_backend backends[] = { ma_backend_null };
        ma_result ctx_result = ma_context_init(backends, 1, NULL, &context);
        if (ctx_result != MA_SUCCESS) {
            rb_raise(rb_eRuntimeError, "Failed to initialize null audio context");
            return Qnil;
        }
        context_initialized = 1;
        using_null_backend = 1;
        config.pContext = &context;
    }

    ma_result result = ma_engine_init(&config, &engine);

    if (result != MA_SUCCESS) {
        if (context_initialized) {
            ma_context_uninit(&context);
            context_initialized = 0;
        }
        rb_raise(rb_eRuntimeError, "Failed to initialize audio engine");
        return Qnil;
    }

    engine_initialized = 1;
    rb_set_end_proc(cleanup_audio, Qnil);

    return Qnil;
}

.load(file) ⇒ Object

Audio.load(path) - Load an audio file, returns clip ID



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'ext/audio/audio.c', line 85

VALUE audio_load(VALUE self, VALUE file)
{
    const char *path = StringValueCStr(file);

    ma_sound *sound = (ma_sound *)malloc(sizeof(ma_sound));
    if (sound == NULL) {
        rb_raise(rb_eRuntimeError, "Failed to allocate memory for sound");
        return Qnil;
    }

    ma_result result = ma_sound_init_from_file(&engine, path, MA_SOUND_FLAG_DECODE, NULL, NULL, sound);
    if (result != MA_SUCCESS) {
        free(sound);
        rb_raise(rb_eRuntimeError, "Failed to load audio file: %s", path);
        return Qnil;
    }

    int id = sound_count;
    sounds[id] = sound;
    sound_count++;

    return rb_int2inum(id);
}

.pause(channel_id) ⇒ Object

Audio.pause(channel) - Pause playback



192
193
194
195
196
197
198
199
200
201
202
203
# File 'ext/audio/audio.c', line 192

VALUE audio_pause(VALUE self, VALUE channel_id)
{
    int channel = NUM2INT(channel_id);

    if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
        return Qnil;
    }

    ma_sound_stop(channels[channel]);

    return Qnil;
}

.play(channel_id, clip) ⇒ Object

Audio.play(channel, clip) - Play a clip on a channel



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'ext/audio/audio.c', line 133

VALUE audio_play(VALUE self, VALUE channel_id, VALUE clip)
{
    int channel = NUM2INT(channel_id);
    int clip_id = NUM2INT(clip);

    if (clip_id < 0 || clip_id >= sound_count || sounds[clip_id] == NULL) {
        rb_raise(rb_eArgError, "Invalid clip ID: %d", clip_id);
        return Qnil;
    }

    if (channel < 0 || channel >= MAX_CHANNELS) {
        rb_raise(rb_eArgError, "Invalid channel ID: %d", channel);
        return Qnil;
    }

    // Clean up existing sound on this channel
    if (channels[channel] != NULL) {
        ma_sound_stop(channels[channel]);
        ma_sound_uninit(channels[channel]);
        free(channels[channel]);
        channels[channel] = NULL;
    }

    // Create a copy of the sound for playback
    ma_sound *playback = (ma_sound *)malloc(sizeof(ma_sound));
    if (playback == NULL) {
        rb_raise(rb_eRuntimeError, "Failed to allocate memory for playback");
        return Qnil;
    }

    ma_result result = ma_sound_init_copy(&engine, sounds[clip_id], 0, NULL, playback);
    if (result != MA_SUCCESS) {
        free(playback);
        rb_raise(rb_eRuntimeError, "Failed to create sound copy for playback");
        return Qnil;
    }

    channels[channel] = playback;
    ma_sound_start(playback);

    return rb_int2inum(channel);
}

.resume(channel_id) ⇒ Object

Audio.resume(channel) - Resume playback



206
207
208
209
210
211
212
213
214
215
216
217
# File 'ext/audio/audio.c', line 206

VALUE audio_resume(VALUE self, VALUE channel_id)
{
    int channel = NUM2INT(channel_id);

    if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
        return Qnil;
    }

    ma_sound_start(channels[channel]);

    return Qnil;
}

.set_pitch(channel_id, pitch) ⇒ Object

Audio.set_pitch(channel, pitch) - Set pitch (1.0 = normal)



240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'ext/audio/audio.c', line 240

VALUE audio_set_pitch(VALUE self, VALUE channel_id, VALUE pitch)
{
    int channel = NUM2INT(channel_id);
    float p = (float)NUM2DBL(pitch);

    if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
        return Qnil;
    }

    ma_sound_set_pitch(channels[channel], p);

    return Qnil;
}

.set_pos(channel_id, angle, distance) ⇒ Object

distance: 0=close, 255=far



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'ext/audio/audio.c', line 257

VALUE audio_set_pos(VALUE self, VALUE channel_id, VALUE angle, VALUE distance)
{
    int channel = NUM2INT(channel_id);
    int ang = NUM2INT(angle);
    int dist = NUM2INT(distance);

    if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
        return Qnil;
    }

    // Convert polar to cartesian
    float rad = ang * (MA_PI / 180.0f);
    float normalized_dist = dist / 255.0f;
    float x = normalized_dist * sinf(rad);
    float z = -normalized_dist * cosf(rad);

    ma_sound_set_position(channels[channel], x, 0.0f, z);

    return Qnil;
}

.set_volume(channel_id, volume) ⇒ Object

Audio.set_volume(channel, volume) - Set volume (0-128)



224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'ext/audio/audio.c', line 224

VALUE audio_set_volume(VALUE self, VALUE channel_id, VALUE volume)
{
    int channel = NUM2INT(channel_id);
    int vol = NUM2INT(volume);

    if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
        return Qnil;
    }

    float normalized_volume = vol / 128.0f;
    ma_sound_set_volume(channels[channel], normalized_volume);

    return Qnil;
}

.stop(channel_id) ⇒ Object

Audio.stop(channel) - Stop playback and rewind



177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'ext/audio/audio.c', line 177

VALUE audio_stop(VALUE self, VALUE channel_id)
{
    int channel = NUM2INT(channel_id);

    if (channel < 0 || channel >= MAX_CHANNELS || channels[channel] == NULL) {
        return Qnil;
    }

    ma_sound_stop(channels[channel]);
    ma_sound_seek_to_pcm_frame(channels[channel], 0);

    return Qnil;
}