beeps 0.4.0 → 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 +1 -1
- 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 +1 -1
- data/.doc/ext/beeps/oscillator.cpp +2 -2
- 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 +1 -1
- data/.doc/ext/beeps/signals.cpp +1 -1
- data/.doc/ext/beeps/sound.cpp +6 -6
- data/.doc/ext/beeps/sound_player.cpp +5 -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 +1 -1
- data/.github/workflows/release-gem.yml +2 -49
- data/.github/workflows/tag.yml +5 -28
- data/.github/workflows/test.yml +1 -37
- data/ChangeLog.md +21 -0
- data/Gemfile.lock +13 -8
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/beeps.gemspec +3 -3
- data/ext/beeps/analyser.cpp +1 -1
- 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 +1 -1
- data/ext/beeps/oscillator.cpp +2 -2
- 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 +1 -1
- data/ext/beeps/signals.cpp +1 -1
- data/ext/beeps/sound.cpp +6 -6
- data/ext/beeps/sound_player.cpp +5 -5
- data/ext/beeps/text_in.cpp +1 -1
- data/ext/beeps/time_stretch.cpp +1 -1
- data/ext/beeps/value.cpp +1 -1
- data/include/beeps/signals.h +2 -2
- data/include/beeps/sound.h +3 -3
- data/lib/beeps/beeps.rb +4 -4
- data/lib/beeps/sound.rb +1 -1
- 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 +347 -152
- data/src/win32/beeps.cpp +25 -2
- data/src/win32/signals.cpp +102 -21
- 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
|
|
@@ -817,12 +1016,12 @@ namespace Beeps
|
|
|
817
1016
|
not_implemented_error(__FILE__, __LINE__);
|
|
818
1017
|
}
|
|
819
1018
|
|
|
820
|
-
virtual
|
|
1019
|
+
virtual uint nchannels () const
|
|
821
1020
|
{
|
|
822
1021
|
return 0;
|
|
823
1022
|
}
|
|
824
1023
|
|
|
825
|
-
virtual
|
|
1024
|
+
virtual double sample_rate () const
|
|
826
1025
|
{
|
|
827
1026
|
return 0;
|
|
828
1027
|
}
|
|
@@ -860,7 +1059,7 @@ namespace Beeps
|
|
|
860
1059
|
|
|
861
1060
|
void attach_to (SoundPlayer* player) override
|
|
862
1061
|
{
|
|
863
|
-
assert(player &&
|
|
1062
|
+
assert(player && !*player);
|
|
864
1063
|
|
|
865
1064
|
player->self->attach_signals(signals);
|
|
866
1065
|
}
|
|
@@ -873,14 +1072,14 @@ namespace Beeps
|
|
|
873
1072
|
Signals_save(signals, path);
|
|
874
1073
|
}
|
|
875
1074
|
|
|
876
|
-
|
|
1075
|
+
uint nchannels () const override
|
|
877
1076
|
{
|
|
878
|
-
return signals ? signals.
|
|
1077
|
+
return signals ? signals.nchannels() : Super::nchannels();
|
|
879
1078
|
}
|
|
880
1079
|
|
|
881
|
-
|
|
1080
|
+
double sample_rate () const override
|
|
882
1081
|
{
|
|
883
|
-
return signals ? signals.
|
|
1082
|
+
return signals ? signals.sample_rate() : Super::sample_rate();
|
|
884
1083
|
}
|
|
885
1084
|
|
|
886
1085
|
float seconds () const override
|
|
@@ -901,17 +1100,17 @@ namespace Beeps
|
|
|
901
1100
|
|
|
902
1101
|
Processor::Ref processor;
|
|
903
1102
|
|
|
904
|
-
|
|
1103
|
+
uint nchannels_ = 0;
|
|
905
1104
|
|
|
906
|
-
|
|
1105
|
+
double sample_rate_ = 0;
|
|
907
1106
|
|
|
908
1107
|
StreamSoundData (Processor* processor, uint nchannels, double sample_rate)
|
|
909
1108
|
{
|
|
910
1109
|
assert(processor && *processor && nchannels > 0 && sample_rate > 0);
|
|
911
1110
|
|
|
912
1111
|
this->processor = processor;
|
|
913
|
-
this->sample_rate_ = sample_rate;
|
|
914
1112
|
this->nchannels_ = nchannels;
|
|
1113
|
+
this->sample_rate_ = sample_rate;
|
|
915
1114
|
}
|
|
916
1115
|
|
|
917
1116
|
Data* dup () const override
|
|
@@ -922,19 +1121,19 @@ namespace Beeps
|
|
|
922
1121
|
|
|
923
1122
|
void attach_to (SoundPlayer* player) override
|
|
924
1123
|
{
|
|
925
|
-
assert(player &&
|
|
1124
|
+
assert(player && !*player);
|
|
926
1125
|
|
|
927
1126
|
player->self->attach_stream(processor, nchannels_, sample_rate_);
|
|
928
1127
|
}
|
|
929
1128
|
|
|
930
|
-
|
|
1129
|
+
uint nchannels () const override
|
|
931
1130
|
{
|
|
932
|
-
return
|
|
1131
|
+
return nchannels_;
|
|
933
1132
|
}
|
|
934
1133
|
|
|
935
|
-
|
|
1134
|
+
double sample_rate () const override
|
|
936
1135
|
{
|
|
937
|
-
return
|
|
1136
|
+
return sample_rate_;
|
|
938
1137
|
}
|
|
939
1138
|
|
|
940
1139
|
float seconds () const override
|
|
@@ -944,7 +1143,7 @@ namespace Beeps
|
|
|
944
1143
|
|
|
945
1144
|
bool is_valid () const override
|
|
946
1145
|
{
|
|
947
|
-
return processor &&
|
|
1146
|
+
return processor && nchannels_ > 0 && sample_rate_ > 0;
|
|
948
1147
|
}
|
|
949
1148
|
|
|
950
1149
|
};// StreamSoundData
|
|
@@ -1003,14 +1202,10 @@ namespace Beeps
|
|
|
1003
1202
|
SoundPlayer
|
|
1004
1203
|
Sound::play ()
|
|
1005
1204
|
{
|
|
1006
|
-
SoundPlayer player
|
|
1007
|
-
|
|
1008
|
-
invalid_state_error(__FILE__, __LINE__);
|
|
1009
|
-
|
|
1205
|
+
SoundPlayer player;
|
|
1206
|
+
self->attach_to(&player);
|
|
1010
1207
|
player.set_gain(gain());
|
|
1011
1208
|
player.set_loop(loop());
|
|
1012
|
-
|
|
1013
|
-
self->attach_to(&player);
|
|
1014
1209
|
player.play();
|
|
1015
1210
|
|
|
1016
1211
|
#if 0
|
|
@@ -1023,18 +1218,18 @@ namespace Beeps
|
|
|
1023
1218
|
return player;
|
|
1024
1219
|
}
|
|
1025
1220
|
|
|
1026
|
-
double
|
|
1027
|
-
Sound::sample_rate () const
|
|
1028
|
-
{
|
|
1029
|
-
return self->sample_rate();
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
1221
|
uint
|
|
1033
1222
|
Sound::nchannels () const
|
|
1034
1223
|
{
|
|
1035
1224
|
return self->nchannels();
|
|
1036
1225
|
}
|
|
1037
1226
|
|
|
1227
|
+
double
|
|
1228
|
+
Sound::sample_rate () const
|
|
1229
|
+
{
|
|
1230
|
+
return self->sample_rate();
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1038
1233
|
float
|
|
1039
1234
|
Sound::seconds () const
|
|
1040
1235
|
{
|