ruby-audio-heroku 1.6.1
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.
- data/LICENSE +340 -0
- data/README.rdoc +38 -0
- data/Rakefile +81 -0
- data/ext/rubyaudio_ext/extconf.rb +32 -0
- data/ext/rubyaudio_ext/ra_buffer.c +342 -0
- data/ext/rubyaudio_ext/ra_buffer.h +41 -0
- data/ext/rubyaudio_ext/ra_sound.c +403 -0
- data/ext/rubyaudio_ext/ra_sound.h +38 -0
- data/ext/rubyaudio_ext/ra_soundinfo.c +165 -0
- data/ext/rubyaudio_ext/ra_soundinfo.h +25 -0
- data/ext/rubyaudio_ext/rubyaudio_ext.c +92 -0
- data/ext/rubyaudio_ext/vendor/libsndfile/include/sndfile.h +666 -0
- data/lib/ruby-audio/buffer.rb +17 -0
- data/lib/ruby-audio/sound.rb +85 -0
- data/lib/ruby-audio/sound_info.rb +83 -0
- data/lib/ruby-audio.rb +10 -0
- data/ruby-audio-heroku.gemspec +22 -0
- data/spec/buffer_spec.rb +107 -0
- data/spec/data/what.mp3 +0 -0
- data/spec/data/what.wav +0 -0
- data/spec/data/what2.wav +0 -0
- data/spec/sound_info_spec.rb +55 -0
- data/spec/sound_spec.rb +220 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +24 -0
- metadata +84 -0
@@ -0,0 +1,403 @@
|
|
1
|
+
#include "ra_sound.h"
|
2
|
+
|
3
|
+
extern VALUE eRubyAudioError;
|
4
|
+
ID id_size;
|
5
|
+
ID id_seek;
|
6
|
+
ID id_read;
|
7
|
+
ID id_write;
|
8
|
+
ID id_tell;
|
9
|
+
|
10
|
+
/*
|
11
|
+
* Class <code>CSound</code> is a very light wrapper around the
|
12
|
+
* <code>SNDFILE</code> struct exposed by libsndfile.
|
13
|
+
*/
|
14
|
+
void Init_ra_sound() {
|
15
|
+
VALUE mRubyAudio = rb_define_module("RubyAudio");
|
16
|
+
VALUE cRASound = rb_define_class_under(mRubyAudio, "CSound", rb_cObject);
|
17
|
+
rb_define_alloc_func(cRASound, ra_sound_allocate);
|
18
|
+
rb_define_singleton_method(cRASound, "open", ra_sound_s_open, -1);
|
19
|
+
rb_define_method(cRASound, "initialize", ra_sound_init, 3);
|
20
|
+
rb_define_method(cRASound, "info", ra_sound_info, 0);
|
21
|
+
rb_define_method(cRASound, "seek", ra_sound_seek, 2);
|
22
|
+
rb_define_method(cRASound, "read", ra_sound_read, 2);
|
23
|
+
rb_define_method(cRASound, "write", ra_sound_write, 1);
|
24
|
+
rb_define_method(cRASound, "<<", ra_sound_addbuf, 1);
|
25
|
+
rb_define_method(cRASound, "close", ra_sound_close, 0);
|
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");
|
34
|
+
}
|
35
|
+
|
36
|
+
static VALUE ra_sound_allocate(VALUE klass) {
|
37
|
+
RA_SOUND *snd = ALLOC(RA_SOUND);
|
38
|
+
memset(snd, 0, sizeof(RA_SOUND));
|
39
|
+
VALUE self = Data_Wrap_Struct(klass, ra_sound_mark, ra_sound_free, snd);
|
40
|
+
return self;
|
41
|
+
}
|
42
|
+
|
43
|
+
static void ra_sound_mark(RA_SOUND *snd) {
|
44
|
+
if(snd) {
|
45
|
+
rb_gc_mark(snd->info);
|
46
|
+
if(snd->vio_source) rb_gc_mark(snd->vio_source);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
static void ra_sound_free(RA_SOUND *snd) {
|
51
|
+
if(!snd->closed && snd->snd != NULL) sf_close(snd->snd);
|
52
|
+
xfree(snd);
|
53
|
+
}
|
54
|
+
|
55
|
+
/*
|
56
|
+
* call-seq:
|
57
|
+
* CSound.open(...) => snd
|
58
|
+
* CSound.open(...) {|snd| block } => obj
|
59
|
+
*
|
60
|
+
* With no associated block, <code>open</code> is a synonym for
|
61
|
+
* <code>CSound.new</code>. If the optional code block is given, it will be
|
62
|
+
* passed <i>snd</i> as an argument, and the CSound object will automatically be
|
63
|
+
* closed when the block terminates. In this instance, <code>CSound.open</code>
|
64
|
+
* returns the value of the block.
|
65
|
+
*/
|
66
|
+
static VALUE ra_sound_s_open(int argc, VALUE *argv, VALUE klass) {
|
67
|
+
VALUE obj = rb_class_new_instance(argc, argv, klass);
|
68
|
+
if(!rb_block_given_p()) return obj;
|
69
|
+
return rb_ensure(rb_yield, obj, ra_sound_close_safe, obj);
|
70
|
+
}
|
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
|
+
|
109
|
+
/*
|
110
|
+
* call-seq:
|
111
|
+
* CSound.new(path, mode, info) => snd
|
112
|
+
* CSound.new(io, mode, info) => snd
|
113
|
+
*
|
114
|
+
* Returns a new <code>CSound</code> object for the audio file at the given path
|
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.
|
120
|
+
*/
|
121
|
+
static VALUE ra_sound_init(VALUE self, VALUE source, VALUE mode, VALUE info) {
|
122
|
+
RA_SOUND *snd;
|
123
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
124
|
+
|
125
|
+
// Get mode
|
126
|
+
const char *m = StringValueCStr(mode);
|
127
|
+
if(strcmp(m, "rw") == 0) snd->mode = SFM_RDWR;
|
128
|
+
else if(strcmp(m, "r") == 0) snd->mode = SFM_READ;
|
129
|
+
else if(strcmp(m, "w") == 0) snd->mode = SFM_WRITE;
|
130
|
+
else rb_raise(rb_eArgError, "invalid access mode %s", m);
|
131
|
+
|
132
|
+
// Set info
|
133
|
+
snd->info = info;
|
134
|
+
|
135
|
+
// Open sound file
|
136
|
+
SF_INFO *sf_info;
|
137
|
+
Data_Get_Struct(info, SF_INFO, sf_info);
|
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
|
+
}
|
155
|
+
if(snd->snd == NULL) rb_raise(eRubyAudioError, sf_strerror(snd->snd));
|
156
|
+
snd->closed = 0;
|
157
|
+
|
158
|
+
return self;
|
159
|
+
}
|
160
|
+
|
161
|
+
/*
|
162
|
+
* call-seq:
|
163
|
+
* snd.info => CSoundInfo
|
164
|
+
*
|
165
|
+
* Returns the info object associated with the sound.
|
166
|
+
*/
|
167
|
+
static VALUE ra_sound_info(VALUE self) {
|
168
|
+
RA_SOUND *snd;
|
169
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
170
|
+
return snd->info;
|
171
|
+
}
|
172
|
+
|
173
|
+
/*
|
174
|
+
* call-seq:
|
175
|
+
* snd.seek(frames, whence) => 0
|
176
|
+
*
|
177
|
+
* Seeks to a given offset <i>anInteger</i> in the sound according to the value
|
178
|
+
* of <i>whence</i>:
|
179
|
+
*
|
180
|
+
* IO::SEEK_CUR | Seeks to _frames_ plus current position
|
181
|
+
* --------------+----------------------------------------------------
|
182
|
+
* IO::SEEK_END | Seeks to _frames_ plus end of stream (you probably
|
183
|
+
* | want a negative value for _frames_)
|
184
|
+
* --------------+----------------------------------------------------
|
185
|
+
* IO::SEEK_SET | Seeks to the absolute location given by _frames_
|
186
|
+
*/
|
187
|
+
static VALUE ra_sound_seek(VALUE self, VALUE frames, VALUE whence) {
|
188
|
+
RA_SOUND *snd;
|
189
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
190
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
191
|
+
|
192
|
+
if(sf_seek(snd->snd, (sf_count_t)NUM2OFFT(frames), FIX2INT(whence)) == -1) {
|
193
|
+
rb_raise(eRubyAudioError, "invalid seek");
|
194
|
+
}
|
195
|
+
|
196
|
+
return INT2FIX(0);
|
197
|
+
}
|
198
|
+
|
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; \
|
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);
|
262
|
+
|
263
|
+
/*
|
264
|
+
* call-seq:
|
265
|
+
* snd.read(buf, frames) => integer
|
266
|
+
*
|
267
|
+
* Tries to read the given number of frames into the buffer and returns the
|
268
|
+
* number of frames actually read.
|
269
|
+
*/
|
270
|
+
static VALUE ra_sound_read(VALUE self, VALUE buf, VALUE frames) {
|
271
|
+
RA_SOUND *snd;
|
272
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
273
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
274
|
+
|
275
|
+
// Get buffer struct
|
276
|
+
RA_BUFFER *b;
|
277
|
+
Data_Get_Struct(buf, RA_BUFFER, b);
|
278
|
+
|
279
|
+
// Double-check frame count against buffer size
|
280
|
+
sf_count_t f = (sf_count_t)NUM2OFFT(frames);
|
281
|
+
if(f < 0 || f > b->size) {
|
282
|
+
rb_raise(eRubyAudioError, "frame count invalid");
|
283
|
+
}
|
284
|
+
|
285
|
+
// Shortcut for 0 frame reads
|
286
|
+
if(f == 0) {
|
287
|
+
b->real_size = 0;
|
288
|
+
return INT2FIX(b->real_size);;
|
289
|
+
}
|
290
|
+
|
291
|
+
// Read based on type
|
292
|
+
switch(b->type) {
|
293
|
+
case RA_BUFFER_TYPE_SHORT:
|
294
|
+
ra_sound_read_short(snd, b, f);
|
295
|
+
break;
|
296
|
+
case RA_BUFFER_TYPE_INT:
|
297
|
+
ra_sound_read_int(snd, b, f);
|
298
|
+
break;
|
299
|
+
case RA_BUFFER_TYPE_FLOAT:
|
300
|
+
ra_sound_read_float(snd, b, f);
|
301
|
+
break;
|
302
|
+
case RA_BUFFER_TYPE_DOUBLE:
|
303
|
+
ra_sound_read_double(snd, b, f);
|
304
|
+
break;
|
305
|
+
}
|
306
|
+
|
307
|
+
// Return read
|
308
|
+
return INT2FIX(b->real_size);
|
309
|
+
}
|
310
|
+
|
311
|
+
/*
|
312
|
+
* call-seq:
|
313
|
+
* snd.write(buf) => integer
|
314
|
+
*
|
315
|
+
* Writes the entire contents of the given buffer to the sound and returns the
|
316
|
+
* number of frames written.
|
317
|
+
*/
|
318
|
+
static VALUE ra_sound_write(VALUE self, VALUE buf) {
|
319
|
+
RA_SOUND *snd;
|
320
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
321
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
322
|
+
|
323
|
+
// Get buffer struct
|
324
|
+
RA_BUFFER *b;
|
325
|
+
Data_Get_Struct(buf, RA_BUFFER, b);
|
326
|
+
|
327
|
+
// Get info struct
|
328
|
+
SF_INFO *info;
|
329
|
+
Data_Get_Struct(snd->info, SF_INFO, info);
|
330
|
+
|
331
|
+
// Check buffer channels matches actual channels
|
332
|
+
if(b->channels != info->channels) {
|
333
|
+
rb_raise(eRubyAudioError, "channel count mismatch: %d vs %d", b->channels, info->channels);
|
334
|
+
}
|
335
|
+
|
336
|
+
// Write data
|
337
|
+
sf_count_t written = 0;
|
338
|
+
switch(b->type) {
|
339
|
+
case RA_BUFFER_TYPE_SHORT:
|
340
|
+
written = sf_writef_short(snd->snd, b->data, b->real_size);
|
341
|
+
break;
|
342
|
+
case RA_BUFFER_TYPE_INT:
|
343
|
+
written = sf_writef_int(snd->snd, b->data, b->real_size);
|
344
|
+
break;
|
345
|
+
case RA_BUFFER_TYPE_FLOAT:
|
346
|
+
written = sf_writef_float(snd->snd, b->data, b->real_size);
|
347
|
+
break;
|
348
|
+
case RA_BUFFER_TYPE_DOUBLE:
|
349
|
+
written = sf_writef_double(snd->snd, b->data, b->real_size);
|
350
|
+
break;
|
351
|
+
}
|
352
|
+
|
353
|
+
return OFFT2NUM(written);
|
354
|
+
}
|
355
|
+
|
356
|
+
/*
|
357
|
+
* call-seq:
|
358
|
+
* snd << buf => snd
|
359
|
+
*
|
360
|
+
* Writes the given buffer to the string.
|
361
|
+
*
|
362
|
+
* snd << buf1 << buf2
|
363
|
+
*/
|
364
|
+
static VALUE ra_sound_addbuf(VALUE self, VALUE buf) {
|
365
|
+
ra_sound_write(self, buf);
|
366
|
+
return self;
|
367
|
+
}
|
368
|
+
|
369
|
+
/*
|
370
|
+
* call-seq:
|
371
|
+
* snd.close => nil
|
372
|
+
*
|
373
|
+
* Closes <i>snd</i> and frees up all memory associated with the sound. The
|
374
|
+
* sound is unavailable for any further data operations; an error is raised if
|
375
|
+
* such an attempt is made. Sounds are automatically closed when they are claimed
|
376
|
+
* by the garbage collector.
|
377
|
+
*/
|
378
|
+
static VALUE ra_sound_close(VALUE self) {
|
379
|
+
RA_SOUND *snd;
|
380
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
381
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
382
|
+
|
383
|
+
sf_close(snd->snd);
|
384
|
+
snd->snd = NULL;
|
385
|
+
snd->closed = 1;
|
386
|
+
return Qnil;
|
387
|
+
}
|
388
|
+
|
389
|
+
static VALUE ra_sound_close_safe(VALUE self) {
|
390
|
+
return rb_rescue(ra_sound_close, self, 0, 0);
|
391
|
+
}
|
392
|
+
|
393
|
+
/*
|
394
|
+
* call-seq:
|
395
|
+
* snd.closed? => true or false
|
396
|
+
*
|
397
|
+
* Whether or not the current sound is closed to further operations.
|
398
|
+
*/
|
399
|
+
static VALUE ra_sound_closed(VALUE self) {
|
400
|
+
RA_SOUND *snd;
|
401
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
402
|
+
return snd->closed ? Qtrue : Qfalse;
|
403
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#ifndef RA_SOUND_H
|
2
|
+
#define RA_SOUND_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <sndfile.h>
|
6
|
+
#include "ra_soundinfo.h"
|
7
|
+
#include "ra_buffer.h"
|
8
|
+
|
9
|
+
typedef struct {
|
10
|
+
SNDFILE *snd;
|
11
|
+
VALUE info;
|
12
|
+
VALUE vio_source;
|
13
|
+
int mode;
|
14
|
+
int closed;
|
15
|
+
} RA_SOUND;
|
16
|
+
|
17
|
+
void Init_ra_sound();
|
18
|
+
|
19
|
+
/*** Initialization and Memory Manangement ***/
|
20
|
+
static VALUE ra_sound_allocate(VALUE klass);
|
21
|
+
static void ra_sound_mark(RA_SOUND *snd);
|
22
|
+
static void ra_sound_free(RA_SOUND *snd);
|
23
|
+
|
24
|
+
/*** Singleton Methods ***/
|
25
|
+
static VALUE ra_sound_s_open(int argc, VALUE *argv, VALUE klass);
|
26
|
+
|
27
|
+
/*** Instance Methods ***/
|
28
|
+
static VALUE ra_sound_init(VALUE self, VALUE path, VALUE mode, VALUE info);
|
29
|
+
static VALUE ra_sound_info(VALUE self);
|
30
|
+
static VALUE ra_sound_seek(VALUE self, VALUE frames, VALUE whence);
|
31
|
+
static VALUE ra_sound_read(VALUE self, VALUE buf, VALUE frames);
|
32
|
+
static VALUE ra_sound_write(VALUE self, VALUE buf);
|
33
|
+
static VALUE ra_sound_addbuf(VALUE self, VALUE buf);
|
34
|
+
static VALUE ra_sound_close(VALUE self);
|
35
|
+
static VALUE ra_sound_close_safe(VALUE self);
|
36
|
+
static VALUE ra_sound_closed(VALUE self);
|
37
|
+
|
38
|
+
#endif // #ifndef RA_SOUND_H
|
@@ -0,0 +1,165 @@
|
|
1
|
+
#include "ra_soundinfo.h"
|
2
|
+
|
3
|
+
extern VALUE eRubyAudioError;
|
4
|
+
|
5
|
+
/*
|
6
|
+
* Class <code>CSoundInfo</code> is a very light wrapper around the
|
7
|
+
* <code>SF_INFO</code> struct exposed by libsndfile. It provides information
|
8
|
+
* about open sound files like format, length, samplerate, channels, and other
|
9
|
+
* things. In addition it can be used to specify the format of new sound files.
|
10
|
+
*/
|
11
|
+
void Init_ra_soundinfo() {
|
12
|
+
VALUE mRubyAudio = rb_define_module("RubyAudio");
|
13
|
+
VALUE cRASoundInfo = rb_define_class_under(mRubyAudio, "CSoundInfo", rb_cObject);
|
14
|
+
rb_define_alloc_func(cRASoundInfo, ra_soundinfo_allocate);
|
15
|
+
rb_define_method(cRASoundInfo, "valid?", ra_soundinfo_valid, 0);
|
16
|
+
rb_define_method(cRASoundInfo, "frames", ra_soundinfo_frames, 0);
|
17
|
+
rb_define_method(cRASoundInfo, "samplerate", ra_soundinfo_samplerate, 0);
|
18
|
+
rb_define_method(cRASoundInfo, "samplerate=", ra_soundinfo_samplerate_set, 1);
|
19
|
+
rb_define_method(cRASoundInfo, "channels", ra_soundinfo_channels, 0);
|
20
|
+
rb_define_method(cRASoundInfo, "channels=", ra_soundinfo_channels_set, 1);
|
21
|
+
rb_define_method(cRASoundInfo, "format", ra_soundinfo_format, 0);
|
22
|
+
rb_define_method(cRASoundInfo, "format=", ra_soundinfo_format_set, 1);
|
23
|
+
rb_define_method(cRASoundInfo, "sections", ra_soundinfo_sections, 0);
|
24
|
+
rb_define_method(cRASoundInfo, "seekable", ra_soundinfo_seekable, 0);
|
25
|
+
}
|
26
|
+
|
27
|
+
static VALUE ra_soundinfo_allocate(VALUE klass) {
|
28
|
+
SF_INFO *info = ALLOC(SF_INFO);
|
29
|
+
memset(info, 0, sizeof(SF_INFO));
|
30
|
+
VALUE self = Data_Wrap_Struct(klass, NULL, ra_soundinfo_free, info);
|
31
|
+
return self;
|
32
|
+
}
|
33
|
+
|
34
|
+
static void ra_soundinfo_free(SF_INFO *info) {
|
35
|
+
xfree(info);
|
36
|
+
}
|
37
|
+
|
38
|
+
/*
|
39
|
+
* call-seq:
|
40
|
+
* info.valid? => true or false
|
41
|
+
*
|
42
|
+
* Calls <code>sf_format_check</code> on the underlying <code>SF_INFO</code>
|
43
|
+
* struct and returns true or false based on validity. Used when creating a new
|
44
|
+
* sound file to check that the format has enough information to properly create
|
45
|
+
* a new sound.
|
46
|
+
*/
|
47
|
+
static VALUE ra_soundinfo_valid(VALUE self) {
|
48
|
+
SF_INFO *info;
|
49
|
+
Data_Get_Struct(self, SF_INFO, info);
|
50
|
+
return sf_format_check(info) ? Qtrue : Qfalse;
|
51
|
+
}
|
52
|
+
|
53
|
+
/*
|
54
|
+
* call-seq:
|
55
|
+
* info.frames => integer
|
56
|
+
*
|
57
|
+
* Returns the number of frames in the associated sound file.
|
58
|
+
*/
|
59
|
+
static VALUE ra_soundinfo_frames(VALUE self) {
|
60
|
+
SF_INFO *info;
|
61
|
+
Data_Get_Struct(self, SF_INFO, info);
|
62
|
+
return OFFT2NUM(info->frames);
|
63
|
+
}
|
64
|
+
|
65
|
+
/*
|
66
|
+
* call-seq:
|
67
|
+
* info.samplerate => integer
|
68
|
+
*
|
69
|
+
* Returns the samplerate of the associated sound file.
|
70
|
+
*/
|
71
|
+
static VALUE ra_soundinfo_samplerate(VALUE self) {
|
72
|
+
SF_INFO *info;
|
73
|
+
Data_Get_Struct(self, SF_INFO, info);
|
74
|
+
return INT2FIX(info->samplerate);
|
75
|
+
}
|
76
|
+
|
77
|
+
/*
|
78
|
+
* call-seq:
|
79
|
+
* info.samplerate = integer => integer
|
80
|
+
*
|
81
|
+
* Set the samplerate for a new sound created with the given info object.
|
82
|
+
*/
|
83
|
+
static VALUE ra_soundinfo_samplerate_set(VALUE self, VALUE new_samplerate) {
|
84
|
+
SF_INFO *info;
|
85
|
+
Data_Get_Struct(self, SF_INFO, info);
|
86
|
+
info->samplerate = FIX2INT(new_samplerate);
|
87
|
+
return new_samplerate;
|
88
|
+
}
|
89
|
+
|
90
|
+
/*
|
91
|
+
* call-seq:
|
92
|
+
* info.channels => integer
|
93
|
+
*
|
94
|
+
* Returns the number of channels in the associated sound file.
|
95
|
+
*/
|
96
|
+
static VALUE ra_soundinfo_channels(VALUE self) {
|
97
|
+
SF_INFO *info;
|
98
|
+
Data_Get_Struct(self, SF_INFO, info);
|
99
|
+
return INT2FIX(info->channels);
|
100
|
+
}
|
101
|
+
|
102
|
+
/*
|
103
|
+
* call-seq:
|
104
|
+
* info.channels = integer => integer
|
105
|
+
*
|
106
|
+
* Set the number of channels for a new sound created with the given info object.
|
107
|
+
*/
|
108
|
+
static VALUE ra_soundinfo_channels_set(VALUE self, VALUE new_channels) {
|
109
|
+
SF_INFO *info;
|
110
|
+
Data_Get_Struct(self, SF_INFO, info);
|
111
|
+
info->channels = FIX2INT(new_channels);
|
112
|
+
return new_channels;
|
113
|
+
}
|
114
|
+
|
115
|
+
/*
|
116
|
+
* call-seq:
|
117
|
+
* info.format => integer
|
118
|
+
*
|
119
|
+
* Returns the format as a combination of binary flags of the associated sound file.
|
120
|
+
*/
|
121
|
+
static VALUE ra_soundinfo_format(VALUE self) {
|
122
|
+
SF_INFO *info;
|
123
|
+
Data_Get_Struct(self, SF_INFO, info);
|
124
|
+
return INT2FIX(info->format);
|
125
|
+
}
|
126
|
+
|
127
|
+
/*
|
128
|
+
* call-seq:
|
129
|
+
* info.format = integer => integer
|
130
|
+
*
|
131
|
+
* Set the format for a new sound created with the given info object.
|
132
|
+
*
|
133
|
+
* info = RubyAudio::CSoundInfo.new
|
134
|
+
* info.format = RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
|
135
|
+
*/
|
136
|
+
static VALUE ra_soundinfo_format_set(VALUE self, VALUE new_format) {
|
137
|
+
SF_INFO *info;
|
138
|
+
Data_Get_Struct(self, SF_INFO, info);
|
139
|
+
info->format = FIX2INT(new_format);
|
140
|
+
return new_format;
|
141
|
+
}
|
142
|
+
|
143
|
+
/*
|
144
|
+
* call-seq:
|
145
|
+
* info.sections => integer
|
146
|
+
*
|
147
|
+
* Returns the number of sections in the associated sound file.
|
148
|
+
*/
|
149
|
+
static VALUE ra_soundinfo_sections(VALUE self) {
|
150
|
+
SF_INFO *info;
|
151
|
+
Data_Get_Struct(self, SF_INFO, info);
|
152
|
+
return INT2FIX(info->sections);
|
153
|
+
}
|
154
|
+
|
155
|
+
/*
|
156
|
+
* call-seq:
|
157
|
+
* info.seekable => true or false
|
158
|
+
*
|
159
|
+
* Whether seeking is supported for the associated sound file.
|
160
|
+
*/
|
161
|
+
static VALUE ra_soundinfo_seekable(VALUE self) {
|
162
|
+
SF_INFO *info;
|
163
|
+
Data_Get_Struct(self, SF_INFO, info);
|
164
|
+
return info->seekable ? Qtrue : Qfalse;
|
165
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#ifndef RA_SOUNDINFO_H
|
2
|
+
#define RA_SOUNDINFO_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <sndfile.h>
|
6
|
+
|
7
|
+
void Init_ra_soundinfo();
|
8
|
+
|
9
|
+
/*** Initialization and Memory Manangement ***/
|
10
|
+
static VALUE ra_soundinfo_allocate(VALUE klass);
|
11
|
+
static void ra_soundinfo_free(SF_INFO *info);
|
12
|
+
|
13
|
+
/*** Instance Methods ***/
|
14
|
+
static VALUE ra_soundinfo_valid(VALUE self);
|
15
|
+
static VALUE ra_soundinfo_frames(VALUE self);
|
16
|
+
static VALUE ra_soundinfo_samplerate(VALUE self);
|
17
|
+
static VALUE ra_soundinfo_samplerate_set(VALUE self, VALUE new_samplerate);
|
18
|
+
static VALUE ra_soundinfo_channels(VALUE self);
|
19
|
+
static VALUE ra_soundinfo_channels_set(VALUE self, VALUE new_channels);
|
20
|
+
static VALUE ra_soundinfo_format(VALUE self);
|
21
|
+
static VALUE ra_soundinfo_format_set(VALUE self, VALUE new_format);
|
22
|
+
static VALUE ra_soundinfo_sections(VALUE self);
|
23
|
+
static VALUE ra_soundinfo_seekable(VALUE self);
|
24
|
+
|
25
|
+
#endif // #ifndef RA_SOUNDINFO_H
|