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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.doc/ext/beeps/analyser.cpp +4 -4
  3. data/.doc/ext/beeps/envelope.cpp +1 -1
  4. data/.doc/ext/beeps/file_in.cpp +1 -1
  5. data/.doc/ext/beeps/gain.cpp +1 -1
  6. data/.doc/ext/beeps/high_pass.cpp +1 -1
  7. data/.doc/ext/beeps/low_pass.cpp +1 -1
  8. data/.doc/ext/beeps/mic_in.cpp +1 -1
  9. data/.doc/ext/beeps/mixer.cpp +2 -2
  10. data/.doc/ext/beeps/oscillator.cpp +3 -3
  11. data/.doc/ext/beeps/pitch_shift.cpp +1 -1
  12. data/.doc/ext/beeps/processor.cpp +1 -1
  13. data/.doc/ext/beeps/reverb.cpp +1 -1
  14. data/.doc/ext/beeps/sequencer.cpp +2 -2
  15. data/.doc/ext/beeps/signals.cpp +3 -3
  16. data/.doc/ext/beeps/sound.cpp +16 -8
  17. data/.doc/ext/beeps/sound_player.cpp +13 -5
  18. data/.doc/ext/beeps/text_in.cpp +1 -1
  19. data/.doc/ext/beeps/time_stretch.cpp +1 -1
  20. data/.doc/ext/beeps/value.cpp +2 -2
  21. data/.github/workflows/release-gem.yml +2 -40
  22. data/.github/workflows/tag.yml +5 -28
  23. data/.github/workflows/test.yml +1 -28
  24. data/ChangeLog.md +28 -0
  25. data/Gemfile.lock +13 -8
  26. data/Rakefile +1 -1
  27. data/VERSION +1 -1
  28. data/beeps.gemspec +3 -3
  29. data/ext/beeps/analyser.cpp +4 -4
  30. data/ext/beeps/envelope.cpp +1 -1
  31. data/ext/beeps/file_in.cpp +1 -1
  32. data/ext/beeps/gain.cpp +1 -1
  33. data/ext/beeps/high_pass.cpp +1 -1
  34. data/ext/beeps/low_pass.cpp +1 -1
  35. data/ext/beeps/mic_in.cpp +1 -1
  36. data/ext/beeps/mixer.cpp +2 -2
  37. data/ext/beeps/oscillator.cpp +3 -3
  38. data/ext/beeps/pitch_shift.cpp +1 -1
  39. data/ext/beeps/processor.cpp +1 -1
  40. data/ext/beeps/reverb.cpp +1 -1
  41. data/ext/beeps/sequencer.cpp +2 -2
  42. data/ext/beeps/signals.cpp +3 -3
  43. data/ext/beeps/sound.cpp +17 -8
  44. data/ext/beeps/sound_player.cpp +14 -5
  45. data/ext/beeps/text_in.cpp +1 -1
  46. data/ext/beeps/time_stretch.cpp +1 -1
  47. data/ext/beeps/value.cpp +2 -2
  48. data/include/beeps/defs.h +14 -4
  49. data/include/beeps/signals.h +2 -2
  50. data/include/beeps/sound.h +9 -3
  51. data/lib/beeps/beeps.rb +4 -4
  52. data/lib/beeps/extension.rb +1 -1
  53. data/lib/beeps/sound.rb +2 -2
  54. data/src/file_in.cpp +2 -0
  55. data/src/mic_in.cpp +10 -9
  56. data/src/openal.cpp +66 -8
  57. data/src/openal.h +5 -1
  58. data/src/oscillator.cpp +1 -1
  59. data/src/signals.cpp +6 -6
  60. data/src/sound.cpp +382 -160
  61. data/src/win32/beeps.cpp +25 -2
  62. data/src/win32/signals.cpp +103 -29
  63. data/test/test_file_in.rb +38 -0
  64. data/test/test_sound.rb +6 -6
  65. data/test/test_sound_player.rb +86 -20
  66. metadata +7 -8
  67. 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(sample_rate > 0 && nchannels > 0);
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
- void create ()
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
- ALint count = 0;
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::STATE_UNKNOWN;
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::STATE_UNKNOWN;
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
- if (!*this) return 1;
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
- float gain = 1;
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
- if (!*this) return false;
366
+ return self->loop;
367
+ }
368
368
 
369
- ALint loop = AL_FALSE;
370
- alGetSourcei(self->id, AL_LOOPING, &loop);
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 loop != AL_FALSE;
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 = NULL;
391
+ void* context = NULL;
390
392
 
