ruby-audio 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'spec/rake/spectask'
6
6
 
7
7
  spec = Gem::Specification.new do |s|
8
8
  s.name = 'ruby-audio'
9
- s.version = '1.0.0'
9
+ s.version = '1.1.0'
10
10
  s.summary = 'ruby-audio wraps around libsndfile to provide simplified sound reading and writing support to ruby programs'
11
11
  s.authors = ['Stephen Augenstein']
12
12
  s.email = 'perl.programmer@gmail.com'
@@ -16,14 +16,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
18
  rb_define_alloc_func(cRABuffer, ra_buffer_allocate);
19
- rb_define_method(cRABuffer, "initialize", ra_buffer_init, -1);
20
- rb_define_method(cRABuffer, "channels", ra_buffer_channels, 0);
21
- rb_define_method(cRABuffer, "size", ra_buffer_size, 0);
22
- rb_define_method(cRABuffer, "real_size", ra_buffer_real_size, 0);
23
- rb_define_method(cRABuffer, "real_size=", ra_buffer_real_size_set, 1);
24
- rb_define_method(cRABuffer, "type", ra_buffer_type, 0);
25
- rb_define_method(cRABuffer, "[]", ra_buffer_aref, 1);
26
- rb_define_method(cRABuffer, "[]=", ra_buffer_aset, 2);
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);
27
28
 
28
29
  ra_short_sym = rb_intern("short");
29
30
  ra_int_sym = rb_intern("int");
@@ -43,6 +44,22 @@ static void ra_buffer_free(RA_BUFFER *buf) {
43
44
  xfree(buf);
44
45
  }
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 int ra_buffer_alloc_data(RA_BUFFER *buf) {
52
+ int size;
53
+ switch(buf->type) {
54
+ case RA_BUFFER_TYPE_SHORT: size = sizeof(short)*buf->size*buf->channels; break;
55
+ case RA_BUFFER_TYPE_INT: size = sizeof(int)*buf->size*buf->channels; break;
56
+ case RA_BUFFER_TYPE_FLOAT: size = sizeof(float)*buf->size*buf->channels; break;
57
+ case RA_BUFFER_TYPE_DOUBLE: size = sizeof(double)*buf->size*buf->channels; break;
58
+ }
59
+ buf->data = (void*)xmalloc(size);
60
+ return size;
61
+ }
62
+
46
63
  /*
47
64
  * call-seq:
48
65
  * RubyAudio::CBuffer.new(type, size, channels=1) => buf
@@ -80,26 +97,39 @@ static VALUE ra_buffer_init(int argc, VALUE *argv, VALUE self) {
80
97
  // Allocate data array based on type
81
98
  buf->size = FIX2INT(argv[1]);
82
99
  buf->real_size = 0;
83
- if(strcmp(buf_type, "short") == 0) {
84
- buf->type = RA_BUFFER_TYPE_SHORT;
85
- buf->data = ALLOC_N(short, buf->size * buf->channels);
86
- } else if(strcmp(buf_type, "int") == 0) {
87
- buf->type = RA_BUFFER_TYPE_INT;
88
- buf->data = ALLOC_N(int, buf->size * buf->channels);
89
- } else if(strcmp(buf_type, "float") == 0) {
90
- buf->type = RA_BUFFER_TYPE_FLOAT;
91
- buf->data = ALLOC_N(float, buf->size * buf->channels);
92
- } else if(strcmp(buf_type, "double") == 0) {
93
- buf->type = RA_BUFFER_TYPE_DOUBLE;
94
- buf->data = ALLOC_N(double, buf->size * buf->channels);
95
- } else {
96
- rb_raise(rb_eArgError, "Invalid type: %s", buf_type);
97
- }
100
+ if(strcmp(buf_type, "short") == 0) buf->type = RA_BUFFER_TYPE_SHORT;
101
+ else if(strcmp(buf_type, "int") == 0) buf->type = RA_BUFFER_TYPE_INT;
102
+ else if(strcmp(buf_type, "float") == 0) buf->type = RA_BUFFER_TYPE_FLOAT;
103
+ else if(strcmp(buf_type, "double") == 0) buf->type = RA_BUFFER_TYPE_DOUBLE;
104
+ else rb_raise(rb_eArgError, "Invalid type: %s", buf_type);
105
+ ra_buffer_alloc_data(buf);
98
106
 
99
107
  // Return self
100
108
  return self;
101
109
  }
102
110
 
111
+ /* :nodoc: */
112
+ static VALUE ra_buffer_init_copy(VALUE copy, VALUE buf) {
113
+ if (copy == buf) return copy;
114
+
115
+ // Checks
116
+ rb_check_frozen(copy);
117
+ if (!rb_obj_is_instance_of(buf, rb_obj_class(copy))) {
118
+ rb_raise(rb_eTypeError, "wrong argument class");
119
+ }
120
+
121
+ RA_BUFFER *copy_struct, *buf_struct;
122
+ Data_Get_Struct(copy, RA_BUFFER, copy_struct);
123
+ Data_Get_Struct(buf, RA_BUFFER, buf_struct);
124
+
125
+ // Clone data
126
+ memcpy(copy_struct, buf_struct, sizeof(RA_BUFFER));
127
+ int size = ra_buffer_alloc_data(copy_struct);
128
+ memcpy(copy_struct->data, buf_struct->data, size);
129
+
130
+ return copy;
131
+ }
132
+
103
133
  /*
104
134
  * call-seq:
105
135
  * buf.channels => integer
@@ -26,6 +26,7 @@ static void ra_buffer_free(RA_BUFFER *buf);
26
26
 
27
27
  /*** Instance Methods ***/
