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 CHANGED
@@ -1,9 +1,11 @@
1
1
  = ruby-audio
2
2
 
3
- Gemified release of ruby/audio. ruby-audio wraps around libsndfile to provide
4
- simplified sound reading and writing support to ruby programs. The core is the
5
- Audio::Sound class, which is a subclass of NArray. Also provided is the
6
- Audio::Soundfile class, which is a wrapper around the wonderful libsndfile.
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 Hans Fugal and Stephen Augenstein
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.2.1'
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 = ['Hans Fugal <hans@fugal.net>', 'Stephen Augenstein']
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 = Gem::Platform::RUBY
16
- s.has_rdoc = true
17
- s.files = FileList['README.rdoc', 'Rakefile', 'LICENSE', 'TODO', 'examples/**/*.rb', 'examples/**/*.wav', 'lib/**/*.rb', 'test/*.rb', 'test/*.wav', 'ext/sndfile/extconf.rb', 'ext/sndfile/sndfile.i']
18
- s.require_path = 'lib'
19
- s.extensions = ["ext/sndfile/extconf.rb"]
20
- s.test_files = Dir[*['test/*.rb']]
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 => :test
26
+ task :default => :spec
29
27
 
30
28
  # Rake gem & package routines
31
- Rake::GemPackageTask.new spec do |pkg|
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 << '--line-numbers' << '--main' << 'README.rdoc'
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
- desc "Run tests"
46
- Rake::TestTask.new do |t|
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
+ }