forked from Sneeds-Feed-and-Seed/sneedacity
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAudioIO.cpp
4607 lines (3984 loc) · 162 KB
/
AudioIO.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**********************************************************************
Sneedacity: A Digital Audio Editor
AudioIO.cpp
Copyright 2000-2004:
Dominic Mazzoni
Joshua Haberman
Markus Meyer
Matt Brubeck
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
********************************************************************//**
\class AudioIoCallback
\brief AudioIoCallback is a class that implements the callback required
by PortAudio. The callback needs to be responsive, has no GUI, and
copies data into and out of the sound card buffers. It also sends data
to the meters.
*//*****************************************************************//**
\class AudioIO
\brief AudioIO uses the PortAudio library to play and record sound.
Great care and attention to detail are necessary for understanding and
modifying this system. The code in this file is run from three
different thread contexts: the UI thread, the disk thread (which
this file creates and maintains; in the code, this is called the
Audio Thread), and the PortAudio callback thread.
To highlight this deliniation, the file is divided into three parts
based on what thread context each function is intended to run in.
\par EXPERIMENTAL_MIDI_OUT
If EXPERIMENTAL_MIDI_OUT is defined, this class also manages
MIDI playback. The reason for putting MIDI here rather than in, say,
class MidiIO, is that there is no high-level synchronization and
transport architecture, so Audio and MIDI must be coupled in order
to start/stop/pause and synchronize them.
\par MIDI With Audio
When Audio and MIDI play simultaneously, MIDI synchronizes to Audio.
This is necessary because the Audio sample clock is not the same
hardware as the system time used to schedule MIDI messages. MIDI
is synchronized to Audio because it is simple to pause or rush
the dispatch of MIDI messages, but generally impossible to pause
or rush synchronous audio samples (without distortion).
\par
MIDI output is driven by yet another thread. In principle, we could
output timestamped MIDI data at the same time we fill audio buffers
from disk, but audio buffers are filled far in advance of playback
time, and there is a lower latency thread (PortAudio's callback) that
actually sends samples to the output device. The relatively low
latency to the output device allows Sneedacity to stop audio output
quickly. We want the same behavior for MIDI, but there is not
periodic callback from PortMidi (because MIDI is asynchronous), so
this function is performed by the MidiThread class.
\par
When Audio is running, MIDI is synchronized to Audio. Globals are set
in the Audio callback (sneedacityAudioCallback) for use by a time
function that reports milliseconds to PortMidi. (Details below.)
\par MIDI Without Audio
When Audio is not running, PortMidi uses its own millisecond timer
since there is no audio to synchronize to. (Details below.)
\par Implementation Notes and Details for MIDI
When opening devices, successAudio and successMidi indicate errors
if false, so normally both are true. Use playbackChannels,
captureChannels and mMidiPlaybackTracks.empty() to determine if
Audio or MIDI is actually in use.
\par Audio Time
Normally, the current time during playback is given by the variable
mTime. mTime normally advances by frames / samplerate each time an
audio buffer is output by the audio callback. However, Sneedacity has
a speed control that can perform continuously variable time stretching
on audio. This is achieved in two places: the playback "mixer" that
generates the samples for output processes the audio according to
the speed control. In a separate algorithm, the audio callback updates
mTime by (frames / samplerate) * factor, where factor reflects the
speed at mTime. This effectively integrates speed to get position.
Negative speeds are allowed too, for instance in scrubbing.
\par The Big Picture
@verbatim
Sample
Time (in seconds, = total_sample_count / sample_rate)
^
| / /
| y=x-mSystemTimeMinusAudioTime / /
| / # /
| / /
| / # <- callbacks (#) showing
| /# / lots of timing jitter.
| top line is "full buffer" / / Some are later,
| condition / / indicating buffer is
| / / getting low. Plot
| / # / shows sample time
| / # / (based on how many
| / # / samples previously
| / / *written*) vs. real
| / # / time.
| /<------->/ audio latency
| /# v/
| / / bottom line is "empty buffer"
| / # / condition = DAC output time =
| / /
| / # <-- rapid callbacks as buffer is filled
| / /
0 +...+---------#---------------------------------------------------->
0 ^ | | real time
| | first callback time
| mSystemMinusAudioTime
|
Probably the actual real times shown in this graph are very large
in practice (> 350,000 sec.), so the X "origin" might be when
the computer was booted or 1970 or something.
@endverbatim
To estimate the true DAC time (needed to synchronize MIDI), we need
a mapping from track time to DAC time. The estimate is the theoretical
time of the full buffer (top diagonal line) + audio latency. To
estimate the top diagonal line, we "draw" the line to be at least
as high as any sample time corresponding to a callback (#), and we
slowly lower the line in case the sample clock is slow or the system
clock is fast, preventing the estimated line from drifting too far
from the actual callback observations. The line is occasionally
"bumped" up by new callback observations, but continuously
"lowered" at a very low rate. All adjustment is accomplished
by changing mSystemMinusAudioTime, shown here as the X-intercept.\n
theoreticalFullBufferTime = realTime - mSystemMinusAudioTime\n
To estimate audio latency, notice that the first callback happens on
an empty buffer, but the buffer soon fills up. This will cause a rapid
re-estimation of mSystemMinusAudioTime. (The first estimate of
mSystemMinusAudioTime will simply be the real time of the first
callback time.) By watching these changes, which happen within ms of
starting, we can estimate the buffer size and thus audio latency.
So, to map from track time to real time, we compute:\n
DACoutputTime = trackTime + mSystemMinusAudioTime\n
There are some additional details to avoid counting samples while
paused or while waiting for initialization, MIDI latency, etc.
Also, in the code, track time is measured with respect to the track
origin, so there's an extra term to add (mT0) if you start somewhere
in the middle of the track.
Finally, when a callback occurs, you might expect there is room in
the output buffer for the requested frames, so maybe the "full buffer"
sample time should be based not on the first sample of the callback, but
the last sample time + 1 sample. I suspect, at least on Linux, that the
callback occurs as soon as the last callback completes, so the buffer is
really full, and the callback thread is going to block waiting for space
in the output buffer.
\par Midi Time
MIDI is not warped according to the speed control. This might be
something that should be changed. (Editorial note: Wouldn't it
make more sense to display audio at the correct time and allow
users to stretch audio the way they can stretch MIDI?) For now,
MIDI plays at 1 second per second, so it requires an unwarped clock.
In fact, MIDI time synchronization requires a millisecond clock that
does not pause. Note that mTime will stop progress when the Pause
button is pressed, even though audio samples (zeros) continue to
be output.
\par
Therefore, we define the following interface for MIDI timing:
\li \c AudioTime() is the time based on all samples written so far, including zeros output during pauses. AudioTime() is based on the start location mT0, not zero.
\li \c PauseTime() is the amount of time spent paused, based on a count of zero-padding samples output.
\li \c MidiTime() is an estimate in milliseconds of the current audio output time + 1s. In other words, what sneedacity track time corresponds to the audio (plus pause insertions) at the DAC output?
\par AudioTime() and PauseTime() computation
AudioTime() is simply mT0 + mNumFrames / mRate.
mNumFrames is incremented in each audio callback. Similarly, PauseTime()
is mNumPauseFrames / mRate. mNumPauseFrames is also incremented in
each audio callback when a pause is in effect or audio output is ready to start.
\par MidiTime() computation
MidiTime() is computed based on information from PortAudio's callback,
which estimates the system time at which the current audio buffer will
be output. Consider the (unimplemented) function RealToTrack() that
maps real audio write time to track time. If writeTime is the system
time for the first sample of the current output buffer, and
if we are in the callback, so AudioTime() also refers to the first sample
of the buffer, then \n
RealToTrack(writeTime) = AudioTime() - PauseTime()\n
We want to know RealToTrack of the current time (when we are not in the
callback, so we use this approximation for small d: \n
RealToTrack(t + d) = RealToTrack(t) + d, or \n
Letting t = writeTime and d = (systemTime - writeTime), we can
substitute to get:\n
RealToTrack(systemTime)
= RealToTrack(writeTime) + systemTime - writeTime\n
= AudioTime() - PauseTime() + (systemTime - writeTime) \n
MidiTime() should include pause time, so that it increases smoothly,
and audioLatency so that MidiTime() corresponds to the time of audio
output rather than audio write times. Also MidiTime() is offset by 1
second to avoid negative time at startup, so add 1: \n
MidiTime(systemTime) in seconds\n
= RealToTrack(systemTime) + PauseTime() - audioLatency + 1 \n
= AudioTime() + (systemTime - writeTime) - audioLatency + 1 \n
(Note that audioLatency is called mAudioOutLatency in the code.)
When we schedule a MIDI event with track time TT, we need
to map TT to a PortMidi timestamp. The PortMidi timestamp is exactly
MidiTime(systemTime) in ms units, and \n
MidiTime(x) = RealToTrack(x) + PauseTime() + 1, so \n
timestamp = TT + PauseTime() + 1 - midiLatency \n
Note 1: The timestamp is incremented by the PortMidi stream latency
(midiLatency) so we subtract midiLatency here for the timestamp
passed to PortMidi. \n
Note 2: Here, we're setting x to the time at which RealToTrack(x) = TT,
so then MidiTime(x) is the desired timestamp. To be completely
correct, we should assume that MidiTime(x + d) = MidiTime(x) + d,
and consider that we compute MidiTime(systemTime) based on the
*current* system time, but we really want the MidiTime(x) for some
future time corresponding when MidiTime(x) = TT.)
\par
Also, we should assume PortMidi was opened with mMidiLatency, and that
MIDI messages become sound with a delay of mSynthLatency. Therefore,
the final timestamp calculation is: \n
timestamp = TT + PauseTime() + 1 - (mMidiLatency + mSynthLatency) \n
(All units here are seconds; some conversion is needed in the code.)
\par
The difference AudioTime() - PauseTime() is the time "cursor" for
MIDI. When the speed control is used, MIDI and Audio will become
unsynchronized. In particular, MIDI will not be synchronized with
the visual cursor, which moves with scaled time reported in mTime.
\par Timing in Linux
It seems we cannot get much info from Linux. We can read the time
when we get a callback, and we get a variable frame count (it changes
from one callback to the next). Returning to the RealToTrack()
equations above: \n
RealToTrack(outputTime) = AudioTime() - PauseTime() - bufferDuration \n
where outputTime should be PortAudio's estimate for the most recent output
buffer, but at least on my Dell Latitude E7450, PortAudio is getting zero
from ALSA, so we need to find a proxy for this.
\par Estimating outputTime (Plan A, assuming double-buffered, fixed-size buffers, please skip to Plan B)
One can expect the audio callback to happen as soon as there is room in
the output for another block of samples, so we could just measure system
time at the top of the callback. Then we could add the maximum delay
buffered in the system. E.g. if there is simple double buffering and the
callback is computing one of the buffers, the callback happens just as
one of the buffers empties, meaning the other buffer is full, so we have
exactly one buffer delay before the next computed sample is output.
If computation falls behind a bit, the callback will be later, so the
delay to play the next computed sample will be less. I think a reasonable
way to estimate the actual output time is to assume that the computer is
mostly keeping up and that *most* callbacks will occur immediately when
there is space. Note that the most likely reason for the high-priority
audio thread to fall behind is the callback itself, but the start of the
callback should be pretty consistently keeping up.
Also, we do not have to have a perfect estimate of the time. Suppose we
estimate a linear mapping from sample count to system time by saying
that the sample count maps to the system time at the most recent callback,
and set the slope to 1% slower than real time (as if the sample clock is
slow). Now, at each callback, if the callback seems to occur earlier than
expected, we can adjust the mapping to be earlier. The earlier the
callback, the more accurate it must be. On the other hand, if the callback
is later than predicted, it must be a delayed callback (or else the
sample clock is more than 1% slow, which is really a hardware problem.)
How bad can this be? Assuming callbacks every 30ms (this seems to be what
I'm observing in a default setup), you'll be a maximum of 1ms off even if
2 out of 3 callbacks are late. This is pretty reasonable given that
PortMIDI clock precision is 1ms. If buffers are larger and callback timing
is more erratic, errors will be larger, but even a few ms error is
probably OK.
\par Estimating outputTime (Plan B, variable framesPerBuffer in callback, please skip to Plan C)
ALSA is complicated because we get varying values of
framesPerBuffer from callback to callback. Assume you get more frames
when the callback is later (because there is more accumulated input to
deliver and more more accumulated room in the output buffers). So take
the current time and subtract the duration of the frame count in the
current callback. This should be a time position that is relatively
jitter free (because we estimated the lateness by frame count and
subtracted that out). This time position intuitively represents the
current ADC time, or if no input, the time of the tail of the output
buffer. If we wanted DAC time, we'd have to add the total output
buffer duration, which should be reported by PortAudio. (If PortAudio
is wrong, we'll be systematically shifted in time by the error.)
Since there is still bound to be jitter, we can smooth these estimates.
First, we will assume a linear mapping from system time to audio time
with slope = 1, so really it's just the offset we need, which is going
to be a double that we can read/write atomically without locks or
anything fancy. (Maybe it should be "volatile".)
To improve the estimate, we get a new offset every callback, so we can
create a "smooth" offset by using a simple regression model (also
this could be seen as a first order filter). The following formula
updates smooth_offset with a new offset estimate in the callback:
smooth_offset = smooth_offset * 0.9 + new_offset_estimate * 0.1
Since this is smooth, we'll have to be careful to give it a good initial
value to avoid a long convergence.
\par Estimating outputTime (Plan C)
ALSA is complicated because we get varying values of
framesPerBuffer from callback to callback. It seems there is a lot
of variation in callback times and buffer space. One solution would
be to go to fixed size double buffer, but Sneedacity seems to work
better as is, so Plan C is to rely on one invariant which is that
the output buffer cannot overflow, so there's a limit to how far
ahead of the DAC time we can be writing samples into the
buffer. Therefore, we'll assume that the audio clock runs slow by
about 0.2% and we'll assume we're computing at that rate. If the
actual output position is ever ahead of the computed position, we'll
increase the computed position to the actual position. Thus whenever
the buffer is less than near full, we'll stay ahead of DAC time,
falling back at a rate of about 0.2% until eventually there's
another near-full buffer callback that will push the time back ahead.
\par Interaction between MIDI, Audio, and Pause
When Pause is used, PauseTime() will increase at the same rate as
AudioTime(), and no more events will be output. Because of the
time advance of mAudioOutputLatency + MIDI_SLEEP + latency and the
fact that
AudioTime() advances stepwise by mAudioBufferDuration, some extra MIDI
might be output, but the same is true of audio: something like
mAudioOutputLatency audio samples will be in the output buffer
(with up to mAudioBufferDuration additional samples, depending on
when the Pause takes effect). When playback is resumed, there will
be a slight delay corresponding to the extra data previously sent.
Again, the same is true of audio. Audio and MIDI will not pause and
resume at exactly the same times, but their pause and resume times
will be within the low tens of milliseconds, and the streams will
be synchronized in any case. I.e. if audio pauses 10ms earlier than
MIDI, it will resume 10ms earlier as well.
\par PortMidi Latency Parameter
PortMidi has a "latency" parameter that is added to all timestamps.
This value must be greater than zero to enable timestamp-based timing,
but serves no other function, so we will set it to 1. All timestamps
must then be adjusted down by 1 before messages are sent. This
adjustment is on top of all the calculations described above. It just
seem too complicated to describe everything in complete detail in one
place.
\par Midi with a time track
When a variable-speed time track is present, MIDI events are output
with the times used by the time track (rather than the raw times).
This ensures MIDI is synchronized with audio.
\par Midi While Recording Only or Without Audio Playback
To reduce duplicate code and to ensure recording is synchronised with
MIDI, a portaudio stream will always be used, even when there is no
actual audio output. For recording, this ensures that the recorded
audio will by synchronized with the MIDI (otherwise, it gets out-of-
sync if played back with correct timing).
\par NoteTrack PlayLooped
When mPlayLooped is true, output is supposed to loop from mT0 to mT1.
For NoteTracks, we interpret this to mean that any note-on or control
change in the range mT0 <= t < mT1 is sent (notes that start before
mT0 are not played even if they extend beyond mT0). Then, all notes
are turned off. Events in the range mT0 <= t < mT1 are then repeated,
offset by (mT1 - mT0), etc. We do NOT go back to the beginning and
play all control changes (update events) up to mT0, nor do we "undo"
any state changes between mT0 and mT1.
\par NoteTrack PlayLooped Implementation
The mIterator object (an Alg_iterator) returns NULL when there are
no more events scheduled before mT1. At mT1, we want to output
all notes off messages, but the FillMidiBuffers() loop will exit
if mNextEvent is NULL, so we create a "fake" mNextEvent for this
special "event" of sending all notes off. After that, we destroy
the iterator and use PrepareMidiIterator() to set up a NEW one.
At each iteration, time must advance by (mT1 - mT0), so the
accumulated complete loop time (in "unwarped," track time) is computed
by MidiLoopOffset().
\todo run through all functions called from audio and portaudio threads
to verify they are thread-safe. Note that synchronization of the style:
"A sets flag to signal B, B clears flag to acknowledge completion"
is not thread safe in a general multiple-CPU context. For example,
B can write to a buffer and set a completion flag. The flag write can
occur before the buffer write due to out-of-order execution. Then A
can see the flag and read the buffer before buffer writes complete.
*//****************************************************************//**
\class AudioThread
\brief Defined different on Mac and other platforms (on Mac it does not
use wxWidgets wxThread), this class sits in a thread loop reading and
writing audio.
*//****************************************************************//**
\class AudioIOListener
\brief Monitors record play start/stop and new sample blocks. Has
callbacks for these events.
*//****************************************************************//**
\class AudioIOStartStreamOptions
\brief struct holding stream options, including a pointer to the
time warp info and AudioIOListener and whether the playback is looped.
*//*******************************************************************/
#include "AudioIO.h"
#include "AudioIOListener.h"
#include "float_cast.h"
#include "DeviceManager.h"
#include <cfloat>
#include <math.h>
#include <stdlib.h>
#include <algorithm>
#ifdef __WXMSW__
#include <malloc.h>
#endif
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif
#include "portaudio.h"
#if USE_PORTMIXER
#include "portmixer.h"
#endif
#include <wx/app.h>
#include <wx/frame.h>
#include <wx/wxcrtvararg.h>
#include <wx/log.h>
#include <wx/textctrl.h>
#include <wx/timer.h>
#include <wx/intl.h>
#include <wx/debug.h>
#if defined(__WXMAC__) || defined(__WXMSW__)
#include <wx/power.h>
#endif
#include "Mix.h"
#include "Resample.h"
#include "RingBuffer.h"
#include "prefs/GUISettings.h"
#include "Prefs.h"
#include "Project.h"
#include "DBConnection.h"
#include "ProjectFileIO.h"
#include "WaveTrack.h"
#include "effects/RealtimeEffectManager.h"
#include "prefs/QualitySettings.h"
#include "prefs/RecordingPrefs.h"
#include "widgets/MeterPanelBase.h"
#include "widgets/SneedacityMessageBox.h"
#include "widgets/ErrorDialog.h"
#ifdef EXPERIMENTAL_MIDI_OUT
#include "../lib-src/portmidi/pm_common/portmidi.h"
#include "../lib-src/portmidi/porttime/porttime.h"
#include "../lib-src/header-substitutes/allegro.h"
#define MIDI_SLEEP 10 /* milliseconds */
// how long do we think the thread that fills MIDI buffers,
// if it is separate from the portaudio thread,
// might be delayed due to other threads?
#ifdef USE_MIDI_THREAD
#define THREAD_LATENCY 10 /* milliseconds */
#else
#define THREAD_LATENCY 0 /* milliseconds */
#endif
#define ROUND(x) (int) ((x)+0.5)
//#include <string.h>
// #include "../lib-src/portmidi/pm_common/portmidi.h"
#include "../lib-src/portaudio-v19/src/common/pa_util.h"
#include "NoteTrack.h"
#endif
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
#define LOWER_BOUND 0.0
#define UPPER_BOUND 1.0
#endif
using std::max;
using std::min;
AudioIO *AudioIO::Get()
{
return static_cast< AudioIO* >( AudioIOBase::Get() );
}
wxDEFINE_EVENT(EVT_AUDIOIO_PLAYBACK, wxCommandEvent);
wxDEFINE_EVENT(EVT_AUDIOIO_CAPTURE, wxCommandEvent);
wxDEFINE_EVENT(EVT_AUDIOIO_MONITOR, wxCommandEvent);
// static
int AudioIoCallback::mNextStreamToken = 0;
double AudioIoCallback::mCachedBestRateOut;
bool AudioIoCallback::mCachedBestRatePlaying;
bool AudioIoCallback::mCachedBestRateCapturing;
enum {
// This is the least positive latency we can
// specify to Pm_OpenOutput, 1 ms, which prevents immediate
// scheduling of events:
MIDI_MINIMAL_LATENCY_MS = 1
};
constexpr size_t TimeQueueGrainSize = 2000;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
#ifdef __WXGTK__
// Might #define this for a useful thing on Linux
#undef REALTIME_ALSA_THREAD
#else
// never on the other operating systems
#undef REALTIME_ALSA_THREAD
#endif
#ifdef REALTIME_ALSA_THREAD
#include "pa_linux_alsa.h"
#endif
struct AudioIoCallback::ScrubState : NonInterferingBase
{
ScrubState(double t0,
double rate,
const ScrubbingOptions &options)
: mRate(rate)
, mStartTime( t0 )
{
const double t1 = options.bySpeed ? options.initSpeed : t0;
Update( t1, options );
}
void Update(double end, const ScrubbingOptions &options)
{
// Called by another thread
mMessage.Write({ end, options });
}
void Get(sampleCount &startSample, sampleCount &endSample,
sampleCount inDuration, sampleCount &duration)
{
// Called by the thread that calls AudioIO::FillBuffers
startSample = endSample = duration = -1LL;
sampleCount s0Init;
Message message( mMessage.Read() );
if ( !mStarted ) {
s0Init = llrint( mRate *
std::max( message.options.minTime,
std::min( message.options.maxTime, mStartTime ) ) );
// Make some initial silence. This is not needed in the case of
// keyboard scrubbing or play-at-speed, because the initial speed
// is known when this function is called the first time.
if ( !(message.options.isKeyboardScrubbing ||
message.options.isPlayingAtSpeed) ) {
mData.mS0 = mData.mS1 = s0Init;
mData.mGoal = -1;
mData.mDuration = duration = inDuration;
mData.mSilence = 0;
}
}
if (mStarted || message.options.isKeyboardScrubbing ||
message.options.isPlayingAtSpeed) {
Data newData;
inDuration += mAccumulatedSeekDuration;
// If already started, use the previous end as NEW start.
const auto s0 = mStarted ? mData.mS1 : s0Init;
const sampleCount s1 ( message.options.bySpeed
? s0.as_double() +
lrint(inDuration.as_double() * message.end) // end is a speed
: lrint(message.end * mRate) // end is a time
);
auto success =
newData.Init(mData, s0, s1, inDuration, message.options, mRate);
if (success)
mAccumulatedSeekDuration = 0;
else {
mAccumulatedSeekDuration += inDuration;
return;
}
mData = newData;
};
mStarted = true;
Data &entry = mData;
if ( mStopped.load( std::memory_order_relaxed ) ) {
// We got the shut-down signal, or we discarded all the work.
// Output the -1 values.
}
else if (entry.mDuration > 0) {
// First use of the entry
startSample = entry.mS0;
endSample = entry.mS1;
duration = entry.mDuration;
entry.mDuration = 0;
}
else if (entry.mSilence > 0) {
// Second use of the entry
startSample = endSample = entry.mS1;
duration = entry.mSilence;
entry.mSilence = 0;
}
}
void Stop()
{
mStopped.store( true, std::memory_order_relaxed );
}
#if 0
// Needed only for the DRAG_SCRUB experiment
// Should make mS1 atomic then?
double LastTrackTime() const
{
// Needed by the main thread sometimes
return mData.mS1.as_double() / mRate;
}
#endif
~ScrubState() {}
private:
struct Data
{
Data()
: mS0(0)
, mS1(0)
, mGoal(0)
, mDuration(0)
, mSilence(0)
{}
bool Init(Data &rPrevious, sampleCount s0, sampleCount s1,
sampleCount duration,
const ScrubbingOptions &options, double rate)
{
auto previous = &rPrevious;
auto origDuration = duration;
mSilence = 0;
const bool &adjustStart = options.adjustStart;
wxASSERT(duration > 0);
double speed =
(std::abs((s1 - s0).as_long_long())) / duration.as_double();
bool adjustedSpeed = false;
auto minSpeed = std::min(options.minSpeed, options.maxSpeed);
wxASSERT(minSpeed == options.minSpeed);
// May change the requested speed and duration
if (!adjustStart && speed > options.maxSpeed)
{
// Reduce speed to the maximum selected in the user interface.
speed = options.maxSpeed;
mGoal = s1;
adjustedSpeed = true;
}
else if (!adjustStart &&
previous->mGoal >= 0 &&
previous->mGoal == s1)
{
// In case the mouse has not moved, and playback
// is catching up to the mouse at maximum speed,
// continue at no less than maximum. (Without this
// the final catch-up can make a slow scrub interval
// that drops the pitch and sounds wrong.)
minSpeed = options.maxSpeed;
mGoal = s1;
adjustedSpeed = true;
}
else
mGoal = -1;
if (speed < minSpeed) {
if (s0 != s1 && adjustStart)
// Do not trim the duration.
;
else
// Trim the duration.
duration =
std::max(0L, lrint(speed * duration.as_double() / minSpeed));
speed = minSpeed;
adjustedSpeed = true;
}
if (speed < ScrubbingOptions::MinAllowedScrubSpeed()) {
// Mixers were set up to go only so slowly, not slower.
// This will put a request for some silence in the work queue.
adjustedSpeed = true;
speed = 0.0;
}
// May change s1 or s0 to match speed change or stay in bounds of the project
if (adjustedSpeed && !adjustStart)
{
// adjust s1
const sampleCount diff = lrint(speed * duration.as_double());
if (s0 < s1)
s1 = s0 + diff;
else
s1 = s0 - diff;
}
bool silent = false;
// Adjust s1 (again), and duration, if s1 is out of bounds,
// or abandon if a stutter is too short.
// (Assume s0 is in bounds, because it equals the last scrub's s1 which was checked.)
if (s1 != s0)
{
// When playback follows a fast mouse movement by "stuttering"
// at maximum playback, don't make stutters too short to be useful.
if (options.adjustStart &&
duration < llrint( options.minStutterTime * rate ) )
return false;
sampleCount minSample { llrint(options.minTime * rate) };
sampleCount maxSample { llrint(options.maxTime * rate) };
auto newDuration = duration;
const auto newS1 = std::max(minSample, std::min(maxSample, s1));
if(s1 != newS1)
newDuration = std::max( sampleCount{ 0 },
sampleCount(
duration.as_double() * (newS1 - s0).as_double() /
(s1 - s0).as_double()
)
);
if (newDuration == 0) {
// A silent scrub with s0 == s1
silent = true;
s1 = s0;
}
else if (s1 != newS1) {
// Shorten
duration = newDuration;
s1 = newS1;
}
}
if (adjustStart && !silent)
{
// Limit diff because this is seeking.
const sampleCount diff =
lrint(std::min(options.maxSpeed, speed) * duration.as_double());
if (s0 < s1)
s0 = s1 - diff;
else
s0 = s1 + diff;
}
mS0 = s0;
mS1 = s1;
mDuration = duration;
if (duration < origDuration)
mSilence = origDuration - duration;
return true;
}
sampleCount mS0;
sampleCount mS1;
sampleCount mGoal;
sampleCount mDuration;
sampleCount mSilence;
};
double mStartTime;
bool mStarted{ false };
std::atomic<bool> mStopped { false };
Data mData;
const double mRate;
struct Message {
Message() = default;
Message(const Message&) = default;
double end;
ScrubbingOptions options;
};
MessageBuffer<Message> mMessage;
sampleCount mAccumulatedSeekDuration{};
};
#endif
#ifdef EXPERIMENTAL_MIDI_OUT
// return the system time as a double
static double streamStartTime = 0; // bias system time to small number
static double SystemTime(bool usingAlsa)
{
#ifdef __WXGTK__
if (usingAlsa) {
struct timespec now;
// CLOCK_MONOTONIC_RAW is unaffected by NTP or adj-time
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
//return now.tv_sec + now.tv_nsec * 0.000000001;
return (now.tv_sec + now.tv_nsec * 0.000000001) - streamStartTime;
}
#else
static_cast<void>(usingAlsa);//compiler food.
#endif
return PaUtil_GetTime() - streamStartTime;
}
#endif
int sneedacityAudioCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags, void *userData );
//////////////////////////////////////////////////////////////////////
//
// class AudioThread - declaration and glue code
//
//////////////////////////////////////////////////////////////////////
#include <thread>
#ifdef __WXMAC__
// On Mac OS X, it's better not to use the wxThread class.
// We use our own implementation based on pthreads instead.
#include <pthread.h>
#include <time.h>
class AudioThread {
public:
typedef int ExitCode;
AudioThread() { mDestroy = false; mThread = NULL; }
virtual ExitCode Entry();
void Create() {}
void Delete() {
mDestroy = true;
pthread_join(mThread, NULL);
}
bool TestDestroy() { return mDestroy; }
void Sleep(int ms) {
struct timespec spec;
spec.tv_sec = 0;
spec.tv_nsec = ms * 1000 * 1000;
nanosleep(&spec, NULL);
}
static void *callback(void *p) {
AudioThread *th = (AudioThread *)p;
return reinterpret_cast<void *>( th->Entry() );
}
void Run() {
pthread_create(&mThread, NULL, callback, this);
}
private:
bool mDestroy;
pthread_t mThread;
};
#else
// The normal wxThread-derived AudioThread class for all other
// platforms:
class AudioThread /* not final */ : public wxThread {
public:
AudioThread():wxThread(wxTHREAD_JOINABLE) {}
ExitCode Entry() override;
};
#endif
#ifdef EXPERIMENTAL_MIDI_OUT
class MidiThread final : public AudioThread {
public:
ExitCode Entry() override;
};
#endif
//////////////////////////////////////////////////////////////////////
//
// UI Thread Context
//
//////////////////////////////////////////////////////////////////////
void AudioIO::Init()
{
ugAudioIO.reset(safenew AudioIO());
Get()->mThread->Run();
#ifdef EXPERIMENTAL_MIDI_OUT
#ifdef USE_MIDI_THREAD
Get()->mMidiThread->Run();
#endif
#endif
// Make sure device prefs are initialized
if (gPrefs->Read(wxT("AudioIO/RecordingDevice"), wxT("")).empty()) {
int i = getRecordDevIndex();
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
if (info) {
AudioIORecordingDevice.Write(DeviceName(info));
AudioIOHost.Write(HostName(info));
}
}
if (gPrefs->Read(wxT("AudioIO/PlaybackDevice"), wxT("")).empty()) {
int i = getPlayDevIndex();
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
if (info) {
AudioIOPlaybackDevice.Write(DeviceName(info));
AudioIOHost.Write(HostName(info));
}
}
gPrefs->Flush();
}
void AudioIO::Deinit()
{
ugAudioIO.reset();
}
bool AudioIO::ValidateDeviceNames(const wxString &play, const wxString &rec)
{
const PaDeviceInfo *pInfo = Pa_GetDeviceInfo(getPlayDevIndex(play));
const PaDeviceInfo *rInfo = Pa_GetDeviceInfo(getRecordDevIndex(rec));
// Valid iff both defined and the same api.
return pInfo != nullptr && rInfo != nullptr && pInfo->hostApi == rInfo->hostApi;
}
AudioIO::AudioIO()
{
if (!std::atomic<double>{}.is_lock_free()) {
// If this check fails, then the atomic<double> members in AudioIO.h
// might be changed to atomic<float> to be more efficient with some
// loss of precision. That could be conditionally compiled depending
// on the platform.
wxASSERT(false);
}
// This ASSERT because of casting in the callback
// functions where we cast a tempFloats buffer to a (short*) buffer.
// We have to ASSERT in the GUI thread, if we are to see it properly.
wxASSERT( sizeof( short ) <= sizeof( float ));
mAudioThreadShouldCallFillBuffersOnce = false;
mAudioThreadFillBuffersLoopRunning = false;
mAudioThreadFillBuffersLoopActive = false;
mPortStreamV19 = NULL;
#ifdef EXPERIMENTAL_MIDI_OUT
mMidiStream = NULL;
mMidiThreadFillBuffersLoopRunning = false;
mMidiThreadFillBuffersLoopActive = false;
mMidiStreamActive = false;
mSendMidiState = false;
mIterator = NULL;
mNumFrames = 0;
mNumPauseFrames = 0;
#endif
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
mAILAActive = false;
#endif
mStreamToken = 0;
mLastPaError = paNoError;
mLastRecordingOffset = 0.0;
mNumCaptureChannels = 0;
mPaused = false;
mSilenceLevel = 0.0;