beeps 0.3.16 → 0.5.1
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/analyser.cpp +4 -4
- data/.doc/ext/beeps/envelope.cpp +1 -1
- data/.doc/ext/beeps/file_in.cpp +1 -1
- data/.doc/ext/beeps/gain.cpp +1 -1
- data/.doc/ext/beeps/high_pass.cpp +1 -1
- data/.doc/ext/beeps/low_pass.cpp +1 -1
- data/.doc/ext/beeps/mic_in.cpp +1 -1
- data/.doc/ext/beeps/mixer.cpp +2 -2
- data/.doc/ext/beeps/oscillator.cpp +3 -3
- data/.doc/ext/beeps/pitch_shift.cpp +1 -1
- data/.doc/ext/beeps/processor.cpp +1 -1
- data/.doc/ext/beeps/reverb.cpp +1 -1
- data/.doc/ext/beeps/sequencer.cpp +2 -2
- data/.doc/ext/beeps/signals.cpp +3 -3
- data/.doc/ext/beeps/sound.cpp +16 -8
- data/.doc/ext/beeps/sound_player.cpp +13 -5
- data/.doc/ext/beeps/text_in.cpp +1 -1
- data/.doc/ext/beeps/time_stretch.cpp +1 -1
- data/.doc/ext/beeps/value.cpp +2 -2
- data/.github/workflows/release-gem.yml +2 -40
- data/.github/workflows/tag.yml +5 -28
- data/.github/workflows/test.yml +1 -28
- data/ChangeLog.md +28 -0
- data/Gemfile.lock +13 -8
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/beeps.gemspec +3 -3
- data/ext/beeps/analyser.cpp +4 -4
- data/ext/beeps/envelope.cpp +1 -1
- data/ext/beeps/file_in.cpp +1 -1
- data/ext/beeps/gain.cpp +1 -1
- data/ext/beeps/high_pass.cpp +1 -1
- data/ext/beeps/low_pass.cpp +1 -1
- data/ext/beeps/mic_in.cpp +1 -1
- data/ext/beeps/mixer.cpp +2 -2
- data/ext/beeps/oscillator.cpp +3 -3
- data/ext/beeps/pitch_shift.cpp +1 -1
- data/ext/beeps/processor.cpp +1 -1
- data/ext/beeps/reverb.cpp +1 -1
- data/ext/beeps/sequencer.cpp +2 -2
- data/ext/beeps/signals.cpp +3 -3
- data/ext/beeps/sound.cpp +17 -8
- data/ext/beeps/sound_player.cpp +14 -5
- data/ext/beeps/text_in.cpp +1 -1
- data/ext/beeps/time_stretch.cpp +1 -1
- data/ext/beeps/value.cpp +2 -2
- data/include/beeps/defs.h +14 -4
- data/include/beeps/signals.h +2 -2
- data/include/beeps/sound.h +9 -3
- data/lib/beeps/beeps.rb +4 -4
- data/lib/beeps/extension.rb +1 -1
- data/lib/beeps/sound.rb +2 -2
- data/src/file_in.cpp +2 -0
- data/src/mic_in.cpp +10 -9
- data/src/openal.cpp +66 -8
- data/src/openal.h +5 -1
- data/src/oscillator.cpp +1 -1
- data/src/signals.cpp +6 -6
- data/src/sound.cpp +382 -160
- data/src/win32/beeps.cpp +25 -2
- data/src/win32/signals.cpp +103 -29
- data/test/test_file_in.rb +38 -0
- data/test/test_sound.rb +6 -6
- data/test/test_sound_player.rb +86 -20
- metadata +7 -8
- data/.github/workflows/utils.rb +0 -127
data/src/sound.cpp
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include <assert.h>
|
|
6
6
|
#include <memory>
|
|
7
7
|
#include <algorithm>
|
|
8
|
+
#include <xot/util.h>
|
|
8
9
|
#include "Stk.h"
|
|
9
10
|
#include "beeps/beeps.h"
|
|
10
11
|
#include "beeps/generator.h"
|
|
@@ -57,11 +58,11 @@ namespace Beeps
|
|
|
57
58
|
if (!*this)
|
|
58
59
|
invalid_state_error(__FILE__, __LINE__);
|
|
59
60
|
|
|
60
|
-
double sample_rate = signals.sample_rate();
|
|
61
61
|
uint nchannels = signals.nchannels();
|
|
62
|
+
double sample_rate = signals.sample_rate();
|
|
62
63
|
uint nsamples = signals.nsamples();
|
|
63
64
|
uint size = nsamples * nchannels;
|
|
64
|
-
assert(
|
|
65
|
+
assert(nchannels > 0 && sample_rate > 0);
|
|
65
66
|
if (nsamples <= 0) return 0;
|
|
66
67
|
|
|
67
68
|
std::unique_ptr<short[]> buffer(new short[size]);
|
|
@@ -91,6 +92,8 @@ namespace Beeps
|
|
|
91
92
|
alGetBufferi(self->id, AL_SIZE, &size);
|
|
92
93
|
alGetBufferi(self->id, AL_BITS, &bits);
|
|
93
94
|
alGetBufferi(self->id, AL_CHANNELS, &channels);
|
|
95
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
|
96
|
+
|
|
94
97
|
if (bits <= 0 || channels <= 0)
|
|
95
98
|
return 0;
|
|
96
99
|
|
|
@@ -136,7 +139,7 @@ namespace Beeps
|
|
|
136
139
|
|
|
137
140
|
~Data ()
|
|
138
141
|
{
|
|
139
|
-
clear();
|
|
142
|
+
Xot::call_and_log_errors([&]() {clear();});
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
void clear ()
|
|
@@ -168,9 +171,14 @@ namespace Beeps
|
|
|
168
171
|
struct SoundSource
|
|
169
172
|
{
|
|
170
173
|
|
|
171
|
-
|
|
174
|
+
bool create ()
|
|
172
175
|
{
|
|
173
|
-
self->create();
|
|
176
|
+
if (!self->create()) return false;
|
|
177
|
+
|
|
178
|
+
set_time_scale(self->time_scale);
|
|
179
|
+
set_gain(self->gain);
|
|
180
|
+
set_loop(self->loop);
|
|
181
|
+
return true;
|
|
174
182
|
}
|
|
175
183
|
|
|
176
184
|
void clear ()
|
|
@@ -179,18 +187,6 @@ namespace Beeps
|
|
|
179
187
|
self->clear();
|
|
180
188
|
}
|
|
181
189
|
|
|
182
|
-
SoundSource reuse ()
|
|
183
|
-
{
|
|
184
|
-
stop();
|
|
185
|
-
set_time_scale(1);
|
|
186
|
-
set_gain(1);
|
|
187
|
-
set_loop(false);
|
|
188
|
-
|
|
189
|
-
SoundSource source;
|
|
190
|
-
source.self.swap(self);
|
|
191
|
-
return source;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
190
|
void attach (const SoundBuffer& buffer)
|
|
195
191
|
{
|
|
196
192
|
assert(buffer);
|
|
@@ -215,11 +211,7 @@ namespace Beeps
|
|
|
215
211
|
|
|
216
212
|
bool unqueue (SoundBuffer* buffer = NULL)
|
|
217
213
|
{
|
|
218
|
-
|
|
219
|
-
alGetSourcei(self->id, AL_BUFFERS_PROCESSED, &count);
|
|
220
|
-
OpenAL_check_error(__FILE__, __LINE__);
|
|
221
|
-
|
|
222
|
-
if (count <= 0) return false;
|
|
214
|
+
if (count_processed_buffers() == 0) return false;
|
|
223
215
|
|
|
224
216
|
ALuint id = 0;
|
|
225
217
|
alSourceUnqueueBuffers(self->id, 1, &id);
|
|
@@ -234,8 +226,24 @@ namespace Beeps
|
|
|
234
226
|
if (!*this)
|
|
235
227
|
invalid_state_error(__FILE__, __LINE__);
|
|
236
228
|
|
|
229
|
+
#if defined(OSX) || defined(IOS)
|
|
230
|
+
// macOS drops the offset set while the source is not playing,
|
|
231
|
+
// so set it again after the source starts to play
|
|
232
|
+
uint offset = state() == SoundPlayer::STATE_NONE ? position() : 0;
|
|
233
|
+
#endif
|
|
234
|
+
|
|
237
235
|
alSourcePlay(self->id);
|
|
238
236
|
OpenAL_check_error(__FILE__, __LINE__);
|
|
237
|
+
|
|
238
|
+
#if defined(OSX) || defined(IOS)
|
|
239
|
+
if (offset > 0)
|
|
240
|
+
{
|
|
241
|
+
pause();
|
|
242
|
+
set_position(offset);
|
|
243
|
+
alSourcePlay(self->id);
|
|
244
|
+
OpenAL_check_error(__FILE__, __LINE__);
|
|
245
|
+
}
|
|
246
|
+
#endif
|
|
239
247
|
}
|
|
240
248
|
|
|
241
249
|
void pause ()
|
|
@@ -276,7 +284,7 @@ namespace Beeps
|
|
|
276
284
|
|
|
277
285
|
SoundPlayer::State state () const
|
|
278
286
|
{
|
|
279
|
-
if (!*this) return SoundPlayer::
|
|
287
|
+
if (!*this) return SoundPlayer::STATE_NONE;
|
|
280
288
|
|
|
281
289
|
ALint state = 0;
|
|
282
290
|
alGetSourcei(self->id, AL_SOURCE_STATE, &state);
|
|
@@ -287,7 +295,7 @@ namespace Beeps
|
|
|
287
295
|
case AL_PLAYING: return SoundPlayer::PLAYING;
|
|
288
296
|
case AL_PAUSED: return SoundPlayer::PAUSED;
|
|
289
297
|
case AL_STOPPED: return SoundPlayer::STOPPED;
|
|
290
|
-
default: return SoundPlayer::
|
|
298
|
+
default: return SoundPlayer::STATE_NONE;
|
|
291
299
|
}
|
|
292
300
|
}
|
|
293
301
|
|
|
@@ -315,6 +323,7 @@ namespace Beeps
|
|
|
315
323
|
if (scale <= 0)
|
|
316
324
|
argument_error(__FILE__, __LINE__);
|
|
317
325
|
|
|
326
|
+
self->time_scale = scale;
|
|
318
327
|
if (!*this) return;
|
|
319
328
|
|
|
320
329
|
alSourcef(self->id, AL_PITCH, scale);
|
|
@@ -323,13 +332,7 @@ namespace Beeps
|
|
|
323
332
|
|
|
324
333
|
float time_scale () const
|
|
325
334
|
{
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
float scale = 1;
|
|
329
|
-
alGetSourcef(self->id, AL_PITCH, &scale);
|
|
330
|
-
OpenAL_check_error(__FILE__, __LINE__);
|
|
331
|
-
|
|
332
|
-
return scale;
|
|
335
|
+
return self->time_scale;
|
|
333
336
|
}
|
|
334
337
|
|
|
335
338
|
void set_gain (float gain)
|
|
@@ -337,6 +340,7 @@ namespace Beeps
|
|
|
337
340
|
if (gain < 0)
|
|
338
341
|
argument_error(__FILE__, __LINE__);
|
|
339
342
|
|
|
343
|
+
self->gain = gain;
|
|
340
344
|
if (!*this) return;
|
|
341
345
|
|
|
342
346
|
alSourcef(self->id, AL_GAIN, gain);
|
|
@@ -345,17 +349,12 @@ namespace Beeps
|
|
|
345
349
|
|
|
346
350
|
float gain () const
|
|
347
351
|
{
|
|
348
|
-
|
|
349
|
-
if (!*this) return gain;
|
|
350
|
-
|
|
351
|
-
alGetSourcef(self->id, AL_GAIN, &gain);
|
|
352
|
-
OpenAL_check_error(__FILE__, __LINE__);
|
|
353
|
-
|
|
354
|
-
return gain;
|
|
352
|
+
return self->gain;
|
|
355
353
|
}
|
|
356
354
|
|
|
357
355
|
void set_loop (bool loop)
|
|
358
356
|
{
|
|
357
|
+
self->loop = loop;
|
|
359
358
|
if (!*this) return;
|
|
360
359
|
|
|
361
360
|
alSourcei(self->id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
|
|
@@ -364,13 +363,16 @@ namespace Beeps
|
|
|
364
363
|
|
|
365
364
|
bool loop () const
|
|
366
365
|
{
|
|
367
|
-
|
|
366
|
+
return self->loop;
|
|
367
|
+
}
|
|
368
368
|
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
uint count_processed_buffers () const
|
|
370
|
+
{
|
|
371
|
+
ALint count = 0;
|
|
372
|
+
alGetSourcei(self->id, AL_BUFFERS_PROCESSED, &count);
|
|
371
373
|
OpenAL_check_error(__FILE__, __LINE__);
|
|
372
374
|
|
|
373
|
-
return
|
|
375
|
+
return count > 0 ? (uint) count : 0;
|
|
374
376
|
}
|
|
375
377
|
|
|
376
378
|
operator bool () const
|
|
@@ -386,23 +388,31 @@ namespace Beeps
|
|
|
386
388
|
struct Data
|
|
387
389
|
{
|
|
388
390
|
|
|
389
|
-
void* context
|
|
391
|
+
void* context = NULL;
|
|
390
392
|
|
|
391
|
-
ALint id
|
|
393
|
+
ALint id = -1;
|
|
394
|
+
|
|
395
|
+
float time_scale = 1;
|
|
396
|
+
|
|
397
|
+
float gain = 1;
|
|
398
|
+
|
|
399
|
+
bool loop = false;
|
|
392
400
|
|
|
393
401
|
~Data ()
|
|
394
402
|
{
|
|
395
|
-
clear();
|
|
403
|
+
Xot::call_and_log_errors([&]() {clear();});
|
|
396
404
|
}
|
|
397
405
|
|
|
398
|
-
|
|
406
|
+
bool create ()
|
|
399
407
|
{
|
|
400
408
|
ALuint id_ = 0;
|
|
401
409
|
alGenSources(1, &id_);
|
|
402
|
-
if (!OpenAL_no_error())
|
|
410
|
+
if (!OpenAL_no_error())
|
|
411
|
+
return false;
|
|
403
412
|
|
|
404
413
|
context = OpenAL_get_context();
|
|
405
414
|
id = id_;
|
|
415
|
+
return true;
|
|
406
416
|
}
|
|
407
417
|
|
|
408
418
|
void clear ()
|
|
@@ -433,50 +443,83 @@ namespace Beeps
|
|
|
433
443
|
struct SoundPlayer::Data
|
|
434
444
|
{
|
|
435
445
|
|
|
436
|
-
|
|
446
|
+
uint nchannels = 0;
|
|
437
447
|
|
|
438
|
-
|
|
448
|
+
double sample_rate = 0;
|
|
439
449
|
|
|
440
|
-
|
|
450
|
+
uint position = 0;
|
|
441
451
|
|
|
442
|
-
|
|
452
|
+
State pending_state = STATE_NONE;
|
|
453
|
+
|
|
454
|
+
Signals signals;
|
|
443
455
|
|
|
444
456
|
Processor::Ref processor;
|
|
445
457
|
|
|
446
458
|
std::unique_ptr<StreamContext> stream_context;
|
|
447
459
|
|
|
460
|
+
SoundSource source;
|
|
461
|
+
|
|
462
|
+
std::vector<SoundBuffer> buffers;
|
|
463
|
+
|
|
464
|
+
~Data ()
|
|
465
|
+
{
|
|
466
|
+
Xot::call_and_log_errors([&]() {clear();});
|
|
467
|
+
}
|
|
468
|
+
|
|
448
469
|
void clear ()
|
|
449
470
|
{
|
|
450
|
-
|
|
451
|
-
|
|
471
|
+
position = 0;
|
|
472
|
+
pending_state = STATE_NONE;
|
|
452
473
|
|
|
453
474
|
source.clear();
|
|
454
475
|
|
|
455
476
|
for (auto& buffer : buffers) buffer.clear();
|
|
456
477
|
buffers.clear();
|
|
478
|
+
|
|
479
|
+
stream_context.reset();
|
|
457
480
|
}
|
|
458
481
|
|
|
459
482
|
void attach_signals (const Signals& signals)
|
|
460
483
|
{
|
|
461
484
|
assert(signals);
|
|
462
485
|
|
|
463
|
-
|
|
486
|
+
this->signals = signals;
|
|
487
|
+
this->sample_rate = signals.sample_rate();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
void attach_stream (Processor* processor, uint nchannels, double sample_rate)
|
|
491
|
+
{
|
|
492
|
+
assert(processor && *processor && nchannels > 0 && sample_rate > 0);
|
|
493
|
+
|
|
494
|
+
this->processor = processor;
|
|
495
|
+
this->nchannels = nchannels;
|
|
496
|
+
this->sample_rate = sample_rate;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
void load_signals ()
|
|
500
|
+
{
|
|
501
|
+
assert(signals);
|
|
464
502
|
|
|
465
503
|
SoundBuffer buffer(signals);
|
|
466
504
|
source.attach(buffer);
|
|
467
505
|
buffers.emplace_back(buffer);
|
|
506
|
+
|
|
507
|
+
source.set_position(position);
|
|
508
|
+
position = 0;
|
|
468
509
|
}
|
|
469
510
|
|
|
470
|
-
|
|
471
|
-
Processor* processor, uint nchannels, double sample_rate)
|
|
511
|
+
bool load_stream ()
|
|
472
512
|
{
|
|
473
513
|
assert(processor && *processor && nchannels > 0 && sample_rate > 0);
|
|
474
514
|
|
|
475
|
-
this->sample_rate = sample_rate;
|
|
476
|
-
this->processor = processor;
|
|
477
515
|
stream_context.reset(
|
|
478
516
|
new StreamContext(sample_rate / 10, nchannels, sample_rate));
|
|
479
517
|
|
|
518
|
+
if (processor->seekable())
|
|
519
|
+
stream_context->seek(position);
|
|
520
|
+
else
|
|
521
|
+
position = 0;
|
|
522
|
+
|
|
480
523
|
for (int i = 0; i < 2; ++i)
|
|
481
524
|
{
|
|
482
525
|
SoundBuffer buffer(true);
|
|
@@ -489,6 +532,7 @@ namespace Beeps
|
|
|
489
532
|
source.queue(buffer);
|
|
490
533
|
buffers.emplace_back(buffer);
|
|
491
534
|
}
|
|
535
|
+
return !buffers.empty();
|
|
492
536
|
}
|
|
493
537
|
|
|
494
538
|
bool process_stream (SoundBuffer* buffer)
|
|
@@ -507,12 +551,12 @@ namespace Beeps
|
|
|
507
551
|
|
|
508
552
|
void process_and_queue_stream_buffers ()
|
|
509
553
|
{
|
|
510
|
-
|
|
511
|
-
|
|
554
|
+
// a stopped source counts even the buffers queued after it stopped
|
|
555
|
+
// as processed, so read the count only once before queueing
|
|
556
|
+
for (uint n = source.count_processed_buffers(); n > 0 && is_streaming(); --n)
|
|
512
557
|
{
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
558
|
+
SoundBuffer buffer;
|
|
559
|
+
source.unqueue(&buffer);
|
|
516
560
|
position += buffer.nsamples();
|
|
517
561
|
|
|
518
562
|
if (!process_stream(&buffer))
|
|
@@ -522,11 +566,17 @@ namespace Beeps
|
|
|
522
566
|
}
|
|
523
567
|
|
|
524
568
|
source.queue(buffer);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (source.state() == STOPPED)
|
|
572
|
+
{
|
|
573
|
+
expand_buffer();
|
|
574
|
+
#if defined(OSX) || defined(IOS)
|
|
575
|
+
// play it on the next play_pending_players()
|
|
576
|
+
pending_state = PLAYING;
|
|
577
|
+
#else
|
|
578
|
+
source.play();
|
|
579
|
+
#endif
|
|
530
580
|
}
|
|
531
581
|
}
|
|
532
582
|
|
|
@@ -542,85 +592,113 @@ namespace Beeps
|
|
|
542
592
|
}
|
|
543
593
|
}
|
|
544
594
|
|
|
545
|
-
bool
|
|
595
|
+
bool is_loaded () const
|
|
546
596
|
{
|
|
547
|
-
return
|
|
597
|
+
return source;
|
|
548
598
|
}
|
|
549
599
|
|
|
550
|
-
|
|
600
|
+
bool is_active () const
|
|
601
|
+
{
|
|
602
|
+
if (!is_loaded())
|
|
603
|
+
return false;
|
|
551
604
|
|
|
605
|
+
if (is_pending())
|
|
606
|
+
return pending_state == PLAYING;
|
|
552
607
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
player.self->source.create();
|
|
558
|
-
return player;
|
|
559
|
-
}
|
|
608
|
+
// a source can stay initial for a moment after it starts to play,
|
|
609
|
+
// so count the initial ones too
|
|
610
|
+
return source.state() != STOPPED;
|
|
611
|
+
}
|
|
560
612
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
613
|
+
bool is_pending () const
|
|
614
|
+
{
|
|
615
|
+
return pending_state != STATE_NONE;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
bool is_streaming () const
|
|
619
|
+
{
|
|
620
|
+
return processor && stream_context;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
};// SoundPlayer::Data
|
|
569
624
|
|
|
570
625
|
|
|
571
626
|
namespace global
|
|
572
627
|
{
|
|
573
628
|
|
|
574
|
-
static std::vector<SoundPlayer> players;
|
|
629
|
+
static std::vector<SoundPlayer> players, pending_players;
|
|
575
630
|
|
|
576
631
|
}// global
|
|
577
632
|
|
|
578
633
|
|
|
634
|
+
static bool
|
|
635
|
+
stop_oldest_player ()
|
|
636
|
+
{
|
|
637
|
+
for (auto& player : global::players)
|
|
638
|
+
{
|
|
639
|
+
if (
|
|
640
|
+
!player.self->is_loaded() ||
|
|
641
|
+
player.self->is_pending() ||
|
|
642
|
+
player.self->is_streaming())
|
|
643
|
+
{
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (player.self->source.state() == SoundPlayer::PLAYING)
|
|
648
|
+
{
|
|
649
|
+
player.stop();
|
|
650
|
+
LOG("stop oldest player");
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
|
|
579
657
|
static void
|
|
580
|
-
|
|
658
|
+
stop_players_over_limit ()
|
|
581
659
|
{
|
|
582
|
-
|
|
660
|
+
uint max = OpenAL_get_max_playing_sources();
|
|
661
|
+
if (max == 0) return;
|
|
662
|
+
|
|
663
|
+
// a source stopped right after it starts can play again on macOS,
|
|
664
|
+
// so count them on each call
|
|
665
|
+
uint count = (uint) std::count_if(
|
|
583
666
|
global::players.begin(), global::players.end(),
|
|
584
|
-
[](auto&
|
|
585
|
-
{
|
|
586
|
-
return !player || player.state() == SoundPlayer::STOPPED;
|
|
587
|
-
});
|
|
667
|
+
[](auto& p) {return p.self->is_active();});
|
|
588
668
|
|
|
589
|
-
|
|
669
|
+
for (uint i = max; i < count; ++i)
|
|
670
|
+
{
|
|
671
|
+
if (!stop_oldest_player())
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
590
674
|
}
|
|
591
675
|
|
|
592
|
-
static
|
|
593
|
-
|
|
676
|
+
static void
|
|
677
|
+
play_pending_players ()
|
|
594
678
|
{
|
|
595
|
-
|
|
596
|
-
if (player)
|
|
597
|
-
LOG("new player");
|
|
679
|
+
stop_players_over_limit();
|
|
598
680
|
|
|
599
|
-
|
|
681
|
+
auto& pendings = global::pending_players;
|
|
682
|
+
while (!pendings.empty())
|
|
600
683
|
{
|
|
601
|
-
|
|
684
|
+
SoundPlayer& player = pendings.front();
|
|
685
|
+
if (player.self->pending_state == SoundPlayer::PLAYING)
|
|
602
686
|
{
|
|
603
|
-
|
|
687
|
+
// try again on the next call
|
|
688
|
+
try
|
|
604
689
|
{
|
|
605
|
-
player
|
|
606
|
-
|
|
607
|
-
|
|
690
|
+
player.self->source.play();
|
|
691
|
+
}
|
|
692
|
+
catch (const OpenALError&)
|
|
693
|
+
{
|
|
694
|
+
return;
|
|
608
695
|
}
|
|
696
|
+
|
|
697
|
+
player.self->pending_state = SoundPlayer::STATE_NONE;
|
|
609
698
|
}
|
|
610
|
-
}
|
|
611
699
|
|
|
612
|
-
|
|
613
|
-
{
|
|
614
|
-
player = reuse_player(&global::players.front());
|
|
615
|
-
LOG("reuse oldest player");
|
|
700
|
+
pendings.erase(pendings.begin());
|
|
616
701
|
}
|
|
617
|
-
|
|
618
|
-
remove_inactive_players();
|
|
619
|
-
|
|
620
|
-
if (player)
|
|
621
|
-
global::players.emplace_back(player);
|
|
622
|
-
|
|
623
|
-
return player;
|
|
624
702
|
}
|
|
625
703
|
|
|
626
704
|
void
|
|
@@ -628,9 +706,22 @@ namespace Beeps
|
|
|
628
706
|
{
|
|
629
707
|
for (auto& player : global::players)
|
|
630
708
|
{
|
|
631
|
-
|
|
632
|
-
|
|
709
|
+
SoundPlayer::Data* self = player.self.get();
|
|
710
|
+
if (!self->is_streaming() || self->is_pending())
|
|
711
|
+
continue;
|
|
712
|
+
|
|
713
|
+
bool processed = Xot::call_and_log_errors<OpenALError>([&]()
|
|
714
|
+
{
|
|
715
|
+
self->process_and_queue_stream_buffers();
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
if (!processed)
|
|
719
|
+
self->clear();
|
|
720
|
+
else if (self->is_pending())
|
|
721
|
+
global::pending_players.emplace_back(player);
|
|
633
722
|
}
|
|
723
|
+
|
|
724
|
+
play_pending_players();
|
|
634
725
|
}
|
|
635
726
|
|
|
636
727
|
void
|
|
@@ -640,6 +731,7 @@ namespace Beeps
|
|
|
640
731
|
player.self->clear();
|
|
641
732
|
|
|
642
733
|
global::players.clear();
|
|
734
|
+
global::pending_players.clear();
|
|
643
735
|
}
|
|
644
736
|
|
|
645
737
|
void
|
|
@@ -658,15 +750,109 @@ namespace Beeps
|
|
|
658
750
|
{
|
|
659
751
|
}
|
|
660
752
|
|
|
753
|
+
static void
|
|
754
|
+
remove_stopped_players ()
|
|
755
|
+
{
|
|
756
|
+
for (auto& player : global::players)
|
|
757
|
+
{
|
|
758
|
+
if (player.self->is_loaded() && player.state() == SoundPlayer::STOPPED)
|
|
759
|
+
player.self->clear();
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
auto it = std::remove_if(
|
|
763
|
+
global::players.begin(), global::players.end(),
|
|
764
|
+
[](auto& player) {return !player.self->is_loaded();});
|
|
765
|
+
|
|
766
|
+
global::players.erase(it, global::players.end());
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
static void
|
|
770
|
+
create_source (SoundSource* source)
|
|
771
|
+
{
|
|
772
|
+
remove_stopped_players();
|
|
773
|
+
if (source->create())
|
|
774
|
+
return;
|
|
775
|
+
|
|
776
|
+
if (!stop_oldest_player())
|
|
777
|
+
openal_error(__FILE__, __LINE__, "too many sound players");
|
|
778
|
+
|
|
779
|
+
remove_stopped_players();
|
|
780
|
+
if (!source->create())
|
|
781
|
+
openal_error(__FILE__, __LINE__, "failed to create a source");
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
static bool
|
|
785
|
+
attach_source (SoundPlayer* player)
|
|
786
|
+
{
|
|
787
|
+
SoundPlayer::Data* self = player->self.get();
|
|
788
|
+
|
|
789
|
+
bool loaded = false;
|
|
790
|
+
try
|
|
791
|
+
{
|
|
792
|
+
Xot::call_and_log_errors<OpenALError>([&]()
|
|
793
|
+
{
|
|
794
|
+
create_source(&self->source);
|
|
795
|
+
if (self->signals)
|
|
796
|
+
self->load_signals();
|
|
797
|
+
else if (!self->load_stream())
|
|
798
|
+
return;
|
|
799
|
+
|
|
800
|
+
loaded = true;
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
catch (...)
|
|
804
|
+
{
|
|
805
|
+
self->clear();
|
|
806
|
+
throw;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (!loaded)
|
|
810
|
+
{
|
|
811
|
+
self->clear();
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
global::players.emplace_back(*player);
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
818
|
+
|
|
661
819
|
void
|
|
662
820
|
SoundPlayer::play ()
|
|
663
821
|
{
|
|
664
|
-
|
|
822
|
+
if (!*this)
|
|
823
|
+
invalid_state_error(__FILE__, __LINE__);
|
|
824
|
+
|
|
825
|
+
if (self->is_loaded() && state() == STOPPED)
|
|
826
|
+
self->clear();
|
|
827
|
+
|
|
828
|
+
if (!self->is_loaded() && !attach_source(this))
|
|
829
|
+
return;
|
|
830
|
+
|
|
831
|
+
#if defined(OSX) || defined(IOS)
|
|
832
|
+
// on macOS, a source fails to play when too many sources are playing,
|
|
833
|
+
// and a stopped source still counts as playing for a moment,
|
|
834
|
+
// so stop the oldest one and let the new one wait in the order
|
|
835
|
+
if (self->pending_state == PLAYING) return;
|
|
836
|
+
|
|
837
|
+
self->pending_state = PLAYING;
|
|
838
|
+
global::pending_players.emplace_back(*this);
|
|
839
|
+
|
|
840
|
+
play_pending_players();
|
|
841
|
+
#else
|
|
842
|
+
if (!Xot::call_and_log_errors<OpenALError>([&]() {self->source.play();}))
|
|
843
|
+
self->clear();
|
|
844
|
+
#endif
|
|
665
845
|
}
|
|
666
846
|
|
|
667
847
|
void
|
|
668
848
|
SoundPlayer::pause ()
|
|
669
849
|
{
|
|
850
|
+
if (self->pending_state == PLAYING)
|
|
851
|
+
{
|
|
852
|
+
self->pending_state = PAUSED;
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
670
856
|
self->source.pause();
|
|
671
857
|
}
|
|
672
858
|
|
|
@@ -682,13 +868,18 @@ namespace Beeps
|
|
|
682
868
|
void
|
|
683
869
|
SoundPlayer::stop ()
|
|
684
870
|
{
|
|
685
|
-
self->
|
|
686
|
-
self->position = 0;
|
|
871
|
+
self->clear();
|
|
687
872
|
}
|
|
688
873
|
|
|
689
874
|
SoundPlayer::State
|
|
690
875
|
SoundPlayer::state () const
|
|
691
876
|
{
|
|
877
|
+
if (self->is_pending())
|
|
878
|
+
return self->pending_state;
|
|
879
|
+
|
|
880
|
+
if (!self->is_loaded())
|
|
881
|
+
return *this ? STOPPED : STATE_NONE;
|
|
882
|
+
|
|
692
883
|
State s = self->source.state();
|
|
693
884
|
if (s == STOPPED && self->is_streaming())
|
|
694
885
|
return PLAYING;
|
|
@@ -698,7 +889,15 @@ namespace Beeps
|
|
|
698
889
|
void
|
|
699
890
|
SoundPlayer::set_position (uint position)
|
|
700
891
|
{
|
|
701
|
-
if (self->
|
|
892
|
+
if (self->signals && position >= self->signals.nsamples())
|
|
893
|
+
argument_error(__FILE__, __LINE__);
|
|
894
|
+
|
|
895
|
+
if (self->is_loaded() && state() == STOPPED)
|
|
896
|
+
self->clear();
|
|
897
|
+
|
|
898
|
+
if (!self->is_loaded())
|
|
899
|
+
self->position = position;
|
|
900
|
+
else if (self->is_streaming())
|
|
702
901
|
{
|
|
703
902
|
if (!self->processor || !self->processor->seekable())
|
|
704
903
|
invalid_state_error(__FILE__, __LINE__, "processor is not seekable");
|
|
@@ -782,7 +981,7 @@ namespace Beeps
|
|
|
782
981
|
|
|
783
982
|
SoundPlayer::operator bool () const
|
|
784
983
|
{
|
|
785
|
-
return self->
|
|
984
|
+
return self->signals || self->processor;
|
|
786
985
|
}
|
|
787
986
|
|
|
788
987
|
bool
|
|
@@ -802,14 +1001,19 @@ namespace Beeps
|
|
|
802
1001
|
{
|
|
803
1002
|
}
|
|
804
1003
|
|
|
1004
|
+
virtual Data* dup () const
|
|
1005
|
+
{
|
|
1006
|
+
return new Data(*this);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
805
1009
|
virtual void attach_to (SoundPlayer* player)
|
|
806
1010
|
{
|
|
807
1011
|
not_implemented_error(__FILE__, __LINE__);
|
|
808
1012
|
}
|
|
809
1013
|
|
|
810
|
-
virtual
|
|
1014
|
+
virtual void save (const char* path) const
|
|
811
1015
|
{
|
|
812
|
-
|
|
1016
|
+
not_implemented_error(__FILE__, __LINE__);
|
|
813
1017
|
}
|
|
814
1018
|
|
|
815
1019
|
virtual uint nchannels () const
|
|
@@ -817,14 +1021,14 @@ namespace Beeps
|
|
|
817
1021
|
return 0;
|
|
818
1022
|
}
|
|
819
1023
|
|
|
820
|
-
virtual
|
|
1024
|
+
virtual double sample_rate () const
|
|
821
1025
|
{
|
|
822
1026
|
return 0;
|
|
823
1027
|
}
|
|
824
1028
|
|
|
825
|
-
virtual
|
|
1029
|
+
virtual float seconds () const
|
|
826
1030
|
{
|
|
827
|
-
|
|
1031
|
+
return 0;
|
|
828
1032
|
}
|
|
829
1033
|
|
|
830
1034
|
virtual bool is_valid () const
|
|
@@ -848,16 +1052,24 @@ namespace Beeps
|
|
|
848
1052
|
{
|
|
849
1053
|
}
|
|
850
1054
|
|
|
1055
|
+
Data* dup () const override
|
|
1056
|
+
{
|
|
1057
|
+
return new SoundData(*this);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
851
1060
|
void attach_to (SoundPlayer* player) override
|
|
852
1061
|
{
|
|
853
|
-
assert(player &&
|
|
1062
|
+
assert(player && !*player);
|
|
854
1063
|
|
|
855
1064
|
player->self->attach_signals(signals);
|
|
856
1065
|
}
|
|
857
1066
|
|
|
858
|
-
|
|
1067
|
+
void save (const char* path) const override
|
|
859
1068
|
{
|
|
860
|
-
|
|
1069
|
+
if (!signals)
|
|
1070
|
+
invalid_state_error(__FILE__, __LINE__);
|
|
1071
|
+
|
|
1072
|
+
Signals_save(signals, path);
|
|
861
1073
|
}
|
|
862
1074
|
|
|
863
1075
|
uint nchannels () const override
|
|
@@ -865,17 +1077,14 @@ namespace Beeps
|
|
|
865
1077
|
return signals ? signals.nchannels() : Super::nchannels();
|
|
866
1078
|
}
|
|
867
1079
|
|
|
868
|
-
|
|
1080
|
+
double sample_rate () const override
|
|
869
1081
|
{
|
|
870
|
-
return signals ?
|
|
1082
|
+
return signals ? signals.sample_rate() : Super::sample_rate();
|
|
871
1083
|
}
|
|
872
1084
|
|
|
873
|
-
|
|
1085
|
+
float seconds () const override
|
|
874
1086
|
{
|
|
875
|
-
|
|
876
|
-
invalid_state_error(__FILE__, __LINE__);
|
|
877
|
-
|
|
878
|
-
Signals_save(signals, path);
|
|
1087
|
+
return signals ? Signals_get_seconds(signals) : Super::seconds();
|
|
879
1088
|
}
|
|
880
1089
|
|
|
881
1090
|
bool is_valid () const override
|
|
@@ -891,34 +1100,40 @@ namespace Beeps
|
|
|
891
1100
|
|
|
892
1101
|
Processor::Ref processor;
|
|
893
1102
|
|
|
894
|
-
|
|
1103
|
+
uint nchannels_ = 0;
|
|
895
1104
|
|
|
896
|
-
|
|
1105
|
+
double sample_rate_ = 0;
|
|
897
1106
|
|
|
898
1107
|
StreamSoundData (Processor* processor, uint nchannels, double sample_rate)
|
|
899
1108
|
{
|
|
900
1109
|
assert(processor && *processor && nchannels > 0 && sample_rate > 0);
|
|
901
1110
|
|
|
902
1111
|
this->processor = processor;
|
|
903
|
-
this->sample_rate_ = sample_rate;
|
|
904
1112
|
this->nchannels_ = nchannels;
|
|
1113
|
+
this->sample_rate_ = sample_rate;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
Data* dup () const override
|
|
1117
|
+
{
|
|
1118
|
+
invalid_state_error(
|
|
1119
|
+
__FILE__, __LINE__, "can not duplicate a streaming sound");
|
|
905
1120
|
}
|
|
906
1121
|
|
|
907
1122
|
void attach_to (SoundPlayer* player) override
|
|
908
1123
|
{
|
|
909
|
-
assert(player &&
|
|
1124
|
+
assert(player && !*player);
|
|
910
1125
|
|
|
911
1126
|
player->self->attach_stream(processor, nchannels_, sample_rate_);
|
|
912
1127
|
}
|
|
913
1128
|
|
|
914
|
-
|
|
1129
|
+
uint nchannels () const override
|
|
915
1130
|
{
|
|
916
|
-
return
|
|
1131
|
+
return nchannels_;
|
|
917
1132
|
}
|
|
918
1133
|
|
|
919
|
-
|
|
1134
|
+
double sample_rate () const override
|
|
920
1135
|
{
|
|
921
|
-
return
|
|
1136
|
+
return sample_rate_;
|
|
922
1137
|
}
|
|
923
1138
|
|
|
924
1139
|
float seconds () const override
|
|
@@ -928,7 +1143,7 @@ namespace Beeps
|
|
|
928
1143
|
|
|
929
1144
|
bool is_valid () const override
|
|
930
1145
|
{
|
|
931
|
-
return processor &&
|
|
1146
|
+
return processor && nchannels_ > 0 && sample_rate_ > 0;
|
|
932
1147
|
}
|
|
933
1148
|
|
|
934
1149
|
};// StreamSoundData
|
|
@@ -969,21 +1184,28 @@ namespace Beeps
|
|
|
969
1184
|
self.reset(new StreamSoundData(processor, nchannels, sample_rate));
|
|
970
1185
|
}
|
|
971
1186
|
|
|
1187
|
+
Sound::Sound (Data* data)
|
|
1188
|
+
: self(data)
|
|
1189
|
+
{
|
|
1190
|
+
}
|
|
1191
|
+
|
|
972
1192
|
Sound::~Sound ()
|
|
973
1193
|
{
|
|
974
1194
|
}
|
|
975
1195
|
|
|
1196
|
+
Sound
|
|
1197
|
+
Sound::dup () const
|
|
1198
|
+
{
|
|
1199
|
+
return Sound(self->dup());
|
|
1200
|
+
}
|
|
1201
|
+
|
|
976
1202
|
SoundPlayer
|
|
977
1203
|
Sound::play ()
|
|
978
1204
|
{
|
|
979
|
-
SoundPlayer player
|
|
980
|
-
|
|
981
|
-
invalid_state_error(__FILE__, __LINE__);
|
|
982
|
-
|
|
1205
|
+
SoundPlayer player;
|
|
1206
|
+
self->attach_to(&player);
|
|
983
1207
|
player.set_gain(gain());
|
|
984
1208
|
player.set_loop(loop());
|
|
985
|
-
|
|
986
|
-
self->attach_to(&player);
|
|
987
1209
|
player.play();
|
|
988
1210
|
|
|
989
1211
|
#if 0
|
|
@@ -996,18 +1218,18 @@ namespace Beeps
|
|
|
996
1218
|
return player;
|
|
997
1219
|
}
|
|
998
1220
|
|
|
999
|
-
double
|
|
1000
|
-
Sound::sample_rate () const
|
|
1001
|
-
{
|
|
1002
|
-
return self->sample_rate();
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
1221
|
uint
|
|
1006
1222
|
Sound::nchannels () const
|
|
1007
1223
|
{
|
|
1008
1224
|
return self->nchannels();
|
|
1009
1225
|
}
|
|
1010
1226
|
|
|
1227
|
+
double
|
|
1228
|
+
Sound::sample_rate () const
|
|
1229
|
+
{
|
|
1230
|
+
return self->sample_rate();
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1011
1233
|
float
|
|
1012
1234
|
Sound::seconds () const
|
|
1013
1235
|
{
|