ruby-audio 1.5.0-x86-mingw32
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 +39 -0
- data/Rakefile +81 -0
- data/ext/rubyaudio_ext/extconf.rb +32 -0
- data/ext/rubyaudio_ext/ra_buffer.c +315 -0
- data/ext/rubyaudio_ext/ra_buffer.h +40 -0
- data/ext/rubyaudio_ext/ra_sound.c +506 -0
- data/ext/rubyaudio_ext/ra_sound.h +37 -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/lib/1.8/rubyaudio_ext.so +0 -0
- data/lib/1.9/rubyaudio_ext.so +0 -0
- data/lib/ruby-audio.rb +10 -0
- data/lib/ruby-audio/buffer.rb +25 -0
- data/lib/ruby-audio/sound.rb +85 -0
- data/lib/ruby-audio/sound_info.rb +66 -0
- data/ruby-audio.gemspec +22 -0
- data/spec/buffer_spec.rb +88 -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 +49 -0
- data/spec/sound_spec.rb +182 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +11 -0
- metadata +101 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$CFLAGS.gsub!("-arch i386", "")
|
4
|
+
$LDFLAGS.gsub!("-arch i386", "")
|
5
|
+
|
6
|
+
dir_config('sndfile')
|
7
|
+
|
8
|
+
# Mega-Nerd windows installer installs as libsndfile-1.dll
|
9
|
+
if RUBY_PLATFORM =~ /(mswin|mingw|cygwin)/
|
10
|
+
sndfile_lib = 'sndfile-1'
|
11
|
+
else
|
12
|
+
sndfile_lib = 'sndfile'
|
13
|
+
end
|
14
|
+
|
15
|
+
INCLUDE_DIRS = ['/opt/local/include', '/usr/local/include', 'C:/Program Files/Mega-Nerd/libsndfile/include', 'C:/Program Files (x86)/Mega-Nerd/libsndfile/include']
|
16
|
+
LIB_DIRS = ['/opt/local/lib', '/usr/local/lib', 'C:/Program Files/Mega-Nerd/libsndfile', 'C:/Program Files (x86)/Mega-Nerd/libsndfile']
|
17
|
+
|
18
|
+
# libsndfile requirements
|
19
|
+
find_header 'sndfile.h', *INCLUDE_DIRS
|
20
|
+
unless ['sndfile-1', 'sndfile'].any? {|lib| find_library lib, 'sf_open', *LIB_DIRS}
|
21
|
+
fail <<-EOM
|
22
|
+
Can't find libsndfile (http://www.mega-nerd.com/libsndfile/)
|
23
|
+
|
24
|
+
Try passing --with-sndfile-dir or --with-sndfile-lib and --with-sndfile-include
|
25
|
+
options to extconf. If there are spaces in the path on windows, it may not work.
|
26
|
+
EOM
|
27
|
+
end
|
28
|
+
|
29
|
+
# Check for format support
|
30
|
+
have_const('SF_FORMAT_OGG', 'sndfile.h')
|
31
|
+
|
32
|
+
create_makefile 'rubyaudio_ext'
|
@@ -0,0 +1,315 @@
|
|
1
|
+
#include "ra_buffer.h"
|
2
|
+
|
3
|
+
ID ra_short_sym, ra_int_sym, ra_float_sym, ra_double_sym;
|
4
|
+
extern VALUE eRubyAudioError;
|
5
|
+
|
6
|
+
// Before RFLOAT_VALUE, value was in a different place in the struct
|
7
|
+
#ifndef RFLOAT_VALUE
|
8
|
+
#define RFLOAT_VALUE(v) (RFLOAT(v)->value)
|
9
|
+
#endif
|
10
|
+
|
11
|
+
/*
|
12
|
+
* Class <code>CBuffer</code> is a very light wrapper around a standard C array
|
13
|
+
* that can be read from and written to by libsndfile.
|
14
|
+
*/
|
15
|
+
void Init_ra_buffer() {
|
16
|
+
VALUE mRubyAudio = rb_define_module("RubyAudio");
|
17
|
+
VALUE cRABuffer = rb_define_class_under(mRubyAudio, "CBuffer", rb_cObject);
|
18
|
+
rb_define_alloc_func(cRABuffer, ra_buffer_allocate);
|
19
|
+
rb_define_method(cRABuffer, "initialize", ra_buffer_init, -1);
|
20
|
+
rb_define_method(cRABuffer, "initialize_copy", ra_buffer_init_copy, 1);
|
21
|
+
rb_define_method(cRABuffer, "channels", ra_buffer_channels, 0);
|
22
|
+
rb_define_method(cRABuffer, "size", ra_buffer_size, 0);
|
23
|
+
rb_define_method(cRABuffer, "real_size", ra_buffer_real_size, 0);
|
24
|
+
rb_define_method(cRABuffer, "real_size=", ra_buffer_real_size_set, 1);
|
25
|
+
rb_define_method(cRABuffer, "type", ra_buffer_type, 0);
|
26
|
+
rb_define_method(cRABuffer, "[]", ra_buffer_aref, 1);
|
27
|
+
rb_define_method(cRABuffer, "[]=", ra_buffer_aset, 2);
|
28
|
+
|
29
|
+
ra_short_sym = rb_intern("short");
|
30
|
+
ra_int_sym = rb_intern("int");
|
31
|
+
ra_float_sym = rb_intern("float");
|
32
|
+
ra_double_sym = rb_intern("double");
|
33
|
+
}
|
34
|
+
|
35
|
+
static VALUE ra_buffer_allocate(VALUE klass) {
|
36
|
+
RA_BUFFER *buf = ALLOC(RA_BUFFER);
|
37
|
+
memset(buf, 0, sizeof(RA_BUFFER));
|
38
|
+
VALUE self = Data_Wrap_Struct(klass, NULL, ra_buffer_free, buf);
|
39
|
+
return self;
|
40
|
+
}
|
41
|
+
|
42
|
+
static void ra_buffer_free(RA_BUFFER *buf) {
|
43
|
+
if(buf->data != NULL) xfree(buf->data);
|
44
|
+
xfree(buf);
|
45
|
+
}
|
46
|
+
|
47
|
+
/*
|
48
|
+
* Uses size, channels, and type to allocate a properly sized array and set data
|
49
|
+
* to the pointer for that data. Returns size.
|
50
|
+
*/
|
51
|
+
static long ra_buffer_alloc_data(RA_BUFFER *buf) {
|
52
|
+
long size = 0;
|
53
|
+
switch(buf->type) {
|
54
|
+
case RA_BUFFER_TYPE_SHORT:
|
55
|
+
size = sizeof(short) * buf->size * buf->channels;
|
56
|
+
break;
|
57
|
+
case RA_BUFFER_TYPE_INT:
|
58
|
+
size = sizeof(int) * buf->size * buf->channels;
|
59
|
+
break;
|
60
|
+
case RA_BUFFER_TYPE_FLOAT:
|
61
|
+
size = sizeof(float) * buf->size * buf->channels;
|
62
|
+
break;
|
63
|
+
case RA_BUFFER_TYPE_DOUBLE:
|
64
|
+
size = sizeof(double) * buf->size * buf->channels;
|
65
|
+
break;
|
66
|
+
}
|
67
|
+
buf->data = (void*)xmalloc(size);
|
68
|
+
return size;
|
69
|
+
}
|
70
|
+
|
71
|
+
/*
|
72
|
+
* call-seq:
|
73
|
+
* RubyAudio::CBuffer.new(type, size, channels=1) => buf
|
74
|
+
*
|
75
|
+
* Returns a new <code>CBuffer</code> object which can contain the given number
|
76
|
+
* of audio frames of the given data type.
|
77
|
+
*
|
78
|
+
* buf = RubyAudio::CBuffer.new("float", 1000)
|
79
|
+
*/
|
80
|
+
static VALUE ra_buffer_init(int argc, VALUE *argv, VALUE self) {
|
81
|
+
RA_BUFFER *buf;
|
82
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
83
|
+
|
84
|
+
// Check args
|
85
|
+
if(argc < 2) rb_raise(rb_eArgError, "At least 2 arguments required");
|
86
|
+
|
87
|
+
// Get type of object
|
88
|
+
const char *buf_type;
|
89
|
+
switch(TYPE(argv[0])) {
|
90
|
+
case T_SYMBOL:
|
91
|
+
buf_type = rb_id2name(SYM2ID(argv[0]));
|
92
|
+
if(!buf_type) rb_raise(rb_eArgError, "bad type");
|
93
|
+
break;
|
94
|
+
case T_STRING:
|
95
|
+
buf_type = RSTRING_PTR(argv[0]);
|
96
|
+
break;
|
97
|
+
default:
|
98
|
+
rb_raise(rb_eArgError, "bad type");
|
99
|
+
break;
|
100
|
+
}
|
101
|
+
|
102
|
+
// Populate channels
|
103
|
+
buf->channels = (argc == 3) ? FIX2INT(argv[2]) : 1;
|
104
|
+
|
105
|
+
// Allocate data array based on type
|
106
|
+
buf->size = FIX2LONG(argv[1]);
|
107
|
+
buf->real_size = 0;
|
108
|
+
if(strcmp(buf_type, "short") == 0) buf->type = RA_BUFFER_TYPE_SHORT;
|
109
|
+
else if(strcmp(buf_type, "int") == 0) buf->type = RA_BUFFER_TYPE_INT;
|
110
|
+
else if(strcmp(buf_type, "float") == 0) buf->type = RA_BUFFER_TYPE_FLOAT;
|
111
|
+
else if(strcmp(buf_type, "double") == 0) buf->type = RA_BUFFER_TYPE_DOUBLE;
|
112
|
+
else rb_raise(rb_eArgError, "Invalid type: %s", buf_type);
|
113
|
+
ra_buffer_alloc_data(buf);
|
114
|
+
|
115
|
+
// Return self
|
116
|
+
return self;
|
117
|
+
}
|
118
|
+
|
119
|
+
/* :nodoc: */
|
120
|
+
static VALUE ra_buffer_init_copy(VALUE copy, VALUE buf) {
|
121
|
+
if (copy == buf) return copy;
|
122
|
+
|
123
|
+
// Checks
|
124
|
+
rb_check_frozen(copy);
|
125
|
+
if (!rb_obj_is_instance_of(buf, rb_obj_class(copy))) {
|
126
|
+
rb_raise(rb_eTypeError, "wrong argument class");
|
127
|
+
}
|
128
|
+
|
129
|
+
RA_BUFFER *copy_struct, *buf_struct;
|
130
|
+
Data_Get_Struct(copy, RA_BUFFER, copy_struct);
|
131
|
+
Data_Get_Struct(buf, RA_BUFFER, buf_struct);
|
132
|
+
|
133
|
+
// Clone data
|
134
|
+
memcpy(copy_struct, buf_struct, sizeof(RA_BUFFER));
|
135
|
+
long size = ra_buffer_alloc_data(copy_struct);
|
136
|
+
memcpy(copy_struct->data, buf_struct->data, size);
|
137
|
+
|
138
|
+
return copy;
|
139
|
+
}
|
140
|
+
|
141
|
+
/*
|
142
|
+
* call-seq:
|
143
|
+
* buf.channels => integer
|
144
|
+
*
|
145
|
+
* Returns the number of channels in a frame of the buffer.
|
146
|
+
*/
|
147
|
+
static VALUE ra_buffer_channels(VALUE self) {
|
148
|
+
RA_BUFFER *buf;
|
149
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
150
|
+
return INT2FIX(buf->channels);
|
151
|
+
}
|
152
|
+
|
153
|
+
/*
|
154
|
+
* call-seq:
|
155
|
+
* buf.size => integer
|
156
|
+
*
|
157
|
+
* Returns the number of frames the buffer can store.
|
158
|
+
*/
|
159
|
+
static VALUE ra_buffer_size(VALUE self) {
|
160
|
+
RA_BUFFER *buf;
|
161
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
162
|
+
return LONG2FIX(buf->size);
|
163
|
+
}
|
164
|
+
|
165
|
+
/*
|
166
|
+
* call-seq:
|
167
|
+
* buf.real_size => integer
|
168
|
+
*
|
169
|
+
* Returns the number of frames of actual data are currently stored in the
|
170
|
+
* buffer.
|
171
|
+
*/
|
172
|
+
static VALUE ra_buffer_real_size(VALUE self) {
|
173
|
+
RA_BUFFER *buf;
|
174
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
175
|
+
return LONG2FIX(buf->real_size);
|
176
|
+
}
|
177
|
+
|
178
|
+
/*:nodoc:*/
|
179
|
+
static VALUE ra_buffer_real_size_set(VALUE self, VALUE real_size) {
|
180
|
+
RA_BUFFER *buf;
|
181
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
182
|
+
|
183
|
+
long new_real_size = FIX2LONG(real_size);
|
184
|
+
if(new_real_size > buf->size) {
|
185
|
+
buf->real_size = buf->size;
|
186
|
+
} else if(new_real_size < 0) {
|
187
|
+
buf->real_size = 0;
|
188
|
+
} else {
|
189
|
+
buf->real_size = new_real_size;
|
190
|
+
}
|
191
|
+
|
192
|
+
return LONG2FIX(buf->real_size);
|
193
|
+
}
|
194
|
+
|
195
|
+
/*
|
196
|
+
* call-seq:
|
197
|
+
* buf.type => symbol
|
198
|
+
*
|
199
|
+
* Returns the type of audio data being stored. <code>:short</code>,
|
200
|
+
* <code>:int</code>, <code>:float</code>, or <code>:double</code>.
|
201
|
+
*/
|
202
|
+
static VALUE ra_buffer_type(VALUE self) {
|
203
|
+
RA_BUFFER *buf;
|
204
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
205
|
+
switch(buf->type) {
|
206
|
+
case RA_BUFFER_TYPE_SHORT: return ID2SYM(ra_short_sym);
|
207
|
+
case RA_BUFFER_TYPE_INT: return ID2SYM(ra_int_sym);
|
208
|
+
case RA_BUFFER_TYPE_FLOAT: return ID2SYM(ra_float_sym);
|
209
|
+
case RA_BUFFER_TYPE_DOUBLE: return ID2SYM(ra_double_sym);
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
/*
|
214
|
+
* call-seq:
|
215
|
+
* buf[integer] => frame
|
216
|
+
*
|
217
|
+
* Returns a frame of audio data at the given offset.
|
218
|
+
*
|
219
|
+
* buf = snd.read(:float, 100) # Mono sound
|
220
|
+
* buf[5] #=> 0.4
|
221
|
+
*
|
222
|
+
* buf2 = snd2.read(:float, 100) # Stereo sound
|
223
|
+
* buf[5] #=> [0.4, 0.3]
|
224
|
+
*/
|
225
|
+
static VALUE ra_buffer_aref(VALUE self, VALUE index) {
|
226
|
+
RA_BUFFER *buf;
|
227
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
228
|
+
|
229
|
+
// Bounds check
|
230
|
+
long f = FIX2LONG(index);
|
231
|
+
if(f < 0 || f >= buf->real_size) return Qnil;
|
232
|
+
long i = f * buf->channels;
|
233
|
+
|
234
|
+
if(buf->channels == 1) {
|
235
|
+
return ra_buffer_index_get(buf, i);
|
236
|
+
} else {
|
237
|
+
VALUE frame = rb_ary_new();
|
238
|
+
long j;
|
239
|
+
for(j = 0; j < buf->channels; j++) {
|
240
|
+
rb_ary_push(frame, ra_buffer_index_get(buf, i+j));
|
241
|
+
}
|
242
|
+
return frame;
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
static VALUE ra_buffer_index_get(RA_BUFFER *buf, long i) {
|
247
|
+
switch(buf->type) {
|
248
|
+
case RA_BUFFER_TYPE_SHORT: return INT2FIX((int)((short*)buf->data)[i]);
|
249
|
+
case RA_BUFFER_TYPE_INT: return INT2FIX(((int*)buf->data)[i]);
|
250
|
+
case RA_BUFFER_TYPE_FLOAT: return rb_float_new((double)((float*)buf->data)[i]);
|
251
|
+
case RA_BUFFER_TYPE_DOUBLE: return rb_float_new(((double*)buf->data)[i]);
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
/*
|
256
|
+
* call-seq:
|
257
|
+
* buf[integer] = numeric => numeric
|
258
|
+
* buf[integer] = array => array
|
259
|
+
*
|
260
|
+
* Sets the frame of audio data at the given offset to the value. For
|
261
|
+
* multi-channel audio, pass in an array of values.
|
262
|
+
*
|
263
|
+
* buf = RubyAudio::Buffer.int(100, 1)
|
264
|
+
* buf[0] = 5
|
265
|
+
*
|
266
|
+
* buf = RubyAudio::Buffer.double(100, 2)
|
267
|
+
* buf[0] = [0.5, 0.3]
|
268
|
+
*/
|
269
|
+
static VALUE ra_buffer_aset(VALUE self, VALUE index, VALUE val) {
|
270
|
+
RA_BUFFER *buf;
|
271
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
272
|
+
|
273
|
+
// Bounds check
|
274
|
+
long f = FIX2LONG(index);
|
275
|
+
if(f < 0 || f >= buf->size) rb_raise(eRubyAudioError, "setting frame out of bounds");
|
276
|
+
long i = f * buf->channels;
|
277
|
+
|
278
|
+
// Set data
|
279
|
+
if(buf->channels == 1) {
|
280
|
+
ra_buffer_index_set(buf, i, val);
|
281
|
+
} else {
|
282
|
+
if(TYPE(val) != T_ARRAY) rb_raise(eRubyAudioError, "must pass in array for multi-channel buffer");
|
283
|
+
long length = RARRAY_LEN(val);
|
284
|
+
if(length != buf->channels) rb_raise(eRubyAudioError, "array length must match channel count");
|
285
|
+
|
286
|
+
long j;
|
287
|
+
for(j = 0; j < length; j++) {
|
288
|
+
ra_buffer_index_set(buf, i+j, rb_ary_entry(val, j));
|
289
|
+
}
|
290
|
+
}
|
291
|
+
|
292
|
+
// Bump real_size
|
293
|
+
if(f + 1 > buf->real_size) {
|
294
|
+
buf->real_size = f + 1;
|
295
|
+
}
|
296
|
+
|
297
|
+
return val;
|
298
|
+
}
|
299
|
+
|
300
|
+
static void ra_buffer_index_set(RA_BUFFER *buf, long i, VALUE val) {
|
301
|
+
switch(buf->type) {
|
302
|
+
case RA_BUFFER_TYPE_SHORT:
|
303
|
+
((short*)buf->data)[i] = (short)FIX2INT(val);
|
304
|
+
break;
|
305
|
+
case RA_BUFFER_TYPE_INT:
|
306
|
+
((int*)buf->data)[i] = FIX2INT(val);
|
307
|
+
break;
|
308
|
+
case RA_BUFFER_TYPE_FLOAT:
|
309
|
+
((float*)buf->data)[i] = (float)RFLOAT_VALUE(val);
|
310
|
+
break;
|
311
|
+
case RA_BUFFER_TYPE_DOUBLE:
|
312
|
+
((double*)buf->data)[i] = RFLOAT_VALUE(val);
|
313
|
+
break;
|
314
|
+
}
|
315
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#ifndef RA_BUFFER_H
|
2
|
+
#define RA_BUFFER_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
|
6
|
+
typedef enum {
|
7
|
+
RA_BUFFER_TYPE_SHORT,
|
8
|
+
RA_BUFFER_TYPE_INT,
|
9
|
+
RA_BUFFER_TYPE_FLOAT,
|
10
|
+
RA_BUFFER_TYPE_DOUBLE
|
11
|
+
} BUFFER_TYPE;
|
12
|
+
|
13
|
+
typedef struct {
|
14
|
+
BUFFER_TYPE type;
|
15
|
+
void *data;
|
16
|
+
long size;
|
17
|
+
long real_size;
|
18
|
+
int channels;
|
19
|
+
} RA_BUFFER;
|
20
|
+
|
21
|
+
void Init_ra_buffer();
|
22
|
+
|
23
|
+
/*** Initialization and Memory Manangement ***/
|
24
|
+
static VALUE ra_buffer_allocate(VALUE klass);
|
25
|
+
static void ra_buffer_free(RA_BUFFER *buf);
|
26
|
+
|
27
|
+
/*** Instance Methods ***/
|
28
|
+
static VALUE ra_buffer_init(int argc, VALUE *argv, VALUE self);
|
29
|
+
static VALUE ra_buffer_init_copy(VALUE copy, VALUE buf);
|
30
|
+
static VALUE ra_buffer_channels(VALUE self);
|
31
|
+
static VALUE ra_buffer_size(VALUE self);
|
32
|
+
static VALUE ra_buffer_real_size(VALUE self);
|
33
|
+
static VALUE ra_buffer_real_size_set(VALUE self, VALUE new_real_size);
|
34
|
+
static VALUE ra_buffer_type(VALUE self);
|
35
|
+
static VALUE ra_buffer_aref(VALUE self, VALUE index);
|
36
|
+
static VALUE ra_buffer_index_get(RA_BUFFER *buf, long i);
|
37
|
+
static VALUE ra_buffer_aset(VALUE self, VALUE index, VALUE val);
|
38
|
+
static void ra_buffer_index_set(RA_BUFFER *buf, long i, VALUE val);
|
39
|
+
|
40
|
+
#endif // #ifndef RA_BUFFER_H
|
@@ -0,0 +1,506 @@
|
|
1
|
+
#include "ra_sound.h"
|
2
|
+
|
3
|
+
extern VALUE eRubyAudioError;
|
4
|
+
|
5
|
+
/*
|
6
|
+
* Class <code>CSound</code> is a very light wrapper around the
|
7
|
+
* <code>SNDFILE</code> struct exposed by libsndfile.
|
8
|
+
*/
|
9
|
+
void Init_ra_sound() {
|
10
|
+
VALUE mRubyAudio = rb_define_module("RubyAudio");
|
11
|
+
VALUE cRASound = rb_define_class_under(mRubyAudio, "CSound", rb_cObject);
|
12
|
+
rb_define_alloc_func(cRASound, ra_sound_allocate);
|
13
|
+
rb_define_singleton_method(cRASound, "open", ra_sound_s_open, -1);
|
14
|
+
rb_define_method(cRASound, "initialize", ra_sound_init, 3);
|
15
|
+
rb_define_method(cRASound, "info", ra_sound_info, 0);
|
16
|
+
rb_define_method(cRASound, "seek", ra_sound_seek, 2);
|
17
|
+
rb_define_method(cRASound, "read", ra_sound_read, 2);
|
18
|
+
rb_define_method(cRASound, "write", ra_sound_write, 1);
|
19
|
+
rb_define_method(cRASound, "<<", ra_sound_addbuf, 1);
|
20
|
+
rb_define_method(cRASound, "close", ra_sound_close, 0);
|
21
|
+
rb_define_method(cRASound, "closed?", ra_sound_closed, 0);
|
22
|
+
}
|
23
|
+
|
24
|
+
static VALUE ra_sound_allocate(VALUE klass) {
|
25
|
+
RA_SOUND *snd = ALLOC(RA_SOUND);
|
26
|
+
memset(snd, 0, sizeof(RA_SOUND));
|
27
|
+
VALUE self = Data_Wrap_Struct(klass, ra_sound_mark, ra_sound_free, snd);
|
28
|
+
return self;
|
29
|
+
}
|
30
|
+
|
31
|
+
static void ra_sound_mark(RA_SOUND *snd) {
|
32
|
+
if(snd) {
|
33
|
+
rb_gc_mark(snd->info);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
static void ra_sound_free(RA_SOUND *snd) {
|
38
|
+
if(!snd->closed && snd->snd != NULL) sf_close(snd->snd);
|
39
|
+
xfree(snd);
|
40
|
+
}
|
41
|
+
|
42
|
+
/*
|
43
|
+
* call-seq:
|
44
|
+
* CSound.open(...) => snd
|
45
|
+
* CSound.open(...) {|snd| block } => obj
|
46
|
+
*
|
47
|
+
* With no associated block, <code>open</code> is a synonym for
|
48
|
+
* <code>CSound.new</code>. If the optional code block is given, it will be
|
49
|
+
* passed <i>snd</i> as an argument, and the CSound object will automatically be
|
50
|
+
* closed when the block terminates. In this instance, <code>CSound.open</code>
|
51
|
+
* returns the value of the block.
|
52
|
+
*/
|
53
|
+
static VALUE ra_sound_s_open(int argc, VALUE *argv, VALUE klass) {
|
54
|
+
VALUE obj = rb_class_new_instance(argc, argv, klass);
|
55
|
+
if(!rb_block_given_p()) return obj;
|
56
|
+
return rb_ensure(rb_yield, obj, ra_sound_close_safe, obj);
|
57
|
+
}
|
58
|
+
|
59
|
+
/*
|
60
|
+
* call-seq:
|
61
|
+
* CSound.new(path, mode, info) => snd
|
62
|
+
*
|
63
|
+
* Returns a new <code>CSound</code> object for the audio file at the given path
|
64
|
+
* with the given mode. Valid modes are <code>"r"</code>, <code>"w"</code>, or
|
65
|
+
* <code>"rw"</code>.
|
66
|
+
*/
|
67
|
+
static VALUE ra_sound_init(VALUE self, VALUE path, VALUE mode, VALUE info) {
|
68
|
+
RA_SOUND *snd;
|
69
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
70
|
+
|
71
|
+
// Get mode
|
72
|
+
const char *m = StringValueCStr(mode);
|
73
|
+
if(strcmp(m, "rw") == 0) snd->mode = SFM_RDWR;
|
74
|
+
else if(strcmp(m, "r") == 0) snd->mode = SFM_READ;
|
75
|
+
else if(strcmp(m, "w") == 0) snd->mode = SFM_WRITE;
|
76
|
+
else rb_raise(rb_eArgError, "invalid access mode %s", m);
|
77
|
+
|
78
|
+
// Set info
|
79
|
+
snd->info = info;
|
80
|
+
|
81
|
+
// Open sound file
|
82
|
+
const char *p = StringValueCStr(path);
|
83
|
+
SF_INFO *sf_info;
|
84
|
+
Data_Get_Struct(info, SF_INFO, sf_info);
|
85
|
+
snd->snd = sf_open(p, snd->mode, sf_info);
|
86
|
+
if(snd->snd == NULL) rb_raise(eRubyAudioError, sf_strerror(snd->snd));
|
87
|
+
snd->closed = 0;
|
88
|
+
|
89
|
+
return self;
|
90
|
+
}
|
91
|
+
|
92
|
+
/*
|
93
|
+
* call-seq:
|
94
|
+
* snd.info => CSoundInfo
|
95
|
+
*
|
96
|
+
* Returns the info object associated with the sound.
|
97
|
+
*/
|
98
|
+
static VALUE ra_sound_info(VALUE self) {
|
99
|
+
RA_SOUND *snd;
|
100
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
101
|
+
return snd->info;
|
102
|
+
}
|
103
|
+
|
104
|
+
/*
|
105
|
+
* call-seq:
|
106
|
+
* snd.seek(frames, whence) => 0
|
107
|
+
*
|
108
|
+
* Seeks to a given offset <i>anInteger</i> in the sound according to the value
|
109
|
+
* of <i>whence</i>:
|
110
|
+
*
|
111
|
+
* IO::SEEK_CUR | Seeks to _frames_ plus current position
|
112
|
+
* --------------+----------------------------------------------------
|
113
|
+
* IO::SEEK_END | Seeks to _frames_ plus end of stream (you probably
|
114
|
+
* | want a negative value for _frames_)
|
115
|
+
* --------------+----------------------------------------------------
|
116
|
+
* IO::SEEK_SET | Seeks to the absolute location given by _frames_
|
117
|
+
*/
|
118
|
+
static VALUE ra_sound_seek(VALUE self, VALUE frames, VALUE whence) {
|
119
|
+
RA_SOUND *snd;
|
120
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
121
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
122
|
+
|
123
|
+
if(sf_seek(snd->snd, (sf_count_t)NUM2OFFT(frames), FIX2INT(whence)) == -1) {
|
124
|
+
rb_raise(eRubyAudioError, "invalid seek");
|
125
|
+
}
|
126
|
+
|
127
|
+
return INT2FIX(0);
|
128
|
+
}
|
129
|
+
|
130
|
+
static void ra_sound_read_short(RA_SOUND *snd, RA_BUFFER *buf, sf_count_t frames) {
|
131
|
+
static short temp[1024];
|
132
|
+
int temp_len = 1024;
|
133
|
+
short *data = (short*)buf->data;
|
134
|
+
short mix_sum;
|
135
|
+
|
136
|
+
// Get info struct
|
137
|
+
SF_INFO *info;
|
138
|
+
Data_Get_Struct(snd->info, SF_INFO, info);
|
139
|
+
|
140
|
+
// Up/Downmix based on channel matching
|
141
|
+
sf_count_t read = 0, r, amount;
|
142
|
+
int i, k;
|
143
|
+
if(buf->channels == info->channels) { // Simply read data without mix
|
144
|
+
read = sf_readf_short(snd->snd, data, frames);
|
145
|
+
} else if(buf->channels == 1) { // Downmix to mono
|
146
|
+
sf_count_t max = temp_len / info->channels;
|
147
|
+
int channels;
|
148
|
+
|
149
|
+
while(read < frames) {
|
150
|
+
// Calculate # of frames to read
|
151
|
+
amount = frames - read;
|
152
|
+
if(amount > max) amount = max;
|
153
|
+
|
154
|
+
r = sf_readf_short(snd->snd, temp, amount);
|
155
|
+
if(r == 0) break;
|
156
|
+
|
157
|
+
// Mix channels together by averaging all channels and store to buffer
|
158
|
+
for(i = 0; i < r; i++) {
|
159
|
+
mix_sum = 0;
|
160
|
+
for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k];
|
161
|
+
data[read] = mix_sum/info->channels;
|
162
|
+
read++;
|
163
|
+
}
|
164
|
+
}
|
165
|
+
} else if(info->channels == 1) { // Upmix from mono by copying channel
|
166
|
+
while(read < frames) {
|
167
|
+
// Calculate # of frames to read
|
168
|
+
amount = frames - read;
|
169
|
+
if(amount > temp_len) amount = temp_len;
|
170
|
+
|
171
|
+
r = sf_readf_short(snd->snd, temp, amount);
|
172
|
+
if(r == 0) break;
|
173
|
+
|
174
|
+
// Write every frame channel times to the buffer
|
175
|
+
for(i = 0; i < r; i++) {
|
176
|
+
for(k = 0; k < buf->channels; k++) {
|
177
|
+
data[read * buf->channels + k] = temp[i];
|
178
|
+
}
|
179
|
+
read++;
|
180
|
+
}
|
181
|
+
}
|
182
|
+
} else {
|
183
|
+
rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels);
|
184
|
+
}
|
185
|
+
|
186
|
+
buf->real_size = read;
|
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;
|
364
|
+
}
|
365
|
+
|
366
|
+
/*
|
367
|
+
* call-seq:
|
368
|
+
* snd.read(buf, frames) => integer
|
369
|
+
*
|
370
|
+
* Tries to read the given number of frames into the buffer and returns the
|
371
|
+
* number of frames actually read.
|
372
|
+
*/
|
373
|
+
static VALUE ra_sound_read(VALUE self, VALUE buf, VALUE frames) {
|
374
|
+
RA_SOUND *snd;
|
375
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
376
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
377
|
+
|
378
|
+
// Get buffer struct
|
379
|
+
RA_BUFFER *b;
|
380
|
+
Data_Get_Struct(buf, RA_BUFFER, b);
|
381
|
+
|
382
|
+
// Double-check frame count against buffer size
|
383
|
+
sf_count_t f = (sf_count_t)NUM2OFFT(frames);
|
384
|
+
if(f < 0 || f > b->size) {
|
385
|
+
rb_raise(eRubyAudioError, "frame count invalid");
|
386
|
+
}
|
387
|
+
|
388
|
+
// Shortcut for 0 frame reads
|
389
|
+
if(f == 0) {
|
390
|
+
b->real_size = 0;
|
391
|
+
return INT2FIX(b->real_size);;
|
392
|
+
}
|
393
|
+
|
394
|
+
// Read based on type
|
395
|
+
switch(b->type) {
|
396
|
+
case RA_BUFFER_TYPE_SHORT:
|
397
|
+
ra_sound_read_short(snd, b, f);
|
398
|
+
break;
|
399
|
+
case RA_BUFFER_TYPE_INT:
|
400
|
+
ra_sound_read_int(snd, b, f);
|
401
|
+
break;
|
402
|
+
case RA_BUFFER_TYPE_FLOAT:
|
403
|
+
ra_sound_read_float(snd, b, f);
|
404
|
+
break;
|
405
|
+
case RA_BUFFER_TYPE_DOUBLE:
|
406
|
+
ra_sound_read_double(snd, b, f);
|
407
|
+
break;
|
408
|
+
}
|
409
|
+
|
410
|
+
// Return read
|
411
|
+
return INT2FIX(b->real_size);
|
412
|
+
}
|
413
|
+
|
414
|
+
/*
|
415
|
+
* call-seq:
|
416
|
+
* snd.write(buf) => integer
|
417
|
+
*
|
418
|
+
* Writes the entire contents of the given buffer to the sound and returns the
|
419
|
+
* number of frames written.
|
420
|
+
*/
|
421
|
+
static VALUE ra_sound_write(VALUE self, VALUE buf) {
|
422
|
+
RA_SOUND *snd;
|
423
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
424
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
425
|
+
|
426
|
+
// Get buffer struct
|
427
|
+
RA_BUFFER *b;
|
428
|
+
Data_Get_Struct(buf, RA_BUFFER, b);
|
429
|
+
|
430
|
+
// Get info struct
|
431
|
+
SF_INFO *info;
|
432
|
+
Data_Get_Struct(snd->info, SF_INFO, info);
|
433
|
+
|
434
|
+
// Check buffer channels matches actual channels
|
435
|
+
if(b->channels != info->channels) {
|
436
|
+
rb_raise(eRubyAudioError, "channel count mismatch: %d vs %d", b->channels, info->channels);
|
437
|
+
}
|
438
|
+
|
439
|
+
// Write data
|
440
|
+
sf_count_t written = 0;
|
441
|
+
switch(b->type) {
|
442
|
+
case RA_BUFFER_TYPE_SHORT:
|
443
|
+
written = sf_writef_short(snd->snd, b->data, b->real_size);
|
444
|
+
break;
|
445
|
+
case RA_BUFFER_TYPE_INT:
|
446
|
+
written = sf_writef_int(snd->snd, b->data, b->real_size);
|
447
|
+
break;
|
448
|
+
case RA_BUFFER_TYPE_FLOAT:
|
449
|
+
written = sf_writef_float(snd->snd, b->data, b->real_size);
|
450
|
+
break;
|
451
|
+
case RA_BUFFER_TYPE_DOUBLE:
|
452
|
+
written = sf_writef_double(snd->snd, b->data, b->real_size);
|
453
|
+
break;
|
454
|
+
}
|
455
|
+
|
456
|
+
return OFFT2NUM(written);
|
457
|
+
}
|
458
|
+
|
459
|
+
/*
|
460
|
+
* call-seq:
|
461
|
+
* snd << buf => snd
|
462
|
+
*
|
463
|
+
* Writes the given buffer to the string.
|
464
|
+
*
|
465
|
+
* snd << buf1 << buf2
|
466
|
+
*/
|
467
|
+
static VALUE ra_sound_addbuf(VALUE self, VALUE buf) {
|
468
|
+
ra_sound_write(self, buf);
|
469
|
+
return self;
|
470
|
+
}
|
471
|
+
|
472
|
+
/*
|
473
|
+
* call-seq:
|
474
|
+
* snd.close => nil
|
475
|
+
*
|
476
|
+
* Closes <i>snd</i> and frees up all memory associated with the sound. The
|
477
|
+
* sound is unavailable for any further data operations; an error is raised if
|
478
|
+
* such an attempt is made. Sounds are automatically closed when they are claimed
|
479
|
+
* by the garbage collector.
|
480
|
+
*/
|
481
|
+
static VALUE ra_sound_close(VALUE self) {
|
482
|
+
RA_SOUND *snd;
|
483
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
484
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
485
|
+
|
486
|
+
sf_close(snd->snd);
|
487
|
+
snd->snd = NULL;
|
488
|
+
snd->closed = 1;
|
489
|
+
return Qnil;
|
490
|
+
}
|
491
|
+
|
492
|
+
static VALUE ra_sound_close_safe(VALUE self) {
|
493
|
+
return rb_rescue(ra_sound_close, self, 0, 0);
|
494
|
+
}
|
495
|
+
|
496
|
+
/*
|
497
|
+
* call-seq:
|
498
|
+
* snd.closed? => true or false
|
499
|
+
*
|
500
|
+
* Whether or not the current sound is closed to further operations.
|
501
|
+
*/
|
502
|
+
static VALUE ra_sound_closed(VALUE self) {
|
503
|
+
RA_SOUND *snd;
|
504
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
505
|
+
return snd->closed ? Qtrue : Qfalse;
|
506
|
+
}
|