diff --git a/src/AudioIO.h b/src/AudioIO.h index 7cb6a0260..5651ac2c2 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -16,6 +16,7 @@ #include "AudioIOBase.h" // to inherit +#include "PlaybackSchedule.h" // member variable diff --git a/src/AudioIOBase.cpp b/src/AudioIOBase.cpp index b0e7610a2..3535cec32 100644 --- a/src/AudioIOBase.cpp +++ b/src/AudioIOBase.cpp @@ -13,10 +13,10 @@ Paul Licameli split from AudioIO.cpp +#include #include #include -#include "Envelope.h" #include "Prefs.h" #include "prefs/RecordingPrefs.h" #include "widgets/MeterPanelBase.h" @@ -379,127 +379,6 @@ bool AudioIOBase::IsMonitoring() const return ( mPortStreamV19 && mStreamToken==0 ); } -void AudioIOBase::PlaybackSchedule::Init( - const double t0, const double t1, - const AudioIOStartStreamOptions &options, - const RecordingSchedule *pRecordingSchedule ) -{ - if ( pRecordingSchedule ) - // It does not make sense to apply the time warp during overdub recording, - // which defeats the purpose of making the recording synchronized with - // the existing audio. (Unless we figured out the inverse warp of the - // captured samples in real time.) - // So just quietly ignore the time track. - mEnvelope = nullptr; - else - mEnvelope = options.envelope; - - mT0 = t0; - if (pRecordingSchedule) - mT0 -= pRecordingSchedule->mPreRoll; - - mT1 = t1; - if (pRecordingSchedule) - // adjust mT1 so that we don't give paComplete too soon to fill up the - // desired length of recording - mT1 -= pRecordingSchedule->mLatencyCorrection; - - // Main thread's initialization of mTime - SetTrackTime( mT0 ); - - mPlayMode = options.playLooped - ? PlaybackSchedule::PLAY_LOOPED - : PlaybackSchedule::PLAY_STRAIGHT; - mCutPreviewGapStart = options.cutPreviewGapStart; - mCutPreviewGapLen = options.cutPreviewGapLen; - -#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - bool scrubbing = (options.pScrubbingOptions != nullptr); - - // Scrubbing is not compatible with looping or recording or a time track! - if (scrubbing) - { - const auto &scrubOptions = *options.pScrubbingOptions; - if (pRecordingSchedule || - Looping() || - mEnvelope || - scrubOptions.maxSpeed < ScrubbingOptions::MinAllowedScrubSpeed()) { - wxASSERT(false); - scrubbing = false; - } - else { - if (scrubOptions.isPlayingAtSpeed) - mPlayMode = PLAY_AT_SPEED; - else if (scrubOptions.isKeyboardScrubbing) - mPlayMode = PLAY_KEYBOARD_SCRUB; - else - mPlayMode = PLAY_SCRUB; - } - } -#endif - - mWarpedTime = 0.0; -#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - if (Scrubbing()) - mWarpedLength = 0.0f; - else -#endif - mWarpedLength = RealDuration(mT1); -} - -double AudioIOBase::PlaybackSchedule::LimitTrackTime() const -{ - // Track time readout for the main thread - // Allows for forward or backward play - return ClampTrackTime( GetTrackTime() ); -} - -double AudioIOBase::PlaybackSchedule::ClampTrackTime( double trackTime ) const -{ - if (ReversedTime()) - return std::max(mT1, std::min(mT0, trackTime)); - else - return std::max(mT0, std::min(mT1, trackTime)); -} - -double AudioIOBase::PlaybackSchedule::NormalizeTrackTime() const -{ - // Track time readout for the main thread - - // dmazzoni: This function is needed for two reasons: - // One is for looped-play mode - this function makes sure that the - // position indicator keeps wrapping around. The other reason is - // more subtle - it's because PortAudio can query the hardware for - // the current stream time, and this query is not always accurate. - // Sometimes it's a little behind or ahead, and so this function - // makes sure that at least we clip it to the selection. - // - // msmeyer: There is also the possibility that we are using "cut preview" - // mode. In this case, we should jump over a defined "gap" in the - // audio. - - double absoluteTime; - -#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - // Limit the time between t0 and t1 if not scrubbing. - // Should the limiting be necessary in any play mode if there are no bugs? - if (Interactive()) - absoluteTime = GetTrackTime(); - else -#endif - absoluteTime = LimitTrackTime(); - - if (mCutPreviewGapLen > 0) - { - // msmeyer: We're in cut preview mode, so if we are on the right - // side of the gap, we jump over it. - if (absoluteTime > mCutPreviewGapStart) - absoluteTime += mCutPreviewGapLen; - } - - return absoluteTime; -} - std::vector AudioIOBase::GetSupportedPlaybackRates(int devIndex, double rate) { if (devIndex == -1) @@ -1181,163 +1060,3 @@ wxString AudioIOBase::GetMidiDeviceInfo() return o.GetString(); } #endif - -bool AudioIOBase::PlaybackSchedule::PassIsComplete() const -{ - // Test mTime within the PortAudio callback - if (Scrubbing()) - return false; // but may be true if playing at speed - return Overruns( GetTrackTime() ); -} - -bool AudioIOBase::PlaybackSchedule::Overruns( double trackTime ) const -{ - return (ReversedTime() ? trackTime <= mT1 : trackTime >= mT1); -} - -namespace -{ -/** @brief Compute the duration (in seconds at playback) of the specified region of the track. - * - * Takes a region of the time track (specified by the unwarped time points in the project), and - * calculates how long it will actually take to play this region back, taking the time track's - * warping effects into account. - * @param t0 unwarped time to start calculation from - * @param t1 unwarped time to stop calculation at - * @return the warped duration in seconds - */ -double ComputeWarpedLength(const Envelope &env, double t0, double t1) -{ - return env.IntegralOfInverse(t0, t1); -} - -/** @brief Compute how much unwarped time must have elapsed if length seconds of warped time has - * elapsed - * - * @param t0 The unwarped time (seconds from project start) at which to start - * @param length How many seconds of warped time went past. - * @return The end point (in seconds from project start) as unwarped time - */ -double SolveWarpedLength(const Envelope &env, double t0, double length) -{ - return env.SolveIntegralOfInverse(t0, length); -} -} - -double AudioIOBase::PlaybackSchedule::AdvancedTrackTime( - double time, double realElapsed, double speed ) const -{ - if (ReversedTime()) - realElapsed *= -1.0; - - // Defense against cases that might cause loops not to terminate - if ( fabs(mT0 - mT1) < 1e-9 ) - return mT0; - - if (mEnvelope) { - wxASSERT( speed == 1.0 ); - - double total=0.0; - bool foundTotal = false; - do { - auto oldTime = time; - if (foundTotal && fabs(realElapsed) > fabs(total)) - // Avoid SolveWarpedLength - time = mT1; - else - time = SolveWarpedLength(*mEnvelope, time, realElapsed); - - if (!Looping() || !Overruns( time )) - break; - - // Bug1922: The part of the time track outside the loop should not - // influence the result - double delta; - if (foundTotal && oldTime == mT0) - // Avoid integrating again - delta = total; - else { - delta = ComputeWarpedLength(*mEnvelope, oldTime, mT1); - if (oldTime == mT0) - foundTotal = true, total = delta; - } - realElapsed -= delta; - time = mT0; - } while ( true ); - } - else { - time += realElapsed * fabs(speed); - - // Wrap to start if looping - if (Looping()) { - while ( Overruns( time ) ) { - // LL: This is not exactly right, but I'm at my wits end trying to - // figure it out. Feel free to fix it. :-) - // MB: it's much easier than you think, mTime isn't warped at all! - time -= mT1 - mT0; - } - } - } - - return time; -} - -void AudioIOBase::PlaybackSchedule::TrackTimeUpdate(double realElapsed) -{ - // Update mTime within the PortAudio callback - - if (Interactive()) - return; - - auto time = GetTrackTime(); - auto newTime = AdvancedTrackTime( time, realElapsed, 1.0 ); - SetTrackTime( newTime ); -} - -double AudioIOBase::PlaybackSchedule::RealDuration(double trackTime1) const -{ - double duration; - if (mEnvelope) - duration = ComputeWarpedLength(*mEnvelope, mT0, trackTime1); - else - duration = trackTime1 - mT0; - return fabs(duration); -} - -double AudioIOBase::PlaybackSchedule::RealTimeRemaining() const -{ - return mWarpedLength - mWarpedTime; -} - -void AudioIOBase::PlaybackSchedule::RealTimeAdvance( double increment ) -{ - mWarpedTime += increment; -} - -void AudioIOBase::PlaybackSchedule::RealTimeInit( double trackTime ) -{ - if (Scrubbing()) - mWarpedTime = 0.0; - else - mWarpedTime = RealDuration( trackTime ); -} - -void AudioIOBase::PlaybackSchedule::RealTimeRestart() -{ - mWarpedTime = 0; -} - -double AudioIOBase::RecordingSchedule::ToConsume() const -{ - return mDuration - Consumed(); -} - -double AudioIOBase::RecordingSchedule::Consumed() const -{ - return std::max( 0.0, mPosition + TotalCorrection() ); -} - -double AudioIOBase::RecordingSchedule::ToDiscard() const -{ - return std::max(0.0, -( mPosition + TotalCorrection() ) ); -} diff --git a/src/AudioIOBase.h b/src/AudioIOBase.h index 3268a72c1..cc4bcdf5f 100644 --- a/src/AudioIOBase.h +++ b/src/AudioIOBase.h @@ -14,7 +14,6 @@ Paul Licameli split from AudioIO.h -#include #include #include #include @@ -302,152 +301,7 @@ protected: static std::vector mCachedSampleRates; static double mCachedBestRateIn; - struct RecordingSchedule { - double mPreRoll{}; - double mLatencyCorrection{}; // negative value usually - double mDuration{}; - PRCrossfadeData mCrossfadeData; - - // These are initialized by the main thread, then updated - // only by the thread calling FillBuffers: - double mPosition{}; - bool mLatencyCorrected{}; - - double TotalCorrection() const { return mLatencyCorrection - mPreRoll; } - double ToConsume() const; - double Consumed() const; - double ToDiscard() const; - }; - - struct PlaybackSchedule { - /// Playback starts at offset of mT0, which is measured in seconds. - double mT0; - /// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing. - double mT1; - /// Current track time position during playback, in seconds. - /// Initialized by the main thread but updated by worker threads during - /// playback or recording, and periodically reread by the main thread for - /// purposes such as display update. - std::atomic mTime; - - /// Accumulated real time (not track position), starting at zero (unlike - /// mTime), and wrapping back to zero each time around looping play. - /// Thus, it is the length in real seconds between mT0 and mTime. - double mWarpedTime; - - /// Real length to be played (if looping, for each pass) after warping via a - /// time track, computed just once when starting the stream. - /// Length in real seconds between mT0 and mT1. Always positive. - double mWarpedLength; - - // mWarpedTime and mWarpedLength are irrelevant when scrubbing, - // else they are used in updating mTime, - // and when not scrubbing or playing looped, mTime is also used - // in the test for termination of playback. - - // with ComputeWarpedLength, it is now possible the calculate the warped length with 100% accuracy - // (ignoring accumulated rounding errors during playback) which fixes the 'missing sound at the end' bug - - const BoundedEnvelope *mEnvelope; - - volatile enum { - PLAY_STRAIGHT, - PLAY_LOOPED, -#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - PLAY_SCRUB, - PLAY_AT_SPEED, // a version of PLAY_SCRUB. - PLAY_KEYBOARD_SCRUB, -#endif - } mPlayMode { PLAY_STRAIGHT }; - double mCutPreviewGapStart; - double mCutPreviewGapLen; - - void Init( - double t0, double t1, - const AudioIOStartStreamOptions &options, - const RecordingSchedule *pRecordingSchedule ); - - /** \brief True if the end time is before the start time */ - bool ReversedTime() const - { - return mT1 < mT0; - } - - /** \brief Get current track time value, unadjusted - * - * Returns a time in seconds. - */ - double GetTrackTime() const - { return mTime.load(std::memory_order_relaxed); } - - /** \brief Set current track time value, unadjusted - */ - void SetTrackTime( double time ) - { mTime.store(time, std::memory_order_relaxed); } - - /** \brief Clamps argument to be between mT0 and mT1 - * - * Returns the bound if the value is out of bounds; does not wrap. - * Returns a time in seconds. - */ - double ClampTrackTime( double trackTime ) const; - - /** \brief Clamps mTime to be between mT0 and mT1 - * - * Returns the bound if the value is out of bounds; does not wrap. - * Returns a time in seconds. - */ - double LimitTrackTime() const; - - /** \brief Normalizes mTime, clamping it and handling gaps from cut preview. - * - * Clamps the time (unless scrubbing), and skips over the cut section. - * Returns a time in seconds. - */ - double NormalizeTrackTime() const; - - void ResetMode() { mPlayMode = PLAY_STRAIGHT; } - - bool PlayingStraight() const { return mPlayMode == PLAY_STRAIGHT; } - bool Looping() const { return mPlayMode == PLAY_LOOPED; } - bool Scrubbing() const { return mPlayMode == PLAY_SCRUB || mPlayMode == PLAY_KEYBOARD_SCRUB; } - bool PlayingAtSpeed() const { return mPlayMode == PLAY_AT_SPEED; } - bool Interactive() const { return Scrubbing() || PlayingAtSpeed(); } - - // Returns true if a loop pass, or the sole pass of straight play, - // is completed at the current value of mTime - bool PassIsComplete() const; - - // Returns true if time equals t1 or is on opposite side of t1, to t0 - bool Overruns( double trackTime ) const; - - // Compute the NEW track time for the given one and a real duration, - // taking into account whether the schedule is for looping - double AdvancedTrackTime( - double trackTime, double realElapsed, double speed) const; - - // Use the function above in the callback after consuming samples from the - // playback ring buffers, during usual straight or looping play - void TrackTimeUpdate(double realElapsed); - - // Convert time between mT0 and argument to real duration, according to - // time track if one is given; result is always nonnegative - double RealDuration(double trackTime1) const; - - // How much real time left? - double RealTimeRemaining() const; - - // Advance the real time position - void RealTimeAdvance( double increment ); - - // Determine starting duration within the first pass -- sometimes not - // zero - void RealTimeInit( double trackTime ); - - void RealTimeRestart(); - - }; - +protected: /** \brief get the index of the supplied (named) recording device, or the * device selected in the preferences if none given. * diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ecb08575f..30c0fc2f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -188,6 +188,8 @@ list( APPEND SOURCES PitchName.h PlatformCompatibility.cpp PlatformCompatibility.h + PlaybackSchedule.cpp + PlaybackSchedule.h PluginManager.cpp PluginManager.h Prefs.cpp diff --git a/src/PlaybackSchedule.cpp b/src/PlaybackSchedule.cpp index b0e7610a2..9cadfc30b 100644 --- a/src/PlaybackSchedule.cpp +++ b/src/PlaybackSchedule.cpp @@ -1,385 +1,19 @@ /********************************************************************** + + Audacity: A Digital Audio Editor + + PlaybackSchedule.cpp + + Paul Licameli split from AudioIOBase.cpp + + **********************************************************************/ -Audacity: A Digital Audio Editor - -AudioIOBase.cpp - -Paul Licameli split from AudioIO.cpp - -**********************************************************************/ - +#include "PlaybackSchedule.h" #include "AudioIOBase.h" - - - -#include -#include - #include "Envelope.h" -#include "Prefs.h" -#include "prefs/RecordingPrefs.h" -#include "widgets/MeterPanelBase.h" -#if USE_PORTMIXER -#include "portmixer.h" -#endif - -#ifdef EXPERIMENTAL_MIDI_OUT -#include "../lib-src/portmidi/pm_common/portmidi.h" -#endif - -int AudioIOBase::mCachedPlaybackIndex = -1; -std::vector AudioIOBase::mCachedPlaybackRates; -int AudioIOBase::mCachedCaptureIndex = -1; -std::vector AudioIOBase::mCachedCaptureRates; -std::vector AudioIOBase::mCachedSampleRates; -double AudioIOBase::mCachedBestRateIn = 0.0; - -const int AudioIOBase::StandardRates[] = { - 8000, - 11025, - 16000, - 22050, - 32000, - 44100, - 48000, - 88200, - 96000, - 176400, - 192000, - 352800, - 384000 -}; - -const int AudioIOBase::NumStandardRates = WXSIZEOF(AudioIOBase::StandardRates); - -const int AudioIOBase::RatesToTry[] = { - 8000, - 9600, - 11025, - 12000, - 15000, - 16000, - 22050, - 24000, - 32000, - 44100, - 48000, - 88200, - 96000, - 176400, - 192000, - 352800, - 384000 -}; -const int AudioIOBase::NumRatesToTry = WXSIZEOF(AudioIOBase::RatesToTry); - -wxString AudioIOBase::DeviceName(const PaDeviceInfo* info) -{ - wxString infoName = wxSafeConvertMB2WX(info->name); - - return infoName; -} - -wxString AudioIOBase::HostName(const PaDeviceInfo* info) -{ - wxString hostapiName = wxSafeConvertMB2WX(Pa_GetHostApiInfo(info->hostApi)->name); - - return hostapiName; -} - -std::unique_ptr AudioIOBase::ugAudioIO; - -AudioIOBase *AudioIOBase::Get() -{ - return ugAudioIO.get(); -} - -AudioIOBase::~AudioIOBase() = default; - -void AudioIOBase::SetMixer(int inputSource) -{ -#if defined(USE_PORTMIXER) - int oldRecordSource = Px_GetCurrentInputSource(mPortMixer); - if ( inputSource != oldRecordSource ) - Px_SetCurrentInputSource(mPortMixer, inputSource); -#endif -} - -void AudioIOBase::HandleDeviceChange() -{ - // This should not happen, but it would screw things up if it did. - // Vaughan, 2010-10-08: But it *did* happen, due to a bug, and nobody - // caught it because this method just returned. Added wxASSERT(). - wxASSERT(!IsStreamActive()); - if (IsStreamActive()) - return; - - // get the selected record and playback devices - const int playDeviceNum = getPlayDevIndex(); - const int recDeviceNum = getRecordDevIndex(); - - // If no change needed, return - if (mCachedPlaybackIndex == playDeviceNum && - mCachedCaptureIndex == recDeviceNum) - return; - - // cache playback/capture rates - mCachedPlaybackRates = GetSupportedPlaybackRates(playDeviceNum); - mCachedCaptureRates = GetSupportedCaptureRates(recDeviceNum); - mCachedSampleRates = GetSupportedSampleRates(playDeviceNum, recDeviceNum); - mCachedPlaybackIndex = playDeviceNum; - mCachedCaptureIndex = recDeviceNum; - mCachedBestRateIn = 0.0; - -#if defined(USE_PORTMIXER) - - // if we have a PortMixer object, close it down - if (mPortMixer) { - #if __WXMAC__ - // on the Mac we must make sure that we restore the hardware playthrough - // state of the sound device to what it was before, because there isn't - // a UI for this (!) - if (Px_SupportsPlaythrough(mPortMixer) && mPreviousHWPlaythrough >= 0.0) - Px_SetPlaythrough(mPortMixer, mPreviousHWPlaythrough); - mPreviousHWPlaythrough = -1.0; - #endif - Px_CloseMixer(mPortMixer); - mPortMixer = NULL; - } - - // that might have given us no rates whatsoever, so we have to guess an - // answer to do the next bit - int numrates = mCachedSampleRates.size(); - int highestSampleRate; - if (numrates > 0) - { - highestSampleRate = mCachedSampleRates[numrates - 1]; - } - else - { // we don't actually have any rates that work for Rec and Play. Guess one - // to use for messing with the mixer, which doesn't actually do either - highestSampleRate = 44100; - // mCachedSampleRates is still empty, but it's not used again, so - // can ignore - } - mInputMixerWorks = false; - mEmulateMixerOutputVol = true; - mMixerOutputVol = 1.0; - - int error; - // This tries to open the device with the samplerate worked out above, which - // will be the highest available for play and record on the device, or - // 44.1kHz if the info cannot be fetched. - - PaStream *stream; - - PaStreamParameters playbackParameters; - - playbackParameters.device = playDeviceNum; - playbackParameters.sampleFormat = paFloat32; - playbackParameters.hostApiSpecificStreamInfo = NULL; - playbackParameters.channelCount = 1; - if (Pa_GetDeviceInfo(playDeviceNum)) - playbackParameters.suggestedLatency = - Pa_GetDeviceInfo(playDeviceNum)->defaultLowOutputLatency; - else - playbackParameters.suggestedLatency = DEFAULT_LATENCY_CORRECTION/1000.0; - - PaStreamParameters captureParameters; - - captureParameters.device = recDeviceNum; - captureParameters.sampleFormat = paFloat32;; - captureParameters.hostApiSpecificStreamInfo = NULL; - captureParameters.channelCount = 1; - if (Pa_GetDeviceInfo(recDeviceNum)) - captureParameters.suggestedLatency = - Pa_GetDeviceInfo(recDeviceNum)->defaultLowInputLatency; - else - captureParameters.suggestedLatency = DEFAULT_LATENCY_CORRECTION/1000.0; - - // try opening for record and playback - // Not really doing I/O so pass nullptr for the callback function - error = Pa_OpenStream(&stream, - &captureParameters, &playbackParameters, - highestSampleRate, paFramesPerBufferUnspecified, - paClipOff | paDitherOff, - nullptr, NULL); - - if (!error) { - // Try portmixer for this stream - mPortMixer = Px_OpenMixer(stream, 0); - if (!mPortMixer) { - Pa_CloseStream(stream); - error = true; - } - } - - // if that failed, try just for record - if( error ) { - error = Pa_OpenStream(&stream, - &captureParameters, NULL, - highestSampleRate, paFramesPerBufferUnspecified, - paClipOff | paDitherOff, - nullptr, NULL); - - if (!error) { - mPortMixer = Px_OpenMixer(stream, 0); - if (!mPortMixer) { - Pa_CloseStream(stream); - error = true; - } - } - } - - // finally, try just for playback - if ( error ) { - error = Pa_OpenStream(&stream, - NULL, &playbackParameters, - highestSampleRate, paFramesPerBufferUnspecified, - paClipOff | paDitherOff, - nullptr, NULL); - - if (!error) { - mPortMixer = Px_OpenMixer(stream, 0); - if (!mPortMixer) { - Pa_CloseStream(stream); - error = true; - } - } - } - - // FIXME: TRAP_ERR errors in HandleDeviceChange not reported. - // if it's still not working, give up - if( error ) - return; - - // Set input source -#if USE_PORTMIXER - int sourceIndex; - if (gPrefs->Read(wxT("/AudioIO/RecordingSourceIndex"), &sourceIndex)) { - if (sourceIndex >= 0) { - //the current index of our source may be different because the stream - //is a combination of two devices, so update it. - sourceIndex = getRecordSourceIndex(mPortMixer); - if (sourceIndex >= 0) - SetMixer(sourceIndex); - } - } -#endif - - // Determine mixer capabilities - if it doesn't support control of output - // signal level, we emulate it (by multiplying this value by all outgoing - // samples) - - mMixerOutputVol = Px_GetPCMOutputVolume(mPortMixer); - mEmulateMixerOutputVol = false; - Px_SetPCMOutputVolume(mPortMixer, 0.0); - if (Px_GetPCMOutputVolume(mPortMixer) > 0.1) - mEmulateMixerOutputVol = true; - Px_SetPCMOutputVolume(mPortMixer, 0.2f); - if (Px_GetPCMOutputVolume(mPortMixer) < 0.1 || - Px_GetPCMOutputVolume(mPortMixer) > 0.3) - mEmulateMixerOutputVol = true; - Px_SetPCMOutputVolume(mPortMixer, mMixerOutputVol); - - float inputVol = Px_GetInputVolume(mPortMixer); - mInputMixerWorks = true; // assume it works unless proved wrong - Px_SetInputVolume(mPortMixer, 0.0); - if (Px_GetInputVolume(mPortMixer) > 0.1) - mInputMixerWorks = false; // can't set to zero - Px_SetInputVolume(mPortMixer, 0.2f); - if (Px_GetInputVolume(mPortMixer) < 0.1 || - Px_GetInputVolume(mPortMixer) > 0.3) - mInputMixerWorks = false; // can't set level accurately - Px_SetInputVolume(mPortMixer, inputVol); - - Pa_CloseStream(stream); - - - #if 0 - wxPrintf("PortMixer: Playback: %s Recording: %s\n", - mEmulateMixerOutputVol? "emulated": "native", - mInputMixerWorks? "hardware": "no control"); - #endif - - mMixerOutputVol = 1.0; - -#endif // USE_PORTMIXER -} - -void AudioIOBase::SetCaptureMeter(AudacityProject *project, MeterPanelBase *meter) -{ - if (( mOwningProject ) && ( mOwningProject != project)) - return; - - if (meter) - { - mInputMeter = meter; - mInputMeter->Reset(mRate, true); - } - else - mInputMeter.Release(); -} - -void AudioIOBase::SetPlaybackMeter(AudacityProject *project, MeterPanelBase *meter) -{ - if (( mOwningProject ) && ( mOwningProject != project)) - return; - - if (meter) - { - mOutputMeter = meter; - mOutputMeter->Reset(mRate, true); - } - else - mOutputMeter.Release(); -} - -bool AudioIOBase::IsPaused() const -{ - return mPaused; -} - -bool AudioIOBase::IsBusy() const -{ - if (mStreamToken != 0) - return true; - - return false; -} - -bool AudioIOBase::IsStreamActive() const -{ - bool isActive = false; - // JKC: Not reporting any Pa error, but that looks OK. - if( mPortStreamV19 ) - isActive = (Pa_IsStreamActive( mPortStreamV19 ) > 0); - -#ifdef EXPERIMENTAL_MIDI_OUT - if( mMidiStreamActive && !mMidiOutputComplete ) - isActive = true; -#endif - return isActive; -} - -bool AudioIOBase::IsStreamActive(int token) const -{ - return (this->IsStreamActive() && this->IsAudioTokenActive(token)); -} - -bool AudioIOBase::IsAudioTokenActive(int token) const -{ - return ( token > 0 && token == mStreamToken ); -} - -bool AudioIOBase::IsMonitoring() const -{ - return ( mPortStreamV19 && mStreamToken==0 ); -} - -void AudioIOBase::PlaybackSchedule::Init( +void PlaybackSchedule::Init( const double t0, const double t1, const AudioIOStartStreamOptions &options, const RecordingSchedule *pRecordingSchedule ) @@ -447,14 +81,14 @@ void AudioIOBase::PlaybackSchedule::Init( mWarpedLength = RealDuration(mT1); } -double AudioIOBase::PlaybackSchedule::LimitTrackTime() const +double PlaybackSchedule::LimitTrackTime() const { // Track time readout for the main thread // Allows for forward or backward play return ClampTrackTime( GetTrackTime() ); } -double AudioIOBase::PlaybackSchedule::ClampTrackTime( double trackTime ) const +double PlaybackSchedule::ClampTrackTime( double trackTime ) const { if (ReversedTime()) return std::max(mT1, std::min(mT0, trackTime)); @@ -462,7 +96,7 @@ double AudioIOBase::PlaybackSchedule::ClampTrackTime( double trackTime ) const return std::max(mT0, std::min(mT1, trackTime)); } -double AudioIOBase::PlaybackSchedule::NormalizeTrackTime() const +double PlaybackSchedule::NormalizeTrackTime() const { // Track time readout for the main thread @@ -500,689 +134,7 @@ double AudioIOBase::PlaybackSchedule::NormalizeTrackTime() const return absoluteTime; } -std::vector AudioIOBase::GetSupportedPlaybackRates(int devIndex, double rate) -{ - if (devIndex == -1) - { // weren't given a device index, get the prefs / default one - devIndex = getPlayDevIndex(); - } - - // Check if we can use the cached rates - if (mCachedPlaybackIndex != -1 && devIndex == mCachedPlaybackIndex - && (rate == 0.0 || make_iterator_range(mCachedPlaybackRates).contains(rate))) - { - return mCachedPlaybackRates; - } - - std::vector supported; - int irate = (int)rate; - const PaDeviceInfo* devInfo = NULL; - int i; - - devInfo = Pa_GetDeviceInfo(devIndex); - - if (!devInfo) - { - wxLogDebug(wxT("GetSupportedPlaybackRates() Could not get device info!")); - return supported; - } - - // LLL: Remove when a proper method of determining actual supported - // DirectSound rate is devised. - const PaHostApiInfo* hostInfo = Pa_GetHostApiInfo(devInfo->hostApi); - bool isDirectSound = (hostInfo && hostInfo->type == paDirectSound); - - PaStreamParameters pars; - - pars.device = devIndex; - pars.channelCount = 1; - pars.sampleFormat = paFloat32; - pars.suggestedLatency = devInfo->defaultHighOutputLatency; - pars.hostApiSpecificStreamInfo = NULL; - - // JKC: PortAudio Errors handled OK here. No need to report them - for (i = 0; i < NumRatesToTry; i++) - { - // LLL: Remove when a proper method of determining actual supported - // DirectSound rate is devised. - if (!(isDirectSound && RatesToTry[i] > 200000)){ - if (Pa_IsFormatSupported(NULL, &pars, RatesToTry[i]) == 0) - supported.push_back(RatesToTry[i]); - Pa_Sleep( 10 );// There are ALSA drivers that don't like being probed - // too quickly. - } - } - - if (irate != 0 && !make_iterator_range(supported).contains(irate)) - { - // LLL: Remove when a proper method of determining actual supported - // DirectSound rate is devised. - if (!(isDirectSound && RatesToTry[i] > 200000)) - if (Pa_IsFormatSupported(NULL, &pars, irate) == 0) - supported.push_back(irate); - } - - return supported; -} - -std::vector AudioIOBase::GetSupportedCaptureRates(int devIndex, double rate) -{ - if (devIndex == -1) - { // not given a device, look up in prefs / default - devIndex = getRecordDevIndex(); - } - - // Check if we can use the cached rates - if (mCachedCaptureIndex != -1 && devIndex == mCachedCaptureIndex - && (rate == 0.0 || make_iterator_range(mCachedCaptureRates).contains(rate))) - { - return mCachedCaptureRates; - } - - std::vector supported; - int irate = (int)rate; - const PaDeviceInfo* devInfo = NULL; - int i; - - devInfo = Pa_GetDeviceInfo(devIndex); - - if (!devInfo) - { - wxLogDebug(wxT("GetSupportedCaptureRates() Could not get device info!")); - return supported; - } - - double latencyDuration = DEFAULT_LATENCY_DURATION; - long recordChannels = 1; - gPrefs->Read(wxT("/AudioIO/LatencyDuration"), &latencyDuration); - gPrefs->Read(wxT("/AudioIO/RecordChannels"), &recordChannels); - - // LLL: Remove when a proper method of determining actual supported - // DirectSound rate is devised. - const PaHostApiInfo* hostInfo = Pa_GetHostApiInfo(devInfo->hostApi); - bool isDirectSound = (hostInfo && hostInfo->type == paDirectSound); - - PaStreamParameters pars; - - pars.device = devIndex; - pars.channelCount = recordChannels; - pars.sampleFormat = paFloat32; - pars.suggestedLatency = latencyDuration / 1000.0; - pars.hostApiSpecificStreamInfo = NULL; - - for (i = 0; i < NumRatesToTry; i++) - { - // LLL: Remove when a proper method of determining actual supported - // DirectSound rate is devised. - if (!(isDirectSound && RatesToTry[i] > 200000)) - { - if (Pa_IsFormatSupported(&pars, NULL, RatesToTry[i]) == 0) - supported.push_back(RatesToTry[i]); - Pa_Sleep( 10 );// There are ALSA drivers that don't like being probed - // too quickly. - } - } - - if (irate != 0 && !make_iterator_range(supported).contains(irate)) - { - // LLL: Remove when a proper method of determining actual supported - // DirectSound rate is devised. - if (!(isDirectSound && RatesToTry[i] > 200000)) - if (Pa_IsFormatSupported(&pars, NULL, irate) == 0) - supported.push_back(irate); - } - - return supported; -} - -std::vector AudioIOBase::GetSupportedSampleRates( - int playDevice, int recDevice, double rate) -{ - // Not given device indices, look up prefs - if (playDevice == -1) { - playDevice = getPlayDevIndex(); - } - if (recDevice == -1) { - recDevice = getRecordDevIndex(); - } - - // Check if we can use the cached rates - if (mCachedPlaybackIndex != -1 && mCachedCaptureIndex != -1 && - playDevice == mCachedPlaybackIndex && - recDevice == mCachedCaptureIndex && - (rate == 0.0 || make_iterator_range(mCachedSampleRates).contains(rate))) - { - return mCachedSampleRates; - } - - auto playback = GetSupportedPlaybackRates(playDevice, rate); - auto capture = GetSupportedCaptureRates(recDevice, rate); - int i; - - // Return only sample rates which are in both arrays - std::vector result; - - for (i = 0; i < (int)playback.size(); i++) - if (make_iterator_range(capture).contains(playback[i])) - result.push_back(playback[i]); - - // If this yields no results, use the default sample rates nevertheless -/* if (result.empty()) - { - for (i = 0; i < NumStandardRates; i++) - result.push_back(StandardRates[i]); - }*/ - - return result; -} - -/** \todo: should this take into account PortAudio's value for - * PaDeviceInfo::defaultSampleRate? In principal this should let us work out - * which rates are "real" and which resampled in the drivers, and so prefer - * the real rates. */ -int AudioIOBase::GetOptimalSupportedSampleRate() -{ - auto rates = GetSupportedSampleRates(); - - if (make_iterator_range(rates).contains(44100)) - return 44100; - - if (make_iterator_range(rates).contains(48000)) - return 48000; - - // if there are no supported rates, the next bit crashes. So check first, - // and give them a "sensible" value if there are no valid values. They - // will still get an error later, but with any luck may have changed - // something by then. It's no worse than having an invalid default rate - // stored in the preferences, which we don't check for - if (rates.empty()) return 44100; - - return rates.back(); -} - -#if USE_PORTMIXER -int AudioIOBase::getRecordSourceIndex(PxMixer *portMixer) -{ - int i; - wxString sourceName = gPrefs->Read(wxT("/AudioIO/RecordingSource"), wxT("")); - int numSources = Px_GetNumInputSources(portMixer); - for (i = 0; i < numSources; i++) { - if (sourceName == wxString(wxSafeConvertMB2WX(Px_GetInputSourceName(portMixer, i)))) - return i; - } - return -1; -} -#endif - -int AudioIOBase::getPlayDevIndex(const wxString &devNameArg) -{ - wxString devName(devNameArg); - // if we don't get given a device, look up the preferences - if (devName.empty()) - { - devName = gPrefs->Read(wxT("/AudioIO/PlaybackDevice"), wxT("")); - } - - wxString hostName = gPrefs->Read(wxT("/AudioIO/Host"), wxT("")); - PaHostApiIndex hostCnt = Pa_GetHostApiCount(); - PaHostApiIndex hostNum; - for (hostNum = 0; hostNum < hostCnt; hostNum++) - { - const PaHostApiInfo *hinfo = Pa_GetHostApiInfo(hostNum); - if (hinfo && wxString(wxSafeConvertMB2WX(hinfo->name)) == hostName) - { - for (PaDeviceIndex hostDevice = 0; hostDevice < hinfo->deviceCount; hostDevice++) - { - PaDeviceIndex deviceNum = Pa_HostApiDeviceIndexToDeviceIndex(hostNum, hostDevice); - - const PaDeviceInfo *dinfo = Pa_GetDeviceInfo(deviceNum); - if (dinfo && DeviceName(dinfo) == devName && dinfo->maxOutputChannels > 0 ) - { - // this device name matches the stored one, and works. - // So we say this is the answer and return it - return deviceNum; - } - } - - // The device wasn't found so use the default for this host. - // LL: At this point, preferences and active no longer match. - return hinfo->defaultOutputDevice; - } - } - - // The host wasn't found, so use the default output device. - // FIXME: TRAP_ERR PaErrorCode not handled well (this code is similar to input code - // and the input side has more comments.) - - PaDeviceIndex deviceNum = Pa_GetDefaultOutputDevice(); - - // Sometimes PortAudio returns -1 if it cannot find a suitable default - // device, so we just use the first one available - // - // LL: At this point, preferences and active no longer match - // - // And I can't imagine how far we'll get specifying an "invalid" index later - // on...are we certain "0" even exists? - if (deviceNum < 0) { - wxASSERT(false); - deviceNum = 0; - } - - return deviceNum; -} - -int AudioIOBase::getRecordDevIndex(const wxString &devNameArg) -{ - wxString devName(devNameArg); - // if we don't get given a device, look up the preferences - if (devName.empty()) - { - devName = gPrefs->Read(wxT("/AudioIO/RecordingDevice"), wxT("")); - } - - wxString hostName = gPrefs->Read(wxT("/AudioIO/Host"), wxT("")); - PaHostApiIndex hostCnt = Pa_GetHostApiCount(); - PaHostApiIndex hostNum; - for (hostNum = 0; hostNum < hostCnt; hostNum++) - { - const PaHostApiInfo *hinfo = Pa_GetHostApiInfo(hostNum); - if (hinfo && wxString(wxSafeConvertMB2WX(hinfo->name)) == hostName) - { - for (PaDeviceIndex hostDevice = 0; hostDevice < hinfo->deviceCount; hostDevice++) - { - PaDeviceIndex deviceNum = Pa_HostApiDeviceIndexToDeviceIndex(hostNum, hostDevice); - - const PaDeviceInfo *dinfo = Pa_GetDeviceInfo(deviceNum); - if (dinfo && DeviceName(dinfo) == devName && dinfo->maxInputChannels > 0 ) - { - // this device name matches the stored one, and works. - // So we say this is the answer and return it - return deviceNum; - } - } - - // The device wasn't found so use the default for this host. - // LL: At this point, preferences and active no longer match. - return hinfo->defaultInputDevice; - } - } - - // The host wasn't found, so use the default input device. - // FIXME: TRAP_ERR PaErrorCode not handled well in getRecordDevIndex() - PaDeviceIndex deviceNum = Pa_GetDefaultInputDevice(); - - // Sometimes PortAudio returns -1 if it cannot find a suitable default - // device, so we just use the first one available - // PortAudio has an error reporting function. We should log/report the error? - // - // LL: At this point, preferences and active no longer match - // - // And I can't imagine how far we'll get specifying an "invalid" index later - // on...are we certain "0" even exists? - if (deviceNum < 0) { - // JKC: This ASSERT will happen if you run with no config file - // This happens once. Config file will exist on the next run. - // TODO: Look into this a bit more. Could be relevant to blank Device Toolbar. - wxASSERT(false); - deviceNum = 0; - } - - return deviceNum; -} - -wxString AudioIOBase::GetDeviceInfo() -{ - wxStringOutputStream o; - wxTextOutputStream s(o, wxEOL_UNIX); - - if (IsStreamActive()) { - return XO("Stream is active ... unable to gather information.\n") - .Translation(); - } - - - // FIXME: TRAP_ERR PaErrorCode not handled. 3 instances in GetDeviceInfo(). - int recDeviceNum = Pa_GetDefaultInputDevice(); - int playDeviceNum = Pa_GetDefaultOutputDevice(); - int cnt = Pa_GetDeviceCount(); - - // PRL: why only into the log? - wxLogDebug(wxT("Portaudio reports %d audio devices"),cnt); - - s << wxT("==============================\n"); - s << XO("Default recording device number: %d\n").Format( recDeviceNum ); - s << XO("Default playback device number: %d\n").Format( playDeviceNum); - - wxString recDevice = gPrefs->Read(wxT("/AudioIO/RecordingDevice"), wxT("")); - wxString playDevice = gPrefs->Read(wxT("/AudioIO/PlaybackDevice"), wxT("")); - int j; - - // This gets info on all available audio devices (input and output) - if (cnt <= 0) { - s << XO("No devices found\n"); - return o.GetString(); - } - - const PaDeviceInfo* info; - - for (j = 0; j < cnt; j++) { - s << wxT("==============================\n"); - - info = Pa_GetDeviceInfo(j); - if (!info) { - s << XO("Device info unavailable for: %d\n").Format( j ); - continue; - } - - wxString name = DeviceName(info); - s << XO("Device ID: %d\n").Format( j ); - s << XO("Device name: %s\n").Format( name ); - s << XO("Host name: %s\n").Format( HostName(info) ); - s << XO("Recording channels: %d\n").Format( info->maxInputChannels ); - s << XO("Playback channels: %d\n").Format( info->maxOutputChannels ); - s << XO("Low Recording Latency: %g\n").Format( info->defaultLowInputLatency ); - s << XO("Low Playback Latency: %g\n").Format( info->defaultLowOutputLatency ); - s << XO("High Recording Latency: %g\n").Format( info->defaultHighInputLatency ); - s << XO("High Playback Latency: %g\n").Format( info->defaultHighOutputLatency ); - - auto rates = GetSupportedPlaybackRates(j, 0.0); - - /* i18n-hint: Supported, meaning made available by the system */ - s << XO("Supported Rates:\n"); - for (int k = 0; k < (int) rates.size(); k++) { - s << wxT(" ") << (int)rates[k] << wxT("\n"); - } - - if (name == playDevice && info->maxOutputChannels > 0) - playDeviceNum = j; - - if (name == recDevice && info->maxInputChannels > 0) - recDeviceNum = j; - - // Sometimes PortAudio returns -1 if it cannot find a suitable default - // device, so we just use the first one available - if (recDeviceNum < 0 && info->maxInputChannels > 0){ - recDeviceNum = j; - } - if (playDeviceNum < 0 && info->maxOutputChannels > 0){ - playDeviceNum = j; - } - } - - bool haveRecDevice = (recDeviceNum >= 0); - bool havePlayDevice = (playDeviceNum >= 0); - - s << wxT("==============================\n"); - if (haveRecDevice) - s << XO("Selected recording device: %d - %s\n").Format( recDeviceNum, recDevice ); - else - s << XO("No recording device found for '%s'.\n").Format( recDevice ); - - if (havePlayDevice) - s << XO("Selected playback device: %d - %s\n").Format( playDeviceNum, playDevice ); - else - s << XO("No playback device found for '%s'.\n").Format( playDevice ); - - std::vector supportedSampleRates; - - if (havePlayDevice && haveRecDevice) { - supportedSampleRates = GetSupportedSampleRates(playDeviceNum, recDeviceNum); - - s << XO("Supported Rates:\n"); - for (int k = 0; k < (int) supportedSampleRates.size(); k++) { - s << wxT(" ") << (int)supportedSampleRates[k] << wxT("\n"); - } - } - else { - s << XO("Cannot check mutual sample rates without both devices.\n"); - return o.GetString(); - } - -#if defined(USE_PORTMIXER) - if (supportedSampleRates.size() > 0) - { - int highestSampleRate = supportedSampleRates.back(); - bool EmulateMixerInputVol = true; - bool EmulateMixerOutputVol = true; - float MixerInputVol = 1.0; - float MixerOutputVol = 1.0; - - int error; - - PaStream *stream; - - PaStreamParameters playbackParameters; - - playbackParameters.device = playDeviceNum; - playbackParameters.sampleFormat = paFloat32; - playbackParameters.hostApiSpecificStreamInfo = NULL; - playbackParameters.channelCount = 1; - if (Pa_GetDeviceInfo(playDeviceNum)){ - playbackParameters.suggestedLatency = - Pa_GetDeviceInfo(playDeviceNum)->defaultLowOutputLatency; - } - else{ - playbackParameters.suggestedLatency = DEFAULT_LATENCY_CORRECTION/1000.0; - } - - PaStreamParameters captureParameters; - - captureParameters.device = recDeviceNum; - captureParameters.sampleFormat = paFloat32;; - captureParameters.hostApiSpecificStreamInfo = NULL; - captureParameters.channelCount = 1; - if (Pa_GetDeviceInfo(recDeviceNum)){ - captureParameters.suggestedLatency = - Pa_GetDeviceInfo(recDeviceNum)->defaultLowInputLatency; - }else{ - captureParameters.suggestedLatency = DEFAULT_LATENCY_CORRECTION/1000.0; - } - - // Not really doing I/O so pass nullptr for the callback function - error = Pa_OpenStream(&stream, - &captureParameters, &playbackParameters, - highestSampleRate, paFramesPerBufferUnspecified, - paClipOff | paDitherOff, - nullptr, NULL); - - if (error) { - error = Pa_OpenStream(&stream, - &captureParameters, NULL, - highestSampleRate, paFramesPerBufferUnspecified, - paClipOff | paDitherOff, - nullptr, NULL); - } - - if (error) { - s << XO("Received %d while opening devices\n").Format( error ); - return o.GetString(); - } - - PxMixer *PortMixer = Px_OpenMixer(stream, 0); - - if (!PortMixer) { - s << XO("Unable to open Portmixer\n"); - Pa_CloseStream(stream); - return o.GetString(); - } - - s << wxT("==============================\n"); - s << XO("Available mixers:\n"); - - // FIXME: ? PortMixer errors on query not reported in GetDeviceInfo - cnt = Px_GetNumMixers(stream); - for (int i = 0; i < cnt; i++) { - wxString name = wxSafeConvertMB2WX(Px_GetMixerName(stream, i)); - s << XO("%d - %s\n").Format( i, name ); - } - - s << wxT("==============================\n"); - s << XO("Available recording sources:\n"); - cnt = Px_GetNumInputSources(PortMixer); - for (int i = 0; i < cnt; i++) { - wxString name = wxSafeConvertMB2WX(Px_GetInputSourceName(PortMixer, i)); - s << XO("%d - %s\n").Format( i, name ); - } - - s << wxT("==============================\n"); - s << XO("Available playback volumes:\n"); - cnt = Px_GetNumOutputVolumes(PortMixer); - for (int i = 0; i < cnt; i++) { - wxString name = wxSafeConvertMB2WX(Px_GetOutputVolumeName(PortMixer, i)); - s << XO("%d - %s\n").Format( i, name ); - } - - // Determine mixer capabilities - if it doesn't support either - // input or output, we emulate them (by multiplying this value - // by all incoming/outgoing samples) - - MixerOutputVol = Px_GetPCMOutputVolume(PortMixer); - EmulateMixerOutputVol = false; - Px_SetPCMOutputVolume(PortMixer, 0.0); - if (Px_GetPCMOutputVolume(PortMixer) > 0.1) - EmulateMixerOutputVol = true; - Px_SetPCMOutputVolume(PortMixer, 0.2f); - if (Px_GetPCMOutputVolume(PortMixer) < 0.1 || - Px_GetPCMOutputVolume(PortMixer) > 0.3) - EmulateMixerOutputVol = true; - Px_SetPCMOutputVolume(PortMixer, MixerOutputVol); - - MixerInputVol = Px_GetInputVolume(PortMixer); - EmulateMixerInputVol = false; - Px_SetInputVolume(PortMixer, 0.0); - if (Px_GetInputVolume(PortMixer) > 0.1) - EmulateMixerInputVol = true; - Px_SetInputVolume(PortMixer, 0.2f); - if (Px_GetInputVolume(PortMixer) < 0.1 || - Px_GetInputVolume(PortMixer) > 0.3) - EmulateMixerInputVol = true; - Px_SetInputVolume(PortMixer, MixerInputVol); - - Pa_CloseStream(stream); - - s << wxT("==============================\n"); - s << ( EmulateMixerInputVol - ? XO("Recording volume is emulated\n") - : XO("Recording volume is native\n") ); - s << ( EmulateMixerOutputVol - ? XO("Playback volume is emulated\n") - : XO("Playback volume is native\n") ); - - Px_CloseMixer(PortMixer); - - } //end of massive if statement if a valid sample rate has been found -#endif - return o.GetString(); -} - -#ifdef EXPERIMENTAL_MIDI_OUT -// FIXME: When EXPERIMENTAL_MIDI_IN is added (eventually) this should also be enabled -- Poke -wxString AudioIOBase::GetMidiDeviceInfo() -{ - wxStringOutputStream o; - wxTextOutputStream s(o, wxEOL_UNIX); - - if (IsStreamActive()) { - return XO("Stream is active ... unable to gather information.\n") - .Translation(); - } - - - // XXX: May need to trap errors as with the normal device info - int recDeviceNum = Pm_GetDefaultInputDeviceID(); - int playDeviceNum = Pm_GetDefaultOutputDeviceID(); - int cnt = Pm_CountDevices(); - - // PRL: why only into the log? - wxLogDebug(wxT("PortMidi reports %d MIDI devices"), cnt); - - s << wxT("==============================\n"); - s << XO("Default recording device number: %d\n").Format( recDeviceNum ); - s << XO("Default playback device number: %d\n").Format( playDeviceNum ); - - wxString recDevice = gPrefs->Read(wxT("/MidiIO/RecordingDevice"), wxT("")); - wxString playDevice = gPrefs->Read(wxT("/MidiIO/PlaybackDevice"), wxT("")); - - // This gets info on all available audio devices (input and output) - if (cnt <= 0) { - s << XO("No devices found\n"); - return o.GetString(); - } - - for (int i = 0; i < cnt; i++) { - s << wxT("==============================\n"); - - const PmDeviceInfo* info = Pm_GetDeviceInfo(i); - if (!info) { - s << XO("Device info unavailable for: %d\n").Format( i ); - continue; - } - - wxString name = wxSafeConvertMB2WX(info->name); - wxString hostName = wxSafeConvertMB2WX(info->interf); - - s << XO("Device ID: %d\n").Format( i ); - s << XO("Device name: %s\n").Format( name ); - s << XO("Host name: %s\n").Format( hostName ); - /* i18n-hint: Supported, meaning made available by the system */ - s << XO("Supports output: %d\n").Format( info->output ); - /* i18n-hint: Supported, meaning made available by the system */ - s << XO("Supports input: %d\n").Format( info->input ); - s << XO("Opened: %d\n").Format( info->opened ); - - if (name == playDevice && info->output) - playDeviceNum = i; - - if (name == recDevice && info->input) - recDeviceNum = i; - - // XXX: This is only done because the same was applied with PortAudio - // If PortMidi returns -1 for the default device, use the first one - if (recDeviceNum < 0 && info->input){ - recDeviceNum = i; - } - if (playDeviceNum < 0 && info->output){ - playDeviceNum = i; - } - } - - bool haveRecDevice = (recDeviceNum >= 0); - bool havePlayDevice = (playDeviceNum >= 0); - - s << wxT("==============================\n"); - if (haveRecDevice) - s << XO("Selected MIDI recording device: %d - %s\n").Format( recDeviceNum, recDevice ); - else - s << XO("No MIDI recording device found for '%s'.\n").Format( recDevice ); - - if (havePlayDevice) - s << XO("Selected MIDI playback device: %d - %s\n").Format( playDeviceNum, playDevice ); - else - s << XO("No MIDI playback device found for '%s'.\n").Format( playDevice ); - - // Mention our conditional compilation flags for Alpha only -#ifdef IS_ALPHA - - // Not internationalizing these alpha-only messages - s << wxT("==============================\n"); -#ifdef EXPERIMENTAL_MIDI_OUT - s << wxT("EXPERIMENTAL_MIDI_OUT is enabled\n"); -#else - s << wxT("EXPERIMENTAL_MIDI_OUT is NOT enabled\n"); -#endif -#ifdef EXPERIMENTAL_MIDI_IN - s << wxT("EXPERIMENTAL_MIDI_IN is enabled\n"); -#else - s << wxT("EXPERIMENTAL_MIDI_IN is NOT enabled\n"); -#endif - -#endif - - return o.GetString(); -} -#endif - -bool AudioIOBase::PlaybackSchedule::PassIsComplete() const +bool PlaybackSchedule::PassIsComplete() const { // Test mTime within the PortAudio callback if (Scrubbing()) @@ -1190,7 +142,7 @@ bool AudioIOBase::PlaybackSchedule::PassIsComplete() const return Overruns( GetTrackTime() ); } -bool AudioIOBase::PlaybackSchedule::Overruns( double trackTime ) const +bool PlaybackSchedule::Overruns( double trackTime ) const { return (ReversedTime() ? trackTime <= mT1 : trackTime >= mT1); } @@ -1224,7 +176,7 @@ double SolveWarpedLength(const Envelope &env, double t0, double length) } } -double AudioIOBase::PlaybackSchedule::AdvancedTrackTime( +double PlaybackSchedule::AdvancedTrackTime( double time, double realElapsed, double speed ) const { if (ReversedTime()) @@ -1247,7 +199,7 @@ double AudioIOBase::PlaybackSchedule::AdvancedTrackTime( else time = SolveWarpedLength(*mEnvelope, time, realElapsed); - if (!Looping() || !Overruns( time )) + if (!Looping() || !Overruns( time )) break; // Bug1922: The part of the time track outside the loop should not @@ -1282,7 +234,7 @@ double AudioIOBase::PlaybackSchedule::AdvancedTrackTime( return time; } -void AudioIOBase::PlaybackSchedule::TrackTimeUpdate(double realElapsed) +void PlaybackSchedule::TrackTimeUpdate(double realElapsed) { // Update mTime within the PortAudio callback @@ -1294,7 +246,7 @@ void AudioIOBase::PlaybackSchedule::TrackTimeUpdate(double realElapsed) SetTrackTime( newTime ); } -double AudioIOBase::PlaybackSchedule::RealDuration(double trackTime1) const +double PlaybackSchedule::RealDuration(double trackTime1) const { double duration; if (mEnvelope) @@ -1304,17 +256,17 @@ double AudioIOBase::PlaybackSchedule::RealDuration(double trackTime1) const return fabs(duration); } -double AudioIOBase::PlaybackSchedule::RealTimeRemaining() const +double PlaybackSchedule::RealTimeRemaining() const { return mWarpedLength - mWarpedTime; } -void AudioIOBase::PlaybackSchedule::RealTimeAdvance( double increment ) +void PlaybackSchedule::RealTimeAdvance( double increment ) { mWarpedTime += increment; } -void AudioIOBase::PlaybackSchedule::RealTimeInit( double trackTime ) +void PlaybackSchedule::RealTimeInit( double trackTime ) { if (Scrubbing()) mWarpedTime = 0.0; @@ -1322,22 +274,22 @@ void AudioIOBase::PlaybackSchedule::RealTimeInit( double trackTime ) mWarpedTime = RealDuration( trackTime ); } -void AudioIOBase::PlaybackSchedule::RealTimeRestart() +void PlaybackSchedule::RealTimeRestart() { mWarpedTime = 0; } -double AudioIOBase::RecordingSchedule::ToConsume() const +double RecordingSchedule::ToConsume() const { return mDuration - Consumed(); } -double AudioIOBase::RecordingSchedule::Consumed() const +double RecordingSchedule::Consumed() const { return std::max( 0.0, mPosition + TotalCorrection() ); } -double AudioIOBase::RecordingSchedule::ToDiscard() const +double RecordingSchedule::ToDiscard() const { return std::max(0.0, -( mPosition + TotalCorrection() ) ); } diff --git a/src/PlaybackSchedule.h b/src/PlaybackSchedule.h index 3268a72c1..8ddfd6782 100644 --- a/src/PlaybackSchedule.h +++ b/src/PlaybackSchedule.h @@ -1,486 +1,167 @@ /********************************************************************** + + Audacity: A Digital Audio Editor + + PlaybackSchedule.h + + Paul Licameli split from AudioIOBase.h + + **********************************************************************/ -Audacity: A Digital Audio Editor - -AudioIOBase.h - -Paul Licameli split from AudioIO.h - -**********************************************************************/ - -#ifndef __AUDACITY_AUDIO_IO_BASE__ -#define __AUDACITY_AUDIO_IO_BASE__ - - - +#ifndef __AUDACITY_PLAYBACK_SCHEDULE__ +#define __AUDACITY_PLAYBACK_SCHEDULE__ #include -#include -#include -#include #include -#include -#include // member variable -struct PaDeviceInfo; -typedef void PaStream; - -#if USE_PORTMIXER -typedef void PxMixer; -#endif - -class AudioIOBase; - -class AudacityProject; -class AudioIOListener; +struct AudioIOStartStreamOptions; class BoundedEnvelope; -// Windows build needs complete type for parameter of wxWeakRef -// class MeterPanelBase; -#include "widgets/MeterPanelBase.h" using PRCrossfadeData = std::vector< std::vector < float > >; -#define BAD_STREAM_TIME (-DBL_MAX) +struct RecordingSchedule { + double mPreRoll{}; + double mLatencyCorrection{}; // negative value usually + double mDuration{}; + PRCrossfadeData mCrossfadeData; -// For putting an increment of work in the scrubbing queue -struct ScrubbingOptions { - ScrubbingOptions() {} + // These are initialized by the main thread, then updated + // only by the thread calling FillBuffers: + double mPosition{}; + bool mLatencyCorrected{}; - bool adjustStart {}; - - // usually from TrackList::GetEndTime() - double maxTime {}; - double minTime {}; - - bool bySpeed {}; - bool isPlayingAtSpeed{}; - bool isKeyboardScrubbing{}; - - double delay {}; - - // Initial and limiting values for the speed of a scrub interval: - double initSpeed { 1.0 }; - double minSpeed { 0.0 }; - double maxSpeed { 1.0 }; - - - // When maximum speed scrubbing skips to follow the mouse, - // this is the minimum amount of playback allowed at the maximum speed: - double minStutterTime {}; - - static double MaxAllowedScrubSpeed() - { return 32.0; } // Is five octaves enough for your amusement? - static double MinAllowedScrubSpeed() - { return 0.01; } // Mixer needs a lower bound speed. Scrub no slower than this. + double TotalCorrection() const { return mLatencyCorrection - mPreRoll; } + double ToConsume() const; + double Consumed() const; + double ToDiscard() const; }; -// To avoid growing the argument list of StartStream, add fields here -struct AudioIOStartStreamOptions -{ - explicit - AudioIOStartStreamOptions(AudacityProject *pProject_, double rate_) - : pProject{ pProject_ } - , envelope(nullptr) - , rate(rate_) - , playLooped(false) - , cutPreviewGapStart(0.0) - , cutPreviewGapLen(0.0) - , pStartTime(NULL) - , preRoll(0.0) - {} +struct AUDACITY_DLL_API PlaybackSchedule { + /// Playback starts at offset of mT0, which is measured in seconds. + double mT0; + /// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing. + double mT1; + /// Current track time position during playback, in seconds. + /// Initialized by the main thread but updated by worker threads during + /// playback or recording, and periodically reread by the main thread for + /// purposes such as display update. + std::atomic mTime; - AudacityProject *pProject{}; - MeterPanelBase *captureMeter{}, *playbackMeter{}; - const BoundedEnvelope *envelope; // for time warping - std::shared_ptr< AudioIOListener > listener; - double rate; - bool playLooped; - double cutPreviewGapStart; - double cutPreviewGapLen; - double * pStartTime; - double preRoll; + /// Accumulated real time (not track position), starting at zero (unlike + /// mTime), and wrapping back to zero each time around looping play. + /// Thus, it is the length in real seconds between mT0 and mTime. + double mWarpedTime; + /// Real length to be played (if looping, for each pass) after warping via a + /// time track, computed just once when starting the stream. + /// Length in real seconds between mT0 and mT1. Always positive. + double mWarpedLength; + + // mWarpedTime and mWarpedLength are irrelevant when scrubbing, + // else they are used in updating mTime, + // and when not scrubbing or playing looped, mTime is also used + // in the test for termination of playback. + + // with ComputeWarpedLength, it is now possible the calculate the warped length with 100% accuracy + // (ignoring accumulated rounding errors during playback) which fixes the 'missing sound at the end' bug + + const BoundedEnvelope *mEnvelope; + + volatile enum { + PLAY_STRAIGHT, + PLAY_LOOPED, #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - // Non-null value indicates that scrubbing will happen - // (do not specify a time track, looping, or recording, which - // are all incompatible with scrubbing): - ScrubbingOptions *pScrubbingOptions {}; + PLAY_SCRUB, + PLAY_AT_SPEED, // a version of PLAY_SCRUB. + PLAY_KEYBOARD_SCRUB, #endif + } mPlayMode { PLAY_STRAIGHT }; + double mCutPreviewGapStart; + double mCutPreviewGapLen; - // contents may get swapped with empty vector - PRCrossfadeData *pCrossfadeData{}; + void Init( + double t0, double t1, + const AudioIOStartStreamOptions &options, + const RecordingSchedule *pRecordingSchedule ); - // An unfortunate thing needed just to make scrubbing work on Linux when - // we can't use a separate polling thread. - // The return value is a number of milliseconds to sleep before calling again - std::function< unsigned long() > playbackStreamPrimer; -}; + /** \brief True if the end time is before the start time */ + bool ReversedTime() const + { + return mT1 < mT0; + } -///\brief A singleton object supporting queries of the state of any active -/// audio streams, and audio device capabilities -class AUDACITY_DLL_API AudioIOBase /* not final */ -{ -public: - static AudioIOBase *Get(); - - virtual ~AudioIOBase(); - - void SetCaptureMeter(AudacityProject *project, MeterPanelBase *meter); - void SetPlaybackMeter(AudacityProject *project, MeterPanelBase *meter); - - /** \brief update state after changing what audio devices are selected + /** \brief Get current track time value, unadjusted * - * 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 explicitly 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. + * Returns a time in seconds. */ - static std::vector GetSupportedPlaybackRates(int DevIndex = -1, - double rate = 0.0); + double GetTrackTime() const + { return mTime.load(std::memory_order_relaxed); } - /** \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 explicitly 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. + /** \brief Set current track time value, unadjusted */ - static std::vector GetSupportedCaptureRates(int devIndex = -1, - double rate = 0.0); + void SetTrackTime( double time ) + { mTime.store(time, std::memory_order_relaxed); } - /** \brief Get a list of sample rates the current input/output device - * combination supports. + /** \brief Clamps argument to be between mT0 and mT1 * - * 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 explicitly 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. + * Returns the bound if the value is out of bounds; does not wrap. + * Returns a time in seconds. */ - static std::vector GetSupportedSampleRates(int playDevice = -1, - int recDevice = -1, - double rate = 0.0); + double ClampTrackTime( double trackTime ) const; - /** \brief Get a supported sample rate which can be used a an optimal - * default. + /** \brief Clamps mTime to be between mT0 and mT1 * - * 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 + * Returns the bound if the value is out of bounds; does not wrap. + * Returns a time in seconds. */ - static int GetOptimalSupportedSampleRate(); + double LimitTrackTime() 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 + /** \brief Normalizes mTime, clamping it and handling gaps from cut preview. * + * Clamps the time (unless scrubbing), and skips over the cut section. + * Returns a time in seconds. */ - wxString GetDeviceInfo(); + double NormalizeTrackTime() const; -#ifdef EXPERIMENTAL_MIDI_OUT - /** \brief Get diagnostic information on all the available MIDI I/O devices */ - wxString GetMidiDeviceInfo(); -#endif + void ResetMode() { mPlayMode = PLAY_STRAIGHT; } - /** \brief Find out if playback / recording is currently paused */ - bool IsPaused() const; + bool PlayingStraight() const { return mPlayMode == PLAY_STRAIGHT; } + bool Looping() const { return mPlayMode == PLAY_LOOPED; } + bool Scrubbing() const { return mPlayMode == PLAY_SCRUB || mPlayMode == PLAY_KEYBOARD_SCRUB; } + bool PlayingAtSpeed() const { return mPlayMode == PLAY_AT_SPEED; } + bool Interactive() const { return Scrubbing() || PlayingAtSpeed(); } - virtual void StopStream() = 0; + // Returns true if a loop pass, or the sole pass of straight play, + // is completed at the current value of mTime + bool PassIsComplete() const; - /** \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; + // Returns true if time equals t1 or is on opposite side of t1, to t0 + bool Overruns( double trackTime ) 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; + // Compute the NEW track time for the given one and a real duration, + // taking into account whether the schedule is for looping + double AdvancedTrackTime( + double trackTime, double realElapsed, double speed) const; - /** \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; + // Use the function above in the callback after consuming samples from the + // playback ring buffers, during usual straight or looping play + void TrackTimeUpdate(double realElapsed); - /** \brief Returns true if we're monitoring input (but not recording or - * playing actual audio) */ - bool IsMonitoring() const; + // Convert time between mT0 and argument to real duration, according to + // time track if one is given; result is always nonnegative + double RealDuration(double trackTime1) 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); + // How much real time left? + double RealTimeRemaining() const; -protected: - static std::unique_ptr ugAudioIO; - static wxString DeviceName(const PaDeviceInfo* info); - static wxString HostName(const PaDeviceInfo* info); + // Advance the real time position + void RealTimeAdvance( double increment ); - AudacityProject *mOwningProject; + // Determine starting duration within the first pass -- sometimes not + // zero + void RealTimeInit( double trackTime ); + + void RealTimeRestart(); - /// True if audio playback is paused - bool mPaused; - - /// True when output reaches mT1 - bool mMidiOutputComplete{ true }; - - /// mMidiStreamActive tells when mMidiStream is open for output - bool mMidiStreamActive; - - volatile int mStreamToken; - - /// Audio playback rate in samples per second - double mRate; - - PaStream *mPortStreamV19; - - wxWeakRef mInputMeter{}; - wxWeakRef mOutputMeter{}; - - #if USE_PORTMIXER - PxMixer *mPortMixer; - float mPreviousHWPlaythrough; - #endif /* USE_PORTMIXER */ - - bool mEmulateMixerOutputVol; - /** @brief Can we control the hardware input level? - * - * This flag is set to true if using portmixer to control the - * input volume seems to be working (and so we offer the user the control), - * and to false (locking the control out) otherwise. This avoids stupid - * scaled clipping problems when trying to do software emulated input volume - * control */ - bool mInputMixerWorks; - float mMixerOutputVol; - - // For cacheing supported sample rates - static int mCachedPlaybackIndex; - static std::vector mCachedPlaybackRates; - static int mCachedCaptureIndex; - static std::vector mCachedCaptureRates; - static std::vector mCachedSampleRates; - static double mCachedBestRateIn; - - struct RecordingSchedule { - double mPreRoll{}; - double mLatencyCorrection{}; // negative value usually - double mDuration{}; - PRCrossfadeData mCrossfadeData; - - // These are initialized by the main thread, then updated - // only by the thread calling FillBuffers: - double mPosition{}; - bool mLatencyCorrected{}; - - double TotalCorrection() const { return mLatencyCorrection - mPreRoll; } - double ToConsume() const; - double Consumed() const; - double ToDiscard() const; - }; - - struct PlaybackSchedule { - /// Playback starts at offset of mT0, which is measured in seconds. - double mT0; - /// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing. - double mT1; - /// Current track time position during playback, in seconds. - /// Initialized by the main thread but updated by worker threads during - /// playback or recording, and periodically reread by the main thread for - /// purposes such as display update. - std::atomic mTime; - - /// Accumulated real time (not track position), starting at zero (unlike - /// mTime), and wrapping back to zero each time around looping play. - /// Thus, it is the length in real seconds between mT0 and mTime. - double mWarpedTime; - - /// Real length to be played (if looping, for each pass) after warping via a - /// time track, computed just once when starting the stream. - /// Length in real seconds between mT0 and mT1. Always positive. - double mWarpedLength; - - // mWarpedTime and mWarpedLength are irrelevant when scrubbing, - // else they are used in updating mTime, - // and when not scrubbing or playing looped, mTime is also used - // in the test for termination of playback. - - // with ComputeWarpedLength, it is now possible the calculate the warped length with 100% accuracy - // (ignoring accumulated rounding errors during playback) which fixes the 'missing sound at the end' bug - - const BoundedEnvelope *mEnvelope; - - volatile enum { - PLAY_STRAIGHT, - PLAY_LOOPED, -#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - PLAY_SCRUB, - PLAY_AT_SPEED, // a version of PLAY_SCRUB. - PLAY_KEYBOARD_SCRUB, -#endif - } mPlayMode { PLAY_STRAIGHT }; - double mCutPreviewGapStart; - double mCutPreviewGapLen; - - void Init( - double t0, double t1, - const AudioIOStartStreamOptions &options, - const RecordingSchedule *pRecordingSchedule ); - - /** \brief True if the end time is before the start time */ - bool ReversedTime() const - { - return mT1 < mT0; - } - - /** \brief Get current track time value, unadjusted - * - * Returns a time in seconds. - */ - double GetTrackTime() const - { return mTime.load(std::memory_order_relaxed); } - - /** \brief Set current track time value, unadjusted - */ - void SetTrackTime( double time ) - { mTime.store(time, std::memory_order_relaxed); } - - /** \brief Clamps argument to be between mT0 and mT1 - * - * Returns the bound if the value is out of bounds; does not wrap. - * Returns a time in seconds. - */ - double ClampTrackTime( double trackTime ) const; - - /** \brief Clamps mTime to be between mT0 and mT1 - * - * Returns the bound if the value is out of bounds; does not wrap. - * Returns a time in seconds. - */ - double LimitTrackTime() const; - - /** \brief Normalizes mTime, clamping it and handling gaps from cut preview. - * - * Clamps the time (unless scrubbing), and skips over the cut section. - * Returns a time in seconds. - */ - double NormalizeTrackTime() const; - - void ResetMode() { mPlayMode = PLAY_STRAIGHT; } - - bool PlayingStraight() const { return mPlayMode == PLAY_STRAIGHT; } - bool Looping() const { return mPlayMode == PLAY_LOOPED; } - bool Scrubbing() const { return mPlayMode == PLAY_SCRUB || mPlayMode == PLAY_KEYBOARD_SCRUB; } - bool PlayingAtSpeed() const { return mPlayMode == PLAY_AT_SPEED; } - bool Interactive() const { return Scrubbing() || PlayingAtSpeed(); } - - // Returns true if a loop pass, or the sole pass of straight play, - // is completed at the current value of mTime - bool PassIsComplete() const; - - // Returns true if time equals t1 or is on opposite side of t1, to t0 - bool Overruns( double trackTime ) const; - - // Compute the NEW track time for the given one and a real duration, - // taking into account whether the schedule is for looping - double AdvancedTrackTime( - double trackTime, double realElapsed, double speed) const; - - // Use the function above in the callback after consuming samples from the - // playback ring buffers, during usual straight or looping play - void TrackTimeUpdate(double realElapsed); - - // Convert time between mT0 and argument to real duration, according to - // time track if one is given; result is always nonnegative - double RealDuration(double trackTime1) const; - - // How much real time left? - double RealTimeRemaining() const; - - // Advance the real time position - void RealTimeAdvance( double increment ); - - // Determine starting duration within the first pass -- sometimes not - // zero - void RealTimeInit( double trackTime ); - - void RealTimeRestart(); - - }; - - /** \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 = {}); - - /** \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 = {}); - - /** \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; }; #endif