diff --git a/include/AudioDevice.h b/include/AudioDevice.h index dcde7d161..b7b1fa71f 100644 --- a/include/AudioDevice.h +++ b/include/AudioDevice.h @@ -113,6 +113,11 @@ protected: m_sampleRate = _new_sr; } + void setChannels(const ch_cnt_t channels) + { + m_channels = channels; + } + AudioEngine* audioEngine() { return m_audioEngine; diff --git a/include/AudioPortAudio.h b/include/AudioPortAudio.h index 271ce0941..004f1d8af 100644 --- a/include/AudioPortAudio.h +++ b/include/AudioPortAudio.h @@ -25,136 +25,91 @@ #ifndef LMMS_AUDIO_PORTAUDIO_H #define LMMS_AUDIO_PORTAUDIO_H -#include - #include "lmmsconfig.h" -#include "ComboBoxModel.h" #ifdef LMMS_HAVE_PORTAUDIO -# include +#include +#include +#include +#include +#include -# include "AudioDevice.h" -# include "AudioDeviceSetupWidget.h" +#include "AudioDevice.h" +#include "AudioDeviceSetupWidget.h" -# if defined paNeverDropInput || defined paNonInterleaved -# define PORTAUDIO_V19 -# else -# define PORTAUDIO_V18 -# endif +namespace lmms { -#endif - - -namespace lmms +namespace detail { +class PortAudioInitializationGuard { - -class AudioPortAudioSetupUtil : public QObject -{ -Q_OBJECT -public slots: - void updateBackends(); - void updateDevices(); - void updateChannels(); - public: - ComboBoxModel m_backendModel; - ComboBoxModel m_deviceModel; + PortAudioInitializationGuard() + : m_error(Pa_Initialize()) + { + if (m_error != paNoError) { throw std::runtime_error{"PortAudio: could not initialize"}; } + } + + ~PortAudioInitializationGuard() + { + if (m_error == paNoError) { Pa_Terminate(); } + } + + PortAudioInitializationGuard(const PortAudioInitializationGuard&) = delete; + PortAudioInitializationGuard(PortAudioInitializationGuard&&) = delete; + PortAudioInitializationGuard& operator=(const PortAudioInitializationGuard&) = delete; + PortAudioInitializationGuard& operator=(PortAudioInitializationGuard&&) = delete; + +private: + PaError m_error = paNoError; }; - - -#ifdef LMMS_HAVE_PORTAUDIO - - -namespace gui -{ -class ComboBox; -} - +} // namespace detail class AudioPortAudio : public AudioDevice { public: - AudioPortAudio( bool & _success_ful, AudioEngine* audioEngine ); + AudioPortAudio(bool& successful, AudioEngine* engine); ~AudioPortAudio() override; - inline static QString name() - { - return QT_TRANSLATE_NOOP( "AudioDeviceSetupWidget", "PortAudio" ); - } + AudioPortAudio(const AudioPortAudio&) = delete; + AudioPortAudio(AudioPortAudio&&) = delete; + AudioPortAudio& operator=(const AudioPortAudio&) = delete; + AudioPortAudio& operator=(AudioPortAudio&&) = delete; - int process_callback(const float* _inputBuffer, float* _outputBuffer, f_cnt_t _framesPerBuffer); - - class setupWidget : public gui::AudioDeviceSetupWidget - { - public: - setupWidget( QWidget * _parent ); - ~setupWidget() override; - - void saveSettings() override; - void show() override; - - private: - gui::ComboBox * m_backend; - gui::ComboBox * m_device; - AudioPortAudioSetupUtil m_setupUtil; - - } ; - -private: void startProcessing() override; void stopProcessing() override; -#ifdef PORTAUDIO_V19 - static int _process_callback( const void *_inputBuffer, void * _outputBuffer, - unsigned long _framesPerBuffer, - const PaStreamCallbackTimeInfo * _timeInfo, - PaStreamCallbackFlags _statusFlags, - void *arg ); + static auto name() -> QString { return QT_TRANSLATE_NOOP("AudioDeviceSetupWidget", "PortAudio"); } -#else +private: + static int processCallback(const void* input, void* output, unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData); -#define paContinue 0 -#define paComplete 1 -#define Pa_GetDeviceCount Pa_CountDevices -#define Pa_GetDefaultInputDevice Pa_GetDefaultInputDeviceID -#define Pa_GetDefaultOutputDevice Pa_GetDefaultOutputDeviceID -#define Pa_IsStreamActive Pa_StreamActive + detail::PortAudioInitializationGuard m_initGuard; - static int _process_callback( void * _inputBuffer, void * _outputBuffer, - unsigned long _framesPerBuffer, PaTimestamp _outTime, void * _arg ); + PaStream* m_paStream = nullptr; + std::vector m_outBuf; + std::size_t m_outBufPos = 0; +}; +} // namespace lmms +namespace lmms::gui { +class AudioPortAudioSetupWidget : public AudioDeviceSetupWidget +{ +public: + AudioPortAudioSetupWidget(QWidget* parent); - using PaTime = double; - using PaDeviceIndex = PaDeviceID; + void show() override; + void saveSettings() override; - using PaStreamParameters = struct - { - PaDeviceIndex device; - int channelCount; - PaSampleFormat sampleFormat; - PaTime suggestedLatency; - void *hostApiSpecificStreamInfo; - - } PaStreamParameters; -#endif // PORTAUDIO_V19 - - PaStream * m_paStream; - PaStreamParameters m_outputParameters; - PaStreamParameters m_inputParameters; - - bool m_wasPAInitError; - - SampleFrame* m_outBuf; - std::size_t m_outBufPos; - fpp_t m_outBufSize; - - bool m_stopped; - -} ; +private: + class DeviceSelectorWidget; + QComboBox* m_backendComboBox = nullptr; + DeviceSelectorWidget* m_inputDevice = nullptr; + DeviceSelectorWidget* m_outputDevice = nullptr; +}; +} // namespace lmms::gui #endif // LMMS_HAVE_PORTAUDIO -} // namespace lmms - #endif // LMMS_AUDIO_PORTAUDIO_H diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index d630e4285..c06536b4b 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -23,6 +23,7 @@ */ #include "AudioEngine.h" +#include #include "MixHelpers.h" #include "denormals.h" @@ -878,7 +879,7 @@ AudioDevice * AudioEngine::tryAudioDevices() #ifdef LMMS_HAVE_PORTAUDIO - if( dev_name == AudioPortAudio::name() || dev_name == "" ) + if (dev_name == AudioPortAudio::name() || dev_name.isEmpty()) { dev = new AudioPortAudio( success_ful, this ); if( success_ful ) diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index 1f7009069..b260fdadf 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -23,443 +23,314 @@ * */ +#include -#include "AudioPortAudio.h" - -#ifndef LMMS_HAVE_PORTAUDIO -namespace lmms -{ - - -void AudioPortAudioSetupUtil::updateBackends() -{ -} - -void AudioPortAudioSetupUtil::updateDevices() -{ -} - -void AudioPortAudioSetupUtil::updateChannels() -{ -} - - -} // namespace lmms -#endif +#include "lmmsconfig.h" #ifdef LMMS_HAVE_PORTAUDIO -#include - -#include "ConfigManager.h" -#include "ComboBox.h" #include "AudioEngine.h" +#include "AudioPortAudio.h" +#include "ConfigManager.h" +#include "LcdSpinBox.h" -namespace lmms +namespace { +enum class Direction { + Input, + Output +}; - -AudioPortAudio::AudioPortAudio( bool & _success_ful, AudioEngine * _audioEngine ) : - AudioDevice(std::clamp( - ConfigManager::inst()->value("audioportaudio", "channels").toInt(), - DEFAULT_CHANNELS, - DEFAULT_CHANNELS), _audioEngine), - m_paStream( nullptr ), - m_wasPAInitError( false ), - m_outBuf(new SampleFrame[audioEngine()->framesPerPeriod()]), - m_outBufPos( 0 ) +constexpr auto tag() { - _success_ful = false; - - m_outBufSize = audioEngine()->framesPerPeriod(); - - PaError err = Pa_Initialize(); - - if( err != paNoError ) { - printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); - m_wasPAInitError = true; - return; - } - - if( Pa_GetDeviceCount() <= 0 ) - { - return; - } - - const QString& backend = ConfigManager::inst()->value( "audioportaudio", "backend" ); - const QString& device = ConfigManager::inst()->value( "audioportaudio", "device" ); - - PaDeviceIndex inDevIdx = -1; - PaDeviceIndex outDevIdx = -1; - for( int i = 0; i < Pa_GetDeviceCount(); ++i ) - { - const auto di = Pa_GetDeviceInfo(i); - if( di->name == device && - Pa_GetHostApiInfo( di->hostApi )->name == backend ) - { - inDevIdx = i; - outDevIdx = i; - } - } - - if( inDevIdx < 0 ) - { - inDevIdx = Pa_GetDefaultInputDevice(); - } - - if( outDevIdx < 0 ) - { - outDevIdx = Pa_GetDefaultOutputDevice(); - } - - if( inDevIdx < 0 || outDevIdx < 0 ) - { - return; - } - - double inLatency = 0;//(double)audioEngine()->framesPerPeriod() / (double)sampleRate(); - double outLatency = 0;//(double)audioEngine()->framesPerPeriod() / (double)sampleRate(); - - //inLatency = Pa_GetDeviceInfo( inDevIdx )->defaultLowInputLatency; - //outLatency = Pa_GetDeviceInfo( outDevIdx )->defaultLowOutputLatency; - const int samples = audioEngine()->framesPerPeriod(); - - // Configure output parameters. - m_outputParameters.device = outDevIdx; - m_outputParameters.channelCount = channels(); - m_outputParameters.sampleFormat = paFloat32; // 32 bit floating point output - m_outputParameters.suggestedLatency = outLatency; - m_outputParameters.hostApiSpecificStreamInfo = nullptr; - - // Configure input parameters. - m_inputParameters.device = inDevIdx; - m_inputParameters.channelCount = DEFAULT_CHANNELS; - m_inputParameters.sampleFormat = paFloat32; // 32 bit floating point input - m_inputParameters.suggestedLatency = inLatency; - m_inputParameters.hostApiSpecificStreamInfo = nullptr; - - // Open an audio I/O stream. - err = Pa_OpenStream( - &m_paStream, - supportsCapture() ? &m_inputParameters : nullptr, // The input parameter - &m_outputParameters, // The outputparameter - sampleRate(), - samples, - paNoFlag, // Don't use any flags - _process_callback, // our callback function - this ); - - if( err == paInvalidDevice && sampleRate() < 48000 ) - { - printf("Pa_OpenStream() failed with 44,1 KHz, trying again with 48 KHz\n"); - // some backends or drivers do not allow 32 bit floating point data - // with a samplerate of 44100 Hz - setSampleRate( 48000 ); - err = Pa_OpenStream( - &m_paStream, - supportsCapture() ? &m_inputParameters : nullptr, // The input parameter - &m_outputParameters, // The outputparameter - sampleRate(), - samples, - paNoFlag, // Don't use any flags - _process_callback, // our callback function - this ); - } - - if( err != paNoError ) - { - printf( "Couldn't open PortAudio: %s\n", Pa_GetErrorText( err ) ); - return; - } - - printf( "Input device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( inDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( inDevIdx )->hostApi )->name ); - printf( "Output device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( outDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( outDevIdx )->hostApi )->name ); - - // TODO: debug AudioEngine::pushInputFrames() - //m_supportsCapture = true; - - _success_ful = true; + return "audioportaudio"; } +constexpr auto backendAttribute() +{ + return "backend"; +} +constexpr auto deviceNameAttribute(Direction direction) +{ + switch (direction) + { + case Direction::Input: + return "inputdevice"; + case Direction::Output: + return "outputdevice"; + } + return ""; +} + +constexpr auto channelsAttribute(Direction direction) +{ + switch (direction) + { + case Direction::Input: + return "inputchannels"; + case Direction::Output: + return "outputchannels"; + } + + return ""; +} + +int maxChannels(const PaDeviceInfo* info, Direction direction) +{ + switch (direction) + { + case Direction::Input: + return info->maxInputChannels; + case Direction::Output: + return info->maxOutputChannels; + } + + return 0; +} + +} // namespace + +namespace lmms { +AudioPortAudio::AudioPortAudio(bool& successful, AudioEngine* engine) + : AudioDevice(DEFAULT_CHANNELS, engine) + , m_outBuf(engine->framesPerPeriod()) +{ + const auto numDevices = Pa_GetDeviceCount(); + if (numDevices < 0) + { + std::cerr << "Pa_GetDeviceCount() failed: " << Pa_GetErrorText(numDevices) << '\n'; + successful = false; + return; + } + + const auto backend = ConfigManager::inst()->value(tag(), backendAttribute()); + + const auto inputDeviceName = ConfigManager::inst()->value(tag(), deviceNameAttribute(Direction::Input)); + const auto inputDeviceChannels = ConfigManager::inst()->value(tag(), channelsAttribute(Direction::Input)).toInt(); + + const auto outputDeviceName = ConfigManager::inst()->value(tag(), deviceNameAttribute(Direction::Output)); + const auto outputDeviceChannels = ConfigManager::inst()->value(tag(), channelsAttribute(Direction::Output)).toInt(); + + auto inputDeviceIndex = paNoDevice; + auto outputDeviceIndex = paNoDevice; + + for (auto i = 0; i < numDevices && (inputDeviceIndex == paNoDevice || outputDeviceIndex == paNoDevice); ++i) + { + const auto deviceInfo = Pa_GetDeviceInfo(i); + const auto hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi); + if (deviceInfo->name == inputDeviceName && hostApiInfo->name == backend) { inputDeviceIndex = i; } + if (deviceInfo->name == outputDeviceName && hostApiInfo->name == backend) { outputDeviceIndex = i; } + } + + const auto sampleRate = engine->baseSampleRate(); + const auto framesPerBuffer = engine->framesPerPeriod(); + const auto inputLatency + = inputDeviceIndex == paNoDevice ? 0. : Pa_GetDeviceInfo(inputDeviceIndex)->defaultLowInputLatency; + const auto outputLatency + = outputDeviceIndex == paNoDevice ? 0. : Pa_GetDeviceInfo(outputDeviceIndex)->defaultLowOutputLatency; + + const auto inputParameters = PaStreamParameters { + .device = inputDeviceIndex, + .channelCount = inputDeviceChannels, + .sampleFormat = paFloat32, + .suggestedLatency = inputLatency, + .hostApiSpecificStreamInfo = nullptr + }; + + const auto outputParameters = PaStreamParameters { + .device = outputDeviceIndex, + .channelCount = outputDeviceChannels, + .sampleFormat = paFloat32, + .suggestedLatency = outputLatency, + .hostApiSpecificStreamInfo = nullptr + }; + + const auto inputParametersPtr = inputDeviceIndex == paNoDevice ? nullptr : &inputParameters; + const auto outputParametersPtr = outputDeviceIndex == paNoDevice ? nullptr : &outputParameters; + auto err = Pa_IsFormatSupported(inputParametersPtr, outputParametersPtr, sampleRate); + + if (err != paFormatIsSupported) + { + std::cerr << "Pa_IsFormatSupported() failed: " << Pa_GetErrorText(err) << '\n'; + successful = false; + return; + } + + err = Pa_OpenStream(&m_paStream, inputParametersPtr, outputParametersPtr, sampleRate, framesPerBuffer, paNoFlag, + &AudioPortAudio::processCallback, this); + + if (err != paNoError) + { + std::cerr << "Pa_OpenStream() failed: " << Pa_GetErrorText(err) << '\n'; + successful = false; + return; + } + + successful = true; + setSampleRate(sampleRate); + setChannels(outputDeviceChannels); +} AudioPortAudio::~AudioPortAudio() { stopProcessing(); - - if( !m_wasPAInitError ) - { - Pa_Terminate(); - } - delete[] m_outBuf; + Pa_CloseStream(m_paStream); } - - - void AudioPortAudio::startProcessing() { - m_stopped = false; - PaError err = Pa_StartStream( m_paStream ); - - if( err != paNoError ) - { - m_stopped = true; - printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) ); - } + Pa_StartStream(m_paStream); } - - - void AudioPortAudio::stopProcessing() { - if( m_paStream && Pa_IsStreamActive( m_paStream ) ) - { - m_stopped = true; - PaError err = Pa_StopStream( m_paStream ); - - if( err != paNoError ) - { - printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) ); - } - } + Pa_StopStream(m_paStream); } - -int AudioPortAudio::process_callback(const float* _inputBuffer, float* _outputBuffer, f_cnt_t _framesPerBuffer) +int AudioPortAudio::processCallback(const void*, void* output, unsigned long frameCount, + const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* userData) { - if( supportsCapture() ) - { - audioEngine()->pushInputFrames( (SampleFrame*)_inputBuffer, _framesPerBuffer ); - } + const auto device = static_cast(userData); - if( m_stopped ) + const auto outputBuffer = static_cast(output); + for (auto frame = std::size_t{0}; frame < frameCount; ++frame) { - memset( _outputBuffer, 0, _framesPerBuffer * - channels() * sizeof(float) ); - return paComplete; - } - - while( _framesPerBuffer ) - { - if( m_outBufPos == 0 ) + if (device->m_outBufPos == 0 && device->getNextBuffer(device->m_outBuf.data()) == 0) { - // frames depend on the sample rate - const fpp_t frames = getNextBuffer( m_outBuf ); - if( !frames ) - { - m_stopped = true; - memset( _outputBuffer, 0, _framesPerBuffer * - channels() * sizeof(float) ); - return paComplete; - } - m_outBufSize = frames; - } - const auto min_len = std::min(_framesPerBuffer, m_outBufSize - m_outBufPos); - - for( fpp_t frame = 0; frame < min_len; ++frame ) - { - for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) - { - (_outputBuffer + frame * channels())[chnl] = AudioEngine::clip(m_outBuf[frame][chnl]); - } + std::fill(outputBuffer + frame * device->channels(), outputBuffer + frameCount * device->channels(), 0.f); + return paComplete; } - _outputBuffer += min_len * channels(); - _framesPerBuffer -= min_len; - m_outBufPos += min_len; - m_outBufPos %= m_outBufSize; + if (device->channels() == 1) + { + outputBuffer[frame] = device->m_outBuf[device->m_outBufPos].average(); + } + else + { + outputBuffer[frame * device->channels()] = device->m_outBuf[device->m_outBufPos][0]; + outputBuffer[frame * device->channels() + 1] = device->m_outBuf[device->m_outBufPos][1]; + } + + device->m_outBufPos = (device->m_outBufPos + 1) % device->m_outBuf.size(); } return paContinue; } +} // namespace lmms +namespace lmms::gui { - -int AudioPortAudio::_process_callback( - const void *_inputBuffer, - void * _outputBuffer, - unsigned long _framesPerBuffer, - const PaStreamCallbackTimeInfo * _timeInfo, - PaStreamCallbackFlags _statusFlags, - void * _arg ) +class AudioPortAudioSetupWidget::DeviceSelectorWidget : public QGroupBox { - Q_UNUSED(_timeInfo); - Q_UNUSED(_statusFlags); - - auto _this = static_cast(_arg); - return _this->process_callback( (const float*)_inputBuffer, - (float*)_outputBuffer, _framesPerBuffer ); -} - - - - -void AudioPortAudioSetupUtil::updateBackends() -{ - PaError err = Pa_Initialize(); - if( err != paNoError ) { - printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); - return; - } - - for( int i = 0; i < Pa_GetHostApiCount(); ++i ) +public: + DeviceSelectorWidget(const QString& deviceLabel, Direction direction, QWidget* parent = nullptr) + : QGroupBox{parent} + , m_deviceComboBox{new QComboBox{this}} + , m_channelSpinBox{new LcdSpinBox{1, this}} + , m_direction(direction) { - const auto hi = Pa_GetHostApiInfo(i); - m_backendModel.addItem( hi->name ); + m_channelSpinBox->setModel(&m_channelModel); + + const auto layout = new QFormLayout{this}; + layout->addRow(deviceLabel, m_deviceComboBox); + layout->addRow(tr("Channels"), m_channelSpinBox); + + connect(m_deviceComboBox, qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) { refreshChannels(m_deviceComboBox->itemData(index).toInt()); }); } - Pa_Terminate(); -} - - - - -void AudioPortAudioSetupUtil::updateDevices() -{ - PaError err = Pa_Initialize(); - if( err != paNoError ) { - printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); - return; - } - - // get active backend - const QString& backend = m_backendModel.currentText(); - int hostApi = 0; - for( int i = 0; i < Pa_GetHostApiCount(); ++i ) + void refreshFromConfig(PaHostApiIndex backendIndex) { - const auto hi = Pa_GetHostApiInfo(i); - if( backend == hi->name ) + using namespace lmms; + + m_deviceComboBox->clear(); + + for (auto i = 0, deviceCount = Pa_GetDeviceCount(); i < deviceCount; ++i) { - hostApi = i; - break; + const auto deviceInfo = Pa_GetDeviceInfo(i); + + if (maxChannels(deviceInfo, m_direction) > 0 && deviceInfo->hostApi == backendIndex) + { + m_deviceComboBox->addItem(deviceInfo->name, i); + } } + + const auto selectedDeviceName = ConfigManager::inst()->value(tag(), deviceNameAttribute(m_direction)); + const auto selectedDeviceIndex = std::max(0, m_deviceComboBox->findText(selectedDeviceName)); + m_deviceComboBox->setCurrentIndex(selectedDeviceIndex); } - // get devices for selected backend - m_deviceModel.clear(); - for( int i = 0; i < Pa_GetDeviceCount(); ++i ) + void refreshChannels(PaDeviceIndex deviceIndex) { - const auto di = Pa_GetDeviceInfo(i); - if( di->hostApi == hostApi ) + const auto maxChannelCount = maxChannels(Pa_GetDeviceInfo(deviceIndex), m_direction); + const auto channelCount = ConfigManager::inst()->value(tag(), channelsAttribute(m_direction)).toInt(); + m_channelModel.setRange(1, maxChannelCount); + m_channelModel.setValue(channelCount == 0 ? DEFAULT_CHANNELS : channelCount); + m_channelSpinBox->setNumDigits(QString::number(maxChannelCount).length()); + } + + void saveToConfig() + { + ConfigManager::inst()->setValue(tag(), deviceNameAttribute(m_direction), m_deviceComboBox->currentText()); + ConfigManager::inst()->setValue(tag(), channelsAttribute(m_direction), QString::number(m_channelModel.value())); + } + +private: + QComboBox* m_deviceComboBox = nullptr; + LcdSpinBox* m_channelSpinBox = nullptr; + IntModel m_channelModel; + Direction m_direction; +}; + +AudioPortAudioSetupWidget::AudioPortAudioSetupWidget(QWidget* parent) + : AudioDeviceSetupWidget{AudioPortAudio::name(), parent} + , m_backendComboBox{new QComboBox{this}} + , m_inputDevice{new DeviceSelectorWidget{tr("Input device"), Direction::Input}} + , m_outputDevice(new DeviceSelectorWidget{tr("Output device"), Direction::Output}) +{ + constexpr auto formVerticalSpacing = 10; + const auto form = new QFormLayout{this}; + form->setRowWrapPolicy(QFormLayout::WrapLongRows); + form->setVerticalSpacing(formVerticalSpacing); + + form->addRow(tr("Backend"), m_backendComboBox); + form->addRow(m_outputDevice); + form->addRow(m_inputDevice); + + connect(m_backendComboBox, qOverload(&QComboBox::currentIndexChanged), this, [this](int index) { + m_inputDevice->refreshFromConfig(m_backendComboBox->itemData(index).toInt()); + m_outputDevice->refreshFromConfig(m_backendComboBox->itemData(index).toInt()); + }); + +} + +void AudioPortAudioSetupWidget::show() +{ + static auto s_initGuard = detail::PortAudioInitializationGuard{}; + + if (m_backendComboBox->count() == 0) + { + for (auto i = 0, backendCount = Pa_GetHostApiCount(); i < backendCount; ++i) { - m_deviceModel.addItem( di->name ); + m_backendComboBox->addItem(Pa_GetHostApiInfo(i)->name, i); } - } - Pa_Terminate(); -} - - - -void AudioPortAudioSetupUtil::updateChannels() -{ - PaError err = Pa_Initialize(); - if( err != paNoError ) { - printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); - return; - } - // get active backend - Pa_Terminate(); -} - - - - -AudioPortAudio::setupWidget::setupWidget( QWidget * _parent ) : - AudioDeviceSetupWidget( AudioPortAudio::name(), _parent ) -{ - using gui::ComboBox; - - QFormLayout * form = new QFormLayout(this); - - m_backend = new ComboBox( this, "BACKEND" ); - form->addRow(tr("Backend"), m_backend); - - m_device = new ComboBox( this, "DEVICE" ); - form->addRow(tr("Device"), m_device); - -/* LcdSpinBoxModel * m = new LcdSpinBoxModel( ); - m->setRange( DEFAULT_CHANNELS, DEFAULT_CHANNELS ); - m->setStep( 2 ); - m->setValue( ConfigManager::inst()->value( "audioportaudio", - "channels" ).toInt() ); - - m_channels = new LcdSpinBox( 1, this ); - m_channels->setModel( m ); - m_channels->setLabel( tr( "Channels" ) ); - m_channels->move( 308, 20 );*/ - - connect( &m_setupUtil.m_backendModel, SIGNAL(dataChanged()), - &m_setupUtil, SLOT(updateDevices())); - - connect( &m_setupUtil.m_deviceModel, SIGNAL(dataChanged()), - &m_setupUtil, SLOT(updateChannels())); - - m_backend->setModel( &m_setupUtil.m_backendModel ); - m_device->setModel( &m_setupUtil.m_deviceModel ); -} - - - - -AudioPortAudio::setupWidget::~setupWidget() -{ - disconnect( &m_setupUtil.m_backendModel, SIGNAL(dataChanged()), - &m_setupUtil, SLOT(updateDevices())); - - disconnect( &m_setupUtil.m_deviceModel, SIGNAL(dataChanged()), - &m_setupUtil, SLOT(updateChannels())); -} - - - - -void AudioPortAudio::setupWidget::saveSettings() -{ - - ConfigManager::inst()->setValue( "audioportaudio", "backend", - m_setupUtil.m_backendModel.currentText() ); - ConfigManager::inst()->setValue( "audioportaudio", "device", - m_setupUtil.m_deviceModel.currentText() ); -/* ConfigManager::inst()->setValue( "audioportaudio", "channels", - QString::number( m_channels->value() ) );*/ - -} - - - - -void AudioPortAudio::setupWidget::show() -{ - if( m_setupUtil.m_backendModel.size() == 0 ) - { - // populate the backend model the first time we are shown - m_setupUtil.updateBackends(); - - const QString& backend = ConfigManager::inst()->value( - "audioportaudio", "backend" ); - const QString& device = ConfigManager::inst()->value( - "audioportaudio", "device" ); - - int i = std::max(0, m_setupUtil.m_backendModel.findText(backend)); - m_setupUtil.m_backendModel.setValue( i ); - - m_setupUtil.updateDevices(); - - i = std::max(0, m_setupUtil.m_deviceModel.findText(device)); - m_setupUtil.m_deviceModel.setValue( i ); + const auto selectedBackendName = ConfigManager::inst()->value(tag(), backendAttribute()); + const auto selectedBackendIndex = std::max(0, m_backendComboBox->findText(selectedBackendName)); + m_backendComboBox->setCurrentIndex(selectedBackendIndex); } AudioDeviceSetupWidget::show(); } -} // namespace lmms - +void AudioPortAudioSetupWidget::saveSettings() +{ + ConfigManager::inst()->setValue(tag(), backendAttribute(), m_backendComboBox->currentText()); + m_inputDevice->saveToConfig(); + m_outputDevice->saveToConfig(); +} +} // namespace lmms::gui #endif // LMMS_HAVE_PORTAUDIO - - - diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index acd8f7329..7e9c16ab9 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -484,6 +484,9 @@ void MainWindow::finalize() else if( Engine::audioEngine()->audioDevStartFailed() || !AudioEngine::isAudioDevNameValid( ConfigManager::inst()->value( "audioengine", "audiodev" ) ) ) { + QMessageBox::critical(nullptr, "Audio device setup failed", + tr("Failed to setup audio device for playback. Try adjusting your audio device settings (e.g. the sample rate), then restart LMMS.")); + // if so, offer the audio settings section of the setup dialog SetupDialog sd( SetupDialog::ConfigTab::AudioSettings ); sd.exec(); diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index d31657ab8..258a176ad 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -510,7 +510,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : #ifdef LMMS_HAVE_PORTAUDIO m_audioIfaceSetupWidgets[AudioPortAudio::name()] = - new AudioPortAudio::setupWidget(as_w); + new AudioPortAudioSetupWidget(as_w); #endif #ifdef LMMS_HAVE_SOUNDIO