28
28
  static VALUE ra_buffer_init(int argc, VALUE *argv, VALUE self);
29
+ static VALUE ra_buffer_init_copy(VALUE copy, VALUE buf);
29
30
  static VALUE ra_buffer_channels(VALUE self);
30
31
  static VALUE ra_buffer_size(VALUE self);
31
32
  static VALUE ra_buffer_real_size(VALUE self);
@@ -127,6 +127,238 @@ static VALUE ra_sound_seek(VALUE self, VALUE frames, VALUE whence) {
127
127
  return INT2FIX(0);
128
128
  }
129
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
+
135
+ // Get info struct
136
+ SF_INFO *info;
137
+ Data_Get_Struct(snd->info, SF_INFO, info);
138
+
139
+ // Up/Downmix based on channel matching
140
+ sf_count_t read = 0, r, amount;
141
+ int i, k;
142
+ if(buf->channels == info->channels) { // Simply read data without mix
143
+ read = sf_readf_short(snd->snd, data, frames);
144
+ } else if(buf->channels == 1) { // Downmix to mono
145
+ sf_count_t max = temp_len / info->channels;
146
+ int channels, mix_sum;
147
+
148
+ while(read < frames) {
149
+ // Calculate # of frames to read
150
+ amount = frames - read;
151
+ if(amount > max) amount = max;
152
+
153
+ r = sf_readf_short(snd->snd, temp, amount);
154
+ if(r == 0) break;
155
+
156
+ // Mix channels together by averaging all channels and store to buffer
157
+ for(i = 0; i < r; i++) {
158
+ mix_sum = 0;
159
+ for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k];
160
+ data[read] = mix_sum/info->channels;
161
+ read++;
162
+ }
163
+ }
164
+ } else if(info->channels == 1) { // Upmix from mono by copying channel
165
+ while(read < frames) {
166
+ // Calculate # of frames to read
167
+ amount = frames - read;
168
+ if(amount > temp_len) amount = temp_len;
169
+
170
+ r = sf_readf_short(snd->snd, temp, amount);
171
+ if(r == 0) break;
172
+
173
+ // Write every frame channel times to the buffer
174
+ for(i = 0; i < r; i++) {
175
+ for(k = 0; k < buf->channels; k++) {
176
+ data[read * buf->channels + k] = temp[i];
177
+ }
178
+ read++;
179
+ }
180
+ }
181
+ } else {
182
+ rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels);
183
+ }
184
+
185
+ buf->real_size = read;
186
+ }
187
+
188
+ static void ra_sound_read_int(RA_SOUND *snd, RA_BUFFER *buf, sf_count_t frames) {
189
+ static int temp[1024];
190
+ int temp_len = 1024;
191
+ int *data = (int*)buf->data;
192
+
193
+ // Get info struct
194
+ SF_INFO *info;
195
+ Data_Get_Struct(snd->info, SF_INFO, info);
196
+
197
+ // Up/Downmix based on channel matching
198
+ sf_count_t read = 0, r, amount;
199
+ int i, k;
200
+ if(buf->channels == info->channels) { // Simply read data without mix
201
+ read = sf_readf_int(snd->snd, data, frames);
202
+ } else if(buf->channels == 1) { // Downmix to mono
203
+ sf_count_t max = temp_len / info->channels;
204
+ int channels, mix_sum;
205
+
206
+ while(read < frames) {
207
+ // Calculate # of frames to read
208
+ amount = frames - read;
209
+ if(amount > max) amount = max;
210
+
211
+ r = sf_readf_int(snd->snd, temp, amount);
212
+ if(r == 0) break;
213
+
214
+ // Mix channels together by averaging all channels and store to buffer
215
+ for(i = 0; i < r; i++) {
216
+ mix_sum = 0;
217
+ for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k];
218
+ data[read] = mix_sum/info->channels;
219
+ read++;
220
+ }
221
+ }
222
+ } else if(info->channels == 1) { // Upmix from mono by copying channel
223
+ while(read < frames) {
224
+ // Calculate # of frames to read
225
+ amount = frames - read;
226
+ if(amount > temp_len) amount = temp_len;
227
+
228
+ r = sf_readf_int(snd->snd, temp, amount);
229
+ if(r == 0) break;
230
+
231
+ // Write every frame channel times to the buffer
232
+ for(i = 0; i < r; i++) {
233
+ for(k = 0; k < buf->channels; k++) {
234
+ data[read * buf->channels + k] = temp[i];
235
+ }
236
+ read++;
237
+ }
238
+ }
239
+ } else {
240
+ rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels);
241
+ }
242
+
243
+ buf->real_size = read;
244
+ }
245
+
246
+ static void ra_sound_read_float(RA_SOUND *snd, RA_BUFFER *buf, sf_count_t frames) {
247
+ static float temp[1024];
248
+ int temp_len = 1024;
249
+ float *data = (float*)buf->data;
250
+
251
+ // Get info struct
252
+ SF_INFO *info;
253
+ Data_Get_Struct(snd->info, SF_INFO, info);
254
+
255
+ // Up/Downmix based on channel matching
256
+ sf_count_t read = 0, r, amount;
257
+ int i, k;
258
+ if(buf->channels == info->channels) { // Simply read data without mix
259
+ read = sf_readf_float(snd->snd, data, frames);
260
+ } else if(buf->channels == 1) { // Downmix to mono
261
+ sf_count_t max = temp_len / info->channels;
262
+ int channels, mix_sum;
263
+
264
+ while(read < frames) {
265
+ // Calculate # of frames to read
266
+ amount = frames - read;
267
+ if(amount > max) amount = max;
268
+
269
+ r = sf_readf_float(snd->snd, temp, amount);
270
+ if(r == 0) break;
271
+
272
+ // Mix channels together by averaging all channels and store to buffer
273
+ for(i = 0; i < r; i++) {
274
+ mix_sum = 0;
275
+ for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k];
276
+ data[read] = mix_sum/info->channels;
277
+ read++;
278
+ }
279
+ }
280
+ } else if(info->channels == 1) { // Upmix from mono by copying channel
281
+ while(read < frames) {
282
+ // Calculate # of frames to read
283
+ amount = frames - read;
284
+ if(amount > temp_len) amount = temp_len;
285
+
286
+ r = sf_readf_float(snd->snd, temp, amount);
287
+ if(r == 0) break;
288
+
289
+ // Write every frame channel times to the buffer
290
+ for(i = 0; i < r; i++) {
291
+ for(k = 0; k < buf->channels; k++) {
292
+ data[read * buf->channels + k] = temp[i];
293
+ }
294
+ read++;
295
+ }
296
+ }
297
+ } else {
298
+ rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels);
299
+ }
300
+
301
+ buf->real_size = read;
302
+ }
303
+
304
+ static void ra_sound_read_double(RA_SOUND *snd, RA_BUFFER *buf, sf_count_t frames) {
305
+ static double temp[1024];
306
+ int temp_len = 1024;
307
+ double *data = (double*)buf->data;
308
+
309
+ // Get info struct
310
+ SF_INFO *info;
311
+ Data_Get_Struct(snd->info, SF_INFO, info);
312
+
313
+ // Up/Downmix based on channel matching
314
+ sf_count_t read = 0, r, amount;
315
+ int i, k;
316
+ if(buf->channels == info->channels) { // Simply read data without mix
317
+ read = sf_readf_double(snd->snd, data, frames);
318
+ } else if(buf->channels == 1) { // Downmix to mono
319
+ sf_count_t max = temp_len / info->channels;
320
+ int channels, mix_sum;
321
+
322
+ while(read < frames) {
323
+ // Calculate # of frames to read
324
+ amount = frames - read;
325
+ if(amount > max) amount = max;
326
+
327
+ r = sf_readf_double(snd->snd, temp, amount);
328
+ if(r == 0) break;
329
+
330
+ // Mix channels together by averaging all channels and store to buffer
331
+ for(i = 0; i < r; i++) {
332
+ mix_sum = 0;
333
+ for(k = 0; k < info->channels; k++) mix_sum += temp[i * info->channels + k];
334
+ data[read] = mix_sum/info->channels;
335
+ read++;
336
+ }
337
+ }
338
+ } else if(info->channels == 1) { // Upmix from mono by copying channel
339
+ while(read < frames) {
340
+ // Calculate # of frames to read
341
+ amount = frames - read;
342
+ if(amount > temp_len) amount = temp_len;
343
+
344
+ r = sf_readf_double(snd->snd, temp, amount);
345
+ if(r == 0) break;
346
+
347
+ // Write every frame channel times to the buffer
348
+ for(i = 0; i < r; i++) {
349
+ for(k = 0; k < buf->channels; k++) {
350
+ data[read * buf->channels + k] = temp[i];
351
+ }
352
+ read++;
353
+ }
354
+ }
355
+ } else {
356
+ rb_raise(eRubyAudioError, "unsupported mix from %d to %d", buf->channels, info->channels);
357
+ }
358
+
359
+ buf->real_size = read;
360
+ }
361
+
130
362
  /*
131
363
  * call-seq:
132
364
  * snd.read(buf, frames) => integer
@@ -143,39 +375,35 @@ static VALUE ra_sound_read(VALUE self, VALUE buf, VALUE frames) {
143
375
  RA_BUFFER *b;
144
376
  Data_Get_Struct(buf, RA_BUFFER, b);
145
377
 
146
- // Get info struct
147
- SF_INFO *info;
148
- Data_Get_Struct(snd->info, SF_INFO, info);
149
-
150
- // Check buffer channels matches actual channels
151
- if(b->channels != info->channels) {
152
- rb_raise(eRubyAudioError, "channel count mismatch: %d vs %d", b->channels, info->channels);
153
- }
154
-
155
378
  // Double-check frame count against buffer size
156
379
  sf_count_t f = (sf_count_t)NUM2OFFT(frames);
157
380
  if(f < 0 || f > b->size) {
158
381
  rb_raise(eRubyAudioError, "frame count invalid");
159
382
  }
160
383
 
161
- // Read data
162
- sf_count_t read;
384
+ // Shortcut for 0 frame reads
385
+ if(f == 0) {
386
+ b->real_size = 0;
387
+ return INT2FIX(b->real_size);;
388
+ }
389
+
390
+ // Read based on type
163
391
  switch(b->type) {
164
392
  case RA_BUFFER_TYPE_SHORT:
165
- read = sf_readf_short(snd->snd, b->data, f);
393
+ ra_sound_read_short(snd, b, f);
166
394
  break;
167
395
  case RA_BUFFER_TYPE_INT:
168
- read = sf_readf_int(snd->snd, b->data, f);
396
+ ra_sound_read_int(snd, b, f);
169
397
  break;
170
398
  case RA_BUFFER_TYPE_FLOAT:
171
- read = sf_readf_float(snd->snd, b->data, f);
399
+ ra_sound_read_float(snd, b, f);
172
400
  break;
173
401
  case RA_BUFFER_TYPE_DOUBLE:
174
- read = sf_readf_double(snd->snd, b->data, f);
402
+ ra_sound_read_double(snd, b, f);
175
403
  break;
176
404
  }
177
- b->real_size = read;
178
405
 
406
+ // Return read
179
407
  return INT2FIX(b->real_size);
180
408
  }
181
409
 
@@ -47,4 +47,16 @@ describe RubyAudio::Buffer do
47
47
  buf.real_size = 101
48
48
  buf.real_size.should == 100
49
49
  end
50
+
51
+ it "should support cloning/duping" do
52
+ buf = RubyAudio::Buffer.int(100)
53
+ buf[4] = 100
54
+
55
+ buf2 = buf.dup
56
+ buf2.size.should == buf.size
57
+ buf2[4].should == 100
58
+
59
+ buf[4] = 140
60
+ buf2[4].should == 100
61
+ end
50
62
  end
@@ -100,13 +100,38 @@ describe RubyAudio::Sound do
100
100
  buf[100].should == nil
101
101
  end
102
102
 
103
- it "should raise exception for channel count mismatch on read" do
104
- buf = RubyAudio::Buffer.float(1000, 1)
103
+ it "should allow downmixing to mono on read" do
104
+ buf = RubyAudio::Buffer.int(100, 1)
105
+ buf2 = RubyAudio::Buffer.int(100, 2)
106
+ RubyAudio::Sound.open(STEREO_TEST_WAV, 'r') do |snd|
107
+ snd.read(buf)
108
+ snd.seek(0)
109
+ snd.read(buf2)
110
+
111
+ f = buf2[99]
112
+ buf[99].should == (f[0] + f[1]) / 2
113
+ end
114
+ end
115
+
116
+ it "should allow upmixing from mono on read" do
117
+ buf = RubyAudio::Buffer.int(100, 1)
118
+ buf2 = RubyAudio::Buffer.int(100, 2)
119
+ RubyAudio::Sound.open(STEREO_TEST_WAV, 'r') do |snd|
120
+ snd.read(buf2)
121
+ snd.seek(0)
122
+ snd.read(buf)
123
+
124
+ buf2[99].should == [buf[99], buf[99]]
125
+ end
126
+ end
127
+
128
+ it "should fail read on unsupported up/downmixing" do
129
+ buf = RubyAudio::Buffer.float(100, 5)
105
130
  lambda {
106
131
  RubyAudio::Sound.open(STEREO_TEST_WAV) do |snd|
107
132
  snd.read(buf)
108
133
  end
109
- }.should raise_error(RubyAudio::Error, "channel count mismatch: 1 vs 2")
134
+ }.should raise_error(RubyAudio::Error, "unsupported mix from 5 to 2")
110
135
  end
111
136
 
112
137
  it "should allow writing to a new sound" do
@@ -144,4 +169,14 @@ describe RubyAudio::Sound do
144
169
 
145
170
  out_buf[50].should == in_buf[50]
146
171
  end
172
+
173
+ it "should fail write on channel mismatch" do
174
+ buf = RubyAudio::Buffer.float(100, 5)
175
+ info = RubyAudio::SoundInfo.new :channels => 2, :samplerate => 48000, :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
176
+ lambda {
177
+ RubyAudio::Sound.open(OUT_WAV, 'w', info) do |snd|
178
+ snd.write(buf)
179
+ end
180
+ }.should raise_error(RubyAudio::Error, "channel count mismatch: 5 vs 2")
181
+ end
147
182
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-audio
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Augenstein
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-03-16 00:00:00 -04:00
12
+ date: 2010-03-24 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15