beeps 0.1.31 → 0.1.33
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.
- checksums.yaml +4 -4
- data/.doc/ext/beeps/adsr.cpp +139 -0
- data/.doc/ext/beeps/analyser.cpp +128 -0
- data/.doc/ext/beeps/beeps.cpp +9 -1
- data/.doc/ext/beeps/file_in.cpp +55 -9
- data/.doc/ext/beeps/gain.cpp +64 -0
- data/.doc/ext/beeps/mic_in.cpp +83 -0
- data/.doc/ext/beeps/native.cpp +20 -8
- data/.doc/ext/beeps/oscillator.cpp +88 -0
- data/.doc/ext/beeps/pitch_shift.cpp +64 -0
- data/.doc/ext/beeps/processor.cpp +31 -2
- data/.doc/ext/beeps/sound.cpp +90 -7
- data/.doc/ext/beeps/sound_player.cpp +156 -0
- data/.doc/ext/beeps/time_stretch.cpp +64 -0
- data/.github/workflows/release-gem.yml +62 -0
- data/.github/workflows/test.yml +0 -6
- data/.github/workflows/utils.rb +13 -5
- data/ChangeLog.md +13 -0
- data/Rakefile +27 -6
- data/VERSION +1 -1
- data/beeps.gemspec +2 -2
- data/ext/beeps/adsr.cpp +150 -0
- data/ext/beeps/analyser.cpp +134 -0
- data/ext/beeps/beeps.cpp +10 -1
- data/ext/beeps/extconf.rb +1 -2
- data/ext/beeps/file_in.cpp +60 -9
- data/ext/beeps/gain.cpp +67 -0
- data/ext/beeps/mic_in.cpp +88 -0
- data/ext/beeps/native.cpp +20 -8
- data/ext/beeps/oscillator.cpp +93 -0
- data/ext/beeps/pitch_shift.cpp +67 -0
- data/ext/beeps/processor.cpp +34 -2
- data/ext/beeps/sound.cpp +99 -7
- data/ext/beeps/sound_player.cpp +169 -0
- data/ext/beeps/time_stretch.cpp +67 -0
- data/include/beeps/beeps.h +2 -1
- data/include/beeps/filter.h +179 -0
- data/include/beeps/generator.h +120 -0
- data/include/beeps/processor.h +37 -68
- data/include/beeps/ruby/filter.h +78 -0
- data/include/beeps/ruby/generator.h +60 -0
- data/include/beeps/ruby/processor.h +5 -45
- data/include/beeps/ruby/sound.h +14 -3
- data/include/beeps/signals.h +10 -4
- data/include/beeps/sound.h +67 -2
- data/lib/beeps/beeps.rb +6 -1
- data/lib/beeps/processor.rb +95 -15
- data/lib/beeps/sound.rb +29 -2
- data/src/adsr.cpp +245 -0
- data/src/analyser.cpp +254 -0
- data/src/beeps.cpp +11 -2
- data/src/file_in.cpp +94 -0
- data/src/gain.cpp +55 -0
- data/src/mic_in.cpp +262 -0
- data/src/mic_in.h +20 -0
- data/src/openal.cpp +2 -1
- data/src/oscillator.cpp +145 -0
- data/src/osx/signals.mm +83 -0
- data/src/pitch_shift.cpp +82 -0
- data/src/processor.cpp +202 -88
- data/src/processor.h +98 -0
- data/src/signals.cpp +326 -20
- data/src/signals.h +192 -2
- data/src/sound.cpp +735 -113
- data/src/sound.h +6 -1
- data/src/time_stretch.cpp +91 -0
- data/test/helper.rb +2 -1
- data/test/test_beeps.rb +10 -7
- data/test/test_beeps_init.rb +18 -0
- data/test/test_file_in.rb +15 -0
- data/test/test_processor.rb +50 -0
- data/test/test_sound.rb +87 -11
- data/test/test_sound_player.rb +134 -0
- metadata +55 -17
- data/.doc/ext/beeps/sawtooth_wave.cpp +0 -61
- data/.doc/ext/beeps/sine_wave.cpp +0 -61
- data/.doc/ext/beeps/square_wave.cpp +0 -61
- data/.github/workflows/release.yml +0 -34
- data/ext/beeps/sawtooth_wave.cpp +0 -64
- data/ext/beeps/sine_wave.cpp +0 -64
- data/ext/beeps/square_wave.cpp +0 -64
data/src/sound.cpp
CHANGED
@@ -1,11 +1,15 @@
|
|
1
|
-
#include "
|
1
|
+
#include "sound.h"
|
2
2
|
|
3
3
|
|
4
4
|
#include <limits.h>
|
5
|
+
#include <memory>
|
6
|
+
#include <algorithm>
|
5
7
|
#include "Stk.h"
|
6
|
-
#include "beeps/
|
8
|
+
#include "beeps/beeps.h"
|
7
9
|
#include "beeps/exception.h"
|
10
|
+
#include "beeps/generator.h"
|
8
11
|
#include "openal.h"
|
12
|
+
#include "processor.h"
|
9
13
|
#include "signals.h"
|
10
14
|
|
11
15
|
|
@@ -20,55 +24,229 @@ namespace Beeps
|
|
20
24
|
{
|
21
25
|
|
22
26
|
|
23
|
-
|
27
|
+
struct SoundBuffer
|
28
|
+
{
|
29
|
+
|
30
|
+
SoundBuffer (bool create = false)
|
31
|
+
{
|
32
|
+
if (create) self->create();
|
33
|
+
}
|
34
|
+
|
35
|
+
SoundBuffer (const Signals& signals)
|
36
|
+
{
|
37
|
+
self->create();
|
38
|
+
write(signals);
|
39
|
+
}
|
40
|
+
|
41
|
+
SoundBuffer (ALint id)
|
42
|
+
{
|
43
|
+
self->id = id;
|
44
|
+
}
|
45
|
+
|
46
|
+
void clear ()
|
47
|
+
{
|
48
|
+
self->clear();
|
49
|
+
}
|
50
|
+
|
51
|
+
uint write (const Signals& signals)
|
52
|
+
{
|
53
|
+
assert(signals);
|
54
|
+
|
55
|
+
if (!*this)
|
56
|
+
invalid_state_error(__FILE__, __LINE__);
|
57
|
+
|
58
|
+
double sample_rate = signals.sample_rate();
|
59
|
+
uint nchannels = signals.nchannels();
|
60
|
+
uint nsamples = signals.nsamples();
|
61
|
+
assert(sample_rate > 0 && nchannels > 0 && nsamples > 0);
|
62
|
+
|
63
|
+
const Frames* frames = Signals_get_frames(&signals);
|
64
|
+
assert(frames);
|
65
|
+
|
66
|
+
std::vector<short> buffer;
|
67
|
+
buffer.reserve(nsamples * nchannels);
|
68
|
+
for (uint sample = 0; sample < nsamples; ++sample)
|
69
|
+
for (uint channel = 0; channel < nchannels; ++channel)
|
70
|
+
buffer.push_back((*frames)(sample, channel) * SHRT_MAX);
|
71
|
+
|
72
|
+
alBufferData(
|
73
|
+
self->id,
|
74
|
+
nchannels == 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
|
75
|
+
&buffer[0],
|
76
|
+
sizeof(short) * nsamples * nchannels,
|
77
|
+
sample_rate);
|
78
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
79
|
+
|
80
|
+
return nsamples;
|
81
|
+
}
|
82
|
+
|
83
|
+
operator bool () const
|
84
|
+
{
|
85
|
+
return self->is_valid();
|
86
|
+
}
|
87
|
+
|
88
|
+
bool operator ! () const
|
89
|
+
{
|
90
|
+
return !operator bool();
|
91
|
+
}
|
92
|
+
|
93
|
+
struct Data
|
94
|
+
{
|
95
|
+
|
96
|
+
ALint id = -1;
|
97
|
+
|
98
|
+
bool owner = false;
|
99
|
+
|
100
|
+
~Data ()
|
101
|
+
{
|
102
|
+
clear();
|
103
|
+
}
|
104
|
+
|
105
|
+
void create ()
|
106
|
+
{
|
107
|
+
clear();
|
108
|
+
|
109
|
+
ALuint id_ = 0;
|
110
|
+
alGenBuffers(1, &id_);
|
111
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
112
|
+
|
113
|
+
id = id_;
|
114
|
+
owner = true;
|
115
|
+
}
|
116
|
+
|
117
|
+
void clear ()
|
118
|
+
{
|
119
|
+
if (owner && id >= 0)
|
120
|
+
{
|
121
|
+
ALuint id_ = id;
|
122
|
+
alDeleteBuffers(1, &id_);
|
123
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
124
|
+
}
|
125
|
+
|
126
|
+
id = -1;
|
127
|
+
owner = false;
|
128
|
+
}
|
129
|
+
|
130
|
+
bool is_valid () const
|
131
|
+
{
|
132
|
+
return id >= 0;
|
133
|
+
}
|
134
|
+
|
135
|
+
};// Data
|
136
|
+
|
137
|
+
Xot::PSharedImpl<Data> self;
|
138
|
+
|
139
|
+
};// SoundBuffer
|
24
140
|
|
25
141
|
|
26
142
|
struct SoundSource
|
27
143
|
{
|
28
144
|
|
29
|
-
|
145
|
+
void create ()
|
146
|
+
{
|
147
|
+
ALuint id_ = 0;
|
148
|
+
alGenSources(1, &id_);
|
149
|
+
if (OpenAL_no_error()) self->id = id_;
|
150
|
+
}
|
151
|
+
|
152
|
+
void clear ()
|
153
|
+
{
|
154
|
+
stop();
|
155
|
+
self->clear();
|
156
|
+
}
|
30
157
|
|
31
|
-
|
158
|
+
SoundSource reuse ()
|
159
|
+
{
|
160
|
+
stop();
|
161
|
+
set_gain(1);
|
162
|
+
set_loop(false);
|
32
163
|
|
33
|
-
|
164
|
+
SoundSource source;
|
165
|
+
source.self.swap(self);
|
166
|
+
return source;
|
167
|
+
}
|
34
168
|
|
35
|
-
|
169
|
+
void attach (const SoundBuffer& buffer)
|
36
170
|
{
|
37
|
-
|
38
|
-
alGenSources(1, &id_);
|
39
|
-
if (!OpenAL_no_error()) return Ptr();
|
171
|
+
assert(buffer);
|
40
172
|
|
41
|
-
|
173
|
+
if (!*this)
|
174
|
+
invalid_state_error(__FILE__, __LINE__);
|
175
|
+
|
176
|
+
alSourcei(self->id, AL_BUFFER, buffer.self->id);
|
177
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
42
178
|
}
|
43
179
|
|
44
|
-
|
180
|
+
void queue (const SoundBuffer& buffer)
|
45
181
|
{
|
46
|
-
|
182
|
+
assert(buffer);
|
47
183
|
|
48
|
-
ALuint
|
49
|
-
|
184
|
+
ALuint id = buffer.self->id;
|
185
|
+
alSourceQueueBuffers(self->id, 1, &id);
|
50
186
|
OpenAL_check_error(__FILE__, __LINE__);
|
187
|
+
|
188
|
+
LOG("queue: %d", buffer.self->id);
|
51
189
|
}
|
52
190
|
|
53
|
-
|
191
|
+
bool unqueue (SoundBuffer* buffer = NULL)
|
54
192
|
{
|
55
|
-
|
193
|
+
ALint count = 0;
|
194
|
+
alGetSourcei(self->id, AL_BUFFERS_PROCESSED, &count);
|
195
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
196
|
+
|
197
|
+
if (count <= 0) return false;
|
198
|
+
|
199
|
+
ALuint id = 0;
|
200
|
+
alSourceUnqueueBuffers(self->id, 1, &id);
|
201
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
202
|
+
|
203
|
+
if (buffer) *buffer = SoundBuffer((ALint) id);
|
204
|
+
return true;
|
205
|
+
}
|
56
206
|
|
207
|
+
void play ()
|
208
|
+
{
|
57
209
|
if (!*this)
|
58
210
|
invalid_state_error(__FILE__, __LINE__);
|
59
211
|
|
60
|
-
|
61
|
-
|
212
|
+
alSourcePlay(self->id);
|
213
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
214
|
+
}
|
215
|
+
|
216
|
+
void pause ()
|
217
|
+
{
|
218
|
+
if (!*this) return;
|
219
|
+
|
220
|
+
alSourcePause(self->id);
|
221
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
222
|
+
}
|
223
|
+
|
224
|
+
void rewind ()
|
225
|
+
{
|
226
|
+
if (!*this) return;
|
227
|
+
|
228
|
+
alSourceRewind(self->id);
|
62
229
|
OpenAL_check_error(__FILE__, __LINE__);
|
63
230
|
}
|
64
231
|
|
65
232
|
void stop ()
|
66
233
|
{
|
67
|
-
if (!*this)
|
68
|
-
|
234
|
+
if (!*this) return;
|
235
|
+
|
236
|
+
alSourceStop(self->id);
|
237
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
69
238
|
|
70
|
-
|
239
|
+
ALint type = 0;
|
240
|
+
alGetSourcei(self->id, AL_SOURCE_TYPE, &type);
|
71
241
|
OpenAL_check_error(__FILE__, __LINE__);
|
242
|
+
|
243
|
+
if (type == AL_STREAMING)
|
244
|
+
while (unqueue());
|
245
|
+
else if (type == AL_STATIC)
|
246
|
+
{
|
247
|
+
alSourcei(self->id, AL_BUFFER, 0);
|
248
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
249
|
+
}
|
72
250
|
}
|
73
251
|
|
74
252
|
bool is_playing () const
|
@@ -76,15 +254,78 @@ namespace Beeps
|
|
76
254
|
if (!*this) return false;
|
77
255
|
|
78
256
|
ALint state = 0;
|
79
|
-
alGetSourcei(id, AL_SOURCE_STATE, &state);
|
257
|
+
alGetSourcei(self->id, AL_SOURCE_STATE, &state);
|
80
258
|
OpenAL_check_error(__FILE__, __LINE__);
|
81
259
|
|
82
260
|
return state == AL_PLAYING;
|
83
261
|
}
|
84
262
|
|
263
|
+
bool is_paused () const
|
264
|
+
{
|
265
|
+
if (!*this) return false;
|
266
|
+
|
267
|
+
ALint state = 0;
|
268
|
+
alGetSourcei(self->id, AL_SOURCE_STATE, &state);
|
269
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
270
|
+
|
271
|
+
return state == AL_PAUSED;
|
272
|
+
}
|
273
|
+
|
274
|
+
bool is_stopped () const
|
275
|
+
{
|
276
|
+
if (!*this) return true;
|
277
|
+
|
278
|
+
ALint state = 0;
|
279
|
+
alGetSourcei(self->id, AL_SOURCE_STATE, &state);
|
280
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
281
|
+
|
282
|
+
return state == AL_STOPPED;
|
283
|
+
}
|
284
|
+
|
285
|
+
void set_gain (float gain)
|
286
|
+
{
|
287
|
+
if (gain < 0)
|
288
|
+
argument_error(__FILE__, __LINE__);
|
289
|
+
|
290
|
+
if (!*this) return;
|
291
|
+
|
292
|
+
alSourcef(self->id, AL_GAIN, gain);
|
293
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
294
|
+
}
|
295
|
+
|
296
|
+
float gain () const
|
297
|
+
{
|
298
|
+
float gain = 1;
|
299
|
+
if (!*this) return gain;
|
300
|
+
|
301
|
+
alGetSourcef(self->id, AL_GAIN, &gain);
|
302
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
303
|
+
|
304
|
+
return gain;
|
305
|
+
}
|
306
|
+
|
307
|
+
void set_loop (bool loop)
|
308
|
+
{
|
309
|
+
if (!*this) return;
|
310
|
+
|
311
|
+
alSourcei(self->id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
|
312
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
313
|
+
}
|
314
|
+
|
315
|
+
bool loop () const
|
316
|
+
{
|
317
|
+
if (!*this) return false;
|
318
|
+
|
319
|
+
ALint loop = AL_FALSE;
|
320
|
+
alGetSourcei(self->id, AL_LOOPING, &loop);
|
321
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
322
|
+
|
323
|
+
return loop != AL_FALSE;
|
324
|
+
}
|
325
|
+
|
85
326
|
operator bool () const
|
86
327
|
{
|
87
|
-
return id >= 0;
|
328
|
+
return self->id >= 0;
|
88
329
|
}
|
89
330
|
|
90
331
|
bool operator ! () const
|
@@ -92,181 +333,562 @@ namespace Beeps
|
|
92
333
|
return !operator bool();
|
93
334
|
}
|
94
335
|
|
95
|
-
|
336
|
+
struct Data
|
337
|
+
{
|
338
|
+
|
339
|
+
ALint id = -1;
|
96
340
|
|
97
|
-
|
98
|
-
: id(id)
|
341
|
+
~Data ()
|
99
342
|
{
|
343
|
+
clear();
|
100
344
|
}
|
101
345
|
|
346
|
+
void clear ()
|
347
|
+
{
|
348
|
+
if (id < 0) return;
|
349
|
+
|
350
|
+
ALuint id_ = id;
|
351
|
+
alDeleteSources(1, &id_);
|
352
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
353
|
+
|
354
|
+
id = -1;
|
355
|
+
}
|
356
|
+
|
357
|
+
};// Data
|
358
|
+
|
359
|
+
Xot::PSharedImpl<Data> self;
|
360
|
+
|
102
361
|
};// SoundSource
|
103
362
|
|
104
363
|
|
105
|
-
|
364
|
+
struct SoundPlayer::Data
|
365
|
+
{
|
366
|
+
|
367
|
+
SoundSource source;
|
368
|
+
|
369
|
+
std::vector<SoundBuffer> buffers;
|
370
|
+
|
371
|
+
Processor::Ref processor;
|
372
|
+
|
373
|
+
std::unique_ptr<ProcessorContext> processor_context;
|
374
|
+
|
375
|
+
void clear ()
|
376
|
+
{
|
377
|
+
source.clear();
|
378
|
+
|
379
|
+
for (auto& buffer : buffers) buffer.clear();
|
380
|
+
buffers.clear();
|
381
|
+
}
|
382
|
+
|
383
|
+
void attach_signals (const Signals& signals)
|
384
|
+
{
|
385
|
+
assert(signals);
|
386
|
+
|
387
|
+
SoundBuffer buffer(signals);
|
388
|
+
source.attach(buffer);
|
389
|
+
buffers.emplace_back(buffer);
|
390
|
+
}
|
391
|
+
|
392
|
+
void attach_stream (
|
393
|
+
Processor* processor, uint nchannels, double sample_rate)
|
394
|
+
{
|
395
|
+
assert(processor && *processor && nchannels > 0 && sample_rate > 0);
|
396
|
+
|
397
|
+
this->processor = processor;
|
398
|
+
processor_context.reset(
|
399
|
+
new ProcessorContext(sample_rate / 10, nchannels, sample_rate));
|
400
|
+
|
401
|
+
for (int i = 0; i < 2; ++i)
|
402
|
+
{
|
403
|
+
SoundBuffer buffer(true);
|
404
|
+
if (!process_stream(&buffer)) break;
|
405
|
+
|
406
|
+
source.queue(buffer);
|
407
|
+
buffers.emplace_back(buffer);
|
408
|
+
}
|
409
|
+
}
|
410
|
+
|
411
|
+
bool process_stream (SoundBuffer* buffer)
|
412
|
+
{
|
413
|
+
assert(buffer && processor && processor_context);
|
414
|
+
|
415
|
+
Signals signals = processor_context->process_signals(processor);
|
416
|
+
if (!signals) return false;
|
417
|
+
|
418
|
+
if (processor_context->is_finished())
|
419
|
+
processor_context.reset();
|
420
|
+
|
421
|
+
return buffer->write(signals) > 0;
|
422
|
+
}
|
423
|
+
|
424
|
+
void process_and_queue_stream_buffers ()
|
425
|
+
{
|
426
|
+
SoundBuffer buffer;
|
427
|
+
while (is_streaming())
|
428
|
+
{
|
429
|
+
if (!source.unqueue(&buffer))
|
430
|
+
return;
|
431
|
+
|
432
|
+
if (!process_stream(&buffer))
|
433
|
+
return;
|
434
|
+
|
435
|
+
source.queue(buffer);
|
436
|
+
if (source.is_stopped()) source.play();
|
437
|
+
}
|
438
|
+
}
|
439
|
+
|
440
|
+
bool is_streaming () const
|
441
|
+
{
|
442
|
+
return processor && processor_context && *processor_context;
|
443
|
+
}
|
444
|
+
|
445
|
+
};// SoundPlayer::Data
|
446
|
+
|
447
|
+
|
448
|
+
static SoundPlayer
|
449
|
+
create_player ()
|
450
|
+
{
|
451
|
+
SoundPlayer player;
|
452
|
+
player.self->source.create();
|
453
|
+
return player;
|
454
|
+
}
|
455
|
+
|
456
|
+
static SoundPlayer
|
457
|
+
reuse_player (SoundPlayer* player)
|
458
|
+
{
|
459
|
+
SoundPlayer newplayer;
|
460
|
+
newplayer.self->source = player->self->source.reuse();
|
461
|
+
player->self->clear();
|
462
|
+
return newplayer;
|
463
|
+
}
|
106
464
|
|
107
465
|
|
108
466
|
namespace global
|
109
467
|
{
|
110
468
|
|
111
|
-
static
|
469
|
+
static std::vector<SoundPlayer> players;
|
112
470
|
|
113
471
|
}// global
|
114
472
|
|
115
473
|
|
116
|
-
void
|
117
|
-
|
474
|
+
static void
|
475
|
+
remove_inactive_players ()
|
118
476
|
{
|
119
|
-
|
477
|
+
auto it = std::remove_if(
|
478
|
+
global::players.begin(), global::players.end(),
|
479
|
+
[](auto& player) {return !player || player.is_stopped();});
|
480
|
+
|
481
|
+
for (auto jt = it; jt != global::players.end(); ++jt)
|
482
|
+
jt->self->clear();
|
483
|
+
|
484
|
+
global::players.erase(it, global::players.end());
|
120
485
|
}
|
121
486
|
|
122
|
-
static
|
123
|
-
|
487
|
+
static SoundPlayer
|
488
|
+
get_next_player ()
|
124
489
|
{
|
125
|
-
|
490
|
+
SoundPlayer player = create_player();
|
491
|
+
|
492
|
+
if (player)
|
493
|
+
LOG("new player");
|
126
494
|
|
127
|
-
|
128
|
-
for (auto it = global::sources.begin(); it != end; ++it)
|
495
|
+
if (!player)
|
129
496
|
{
|
130
|
-
|
131
|
-
if (p && *p && !p->is_playing())
|
497
|
+
for (auto& p : global::players)
|
132
498
|
{
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
499
|
+
if (p && p.is_stopped())
|
500
|
+
{
|
501
|
+
player = reuse_player(&p);
|
502
|
+
LOG("reuse stopped player");
|
503
|
+
break;
|
504
|
+
}
|
137
505
|
}
|
138
506
|
}
|
139
507
|
|
140
|
-
if (!
|
508
|
+
if (!player && !global::players.empty())
|
141
509
|
{
|
142
|
-
|
143
|
-
LOG("
|
510
|
+
player = reuse_player(&global::players.front());
|
511
|
+
LOG("reuse oldest player");
|
144
512
|
}
|
145
513
|
|
146
|
-
|
514
|
+
remove_inactive_players();
|
515
|
+
|
516
|
+
if (player)
|
517
|
+
global::players.emplace_back(player);
|
518
|
+
|
519
|
+
return player;
|
520
|
+
}
|
521
|
+
|
522
|
+
void
|
523
|
+
SoundPlayer_process_streams ()
|
524
|
+
{
|
525
|
+
for (auto& player : global::players)
|
147
526
|
{
|
148
|
-
|
149
|
-
|
150
|
-
global::sources.erase(global::sources.begin());
|
151
|
-
LOG("stop and reuse oldest source");
|
527
|
+
if (player.self->is_streaming())
|
528
|
+
player.self->process_and_queue_stream_buffers();
|
152
529
|
}
|
530
|
+
}
|
531
|
+
|
532
|
+
void
|
533
|
+
SoundPlayer_clear_all ()
|
534
|
+
{
|
535
|
+
for (auto& player : global::players)
|
536
|
+
player.self->clear();
|
537
|
+
|
538
|
+
global::players.clear();
|
539
|
+
}
|
153
540
|
|
154
|
-
|
155
|
-
|
541
|
+
void
|
542
|
+
stop_all_sound_players ()
|
543
|
+
{
|
544
|
+
for (auto& player : global::players)
|
545
|
+
player.stop();
|
546
|
+
}
|
547
|
+
|
548
|
+
|
549
|
+
SoundPlayer::SoundPlayer ()
|
550
|
+
{
|
551
|
+
}
|
552
|
+
|
553
|
+
SoundPlayer::~SoundPlayer ()
|
554
|
+
{
|
555
|
+
}
|
556
|
+
|
557
|
+
void
|
558
|
+
SoundPlayer::play ()
|
559
|
+
{
|
560
|
+
self->source.play();
|
561
|
+
}
|
156
562
|
|
157
|
-
|
158
|
-
|
563
|
+
void
|
564
|
+
SoundPlayer::pause ()
|
565
|
+
{
|
566
|
+
self->source.pause();
|
159
567
|
}
|
160
568
|
|
569
|
+
void
|
570
|
+
SoundPlayer::rewind ()
|
571
|
+
{
|
572
|
+
not_implemented_error(__FILE__, __LINE__);
|
573
|
+
}
|
161
574
|
|
162
|
-
|
575
|
+
void
|
576
|
+
SoundPlayer::stop ()
|
163
577
|
{
|
578
|
+
self->source.stop();
|
579
|
+
}
|
164
580
|
|
165
|
-
|
581
|
+
bool
|
582
|
+
SoundPlayer::is_playing () const
|
583
|
+
{
|
584
|
+
return
|
585
|
+
self->source.is_playing() ||
|
586
|
+
(self->is_streaming() && self->source.is_stopped());
|
587
|
+
}
|
166
588
|
|
167
|
-
|
168
|
-
|
589
|
+
bool
|
590
|
+
SoundPlayer::is_paused () const
|
591
|
+
{
|
592
|
+
return self->source.is_paused();
|
593
|
+
}
|
594
|
+
|
595
|
+
bool
|
596
|
+
SoundPlayer::is_stopped () const
|
597
|
+
{
|
598
|
+
return self->source.is_stopped() && !self->is_streaming();
|
599
|
+
}
|
600
|
+
|
601
|
+
void
|
602
|
+
SoundPlayer::set_gain (float gain)
|
603
|
+
{
|
604
|
+
self->source.set_gain(gain);
|
605
|
+
}
|
606
|
+
|
607
|
+
float
|
608
|
+
SoundPlayer::gain () const
|
609
|
+
{
|
610
|
+
return self->source.gain();
|
611
|
+
}
|
612
|
+
|
613
|
+
void
|
614
|
+
SoundPlayer::set_loop (bool loop)
|
615
|
+
{
|
616
|
+
self->source.set_loop(loop);
|
617
|
+
}
|
618
|
+
|
619
|
+
bool
|
620
|
+
SoundPlayer::loop () const
|
621
|
+
{
|
622
|
+
return self->source.loop();
|
623
|
+
}
|
624
|
+
|
625
|
+
SoundPlayer::operator bool () const
|
626
|
+
{
|
627
|
+
return self->source;
|
628
|
+
}
|
629
|
+
|
630
|
+
bool
|
631
|
+
SoundPlayer::operator ! () const
|
632
|
+
{
|
633
|
+
return !operator bool();
|
634
|
+
}
|
635
|
+
|
636
|
+
|
637
|
+
struct Sound::Data {
|
638
|
+
|
639
|
+
float gain = 1;
|
640
|
+
|
641
|
+
bool loop = false;
|
642
|
+
|
643
|
+
virtual ~Data ()
|
169
644
|
{
|
170
645
|
}
|
171
646
|
|
172
|
-
|
647
|
+
virtual void attach_to (SoundPlayer* player)
|
173
648
|
{
|
174
|
-
|
649
|
+
not_implemented_error(__FILE__, __LINE__);
|
175
650
|
}
|
176
651
|
|
177
|
-
void
|
652
|
+
virtual void save (const char* path) const
|
178
653
|
{
|
179
|
-
|
180
|
-
|
181
|
-
ALuint id_ = 0;
|
182
|
-
alGenBuffers(1, &id_);
|
183
|
-
OpenAL_check_error(__FILE__, __LINE__);
|
654
|
+
not_implemented_error(__FILE__, __LINE__);
|
655
|
+
}
|
184
656
|
|
185
|
-
|
657
|
+
virtual double sample_rate () const
|
658
|
+
{
|
659
|
+
return 0;
|
186
660
|
}
|
187
661
|
|
188
|
-
|
662
|
+
virtual uint nchannels () const
|
189
663
|
{
|
190
|
-
|
191
|
-
|
192
|
-
ALuint id_ = id;
|
193
|
-
alDeleteBuffers(1, &id_);
|
194
|
-
OpenAL_check_error(__FILE__, __LINE__);
|
195
|
-
}
|
664
|
+
return 0;
|
665
|
+
}
|
196
666
|
|
197
|
-
|
667
|
+
virtual float seconds () const
|
668
|
+
{
|
669
|
+
return 0;
|
198
670
|
}
|
199
671
|
|
200
|
-
bool is_valid () const
|
672
|
+
virtual bool is_valid () const
|
201
673
|
{
|
202
|
-
return
|
674
|
+
return false;
|
203
675
|
}
|
204
676
|
|
205
677
|
};// Sound::Data
|
206
678
|
|
207
679
|
|
208
|
-
|
209
|
-
get_buffer_id (const Sound& sound)
|
680
|
+
struct SoundData : public Sound::Data
|
210
681
|
{
|
211
|
-
return sound.self->id;
|
212
|
-
}
|
213
682
|
|
683
|
+
typedef Sound::Data Super;
|
214
684
|
|
215
|
-
|
685
|
+
Signals signals;
|
686
|
+
|
687
|
+
SoundData (
|
688
|
+
Processor* processor, float seconds, uint nchannels, double sample_rate)
|
689
|
+
{
|
690
|
+
assert(
|
691
|
+
processor && *processor &&
|
692
|
+
seconds > 0 && nchannels > 0 && sample_rate > 0);
|
693
|
+
|
694
|
+
ProcessorContext context(seconds * sample_rate, nchannels, sample_rate);
|
695
|
+
Signals signals = context.process_signals(processor);
|
696
|
+
if (!signals)
|
697
|
+
beeps_error(__FILE__, __LINE__, "failed to process signals");
|
698
|
+
|
699
|
+
this->signals = signals;
|
700
|
+
}
|
701
|
+
|
702
|
+
void attach_to (SoundPlayer* player) override
|
703
|
+
{
|
704
|
+
assert(player && *player);
|
705
|
+
|
706
|
+
player->self->attach_signals(signals);
|
707
|
+
}
|
708
|
+
|
709
|
+
void save (const char* path) const override
|
710
|
+
{
|
711
|
+
if (!signals)
|
712
|
+
invalid_state_error(__FILE__, __LINE__);
|
713
|
+
|
714
|
+
Signals_save(signals, path);
|
715
|
+
}
|
716
|
+
|
717
|
+
double sample_rate () const override
|
718
|
+
{
|
719
|
+
return signals ? signals.sample_rate() : Super::sample_rate();
|
720
|
+
}
|
721
|
+
|
722
|
+
uint nchannels () const override
|
723
|
+
{
|
724
|
+
return signals ? signals.nchannels() : Super::nchannels();
|
725
|
+
}
|
726
|
+
|
727
|
+
float seconds () const override
|
728
|
+
{
|
729
|
+
return signals ? Signals_get_seconds(signals) : Super::seconds();
|
730
|
+
}
|
731
|
+
|
732
|
+
bool is_valid () const override
|
733
|
+
{
|
734
|
+
return signals;
|
735
|
+
}
|
736
|
+
|
737
|
+
};// SoundData
|
738
|
+
|
739
|
+
|
740
|
+
struct StreamSoundData : public Sound::Data
|
216
741
|
{
|
217
|
-
}
|
218
742
|
|
219
|
-
|
743
|
+
Processor::Ref processor;
|
744
|
+
|
745
|
+
double sample_rate_ = 0;
|
746
|
+
|
747
|
+
uint nchannels_ = 0;
|
748
|
+
|
749
|
+
StreamSoundData (Processor* processor, uint nchannels, double sample_rate)
|
750
|
+
{
|
751
|
+
assert(processor && *processor && nchannels > 0 && sample_rate > 0);
|
752
|
+
|
753
|
+
this->processor = processor;
|
754
|
+
this->sample_rate_ = sample_rate;
|
755
|
+
this->nchannels_ = nchannels;
|
756
|
+
}
|
757
|
+
|
758
|
+
void attach_to (SoundPlayer* player) override
|
759
|
+
{
|
760
|
+
assert(player && *player);
|
761
|
+
|
762
|
+
player->self->attach_stream(processor, nchannels_, sample_rate_);
|
763
|
+
}
|
764
|
+
|
765
|
+
double sample_rate () const override
|
766
|
+
{
|
767
|
+
return sample_rate_;
|
768
|
+
}
|
769
|
+
|
770
|
+
uint nchannels () const override
|
771
|
+
{
|
772
|
+
return nchannels_;
|
773
|
+
}
|
774
|
+
|
775
|
+
float seconds () const override
|
776
|
+
{
|
777
|
+
return -1;
|
778
|
+
}
|
779
|
+
|
780
|
+
bool is_valid () const override
|
781
|
+
{
|
782
|
+
return processor && sample_rate_ > 0 && nchannels_ > 0;
|
783
|
+
}
|
784
|
+
|
785
|
+
};// StreamSoundData
|
786
|
+
|
787
|
+
|
788
|
+
Sound
|
789
|
+
load_sound (const char* path)
|
220
790
|
{
|
221
|
-
|
222
|
-
|
791
|
+
FileIn* f = new FileIn(path);
|
792
|
+
return Sound(f, f->seconds(), f->nchannels(), f->sample_rate());
|
793
|
+
}
|
223
794
|
|
224
|
-
self->create();
|
225
795
|
|
226
|
-
|
227
|
-
|
796
|
+
Sound::Sound ()
|
797
|
+
{
|
798
|
+
}
|
228
799
|
|
229
|
-
|
230
|
-
|
231
|
-
|
800
|
+
Sound::Sound (
|
801
|
+
Processor* processor, float seconds, uint nchannels, double sample_rate)
|
802
|
+
{
|
803
|
+
Processor::Ref ref = processor;
|
232
804
|
|
233
|
-
|
234
|
-
|
235
|
-
return;
|
805
|
+
if (!processor || !*processor)
|
806
|
+
argument_error(__FILE__, __LINE__);
|
236
807
|
|
237
|
-
|
238
|
-
buffer.reserve(size);
|
239
|
-
for (ALsizei i = 0; i < size; ++i)
|
240
|
-
buffer.push_back((*frames)[i] * SHRT_MAX);
|
808
|
+
if (sample_rate <= 0) sample_rate = Beeps::sample_rate();
|
241
809
|
|
242
|
-
|
243
|
-
self
|
244
|
-
|
245
|
-
|
810
|
+
if (seconds > 0)
|
811
|
+
self.reset(new SoundData(processor, seconds, nchannels, sample_rate));
|
812
|
+
else
|
813
|
+
self.reset(new StreamSoundData(processor, nchannels, sample_rate));
|
246
814
|
}
|
247
815
|
|
248
816
|
Sound::~Sound ()
|
249
817
|
{
|
250
818
|
}
|
251
819
|
|
252
|
-
|
820
|
+
SoundPlayer
|
253
821
|
Sound::play ()
|
254
822
|
{
|
255
|
-
|
823
|
+
SoundPlayer player = get_next_player();
|
824
|
+
if (!player)
|
256
825
|
invalid_state_error(__FILE__, __LINE__);
|
257
826
|
|
258
|
-
|
259
|
-
|
260
|
-
invalid_state_error(__FILE__, __LINE__);
|
827
|
+
player.set_gain(gain());
|
828
|
+
player.set_loop(loop());
|
261
829
|
|
262
|
-
|
830
|
+
self->attach_to(&player);
|
831
|
+
player.play();
|
263
832
|
|
264
833
|
#if 0
|
265
834
|
std::string ox = "";
|
266
|
-
for (
|
267
|
-
ox +=
|
268
|
-
LOG("
|
835
|
+
for (auto& player : global::players)
|
836
|
+
ox += player.is_playing() ? 'o' : 'x';
|
837
|
+
LOG("%d players. (%s)", global::players.size(), ox.c_str());
|
269
838
|
#endif
|
839
|
+
|
840
|
+
return player;
|
841
|
+
}
|
842
|
+
|
843
|
+
void
|
844
|
+
Sound::save (const char* path) const
|
845
|
+
{
|
846
|
+
self->save(path);
|
847
|
+
}
|
848
|
+
|
849
|
+
double
|
850
|
+
Sound::sample_rate () const
|
851
|
+
{
|
852
|
+
return self->sample_rate();
|
853
|
+
}
|
854
|
+
|
855
|
+
uint
|
856
|
+
Sound::nchannels () const
|
857
|
+
{
|
858
|
+
return self->nchannels();
|
859
|
+
}
|
860
|
+
|
861
|
+
float
|
862
|
+
Sound::seconds () const
|
863
|
+
{
|
864
|
+
return self->seconds();
|
865
|
+
}
|
866
|
+
|
867
|
+
void
|
868
|
+
Sound::set_gain (float gain)
|
869
|
+
{
|
870
|
+
if (gain < 0)
|
871
|
+
argument_error(__FILE__, __LINE__);
|
872
|
+
|
873
|
+
self->gain = gain;
|
874
|
+
}
|
875
|
+
|
876
|
+
float
|
877
|
+
Sound::gain () const
|
878
|
+
{
|
879
|
+
return self->gain;
|
880
|
+
}
|
881
|
+
|
882
|
+
void
|
883
|
+
Sound::set_loop (bool loop)
|
884
|
+
{
|
885
|
+
self->loop = loop;
|
886
|
+
}
|
887
|
+
|
888
|
+
bool
|
889
|
+
Sound::loop () const
|
890
|
+
{
|
891
|
+
return self->loop;
|
270
892
|
}
|
271
893
|
|
272
894
|
Sound::operator bool () const
|