hallon-openal 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.o
19
+ *.bundle
20
+ Makefile
21
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ -fs
2
+ --colour
3
+ -Iext
4
+ -Ilib
@@ -0,0 +1,5 @@
1
+ # v0.0.2
2
+ - Update dependency information; Hallon v0.12 or higher is required.
3
+
4
+ # v0.0.1
5
+ - Initial release.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Kim Burgestrand
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ # OpenAL audio drivers for [Hallon](http://rubygems.org/gems/hallon)
2
+
3
+ This gem supplies Hallon with the ability to stream audio through your
4
+ speakers using OpenAL.
5
+
6
+ ## Installation
7
+
8
+ gem install hallon-openal
9
+
10
+ ## Issues? Bugs? Feedback?
11
+
12
+ I’d be happy to discuss any and all of these with you. Feel free to [create an issue](https://github.com/Burgestrand/hallon-openal/issues),
13
+ [email me](http://github.com/Burgestrand) or [tweet me at @Burgestrand](http://twitter.com/Burgestrand).
14
+
15
+ ## License
16
+
17
+ X11 license. See LICENSE.txt for details.
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :compile do
4
+ Dir.chdir('ext/hallon') do
5
+ sh 'ruby extconf.rb'
6
+ sh 'make'
7
+ end
8
+ end
9
+
10
+ require 'rspec/core/rake_task'
11
+ RSpec::Core::RakeTask.new
12
+
13
+ task :default => [:compile, :spec]
@@ -0,0 +1,15 @@
1
+ require 'mkmf'
2
+
3
+ def error(message)
4
+ abort "[ERROR] #{message}"
5
+ end
6
+
7
+ $CFLAGS << ' -ggdb -O0 -Wextra'
8
+
9
+ error 'Missing ruby header' unless have_header 'ruby.h'
10
+ error 'Missing OpenAL/alc.h' unless have_header 'OpenAL/alc.h'
11
+ error 'Missing OpenAL/al.h' unless have_header 'OpenAL/al.h'
12
+
13
+ with_ldflags('-framework OpenAL') { RUBY_PLATFORM =~ /darwin/ }
14
+
15
+ create_makefile('openal_ext')
@@ -0,0 +1,377 @@
1
+ #include <ruby.h>
2
+ #include <OpenAL/alc.h>
3
+ #include <OpenAL/al.h>
4
+ #include <unistd.h>
5
+
6
+ #if DEBUG_H
7
+ # define DEBUG printf
8
+ #else
9
+ # define DEBUG(...) //
10
+ #endif
11
+
12
+ // How many audio buffers to keep
13
+ #define NUM_BUFFERS 3
14
+
15
+ // Globals
16
+ ID oa_iv_playing;
17
+ ID oa_iv_format;
18
+ ID oa_id_call;
19
+ ID oa_id_puts;
20
+ VALUE oa_key_channels;
21
+ VALUE oa_key_rate;
22
+ VALUE oa_key_type;
23
+
24
+ // struct information stored
25
+ // with the OpenAL driver instance
26
+ typedef struct
27
+ {
28
+ ALCdevice *device;
29
+ ALCcontext *context;
30
+ ALuint buffers[NUM_BUFFERS];
31
+ ALuint source;
32
+ } oa_struct_t;
33
+
34
+ // Utility
35
+
36
+ #define SYM2STR(x) rb_id2name(SYM2ID((x)))
37
+ #define STR2SYM(x) ID2SYM(rb_intern((x)))
38
+
39
+ #define OA_CHECK_ERRORS(msg) do { \
40
+ ALenum _error; \
41
+ if ((_error = alGetError()) != AL_NO_ERROR) \
42
+ { \
43
+ rb_raise(rb_eRuntimeError, "OpenAL error: %u (%s)", _error, (msg)); \
44
+ } \
45
+ } while(0)
46
+
47
+ static inline oa_struct_t* oa_struct(VALUE self)
48
+ {
49
+ oa_struct_t *data_ptr;
50
+ Data_Get_Struct(self, oa_struct_t, data_ptr);
51
+ return data_ptr;
52
+ }
53
+
54
+ static inline void oa_ensure_playing(VALUE self)
55
+ {
56
+ ALint state;
57
+ ALuint source = oa_struct(self)->source;
58
+ VALUE playing = rb_ivar_get(self, oa_iv_playing);
59
+
60
+ alGetSourcei(source, AL_SOURCE_STATE, &state);
61
+ OA_CHECK_ERRORS("AL_SOURCE_STATE");
62
+
63
+ if (RTEST(playing) && state != AL_PLAYING)
64
+ {
65
+ alSourcePlay(source);
66
+ OA_CHECK_ERRORS("alSourcePlay (forced continue)");
67
+ }
68
+ }
69
+
70
+ static inline void oa_puts(const char *message)
71
+ {
72
+ VALUE rbmessage = rb_str_new2(message);
73
+ rb_funcall(rb_cObject, oa_id_puts, 1, rbmessage);
74
+ }
75
+
76
+ static VALUE oa_format_get(VALUE self);
77
+
78
+ static inline int _oa_format_channels(VALUE self)
79
+ {
80
+ VALUE channels = rb_hash_aref(oa_format_get(self), oa_key_channels);
81
+ return FIX2INT(channels);
82
+ }
83
+
84
+ static inline int _oa_format_rate(VALUE self)
85
+ {
86
+ VALUE rate = rb_hash_aref(oa_format_get(self), oa_key_rate);
87
+ return FIX2LONG(rate);
88
+ }
89
+
90
+ static inline VALUE _oa_format_type(VALUE self)
91
+ {
92
+ VALUE size = rb_hash_aref(oa_format_get(self), oa_key_type);
93
+ return size;
94
+ }
95
+
96
+ // implementation
97
+
98
+ static void oa_free(oa_struct_t *data_ptr)
99
+ {
100
+ int i;
101
+
102
+ // exit early if we have no data_ptr at all
103
+ if ( ! data_ptr) return;
104
+
105
+ // deleting a source stops it from playing and
106
+ // then destroys it
107
+ if (data_ptr->source)
108
+ alDeleteSources(1, &data_ptr->source);
109
+
110
+ // now that the source is gone, we can safely delete buffers
111
+ if (data_ptr->buffers[0])
112
+ alDeleteBuffers(NUM_BUFFERS, data_ptr->buffers);
113
+
114
+ // docs tell us to do this to make sure
115
+ // our source is not the current one
116
+ alcMakeContextCurrent(NULL);
117
+
118
+ if (data_ptr->context)
119
+ alcDestroyContext(data_ptr->context);
120
+
121
+ if (data_ptr->device)
122
+ alcCloseDevice(data_ptr->device);
123
+
124
+ xfree(data_ptr);
125
+ }
126
+
127
+ static VALUE oa_allocate(VALUE klass)
128
+ {
129
+ oa_struct_t *data_ptr;
130
+ return Data_Make_Struct(klass, oa_struct_t, NULL, oa_free, data_ptr);
131
+ }
132
+
133
+ static VALUE oa_initialize(VALUE self)
134
+ {
135
+ oa_struct_t *data_ptr;
136
+ ALenum error = AL_NO_ERROR;
137
+
138
+ // initialize openal
139
+ Data_Get_Struct(self, oa_struct_t, data_ptr);
140
+
141
+ data_ptr->device = alcOpenDevice(NULL);
142
+ if ( ! data_ptr->device)
143
+ {
144
+ rb_raise(rb_eRuntimeError, "failed to open device");
145
+ }
146
+
147
+ data_ptr->context = alcCreateContext(data_ptr->device, NULL);
148
+ if ( ! data_ptr->context)
149
+ {
150
+ rb_raise(rb_eRuntimeError, "failed to create context");
151
+ }
152
+
153
+ // reset error state
154
+ alGetError();
155
+
156
+ alcMakeContextCurrent(data_ptr->context);
157
+ OA_CHECK_ERRORS("context current");
158
+
159
+ // Set some defualt properties
160
+ alListenerf(AL_GAIN, 1.0f);
161
+ alDistanceModel(AL_NONE);
162
+ OA_CHECK_ERRORS("listener/distance");
163
+
164
+ // generate some buffers
165
+ alGenBuffers((ALsizei)NUM_BUFFERS, data_ptr->buffers);
166
+ OA_CHECK_ERRORS("generate buffers");
167
+
168
+ // generate our source
169
+ alGenSources(1, &data_ptr->source);
170
+ OA_CHECK_ERRORS("gen sources");
171
+ }
172
+
173
+ static VALUE oa_play(VALUE self)
174
+ {
175
+ rb_ivar_set(self, oa_iv_playing, Qtrue);
176
+ alSourcePlay(oa_struct(self)->source);
177
+ return self;
178
+ }
179
+
180
+ static VALUE oa_stop(VALUE self)
181
+ {
182
+ ALuint source = oa_struct(self)->source;
183
+ rb_ivar_set(self, oa_iv_playing, Qfalse);
184
+
185
+ // remove all buffers from the source
186
+ alSourcei(source, AL_BUFFER, 0);
187
+ OA_CHECK_ERRORS("remove buffers!");
188
+
189
+ // and stop the source
190
+ alSourceStop(source);
191
+ OA_CHECK_ERRORS("stop!");
192
+
193
+ return self;
194
+ }
195
+
196
+ static VALUE oa_pause(VALUE self)
197
+ {
198
+ rb_ivar_set(self, oa_iv_playing, Qfalse);
199
+ alSourcePause(oa_struct(self)->source);
200
+ OA_CHECK_ERRORS("pause!");
201
+ return self;
202
+ }
203
+
204
+ static VALUE oa_drops(VALUE self)
205
+ {
206
+ return INT2NUM(0);
207
+ }
208
+
209
+ static ALuint find_empty_buffer(VALUE self)
210
+ {
211
+ ALuint empty_buffer;
212
+
213
+ oa_struct_t *data_ptr = oa_struct(self);
214
+ ALuint source = data_ptr->source;
215
+ ALuint *buffers = data_ptr->buffers;
216
+
217
+ ALint num_queued = 0;
218
+ alGetSourcei(source, AL_BUFFERS_QUEUED, &num_queued);
219
+ OA_CHECK_ERRORS("AL_BUFFERS_QUEUED");
220
+
221
+ if (num_queued < NUM_BUFFERS)
222
+ {
223
+ empty_buffer = buffers[num_queued];
224
+ }
225
+ else
226
+ {
227
+ int processed;
228
+ struct timeval poll_time;
229
+ poll_time.tv_sec = 0;
230
+ poll_time.tv_usec = 100; /* 0.000100 sec */
231
+
232
+ for (processed = 0; processed == 0; rb_thread_wait_for(poll_time))
233
+ {
234
+ alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
235
+ OA_CHECK_ERRORS("AL_BUFFERS_PROCESSED");
236
+ }
237
+
238
+ alSourceUnqueueBuffers(source, 1, &empty_buffer);
239
+ OA_CHECK_ERRORS("alSourceUnqueueBuffers");
240
+ }
241
+
242
+ return empty_buffer;
243
+ }
244
+
245
+ static VALUE oa_format_get(VALUE self)
246
+ {
247
+ return rb_ivar_get(self, oa_iv_format);
248
+ }
249
+
250
+ static VALUE oa_format_set(VALUE self, VALUE format)
251
+ {
252
+ rb_ivar_set(self, oa_iv_format, format);
253
+ return format;
254
+ }
255
+
256
+ static VALUE oa_stream(VALUE self)
257
+ {
258
+ VALUE int16ne = STR2SYM("int16");
259
+ ALuint source = oa_struct(self)->source;
260
+ signed short *sample_ary = NULL;
261
+
262
+ for (;;)
263
+ {
264
+ // make sure we have no preattached buffers
265
+ alSourcei(source, AL_BUFFER, 0);
266
+ OA_CHECK_ERRORS("detach all buffers from the source");
267
+
268
+ // make sure we’re not playing audio
269
+ alSourceStop(source);
270
+ OA_CHECK_ERRORS("reset driver");
271
+
272
+ // read the format (it never changes in the inner loop)
273
+ int f_channels = _oa_format_channels(self);
274
+ int f_rate = _oa_format_rate(self);
275
+ VALUE f_type = _oa_format_type(self);
276
+ size_t f_size = 0;
277
+
278
+ DEBUG("%d channels %d rate", f_channels, f_rate);
279
+
280
+ // each time we buffer down there, it’ll be for about .5s of audio each time
281
+ int sample_ary_frames = f_rate / 2;
282
+ int sample_ary_length = sample_ary_frames * f_channels; // integer division
283
+ xfree(sample_ary); // ruby handles NULL pointer too
284
+
285
+ if (rb_eql(f_type, int16ne))
286
+ {
287
+ sample_ary = ALLOCA_N(short, sample_ary_length);
288
+ f_size = sizeof(short);
289
+ }
290
+ else
291
+ {
292
+ printf("Hallon::OpenAL#stream -> cannot handle format size %s!", SYM2STR(f_type));
293
+ rb_notimplement();
294
+ }
295
+
296
+ for (;;)
297
+ {
298
+ // pull some audio out of hallon
299
+ VALUE frames = rb_yield(INT2FIX(sample_ary_frames));
300
+
301
+ // if we received nil, it means format changed; the new
302
+ // format is already in @format, so we reinitialize!
303
+ if ( ! RTEST(frames))
304
+ {
305
+ break;
306
+ }
307
+
308
+ ALuint buffer = find_empty_buffer(self);
309
+ OA_CHECK_ERRORS("find_empty_buffer");
310
+
311
+ // convert the frames from ruby to C
312
+ int num_current_samples = ((int) RARRAY_LEN(frames)) * f_channels;
313
+
314
+ VALUE frame, sample;
315
+ int i, rb_i, rb_j;
316
+ for (i = 0; i < num_current_samples; ++i)
317
+ {
318
+ rb_i = i / f_channels; // integer division
319
+ rb_j = i % f_channels;
320
+
321
+ frame = RARRAY_PTR(frames)[rb_i];
322
+ sample = RARRAY_PTR(frame)[rb_j];
323
+
324
+ long value = FIX2LONG(sample);
325
+
326
+ sample_ary[i] = (short) value;
327
+ }
328
+
329
+ DEBUG("%d +%d\n", buffer, num_current_samples);
330
+
331
+ // pucker up all the params
332
+ ALenum type = f_channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
333
+ ALsizei size = f_size * num_current_samples;
334
+ ALsizei freq = f_rate;
335
+
336
+ // queue the data!
337
+ alBufferData(buffer, type, sample_ary, size, freq);
338
+ OA_CHECK_ERRORS("buffer data");
339
+
340
+ alSourceQueueBuffers(source, 1, &buffer);
341
+ OA_CHECK_ERRORS("queue a buffer");
342
+
343
+ // OpenAL transitions to :stopped state if we cannot
344
+ // keep buffers properly feeded — but we want it to
345
+ // play as soon as it can if we’re playing, so fix it
346
+ oa_ensure_playing(self);
347
+ }
348
+ }
349
+
350
+ return Qtrue;
351
+ }
352
+
353
+ void Init_openal_ext(void)
354
+ {
355
+ VALUE mHallon = rb_const_get(rb_cObject, rb_intern("Hallon"));
356
+ VALUE cOpenAL = rb_define_class_under(mHallon, "OpenAL", rb_cObject);
357
+
358
+ oa_iv_playing = rb_intern("@playing");
359
+ oa_id_call = rb_intern("call");
360
+ oa_id_puts = rb_intern("puts");
361
+ oa_iv_format = rb_intern("format");
362
+ oa_key_channels = STR2SYM("channels");
363
+ oa_key_rate = STR2SYM("rate");
364
+ oa_key_type = STR2SYM("type");
365
+
366
+ rb_define_alloc_func(cOpenAL, oa_allocate);
367
+
368
+ rb_define_method(cOpenAL, "initialize", oa_initialize, 0);
369
+ rb_define_method(cOpenAL, "play", oa_play, 0);
370
+ rb_define_method(cOpenAL, "stop", oa_stop, 0);
371
+ rb_define_method(cOpenAL, "pause", oa_pause, 0);
372
+ rb_define_method(cOpenAL, "stream", oa_stream, 0);
373
+ rb_define_method(cOpenAL, "format=", oa_format_set, 1);
374
+ rb_define_method(cOpenAL, "format", oa_format_get, 0);
375
+
376
+ rb_define_method(cOpenAL, "drops", oa_drops, 0);
377
+ }
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/hallon/openal/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "hallon-openal"
6
+
7
+ gem.authors = ["Kim Burgestrand"]
8
+ gem.email = ["kim@burgestrand.se"]
9
+ gem.summary = %q{OpenAL audio drivers for Hallon: http://rubygems.org/gems/hallon}
10
+
11
+ gem.files = `git ls-files`.split("\n")
12
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
+ gem.require_paths = ["lib", "ext"]
14
+ gem.extensions = ["ext/hallon/extconf.rb"]
15
+ gem.version = Hallon::OpenAL::VERSION
16
+
17
+ gem.add_dependency 'hallon', '~> 0.13'
18
+ gem.add_development_dependency 'rspec', '~> 2.7'
19
+ end
@@ -0,0 +1 @@
1
+ require 'hallon/openal'
@@ -0,0 +1,3 @@
1
+ require 'hallon'
2
+ require 'hallon/openal/version'
3
+ require 'hallon/openal_ext'
@@ -0,0 +1,5 @@
1
+ module Hallon
2
+ class OpenAL
3
+ VERSION = '0.0.2'
4
+ end
5
+ end
@@ -0,0 +1,48 @@
1
+ require 'bundler/setup'
2
+ require 'hallon/openal'
3
+
4
+ describe Hallon::OpenAL do
5
+ let(:klass) { described_class }
6
+ let(:format) { Hash.new }
7
+ subject { klass.new(format) }
8
+
9
+ describe "#initialize" do
10
+ it "should raise an error if not given a format" do
11
+ expect { klass.new {} }.to raise_error(ArgumentError)
12
+ end
13
+ end
14
+
15
+ describe "#start" do
16
+ it "should not raise an error" do
17
+ expect { subject.start }.to_not raise_error
18
+ end
19
+ end
20
+
21
+ describe "#stop" do
22
+ it "should not raise an error" do
23
+ expect { subject.stop }.to_not raise_error
24
+ end
25
+ end
26
+
27
+ describe "#pause" do
28
+ it "should not raise an error" do
29
+ expect { subject.pause }.to_not raise_error
30
+ end
31
+ end
32
+
33
+ describe "#drops" do
34
+ it "should always return zero" do
35
+ subject.drops.should be_zero
36
+ end
37
+ end
38
+
39
+ describe "#format" do
40
+ it "should be settable and gettable" do
41
+ format = { channels: 1, rate: 44100 }
42
+
43
+ subject.format.should == {}
44
+ subject.format = format
45
+ subject.format.should eq format
46
+ end
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hallon-openal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kim Burgestrand
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: hallon
16
+ requirement: &70281050669480 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.13'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70281050669480
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &70281050668320 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.7'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70281050668320
36
+ description:
37
+ email:
38
+ - kim@burgestrand.se
39
+ executables: []
40
+ extensions:
41
+ - ext/hallon/extconf.rb
42
+ extra_rdoc_files: []
43
+ files:
44
+ - .gitignore
45
+ - .rspec
46
+ - CHANGELOG.md
47
+ - Gemfile
48
+ - LICENSE.txt
49
+ - README.md
50
+ - Rakefile
51
+ - ext/hallon/extconf.rb
52
+ - ext/hallon/openal_ext.c
53
+ - hallon-openal.gemspec
54
+ - lib/hallon-openal.rb
55
+ - lib/hallon/openal.rb
56
+ - lib/hallon/openal/version.rb
57
+ - spec/hallon_openal_spec.rb
58
+ homepage:
59
+ licenses: []
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ - ext
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.12
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: ! 'OpenAL audio drivers for Hallon: http://rubygems.org/gems/hallon'
83
+ test_files:
84
+ - spec/hallon_openal_spec.rb
85
+ has_rdoc: