1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
/* study_audio.c — Native helpers for audio analysis via raylib's Wave API.
*
* Two functions:
* 1. load_wave_samples(path) → [sample_rate, FloatArray] (small files only)
* 2. scan_silence(path, threshold, min_duration) → [[start,end],...] (normalized)
*
* scan_silence is the "imperative shell" for the pure-Ruby Core — it runs the
* peak-scanning loop in C (avoids building a Ruby array of millions of floats)
* and returns only the silence region pairs.
*/
#include <mruby.h>
#include <mruby/array.h>
#include <mruby/string.h>
#include "raylib.h"
/* ------------------------------------------------------------------ */
/* load_wave_samples — for testing / small files only */
/* ------------------------------------------------------------------ */
static mrb_value
sa_load_wave_samples(mrb_state *mrb, mrb_value self)
{
const char *path;
mrb_get_args(mrb, "z", &path);
Wave wave = LoadWave(path);
if (wave.data == NULL || wave.frameCount == 0) {
return mrb_nil_value();
}
WaveFormat(&wave, wave.sampleRate, 32, 1);
float *samples = (float *)wave.data;
unsigned int frameCount = wave.frameCount;
int sampleRate = (int)wave.sampleRate;
mrb_value ary = mrb_ary_new_capa(mrb, frameCount);
for (unsigned int i = 0; i < frameCount; i++) {
mrb_ary_push(mrb, ary, mrb_float_value(mrb, (double)samples[i]));
}
UnloadWave(wave);
mrb_value result = mrb_ary_new_capa(mrb, 2);
mrb_ary_push(mrb, result, mrb_fixnum_value(sampleRate));
mrb_ary_push(mrb, result, ary);
return result;
}
/* ------------------------------------------------------------------ */
/* scan_silence — C-level peak scan, returns normalized region pairs */
/* ------------------------------------------------------------------ */
#define MAX_REGIONS 4096
static mrb_value
sa_scan_silence(mrb_state *mrb, mrb_value self)
{
const char *path;
mrb_float threshold, min_duration;
mrb_get_args(mrb, "zff", &path, &threshold, &min_duration);
Wave wave = LoadWave(path);
if (wave.data == NULL || wave.frameCount == 0) {
return mrb_ary_new(mrb);
}
WaveFormat(&wave, wave.sampleRate, 32, 1);
float *samples = (float *)wave.data;
unsigned int totalFrames = wave.frameCount;
float sampleRate = (float)wave.sampleRate;
/* Chunk size: ~10ms */
int chunkSize = (int)(sampleRate * 0.01f);
if (chunkSize < 1) chunkSize = 1;
float minFrames = min_duration * sampleRate;
mrb_value regions = mrb_ary_new_capa(mrb, 64);
int inSilence = 0;
unsigned int silenceStart = 0;
int regionCount = 0;
unsigned int i;
for (i = 0; i < totalFrames; i += chunkSize)
{
unsigned int end = i + chunkSize;
if (end > totalFrames) end = totalFrames;
/* Find peak amplitude in this chunk */
float peak = 0.0f;
unsigned int j;
for (j = i; j < end; j++) {
float v = samples[j];
if (v < 0.0f) v = -v;
if (v > peak) peak = v;
}
if (peak < threshold) {
if (!inSilence) { silenceStart = i; inSilence = 1; }
} else {
if (inSilence) {
unsigned int len = i - silenceStart;
if ((float)len >= minFrames && regionCount < MAX_REGIONS) {
mrb_value pair = mrb_ary_new_capa(mrb, 2);
mrb_ary_push(mrb, pair,
mrb_float_value(mrb, (double)silenceStart / (double)totalFrames));
mrb_ary_push(mrb, pair,
mrb_float_value(mrb, (double)i / (double)totalFrames));
mrb_ary_push(mrb, regions, pair);
regionCount++;
}
inSilence = 0;
}
}
}
/* Close trailing silence */
if (inSilence) {
unsigned int len = totalFrames - silenceStart;
if ((float)len >= minFrames && regionCount < MAX_REGIONS) {
mrb_value pair = mrb_ary_new_capa(mrb, 2);
mrb_ary_push(mrb, pair,
mrb_float_value(mrb, (double)silenceStart / (double)totalFrames));
mrb_ary_push(mrb, pair, mrb_float_value(mrb, 1.0));
mrb_ary_push(mrb, regions, pair);
}
}
UnloadWave(wave);
return regions;
}
/* ------------------------------------------------------------------ */
/* gem init */
/* ------------------------------------------------------------------ */
void
mrb_study_audio_gem_init(mrb_state *mrb)
{
struct RClass *mod = mrb_define_module(mrb, "StudyAudio");
mrb_define_module_function(mrb, mod, "load_wave_samples",
sa_load_wave_samples, MRB_ARGS_REQ(1));
mrb_define_module_function(mrb, mod, "scan_silence",
sa_scan_silence, MRB_ARGS_REQ(3));
}
void
mrb_study_audio_gem_final(mrb_state *mrb)
{
/* nothing */
}
|