391
- ALint id = -1;
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
- void create ()
406
+ bool create ()
399
407
  {
400
408
  ALuint id_ = 0;
401
409
  alGenSources(1, &id_);
402
- if (!OpenAL_no_error()) return;
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
- double sample_rate = 0;
446
+ uint nchannels = 0;
437
447
 
438
- uint position = 0;
448
+ double sample_rate = 0;
439
449
 
440
- SoundSource source;
450
+ uint position = 0;
441
451
 
442
- std::vector<SoundBuffer> buffers;
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
- sample_rate = 0;
451
- position = 0;
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
- sample_rate = signals.sample_rate();
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
- void attach_stream (
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
- SoundBuffer buffer;
511
- while (is_streaming())
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
- if (!source.unqueue(&buffer))
514
- return;
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
- if (source.state() == STOPPED)
526
- {
527
- expand_buffer();
528
- source.play();
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 is_streaming () const
595
+ bool is_loaded () const
546
596
  {
547
- return processor && stream_context;
597
+ return source;
548
598
  }
549
599
 
550
- };// SoundPlayer::Data
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
- static SoundPlayer
554
- create_player ()
555
- {
556
- SoundPlayer player;
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
- static SoundPlayer
562
- reuse_player (SoundPlayer* player)
563
- {
564
- SoundPlayer newplayer;
565
- newplayer.self->source = player->self->source.reuse();
566
- player->self->clear();
567
- return newplayer;
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
- remove_inactive_players ()
658
+ stop_players_over_limit ()
581
659
  {
582
- auto it = std::remove_if(
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& player)
585
- {
586
- return !player || player.state() == SoundPlayer::STOPPED;
587
- });
667
+ [](auto& p) {return p.self->is_active();});
588
668
 
589
- global::players.erase(it, global::players.end());
669
+ for (uint i = max; i < count; ++i)
670
+ {
671
+ if (!stop_oldest_player())
672
+ break;
673
+ }
590
674
  }
591
675
 
592
- static SoundPlayer
593
- get_next_player ()
676
+ static void
677
+ play_pending_players ()
594
678
  {
595
- SoundPlayer player = create_player();
596
- if (player)
597
- LOG("new player");
679
+ stop_players_over_limit();
598
680
 
599
- if (!player)
681
+ auto& pendings = global::pending_players;
682
+ while (!pendings.empty())
600
683
  {
601
- for (auto& p : global::players)
684
+ SoundPlayer& player = pendings.front();
685
+ if (player.self->pending_state == SoundPlayer::PLAYING)
602
686
  {
603
- if (p && p.state() == SoundPlayer::STOPPED)
687
+ // try again on the next call
688
+ try
604
689
  {
605
- player = reuse_player(&p);
606
- LOG("reuse stopped player");
607
- break;
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
- if (!player && !global::players.empty())
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
- if (player.self->is_streaming())
632
- player.self->process_and_queue_stream_buffers();
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
- self->source.play();
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->source.stop();
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->is_streaming())
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->source;
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 double sample_rate () const
1014
+ virtual void save (const char* path) const
811
1015
  {
812
- return 0;
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 float seconds () const
1024
+ virtual double sample_rate () const
821
1025
  {
822
1026
  return 0;
823
1027
  }
824
1028
 
825
- virtual void save (const char* path) const
1029
+ virtual float seconds () const
826
1030
  {
827
- not_implemented_error(__FILE__, __LINE__);
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 && *player);
1062
+ assert(player && !*player);
854
1063
 
855
1064
  player->self->attach_signals(signals);
856
1065
  }
857
1066
 
858
- double sample_rate () const override
1067
+ void save (const char* path) const override
859
1068
  {
860
- return signals ? signals.sample_rate() : Super::sample_rate();
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
- float seconds () const override
1080
+ double sample_rate () const override
869
1081
  {
870
- return signals ? Signals_get_seconds(signals) : Super::seconds();
1082
+ return signals ? signals.sample_rate() : Super::sample_rate();
871
1083
  }
872
1084
 
873
- void save (const char* path) const override
1085
+ float seconds () const override
874
1086
  {
875
- if (!signals)
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
- double sample_rate_ = 0;
1103
+ uint nchannels_ = 0;
895
1104
 
896
- uint nchannels_ = 0;
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 && *player);
1124
+ assert(player && !*player);
910
1125
 
911
1126
  player->self->attach_stream(processor, nchannels_, sample_rate_);
912
1127
  }
913
1128
 
914
- double sample_rate () const override
1129
+ uint nchannels () const override
915
1130
  {
916
- return sample_rate_;
1131
+ return nchannels_;
917
1132
  }
918
1133
 
919
- uint nchannels () const override
1134
+ double sample_rate () const override
920
1135
  {
921
- return nchannels_;
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 && sample_rate_ > 0 && nchannels_ > 0;
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 = get_next_player();
980
- if (!player)
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
  {