ruby-audio 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ext/rubyaudio_ext/extconf.rb +2 -2
- data/ext/rubyaudio_ext/ra_buffer.c +23 -0
- data/ext/rubyaudio_ext/ra_buffer.h +1 -0
- data/ext/rubyaudio_ext/ra_sound.c +136 -239
- data/ext/rubyaudio_ext/ra_sound.h +1 -0
- data/lib/ruby-audio/buffer.rb +0 -8
- data/lib/ruby-audio/sound_info.rb +18 -1
- data/ruby-audio.gemspec +1 -1
- data/spec/buffer_spec.rb +6 -1
- data/spec/sound_info_spec.rb +6 -0
- data/spec/sound_spec.rb +64 -26
- data/spec/spec_helper.rb +14 -1
- metadata +4 -9
@@ -12,8 +12,8 @@ else
|
|
12
12
|
sndfile_lib = 'sndfile'
|
13
13
|
end
|
14
14
|
|
15
|
-
INCLUDE_DIRS = ['/opt/local/include', '/usr/local/include', 'C:/Program Files/Mega-Nerd/libsndfile/include', 'C:/Program Files
|
16
|
-
LIB_DIRS = ['/opt/local/lib', '/usr/local/lib', 'C:/Program Files/Mega-Nerd/libsndfile', 'C:/Program Files
|
15
|
+
INCLUDE_DIRS = ['/opt/local/include', '/usr/local/include', 'C:/Program Files (x86)/Mega-Nerd/libsndfile/include', 'C:/Program Files/Mega-Nerd/libsndfile/include']
|
16
|
+
LIB_DIRS = ['/opt/local/lib', '/usr/local/lib', 'C:/Program Files (x86)/Mega-Nerd/libsndfile/bin', 'C:/Program Files/Mega-Nerd/libsndfile/bin']
|
17
17
|
|
18
18
|
# libsndfile requirements
|
19
19
|
find_header 'sndfile.h', *INCLUDE_DIRS
|
@@ -15,6 +15,7 @@ extern VALUE eRubyAudioError;
|
|
15
15
|
void Init_ra_buffer() {
|
16
16
|
VALUE mRubyAudio = rb_define_module("RubyAudio");
|
17
17
|
VALUE cRABuffer = rb_define_class_under(mRubyAudio, "CBuffer", rb_cObject);
|
18
|
+
rb_include_module(cRABuffer, rb_mEnumerable);
|
18
19
|
rb_define_alloc_func(cRABuffer, ra_buffer_allocate);
|
19
20
|
rb_define_method(cRABuffer, "initialize", ra_buffer_init, -1);
|
20
21
|
rb_define_method(cRABuffer, "initialize_copy", ra_buffer_init_copy, 1);
|
@@ -23,6 +24,7 @@ void Init_ra_buffer() {
|
|
23
24
|
rb_define_method(cRABuffer, "real_size", ra_buffer_real_size, 0);
|
24
25
|
rb_define_method(cRABuffer, "real_size=", ra_buffer_real_size_set, 1);
|
25
26
|
rb_define_method(cRABuffer, "type", ra_buffer_type, 0);
|
27
|
+
rb_define_method(cRABuffer, "each", ra_buffer_each, 0);
|
26
28
|
rb_define_method(cRABuffer, "[]", ra_buffer_aref, 1);
|
27
29
|
rb_define_method(cRABuffer, "[]=", ra_buffer_aset, 2);
|
28
30
|
|
@@ -210,6 +212,27 @@ static VALUE ra_buffer_type(VALUE self) {
|
|
210
212
|
}
|
211
213
|
}
|
212
214
|
|
215
|
+
/*
|
216
|
+
* call-seq:
|
217
|
+
* buf.each {|frame| block } => buf
|
218
|
+
* buf.each => anEnumerator
|
219
|
+
*
|
220
|
+
* Iterates through each frame in the buffer. Each frame is either a number or
|
221
|
+
* an array of numbers if there are multiple channels.
|
222
|
+
*/
|
223
|
+
static VALUE ra_buffer_each(VALUE self) {
|
224
|
+
RA_BUFFER *buf;
|
225
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
226
|
+
|
227
|
+
RETURN_ENUMERATOR(self, 0, 0);
|
228
|
+
|
229
|
+
long i;
|
230
|
+
for(i = 0; i < buf->real_size; i++) {
|
231
|
+
rb_yield(ra_buffer_aref(self, LONG2FIX(i)));
|
232
|
+
}
|
233
|
+
return self;
|
234
|
+
}
|
235
|
+
|
213
236
|
/*
|
214
237
|
* call-seq:
|
215
238
|
* buf[integer] => frame
|
@@ -32,6 +32,7 @@ static VALUE ra_buffer_size(VALUE self);
|
|
32
32
|
static VALUE ra_buffer_real_size(VALUE self);
|
33
33
|
static VALUE ra_buffer_real_size_set(VALUE self, VALUE new_real_size);
|
34
34
|
static VALUE ra_buffer_type(VALUE self);
|
35
|
+
static VALUE ra_buffer_each(VALUE self);
|
35
36
|
static VALUE ra_buffer_aref(VALUE self, VALUE index);
|
36
37
|
static VALUE ra_buffer_index_get(RA_BUFFER *buf, long i);
|
37
38
|
static VALUE ra_buffer_aset(VALUE self, VALUE index, VALUE val);
|
@@ -1,6 +1,11 @@
|
|
1
1
|
#include "ra_sound.h"
|
2
2
|
|
3
3
|
extern VALUE eRubyAudioError;
|
4
|
+
ID id_size;
|
5
|
+
ID id_seek;
|
6
|
+
ID id_read;
|
7
|
+
ID id_write;
|
8
|
+
ID id_tell;
|
4
9
|
|
5
10
|
/*
|
6
11
|
* Class <code>CSound</code> is a very light wrapper around the
|
@@ -19,6 +24,13 @@ void Init_ra_sound() {
|
|
19
24
|
rb_define_method(cRASound, "<<", ra_sound_addbuf, 1);
|
20
25
|
rb_define_method(cRASound, "close", ra_sound_close, 0);
|
21
26
|
rb_define_method(cRASound, "closed?", ra_sound_closed, 0);
|
27
|
+
|
28
|
+
// Get refs to commonly used symbols and ids
|
29
|
+
id_size = rb_intern("size");
|
30
|
+
id_seek = rb_intern("seek");
|
31
|
+
id_read = rb_intern("read");
|
32
|
+
id_write = rb_intern("write");
|
33
|
+
id_tell = rb_intern("tell");
|
22
34
|
}
|
23
35
|
|
24
36
|
static VALUE ra_sound_allocate(VALUE klass) {
|
@@ -31,6 +43,7 @@ static VALUE ra_sound_allocate(VALUE klass) {
|
|
31
43
|
static void ra_sound_mark(RA_SOUND *snd) {
|
32
44
|
if(snd) {
|
33
45
|
rb_gc_mark(snd->info);
|
46
|
+
if(snd->vio_source) rb_gc_mark(snd->vio_source);
|
34
47
|
}
|
35
48
|
}
|
36
49
|
|
@@ -56,15 +69,56 @@ static VALUE ra_sound_s_open(int argc, VALUE *argv, VALUE klass) {
|
|
56
69
|
return rb_ensure(rb_yield, obj, ra_sound_close_safe, obj);
|
57
70
|
}
|
58
71
|
|
72
|
+
static sf_count_t ra_vir_size(void *user_data) {
|
73
|
+
VALUE io = (VALUE)user_data;
|
74
|
+
return NUM2OFFT(rb_funcall(io, id_size, 0));
|
75
|
+
}
|
76
|
+
|
77
|
+
static sf_count_t ra_vir_seek(sf_count_t offset, int whence, void *user_data) {
|
78
|
+
VALUE io = (VALUE)user_data;
|
79
|
+
rb_funcall(io, id_seek, 2, OFFT2NUM(offset), INT2FIX(whence));
|
80
|
+
return NUM2OFFT(rb_funcall(io, id_tell, 0));
|
81
|
+
}
|
82
|
+
|
83
|
+
static sf_count_t ra_vir_read(void *ptr, sf_count_t count, void *user_data) {
|
84
|
+
VALUE io = (VALUE)user_data;
|
85
|
+
if(count <= 0) return 0;
|
86
|
+
|
87
|
+
// It would be nice if we could create a fake buffer string with ptr as the target
|
88
|
+
VALUE read = rb_funcall(io, id_read, 1, OFFT2NUM(count));
|
89
|
+
sf_count_t len = RSTRING_LEN(read);
|
90
|
+
memcpy(ptr, RSTRING_PTR(read), RSTRING_LEN(read));
|
91
|
+
return len;
|
92
|
+
}
|
93
|
+
|
94
|
+
static sf_count_t ra_vir_write(const void *ptr, sf_count_t count, void *user_data) {
|
95
|
+
VALUE io = (VALUE)user_data;
|
96
|
+
if(count <= 0) return 0;
|
97
|
+
|
98
|
+
// It would be nice if we could create a fake string with ptr as the source
|
99
|
+
VALUE str = rb_str_new(ptr, count);
|
100
|
+
VALUE wrote = rb_funcall(io, id_write, 1, str);
|
101
|
+
return NUM2OFFT(wrote);
|
102
|
+
}
|
103
|
+
|
104
|
+
static sf_count_t ra_vir_tell(void *user_data) {
|
105
|
+
VALUE io = (VALUE)user_data;
|
106
|
+
return NUM2OFFT(rb_funcall(io, id_tell, 0));
|
107
|
+
}
|
108
|
+
|
59
109
|
/*
|
60
110
|
* call-seq:
|
61
111
|
* CSound.new(path, mode, info) => snd
|
112
|
+
* CSound.new(io, mode, info) => snd
|
62
113
|
*
|
63
114
|
* Returns a new <code>CSound</code> object for the audio file at the given path
|
64
|
-
* with the given mode. Valid modes
|
65
|
-
* <code>"rw"</code>.
|
115
|
+
* or using the given fixed-length IO-like object with the given mode. Valid modes
|
116
|
+
* are <code>"r"</code>, <code>"w"</code>, or <code>"rw"</code>.
|
117
|
+
* <code>StringIO</code> is the only valid IO-like object in the standard library,
|
118
|
+
* although any object that implements <code>size</code>, <code>seek</code>,
|
119
|
+
* <code>read</code>, <code>write</code>, and <code>tell</code> will work.
|
66
120
|
*/
|
67
|
-
static VALUE ra_sound_init(VALUE self, VALUE
|
121
|
+
static VALUE ra_sound_init(VALUE self, VALUE source, VALUE mode, VALUE info) {
|
68
122
|
RA_SOUND *snd;
|
69
123
|
Data_Get_Struct(self, RA_SOUND, snd);
|
70
124
|
|
@@ -79,10 +133,25 @@ static VALUE ra_sound_init(VALUE self, VALUE path, VALUE mode, VALUE info) {
|
|
79
133
|
snd->info = info;
|
80
134
|
|
81
135
|
// Open sound file
|
82
|
-
const char *p = StringValueCStr(path);
|
83
136
|
SF_INFO *sf_info;
|
84
137
|
Data_Get_Struct(info, SF_INFO, sf_info);
|
85
|
-
|
138
|
+
if(TYPE(source) == T_STRING) {
|
139
|
+
// Open sound file at the path
|
140
|
+
const char *p = StringValueCStr(source);
|
141
|
+
snd->snd = sf_open(p, snd->mode, sf_info);
|
142
|
+
} else {
|
143
|
+
// Check if the source implements the right methods
|
144
|
+
if(!rb_respond_to(source, id_size)) rb_raise(eRubyAudioError, "source does not implement size");
|
145
|
+
if(!rb_respond_to(source, id_seek)) rb_raise(eRubyAudioError, "source does not implement seek");
|
146
|
+
if(!rb_respond_to(source, id_read)) rb_raise(eRubyAudioError, "source does not implement read");
|
147
|
+
if(!rb_respond_to(source, id_write)) rb_raise(eRubyAudioError, "source does not implement write");
|
148
|
+
if(!rb_respond_to(source, id_tell)) rb_raise(eRubyAudioError, "source does not implement tell");
|
149
|
+
|
150
|
+
// Open sound using the virtual IO API
|
151
|
+
snd->vio_source = source;
|
152
|
+
SF_VIRTUAL_IO vir_io = {ra_vir_size, ra_vir_seek, ra_vir_read, ra_vir_write, ra_vir_tell};
|
153
|
+
snd->snd = sf_open_virtual(&vir_io, snd->mode, sf_info, (void*)source);
|
154
|
+
}
|
86
155
|
if(snd->snd == NULL) rb_raise(eRubyAudioError, sf_strerror(snd->snd));
|
87
156
|
snd->closed = 0;
|
88
157
|
|
@@ -127,241 +196,69 @@ static VALUE ra_sound_seek(VALUE self, VALUE frames, VALUE whence) {
|
|
127
196
|
return INT2FIX(0);
|
128
197
|
}
|
129
198
|
|
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
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
read
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
static void ra_sound_read_int(RA_SOUND *snd, RA_BUFFER *buf, sf_count_t frames) {
|
190
|
-
static int temp[1024];
|
191
|
-
int temp_len = 1024;
|
192
|
-
int *data = (int*)buf->data;
|
193
|
-
int mix_sum;
|
194
|
-
|
195
|
-
// Get info struct
|
196
|
-
SF_INFO *info;
|
197
|
-
Data_Get_Struct(snd->info, SF_INFO, info);
|
198
|
-
|
199
|
-
// Up/Downmix based on channel matching
|
200
|
-
sf_count_t read = 0, r, amount;
|
201
|
-
int i, k;
|
202
|
-
if(buf->channels == info->channels) { // Simply read data without mix
|
203
|
-
read = sf_readf_int(snd->snd, data, frames);
|
204
|
-
} else if(buf->channels == 1) { // Downmix to mono
|
205
|
-
sf_count_t max = temp_len / info->channels;
|
206
|
-
int channels;
|
207
|
-
|
208
|
-
while(read < frames) {
|
209
|
-
// Calculate # of frames to read
|
210
|
-
amount = frames - read;
|
211
|
-
if(amount > max) amount = max;
|
212
|
-
|
213
|
-
r = sf_readf_int(snd->snd, temp, amount);
|
214
|
-
if(r == 0) break;
|
215
|
-
|
216
|
-
// Mix channels together by averaging all channels and store to buffer
|
217
|
-
for(i = 0; i < r; i++) {
|
218
|
-
mix_sum = 0;
|
219
|
-
for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k];
|
220
|
-
data[read] = mix_sum/info->channels;
|
221
|
-
read++;
|
222
|
-
}
|
223
|
-
}
|
224
|
-
} else if(info->channels == 1) { // Upmix from mono by copying channel
|
225
|
-
while(read < frames) {
|
226
|
-
// Calculate # of frames to read
|
227
|
-
amount = frames - read;
|
228
|
-
if(amount > temp_len) amount = temp_len;
|
229
|
-
|
230
|
-
r = sf_readf_int(snd->snd, temp, amount);
|
231
|
-
if(r == 0) break;
|
232
|
-
|
233
|
-
// Write every frame channel times to the buffer
|
234
|
-
for(i = 0; i < r; i++) {
|
235
|
-
for(k = 0; k < buf->channels; k++) {
|
236
|
-
data[read * buf->channels + k] = temp[i];
|
237
|
-
}
|
238
|
-
read++;
|
239
|
-
}
|
240
|
-
}
|
241
|
-
} else {
|
242
|
-
rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels);
|
243
|
-
}
|
244
|
-
|
245
|
-
buf->real_size = read;
|
246
|
-
}
|
247
|
-
|
248
|
-
static void ra_sound_read_float(RA_SOUND *snd, RA_BUFFER *buf, sf_count_t frames) {
|
249
|
-
static float temp[1024];
|
250
|
-
int temp_len = 1024;
|
251
|
-
float *data = (float*)buf->data;
|
252
|
-
float mix_sum;
|
253
|
-
|
254
|
-
// Get info struct
|
255
|
-
SF_INFO *info;
|
256
|
-
Data_Get_Struct(snd->info, SF_INFO, info);
|
257
|
-
|
258
|
-
// Up/Downmix based on channel matching
|
259
|
-
sf_count_t read = 0, r, amount;
|
260
|
-
int i, k;
|
261
|
-
if(buf->channels == info->channels) { // Simply read data without mix
|
262
|
-
read = sf_readf_float(snd->snd, data, frames);
|
263
|
-
} else if(buf->channels == 1) { // Downmix to mono
|
264
|
-
sf_count_t max = temp_len / info->channels;
|
265
|
-
int channels;
|
266
|
-
|
267
|
-
while(read < frames) {
|
268
|
-
// Calculate # of frames to read
|
269
|
-
amount = frames - read;
|
270
|
-
if(amount > max) amount = max;
|
271
|
-
|
272
|
-
r = sf_readf_float(snd->snd, temp, amount);
|
273
|
-
if(r == 0) break;
|
274
|
-
|
275
|
-
// Mix channels together by averaging all channels and store to buffer
|
276
|
-
for(i = 0; i < r; i++) {
|
277
|
-
mix_sum = 0;
|
278
|
-
for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k];
|
279
|
-
data[read] = mix_sum/info->channels;
|
280
|
-
read++;
|
281
|
-
}
|
282
|
-
}
|
283
|
-
} else if(info->channels == 1) { // Upmix from mono by copying channel
|
284
|
-
while(read < frames) {
|
285
|
-
// Calculate # of frames to read
|
286
|
-
amount = frames - read;
|
287
|
-
if(amount > temp_len) amount = temp_len;
|
288
|
-
|
289
|
-
r = sf_readf_float(snd->snd, temp, amount);
|
290
|
-
if(r == 0) break;
|
291
|
-
|
292
|
-
// Write every frame channel times to the buffer
|
293
|
-
for(i = 0; i < r; i++) {
|
294
|
-
for(k = 0; k < buf->channels; k++) {
|
295
|
-
data[read * buf->channels + k] = temp[i];
|
296
|
-
}
|
297
|
-
read++;
|
298
|
-
}
|
299
|
-
}
|
300
|
-
} else {
|
301
|
-
rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels);
|
302
|
-
}
|
303
|
-
|
304
|
-
buf->real_size = read;
|
305
|
-
}
|
306
|
-
|
307
|
-
static void ra_sound_read_double(RA_SOUND *snd, RA_BUFFER *buf, sf_count_t frames) {
|
308
|
-
static double temp[1024];
|
309
|
-
int temp_len = 1024;
|
310
|
-
double *data = (double*)buf->data;
|
311
|
-
double mix_sum;
|
312
|
-
|
313
|
-
// Get info struct
|
314
|
-
SF_INFO *info;
|
315
|
-
Data_Get_Struct(snd->info, SF_INFO, info);
|
316
|
-
|
317
|
-
// Up/Downmix based on channel matching
|
318
|
-
sf_count_t read = 0, r, amount;
|
319
|
-
int i, k;
|
320
|
-
if(buf->channels == info->channels) { // Simply read data without mix
|
321
|
-
read = sf_readf_double(snd->snd, data, frames);
|
322
|
-
} else if(buf->channels == 1) { // Downmix to mono
|
323
|
-
sf_count_t max = temp_len / info->channels;
|
324
|
-
int channels;
|
325
|
-
|
326
|
-
while(read < frames) {
|
327
|
-
// Calculate # of frames to read
|
328
|
-
amount = frames - read;
|
329
|
-
if(amount > max) amount = max;
|
330
|
-
|
331
|
-
r = sf_readf_double(snd->snd, temp, amount);
|
332
|
-
if(r == 0) break;
|
333
|
-
|
334
|
-
// Mix channels together by averaging all channels and store to buffer
|
335
|
-
for(i = 0; i < r; i++) {
|
336
|
-
mix_sum = 0;
|
337
|
-
for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k];
|
338
|
-
data[read] = mix_sum/info->channels;
|
339
|
-
read++;
|
340
|
-
}
|
341
|
-
}
|
342
|
-
} else if(info->channels == 1) { // Upmix from mono by copying channel
|
343
|
-
while(read < frames) {
|
344
|
-
// Calculate # of frames to read
|
345
|
-
amount = frames - read;
|
346
|
-
if(amount > temp_len) amount = temp_len;
|
347
|
-
|
348
|
-
r = sf_readf_double(snd->snd, temp, amount);
|
349
|
-
if(r == 0) break;
|
350
|
-
|
351
|
-
// Write every frame channel times to the buffer
|
352
|
-
for(i = 0; i < r; i++) {
|
353
|
-
for(k = 0; k < buf->channels; k++) {
|
354
|
-
data[read * buf->channels + k] = temp[i];
|
355
|
-
}
|
356
|
-
read++;
|
357
|
-
}
|
358
|
-
}
|
359
|
-
} else {
|
360
|
-
rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels);
|
361
|
-
}
|
362
|
-
|
363
|
-
buf->real_size = read;
|
199
|
+
#define DEFINE_RA_SOUND_READ_TYPE(itype) \
|
200
|
+
static void ra_sound_read_##itype(RA_SOUND *snd, RA_BUFFER *buf, sf_count_t frames) { \
|
201
|
+
static itype temp[1024]; \
|
202
|
+
int temp_len = 1024; \
|
203
|
+
itype *data = (itype*)buf->data; \
|
204
|
+
itype mix_sum; \
|
205
|
+
\
|
206
|
+
/* Get info struct */ \
|
207
|
+
SF_INFO *info; \
|
208
|
+
Data_Get_Struct(snd->info, SF_INFO, info); \
|
209
|
+
\
|
210
|
+
/* Up/Downmix based on channel matching */ \
|
211
|
+
sf_count_t read = 0, r, amount; \
|
212
|
+
int i, k; \
|
213
|
+
if(buf->channels == info->channels) { /* Simply read data without mix */ \
|
214
|
+
read = sf_readf_##itype(snd->snd, data, frames); \
|
215
|
+
} else if(buf->channels == 1) { /* Downmix to mono */ \
|
216
|
+
sf_count_t max = temp_len / info->channels; \
|
217
|
+
int channels; \
|
218
|
+
\
|
219
|
+
while(read < frames) { \
|
220
|
+
/* Calculate # of frames to read */ \
|
221
|
+
amount = frames - read; \
|
222
|
+
if(amount > max) amount = max; \
|
223
|
+
\
|
224
|
+
r = sf_readf_##itype(snd->snd, temp, amount); \
|
225
|
+
if(r == 0) break; \
|
226
|
+
\
|
227
|
+
/* Mix channels together by averaging all channels and store to buffer */ \
|
228
|
+
for(i = 0; i < r; i++) { \
|
229
|
+
mix_sum = 0; \
|
230
|
+
for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k]; \
|
231
|
+
data[read] = mix_sum/info->channels; \
|
232
|
+
read++; \
|
233
|
+
} \
|
234
|
+
} \
|
235
|
+
} else if(info->channels == 1) { /* Upmix from mono by copying channel */ \
|
236
|
+
while(read < frames) { \
|
237
|
+
/* Calculate # of frames to read */ \
|
238
|
+
amount = frames - read; \
|
239
|
+
if(amount > temp_len) amount = temp_len; \
|
240
|
+
\
|
241
|
+
r = sf_readf_##itype(snd->snd, temp, amount); \
|
242
|
+
if(r == 0) break; \
|
243
|
+
\
|
244
|
+
/* Write every frame channel times to the buffer */ \
|
245
|
+
for(i = 0; i < r; i++) { \
|
246
|
+
for(k = 0; k < buf->channels; k++) { \
|
247
|
+
data[read * buf->channels + k] = temp[i]; \
|
248
|
+
} \
|
249
|
+
read++; \
|
250
|
+
} \
|
251
|
+
} \
|
252
|
+
} else { \
|
253
|
+
rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels); \
|
254
|
+
} \
|
255
|
+
\
|
256
|
+
buf->real_size = read; \
|
364
257
|
}
|
258
|
+
DEFINE_RA_SOUND_READ_TYPE(short);
|
259
|
+
DEFINE_RA_SOUND_READ_TYPE(int);
|
260
|
+
DEFINE_RA_SOUND_READ_TYPE(float);
|
261
|
+
DEFINE_RA_SOUND_READ_TYPE(double);
|
365
262
|
|
366
263
|
/*
|
367
264
|
* call-seq:
|
data/lib/ruby-audio/buffer.rb
CHANGED
@@ -10,14 +10,6 @@ module RubyAudio
|
|
10
10
|
# buf = RubyAudio::Buffer.float(1000)
|
11
11
|
# buf = RubyAudio::Buffer.new("float", 1000, 1)
|
12
12
|
class Buffer < CBuffer
|
13
|
-
include Enumerable
|
14
|
-
|
15
|
-
def each
|
16
|
-
self.size.times do |i|
|
17
|
-
yield self[i]
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
13
|
[:short, :int, :float, :double].each do |type|
|
22
14
|
eval "def self.#{type}(frames, channels=1); self.new(:#{type}, frames, channels); end"
|
23
15
|
end
|
@@ -24,7 +24,7 @@ module RubyAudio
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
# Returns a new <code>SoundInfo</code> object that
|
27
|
+
# Returns a new <code>SoundInfo</code> object that has the same channel
|
28
28
|
# count, sample rate, and format. This is useful in creating a new sound with
|
29
29
|
# the same format as an already existing sound.
|
30
30
|
#
|
@@ -37,16 +37,33 @@ module RubyAudio
|
|
37
37
|
|
38
38
|
alias_method :seekable?, :seekable
|
39
39
|
|
40
|
+
# Returns the main format constant as a string
|
41
|
+
#
|
42
|
+
# Example:
|
43
|
+
# info = RubyAudio::SoundInfo.new :channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
|
44
|
+
# info.main_format
|
45
|
+
# #=> "FORMAT_WAV"
|
40
46
|
def main_format
|
41
47
|
calculate_format if @main_format.nil?
|
42
48
|
@main_format
|
43
49
|
end
|
44
50
|
|
51
|
+
# Returns the sub format constant as a string
|
52
|
+
#
|
53
|
+
# Example:
|
54
|
+
# info = RubyAudio::SoundInfo.new :channels => 1, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
|
55
|
+
# info.sub_format
|
56
|
+
# #=> "FORMAT_PCM_16"
|
45
57
|
def sub_format
|
46
58
|
calculate_format if @sub_format.nil?
|
47
59
|
@sub_format
|
48
60
|
end
|
49
61
|
|
62
|
+
# Returns the length of the audio file in seconds
|
63
|
+
def length
|
64
|
+
frames / samplerate.to_f
|
65
|
+
end
|
66
|
+
|
50
67
|
private
|
51
68
|
def calculate_format
|
52
69
|
RubyAudio.constants.grep(/FORMAT_/).map(&:to_s).each do |f|
|
data/ruby-audio.gemspec
CHANGED
data/spec/buffer_spec.rb
CHANGED
@@ -78,11 +78,16 @@ describe RubyAudio::Buffer do
|
|
78
78
|
end
|
79
79
|
|
80
80
|
it "shouldn't do anything on an empty buffer" do
|
81
|
-
buf = RubyAudio::Buffer.int(
|
81
|
+
buf = RubyAudio::Buffer.int(50, 2)
|
82
82
|
|
83
83
|
buf.each do
|
84
84
|
fail "This shouldn't be executed"
|
85
85
|
end
|
86
86
|
end
|
87
|
+
|
88
|
+
it "should support usage through returned enumerable" do
|
89
|
+
enum = @buf.each
|
90
|
+
enum.any? {|frame| frame[0] == 5}.should == true
|
91
|
+
end
|
87
92
|
end
|
88
93
|
end
|
data/spec/sound_info_spec.rb
CHANGED
@@ -46,4 +46,10 @@ describe RubyAudio::SoundInfo do
|
|
46
46
|
info.main_format.should == "FORMAT_WAVEX"
|
47
47
|
info.sub_format.should == "FORMAT_MS_ADPCM"
|
48
48
|
end
|
49
|
+
|
50
|
+
it "should return the length of an audio file" do
|
51
|
+
RubyAudio::Sound.open(fixture('what.wav')) do |snd|
|
52
|
+
snd.info.length.should == 26413 / 16000.0
|
53
|
+
end
|
54
|
+
end
|
49
55
|
end
|
data/spec/sound_spec.rb
CHANGED
@@ -1,30 +1,31 @@
|
|
1
1
|
require "spec_helper.rb"
|
2
2
|
|
3
3
|
describe RubyAudio::Sound do
|
4
|
-
MONO_TEST_WAV = File.dirname(__FILE__)+'/data/what.wav'
|
5
|
-
STEREO_TEST_WAV = File.dirname(__FILE__)+'/data/what2.wav'
|
6
|
-
TEST_MP3 = File.dirname(__FILE__)+'/data/what.mp3'
|
7
|
-
OUT_WAV = File.dirname(__FILE__)+'/data/temp.wav'
|
8
|
-
|
9
4
|
after :each do
|
10
|
-
File.delete(
|
5
|
+
File.delete(fixture('temp.wav')) if File.exists?(fixture('temp.wav'))
|
11
6
|
end
|
12
7
|
|
13
8
|
it "should open a standard wav without issues" do
|
14
9
|
lambda {
|
15
|
-
RubyAudio::Sound.open(
|
10
|
+
RubyAudio::Sound.open(fixture('what.wav'))
|
11
|
+
}.should_not raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should open an IO conformer without issues" do
|
15
|
+
lambda {
|
16
|
+
RubyAudio::Sound.open(io_fixture('what.wav'))
|
16
17
|
}.should_not raise_error
|
17
18
|
end
|
18
19
|
|
19
20
|
it "should raise an exception if the mode is invalid" do
|
20
21
|
lambda {
|
21
|
-
RubyAudio::Sound.open(
|
22
|
+
RubyAudio::Sound.open(fixture('what.wav'), 'q')
|
22
23
|
}.should raise_error(ArgumentError, 'invalid access mode q')
|
23
24
|
end
|
24
25
|
|
25
26
|
it "should close the sound on block exit" do
|
26
27
|
s = nil
|
27
|
-
RubyAudio::Sound.open(
|
28
|
+
RubyAudio::Sound.open(fixture('what.wav')) do |snd|
|
28
29
|
snd.closed?.should be_false
|
29
30
|
s = snd
|
30
31
|
end
|
@@ -33,18 +34,18 @@ describe RubyAudio::Sound do
|
|
33
34
|
|
34
35
|
it "should raise an exception for an unsupported file" do
|
35
36
|
lambda {
|
36
|
-
RubyAudio::Sound.open(
|
37
|
+
RubyAudio::Sound.open(fixture('what.mp3'))
|
37
38
|
}.should raise_error(RubyAudio::Error, "File contains data in an unknown format.")
|
38
39
|
end
|
39
40
|
|
40
41
|
it "should raise an exception if file does not exist" do
|
41
42
|
lambda {
|
42
|
-
RubyAudio::Sound.open(
|
43
|
+
RubyAudio::Sound.open(fixture('what.mp3')+'.not')
|
43
44
|
}.should raise_error(RubyAudio::Error, "System error : No such file or directory.")
|
44
45
|
end
|
45
46
|
|
46
47
|
it "should have the proper sound info" do
|
47
|
-
RubyAudio::Sound.open(
|
48
|
+
RubyAudio::Sound.open(fixture('what.wav')) do |snd|
|
48
49
|
snd.info.channels.should == 1
|
49
50
|
snd.info.samplerate.should == 16000
|
50
51
|
snd.info.format.should == RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
|
@@ -53,7 +54,17 @@ describe RubyAudio::Sound do
|
|
53
54
|
|
54
55
|
it "should allow seeking" do
|
55
56
|
lambda {
|
56
|
-
RubyAudio::Sound.open(
|
57
|
+
RubyAudio::Sound.open(fixture('what.wav')) do |snd|
|
58
|
+
snd.seek(100)
|
59
|
+
buf = snd.read(:float, 100)
|
60
|
+
buf[0].should > 0
|
61
|
+
end
|
62
|
+
}.should_not raise_error
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should allow seeking in IO conformers" do
|
66
|
+
lambda {
|
67
|
+
RubyAudio::Sound.open(io_fixture('what.wav')) do |snd|
|
57
68
|
snd.seek(100)
|
58
69
|
buf = snd.read(:float, 100)
|
59
70
|
buf[0].should > 0
|
@@ -63,15 +74,24 @@ describe RubyAudio::Sound do
|
|
63
74
|
|
64
75
|
it "should raise exceptions for invalid seeks" do
|
65
76
|
lambda {
|
66
|
-
RubyAudio::Sound.open(
|
77
|
+
RubyAudio::Sound.open(fixture('what.wav')) {|snd| snd.seek(-1)}
|
67
78
|
}.should raise_error(RubyAudio::Error, "invalid seek")
|
68
79
|
lambda {
|
69
|
-
RubyAudio::Sound.open(
|
80
|
+
RubyAudio::Sound.open(fixture('what.wav')) {|snd| snd.seek(1000000)}
|
70
81
|
}.should raise_error(RubyAudio::Error, "invalid seek")
|
71
82
|
end
|
72
83
|
|
73
84
|
it "should allow reading samples from the sound" do
|
74
|
-
RubyAudio::Sound.open(
|
85
|
+
RubyAudio::Sound.open(fixture('what2.wav')) do |snd|
|
86
|
+
buf = snd.read(:float, 1000)
|
87
|
+
buf.size.should == 1000
|
88
|
+
buf.real_size.should == 1000
|
89
|
+
buf[999].length.should == 2
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should allow reading samples from IO conformers" do
|
94
|
+
RubyAudio::Sound.open(io_fixture('what2.wav')) do |snd|
|
75
95
|
buf = snd.read(:float, 1000)
|
76
96
|
buf.size.should == 1000
|
77
97
|
buf.real_size.should == 1000
|
@@ -82,7 +102,7 @@ describe RubyAudio::Sound do
|
|
82
102
|
it "should allow reading into an existing buffer" do
|
83
103
|
buf = RubyAudio::Buffer.float(1000)
|
84
104
|
buf.real_size.should == 0
|
85
|
-
RubyAudio::Sound.open(
|
105
|
+
RubyAudio::Sound.open(fixture('what.wav')) do |snd|
|
86
106
|
snd.read(buf)
|
87
107
|
end
|
88
108
|
buf.real_size.should == 1000
|
@@ -92,7 +112,7 @@ describe RubyAudio::Sound do
|
|
92
112
|
it "should allow reading into an existing buffer partially" do
|
93
113
|
buf = RubyAudio::Buffer.float(1000)
|
94
114
|
buf.real_size.should == 0
|
95
|
-
RubyAudio::Sound.open(
|
115
|
+
RubyAudio::Sound.open(fixture('what.wav')) do |snd|
|
96
116
|
snd.read(buf, 100)
|
97
117
|
end
|
98
118
|
buf.real_size.should == 100
|
@@ -103,7 +123,7 @@ describe RubyAudio::Sound do
|
|
103
123
|
it "should allow downmixing to mono on read" do
|
104
124
|
buf = RubyAudio::Buffer.int(100, 1)
|
105
125
|
buf2 = RubyAudio::Buffer.int(100, 2)
|
106
|
-
RubyAudio::Sound.open(
|
126
|
+
RubyAudio::Sound.open(fixture('what2.wav'), 'r') do |snd|
|
107
127
|
snd.read(buf)
|
108
128
|
snd.seek(0)
|
109
129
|
snd.read(buf2)
|
@@ -116,7 +136,7 @@ describe RubyAudio::Sound do
|
|
116
136
|
it "should allow upmixing from mono on read" do
|
117
137
|
buf = RubyAudio::Buffer.int(100, 1)
|
118
138
|
buf2 = RubyAudio::Buffer.int(100, 2)
|
119
|
-
RubyAudio::Sound.open(
|
139
|
+
RubyAudio::Sound.open(fixture('what2.wav'), 'r') do |snd|
|
120
140
|
snd.read(buf2)
|
121
141
|
snd.seek(0)
|
122
142
|
snd.read(buf)
|
@@ -128,7 +148,7 @@ describe RubyAudio::Sound do
|
|
128
148
|
it "should fail read on unsupported up/downmixing" do
|
129
149
|
buf = RubyAudio::Buffer.float(100, 5)
|
130
150
|
lambda {
|
131
|
-
RubyAudio::Sound.open(
|
151
|
+
RubyAudio::Sound.open(fixture('what2.wav')) do |snd|
|
132
152
|
snd.read(buf)
|
133
153
|
end
|
134
154
|
}.should raise_error(RubyAudio::Error, "unsupported mix from 5 to 2")
|
@@ -138,12 +158,30 @@ describe RubyAudio::Sound do
|
|
138
158
|
in_buf = RubyAudio::Buffer.float(100)
|
139
159
|
out_buf = RubyAudio::Buffer.float(100)
|
140
160
|
out_info = nil
|
141
|
-
RubyAudio::Sound.open(
|
161
|
+
RubyAudio::Sound.open(fixture('what.wav')) do |snd|
|
162
|
+
snd.read(in_buf)
|
163
|
+
out_info = snd.info.clone
|
164
|
+
end
|
165
|
+
|
166
|
+
RubyAudio::Sound.open(fixture('temp.wav'), 'rw', out_info) do |snd|
|
167
|
+
snd.write(in_buf)
|
168
|
+
snd.seek(0)
|
169
|
+
snd.read(out_buf)
|
170
|
+
end
|
171
|
+
|
172
|
+
out_buf[50].should == in_buf[50]
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should allow writing to an IO conformer" do
|
176
|
+
in_buf = RubyAudio::Buffer.float(100)
|
177
|
+
out_buf = RubyAudio::Buffer.float(100)
|
178
|
+
out_info = nil
|
179
|
+
RubyAudio::Sound.open(fixture('what.wav')) do |snd|
|
142
180
|
snd.read(in_buf)
|
143
181
|
out_info = snd.info.clone
|
144
182
|
end
|
145
183
|
|
146
|
-
RubyAudio::Sound.open(
|
184
|
+
RubyAudio::Sound.open(io_fixture, 'rw', out_info) do |snd|
|
147
185
|
snd.write(in_buf)
|
148
186
|
snd.seek(0)
|
149
187
|
snd.read(out_buf)
|
@@ -156,12 +194,12 @@ describe RubyAudio::Sound do
|
|
156
194
|
in_buf = RubyAudio::Buffer.float(100)
|
157
195
|
out_buf = RubyAudio::Buffer.float(100)
|
158
196
|
out_info = nil
|
159
|
-
RubyAudio::Sound.open(
|
197
|
+
RubyAudio::Sound.open(fixture('what.wav')) do |snd|
|
160
198
|
snd.read(in_buf)
|
161
199
|
out_info = snd.info.clone
|
162
200
|
end
|
163
201
|
|
164
|
-
RubyAudio::Sound.open(
|
202
|
+
RubyAudio::Sound.open(fixture('temp.wav'), 'rw', out_info) do |snd|
|
165
203
|
snd << in_buf
|
166
204
|
snd.seek(0)
|
167
205
|
snd.read(out_buf)
|
@@ -174,7 +212,7 @@ describe RubyAudio::Sound do
|
|
174
212
|
buf = RubyAudio::Buffer.float(100, 5)
|
175
213
|
info = RubyAudio::SoundInfo.new :channels => 2, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
|
176
214
|
lambda {
|
177
|
-
RubyAudio::Sound.open(
|
215
|
+
RubyAudio::Sound.open(fixture('temp.wav'), 'w', info) do |snd|
|
178
216
|
snd.write(buf)
|
179
217
|
end
|
180
218
|
}.should raise_error(RubyAudio::Error, "channel count mismatch: 5 vs 2")
|
data/spec/spec_helper.rb
CHANGED
@@ -8,4 +8,17 @@ end
|
|
8
8
|
require 'spec/autorun'
|
9
9
|
|
10
10
|
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
11
|
-
require 'ruby-audio'
|
11
|
+
require 'ruby-audio'
|
12
|
+
|
13
|
+
def fixture file_name
|
14
|
+
File.join(File.dirname(__FILE__), 'data', file_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def io_fixture file_name=nil
|
18
|
+
if file_name
|
19
|
+
path = fixture(file_name)
|
20
|
+
StringIO.new(File.read(path))
|
21
|
+
else
|
22
|
+
StringIO.new
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-audio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 3
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 1
|
8
|
-
-
|
7
|
+
- 6
|
9
8
|
- 0
|
10
|
-
version: 1.
|
9
|
+
version: 1.6.0
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Stephen Augenstein
|
@@ -15,7 +14,7 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2011-
|
17
|
+
date: 2011-11-19 00:00:00 -05:00
|
19
18
|
default_executable:
|
20
19
|
dependencies: []
|
21
20
|
|
@@ -69,27 +68,23 @@ rdoc_options:
|
|
69
68
|
require_paths:
|
70
69
|
- lib
|
71
70
|
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
-
none: false
|
73
71
|
requirements:
|
74
72
|
- - ">="
|
75
73
|
- !ruby/object:Gem::Version
|
76
|
-
hash: 3
|
77
74
|
segments:
|
78
75
|
- 0
|
79
76
|
version: "0"
|
80
77
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
78
|
requirements:
|
83
79
|
- - ">="
|
84
80
|
- !ruby/object:Gem::Version
|
85
|
-
hash: 3
|
86
81
|
segments:
|
87
82
|
- 0
|
88
83
|
version: "0"
|
89
84
|
requirements:
|
90
85
|
- libsndfile (http://www.mega-nerd.com/libsndfile/)
|
91
86
|
rubyforge_project:
|
92
|
-
rubygems_version: 1.3.
|
87
|
+
rubygems_version: 1.3.6
|
93
88
|
signing_key:
|
94
89
|
specification_version: 3
|
95
90
|
summary: libsndfile wrapper for ruby
|