ruby-audio 1.0.0 → 1.1.0

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/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