ruby-audio 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }