Split AudioIO into two classes

AudioIO.cpp has nearly 6000 lines and needs to be broken up into separate aspects.
The main point of the new split is to better separate the time critical callback code (that runs in the portaudio thread) from the code that interacts with the disk (that runs in the Audio Thread).
This commit is contained in:
James Crook 2018-10-13 20:50:08 +01:00
parent 26086a4142
commit ffa67d2b1e
2 changed files with 451 additions and 409 deletions

View File

@ -17,6 +17,15 @@
********************************************************************//**
\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.
@ -487,14 +496,14 @@ wxDEFINE_EVENT(EVT_AUDIOIO_CAPTURE, wxCommandEvent);
wxDEFINE_EVENT(EVT_AUDIOIO_MONITOR, wxCommandEvent);
// static
int AudioIO::mNextStreamToken = 0;
int AudioIO::mCachedPlaybackIndex = -1;
std::vector<long> AudioIO::mCachedPlaybackRates;
int AudioIO::mCachedCaptureIndex = -1;
std::vector<long> AudioIO::mCachedCaptureRates;
std::vector<long> AudioIO::mCachedSampleRates;
double AudioIO::mCachedBestRateIn = 0.0;
double AudioIO::mCachedBestRateOut;
int AudioIoCallback::mNextStreamToken = 0;
int AudioIoCallback::mCachedPlaybackIndex = -1;
std::vector<long> AudioIoCallback::mCachedPlaybackRates;
int AudioIoCallback::mCachedCaptureIndex = -1;
std::vector<long> AudioIoCallback::mCachedCaptureRates;
std::vector<long> AudioIoCallback::mCachedSampleRates;
double AudioIoCallback::mCachedBestRateIn = 0.0;
double AudioIoCallback::mCachedBestRateOut;
enum {
// This is the least positive latency we can
@ -522,7 +531,7 @@ constexpr size_t TimeQueueGrainSize = 2000;
#endif
struct AudioIO::ScrubState
struct AudioIoCallback::ScrubState
{
ScrubState(double t0,
double rate,
@ -2712,7 +2721,7 @@ void AudioIO::SetPaused(bool state)
mPaused = state;
}
bool AudioIO::IsPaused() const
bool AudioIoCallback::IsPaused() const
{
return mPaused;
}
@ -3253,7 +3262,7 @@ size_t AudioIO::GetCommonlyFreePlayback()
return commonlyAvail - std::min(size_t(10), commonlyAvail);
}
size_t AudioIO::GetCommonlyReadyPlayback()
size_t AudioIoCallback::GetCommonlyReadyPlayback()
{
if (mPlaybackTracks.empty())
return 0;
@ -4709,7 +4718,7 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
}
void AudioIO::ComputeMidiTimings(
void AudioIoCallback::ComputeMidiTimings(
const PaStreamCallbackTimeInfo *timeInfo,
unsigned long framesPerBuffer
)
@ -4792,9 +4801,6 @@ void AudioIO::ComputeMidiTimings(
#endif
}
void AudioIO::ComputeAudibilities(){
}
// Stop recording if 'silence' is detected
// Start recording if sound detected.
//
@ -4802,7 +4808,7 @@ void AudioIO::ComputeAudibilities(){
// to run in the main GUI thread after the next event loop iteration.
// That's important, because Pause() updates GUI, such as status bar,
// and that should NOT happen in this audio non-gui thread.
void AudioIO::CheckSoundActivatedRecordingLevel( )
void AudioIoCallback::CheckSoundActivatedRecordingLevel( )
{
if(mPauseRec && mInputMeter) {
bool bShouldBePaused = mInputMeter->GetMaxPeak() < mSilenceLevel;
@ -4818,7 +4824,7 @@ void AudioIO::CheckSoundActivatedRecordingLevel( )
//
// Mix and copy to PortAudio's output buffer
//
bool AudioIO::FillOutputBuffers(
bool AudioIoCallback::FillOutputBuffers(
const void *inputBuffer,
void *outputBuffer,
unsigned long framesPerBuffer,
@ -5065,7 +5071,7 @@ bool AudioIO::FillOutputBuffers(
//
// Copy from PortAudio to our input buffers.
//
bool AudioIO::FillInputBuffers(
bool AudioIoCallback::FillInputBuffers(
const void *inputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackFlags statusFlags,
@ -5213,7 +5219,7 @@ bool AudioIO::FillInputBuffers(
// return true, IFF we have fully handled the callback.
// No tracks to play, but we should clear the output, and
// possibly do software playthrough...
bool AudioIO::OnlyDoPlaythrough(
bool AudioIoCallback::OnlyDoPlaythrough(
const void *inputBuffer,
void *outputBuffer,
unsigned long framesPerBuffer,
@ -5247,7 +5253,7 @@ bool AudioIO::OnlyDoPlaythrough(
}
// Also computes rms
void AudioIO::SendVuInputMeterData(
void AudioIoCallback::SendVuInputMeterData(
float *tempFloats,
const void *inputBuffer,
unsigned long framesPerBuffer
@ -5288,7 +5294,7 @@ void AudioIO::SendVuInputMeterData(
} // end recording VU meter update
}
void AudioIO::SendVuOutputMeterData(
void AudioIoCallback::SendVuOutputMeterData(
float *outputMeterFloats,
unsigned long framesPerBuffer)
{
@ -5331,7 +5337,7 @@ void AudioIO::SendVuOutputMeterData(
}
unsigned AudioIO::CountSoloingTracks(){
unsigned AudioIoCallback::CountSoloingTracks(){
const auto numPlaybackTracks = mPlaybackTracks.size();
// MOVE_TO: CountSoloedTracks() function
@ -5354,7 +5360,7 @@ unsigned AudioIO::CountSoloingTracks(){
// true IFF the track should be silent.
// The track may not yet be silent, since it may still be
// fading out.
bool AudioIO::TrackShouldBeSilent( const WaveTrack &wt )
bool AudioIoCallback::TrackShouldBeSilent( const WaveTrack &wt )
{
return mPaused || (!wt.GetSolo() && (
// Cut if somebody else is soloing
@ -5365,7 +5371,7 @@ bool AudioIO::TrackShouldBeSilent( const WaveTrack &wt )
}
// This is about micro-fades.
bool AudioIO::TrackHasBeenFadedOut( const WaveTrack &wt )
bool AudioIoCallback::TrackHasBeenFadedOut( const WaveTrack &wt )
{
const auto channel = wt.GetChannelIgnoringPan();
if ((channel == Track::LeftChannel || channel == Track::MonoChannel) &&
@ -5377,7 +5383,7 @@ bool AudioIO::TrackHasBeenFadedOut( const WaveTrack &wt )
return true;
}
bool AudioIO::AllTracksAlreadySilent()
bool AudioIoCallback::AllTracksAlreadySilent()
{
const bool dropAllQuickly = std::all_of(
mPlaybackTracks.begin(), mPlaybackTracks.end(),
@ -5399,7 +5405,7 @@ bool AudioIO::AllTracksAlreadySilent()
// this callback, and so the next time round 'AllTracksAlreadySilent' will be true.
// All this logic will be easier when we shift over to working with gains that
// go to zero, and not flags.
bool AudioIO::QuickSilentPlayback(const void *inputBuffer, void *outputBuffer,
bool AudioIoCallback::QuickSilentPlayback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer)
{
const auto numPlaybackTracks = mPlaybackTracks.size();
@ -5424,9 +5430,17 @@ bool AudioIO::QuickSilentPlayback(const void *inputBuffer, void *outputBuffer,
return false;
}
AudioIoCallback::AudioIoCallback()
{
}
int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
AudioIoCallback::~AudioIoCallback()
{
}
int AudioIoCallback::AudioCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void * WXUNUSED(userData) )
@ -5519,7 +5533,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
return mCallbackReturn;
}
PaStreamCallbackResult AudioIO::CallbackDoSeek()
PaStreamCallbackResult AudioIoCallback::CallbackDoSeek()
{
const int token = mStreamToken;
wxMutexLocker locker(mSuspendAudioThread);
@ -5570,7 +5584,7 @@ PaStreamCallbackResult AudioIO::CallbackDoSeek()
return paContinue;
}
void AudioIO::CallbackCheckCompletion(
void AudioIoCallback::CallbackCheckCompletion(
int &callbackReturn, unsigned long len)
{
if (mPaused)

View File

@ -301,12 +301,12 @@ void MessageBuffer<Data>::Write( Data &&data )
mSlots[idx].mBusy.store( false, std::memory_order_release );
}
class AUDACITY_DLL_API AudioIO final {
public:
AudioIO();
~AudioIO();
class AUDACITY_DLL_API AudioIoCallback {
public:
AudioIoCallback();
~AudioIoCallback();
public:
// This function executes in a thread spawned by the PortAudio library
int AudioCallback(
const void *inputBuffer, void *outputBuffer,
@ -321,7 +321,6 @@ class AUDACITY_DLL_API AudioIO final {
void CallbackCheckCompletion(
int &callbackReturn, unsigned long len);
private:
int mbHasSoloTracks;
int mCallbackReturn;
// Helpers to determine if tracks have already been faded out.
@ -334,7 +333,6 @@ private:
void ComputeMidiTimings(
const PaStreamCallbackTimeInfo *timeInfo,
unsigned long framesPerBuffer);
void ComputeAudibilities();
void CheckSoundActivatedRecordingLevel();
bool QuickSilentPlayback(
const void *inputBuffer,
@ -366,328 +364,17 @@ private:
void SendVuOutputMeterData(
float *outputMeterFloats,
unsigned long framesPerBuffer
);
);
public:
AudioIOListener* GetListener() { return mListener; }
void SetListener(AudioIOListener* listener);
/** \brief Start up Portaudio for capture and recording as needed for
* input monitoring and software playthrough only
*
* This uses the Default project sample format, current sample rate, and
* selected number of input channels to open the recording device and start
* reading input data. If software playthrough is enabled, it also opens
* the output device in stereo to play the data through */
void StartMonitoring(double sampleRate);
/** \brief Start recording or playing back audio
*
* Allocates buffers for recording and playback, gets the Audio thread to
* fill them, and sets the stream rolling.
* If successful, returns a token identifying this particular stream
* instance. For use with IsStreamActive() below */
int StartStream(const TransportTracks &tracks,
double t0, double t1,
const AudioIOStartStreamOptions &options);
/** \brief Stop recording, playback or input monitoring.
*
* Does quite a bit of housekeeping, including switching off monitoring,
* flushing recording buffers out to wave tracks, and applies latency
* correction to recorded tracks if necessary */
void StopStream();
/** \brief Move the playback / recording position of the current stream
* by the specified amount from where it is now */
void SeekStream(double seconds) { mSeek = seconds; }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
bool IsScrubbing() const { return IsBusy() && mScrubState != 0; }
/** \brief Notify scrubbing engine of desired position or speed.
* If options.adjustStart is true, then when mouse movement exceeds maximum
* scrub speed, adjust the beginning of the scrub interval rather than the
* end, so that the scrub skips or "stutters" to stay near the cursor.
*/
void UpdateScrub(double endTimeOrSpeed, const ScrubbingOptions &options);
void StopScrub();
/** \brief return the ending time of the last scrub interval.
*/
double GetLastScrubTime() const;
#endif
/** \brief Returns true if audio i/o is busy starting, stopping, playing,
* or recording.
*
* When this is false, it's safe to start playing or recording */
bool IsBusy() const;
/** \brief Returns true if the audio i/o is running at all, but not during
* cleanup
*
* Doesn't return true if the device has been closed but some disk i/o or
* cleanup is still going on. If you want to know if it's safe to start a
* NEW stream, use IsBusy() */
bool IsStreamActive() const;
bool IsStreamActive(int token) const;
wxLongLong GetLastPlaybackTime() const { return mLastPlaybackTimeMillis; }
AudacityProject *GetOwningProject() const { return mOwningProject; }
// Required by these functions...
#ifdef EXPERIMENTAL_MIDI_OUT
/** \brief Compute the current PortMidi timestamp time.
*
* This is used by PortMidi to synchronize midi time to audio samples
*/
PmTimestamp MidiTime();
// Note: audio code solves the problem of soloing/muting tracks by scanning
// all playback tracks on every call to the audio buffer fill routine.
// We do the same for Midi, but it seems wasteful for at least two
// threads to be frequently polling to update status. This could be
// eliminated (also with a reduction in code I think) by updating mHasSolo
// each time a solo button is activated or deactivated. For now, I'm
// going to do this polling in the FillMidiBuffer routine to localize
// changes for midi to the midi code, but I'm declaring the variable
// here so possibly in the future, Audio code can use it too. -RBD
private:
bool mHasSolo; // is any playback solo button pressed?
public:
bool SetHasSolo(bool hasSolo);
bool GetHasSolo() { return mHasSolo; }
double AudioTime() { return mPlaybackSchedule.mT0 + mNumFrames / mRate; }
#endif
/** \brief Returns true if the stream is active, or even if audio I/O is
* busy cleaning up its data or writing to disk.
*
* This is used by TrackPanel to determine when a track has been completely
* recorded, and it's safe to flush to disk. */
bool IsAudioTokenActive(int token) const;
/** \brief Returns true if we're monitoring input (but not recording or
* playing actual audio) */
bool IsMonitoring() const;
/** \brief Pause and un-pause playback and recording */
void SetPaused(bool state);
/** \brief Find out if playback / recording is currently paused */
bool IsPaused() const;
/* Mixer services are always available. If no stream is running, these
* methods use whatever device is specified by the preferences. If a
* stream *is* running, naturally they manipulate the mixer associated
* with that stream. If no mixer is available, output is emulated and
* input is stuck at 1.0f (a gain is applied to output samples).
*/
void SetMixer(int inputSource);
void SetMixer(int inputSource, float inputVolume,
float playbackVolume);
void GetMixer(int *inputSource, float *inputVolume,
float *playbackVolume);
/** @brief Find out if the input hardware level control is available
*
* Checks the mInputMixerWorks variable, which is set up in
* AudioIO::HandleDeviceChange(). External people care, because we want to
* disable the UI if it doesn't work.
*/
bool InputMixerWorks();
/** @brief Find out if the output level control is being emulated via software attenuation
*
* Checks the mEmulateMixerOutputVol variable, which is set up in
* AudioIO::HandleDeviceChange(). External classes care, because we want to
* modify the UI if it doesn't work.
*/
bool OutputMixerEmulated();
/** \brief Get the list of inputs to the current mixer device
*
* Returns an array of strings giving the names of the inputs to the
* soundcard mixer (driven by PortMixer) */
wxArrayString GetInputSourceNames();
/** \brief update state after changing what audio devices are selected
*
* Called when the devices stored in the preferences are changed to update
* the audio mixer capabilities
*
* \todo: Make this do a sample rate query and store the result in the
* AudioIO object to avoid doing it later? Would simplify the
* GetSupported*Rate functions considerably */
void HandleDeviceChange();
/** \brief Get a list of sample rates the output (playback) device
* supports.
*
* If no information about available sample rates can be fetched,
* an empty list is returned.
*
* You can explicitely give the index of the device. If you don't
* give it, the currently selected device from the preferences will be used.
*
* You may also specify a rate for which to check in addition to the
* standard rates.
*/
static std::vector<long> GetSupportedPlaybackRates(int DevIndex = -1,
double rate = 0.0);
/** \brief Get a list of sample rates the input (recording) device
* supports.
*
* If no information about available sample rates can be fetched,
* an empty list is returned.
*
* You can explicitely give the index of the device. If you don't
* give it, the currently selected device from the preferences will be used.
*
* You may also specify a rate for which to check in addition to the
* standard rates.
*/
static std::vector<long> GetSupportedCaptureRates(int devIndex = -1,
double rate = 0.0);
/** \brief Get a list of sample rates the current input/output device
* combination supports.
*
* Since there is no concept (yet) for different input/output
* sample rates, this currently returns only sample rates that are
* supported on both the output and input device. If no information
* about available sample rates can be fetched, it returns a default
* list.
* You can explicitely give the indexes of the playDevice/recDevice.
* If you don't give them, the selected devices from the preferences
* will be used.
* You may also specify a rate for which to check in addition to the
* standard rates.
*/
static std::vector<long> GetSupportedSampleRates(int playDevice = -1,
int recDevice = -1,
double rate = 0.0);
/** \brief Get a supported sample rate which can be used a an optimal
* default.
*
* Currently, this uses the first supported rate in the list
* [44100, 48000, highest sample rate]. Used in Project as a default value
* for project rates if one cannot be retrieved from the preferences.
* So all in all not that useful or important really
*/
static int GetOptimalSupportedSampleRate();
/** \brief During playback, the track time most recently played
*
* When playing looped, this will start from t0 again,
* too. So the returned time should be always between
* t0 and t1
*/
double GetStreamTime();
sampleFormat GetCaptureFormat() { return mCaptureFormat; }
unsigned GetNumPlaybackChannels() const { return mNumPlaybackChannels; }
unsigned GetNumCaptureChannels() const { return mNumCaptureChannels; }
// Meaning really capturing, not just pre-rolling
bool IsCapturing() const;
/** \brief Array of common audio sample rates
*
* These are the rates we will always support, regardless of hardware support
* for them (by resampling in audacity if needed) */
static const int StandardRates[];
/** \brief How many standard sample rates there are */
static const int NumStandardRates;
/** \brief Get diagnostic information on all the available audio I/O devices
*
*/
wxString GetDeviceInfo();
#ifdef EXPERIMENTAL_MIDI_OUT
/** \brief Get diagnostic information on all the available MIDI I/O devices */
wxString GetMidiDeviceInfo();
#endif
/** \brief Ensure selected device names are valid
*
*/
static bool ValidateDeviceNames(const wxString &play, const wxString &rec);
/** \brief Function to automatically set an acceptable volume
*
*/
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
void AILAInitialize();
void AILADisable();
bool AILAIsActive();
void AILAProcess(double maxPeak);
void AILASetStartTime();
double AILAGetLastDecisionTime();
#endif
bool IsAvailable(AudacityProject *projecT) const;
void SetCaptureMeter(AudacityProject *project, MeterPanel *meter);
void SetPlaybackMeter(AudacityProject *project, MeterPanel *meter);
/** \brief Return a valid sample rate that is supported by the current I/O
* device(s).
*
* The return from this function is used to determine the sample rate that
* audacity actually runs the audio I/O stream at. if there is no suitable
* rate available from the hardware, it returns 0.
* The sampleRate argument gives the desired sample rate (the rate of the
* audio to be handeled, i.e. the currently Project Rate).
* capturing is true if the stream is capturing one or more audio channels,
* and playing is true if one or more channels are being played. */
double GetBestRate(bool capturing, bool playing, double sampleRate);
private:
/** \brief Set the current VU meters - this should be done once after
* each call to StartStream currently */
void SetMeters();
/** \brief Opens the portaudio stream(s) used to do playback or recording
* (or both) through.
*
* The sampleRate passed is the Project Rate of the active project. It may
* or may not be actually supported by playback or recording hardware
* currently in use (for many reasons). The number of Capture and Playback
* channels requested includes an allocation for doing software playthrough
* if necessary. The captureFormat is used for recording only, the playback
* being floating point always. Returns true if the stream opened sucessfully
* and false if it did not. */
bool StartPortAudioStream(double sampleRate,
unsigned int numPlaybackChannels,
unsigned int numCaptureChannels,
sampleFormat captureFormat);
void FillBuffers();
#ifdef EXPERIMENTAL_MIDI_OUT
void PrepareMidiIterator(bool send = true, double offset = 0);
bool StartPortMidiStream();
// Compute nondecreasing real time stamps, accounting for pauses, but not the
// synth latency.
double UncorrectedMidiEventTime();
void OutputEvent();
void FillMidiBuffers();
void GetNextEvent();
double AudioTime() { return mPlaybackSchedule.mT0 + mNumFrames / mRate; }
double PauseTime();
void AllNotesOff(bool looping = false);
#endif
/** \brief Get the number of audio samples free in all of the playback
* buffers.
*
* Returns the smallest of the buffer free space values in the event that
* they are different. */
size_t GetCommonlyFreePlayback();
/** \brief Get the number of audio samples ready in all of the playback
* buffers.
@ -696,61 +383,6 @@ private:
* they are different. */
size_t GetCommonlyReadyPlayback();
/** \brief Get the number of audio samples ready in all of the recording
* buffers.
*
* Returns the smallest of the number of samples available for storage in
* the recording buffers (i.e. the number of samples that can be read from
* all record buffers without underflow). */
size_t GetCommonlyAvailCapture();
/** \brief get the index of the supplied (named) recording device, or the
* device selected in the preferences if none given.
*
* Pure utility function, but it comes round a number of times in the code
* and would be neater done once. If the device isn't found, return the
* default device index.
*/
static int getRecordDevIndex(const wxString &devName = wxEmptyString);
/** \brief get the index of the device selected in the preferences.
*
* If the device isn't found, returns -1
*/
#if USE_PORTMIXER
static int getRecordSourceIndex(PxMixer *portMixer);
#endif
/** \brief get the index of the supplied (named) playback device, or the
* device selected in the preferences if none given.
*
* Pure utility function, but it comes round a number of times in the code
* and would be neater done once. If the device isn't found, return the
* default device index.
*/
static int getPlayDevIndex(const wxString &devName = wxEmptyString);
/** \brief Array of audio sample rates to try to use
*
* These are the rates we will check if a device supports, and is as long
* as I can think of (to try and work out what the card can do) */
static const int RatesToTry[];
/** \brief How many sample rates to try */
static const int NumRatesToTry;
/** \brief Allocate RingBuffer structures, and others, needed for playback
* and recording.
*
* Returns true iff successful.
*/
bool AllocateBuffers(
const AudioIOStartStreamOptions &options,
const TransportTracks &tracks, double t0, double t1, double sampleRate,
bool scrubbing );
/** \brief Clean up after StartStream if it fails.
*
* If bOnlyBuffers is specified, it only cleans up the buffers. */
void StartStreamCleanup(bool bOnlyBuffers = false);
#ifdef EXPERIMENTAL_MIDI_OUT
// MIDI_PLAYBACK:
@ -901,9 +533,7 @@ private:
volatile double mLastRecordingOffset;
PaError mLastPaError;
public:
wxString LastPaErrorString();
private:
protected:
AudacityProject *mOwningProject;
wxWeakRef<MeterPanel> mInputMeter{};
@ -952,6 +582,7 @@ private:
wxMutex mSuspendAudioThread;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
public:
struct ScrubState;
std::unique_ptr<ScrubState> mScrubState;
@ -960,6 +591,7 @@ private:
sampleCount mScrubDuration;
#endif
protected:
// A flag tested and set in one thread, cleared in another. Perhaps
// this guarantee of atomicity is more cautious than necessary.
wxAtomicInt mRecordingException {};
@ -983,7 +615,7 @@ public:
// detect more dropouts
bool mDetectUpstreamDropouts{ true };
private:
protected:
struct RecordingSchedule {
double mPreRoll{};
double mLatencyCorrection{}; // negative value usually
@ -1153,6 +785,402 @@ private:
size_t nSamples );
double Consumer( size_t nSamples, double rate );
} mTimeQueue;
};
class AUDACITY_DLL_API AudioIO final : public AudioIoCallback {
public:
AudioIO();
~AudioIO();
public:
AudioIOListener* GetListener() { return mListener; }
void SetListener(AudioIOListener* listener);
/** \brief Start up Portaudio for capture and recording as needed for
* input monitoring and software playthrough only
*
* This uses the Default project sample format, current sample rate, and
* selected number of input channels to open the recording device and start
* reading input data. If software playthrough is enabled, it also opens
* the output device in stereo to play the data through */
void StartMonitoring(double sampleRate);
/** \brief Start recording or playing back audio
*
* Allocates buffers for recording and playback, gets the Audio thread to
* fill them, and sets the stream rolling.
* If successful, returns a token identifying this particular stream
* instance. For use with IsStreamActive() below */
int StartStream(const TransportTracks &tracks,
double t0, double t1,
const AudioIOStartStreamOptions &options);
/** \brief Stop recording, playback or input monitoring.
*
* Does quite a bit of housekeeping, including switching off monitoring,
* flushing recording buffers out to wave tracks, and applies latency
* correction to recorded tracks if necessary */
void StopStream();
/** \brief Move the playback / recording position of the current stream
* by the specified amount from where it is now */
void SeekStream(double seconds) { mSeek = seconds; }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
bool IsScrubbing() const { return IsBusy() && mScrubState != 0; }
/** \brief Notify scrubbing engine of desired position or speed.
* If options.adjustStart is true, then when mouse movement exceeds maximum
* scrub speed, adjust the beginning of the scrub interval rather than the
* end, so that the scrub skips or "stutters" to stay near the cursor.
*/
void UpdateScrub(double endTimeOrSpeed, const ScrubbingOptions &options);
void StopScrub();
/** \brief return the ending time of the last scrub interval.
*/
double GetLastScrubTime() const;
#endif
/** \brief Returns true if audio i/o is busy starting, stopping, playing,
* or recording.
*
* When this is false, it's safe to start playing or recording */
bool IsBusy() const;
/** \brief Returns true if the audio i/o is running at all, but not during
* cleanup
*
* Doesn't return true if the device has been closed but some disk i/o or
* cleanup is still going on. If you want to know if it's safe to start a
* NEW stream, use IsBusy() */
bool IsStreamActive() const;
bool IsStreamActive(int token) const;
public:
wxString LastPaErrorString();
wxLongLong GetLastPlaybackTime() const { return mLastPlaybackTimeMillis; }
AudacityProject *GetOwningProject() const { return mOwningProject; }
#ifdef EXPERIMENTAL_MIDI_OUT
/** \brief Compute the current PortMidi timestamp time.
*
* This is used by PortMidi to synchronize midi time to audio samples
*/
PmTimestamp MidiTime();
// Note: audio code solves the problem of soloing/muting tracks by scanning
// all playback tracks on every call to the audio buffer fill routine.
// We do the same for Midi, but it seems wasteful for at least two
// threads to be frequently polling to update status. This could be
// eliminated (also with a reduction in code I think) by updating mHasSolo
// each time a solo button is activated or deactivated. For now, I'm
// going to do this polling in the FillMidiBuffer routine to localize
// changes for midi to the midi code, but I'm declaring the variable
// here so possibly in the future, Audio code can use it too. -RBD
private:
bool mHasSolo; // is any playback solo button pressed?
public:
bool SetHasSolo(bool hasSolo);
bool GetHasSolo() { return mHasSolo; }
#endif
/** \brief Returns true if the stream is active, or even if audio I/O is
* busy cleaning up its data or writing to disk.
*
* This is used by TrackPanel to determine when a track has been completely
* recorded, and it's safe to flush to disk. */
bool IsAudioTokenActive(int token) const;
/** \brief Returns true if we're monitoring input (but not recording or
* playing actual audio) */
bool IsMonitoring() const;
/** \brief Pause and un-pause playback and recording */
void SetPaused(bool state);
/* Mixer services are always available. If no stream is running, these
* methods use whatever device is specified by the preferences. If a
* stream *is* running, naturally they manipulate the mixer associated
* with that stream. If no mixer is available, output is emulated and
* input is stuck at 1.0f (a gain is applied to output samples).
*/
void SetMixer(int inputSource);
void SetMixer(int inputSource, float inputVolume,
float playbackVolume);
void GetMixer(int *inputSource, float *inputVolume,
float *playbackVolume);
/** @brief Find out if the input hardware level control is available
*
* Checks the mInputMixerWorks variable, which is set up in
* AudioIO::HandleDeviceChange(). External people care, because we want to
* disable the UI if it doesn't work.
*/
bool InputMixerWorks();
/** @brief Find out if the output level control is being emulated via software attenuation
*
* Checks the mEmulateMixerOutputVol variable, which is set up in
* AudioIO::HandleDeviceChange(). External classes care, because we want to
* modify the UI if it doesn't work.
*/
bool OutputMixerEmulated();
/** \brief Get the list of inputs to the current mixer device
*
* Returns an array of strings giving the names of the inputs to the
* soundcard mixer (driven by PortMixer) */
wxArrayString GetInputSourceNames();
/** \brief update state after changing what audio devices are selected
*
* Called when the devices stored in the preferences are changed to update
* the audio mixer capabilities
*
* \todo: Make this do a sample rate query and store the result in the
* AudioIO object to avoid doing it later? Would simplify the
* GetSupported*Rate functions considerably */
void HandleDeviceChange();
/** \brief Get a list of sample rates the output (playback) device
* supports.
*
* If no information about available sample rates can be fetched,
* an empty list is returned.
*
* You can explicitely give the index of the device. If you don't
* give it, the currently selected device from the preferences will be used.
*
* You may also specify a rate for which to check in addition to the
* standard rates.
*/
static std::vector<long> GetSupportedPlaybackRates(int DevIndex = -1,
double rate = 0.0);
/** \brief Get a list of sample rates the input (recording) device
* supports.
*
* If no information about available sample rates can be fetched,
* an empty list is returned.
*
* You can explicitely give the index of the device. If you don't
* give it, the currently selected device from the preferences will be used.
*
* You may also specify a rate for which to check in addition to the
* standard rates.
*/
static std::vector<long> GetSupportedCaptureRates(int devIndex = -1,
double rate = 0.0);
/** \brief Get a list of sample rates the current input/output device
* combination supports.
*
* Since there is no concept (yet) for different input/output
* sample rates, this currently returns only sample rates that are
* supported on both the output and input device. If no information
* about available sample rates can be fetched, it returns a default
* list.
* You can explicitely give the indexes of the playDevice/recDevice.
* If you don't give them, the selected devices from the preferences
* will be used.
* You may also specify a rate for which to check in addition to the
* standard rates.
*/
static std::vector<long> GetSupportedSampleRates(int playDevice = -1,
int recDevice = -1,
double rate = 0.0);
/** \brief Get a supported sample rate which can be used a an optimal
* default.
*
* Currently, this uses the first supported rate in the list
* [44100, 48000, highest sample rate]. Used in Project as a default value
* for project rates if one cannot be retrieved from the preferences.
* So all in all not that useful or important really
*/
static int GetOptimalSupportedSampleRate();
/** \brief During playback, the track time most recently played
*
* When playing looped, this will start from t0 again,
* too. So the returned time should be always between
* t0 and t1
*/
double GetStreamTime();
sampleFormat GetCaptureFormat() { return mCaptureFormat; }
unsigned GetNumPlaybackChannels() const { return mNumPlaybackChannels; }
unsigned GetNumCaptureChannels() const { return mNumCaptureChannels; }
// Meaning really capturing, not just pre-rolling
bool IsCapturing() const;
/** \brief Array of common audio sample rates
*
* These are the rates we will always support, regardless of hardware support
* for them (by resampling in audacity if needed) */
static const int StandardRates[];
/** \brief How many standard sample rates there are */
static const int NumStandardRates;
/** \brief Get diagnostic information on all the available audio I/O devices
*
*/
wxString GetDeviceInfo();
#ifdef EXPERIMENTAL_MIDI_OUT
/** \brief Get diagnostic information on all the available MIDI I/O devices */
wxString GetMidiDeviceInfo();
#endif
/** \brief Ensure selected device names are valid
*
*/
static bool ValidateDeviceNames(const wxString &play, const wxString &rec);
/** \brief Function to automatically set an acceptable volume
*
*/
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
void AILAInitialize();
void AILADisable();
bool AILAIsActive();
void AILAProcess(double maxPeak);
void AILASetStartTime();
double AILAGetLastDecisionTime();
#endif
bool IsAvailable(AudacityProject *projecT) const;
void SetCaptureMeter(AudacityProject *project, MeterPanel *meter);
void SetPlaybackMeter(AudacityProject *project, MeterPanel *meter);
/** \brief Return a valid sample rate that is supported by the current I/O
* device(s).
*
* The return from this function is used to determine the sample rate that
* audacity actually runs the audio I/O stream at. if there is no suitable
* rate available from the hardware, it returns 0.
* The sampleRate argument gives the desired sample rate (the rate of the
* audio to be handeled, i.e. the currently Project Rate).
* capturing is true if the stream is capturing one or more audio channels,
* and playing is true if one or more channels are being played. */
double GetBestRate(bool capturing, bool playing, double sampleRate);
friend class AudioThread;
#ifdef EXPERIMENTAL_MIDI_OUT
friend class MidiThread;
#endif
friend void InitAudioIO();
private:
/** \brief Set the current VU meters - this should be done once after
* each call to StartStream currently */
void SetMeters();
/** \brief Opens the portaudio stream(s) used to do playback or recording
* (or both) through.
*
* The sampleRate passed is the Project Rate of the active project. It may
* or may not be actually supported by playback or recording hardware
* currently in use (for many reasons). The number of Capture and Playback
* channels requested includes an allocation for doing software playthrough
* if necessary. The captureFormat is used for recording only, the playback
* being floating point always. Returns true if the stream opened sucessfully
* and false if it did not. */
bool StartPortAudioStream(double sampleRate,
unsigned int numPlaybackChannels,
unsigned int numCaptureChannels,
sampleFormat captureFormat);
void FillBuffers();
#ifdef EXPERIMENTAL_MIDI_OUT
void PrepareMidiIterator(bool send = true, double offset = 0);
bool StartPortMidiStream();
// Compute nondecreasing real time stamps, accounting for pauses, but not the
// synth latency.
double UncorrectedMidiEventTime();
void OutputEvent();
void FillMidiBuffers();
void GetNextEvent();
double PauseTime();
void AllNotesOff(bool looping = false);
#endif
/** \brief Get the number of audio samples free in all of the playback
* buffers.
*
* Returns the smallest of the buffer free space values in the event that
* they are different. */
size_t GetCommonlyFreePlayback();
/** \brief Get the number of audio samples ready in all of the recording
* buffers.
*
* Returns the smallest of the number of samples available for storage in
* the recording buffers (i.e. the number of samples that can be read from
* all record buffers without underflow). */
size_t GetCommonlyAvailCapture();
/** \brief get the index of the supplied (named) recording device, or the
* device selected in the preferences if none given.
*
* Pure utility function, but it comes round a number of times in the code
* and would be neater done once. If the device isn't found, return the
* default device index.
*/
static int getRecordDevIndex(const wxString &devName = wxEmptyString);
/** \brief get the index of the device selected in the preferences.
*
* If the device isn't found, returns -1
*/
#if USE_PORTMIXER
static int getRecordSourceIndex(PxMixer *portMixer);
#endif
/** \brief get the index of the supplied (named) playback device, or the
* device selected in the preferences if none given.
*
* Pure utility function, but it comes round a number of times in the code
* and would be neater done once. If the device isn't found, return the
* default device index.
*/
static int getPlayDevIndex(const wxString &devName = wxEmptyString);
/** \brief Array of audio sample rates to try to use
*
* These are the rates we will check if a device supports, and is as long
* as I can think of (to try and work out what the card can do) */
static const int RatesToTry[];
/** \brief How many sample rates to try */
static const int NumRatesToTry;
/** \brief Allocate RingBuffer structures, and others, needed for playback
* and recording.
*
* Returns true iff successful.
*/
bool AllocateBuffers(
const AudioIOStartStreamOptions &options,
const TransportTracks &tracks, double t0, double t1, double sampleRate,
bool scrubbing );
/** \brief Clean up after StartStream if it fails.
*
* If bOnlyBuffers is specified, it only cleans up the buffers. */
void StartStreamCleanup(bool bOnlyBuffers = false);
};
#endif