diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 0187c81fb..d73c3ae8c 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -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 AudioIO::mCachedPlaybackRates; -int AudioIO::mCachedCaptureIndex = -1; -std::vector AudioIO::mCachedCaptureRates; -std::vector AudioIO::mCachedSampleRates; -double AudioIO::mCachedBestRateIn = 0.0; -double AudioIO::mCachedBestRateOut; +int AudioIoCallback::mNextStreamToken = 0; +int AudioIoCallback::mCachedPlaybackIndex = -1; +std::vector AudioIoCallback::mCachedPlaybackRates; +int AudioIoCallback::mCachedCaptureIndex = -1; +std::vector AudioIoCallback::mCachedCaptureRates; +std::vector 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) diff --git a/src/AudioIO.h b/src/AudioIO.h index c10910112..132a9f421 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -301,12 +301,12 @@ void MessageBuffer::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 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 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 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 mInputMeter{}; @@ -952,6 +582,7 @@ private: wxMutex mSuspendAudioThread; #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT +public: struct ScrubState; std::unique_ptr 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 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 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 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