audite-lib 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 041f8e7065b49fc7d94f63647ac4c1f6d584f715
4
+ data.tar.gz: ccbf5a030a7523d61bf785ffa7387b9792dfcd0c
5
+ SHA512:
6
+ metadata.gz: 8ed5cc854d9472737ed0e27ca389e8be9c194480ed44b2458b84ba1f612a1af832114e15d090044b1a3a3f04206c31f02f2b402259780b035f82fe9d04770f4d
7
+ data.tar.gz: 16c86468704fb1ee735c024e5884e9bf63d8c3e7b729cfb79674daf4af41704ca59f8fd6ba988601f62f937c7813c4e04fd2d550dd37ba25e0fe8a704f7718dc
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *~
2
+ *.gem
3
+ ext/*/Makefile
4
+ ext/*/*.o
5
+ ext/*/*.bundle
6
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in audite.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2013 Matthias Georgi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ Audite - a portable mp3 player in Ruby
2
+ ======================================
3
+
4
+ Audite is a portable ruby library for playing mp3 files built on
5
+ libmp123 and portaudio.
6
+
7
+ ## Features
8
+
9
+ * Nonblocking playback using native threads
10
+ * Realtime seeking
11
+ * Realtime events
12
+ * Progress information
13
+ * Simple level meter
14
+
15
+ ## Usage
16
+
17
+ Audite ships an example player:
18
+
19
+ ```
20
+ audite test.mp3
21
+ ```
22
+
23
+ ## API Example
24
+
25
+ ```
26
+ require 'audite'
27
+
28
+ player = Audite.new
29
+
30
+ player.events.on(:complete) do
31
+ puts "COMPLETE"
32
+ end
33
+
34
+ player.events.on(:position_change) do |pos|
35
+ puts "POSITION: #{pos} seconds level #{player.level}"
36
+ end
37
+
38
+ player.load('test.mp3')
39
+ player.start_stream
40
+ player.forward(20)
41
+ player.thread.join
42
+
43
+ ```
44
+
45
+ ## Requirements
46
+
47
+ * Portaudio >= 19
48
+ * Mpg123 >= 1.14
49
+
50
+ ## OSX Install
51
+
52
+ ```
53
+ brew install portaudio
54
+ brew install mpg123
55
+
56
+ gem install audite
57
+ ```
58
+
59
+
60
+ ## Debian / Ubuntu Install
61
+ ```
62
+ apt-get install libjack0 libjack-dev
63
+ apt-get install libportaudiocpp0 portaudio19-dev libmpg123-dev
64
+ gem install audite
65
+ ```
66
+
67
+ ## Soundcloud2000
68
+
69
+ Audite provides the audio engine for [Soundcloud2000][1]
70
+
71
+ [1]: https://github.com/grobie/soundcloud2000
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/audite.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "audite-lib"
5
+ s.version = "0.4.1"
6
+ s.author = "Matthias Georgi"
7
+ s.email = "matti.georgi@gmail.com"
8
+ s.homepage = "http://georgi.github.com/audite"
9
+ s.summary = "Portable mp3 player"
10
+ s.description = "Portable mp3 player built on mpg123 and portaudio"
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.require_paths = ["lib"]
14
+
15
+ s.add_development_dependency "bundler", "~> 1.3"
16
+ s.add_development_dependency "rake"
17
+ s.add_development_dependency "rspec"
18
+
19
+ s.extra_rdoc_files = ["README.md"]
20
+
21
+ s.extensions << 'ext/mpg123/extconf.rb'
22
+ s.extensions << 'ext/portaudio/extconf.rb'
23
+ end
@@ -0,0 +1,13 @@
1
+ require 'mkmf'
2
+
3
+ unless have_header('mpg123.h')
4
+ puts "please install mpg123 headers"
5
+ exit
6
+ end
7
+
8
+ unless have_library('mpg123')
9
+ puts "please install mpg123 lib"
10
+ exit
11
+ end
12
+
13
+ create_makefile('mpg123')
@@ -0,0 +1,157 @@
1
+ #include <ruby.h>
2
+
3
+ #include <stdio.h>
4
+ #include <strings.h>
5
+ #include <mpg123.h>
6
+
7
+ void cleanup(mpg123_handle *mh)
8
+ {
9
+ mpg123_close(mh);
10
+ mpg123_delete(mh);
11
+ }
12
+
13
+ static VALUE rb_cMpg123;
14
+
15
+ VALUE rb_mpg123_new(VALUE klass, VALUE filename) {
16
+ int err = MPG123_OK;
17
+ mpg123_handle *mh;
18
+ VALUE mpg123;
19
+ long rate;
20
+ int channels, encoding;
21
+
22
+ Check_Type(filename, T_STRING);
23
+
24
+ if ((mh = mpg123_new(NULL, &err)) == NULL) {
25
+ rb_raise(rb_eStandardError, "%s", mpg123_plain_strerror(err));
26
+ }
27
+
28
+ mpg123_param(mh, MPG123_ADD_FLAGS, MPG123_FORCE_FLOAT, 0.);
29
+
30
+ if (mpg123_open(mh, (char*) RSTRING_PTR(filename)) != MPG123_OK ||
31
+ mpg123_getformat(mh, &rate, &channels, &encoding) != MPG123_OK) {
32
+ rb_raise(rb_eStandardError, "%s", mpg123_strerror(mh));
33
+ }
34
+
35
+ if (encoding != MPG123_ENC_FLOAT_32) {
36
+ rb_raise(rb_eStandardError, "bad encoding");
37
+ }
38
+
39
+ mpg123_format_none(mh);
40
+ mpg123_format(mh, rate, channels, encoding);
41
+
42
+ VALUE new_mpg123 = Data_Wrap_Struct(rb_cMpg123, 0, cleanup, mh);
43
+ rb_iv_set(new_mpg123, "@file", filename);
44
+ return new_mpg123;
45
+ }
46
+
47
+ static VALUE rb_mpg123_file(VALUE self, VALUE obj)
48
+ {
49
+ return rb_iv_get(self, "@file");
50
+ }
51
+
52
+ VALUE rb_mpg123_close(VALUE self)
53
+ {
54
+ mpg123_close(DATA_PTR(self));
55
+ return self;
56
+ }
57
+
58
+ VALUE rb_mpg123_read(VALUE self, VALUE _size)
59
+ {
60
+ int size = FIX2INT(_size);
61
+ float *buffer = malloc(size * sizeof(float));
62
+ VALUE result = rb_ary_new2(size);
63
+ mpg123_handle *mh = NULL;
64
+ size_t done = 0;
65
+ int i;
66
+ int err = MPG123_OK;
67
+
68
+ Data_Get_Struct(self, mpg123_handle, mh);
69
+ err = mpg123_read(mh, (unsigned char *) buffer, size * sizeof(float), &done);
70
+
71
+ if (err == MPG123_OK || err == MPG123_DONE) {
72
+ for (i = 0; i < size; i++) {
73
+ rb_ary_store(result, i, rb_float_new(buffer[i]));
74
+ }
75
+ free(buffer);
76
+ return result;
77
+ }
78
+ else {
79
+ free(buffer);
80
+ rb_raise(rb_eStandardError, "%s", mpg123_plain_strerror(err));
81
+ }
82
+ }
83
+
84
+ VALUE rb_mpg123_length(VALUE self)
85
+ {
86
+ /*
87
+ * mpg123_length() only returns an estimated duration
88
+ * if the song hasn't previously been scanned.
89
+ * This can be incorrect if, for example, the song is corrupted
90
+ * and cannot be played after a certain point.
91
+ * Run mpg123_scan() first to get an accurate length reading.
92
+ */
93
+ mpg123_scan(DATA_PTR(self));
94
+ return INT2FIX(mpg123_length(DATA_PTR(self)));
95
+ }
96
+
97
+ VALUE rb_mpg123_spf(VALUE self)
98
+ {
99
+ return rb_float_new(mpg123_spf(DATA_PTR(self)));
100
+ }
101
+
102
+ VALUE rb_mpg123_tpf(VALUE self)
103
+ {
104
+ return rb_float_new(mpg123_tpf(DATA_PTR(self)));
105
+ }
106
+
107
+ VALUE rb_mpg123_tell(VALUE self)
108
+ {
109
+ return INT2FIX(mpg123_tell(DATA_PTR(self)));
110
+ }
111
+
112
+ VALUE rb_mpg123_tellframe(VALUE self)
113
+ {
114
+ return INT2FIX(mpg123_tellframe(DATA_PTR(self)));
115
+ }
116
+
117
+ VALUE rb_mpg123_seek(VALUE self, VALUE offset)
118
+ {
119
+ return INT2FIX(mpg123_seek(DATA_PTR(self), FIX2INT(offset), SEEK_SET));
120
+ }
121
+
122
+ VALUE rb_mpg123_seek_frame(VALUE self, VALUE offset)
123
+ {
124
+ return INT2FIX(mpg123_seek_frame(DATA_PTR(self), FIX2INT(offset), SEEK_SET));
125
+ }
126
+
127
+ VALUE rb_mpg123_timeframe(VALUE self, VALUE seconds)
128
+ {
129
+ return INT2FIX(mpg123_timeframe(DATA_PTR(self), NUM2DBL(seconds)));
130
+ }
131
+
132
+ void Init_mpg123(void) {
133
+ int err = MPG123_OK;
134
+
135
+ err = mpg123_init();
136
+
137
+ if (err != MPG123_OK) {
138
+ rb_raise(rb_eStandardError, "%s", mpg123_plain_strerror(err));
139
+ }
140
+
141
+ rb_cMpg123 = rb_define_class("Mpg123", rb_cObject);
142
+
143
+ rb_define_singleton_method(rb_cMpg123, "new", rb_mpg123_new, 1);
144
+
145
+ rb_define_method(rb_cMpg123, "file", rb_mpg123_file, 0);
146
+
147
+ rb_define_method(rb_cMpg123, "close", rb_mpg123_close, 0);
148
+ rb_define_method(rb_cMpg123, "read", rb_mpg123_read, 1);
149
+ rb_define_method(rb_cMpg123, "length", rb_mpg123_length, 0);
150
+ rb_define_method(rb_cMpg123, "spf", rb_mpg123_spf, 0);
151
+ rb_define_method(rb_cMpg123, "tpf", rb_mpg123_tpf, 0);
152
+ rb_define_method(rb_cMpg123, "tell", rb_mpg123_tell, 0);
153
+ rb_define_method(rb_cMpg123, "tellframe", rb_mpg123_tellframe, 0);
154
+ rb_define_method(rb_cMpg123, "seek", rb_mpg123_seek, 1);
155
+ rb_define_method(rb_cMpg123, "seek_frame", rb_mpg123_seek_frame, 1);
156
+ rb_define_method(rb_cMpg123, "timeframe", rb_mpg123_timeframe, 1);
157
+ }
@@ -0,0 +1,38 @@
1
+ require 'mkmf'
2
+
3
+ unless have_header('portaudio.h')
4
+ puts "portaudio.h not found"
5
+ puts "please brew install portaudio"
6
+ puts "or apt-get install portaudio19-dev"
7
+ exit
8
+ end
9
+
10
+ unless have_header('mpg123.h')
11
+ puts "mpg123.h not found"
12
+ puts "please brew install mpg123"
13
+ puts "or apt-get install libmpg123-dev"
14
+ exit
15
+ end
16
+
17
+ unless have_library('portaudio')
18
+ puts "lib portaudio not found"
19
+ puts "brew install portaudio"
20
+ puts "or apt-get install portaudio19-dev"
21
+ exit
22
+ end
23
+
24
+ unless have_library('mpg123')
25
+ puts "lib mpg123 not found"
26
+ puts "please brew install mpg123"
27
+ puts "or apt-get install libmpg123-0"
28
+ exit
29
+ end
30
+
31
+ unless have_type('PaStreamCallbackTimeInfo', 'portaudio.h')
32
+ puts "portaudio19 not found"
33
+ puts "brew install portaudio"
34
+ puts "or apt-get install portaudio19-dev"
35
+ exit
36
+ end
37
+
38
+ create_makefile('portaudio')
@@ -0,0 +1,283 @@
1
+ #include <ruby.h>
2
+ #include <math.h>
3
+ #include <pthread.h>
4
+ #include <portaudio.h>
5
+ #include <mpg123.h>
6
+ #include <string.h>
7
+
8
+ typedef struct {
9
+ PaStream *stream;
10
+ int size;
11
+ float *buffer;
12
+ float rms;
13
+ pthread_mutex_t mutex;
14
+ pthread_cond_t cond;
15
+ } Portaudio;
16
+
17
+ static VALUE rb_cPortaudio;
18
+
19
+ void free_portaudio(void *ptr)
20
+ {
21
+ Portaudio *portaudio = (Portaudio *) ptr;
22
+
23
+ if (portaudio) {
24
+ pthread_cond_destroy(&portaudio->cond);
25
+ pthread_mutex_destroy(&portaudio->mutex);
26
+
27
+ if (portaudio->buffer) {
28
+ free(portaudio->buffer);
29
+ }
30
+
31
+ Pa_CloseStream(portaudio->stream);
32
+ }
33
+ }
34
+
35
+ static int paCallback(const void *inputBuffer,
36
+ void *outputBuffer,
37
+ unsigned long framesPerBuffer,
38
+ const PaStreamCallbackTimeInfo* timeInfo,
39
+ PaStreamCallbackFlags statusFlags,
40
+ void *userData )
41
+ {
42
+ Portaudio *portaudio = (Portaudio *) userData;
43
+ float *out = (float*) outputBuffer;
44
+ unsigned int i;
45
+
46
+ pthread_mutex_lock(&portaudio->mutex);
47
+
48
+ memcpy(out, portaudio->buffer, sizeof(float) * portaudio->size);
49
+
50
+ pthread_cond_broadcast(&portaudio->cond);
51
+ pthread_mutex_unlock(&portaudio->mutex);
52
+
53
+ return 0;
54
+ }
55
+
56
+ VALUE rb_portaudio_new(VALUE klass, VALUE framesPerBuffer, VALUE deviceName)
57
+ {
58
+ PaStreamParameters outputParameters;
59
+ const PaDeviceInfo *deviceInfo;
60
+ PaError err;
61
+ int device, numDevices, foundDevice = -1;
62
+ VALUE self;
63
+ Portaudio *portaudio = (Portaudio *) malloc(sizeof(Portaudio));
64
+
65
+ pthread_mutex_init(&portaudio->mutex, NULL);
66
+ pthread_cond_init(&portaudio->cond, NULL);
67
+
68
+ portaudio->size = FIX2INT(framesPerBuffer) * 2;
69
+ portaudio->buffer = (float *) malloc(sizeof(float) * portaudio->size);
70
+ numDevices = Pa_GetDeviceCount();
71
+ if( numDevices < 0 ) {
72
+ printf( "ERROR: Pa_CountDevices returned 0x%x\n", numDevices );
73
+ err = numDevices;
74
+ }
75
+
76
+ for ( device=0; device<numDevices; device++ ) {
77
+ deviceInfo = Pa_GetDeviceInfo( device );
78
+ if (strcmp(deviceInfo->name, StringValueCStr(deviceName)) == 0) {
79
+ foundDevice = device;
80
+ break;
81
+ }
82
+ }
83
+
84
+ if (foundDevice == -1) {
85
+ err = Pa_OpenDefaultStream(&portaudio->stream,
86
+ 0, /* no input channels */
87
+ 2, /* stereo output */
88
+ paFloat32, /* 32 bit floating point output */
89
+ 44100, /* sample rate*/
90
+ FIX2INT(framesPerBuffer),
91
+ paCallback,
92
+ (void*) portaudio);
93
+ } else {
94
+ bzero( &outputParameters, sizeof( outputParameters ) );
95
+ outputParameters.device = foundDevice;
96
+ outputParameters.channelCount = 2;
97
+ outputParameters.sampleFormat = paFloat32;
98
+ outputParameters.suggestedLatency = Pa_GetDeviceInfo(foundDevice)->defaultLowOutputLatency ;
99
+
100
+ err = Pa_OpenStream(&portaudio->stream,
101
+ NULL,
102
+ &outputParameters,
103
+ 44100,
104
+ FIX2INT(framesPerBuffer),
105
+ paNoFlag,
106
+ paCallback,
107
+ (void*) portaudio);
108
+ }
109
+
110
+ if (err != paNoError) {
111
+ rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
112
+ }
113
+
114
+ self = Data_Wrap_Struct(rb_cPortaudio, 0, free_portaudio, portaudio);
115
+
116
+ return self;
117
+ }
118
+
119
+
120
+ VALUE portaudio_wait(void *ptr)
121
+ {
122
+ Portaudio *portaudio = (Portaudio *) ptr;
123
+
124
+ pthread_cond_wait(&portaudio->cond, &portaudio->mutex);
125
+
126
+ return Qnil;
127
+ }
128
+
129
+ float rms(float *v, int n)
130
+ {
131
+ int i;
132
+ float sum = 0.0;
133
+
134
+ for (i = 0; i < n; i++) {
135
+ sum += v[i] * v[i];
136
+ }
137
+
138
+ return sqrt(sum / n);
139
+ }
140
+
141
+ VALUE rb_portaudio_write_from_mpg(VALUE self, VALUE mpg)
142
+ {
143
+ int err;
144
+ size_t done = 0;
145
+ Portaudio *portaudio;
146
+ mpg123_handle *mh = NULL;
147
+
148
+ Data_Get_Struct(self, Portaudio, portaudio);
149
+ Data_Get_Struct(mpg, mpg123_handle, mh);
150
+
151
+ err = mpg123_read(mh, (unsigned char *) portaudio->buffer, portaudio->size * sizeof(float), &done);
152
+
153
+ portaudio->rms = rms(portaudio->buffer, portaudio->size);
154
+
155
+ switch (err) {
156
+ case MPG123_OK: return ID2SYM(rb_intern("ok"));
157
+ case MPG123_DONE: return ID2SYM(rb_intern("done"));
158
+ case MPG123_NEED_MORE: return ID2SYM(rb_intern("need_more"));
159
+ }
160
+
161
+ rb_raise(rb_eStandardError, "%s", mpg123_plain_strerror(err));
162
+ }
163
+
164
+ VALUE rb_portaudio_wait(VALUE self)
165
+ {
166
+ Portaudio *portaudio;
167
+ Data_Get_Struct(self, Portaudio, portaudio);
168
+
169
+ #ifdef RUBY_UBF_IO
170
+ rb_thread_call_without_gvl(portaudio_wait, portaudio, RUBY_UBF_IO, NULL);
171
+ #else
172
+ portaudio_wait(portaudio);
173
+ #endif
174
+ return self;
175
+ }
176
+
177
+ VALUE rb_portaudio_write(VALUE self, VALUE buffer)
178
+ {
179
+ int i, len;
180
+ Portaudio *portaudio;
181
+ Data_Get_Struct(self, Portaudio, portaudio);
182
+
183
+ Check_Type(buffer, T_ARRAY);
184
+
185
+ len = RARRAY_LEN(buffer);
186
+
187
+ if (len != portaudio->size) {
188
+ rb_raise(rb_eStandardError, "array length does not match buffer size: %d != %d", len, portaudio->size);
189
+ }
190
+
191
+ for (i = 0; i < len; i++) {
192
+ portaudio->buffer[i] = NUM2DBL(rb_ary_entry(buffer, i));
193
+ }
194
+
195
+ return self;
196
+ }
197
+
198
+ VALUE rb_portaudio_rms(VALUE self)
199
+ {
200
+ Portaudio *portaudio;
201
+ Data_Get_Struct(self, Portaudio, portaudio);
202
+ return rb_float_new(portaudio->rms);
203
+ }
204
+
205
+ VALUE rb_portaudio_start(VALUE self)
206
+ {
207
+ Portaudio *portaudio;
208
+ Data_Get_Struct(self, Portaudio, portaudio);
209
+ int err = Pa_StartStream(portaudio->stream);
210
+
211
+ pthread_cond_broadcast(&portaudio->cond);
212
+
213
+ if (err != paNoError) {
214
+ rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
215
+ }
216
+
217
+ return self;
218
+ }
219
+
220
+ VALUE rb_portaudio_stop(VALUE self)
221
+ {
222
+ Portaudio *portaudio;
223
+ Data_Get_Struct(self, Portaudio, portaudio);
224
+ int err = Pa_StopStream(portaudio->stream);
225
+
226
+ pthread_cond_broadcast(&portaudio->cond);
227
+
228
+ if (err != paNoError) {
229
+ rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
230
+ }
231
+
232
+ return self;
233
+ }
234
+
235
+ VALUE rb_portaudio_stream_stopped(VALUE self)
236
+ {
237
+ Portaudio *portaudio;
238
+ Data_Get_Struct(self, Portaudio, portaudio);
239
+ int err = Pa_IsStreamStopped(portaudio->stream);
240
+
241
+ if (err == 1) {
242
+ return Qtrue;
243
+ } else if (err == 0) {
244
+ return Qfalse;
245
+ }
246
+
247
+ rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
248
+ }
249
+
250
+ VALUE rb_portaudio_close(VALUE self)
251
+ {
252
+ Portaudio *portaudio;
253
+ Data_Get_Struct(self, Portaudio, portaudio);
254
+ int err = Pa_CloseStream(portaudio->stream);
255
+
256
+ pthread_cond_broadcast(&portaudio->cond);
257
+
258
+ if (err != paNoError) {
259
+ rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
260
+ }
261
+
262
+ return self;
263
+ }
264
+
265
+ void Init_portaudio(void) {
266
+ int err = Pa_Initialize();
267
+
268
+ if (err != paNoError) {
269
+ rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
270
+ }
271
+
272
+ rb_cPortaudio = rb_define_class("Portaudio", rb_cObject);
273
+
274
+ rb_define_singleton_method(rb_cPortaudio, "new", rb_portaudio_new, 2);
275
+ rb_define_method(rb_cPortaudio, "wait", rb_portaudio_wait, 0);
276
+ rb_define_method(rb_cPortaudio, "stopped?", rb_portaudio_stream_stopped, 0);
277
+ rb_define_method(rb_cPortaudio, "close", rb_portaudio_close, 0);
278
+ rb_define_method(rb_cPortaudio, "rms", rb_portaudio_rms, 0);
279
+ rb_define_method(rb_cPortaudio, "write", rb_portaudio_write, 1);
280
+ rb_define_method(rb_cPortaudio, "write_from_mpg", rb_portaudio_write_from_mpg, 1);
281
+ rb_define_method(rb_cPortaudio, "start", rb_portaudio_start, 0);
282
+ rb_define_method(rb_cPortaudio, "stop", rb_portaudio_stop, 0);
283
+ }
data/lib/audite.rb ADDED
@@ -0,0 +1,188 @@
1
+ require 'portaudio'
2
+ require 'mpg123'
3
+
4
+ class Audite
5
+ class Events
6
+ def initialize
7
+ @handlers = Hash.new {|h,k| h[k] = [] }
8
+ end
9
+
10
+ def on(event, &block)
11
+ @handlers[event] << block
12
+ end
13
+
14
+ def trigger(event, *args)
15
+ @handlers[event].each do |handler|
16
+ handler.call(*args)
17
+ end
18
+ end
19
+ end
20
+
21
+ attr_reader :events, :active, :stream, :mp3, :thread, :file, :song_list
22
+
23
+ def initialize(buffer_size = 2**12, device_name = '')
24
+ @buffer_size = buffer_size
25
+ @device_name = device_name
26
+ @events = Events.new
27
+ @stream = Portaudio.new(@buffer_size, @device_name)
28
+ @song_list = []
29
+ end
30
+
31
+ def start_thread
32
+ @thread ||= Thread.start do
33
+ loop do
34
+ process @stream.write_from_mpg(@mp3)
35
+ @stream.wait
36
+ end
37
+ end
38
+ end
39
+
40
+ def level
41
+ @stream.rms
42
+ end
43
+
44
+ def process(status)
45
+ if [:done, :need_more].include? status
46
+ request_next_song
47
+ events.trigger(:complete)
48
+ else
49
+ events.trigger(:position_change, position)
50
+ end
51
+
52
+ rescue => e
53
+ $stderr.puts e.message
54
+ $stderr.puts e.backtrace
55
+ end
56
+
57
+ def current_song_name
58
+ File.basename mp3.file
59
+ end
60
+
61
+ def request_next_song
62
+ if songs_in_queue?
63
+ set_current_song
64
+ start_stream
65
+ else
66
+ stop_stream
67
+ end
68
+ end
69
+
70
+ def close
71
+ stream.close
72
+ exit
73
+ end
74
+
75
+ def start_stream
76
+ unless @active || !song_loaded?
77
+ @active = true
78
+ @stream.start
79
+ start_thread
80
+ events.trigger(:toggle, @active)
81
+ end
82
+ end
83
+
84
+ def stop_stream
85
+ if @active
86
+ @active = false
87
+ @thread = nil unless @thread.alive?
88
+ unless @stream.stopped?
89
+ @stream.stop
90
+ events.trigger(:toggle, @active)
91
+ end
92
+ end
93
+ end
94
+
95
+ def toggle
96
+ if @active
97
+ stop_stream
98
+ else
99
+ start_stream
100
+ end
101
+ end
102
+
103
+ def load(files)
104
+ files = [] << files unless Array === files
105
+ files.each {|file| queue file }
106
+ set_current_song
107
+ end
108
+
109
+ def song_loaded?
110
+ !@mp3.nil?
111
+ end
112
+
113
+ def set_current_song
114
+ @mp3 = song_list.shift
115
+ start_thread
116
+ end
117
+
118
+ def queue song
119
+ @song_list << Mpg123.new(song)
120
+ end
121
+
122
+ def queued_songs
123
+ @song_list.map {|s| File.basename s.file }
124
+ end
125
+
126
+ def songs_in_queue?
127
+ !@song_list.empty?
128
+ end
129
+
130
+ def time_per_frame
131
+ @mp3.tpf
132
+ end
133
+
134
+ def samples_per_frame
135
+ @mp3.spf
136
+ end
137
+
138
+ def tell
139
+ @mp3 ? @mp3.tell : 0
140
+ end
141
+
142
+ def length
143
+ @mp3 ? @mp3.length : 0
144
+ end
145
+
146
+ def seconds_to_frames(seconds)
147
+ seconds / time_per_frame
148
+ end
149
+
150
+ def seconds_to_samples(seconds)
151
+ seconds_to_frames(seconds) * samples_per_frame
152
+ end
153
+
154
+ def samples_to_seconds(samples)
155
+ samples_to_frames(samples) * time_per_frame
156
+ end
157
+
158
+ def samples_to_frames(samples)
159
+ samples / samples_per_frame
160
+ end
161
+
162
+ def seek(seconds)
163
+ if @mp3
164
+ samples = seconds_to_samples(seconds)
165
+
166
+ if (0..length).include?(samples)
167
+ @mp3.seek(samples)
168
+ events.trigger(:position_change, position)
169
+ end
170
+ end
171
+ end
172
+
173
+ def position
174
+ samples_to_seconds(tell)
175
+ end
176
+
177
+ def length_in_seconds
178
+ samples_to_seconds(length)
179
+ end
180
+
181
+ def rewind(seconds = 2)
182
+ seek(position - seconds)
183
+ end
184
+
185
+ def forward(seconds = 2)
186
+ seek(position + seconds)
187
+ end
188
+ end
data/spec/30sec.mp3 ADDED
Binary file
@@ -0,0 +1,14 @@
1
+ require 'audite'
2
+
3
+ describe Audite do
4
+ it "loads and reports on an mp3" do
5
+ player = Audite.new
6
+ player.load(File.dirname(__FILE__)+'/30sec.mp3')
7
+ player.length.should eq(1326351)
8
+ player.length_in_seconds.should eq(30.075986394557827)
9
+ end
10
+ it "generates an error on a non-existant mp3" do
11
+ player = Audite.new
12
+ expect {player.load('/tmp/file.mp3')}.to raise_error
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: audite-lib
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Georgi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Portable mp3 player built on mpg123 and portaudio
56
+ email: matti.georgi@gmail.com
57
+ executables: []
58
+ extensions:
59
+ - ext/mpg123/extconf.rb
60
+ - ext/portaudio/extconf.rb
61
+ extra_rdoc_files:
62
+ - README.md
63
+ files:
64
+ - ".gitignore"
65
+ - Gemfile
66
+ - LICENSE
67
+ - README.md
68
+ - Rakefile
69
+ - audite.gemspec
70
+ - ext/mpg123/extconf.rb
71
+ - ext/mpg123/mpg123.c
72
+ - ext/portaudio/extconf.rb
73
+ - ext/portaudio/portaudio.c
74
+ - lib/audite.rb
75
+ - spec/30sec.mp3
76
+ - spec/audite_spec.rb
77
+ homepage: http://georgi.github.com/audite
78
+ licenses: []
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.5.1
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Portable mp3 player
100
+ test_files: []