diff --git a/include/AudioDevice.h b/include/AudioDevice.h index 80e392450b2..e433ee15b8e 100644 --- a/include/AudioDevice.h +++ b/include/AudioDevice.h @@ -113,6 +113,11 @@ class AudioDevice 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 fbfa9b60dd6..e03e72ef4bb 100644 --- a/include/AudioPortAudio.h +++ b/include/AudioPortAudio.h @@ -25,137 +25,63 @@ #ifndef LMMS_AUDIO_PORTAUDIO_H #define LMMS_AUDIO_PORTAUDIO_H -#include - #include "lmmsconfig.h" -#include "ComboBoxModel.h" - -#ifdef LMMS_HAVE_PORTAUDIO - -# include - -# include "AudioDevice.h" -# include "AudioDeviceSetupWidget.h" - -# if defined paNeverDropInput || defined paNonInterleaved -# define PORTAUDIO_V19 -# else -# define PORTAUDIO_V18 -# endif - -#endif - - -namespace lmms -{ - -class AudioPortAudioSetupUtil : public QObject -{ -Q_OBJECT -public slots: - void updateBackends(); - void updateDevices(); - void updateChannels(); - -public: - ComboBoxModel m_backendModel; - ComboBoxModel m_deviceModel; -}; - #ifdef LMMS_HAVE_PORTAUDIO +#include +#include +#include +#include +#include -namespace gui -{ -class ComboBox; -class LcdSpinBox; -} - +#include "AudioDevice.h" +#include "AudioDeviceSetupWidget.h" +namespace lmms { class AudioPortAudio : public AudioDevice { public: - AudioPortAudio( bool & _success_ful, AudioEngine* audioEngine ); + AudioPortAudio(AudioEngine* engine); ~AudioPortAudio() override; - inline static QString name() - { - return QT_TRANSLATE_NOOP( "AudioDeviceSetupWidget", "PortAudio" ); - } - - int process_callback(const float* _inputBuffer, float* _outputBuffer, f_cnt_t _framesPerBuffer); - - class setupWidget : public gui::AudioDeviceSetupWidget - { - public: - setupWidget( QWidget * _parent ); - ~setupWidget() override; + AudioPortAudio(const AudioPortAudio&) = delete; + AudioPortAudio(AudioPortAudio&&) = delete; + AudioPortAudio& operator=(const AudioPortAudio&) = delete; + AudioPortAudio& operator=(AudioPortAudio&&) = delete; - 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 ); - -#else - -#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 - - static int _process_callback( void * _inputBuffer, void * _outputBuffer, - unsigned long _framesPerBuffer, PaTimestamp _outTime, void * _arg ); - - - using PaTime = double; - using PaDeviceIndex = PaDeviceID; - - using PaStreamParameters = struct - { - PaDeviceIndex device; - int channelCount; - PaSampleFormat sampleFormat; - PaTime suggestedLatency; - void *hostApiSpecificStreamInfo; + static auto name() -> QString { return QT_TRANSLATE_NOOP("AudioDeviceSetupWidget", "PortAudio"); } - } PaStreamParameters; -#endif // PORTAUDIO_V19 - - PaStream * m_paStream; - PaStreamParameters m_outputParameters; - PaStreamParameters m_inputParameters; +private: + static int processCallback(const void* input, void* output, unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData); - bool m_wasPAInitError; + PaStream* m_paStream = nullptr; + std::vector m_outBuf; + std::size_t m_outBufPos = 0; +}; +} // namespace lmms - SampleFrame* m_outBuf; - std::size_t m_outBufPos; - fpp_t m_outBufSize; +namespace lmms::gui { +class AudioPortAudioSetupWidget : public AudioDeviceSetupWidget +{ +public: + AudioPortAudioSetupWidget(QWidget* parent); - bool m_stopped; + void show() override; + void saveSettings() override; -} ; +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 435fa38fa56..aa6fcdee68e 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" @@ -907,14 +908,23 @@ AudioDevice * AudioEngine::tryAudioDevices() #ifdef LMMS_HAVE_PORTAUDIO - if( dev_name == AudioPortAudio::name() || dev_name == "" ) + // TODO: Eventually move all devices to use exception handling instead of passing in a boolean parameter + // to the constructor + try { - dev = new AudioPortAudio( success_ful, this ); - if( success_ful ) + if (dev_name == AudioPortAudio::name() || dev_name == "") { + dev = new AudioPortAudio(this); m_audioDevName = AudioPortAudio::name(); return dev; } + + success_ful = true; + } + catch (std::runtime_error& error) + { + std::cerr << error.what() << '\n'; + success_ful = false; delete dev; } #endif diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index eb5058bc6b5..6357de3d30c 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -23,444 +23,332 @@ * */ +#include "lmmsconfig.h" +#ifdef LMMS_HAVE_PORTAUDIO + +#include "AudioEngine.h" #include "AudioPortAudio.h" +#include "ConfigManager.h" +#include "LcdSpinBox.h" -#ifndef LMMS_HAVE_PORTAUDIO -namespace lmms +namespace { +enum class Direction { + Input, + Output +}; - -void AudioPortAudioSetupUtil::updateBackends() +constexpr auto tag() { + return "audioportaudio"; } - -void AudioPortAudioSetupUtil::updateDevices() +constexpr auto backendAttribute() { + return "backend"; } -void AudioPortAudioSetupUtil::updateChannels() +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"; + } -} // namespace lmms -#endif + return ""; +} -#ifdef LMMS_HAVE_PORTAUDIO +int maxChannels(const PaDeviceInfo* info, Direction direction) +{ + switch (direction) + { + case Direction::Input: + return info->maxInputChannels; + case Direction::Output: + return info->maxOutputChannels; + } -#include + return 0; +} -#include "Engine.h" -#include "ConfigManager.h" -#include "ComboBox.h" -#include "AudioEngine.h" +QString numChannelsFromConfig(Direction direction) +{ + using namespace lmms; + const auto defaultNumChannels = QString::number(DEFAULT_CHANNELS); + const auto numChannels = ConfigManager::inst()->value(tag(), channelsAttribute(direction), defaultNumChannels); + return numChannels; +} -namespace lmms +PaStreamParameters createStreamParameters(PaDeviceIndex index, double suggestedLatency, Direction direction) { + using namespace lmms; + return PaStreamParameters{.device = index, + .channelCount = numChannelsFromConfig(direction).toInt(), + .sampleFormat = paFloat32, + .suggestedLatency = suggestedLatency, + .hostApiSpecificStreamInfo = nullptr}; +} -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 ) +PaDeviceIndex findDeviceFromConfig(Direction direction) { - _success_ful = false; + using namespace lmms; - m_outBufSize = audioEngine()->framesPerPeriod(); + const auto backendName = ConfigManager::inst()->value(tag(), backendAttribute()); + const auto deviceName = ConfigManager::inst()->value(tag(), deviceNameAttribute(direction)); - PaError err = Pa_Initialize(); - - if( err != paNoError ) { - printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); - m_wasPAInitError = true; - return; - } + auto deviceIndex = paNoDevice; - if( Pa_GetDeviceCount() <= 0 ) + for (auto i = 0, deviceCount = Pa_GetDeviceCount(); i < deviceCount; ++i) { - 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 ) + const auto deviceInfo = Pa_GetDeviceInfo(i); + const auto backendInfo = Pa_GetHostApiInfo(deviceInfo->hostApi); + + if (deviceInfo->name == deviceName && backendInfo->name == backendName) { - inDevIdx = i; - outDevIdx = i; + deviceIndex = i; + break; } } - if( inDevIdx < 0 ) + if (deviceIndex == paNoDevice) { - inDevIdx = Pa_GetDefaultInputDevice(); - } - - if( outDevIdx < 0 ) - { - outDevIdx = Pa_GetDefaultOutputDevice(); + switch (direction) + { + case Direction::Input: + deviceIndex = Pa_GetDefaultInputDevice(); + break; + case Direction::Output: + deviceIndex = Pa_GetDefaultOutputDevice(); + break; + } } - if( inDevIdx < 0 || outDevIdx < 0 ) - { - return; - } + return deviceIndex; +} - 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 ) +class PortAudioInitializationGuard +{ +public: + PortAudioInitializationGuard() + : m_error(Pa_Initialize()) { - 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 (m_error != paNoError) { throw std::runtime_error{"PortAudio: could not initialize"}; } } - if( err != paNoError ) + ~PortAudioInitializationGuard() { - printf( "Couldn't open PortAudio: %s\n", Pa_GetErrorText( err ) ); - return; + if (m_error == paNoError) { Pa_Terminate(); } } - 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 ); + PortAudioInitializationGuard(const PortAudioInitializationGuard&) = default; + PortAudioInitializationGuard(PortAudioInitializationGuard&&) = delete; + PortAudioInitializationGuard& operator=(const PortAudioInitializationGuard&) = default; + PortAudioInitializationGuard& operator=(PortAudioInitializationGuard&&) = delete; - // TODO: debug AudioEngine::pushInputFrames() - //m_supportsCapture = true; +private: + PaError m_error = paNoError; +}; +} // namespace - _success_ful = true; -} +namespace lmms { +AudioPortAudio::AudioPortAudio(AudioEngine* engine) + : AudioDevice(DEFAULT_CHANNELS, engine) + , m_outBuf(engine->framesPerPeriod()) +{ + static auto s_initGuard = PortAudioInitializationGuard{}; + auto inputDevice = findDeviceFromConfig(Direction::Input); + m_supportsCapture = inputDevice != paNoDevice; + auto outputDevice = findDeviceFromConfig(Direction::Output); + if (outputDevice == paNoDevice) { throw std::runtime_error{"PortAudio: could not load output device"}; } + const auto sampleRate = engine->baseSampleRate(); + const auto framesPerBuffer = engine->framesPerPeriod(); + const auto suggestedLatency = static_cast(framesPerBuffer) / sampleRate; -AudioPortAudio::~AudioPortAudio() -{ - stopProcessing(); + const auto inputStreamParameters = createStreamParameters(inputDevice, suggestedLatency, Direction::Input); + const auto outputStreamParameters = createStreamParameters(outputDevice, suggestedLatency, Direction::Output); - if( !m_wasPAInitError ) + if (const auto err = Pa_OpenStream(&m_paStream, inputDevice == paNoDevice ? nullptr : &inputStreamParameters, + &outputStreamParameters, sampleRate, framesPerBuffer, paNoFlag, &processCallback, this)) { - Pa_Terminate(); + throw std::runtime_error{std::string{"PortAudio: could not open stream, "} + Pa_GetErrorText(err)}; } - delete[] m_outBuf; -} - + setSampleRate(sampleRate); + setChannels(outputStreamParameters.channelCount); +} +AudioPortAudio::~AudioPortAudio() +{ + stopProcessing(); + 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 outputBuffer = static_cast(output); + const auto device = static_cast(userData); - if( m_stopped ) - { - memset( _outputBuffer, 0, _framesPerBuffer * - channels() * sizeof(float) ); - return paComplete; - } + std::fill_n(outputBuffer, frameCount * device->channels(), 0.f); - while( _framesPerBuffer ) + for (auto frame = std::size_t{0}; frame < frameCount; ++frame) { - if( m_outBufPos == 0 ) + if (device->m_outBufPos == 0 && device->getNextBuffer(device->m_outBuf.data()) == 0) { return paComplete; } + + if (device->channels() == 1) { - // 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; + outputBuffer[frame] = device->m_outBuf[device->m_outBufPos].average(); } - const auto min_len = std::min(_framesPerBuffer, m_outBufSize - m_outBufPos); - - for( fpp_t frame = 0; frame < min_len; ++frame ) + else { - for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) - { - (_outputBuffer + frame * channels())[chnl] = AudioEngine::clip(m_outBuf[frame][chnl]); - } + outputBuffer[frame * device->channels()] = device->m_outBuf[device->m_outBufPos][0]; + outputBuffer[frame * device->channels() + 1] = device->m_outBuf[device->m_outBufPos][1]; } - _outputBuffer += min_len * channels(); - _framesPerBuffer -= min_len; - m_outBufPos += min_len; - m_outBufPos %= m_outBufSize; + device->m_outBufPos = frame % 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 ); -} - - +public: + DeviceSelectorWidget(const QString& deviceLabel, Direction direction, QWidget* parent = nullptr) + : QGroupBox{parent} + , m_deviceComboBox{new QComboBox{this}} + , m_channelSpinBox{new LcdSpinBox{1, this}} + { + m_channelSpinBox->setModel(&m_channelModel); + const auto layout = new QFormLayout{this}; + layout->addRow(deviceLabel, m_deviceComboBox); + layout->addRow(tr("Channels"), m_channelSpinBox); -void AudioPortAudioSetupUtil::updateBackends() -{ - PaError err = Pa_Initialize(); - if( err != paNoError ) { - printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); - return; + connect(m_deviceComboBox, qOverload(&QComboBox::currentIndexChanged), this, + [this, direction](int index) { refreshChannels(m_deviceComboBox->itemData(index).toInt(), direction); }); } - for( int i = 0; i < Pa_GetHostApiCount(); ++i ) + void refreshFromConfig(PaHostApiIndex backendIndex, Direction direction) { - const auto hi = Pa_GetHostApiInfo(i); - m_backendModel.addItem( hi->name ); - } - - Pa_Terminate(); -} + using namespace lmms; + m_deviceComboBox->clear(); + for (auto i = 0, deviceCount = Pa_GetDeviceCount(); i < deviceCount; ++i) + { + const auto deviceInfo = Pa_GetDeviceInfo(i); + if (maxChannels(deviceInfo, direction) > 0 && deviceInfo->hostApi == backendIndex) + { + m_deviceComboBox->addItem(deviceInfo->name, i); + } + } -void AudioPortAudioSetupUtil::updateDevices() -{ - PaError err = Pa_Initialize(); - if( err != paNoError ) { - printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); - return; + const auto selectedDeviceName = ConfigManager::inst()->value(tag(), deviceNameAttribute(direction)); + const auto selectedDeviceIndex = std::max(0, m_deviceComboBox->findText(selectedDeviceName)); + m_deviceComboBox->setCurrentIndex(selectedDeviceIndex); } - // get active backend - const QString& backend = m_backendModel.currentText(); - int hostApi = 0; - for( int i = 0; i < Pa_GetHostApiCount(); ++i ) + void refreshChannels(PaDeviceIndex deviceIndex, Direction direction) { - const auto hi = Pa_GetHostApiInfo(i); - if( backend == hi->name ) - { - hostApi = i; - break; - } + const auto maxChannelCount = maxChannels(Pa_GetDeviceInfo(deviceIndex), direction); + m_channelModel.setRange(1, static_cast(maxChannelCount)); + m_channelModel.setValue(static_cast(numChannelsFromConfig(direction).toInt())); + m_channelSpinBox->setNumDigits(QString::number(maxChannelCount).length()); } - // get devices for selected backend - m_deviceModel.clear(); - for( int i = 0; i < Pa_GetDeviceCount(); ++i ) + void saveToConfig(Direction direction) { - const auto di = Pa_GetDeviceInfo(i); - if( di->hostApi == hostApi ) - { - m_deviceModel.addItem( di->name ); - } - } - Pa_Terminate(); -} - - - - -void AudioPortAudioSetupUtil::updateChannels() -{ - PaError err = Pa_Initialize(); - if( err != paNoError ) { - printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); - return; + ConfigManager::inst()->setValue(tag(), deviceNameAttribute(direction), m_deviceComboBox->currentText()); + ConfigManager::inst()->setValue(tag(), channelsAttribute(direction), QString::number(m_channelModel.value())); } - // 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() +private: + QComboBox* m_deviceComboBox = nullptr; + lmms::gui::LcdSpinBox* m_channelSpinBox = nullptr; + lmms::IntModel m_channelModel; +}; + +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}) { - disconnect( &m_setupUtil.m_backendModel, SIGNAL(dataChanged()), - &m_setupUtil, SLOT(updateDevices())); - - disconnect( &m_setupUtil.m_deviceModel, SIGNAL(dataChanged()), - &m_setupUtil, SLOT(updateChannels())); + 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(), Direction::Input); + m_outputDevice->refreshFromConfig(m_backendComboBox->itemData(index).toInt(), Direction::Output); + }); } - - - -void AudioPortAudio::setupWidget::saveSettings() +void AudioPortAudioSetupWidget::show() { + static auto s_initGuard = PortAudioInitializationGuard{}; - 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 ) + if (m_backendComboBox->count() == 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 ); + for (auto i = 0, backendCount = Pa_GetHostApiCount(); i < backendCount; ++i) + { + m_backendComboBox->addItem(Pa_GetHostApiInfo(i)->name, 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(Direction::Input); + m_outputDevice->saveToConfig(Direction::Output); +} +} // namespace lmms::gui #endif // LMMS_HAVE_PORTAUDIO - - - diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index d71ede03f53..5186dca3d48 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -496,7 +496,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