ruby-audio 0.2.1 → 1.0.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/README.rdoc +7 -6
- data/Rakefile +14 -21
- data/ext/extconf.rb +12 -0
- data/ext/ra_buffer.c +277 -0
- data/ext/ra_buffer.h +39 -0
- data/ext/ra_sound.c +274 -0
- data/ext/ra_sound.h +37 -0
- data/ext/ra_soundinfo.c +165 -0
- data/ext/ra_soundinfo.h +25 -0
- data/ext/rubyaudio_ext.c +91 -0
- data/lib/ruby-audio.rb +4 -0
- data/lib/ruby-audio/buffer.rb +17 -0
- data/lib/ruby-audio/sound.rb +85 -0
- data/lib/ruby-audio/sound_info.rb +40 -0
- data/spec/buffer_spec.rb +50 -0
- data/spec/data/what.mp3 +0 -0
- data/{test → spec/data}/what.wav +0 -0
- data/{test → spec/data}/what2.wav +0 -0
- data/spec/sound_info_spec.rb +39 -0
- data/spec/sound_spec.rb +147 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +12 -0
- metadata +37 -31
- data/TODO +0 -5
- data/examples/load_samples.rb +0 -27
- data/examples/t.rb +0 -10
- data/ext/sndfile/extconf.rb +0 -31
- data/ext/sndfile/sndfile.i +0 -90
- data/lib/audio.rb +0 -134
- data/lib/audio/sndfile.rb +0 -156
- data/test/test_audio.rb +0 -82
- data/test/test_sndfile.rb +0 -191
data/README.rdoc
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
= ruby-audio
|
2
2
|
|
3
|
-
Gemified release of ruby/audio. ruby-audio wraps around
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
Gemified release of ruby/audio. ruby-audio wraps around
|
4
|
+
libsndfile[http://www.mega-nerd.com/libsndfile/] to provide simplified sound
|
5
|
+
reading and writing support to ruby programs. The core is the RubyAudio::Sound
|
6
|
+
class, which is a subclass of RubyAudio::CSound. RubyAudio::Buffer contains
|
7
|
+
sound data and RubyAudio::SoundInfo contains information about the format of a
|
8
|
+
sound file.
|
7
9
|
|
8
10
|
== About This Release
|
9
11
|
|
@@ -17,11 +19,10 @@ Please contact me with any questions or comments.
|
|
17
19
|
=== Prerequisites
|
18
20
|
|
19
21
|
- libsndfile[http://www.mega-nerd.com/libsndfile/]
|
20
|
-
- SWIG[http://www.swig.org/]
|
21
22
|
|
22
23
|
== License
|
23
24
|
|
24
|
-
Copyright (C) 2010
|
25
|
+
Copyright (C) 2010 Stephen Augenstein
|
25
26
|
|
26
27
|
This program is free software; you can redistribute it and/or modify
|
27
28
|
it under the terms of the GNU General Public License as published by
|
data/Rakefile
CHANGED
@@ -1,48 +1,41 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
|
-
require 'rake/testtask'
|
4
3
|
require 'rake/rdoctask'
|
5
4
|
require 'rake/gempackagetask'
|
5
|
+
require 'spec/rake/spectask'
|
6
6
|
|
7
7
|
spec = Gem::Specification.new do |s|
|
8
8
|
s.name = 'ruby-audio'
|
9
|
-
s.version = '0.
|
9
|
+
s.version = '1.0.0'
|
10
10
|
s.summary = 'ruby-audio wraps around libsndfile to provide simplified sound reading and writing support to ruby programs'
|
11
|
-
s.authors = ['
|
11
|
+
s.authors = ['Stephen Augenstein']
|
12
12
|
s.email = 'perl.programmer@gmail.com'
|
13
13
|
s.homepage = 'http://github.com/warhammerkid/ruby-audio'
|
14
14
|
|
15
|
-
s.platform
|
16
|
-
s.
|
17
|
-
s.
|
18
|
-
s.
|
19
|
-
s.extensions
|
20
|
-
s.test_files
|
15
|
+
s.platform = Gem::Platform::RUBY
|
16
|
+
s.rdoc_options << '--line-numbers' << '--main' << 'README.rdoc'
|
17
|
+
s.rdoc_options += FileList['ext/**/*.c', 'README.rdoc']
|
18
|
+
s.files = FileList['README.rdoc', 'Rakefile', 'LICENSE', 'lib/**/*.rb', 'spec/**/*.{rb,opts,wav,mp3}', 'ext/extconf.rb', 'ext/*.{c,h}']
|
19
|
+
s.extensions = ["ext/extconf.rb"]
|
20
|
+
s.test_files = Dir[*['spec/**/*_spec.rb']]
|
21
21
|
|
22
|
-
s.add_dependency('narray')
|
23
22
|
s.requirements << 'libsndfile (http://www.mega-nerd.com/libsndfile/)'
|
24
|
-
s.requirements << 'SWIG (http://www.swig.org/)'
|
25
23
|
end
|
26
24
|
|
27
25
|
desc 'Default: Run the tests'
|
28
|
-
task :default => :
|
26
|
+
task :default => :spec
|
29
27
|
|
30
28
|
# Rake gem & package routines
|
31
|
-
Rake::GemPackageTask.new
|
32
|
-
pkg.need_tar = true
|
33
|
-
pkg.need_zip = true
|
34
|
-
end
|
29
|
+
Rake::GemPackageTask.new(spec).define
|
35
30
|
|
36
31
|
desc "Generate documentation"
|
37
32
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
38
33
|
rdoc.rdoc_dir = 'rdoc'
|
39
34
|
rdoc.title = 'ruby-audio'
|
40
|
-
rdoc.options
|
41
|
-
rdoc.rdoc_files.include('README.rdoc')
|
35
|
+
rdoc.options = spec.rdoc_options
|
42
36
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
43
37
|
end
|
44
38
|
|
45
|
-
|
46
|
-
|
47
|
-
t.libs += ['ext/sndfile']
|
39
|
+
Spec::Rake::SpecTask.new do |t|
|
40
|
+
t.spec_opts = ['--options', 'spec/spec.opts']
|
48
41
|
end
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$CFLAGS = '-I/opt/local/include'
|
4
|
+
$LDFLAGS = '-L/opt/local/lib -L/usr/local/lib'
|
5
|
+
|
6
|
+
# libsndfile requirements
|
7
|
+
unless find_library 'sndfile', 'sf_open'
|
8
|
+
raise 'You need to install libsndfile (http://www.mega-nerd.com/libsndfile/)'
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
|
12
|
+
create_makefile 'rubyaudio_ext'
|
data/ext/ra_buffer.c
ADDED
@@ -0,0 +1,277 @@
|
|
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, "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);
|
27
|
+
|
28
|
+
ra_short_sym = rb_intern("short");
|
29
|
+
ra_int_sym = rb_intern("int");
|
30
|
+
ra_float_sym = rb_intern("float");
|
31
|
+
ra_double_sym = rb_intern("double");
|
32
|
+
}
|
33
|
+
|
34
|
+
static VALUE ra_buffer_allocate(VALUE klass) {
|
35
|
+
RA_BUFFER *buf = ALLOC(RA_BUFFER);
|
36
|
+
memset(buf, 0, sizeof(RA_BUFFER));
|
37
|
+
VALUE self = Data_Wrap_Struct(klass, NULL, ra_buffer_free, buf);
|
38
|
+
return self;
|
39
|
+
}
|
40
|
+
|
41
|
+
static void ra_buffer_free(RA_BUFFER *buf) {
|
42
|
+
if(buf->data != NULL) xfree(buf->data);
|
43
|
+
xfree(buf);
|
44
|
+
}
|
45
|
+
|
46
|
+
/*
|
47
|
+
* call-seq:
|
48
|
+
* RubyAudio::CBuffer.new(type, size, channels=1) => buf
|
49
|
+
*
|
50
|
+
* Returns a new <code>CBuffer</code> object which can contain the given number
|
51
|
+
* of audio frames of the given data type.
|
52
|
+
*
|
53
|
+
* buf = RubyAudio::CBuffer.new("float", 1000)
|
54
|
+
*/
|
55
|
+
static VALUE ra_buffer_init(int argc, VALUE *argv, VALUE self) {
|
56
|
+
RA_BUFFER *buf;
|
57
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
58
|
+
|
59
|
+
// Check args
|
60
|
+
if(argc < 2) rb_raise(rb_eArgError, "At least 2 arguments required");
|
61
|
+
|
62
|
+
// Get type of object
|
63
|
+
const char *buf_type;
|
64
|
+
switch(TYPE(argv[0])) {
|
65
|
+
case T_SYMBOL:
|
66
|
+
buf_type = rb_id2name(SYM2ID(argv[0]));
|
67
|
+
if(!buf_type) rb_raise(rb_eArgError, "bad type");
|
68
|
+
break;
|
69
|
+
case T_STRING:
|
70
|
+
buf_type = RSTRING_PTR(argv[0]);
|
71
|
+
break;
|
72
|
+
default:
|
73
|
+
rb_raise(rb_eArgError, "bad type");
|
74
|
+
break;
|
75
|
+
}
|
76
|
+
|
77
|
+
// Populate channels
|
78
|
+
buf->channels = (argc == 3) ? FIX2INT(argv[2]) : 1;
|
79
|
+
|
80
|
+
// Allocate data array based on type
|
81
|
+
buf->size = FIX2INT(argv[1]);
|
82
|
+
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
|
+
}
|
98
|
+
|
99
|
+
// Return self
|
100
|
+
return self;
|
101
|
+
}
|
102
|
+
|
103
|
+
/*
|
104
|
+
* call-seq:
|
105
|
+
* buf.channels => integer
|
106
|
+
*
|
107
|
+
* Returns the number of channels in a frame of the buffer.
|
108
|
+
*/
|
109
|
+
static VALUE ra_buffer_channels(VALUE self) {
|
110
|
+
RA_BUFFER *buf;
|
111
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
112
|
+
return INT2FIX(buf->channels);
|
113
|
+
}
|
114
|
+
|
115
|
+
/*
|
116
|
+
* call-seq:
|
117
|
+
* buf.size => integer
|
118
|
+
*
|
119
|
+
* Returns the number of frames the buffer can store.
|
120
|
+
*/
|
121
|
+
static VALUE ra_buffer_size(VALUE self) {
|
122
|
+
RA_BUFFER *buf;
|
123
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
124
|
+
return INT2FIX(buf->size);
|
125
|
+
}
|
126
|
+
|
127
|
+
/*
|
128
|
+
* call-seq:
|
129
|
+
* buf.real_size => integer
|
130
|
+
*
|
131
|
+
* Returns the number of frames of actual data are currently stored in the
|
132
|
+
* buffer.
|
133
|
+
*/
|
134
|
+
static VALUE ra_buffer_real_size(VALUE self) {
|
135
|
+
RA_BUFFER *buf;
|
136
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
137
|
+
return INT2FIX(buf->real_size);
|
138
|
+
}
|
139
|
+
|
140
|
+
/*:nodoc:*/
|
141
|
+
static VALUE ra_buffer_real_size_set(VALUE self, VALUE real_size) {
|
142
|
+
RA_BUFFER *buf;
|
143
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
144
|
+
|
145
|
+
int new_real_size = FIX2INT(real_size);
|
146
|
+
if(new_real_size > buf->size) {
|
147
|
+
buf->real_size = buf->size;
|
148
|
+
} else if(new_real_size < 0) {
|
149
|
+
buf->real_size = 0;
|
150
|
+
} else {
|
151
|
+
buf->real_size = new_real_size;
|
152
|
+
}
|
153
|
+
|
154
|
+
return INT2FIX(buf->real_size);
|
155
|
+
}
|
156
|
+
|
157
|
+
/*
|
158
|
+
* call-seq:
|
159
|
+
* buf.type => symbol
|
160
|
+
*
|
161
|
+
* Returns the type of audio data being stored. <code>:short</code>,
|
162
|
+
* <code>:int</code>, <code>:float</code>, or <code>:double</code>.
|
163
|
+
*/
|
164
|
+
static VALUE ra_buffer_type(VALUE self) {
|
165
|
+
RA_BUFFER *buf;
|
166
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
167
|
+
switch(buf->type) {
|
168
|
+
case RA_BUFFER_TYPE_SHORT: return ID2SYM(ra_short_sym);
|
169
|
+
case RA_BUFFER_TYPE_INT: return ID2SYM(ra_int_sym);
|
170
|
+
case RA_BUFFER_TYPE_FLOAT: return ID2SYM(ra_float_sym);
|
171
|
+
case RA_BUFFER_TYPE_DOUBLE: return ID2SYM(ra_double_sym);
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
/*
|
176
|
+
* call-seq:
|
177
|
+
* buf[integer] => frame
|
178
|
+
*
|
179
|
+
* Returns a frame of audio data at the given offset.
|
180
|
+
*
|
181
|
+
* buf = snd.read(:float, 100) # Mono sound
|
182
|
+
* buf[5] #=> 0.4
|
183
|
+
*
|
184
|
+
* buf2 = snd2.read(:float, 100) # Stereo sound
|
185
|
+
* buf[5] #=> [0.4, 0.3]
|
186
|
+
*/
|
187
|
+
static VALUE ra_buffer_aref(VALUE self, VALUE index) {
|
188
|
+
RA_BUFFER *buf;
|
189
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
190
|
+
|
191
|
+
// Bounds check
|
192
|
+
int f = FIX2INT(index);
|
193
|
+
if(f < 0 || f >= buf->real_size) return Qnil;
|
194
|
+
int i = f * buf->channels;
|
195
|
+
|
196
|
+
if(buf->channels == 1) {
|
197
|
+
return ra_buffer_index_get(buf, i);
|
198
|
+
} else {
|
199
|
+
VALUE frame = rb_ary_new();
|
200
|
+
int j;
|
201
|
+
for(j = 0; j < buf->channels; j++) {
|
202
|
+
rb_ary_push(frame, ra_buffer_index_get(buf, i+j));
|
203
|
+
}
|
204
|
+
return frame;
|
205
|
+
}
|
206
|
+
}
|
207
|
+
|
208
|
+
static VALUE ra_buffer_index_get(RA_BUFFER *buf, int i) {
|
209
|
+
switch(buf->type) {
|
210
|
+
case RA_BUFFER_TYPE_SHORT: return INT2FIX((int)((short*)buf->data)[i]);
|
211
|
+
case RA_BUFFER_TYPE_INT: return INT2FIX(((int*)buf->data)[i]);
|
212
|
+
case RA_BUFFER_TYPE_FLOAT: return rb_float_new((double)((float*)buf->data)[i]);
|
213
|
+
case RA_BUFFER_TYPE_DOUBLE: return rb_float_new(((double*)buf->data)[i]);
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
/*
|
218
|
+
* call-seq:
|
219
|
+
* buf[integer] = numeric => numeric
|
220
|
+
* buf[integer] = array => array
|
221
|
+
*
|
222
|
+
* Sets the frame of audio data at the given offset to the value. For
|
223
|
+
* multi-channel audio, pass in an array of values.
|
224
|
+
*
|
225
|
+
* buf = RubyAudio::Buffer.int(100, 1)
|
226
|
+
* buf[0] = 5
|
227
|
+
*
|
228
|
+
* buf = RubyAudio::Buffer.double(100, 2)
|
229
|
+
* buf[0] = [0.5, 0.3]
|
230
|
+
*/
|
231
|
+
static VALUE ra_buffer_aset(VALUE self, VALUE index, VALUE val) {
|
232
|
+
RA_BUFFER *buf;
|
233
|
+
Data_Get_Struct(self, RA_BUFFER, buf);
|
234
|
+
|
235
|
+
// Bounds check
|
236
|
+
int f = FIX2INT(index);
|
237
|
+
if(f < 0 || f >= buf->size) rb_raise(eRubyAudioError, "setting frame out of bounds");
|
238
|
+
int i = f * buf->channels;
|
239
|
+
|
240
|
+
// Set data
|
241
|
+
if(buf->channels == 1) {
|
242
|
+
ra_buffer_index_set(buf, i, val);
|
243
|
+
} else {
|
244
|
+
if(TYPE(val) != T_ARRAY) rb_raise(eRubyAudioError, "must pass in array for multi-channel buffer");
|
245
|
+
long length = RARRAY_LEN(val);
|
246
|
+
if(length != buf->channels) rb_raise(eRubyAudioError, "array length must match channel count");
|
247
|
+
|
248
|
+
int j;
|
249
|
+
for(j = 0; j < length; j++) {
|
250
|
+
ra_buffer_index_set(buf, i+j, rb_ary_entry(val, j));
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
// Bump real_size
|
255
|
+
if(f + 1 > buf->real_size) {
|
256
|
+
buf->real_size = f + 1;
|
257
|
+
}
|
258
|
+
|
259
|
+
return val;
|
260
|
+
}
|
261
|
+
|
262
|
+
static void ra_buffer_index_set(RA_BUFFER *buf, int i, VALUE val) {
|
263
|
+
switch(buf->type) {
|
264
|
+
case RA_BUFFER_TYPE_SHORT:
|
265
|
+
((short*)buf->data)[i] = (short)FIX2INT(val);
|
266
|
+
break;
|
267
|
+
case RA_BUFFER_TYPE_INT:
|
268
|
+
((int*)buf->data)[i] = FIX2INT(val);
|
269
|
+
break;
|
270
|
+
case RA_BUFFER_TYPE_FLOAT:
|
271
|
+
((float*)buf->data)[i] = (float)RFLOAT_VALUE(val);
|
272
|
+
break;
|
273
|
+
case RA_BUFFER_TYPE_DOUBLE:
|
274
|
+
((double*)buf->data)[i] = RFLOAT_VALUE(val);
|
275
|
+
break;
|
276
|
+
}
|
277
|
+
}
|
data/ext/ra_buffer.h
ADDED
@@ -0,0 +1,39 @@
|
|
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
|
+
int size;
|
17
|
+
int 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_channels(VALUE self);
|
30
|
+
static VALUE ra_buffer_size(VALUE self);
|
31
|
+
static VALUE ra_buffer_real_size(VALUE self);
|
32
|
+
static VALUE ra_buffer_real_size_set(VALUE self, VALUE new_real_size);
|
33
|
+
static VALUE ra_buffer_type(VALUE self);
|
34
|
+
static VALUE ra_buffer_aref(VALUE self, VALUE index);
|
35
|
+
static VALUE ra_buffer_index_get(RA_BUFFER *buf, int i);
|
36
|
+
static VALUE ra_buffer_aset(VALUE self, VALUE index, VALUE val);
|
37
|
+
static void ra_buffer_index_set(RA_BUFFER *buf, int i, VALUE val);
|
38
|
+
|
39
|
+
#endif // #ifndef RA_BUFFER_H
|
data/ext/ra_sound.c
ADDED
@@ -0,0 +1,274 @@
|
|
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
|
+
/*
|
131
|
+
* call-seq:
|
132
|
+
* snd.read(buf, frames) => integer
|
133
|
+
*
|
134
|
+
* Tries to read the given number of frames into the buffer and returns the
|
135
|
+
* number of frames actually read.
|
136
|
+
*/
|
137
|
+
static VALUE ra_sound_read(VALUE self, VALUE buf, VALUE frames) {
|
138
|
+
RA_SOUND *snd;
|
139
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
140
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
141
|
+
|
142
|
+
// Get buffer struct
|
143
|
+
RA_BUFFER *b;
|
144
|
+
Data_Get_Struct(buf, RA_BUFFER, b);
|
145
|
+
|
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
|
+
// Double-check frame count against buffer size
|
156
|
+
sf_count_t f = (sf_count_t)NUM2OFFT(frames);
|
157
|
+
if(f < 0 || f > b->size) {
|
158
|
+
rb_raise(eRubyAudioError, "frame count invalid");
|
159
|
+
}
|
160
|
+
|
161
|
+
// Read data
|
162
|
+
sf_count_t read;
|
163
|
+
switch(b->type) {
|
164
|
+
case RA_BUFFER_TYPE_SHORT:
|
165
|
+
read = sf_readf_short(snd->snd, b->data, f);
|
166
|
+
break;
|
167
|
+
case RA_BUFFER_TYPE_INT:
|
168
|
+
read = sf_readf_int(snd->snd, b->data, f);
|
169
|
+
break;
|
170
|
+
case RA_BUFFER_TYPE_FLOAT:
|
171
|
+
read = sf_readf_float(snd->snd, b->data, f);
|
172
|
+
break;
|
173
|
+
case RA_BUFFER_TYPE_DOUBLE:
|
174
|
+
read = sf_readf_double(snd->snd, b->data, f);
|
175
|
+
break;
|
176
|
+
}
|
177
|
+
b->real_size = read;
|
178
|
+
|
179
|
+
return INT2FIX(b->real_size);
|
180
|
+
}
|
181
|
+
|
182
|
+
/*
|
183
|
+
* call-seq:
|
184
|
+
* snd.write(buf) => integer
|
185
|
+
*
|
186
|
+
* Writes the entire contents of the given buffer to the sound and returns the
|
187
|
+
* number of frames written.
|
188
|
+
*/
|
189
|
+
static VALUE ra_sound_write(VALUE self, VALUE buf) {
|
190
|
+
RA_SOUND *snd;
|
191
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
192
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
193
|
+
|
194
|
+
// Get buffer struct
|
195
|
+
RA_BUFFER *b;
|
196
|
+
Data_Get_Struct(buf, RA_BUFFER, b);
|
197
|
+
|
198
|
+
// Get info struct
|
199
|
+
SF_INFO *info;
|
200
|
+
Data_Get_Struct(snd->info, SF_INFO, info);
|
201
|
+
|
202
|
+
// Check buffer channels matches actual channels
|
203
|
+
if(b->channels != info->channels) {
|
204
|
+
rb_raise(eRubyAudioError, "channel count mismatch: %d vs %d", b->channels, info->channels);
|
205
|
+
}
|
206
|
+
|
207
|
+
// Write data
|
208
|
+
sf_count_t written;
|
209
|
+
switch(b->type) {
|
210
|
+
case RA_BUFFER_TYPE_SHORT:
|
211
|
+
written = sf_writef_short(snd->snd, b->data, b->real_size);
|
212
|
+
break;
|
213
|
+
case RA_BUFFER_TYPE_INT:
|
214
|
+
written = sf_writef_int(snd->snd, b->data, b->real_size);
|
215
|
+
break;
|
216
|
+
case RA_BUFFER_TYPE_FLOAT:
|
217
|
+
written = sf_writef_float(snd->snd, b->data, b->real_size);
|
218
|
+
break;
|
219
|
+
case RA_BUFFER_TYPE_DOUBLE:
|
220
|
+
written = sf_writef_double(snd->snd, b->data, b->real_size);
|
221
|
+
break;
|
222
|
+
}
|
223
|
+
|
224
|
+
return OFFT2NUM(written);
|
225
|
+
}
|
226
|
+
|
227
|
+
/*
|
228
|
+
* call-seq:
|
229
|
+
* snd << buf => snd
|
230
|
+
*
|
231
|
+
* Writes the given buffer to the string.
|
232
|
+
*
|
233
|
+
* snd << buf1 << buf2
|
234
|
+
*/
|
235
|
+
static VALUE ra_sound_addbuf(VALUE self, VALUE buf) {
|
236
|
+
ra_sound_write(self, buf);
|
237
|
+
return self;
|
238
|
+
}
|
239
|
+
|
240
|
+
/*
|
241
|
+
* call-seq:
|
242
|
+
* snd.close => nil
|
243
|
+
*
|
244
|
+
* Closes <i>snd</i> and frees up all memory associated with the sound. The
|
245
|
+
* sound is unavailable for any further data operations; an error is raised if
|
246
|
+
* such an attempt is made. Sounds are automatically closed when they are claimed
|
247
|
+
* by the garbage collector.
|
248
|
+
*/
|
249
|
+
static VALUE ra_sound_close(VALUE self) {
|
250
|
+
RA_SOUND *snd;
|
251
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
252
|
+
if(snd->closed) rb_raise(eRubyAudioError, "closed sound");
|
253
|
+
|
254
|
+
sf_close(snd->snd);
|
255
|
+
snd->snd = NULL;
|
256
|
+
snd->closed = 1;
|
257
|
+
return Qnil;
|
258
|
+
}
|
259
|
+
|
260
|
+
static VALUE ra_sound_close_safe(VALUE self) {
|
261
|
+
return rb_rescue(ra_sound_close, self, 0, 0);
|
262
|
+
}
|
263
|
+
|
264
|
+
/*
|
265
|
+
* call-seq:
|
266
|
+
* snd.closed? => true or false
|
267
|
+
*
|
268
|
+
* Whether or not the current sound is closed to further operations.
|
269
|
+
*/
|
270
|
+
static VALUE ra_sound_closed(VALUE self) {
|
271
|
+
RA_SOUND *snd;
|
272
|
+
Data_Get_Struct(self, RA_SOUND, snd);
|
273
|
+
return snd->closed ? Qtrue : Qfalse;
|
274
|
+
}
|