code:
/*
 * AudioPort.cpp - base-class for objects providing sound at a port
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "AudioPort.h"
#include "AudioDevice.h"
#include "EffectChain.h"
#include "FxMixer.h"
#include "Engine.h"
#include "Mixer.h"
#include "MixHelpers.h"
#include "BufferManager.h"

AudioPort::AudioPort( const QString & _name, bool _has_effect_chain,
		FloatModel * volumeModel, FloatModel * panningModel,
		BoolModel * mutedModel ) :
	m_bufferUsage( false ),
	m_portBuffer( BufferManager::acquire() ),
	m_extOutputEnabled( false ),
	m_nextFxChannel( 0 ),
	m_name( "unnamed port" ),
	m_effects( _has_effect_chain ? new EffectChain( NULL ) : NULL ),
	m_volumeModel( volumeModel ),
	m_panningModel( panningModel ),
	m_mutedModel( mutedModel )
{
	Engine::mixer()->addAudioPort( this );
	setExtOutputEnabled( true );
}

AudioPort::~AudioPort()
{
	setExtOutputEnabled( false );
	Engine::mixer()->removeAudioPort( this );
	BufferManager::release( m_portBuffer );
}

void AudioPort::setExtOutputEnabled( bool _enabled )
{
	if( _enabled != m_extOutputEnabled )
	{
		m_extOutputEnabled = _enabled;
		if( m_extOutputEnabled )
		{
			Engine::mixer()->audioDev()->registerPort( this );
		}
		else
		{
			Engine::mixer()->audioDev()->unregisterPort( this );
		}
	}
}

void AudioPort::setName( const QString & _name )
{
	m_name = _name;
	Engine::mixer()->audioDev()->renamePort( this );
}

bool AudioPort::processEffects()
{
	if( m_effects )
	{
		bool more = m_effects->processAudioBuffer( m_portBuffer, Engine::mixer()->framesPerPeriod(), m_bufferUsage );
		return more;
	}
	return false;
}

void AudioPort::doProcessing()
{
	if( m_mutedModel && m_mutedModel->value() )
	{
		return;
	}

	const fpp_t fpp = Engine::mixer()->framesPerPeriod();

	// clear the buffer
	BufferManager::clear( m_portBuffer, fpp );

	//qDebug( "Playhandles: %d", m_playHandles.size() );
	for( PlayHandle * ph : m_playHandles ) // now we mix all playhandle buffers into the audioport buffer
	{
		if( ph->buffer() )
		{
			if( ph->usesBuffer()
				&& ( ph->type() == PlayHandle::TypeNotePlayHandle
					|| !MixHelpers::isSilent( ph->buffer(), fpp ) ) )
			{
				m_bufferUsage = true;
				MixHelpers::add( m_portBuffer, ph->buffer(), fpp );
			}
			ph->releaseBuffer(); 	// gets rid of playhandle's buffer and sets
									// pointer to null, so if it doesn't get re-acquired we know to skip it next time
		}
	}

	if( m_bufferUsage )
	{
		// handle volume and panning
		// has both vol and pan models
		if( m_volumeModel && m_panningModel )
		{
			ValueBuffer * volBuf = m_volumeModel->valueBuffer();
			ValueBuffer * panBuf = m_panningModel->valueBuffer();

			// both vol and pan have s.ex.data:
			if( volBuf && panBuf )
			{
				for( f_cnt_t f = 0; f < fpp; ++f )
				{
					float v = volBuf->values()[ f ] * 0.01f;
					float p = panBuf->values()[ f ] * 0.01f;
					m_portBuffer[f][0] *= ( p <= 0 ? 1.0f : 1.0f - p ) * v;
					m_portBuffer[f][1] *= ( p >= 0 ? 1.0f : 1.0f + p ) * v;
				}
			}

			// only vol has s.ex.data:
			else if( volBuf )
			{
				float p = m_panningModel->value() * 0.01f;
				float l = ( p <= 0 ? 1.0f : 1.0f - p );
				float r = ( p >= 0 ? 1.0f : 1.0f + p );
				for( f_cnt_t f = 0; f < fpp; ++f )
				{
					float v = volBuf->values()[ f ] * 0.01f;
					m_portBuffer[f][0] *= v * l;
					m_portBuffer[f][1] *= v * r;
				}
			}

			// only pan has s.ex.data:
			else if( panBuf )
			{
				float v = m_volumeModel->value() * 0.01f;
				for( f_cnt_t f = 0; f < fpp; ++f )
				{
					float p = panBuf->values()[ f ] * 0.01f;
					m_portBuffer[f][0] *= ( p <= 0 ? 1.0f : 1.0f - p ) * v;
					m_portBuffer[f][1] *= ( p >= 0 ? 1.0f : 1.0f + p ) * v;
				}
			}

			// neither has s.ex.data:
			else
			{
				float p = m_panningModel->value() * 0.01f;
				float v = m_volumeModel->value() * 0.01f;
				for( f_cnt_t f = 0; f < fpp; ++f )
				{
					m_portBuffer[f][0] *= ( p <= 0 ? 1.0f : 1.0f - p ) * v;
					m_portBuffer[f][1] *= ( p >= 0 ? 1.0f : 1.0f + p ) * v;
				}
			}
		}

		// has vol model only
		else if( m_volumeModel )
		{
			ValueBuffer * volBuf = m_volumeModel->valueBuffer();

			if( volBuf )
			{
				for( f_cnt_t f = 0; f < fpp; ++f )
				{
					float v = volBuf->values()[ f ] * 0.01f;
					m_portBuffer[f][0] *= v;
					m_portBuffer[f][1] *= v;
				}
			}
			else
			{
				float v = m_volumeModel->value() * 0.01f;
				for( f_cnt_t f = 0; f < fpp; ++f )
				{
					m_portBuffer[f][0] *= v;
					m_portBuffer[f][1] *= v;
				}
			}
		}
	}
	// as of now there's no situation where we only have panning model but no volume model
	// if we have neither, we don't have to do anything here - just pass the audio as is

	// handle effects
	const bool me = processEffects();
	if( me || m_bufferUsage )
	{
		Engine::fxMixer()->mixToChannel( m_portBuffer, m_nextFxChannel ); 	// send output to fx mixer
																			// TODO: improve the flow here - convert to pull model
		m_bufferUsage = false;
	}
}

void AudioPort::addPlayHandle( PlayHandle * handle )
{
	m_playHandleLock.lock();
		m_playHandles.append( handle );
	m_playHandleLock.unlock();
}

void AudioPort::removePlayHandle( PlayHandle * handle )
{
	m_playHandleLock.lock();
		PlayHandleList::Iterator it =	std::find( m_playHandles.begin(), m_playHandles.end(), handle );
		if( it != m_playHandles.end() )
		{
			m_playHandles.erase( it );
		}
	m_playHandleLock.unlock();
}


code:
/*
 * AudioPortAudio.cpp - device-class that performs PCM-output via PortAudio
 *
 * Copyright (c) 2008 Csaba Hruska <csaba.hruska/at/gmail.com>
 * Copyright (c) 2010 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "AudioPortAudio.h"

#ifndef LMMS_HAVE_PORTAUDIO
void AudioPortAudioSetupUtil::updateBackends()
{
}

void AudioPortAudioSetupUtil::updateDevices()
{
}

void AudioPortAudioSetupUtil::updateChannels()
{
}
#endif

#ifdef LMMS_HAVE_PORTAUDIO

#include <QLabel>
#include <QLineEdit>

#include "Engine.h"
#include "ConfigManager.h"
#include "gui_templates.h"
#include "ComboBox.h"
#include "Mixer.h"

AudioPortAudio::AudioPortAudio( bool & _success_ful, Mixer * _mixer ) :
	AudioDevice( qBound<ch_cnt_t>(
		DEFAULT_CHANNELS,
		ConfigManager::inst()->value( "audioportaudio", "channels" ).toInt(),
		SURROUND_CHANNELS ), _mixer ),
	m_paStream( NULL ),
	m_wasPAInitError( false ),
	m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ),
	m_outBufPos( 0 )
{
	_success_ful = false;

	m_outBufSize = mixer()->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;
	const PaDeviceInfo * di;
	for( int i = 0; i < Pa_GetDeviceCount(); ++i )
	{
		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)mixer()->framesPerPeriod() / (double)sampleRate();
	double outLatency = 0;//(double)mixer()->framesPerPeriod() / (double)sampleRate();

	//inLatency = Pa_GetDeviceInfo( inDevIdx )->defaultLowInputLatency;
	//outLatency = Pa_GetDeviceInfo( outDevIdx )->defaultLowOutputLatency;
	const int samples = mixer()->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 = NULL;

	// 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 = NULL;

	// Open an audio I/O stream.
	err = Pa_OpenStream(
			&m_paStream,
			supportsCapture() ? &m_inputParameters : NULL,	// 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 : NULL,	// 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 Mixer::pushInputFrames()
	//m_supportsCapture = true;

	_success_ful = true;
}

AudioPortAudio::~AudioPortAudio()
{
	stopProcessing();

	if( !m_wasPAInitError )
	{
		Pa_Terminate();
	}
	delete[] m_outBuf;
}

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 ) );
	}
}

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 ) );
		}
	}
}

void AudioPortAudio::applyQualitySettings()
{
	if( hqAudio() )
	{

		setSampleRate( Engine::mixer()->processingSampleRate() );
		int samples = mixer()->framesPerPeriod();

		PaError err = Pa_OpenStream(
			&m_paStream,
			supportsCapture() ? &m_inputParameters : NULL,	// 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;
		}
	}

	AudioDevice::applyQualitySettings();
}

int AudioPortAudio::process_callback(
	const float *_inputBuffer,
	float * _outputBuffer,
	unsigned long _framesPerBuffer )
{
	if( supportsCapture() )
	{
		mixer()->pushInputFrames( (sampleFrame*)_inputBuffer,
												_framesPerBuffer );
	}

	if( m_stopped )
	{
		memset( _outputBuffer, 0, _framesPerBuffer *
			channels() * sizeof(float) );
		return paComplete;
	}

	while( _framesPerBuffer )
	{
		if( m_outBufPos == 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 int min_len = qMin( (int)_framesPerBuffer,
			m_outBufSize - m_outBufPos );

		float master_gain = mixer()->masterGain();

		for( fpp_t frame = 0; frame < min_len; ++frame )
		{
			for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
			{
				( _outputBuffer + frame * channels() )[chnl] =
						Mixer::clip( m_outBuf[frame][chnl] *
						master_gain );
			}
		}

		_outputBuffer += min_len * channels();
		_framesPerBuffer -= min_len;
		m_outBufPos += min_len;
		m_outBufPos %= m_outBufSize;
	}

	return paContinue;
}

int AudioPortAudio::_process_callback(
	const void *_inputBuffer,
	void * _outputBuffer,
	unsigned long _framesPerBuffer,
	const PaStreamCallbackTimeInfo * _timeInfo,
	PaStreamCallbackFlags _statusFlags,
	void * _arg )
{
	Q_UNUSED(_timeInfo);
	Q_UNUSED(_statusFlags);

	AudioPortAudio * _this  = static_cast<AudioPortAudio *> (_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;
	}

	const PaHostApiInfo * hi;
	for( int i = 0; i < Pa_GetHostApiCount(); ++i )
	{
		hi = Pa_GetHostApiInfo( i );
		m_backendModel.addItem( hi->name );
	}

	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;
	const PaHostApiInfo * hi;
	for( int i = 0; i < Pa_GetHostApiCount(); ++i )
	{
		hi = Pa_GetHostApiInfo( i );
		if( backend == hi->name )
		{
			hostApi = i;
			break;
		}
	}

	// get devices for selected backend
	m_deviceModel.clear();
	const PaDeviceInfo * di;
	for( int i = 0; i < Pa_GetDeviceCount(); ++i )
	{
		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;
	}
	// get active backend
	Pa_Terminate();
}

AudioPortAudio::setupWidget::setupWidget( QWidget * _parent ) :
	AudioDeviceSetupWidget( AudioPortAudio::name(), _parent )
{
	m_backend = new ComboBox( this, "BACKEND" );
	m_backend->setGeometry( 64, 15, 260, ComboBox::DEFAULT_HEIGHT );

	QLabel * backend_lbl = new QLabel( tr( "Backend" ), this );
	backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) );
	backend_lbl->move( 8, 18 );

	m_device = new ComboBox( this, "DEVICE" );
	m_device->setGeometry( 64, 35, 260, ComboBox::DEFAULT_HEIGHT );

	QLabel * dev_lbl = new QLabel( tr( "Device" ), this );
	dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
	dev_lbl->move( 8, 38 );

/*	LcdSpinBoxModel * m = new LcdSpinBoxModel(  );
	m->setRange( DEFAULT_CHANNELS, SURROUND_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<int>() ) );*/

}

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 = qMax( 0, m_setupUtil.m_backendModel.findText( backend ) );
		m_setupUtil.m_backendModel.setValue( i );

		m_setupUtil.updateDevices();

		i = qMax( 0, m_setupUtil.m_deviceModel.findText( device ) );
		m_setupUtil.m_deviceModel.setValue( i );
	}

	AudioDeviceSetupWidget::show();
}

#endif

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * AudioPulseAudio.cpp - device-class which implements PulseAudio-output
 *
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QLineEdit>
#include <QLabel>

#include "AudioPulseAudio.h"

#ifdef LMMS_HAVE_PULSEAUDIO

#include "ConfigManager.h"
#include "LcdSpinBox.h"
#include "Mixer.h"
#include "gui_templates.h"
#include "Engine.h"

static void stream_write_callback(pa_stream *s, size_t length, void *userdata)
{
	static_cast<AudioPulseAudio *>( userdata )->streamWriteCallback( s, length );
}

AudioPulseAudio::AudioPulseAudio( bool & _success_ful, Mixer*  _mixer ) :
	AudioDevice( qBound<ch_cnt_t>(
		DEFAULT_CHANNELS,
		ConfigManager::inst()->value( "audiopa", "channels" ).toInt(),
		SURROUND_CHANNELS ), _mixer ),
	m_s( NULL ),
	m_quit( false ),
	m_convertEndian( false )
{
	_success_ful = false;

	m_sampleSpec.format = PA_SAMPLE_S16LE;
	m_sampleSpec.rate = sampleRate();
	m_sampleSpec.channels = channels();

	_success_ful = true;
}

AudioPulseAudio::~AudioPulseAudio()
{
	stopProcessing();
}

QString AudioPulseAudio::probeDevice()
{
	QString dev = ConfigManager::inst()->value( "audiopa", "device" );
	if( dev.isEmpty() )
	{
		if( getenv( "AUDIODEV" ) != NULL )
		{
			return getenv( "AUDIODEV" );
		}
		return "default";
	}
	return dev;
}

void AudioPulseAudio::startProcessing()
{
	if( !isRunning() )
	{
		start( QThread::HighPriority );
	}
}

void AudioPulseAudio::stopProcessing()
{
	m_quit = true;
	stopProcessingThread( this );
}

void AudioPulseAudio::applyQualitySettings()
{
	if( hqAudio() )
	{
//		setSampleRate( engine::mixer()->processingSampleRate() );

	}

	AudioDevice::applyQualitySettings();
}

/* This routine is called whenever the stream state changes */
static void stream_state_callback( pa_stream *s, void * userdata )
{
	switch( pa_stream_get_state( s ) )
	{
		case PA_STREAM_CREATING:
		case PA_STREAM_TERMINATED:
			break;

		case PA_STREAM_READY:
			qDebug( "Stream successfully created\n" );
			break;

		case PA_STREAM_FAILED:
		default:
			qCritical( "Stream error: %s\n",
					pa_strerror(pa_context_errno(
						pa_stream_get_context( s ) ) ) );
	}
}

/* This is called whenever the context status changes */
static void context_state_callback(pa_context *c, void *userdata)
{
	AudioPulseAudio * _this = static_cast<AudioPulseAudio *>( userdata );
	switch( pa_context_get_state( c ) )
	{
		case PA_CONTEXT_CONNECTING:
		case PA_CONTEXT_AUTHORIZING:
		case PA_CONTEXT_SETTING_NAME:
			break;

		case PA_CONTEXT_READY:
		{
			qDebug( "Connection established.\n" );
			_this->m_s = pa_stream_new( c, "lmms", &_this->m_sampleSpec,  NULL);
			pa_stream_set_state_callback( _this->m_s, stream_state_callback, _this );
			pa_stream_set_write_callback( _this->m_s, stream_write_callback, _this );

			pa_buffer_attr buffer_attr;

			buffer_attr.maxlength = (uint32_t)(-1);

			// play silence in case of buffer underun instead of using default rewind
			buffer_attr.prebuf = 0;

			buffer_attr.minreq = (uint32_t)(-1);
			buffer_attr.fragsize = (uint32_t)(-1);

			double latency = (double)( Engine::mixer()->framesPerPeriod() ) /
													(double)_this->sampleRate();

			// ask PulseAudio for the desired latency (which might not be approved)
			buffer_attr.tlength = pa_usec_to_bytes( latency * PA_USEC_PER_MSEC,
														&_this->m_sampleSpec );

			pa_stream_connect_playback( _this->m_s, NULL, &buffer_attr,
										PA_STREAM_ADJUST_LATENCY,
										NULL,	// volume
										NULL );
			_this->signalConnected( true );
			break;
		}

		case PA_CONTEXT_TERMINATED:
			break;

		case PA_CONTEXT_FAILED:
		default:
			qCritical( "Connection failure: %s\n", pa_strerror( pa_context_errno( c ) ) );
			_this->signalConnected( false );
	}
}

void AudioPulseAudio::run()
{
	pa_mainloop * mainLoop = pa_mainloop_new();
	if( !mainLoop )
	{
		qCritical( "pa_mainloop_new() failed.\n" );
		return;
	}
	pa_mainloop_api * mainloop_api = pa_mainloop_get_api( mainLoop );

	pa_context *context = pa_context_new( mainloop_api, "lmms" );
	if ( context == NULL )
	{
		qCritical( "pa_context_new() failed." );
		return;
	}

	m_connected = false;

	pa_context_set_state_callback( context, context_state_callback, this  );
	// connect the context
	pa_context_connect( context, NULL, (pa_context_flags) 0, NULL );

	while (!m_connectedSemaphore.tryAcquire()) {
		pa_mainloop_iterate(mainLoop, 1, NULL);
	}

	// run the main loop
	if( m_connected )
	{
		int ret = 0;
		m_quit = false;
		while( m_quit == false
			&& pa_mainloop_iterate( mainLoop, 1, &ret ) >= 0 )
		{
		}

		pa_stream_disconnect( m_s );
		pa_stream_unref( m_s );
	}
	else
	{
		const fpp_t fpp = mixer()->framesPerPeriod();
		surroundSampleFrame * temp = new surroundSampleFrame[fpp];
		while( getNextBuffer( temp ) )
		{
		}
		delete[] temp;
	}

	pa_context_disconnect( context );
	pa_context_unref( context );

	pa_mainloop_free( mainLoop );
}

void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length )
{
	const fpp_t fpp = mixer()->framesPerPeriod();
	surroundSampleFrame * temp = new surroundSampleFrame[fpp];
	int_sample_t* pcmbuf = (int_sample_t *)pa_xmalloc( fpp * channels() * sizeof(int_sample_t) );

	size_t fd = 0;
	while( fd < length/4 && m_quit == false )
	{
		const fpp_t frames = getNextBuffer( temp );
		if( !frames )
		{
			m_quit = true;
			break;
		}
		int bytes = convertToS16( temp, frames,
						mixer()->masterGain(),
						pcmbuf,
						m_convertEndian );
		if( bytes > 0 )
		{
			pa_stream_write( m_s, pcmbuf, bytes, NULL, 0,
							PA_SEEK_RELATIVE );
		}
		fd += frames;
	}

	pa_xfree( pcmbuf );
	delete[] temp;
}

void AudioPulseAudio::signalConnected( bool connected )
{
	if( !m_connected )
	{
		m_connected = connected;
		m_connectedSemaphore.release();
	}
}

AudioPulseAudio::setupWidget::setupWidget( QWidget * _parent ) :
	AudioDeviceSetupWidget( AudioPulseAudio::name(), _parent )
{
	m_device = new QLineEdit( AudioPulseAudio::probeDevice(), this );
	m_device->setGeometry( 10, 20, 160, 20 );

	QLabel * dev_lbl = new QLabel( tr( "Device" ), this );
	dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
	dev_lbl->setGeometry( 10, 40, 160, 10 );

	LcdSpinBoxModel * m = new LcdSpinBoxModel( /* this */ );
	m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
	m->setStep( 2 );
	m->setValue( ConfigManager::inst()->value( "audiopa",
							"channels" ).toInt() );

	m_channels = new LcdSpinBox( 1, this );
	m_channels->setModel( m );
	m_channels->setLabel( tr( "Channels" ) );
	m_channels->move( 180, 20 );

}

AudioPulseAudio::setupWidget::~setupWidget()
{
	delete m_channels->model();
}

void AudioPulseAudio::setupWidget::saveSettings()
{
	ConfigManager::inst()->setValue( "audiopa", "device",
							m_device->text() );
	ConfigManager::inst()->setValue( "audiopa", "channels",
				QString::number( m_channels->value<int>() ) );
}

#endif


code:
/*
 * AudioSampleRecorder.cpp - device-class that implements recording
 *                           audio-buffers into RAM
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "AudioSampleRecorder.h"
#include "SampleBuffer.h"
#include "debug.h"

AudioSampleRecorder::AudioSampleRecorder( const ch_cnt_t _channels,
							bool & _success_ful,
							Mixer * _mixer ) :
	AudioDevice( _channels, _mixer ),
	m_buffers()
{
	_success_ful = true;
}

AudioSampleRecorder::~AudioSampleRecorder()
{
	while( !m_buffers.empty() )
	{
		delete[] m_buffers.front().first;
		m_buffers.erase( m_buffers.begin() );
	}
}

f_cnt_t AudioSampleRecorder::framesRecorded() const
{
	f_cnt_t frames = 0;
	for( BufferList::ConstIterator it = m_buffers.begin();
						it != m_buffers.end(); ++it )
	{
		frames += ( *it ).second;
	}
	return frames;
}

void AudioSampleRecorder::createSampleBuffer( SampleBuffer** sampleBuf )
{
	const f_cnt_t frames = framesRecorded();
	// create buffer to store all recorded buffers in
	sampleFrame * data = new sampleFrame[frames];
	// make sure buffer is cleaned up properly at the end...
	sampleFrame * data_ptr = data;

	assert( data != NULL );

	// now copy all buffers into big buffer
	for( BufferList::ConstIterator it = m_buffers.begin();
						it != m_buffers.end(); ++it )
	{
		memcpy( data_ptr, ( *it ).first, ( *it ).second *
							sizeof( sampleFrame ) );
		data_ptr += ( *it ).second;
	}
	// create according sample-buffer out of big buffer
	*sampleBuf = new SampleBuffer( data, frames );
	( *sampleBuf )->setSampleRate( sampleRate() );
	delete[] data;
}

void AudioSampleRecorder::writeBuffer( const surroundSampleFrame * _ab,
					const fpp_t _frames, const float )
{
	sampleFrame * buf = new sampleFrame[_frames];
	for( fpp_t frame = 0; frame < _frames; ++frame )
	{
		for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl )
		{
			buf[frame][chnl] = _ab[frame][chnl];
		}
	}
	m_buffers.push_back( qMakePair( buf, _frames ) );
}


code:
/*
 * AudioSdl.cpp - device-class that performs PCM-output via SDL
 *
 * Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "AudioSdl.h"

#ifdef LMMS_HAVE_SDL

#include <QLabel>
#include <QLineEdit>

#include "Engine.h"
#include "ConfigManager.h"
#include "gui_templates.h"
#include "Mixer.h"

AudioSdl::AudioSdl( bool & _success_ful, Mixer*  _mixer ) :
	AudioDevice( DEFAULT_CHANNELS, _mixer ),
	m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] )
{
	_success_ful = false;

#ifdef LMMS_HAVE_SDL2
	m_currentBufferFramesCount = 0;
	m_currentBufferFramePos = 0;
#else
	m_convertedBufSize = mixer()->framesPerPeriod() * channels()
						* sizeof( int_sample_t );
	m_convertedBufPos = 0;
	m_convertedBuf = new Uint8[m_convertedBufSize];
#endif

	if( SDL_Init( SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE ) < 0 )
	{
		qCritical( "Couldn't initialize SDL: %s\n", SDL_GetError() );
		return;
	}

	m_audioHandle.freq = sampleRate();
#ifdef LMMS_HAVE_SDL2
	m_audioHandle.format = AUDIO_F32SYS;	// we want it in byte-order
						// of system, so we don't have
						// to convert the buffers
#else
	m_audioHandle.format = AUDIO_S16SYS;	// we want it in byte-order
						// of system, so we don't have
						// to convert the buffers
#endif
	m_audioHandle.channels = channels();
	m_audioHandle.samples = qMax( 1024, mixer()->framesPerPeriod()*2 );

	m_audioHandle.callback = sdlAudioCallback;
	m_audioHandle.userdata = this;

  	SDL_AudioSpec actual;

#ifdef LMMS_HAVE_SDL2
	m_outputDevice = SDL_OpenAudioDevice (NULL,
										  0,
										  &m_audioHandle,
										  &actual,
										  0);
	if (m_outputDevice == 0) {
		qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() );
		return;
	}
#else
	// open the audio device, forcing the desired format
	if( SDL_OpenAudio( &m_audioHandle, &actual ) < 0 )
	{
		qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() );
		return;
	}

	m_outConvertEndian = ( m_audioHandle.format != actual.format );
#endif

	_success_ful = true;

#ifdef LMMS_HAVE_SDL2
	// Workaround for a race condition that causes SDL to segfault
	SDL_Delay(50);

	m_inputAudioHandle = m_audioHandle;
	m_inputAudioHandle.callback = sdlInputAudioCallback;

	m_inputDevice = SDL_OpenAudioDevice (NULL,
										 1,
										 &m_inputAudioHandle,
										 &actual,
										 0);
	if (m_inputDevice != 0) {
		m_supportsCapture = true;
	} else {
		m_supportsCapture = false;
		qWarning ( "Couldn't open SDL capture device: %s\n", SDL_GetError ());
	}

#endif
}

AudioSdl::~AudioSdl()
{
	stopProcessing();

#ifdef LMMS_HAVE_SDL2
	if (m_inputDevice != 0)
		SDL_CloseAudioDevice(m_inputDevice);
	if (m_outputDevice != 0)
		SDL_CloseAudioDevice(m_outputDevice);
#else
	SDL_CloseAudio();
	delete[] m_convertedBuf;
#endif

	SDL_Quit();

	delete[] m_outBuf;
}

void AudioSdl::startProcessing()
{
	m_stopped = false;

#ifdef LMMS_HAVE_SDL2
	SDL_PauseAudioDevice (m_outputDevice, 0);
	SDL_PauseAudioDevice (m_inputDevice, 0);
#else
	SDL_PauseAudio( 0 );
#endif
}

void AudioSdl::stopProcessing()
{
#ifdef LMMS_HAVE_SDL2
	if( SDL_GetAudioDeviceStatus(m_outputDevice) == SDL_AUDIO_PLAYING )
#else
	if( SDL_GetAudioStatus() == SDL_AUDIO_PLAYING )
#endif
	{
#ifdef LMMS_HAVE_SDL2
		SDL_LockAudioDevice (m_inputDevice);
		SDL_LockAudioDevice (m_outputDevice);

		m_stopped = true;

		SDL_PauseAudioDevice (m_inputDevice,	1);
		SDL_PauseAudioDevice (m_outputDevice,	1);

		SDL_UnlockAudioDevice (m_inputDevice);
		SDL_UnlockAudioDevice (m_outputDevice);
#else
		SDL_LockAudio();
		m_stopped = true;
		SDL_PauseAudio( 1 );
		SDL_UnlockAudio();
#endif

	}
}

void AudioSdl::applyQualitySettings()
{
	// Better than if (0)
#if 0
	if( 0 )//hqAudio() )
	{
		SDL_CloseAudio();

		setSampleRate( Engine::mixer()->processingSampleRate() );

		m_audioHandle.freq = sampleRate();

		SDL_AudioSpec actual;

		// open the audio device, forcing the desired format
		if( SDL_OpenAudio( &m_audioHandle, &actual ) < 0 )
		{
			qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() );
		}
	}
#endif

	AudioDevice::applyQualitySettings();
}

void AudioSdl::sdlAudioCallback( void * _udata, Uint8 * _buf, int _len )
{
	AudioSdl * _this = static_cast<AudioSdl *>( _udata );

	_this->sdlAudioCallback( _buf, _len );
}

void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len )
{
	if( m_stopped )
	{
		memset( _buf, 0, _len );
		return;
	}

	// SDL2: process float samples
#ifdef LMMS_HAVE_SDL2
	while( _len )
	{
		if( m_currentBufferFramePos == 0 )
		{
			// frames depend on the sample rate
			const fpp_t frames = getNextBuffer( m_outBuf );
			if( !frames )
			{
				memset( _buf, 0, _len );
				return;
			}
			m_currentBufferFramesCount = frames;

		}
		const uint min_frames_count = qMin( _len/sizeof(sampleFrame),
										  m_currentBufferFramesCount
										- m_currentBufferFramePos );

		const float gain = mixer()->masterGain();
		for (uint f = 0; f < min_frames_count; f++)
		{
			(m_outBuf + m_currentBufferFramePos)[f][0] *= gain;
			(m_outBuf + m_currentBufferFramePos)[f][1] *= gain;
		}

		memcpy( _buf, m_outBuf + m_currentBufferFramePos, min_frames_count*sizeof(sampleFrame) );
		_buf += min_frames_count*sizeof(sampleFrame);
		_len -= min_frames_count*sizeof(sampleFrame);
		m_currentBufferFramePos += min_frames_count;

		m_currentBufferFramePos %= m_currentBufferFramesCount;
	}
#else
	while( _len )
	{
		if( m_convertedBufPos == 0 )
		{
			// frames depend on the sample rate
			const fpp_t frames = getNextBuffer( m_outBuf );
			if( !frames )
			{
				m_stopped = true;
				memset( _buf, 0, _len );
				return;
			}
			m_convertedBufSize = frames * channels()
						* sizeof( int_sample_t );

			convertToS16( m_outBuf, frames,
						mixer()->masterGain(),
						(int_sample_t *)m_convertedBuf,
						m_outConvertEndian );
		}
		const int min_len = qMin( _len, m_convertedBufSize
							- m_convertedBufPos );
		memcpy( _buf, m_convertedBuf + m_convertedBufPos, min_len );
		_buf += min_len;
		_len -= min_len;
		m_convertedBufPos += min_len;
		m_convertedBufPos %= m_convertedBufSize;
	}
#endif
}

#ifdef LMMS_HAVE_SDL2

void AudioSdl::sdlInputAudioCallback(void *_udata, Uint8 *_buf, int _len) {
	AudioSdl * _this = static_cast<AudioSdl *>( _udata );

	_this->sdlInputAudioCallback( _buf, _len );
}

void AudioSdl::sdlInputAudioCallback(Uint8 *_buf, int _len) {
	sampleFrame *samples_buffer = (sampleFrame *) _buf;
	fpp_t frames = _len / sizeof ( sampleFrame );

	mixer()->pushInputFrames (samples_buffer, frames);
}

#endif

AudioSdl::setupWidget::setupWidget( QWidget * _parent ) :
	AudioDeviceSetupWidget( AudioSdl::name(), _parent )
{
	QString dev = ConfigManager::inst()->value( "audiosdl", "device" );
	m_device = new QLineEdit( dev, this );
	m_device->setGeometry( 10, 20, 160, 20 );

	QLabel * dev_lbl = new QLabel( tr( "Device" ), this );
	dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
	dev_lbl->setGeometry( 10, 40, 160, 10 );

}

AudioSdl::setupWidget::~setupWidget()
{
}

void AudioSdl::setupWidget::saveSettings()
{
	ConfigManager::inst()->setValue( "audiosdl", "device",
							m_device->text() );
}

#endif


code:
/*
 * AudioSndio.cpp - base-class that implements sndio audio support
 *
 * Copyright (c) 2010-2016 jackmsr@openbsd.net
 * Copyright (c) 2016-2017 David Carlier <devnexen@gmail.com>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "AudioSndio.h"

#ifdef LMMS_HAVE_SNDIO

#include <QFileInfo>
#include <QLabel>
#include <QLineEdit>

#include "endian_handling.h"
#include "LcdSpinBox.h"
#include "Mixer.h"
#include "Engine.h"
#include "gui_templates.h"

#ifdef LMMS_HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef LMMS_HAVE_STDLIB_H
#include <stdlib.h>
#endif

#include "ConfigManager.h"

AudioSndio::AudioSndio(bool & _success_ful, Mixer * _mixer) :
	AudioDevice( qBound<ch_cnt_t>(
		DEFAULT_CHANNELS,
		ConfigManager::inst()->value( "audiosndio", "channels" ).toInt(),
		SURROUND_CHANNELS ), _mixer ),
	m_convertEndian ( false )
{
	_success_ful = false;

	QString dev = ConfigManager::inst()->value( "audiosndio", "device" );

	if (dev == "")
	{
		m_hdl = sio_open( NULL, SIO_PLAY, 0 );
	}
	else
	{
		m_hdl = sio_open( dev.toLatin1().constData(), SIO_PLAY, 0 );
	}

	if( m_hdl == NULL )
	{
		printf( "sndio: failed opening audio-device\n" );
		return;
	}

	sio_initpar(&m_par);

	m_par.pchan = channels();
	m_par.bits = 16;
	m_par.le = SIO_LE_NATIVE;
	m_par.rate = sampleRate();
	m_par.round = mixer()->framesPerPeriod();
	m_par.appbufsz = m_par.round * 2;

	if ( (isLittleEndian() && (m_par.le == 0)) ||
	     (!isLittleEndian() && (m_par.le == 1))) {
		m_convertEndian = true;
	}

	struct sio_par reqpar = m_par;

	if (!sio_setpar(m_hdl, &m_par))
	{
		printf( "sndio: sio_setpar failed\n" );
		return;
	}
	if (!sio_getpar(m_hdl, &m_par))
	{
		printf( "sndio: sio_getpar failed\n" );
		return;
	}

	if (reqpar.pchan != m_par.pchan ||
		reqpar.bits != m_par.bits ||
		reqpar.le != m_par.le ||
		(::abs(static_cast<int>(reqpar.rate) - static_cast<int>(m_par.rate)) * 100)/reqpar.rate > 2)
	{
		printf( "sndio: returned params not as requested\n" );
		return;
	}

	if (!sio_start(m_hdl))
	{
		printf( "sndio: sio_start failed\n" );
		return;
	}

	_success_ful = true;
}

AudioSndio::~AudioSndio()
{
	stopProcessing();
	if (m_hdl != NULL)
	{
		sio_close( m_hdl );
		m_hdl = NULL;
	}
}

void AudioSndio::startProcessing( void )
{
	if( !isRunning() )
	{
		start( QThread::HighPriority );
	}
}

void AudioSndio::stopProcessing( void )
{
	stopProcessingThread( this );
}

void AudioSndio::applyQualitySettings( void )
{
	if( hqAudio() )
	{
		setSampleRate( Engine::mixer()->processingSampleRate() );

		/* change sample rate to sampleRate() */
	}

	AudioDevice::applyQualitySettings();
}

void AudioSndio::run( void )
{
	surroundSampleFrame * temp =
	    new surroundSampleFrame[mixer()->framesPerPeriod()];
	int_sample_t * outbuf =
	    new int_sample_t[mixer()->framesPerPeriod() * channels()];

	while( true )
	{
		const fpp_t frames = getNextBuffer( temp );
		if( !frames )
		{
			break;
		}

		uint bytes = convertToS16( temp, frames,
		    mixer()->masterGain(), outbuf, m_convertEndian );
		if( sio_write( m_hdl, outbuf, bytes ) != bytes )
		{
			break;
		}
	}

	delete[] temp;
	delete[] outbuf;
}

AudioSndio::setupWidget::setupWidget( QWidget * _parent ) :
	AudioDeviceSetupWidget( AudioSndio::name(), _parent )
{
	m_device = new QLineEdit( "", this );
	m_device->setGeometry( 10, 20, 160, 20 );

	QLabel * dev_lbl = new QLabel( tr( "Device" ), this );
	dev_lbl->setFont( pointSize<6>( dev_lbl->font() ) );
	dev_lbl->setGeometry( 10, 40, 160, 10 );

	LcdSpinBoxModel * m = new LcdSpinBoxModel( /* this */ );
	m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
	m->setStep( 2 );
	m->setValue( ConfigManager::inst()->value( "audiosndio",
	    "channels" ).toInt() );

	m_channels = new LcdSpinBox( 1, this );
	m_channels->setModel( m );
	m_channels->setLabel( tr( "Channels" ) );
	m_channels->move( 180, 20 );

}

AudioSndio::setupWidget::~setupWidget()
{

}

void AudioSndio::setupWidget::saveSettings( void )
{
	ConfigManager::inst()->setValue( "audiosndio", "device",
	    m_device->text() );
	ConfigManager::inst()->setValue( "audiosndio", "channels",
	    QString::number( m_channels->value<int>() ) );
}

#endif	/* LMMS_HAVE_SNDIO */


code:
/*
 * AudioSoundIo.cpp - device-class that performs PCM-output via libsoundio
 *
 * Copyright (c) 2015 Andrew Kelley <superjoe30@gmail.com>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "AudioSoundIo.h"

#ifdef LMMS_HAVE_SOUNDIO

#include <QLabel>
#include <QLineEdit>

#include "Engine.h"
#include "debug.h"
#include "ConfigManager.h"
#include "gui_templates.h"
#include "ComboBox.h"
#include "Mixer.h"

AudioSoundIo::AudioSoundIo( bool & outSuccessful, Mixer * _mixer ) :
	AudioDevice( qBound<ch_cnt_t>(
		DEFAULT_CHANNELS,
		ConfigManager::inst()->value( "audiosoundio", "channels" ).toInt(),
		SURROUND_CHANNELS ), _mixer )
{
	outSuccessful = false;
	m_soundio = NULL;
	m_outstream = NULL;
	m_outBuf = NULL;
	m_disconnectErr = 0;
	m_outBufFrameIndex = 0;
	m_outBufFramesTotal = 0;
	m_stopped = true;
	m_outstreamStarted = false;

	m_soundio = soundio_create();
	if (!m_soundio)
	{
		fprintf(stderr, "Unable to initialize soundio: out of memory\n");
		return;
	}

	m_soundio->app_name = "LMMS";
	m_soundio->userdata = this;
	m_soundio->on_backend_disconnect = staticOnBackendDisconnect;

	const QString& configBackend = ConfigManager::inst()->value( "audiosoundio", "backend" );
	const QString& configDeviceId = ConfigManager::inst()->value( "audiosoundio", "out_device_id" );
	const QString& configDeviceRaw = ConfigManager::inst()->value( "audiosoundio", "out_device_raw" );

	int err;
	int outDeviceCount = 0;
	int backendCount = soundio_backend_count(m_soundio);
	for (int i = 0; i < backendCount; i += 1)
	{
		SoundIoBackend backend = soundio_get_backend(m_soundio, i);
		if (configBackend == soundio_backend_name(backend))
		{
			if ((err = soundio_connect_backend(m_soundio, backend)))
			{
				// error occurred, leave outDeviceCount 0
			}
			else
			{
				soundio_flush_events(m_soundio);
				if (m_disconnectErr)
				{
					fprintf(stderr, "Unable to initialize soundio: %s\n", soundio_strerror(m_disconnectErr));
					return;
				}
				outDeviceCount = soundio_output_device_count(m_soundio);
			}
			break;
		}
	}

	if (outDeviceCount <= 0)
	{
		// try connecting to the default backend
		if ((err = soundio_connect(m_soundio)))
		{
			fprintf(stderr, "Unable to initialize soundio: %s\n", soundio_strerror(err));
			return;
		}

		soundio_flush_events(m_soundio);
		if (m_disconnectErr)
		{
			fprintf(stderr, "Unable to initialize soundio: %s\n", soundio_strerror(m_disconnectErr));
			return;
		}

		outDeviceCount = soundio_output_device_count(m_soundio);

		if (outDeviceCount <= 0)
		{
			fprintf(stderr, "Unable to initialize soundio: no devices found\n");
			return;
		}
	}

	int selected_device_index = soundio_default_output_device_index(m_soundio);

	bool wantRaw = (configDeviceRaw == "yes");
	for (int i = 0; i < outDeviceCount; i += 1)
	{
		SoundIoDevice *device = soundio_get_output_device(m_soundio, i);
		bool isThisOne = (configDeviceId == device->id && wantRaw == device->is_raw);
		soundio_device_unref(device);
		if (isThisOne)
		{
			selected_device_index = i;
			break;
		}
	}

	SoundIoDevice *device = soundio_get_output_device(m_soundio, selected_device_index);
	m_outstream = soundio_outstream_create(device);
	soundio_device_unref(device);

	if (!m_outstream)
	{
		fprintf(stderr, "Unable to initialize soundio: out of memory\n");
		return;
	}

	int currentSampleRate = sampleRate();
	int closestSupportedSampleRate = -1;

	for (int i = 0; i < device->sample_rate_count; i += 1)
	{
		SoundIoSampleRateRange *range = &device->sample_rates[i];
		if (range->min <= currentSampleRate && currentSampleRate <= range->max)
		{
			closestSupportedSampleRate = currentSampleRate;
			break;
		}
		if (closestSupportedSampleRate == -1 ||
			abs(range->max - currentSampleRate) < abs(closestSupportedSampleRate - currentSampleRate))
		{
			closestSupportedSampleRate = range->max;
		}
	}

	if (closestSupportedSampleRate != currentSampleRate)
	{
		setSampleRate(closestSupportedSampleRate);
		currentSampleRate = closestSupportedSampleRate;
	}

	m_outstream->name = "LMMS";
	m_outstream->software_latency = (double)mixer()->framesPerPeriod() / (double)currentSampleRate;
	m_outstream->userdata = this;
	m_outstream->write_callback = staticWriteCallback;
	m_outstream->error_callback = staticErrorCallback;
	m_outstream->underflow_callback = staticUnderflowCallback;
	m_outstream->sample_rate = currentSampleRate;
	m_outstream->layout = *soundio_channel_layout_get_default(channels());
	m_outstream->format = SoundIoFormatFloat32NE;

	if ((err = soundio_outstream_open(m_outstream)))
	{
		fprintf(stderr, "Unable to initialize soundio: %s\n", soundio_strerror(err));
		return;
	}

	fprintf(stderr, "Output device: '%s' backend: '%s'\n",
			device->name, soundio_backend_name(m_soundio->current_backend));

	outSuccessful = true;
}

void AudioSoundIo::onBackendDisconnect(int err)
{
	m_disconnectErr = err;
}

AudioSoundIo::~AudioSoundIo()
{
	stopProcessing();

	if (m_outstream)
	{
		soundio_outstream_destroy(m_outstream);
	}

	if (m_soundio)
	{
		soundio_destroy(m_soundio);
		m_soundio = NULL;
	}
}

void AudioSoundIo::startProcessing()
{
	int err;

	m_outBufFrameIndex = 0;
	m_outBufFramesTotal = 0;
	m_outBufSize = mixer()->framesPerPeriod();

	m_outBuf = new surroundSampleFrame[m_outBufSize];

	if (! m_outstreamStarted)
	{
		if ((err = soundio_outstream_start(m_outstream)))
		{
			fprintf(stderr,
				"AudioSoundIo::startProcessing() :: soundio unable to start stream: %s\n",
				soundio_strerror(err));
		} else {
			m_outstreamStarted = true;
		}
	}

	m_stopped = false;

	if ((err = soundio_outstream_pause(m_outstream, false)))
	{
		m_stopped = true;
		fprintf(stderr,
			"AudioSoundIo::startProcessing() :: resuming result error: %s\n",
			soundio_strerror(err));
	}
}

void AudioSoundIo::stopProcessing()
{
	int err;

	m_stopped = true;
	if (m_outstream)
	{
		if ((err = soundio_outstream_pause(m_outstream, true)))
		{
			fprintf(stderr,
				"AudioSoundIo::stopProcessing() :: pausing result error: %s\n",
				soundio_strerror(err));
		}
	}

	if (m_outBuf)
	{
		delete[] m_outBuf;
		m_outBuf = NULL;
	}
}

void AudioSoundIo::errorCallback(int err)
{
	fprintf(stderr, "soundio: error streaming: %s\n", soundio_strerror(err));
}

void AudioSoundIo::underflowCallback()
{
	fprintf(stderr, "soundio: buffer underflow reported\n");
}

void AudioSoundIo::writeCallback(int frameCountMin, int frameCountMax)
{
	if (m_stopped) {return;}
	const struct SoundIoChannelLayout *layout = &m_outstream->layout;
	SoundIoChannelArea *areas;
	int bytesPerSample = m_outstream->bytes_per_sample;
	int err;

	const float gain = mixer()->masterGain();

	int framesLeft = frameCountMax;

	while (framesLeft > 0)
	{
		int frameCount = framesLeft;
		if ((err = soundio_outstream_begin_write(m_outstream, &areas, &frameCount)))
		{
			errorCallback(err);
			return;
		}

		if (!frameCount)
			break;

		if (m_stopped)
		{
			for (int channel = 0; channel < layout->channel_count; ++channel)
			{
				memset(areas[channel].ptr, 0, bytesPerSample * frameCount);
				areas[channel].ptr += areas[channel].step * frameCount;
			}
			continue;
		}

		for (int frame = 0; frame < frameCount; frame += 1)
		{
			if (m_outBufFrameIndex >= m_outBufFramesTotal)
			{
				m_outBufFramesTotal = getNextBuffer(m_outBuf);
				if (m_outBufFramesTotal == 0)
				{
					m_stopped = true;
					break;
				}
				m_outBufFrameIndex = 0;
			}

			for (int channel = 0; channel < layout->channel_count; channel += 1)
			{
				float sample = gain * m_outBuf[m_outBufFrameIndex][channel];
				memcpy(areas[channel].ptr, &sample, bytesPerSample);
				areas[channel].ptr += areas[channel].step;
			}
			m_outBufFrameIndex += 1;
		}

		if ((err = soundio_outstream_end_write(m_outstream)))
		{
			errorCallback(err);
			return;
		}

		framesLeft -= frameCount;
	}
}

AudioSoundIoSetupUtil::~AudioSoundIoSetupUtil()
{
}

void AudioSoundIoSetupUtil::reconnectSoundIo()
{
	((AudioSoundIo::setupWidget *)m_setupWidget)->reconnectSoundIo();
}

void AudioSoundIoSetupUtil::updateDevices()
{
	((AudioSoundIo::setupWidget *)m_setupWidget)->updateDevices();
}

static void setupWidgetOnBackendDisconnect(SoundIo *soundio, int err)
{
	AudioSoundIo::setupWidget *setupWidget = (AudioSoundIo::setupWidget *)soundio->userdata;
	setupWidget->reconnectSoundIo();
}

static void setup_widget_on_devices_change(SoundIo *soundio)
{
	AudioSoundIo::setupWidget *setupWidget = (AudioSoundIo::setupWidget *)soundio->userdata;
	setupWidget->updateDevices();
}

void AudioSoundIo::setupWidget::reconnectSoundIo()
{
	const QString& configBackend = m_isFirst ?
		ConfigManager::inst()->value( "audiosoundio", "backend" ) : m_backendModel.currentText();
	m_isFirst = false;

	soundio_disconnect(m_soundio);

	int err;
	int backend_index = m_backendModel.findText(configBackend);
	if (backend_index < 0)
	{
		if ((err = soundio_connect(m_soundio)))
		{
			fprintf(stderr, "soundio: unable to connect backend: %s\n", soundio_strerror(err));
			return;
		}
		backend_index = m_backendModel.findText(soundio_backend_name(m_soundio->current_backend));
		assert(backend_index >= 0);
	}
	else
	{
		SoundIoBackend backend = soundio_get_backend(m_soundio, backend_index);
		if ((err = soundio_connect_backend(m_soundio, backend)))
		{
			fprintf(stderr, "soundio: unable to connect %s backend: %s\n",
					soundio_backend_name(backend), soundio_strerror(err));
			if ((err = soundio_connect(m_soundio)))
			{
				fprintf(stderr, "soundio: unable to connect backend: %s\n", soundio_strerror(err));
				return;
			}
			backend_index = m_backendModel.findText(soundio_backend_name(m_soundio->current_backend));
			assert(backend_index >= 0);
		}
	}
	m_backendModel.setValue(backend_index);

	soundio_flush_events(m_soundio);

	const QString& configDeviceId = ConfigManager::inst()->value( "audiosoundio", "out_device_id" );
	const QString& configDeviceRaw = ConfigManager::inst()->value( "audiosoundio", "out_device_raw" );

	int deviceIndex = m_defaultOutIndex;
	bool wantRaw = (configDeviceRaw == "yes");
	for (int i = 0; i < m_deviceList.length(); i += 1)
	{
		const DeviceId *deviceId = &m_deviceList.at(i);
		if (deviceId->id == configDeviceId && deviceId->is_raw == wantRaw)
		{
			deviceIndex = i;
			break;
		}
	}

	m_deviceModel.setValue(deviceIndex);
}

void AudioSoundIo::setupWidget::updateDevices()
{
	m_defaultOutIndex = soundio_default_output_device_index(m_soundio);

	// get devices for selected backend
	m_deviceModel.clear();
	m_deviceList.clear();
	int outDeviceCount = soundio_output_device_count(m_soundio);
	for (int i = 0; i < outDeviceCount; i += 1)
	{
		SoundIoDevice *device = soundio_get_output_device(m_soundio, i);

		QString raw_text = device->is_raw ? " (raw)" : "";
		QString default_text = (i == m_defaultOutIndex) ? " (default)" : "";

		m_deviceModel.addItem(device->name + raw_text + default_text);
		m_deviceList.append({device->id, device->is_raw});
	}

}

AudioSoundIo::setupWidget::setupWidget( QWidget * _parent ) :
	AudioDeviceSetupWidget( AudioSoundIo::name(), _parent )
{
	m_setupUtil.m_setupWidget = this;

	m_backend = new ComboBox( this, "BACKEND" );
	m_backend->setGeometry( 64, 15, 260, 20 );

	QLabel * backend_lbl = new QLabel( tr( "Backend" ), this );
	backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) );
	backend_lbl->move( 8, 18 );

	m_device = new ComboBox( this, "DEVICE" );
	m_device->setGeometry( 64, 35, 260, 20 );

	QLabel * dev_lbl = new QLabel( tr( "Device" ), this );
	dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
	dev_lbl->move( 8, 38 );

	// Setup models
	m_soundio = soundio_create();
	if (!m_soundio)
	{
		fprintf(stderr, "Unable to initialize soundio: out of memory\n");
		return;
	}
	m_soundio->userdata = this;
	m_soundio->on_backend_disconnect = setupWidgetOnBackendDisconnect;
	m_soundio->on_devices_change = setup_widget_on_devices_change;
	m_soundio->app_name = "LMMS";

	int backendCount = soundio_backend_count(m_soundio);
	for (int i = 0; i < backendCount; i += 1)
	{
		SoundIoBackend backend = soundio_get_backend(m_soundio, i);
		m_backendModel.addItem(soundio_backend_name(backend));
	}

	m_isFirst = true;

	reconnectSoundIo();

	bool ok = connect( &m_backendModel, SIGNAL( dataChanged() ), &m_setupUtil, SLOT( reconnectSoundIo() ) );
	assert(ok);

	m_backend->setModel( &m_backendModel );
	m_device->setModel( &m_deviceModel );
}

AudioSoundIo::setupWidget::~setupWidget()
{
	bool ok = disconnect( &m_backendModel, SIGNAL( dataChanged() ), &m_setupUtil, SLOT( reconnectSoundIo() ) );
	assert(ok);
	if (m_soundio)
	{
		soundio_destroy(m_soundio);
		m_soundio = NULL;
	}
}

void AudioSoundIo::setupWidget::saveSettings()
{
	int deviceIndex = m_deviceModel.value();
	const DeviceId *deviceId = &m_deviceList.at(deviceIndex);

	QString configDeviceRaw = deviceId->is_raw ? "yes" : "no";

	ConfigManager::inst()->setValue( "audiosoundio", "backend", m_backendModel.currentText());
	ConfigManager::inst()->setValue( "audiosoundio", "out_device_id", deviceId->id);
	ConfigManager::inst()->setValue( "audiosoundio", "out_device_raw", configDeviceRaw);
}

#endif


code:
/*
 * base64.cpp - namespace base64 with methods for encoding/decoding binary data
 *              to/from base64
 *
 * Copyright (c) 2006-2008 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "base64.h"

#include <QBuffer>
#include <QDataStream>

namespace base64
{

QVariant decode( const QString & _b64, QVariant::Type _force_type )
{
	char * dst = NULL;
	int dsize = 0;
	base64::decode( _b64, &dst, &dsize );
	QByteArray ba( dst, dsize );
	QBuffer buf( &ba );
	buf.open( QBuffer::ReadOnly );
	QDataStream in( &buf );
	QVariant ret;
	in >> ret;
	if( _force_type != QVariant::Invalid && ret.type() != _force_type )
	{
		buf.reset();
		in.setVersion( QDataStream::Qt_3_3 );
		in >> ret;
	}
	delete[] dst;
	return( ret );
}

} ;


code:
/*
 * fft_helpers.cpp - some functions around FFT analysis
 *
 * Copyright (c) 2008-2012 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 * Copyright (c) 2019 Martin Pavelek <he29.HS/at/gmail.com>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "fft_helpers.h"

#include <cmath>
#include "lmms_constants.h"

/* Returns biggest value from abs_spectrum[spec_size] array.
 *
 * return -1 on error, otherwise the maximum value
 */
float maximum(const float *abs_spectrum, unsigned int spec_size)
{
	if (abs_spectrum == NULL) {return -1;}
	if (spec_size == 0) {return -1;}

	float maxi = 0;

	for (unsigned int i = 0; i < spec_size; i++)
	{
		if (abs_spectrum[i] > maxi) {maxi = abs_spectrum[i];}
	}
	return maxi;
}

float maximum(const std::vector<float> &abs_spectrum)
{
	return maximum(abs_spectrum.data(), abs_spectrum.size());
}

/* Normalize the array of absolute magnitudes to a 0..1 range.
 * Block size refers to FFT block size before any zero padding.
 *
 * return -1 on error, 0 on success
 */
int normalize(const float *abs_spectrum, float *norm_spectrum, unsigned int bin_count, unsigned int block_size)
{
	if (abs_spectrum == NULL || norm_spectrum == NULL) {return -1;}
	if (bin_count == 0 || block_size == 0) {return -1;}

	block_size /= 2;
	for (unsigned int i = 0; i < bin_count; i++)
	{
		norm_spectrum[i] = abs_spectrum[i] / block_size;
	}
	return 0;
}

int normalize(const std::vector<float> &abs_spectrum, std::vector<float> &norm_spectrum, unsigned int block_size)
{
	if (abs_spectrum.size() != norm_spectrum.size()) {return -1;}

	return normalize(abs_spectrum.data(), norm_spectrum.data(), abs_spectrum.size(), block_size);
}

/* Check if the spectrum contains any non-zero value.
 *
 * return 1 if spectrum contains any non-zero value
 * return 0 otherwise
 */
int notEmpty(const std::vector<float> &spectrum)
{
	for (float s : spectrum)
	{
		if (s != 0) {return 1;}
	}
	return 0;
}

/* Precompute an FFT window function for later real-time use.
 *
 * return -1 on error
 */
int precomputeWindow(float *window, unsigned int length, FFT_WINDOWS type, bool normalized)
{
	if (window == NULL) {return -1;}

	float gain = 0;
	float a0;
	float a1;
	float a2;
	float a3;

	// constants taken from
	// https://en.wikipedia.org/wiki/Window_function#AList_of_window_functions
	switch (type)
	{
		default:
		case RECTANGULAR:
			for (unsigned int i = 0; i < length; i++) {window[i] = 1.0;}
			gain = 1;
			return 0;
		case BLACKMAN_HARRIS:
			a0 = 0.35875;
			a1 = 0.48829;
			a2 = 0.14128;
			a3 = 0.01168;
			break;
		case HAMMING:
			a0 = 0.54;
			a1 = 1.0 - a0;
			a2 = 0;
			a3 = 0;
			break;
		case HANNING:
			a0 = 0.5;
			a1 = 1.0 - a0;
			a2 = 0;
			a3 = 0;
			break;
	}

	// common computation for cosine-sum based windows
	for (unsigned int i = 0; i < length; i++)
	{
		window[i] =	(a0 - a1 * cos(2 * F_PI * i / ((float)length - 1.0))
						+ a2 * cos(4 * F_PI * i / ((float)length - 1.0))
						- a3 * cos(6 * F_PI * i / ((float)length - 1.0)));
		gain += window[i];
	}

	// apply amplitude correction
	gain /= (float) length;
	for (unsigned int i = 0; i < length; i++) {window[i] /= gain;}

	return 0;
}

/* Compute absolute values of complex_buffer, save to absspec_buffer.
 * Take care that - compl_len is not bigger than complex_buffer!
 *                - absspec buffer is big enough!
 *
 * return 0 on success, else -1
 */
int absspec(const fftwf_complex *complex_buffer, float *absspec_buffer, unsigned int compl_length)
{
	if (complex_buffer == NULL || absspec_buffer == NULL) {return -1;}
	if (compl_length == 0) {return -1;}

	for (unsigned int i = 0; i < compl_length; i++)
	{
		absspec_buffer[i] = (float)sqrt(complex_buffer[i][0] * complex_buffer[i][0]
							+ complex_buffer[i][1] * complex_buffer[i][1]);
	}

	return 0;
}

/* Build fewer subbands from many absolute spectrum values.
 * Take care that - compressedbands[] array num_new elements long
 *                - num_old > num_new
 *
 * return 0 on success, else -1
 */
int compressbands(const float *absspec_buffer, float *compressedband, int num_old, int num_new, int bottom, int top)
{
	if (absspec_buffer == NULL || compressedband == NULL) {return -1;}
	if (num_old < num_new) {return -1;}
	if (num_old <= 0 || num_new <= 0) {return -1;}
	if (bottom < 0) {bottom = 0;}
	if (top >= num_old) {top = num_old - 1;}

	int usefromold = num_old - (num_old - top) - bottom;

	float ratio = (float)usefromold / (float)num_new;

	// for each new subband
	for (int i = 0; i < num_new; i++)
	{
		compressedband[i] = 0;

		float j_min = (i * ratio) + bottom;

		if (j_min < 0) {j_min = bottom;}

		float j_max = j_min + ratio;

		for (float j = (int)j_min; j <= j_max; j++)
		{
			compressedband[i] += absspec_buffer[(int)j];
		}
	}

	return 0;
}


code:
/*
 * Lv2Basics.cpp - basic Lv2 functions
 *
 * Copyright (c) 2019-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2Basics.h"

#ifdef LMMS_HAVE_LV2

QString qStringFromPluginNode(const LilvPlugin* plug,
	LilvNode* (*getFunc)(const LilvPlugin*))
{
	return QString::fromUtf8(
		lilv_node_as_string(AutoLilvNode((*getFunc)(plug)).get()));
}

QString qStringFromPortName(const LilvPlugin* plug, const LilvPort* port)
{
	return QString::fromUtf8(
		lilv_node_as_string(AutoLilvNode(lilv_port_get_name(plug, port)).get()));
}

std::string stdStringFromPortName(const LilvPlugin* plug, const LilvPort* port)
{
	return std::string(
			lilv_node_as_string(AutoLilvNode(lilv_port_get_name(plug, port)).get()));
}

#endif // LMMS_HAVE_LV2


code:
/*
 * Lv2ControlBase.cpp - Lv2 control base class
 *
 * Copyright (c) 2018-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2ControlBase.h"

#ifdef LMMS_HAVE_LV2

#include <algorithm>
#include <QtGlobal>

#include "Engine.h"
#include "Lv2Manager.h"
#include "Lv2Proc.h"

Plugin::PluginTypes Lv2ControlBase::check(const LilvPlugin *plugin,
	std::vector<PluginIssue> &issues)
{
	// for some reason, all checks can be done by one processor...
	return Lv2Proc::check(plugin, issues);
}

Lv2ControlBase::Lv2ControlBase(Model* that, const QString &uri) :
	m_plugin(Engine::getLv2Manager()->getPlugin(uri))
{
	if (m_plugin)
	{
		int channelsLeft = DEFAULT_CHANNELS; // LMMS plugins are stereo
		while (channelsLeft > 0)
		{
			std::unique_ptr<Lv2Proc> newOne = std::make_unique<Lv2Proc>(m_plugin, that);
			if (newOne->isValid())
			{
				channelsLeft -= std::max(
					1 + static_cast<bool>(newOne->inPorts().m_right),
					1 + static_cast<bool>(newOne->outPorts().m_right));
				Q_ASSERT(channelsLeft >= 0);
				m_procs.push_back(std::move(newOne));
			}
			else
			{
				qCritical() << "Failed instantiating LV2 processor";
				m_valid = false;
				channelsLeft = 0;
			}
		}
		if (m_valid)
		{
			m_channelsPerProc = DEFAULT_CHANNELS / m_procs.size();
			linkAllModels();
		}
	}
	else
	{
		qCritical() << "No Lv2 plugin found for URI" << uri;
		m_valid = false;
	}
}

Lv2ControlBase::~Lv2ControlBase() {}

LinkedModelGroup *Lv2ControlBase::getGroup(std::size_t idx)
{
	return (m_procs.size() > idx) ? m_procs[idx].get() : nullptr;
}

const LinkedModelGroup *Lv2ControlBase::getGroup(std::size_t idx) const
{
	return (m_procs.size() > idx) ? m_procs[idx].get() : nullptr;
}

void Lv2ControlBase::copyModelsFromLmms() {
	for (auto& c : m_procs) { c->copyModelsFromCore(); }
}

void Lv2ControlBase::copyModelsToLmms() const
{
	for (auto& c : m_procs) { c->copyModelsToCore(); }
}

void Lv2ControlBase::copyBuffersFromLmms(const sampleFrame *buf, fpp_t frames) {
	unsigned firstChan = 0; // tell the procs which channels they shall read from
	for (auto& c : m_procs) {
		c->copyBuffersFromCore(buf, firstChan, m_channelsPerProc, frames);
		firstChan += m_channelsPerProc;
	}
}

void Lv2ControlBase::copyBuffersToLmms(sampleFrame *buf, fpp_t frames) const {
	unsigned firstChan = 0; // tell the procs which channels they shall write to
	for (const auto& c : m_procs) {
		c->copyBuffersToCore(buf, firstChan, m_channelsPerProc, frames);
		firstChan += m_channelsPerProc;
	}
}

void Lv2ControlBase::run(fpp_t frames) {
	for (auto& c : m_procs) { c->run(frames); }
}

void Lv2ControlBase::saveSettings(QDomDocument &doc, QDomElement &that)
{
	LinkedModelGroups::saveSettings(doc, that);

	// TODO: save state if supported by plugin
}

void Lv2ControlBase::loadSettings(const QDomElement &that)
{
	LinkedModelGroups::loadSettings(that);

	// TODO: load state if supported by plugin
}

void Lv2ControlBase::loadFile(const QString &file)
{
	(void)file;
}

void Lv2ControlBase::reloadPlugin()
{
	// TODO
}

std::size_t Lv2ControlBase::controlCount() const {
	std::size_t res = 0;
	for (const auto& c : m_procs) { res += c->controlCount(); }
	return res;
}

bool Lv2ControlBase::hasNoteInput() const
{
	return std::any_of(m_procs.begin(), m_procs.end(),
		[](const auto& c) { return c->hasNoteInput(); });
}

void Lv2ControlBase::handleMidiInputEvent(const MidiEvent &event,
	const TimePos &time, f_cnt_t offset)
{
	for (auto& c : m_procs) { c->handleMidiInputEvent(event, time, offset); }
}

#endif // LMMS_HAVE_LV2


code:
/*
 * lv2_evbuf.cpp - Lv2 event buffer implementation
 *
 * Copyright (c) 2019-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

/*
 * The original code was written by David Robillard <http://drobilla.net>
 */

#include "Lv2Evbuf.h"

#ifdef LMMS_HAVE_LV2

#include <cassert>
#include <cstdlib>
#include <cstring>

#include <lv2/lv2plug.in/ns/ext/atom/atom.h>

struct LV2_Evbuf_Impl {
	uint32_t capacity;
	uint32_t atom_Chunk;
	uint32_t atom_Sequence;
	LV2_Atom_Sequence buf;
};

static inline uint32_t
lv2_evbuf_pad_size(uint32_t size)
{
	return (size + 7) & (~7);
}

LV2_Evbuf*
lv2_evbuf_new(uint32_t capacity, uint32_t atom_Chunk, uint32_t atom_Sequence)
{
	// FIXME: memory must be 64-bit aligned
	LV2_Evbuf* evbuf = (LV2_Evbuf*)malloc(
		sizeof(LV2_Evbuf) + sizeof(LV2_Atom_Sequence) + capacity);
	evbuf->capacity = capacity;
	evbuf->atom_Chunk = atom_Chunk;
	evbuf->atom_Sequence = atom_Sequence;
	lv2_evbuf_reset(evbuf, true);
	return evbuf;
}

void
lv2_evbuf_free(LV2_Evbuf* evbuf)
{
	free(evbuf);
}

void
lv2_evbuf_reset(LV2_Evbuf* evbuf, bool input)
{
	if (input) {
		evbuf->buf.atom.size = sizeof(LV2_Atom_Sequence_Body);
		evbuf->buf.atom.type = evbuf->atom_Sequence;
	} else {
		evbuf->buf.atom.size = evbuf->capacity;
		evbuf->buf.atom.type = evbuf->atom_Chunk;
	}
}

uint32_t
lv2_evbuf_get_size(LV2_Evbuf* evbuf)
{
	assert(evbuf->buf.atom.type != evbuf->atom_Sequence
	|| evbuf->buf.atom.size >= sizeof(LV2_Atom_Sequence_Body));
	return evbuf->buf.atom.type == evbuf->atom_Sequence
		? evbuf->buf.atom.size - sizeof(LV2_Atom_Sequence_Body)
		: 0;
}

void*
lv2_evbuf_get_buffer(LV2_Evbuf* evbuf)
{
	return &evbuf->buf;
}

LV2_Evbuf_Iterator
lv2_evbuf_begin(LV2_Evbuf* evbuf)
{
	LV2_Evbuf_Iterator iter = { evbuf, 0 };
	return iter;
}

LV2_Evbuf_Iterator
lv2_evbuf_end(LV2_Evbuf* evbuf)
{
	const uint32_t size = lv2_evbuf_get_size(evbuf);
	const LV2_Evbuf_Iterator iter = { evbuf, lv2_evbuf_pad_size(size) };
	return iter;
}

bool
lv2_evbuf_is_valid(LV2_Evbuf_Iterator iter)
{
	return iter.offset < lv2_evbuf_get_size(iter.evbuf);
}

LV2_Evbuf_Iterator
lv2_evbuf_next(LV2_Evbuf_Iterator iter)
{
	if (!lv2_evbuf_is_valid(iter)) {
		return iter;
	}

	LV2_Evbuf* evbuf  = iter.evbuf;
	uint32_t   offset = iter.offset;
	uint32_t   size;
	size = ((LV2_Atom_Event*)
		((char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, &evbuf->buf.atom)
		+ offset))->body.size;
	offset += lv2_evbuf_pad_size(sizeof(LV2_Atom_Event) + size);

	LV2_Evbuf_Iterator next = { evbuf, offset };
	return next;
}

bool
lv2_evbuf_get(LV2_Evbuf_Iterator iter,
	uint32_t* frames,
	uint32_t* type,
	uint32_t* size,
	uint8_t** data)
{
	*frames = *type = *size = 0;
	*data = NULL;

	if (!lv2_evbuf_is_valid(iter)) {
		return false;
	}

	LV2_Atom_Sequence* aseq = &iter.evbuf->buf;
	LV2_Atom_Event*    aev  = (LV2_Atom_Event*)(
		(char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, aseq) + iter.offset);

	*frames = aev->time.frames;
	*type = aev->body.type;
	*size = aev->body.size;
	*data = (uint8_t*)LV2_ATOM_BODY(&aev->body);

	return true;
}

bool
lv2_evbuf_write(LV2_Evbuf_Iterator* iter,
	uint32_t frames,
	uint32_t type,
	uint32_t size,
	const uint8_t* data)
{
	LV2_Atom_Sequence* aseq = &iter->evbuf->buf;
	if (iter->evbuf->capacity - sizeof(LV2_Atom) - aseq->atom.size <
		sizeof(LV2_Atom_Event) + size) {
		return false;
	}

	LV2_Atom_Event* aev = (LV2_Atom_Event*)(
		(char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, aseq) + iter->offset);

	aev->time.frames = frames;
	aev->body.type = type;
	aev->body.size = size;
	memcpy(LV2_ATOM_BODY(&aev->body), data, size);

	size = lv2_evbuf_pad_size(sizeof(LV2_Atom_Event) + size);
	aseq->atom.size += size;
	iter->offset += size;

	return true;
}

#endif // LMMS_HAVE_LV2


code:
/*
 * Lv2Features.cpp - Lv2Features implementation
 *
 * Copyright (c) 2020-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2Features.h"

#ifdef LMMS_HAVE_LV2

#include <QtGlobal>

#include "Engine.h"
#include "Lv2Manager.h"

bool Lv2Features::isFeatureSupported(const char* featName)
{
	return Engine::getLv2Manager()->isFeatureSupported(featName);
}

Lv2Features::Lv2Features()
{
	const Lv2Manager* man = Engine::getLv2Manager();
	// create (yet empty) map feature URI -> feature
	for(const char* uri : man->supportedFeatureURIs())
	{
		m_featureByUri.emplace(uri, nullptr);
	}
}

void Lv2Features::initCommon()
{
	Lv2Manager* man = Engine::getLv2Manager();
	// init m_featureByUri with the plugin-common features
	operator[](LV2_URID__map) = man->uridMap().mapFeature();
	operator[](LV2_URID__unmap) = man->uridMap().unmapFeature();
}

void Lv2Features::createFeatureVectors()
{
	// create vector of features
	for(std::pair<const char* const, void*>& pr : m_featureByUri)
	{
		Q_ASSERT(pr.second != nullptr);
		m_features.push_back(LV2_Feature { pr.first, pr.second });
	}

	// create pointer vector (for lilv_plugin_instantiate)
	m_featurePointers.reserve(m_features.size() + 1);
	for(std::size_t i = 0; i < m_features.size(); ++i)
	{
		m_featurePointers.push_back(&m_features[i]);
	}
	m_featurePointers.push_back(nullptr);
}

void *&Lv2Features::operator[](const char *featName)
{
	auto itr = m_featureByUri.find(featName);
	Q_ASSERT(itr != m_featureByUri.end());
	return itr->second;
}

#endif // LMMS_HAVE_LV2


code:
/*
 * Lv2Manager.cpp - Implementation of Lv2Manager class
 *
 * Copyright (c) 2018-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2Manager.h"

#ifdef LMMS_HAVE_LV2

#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <lilv/lilv.h>
#include <lv2.h>
#include <lv2/lv2plug.in/ns/ext/options/options.h>
#include <QDebug>
#include <QDir>
#include <QLibrary>
#include <QElapsedTimer>

#include "ConfigManager.h"
#include "Engine.h"
#include "Plugin.h"
#include "PluginFactory.h"
#include "Lv2ControlBase.h"
#include "Lv2Options.h"
#include "PluginIssue.h"

const std::set<const char*, Lv2Manager::CmpStr> Lv2Manager::pluginBlacklist =
{
	// github.com/calf-studio-gear/calf, #278
	"http://calf.sourceforge.net/plugins/Analyzer",
	"http://calf.sourceforge.net/plugins/BassEnhancer",
	"http://calf.sourceforge.net/plugins/CompensationDelay",
	"http://calf.sourceforge.net/plugins/Crusher",
	"http://calf.sourceforge.net/plugins/Exciter",
	"http://calf.sourceforge.net/plugins/Saturator",
	"http://calf.sourceforge.net/plugins/StereoTools",
	"http://calf.sourceforge.net/plugins/TapeSimulator",
	"http://calf.sourceforge.net/plugins/TransientDesigner",
	"http://calf.sourceforge.net/plugins/Vinyl"
};

Lv2Manager::Lv2Manager() :
	m_uridCache(m_uridMap)
{
	const char* dbgStr = getenv("LMMS_LV2_DEBUG");
	m_debug = (dbgStr && *dbgStr);

	m_world = lilv_world_new();
	lilv_world_load_all(m_world);

	m_supportedFeatureURIs.insert(LV2_URID__map);
	m_supportedFeatureURIs.insert(LV2_URID__unmap);
	m_supportedFeatureURIs.insert(LV2_OPTIONS__options);

	auto supportOpt = [this](Lv2UridCache::Id id)
	{
		Lv2Options::supportOption(uridCache()[id]);
	};
	supportOpt(Lv2UridCache::Id::param_sampleRate);
	supportOpt(Lv2UridCache::Id::bufsz_maxBlockLength);
	supportOpt(Lv2UridCache::Id::bufsz_minBlockLength);
	supportOpt(Lv2UridCache::Id::bufsz_nominalBlockLength);
	supportOpt(Lv2UridCache::Id::bufsz_sequenceSize);
}

Lv2Manager::~Lv2Manager()
{
	lilv_world_free(m_world);
}

AutoLilvNode Lv2Manager::uri(const char *uriStr)
{
	return AutoLilvNode(lilv_new_uri(m_world, uriStr));
}

const LilvPlugin *Lv2Manager::getPlugin(const std::string &uri)
{
	auto itr = m_lv2InfoMap.find(uri);
	return itr == m_lv2InfoMap.end() ? nullptr : itr->second.plugin();
}

const LilvPlugin *Lv2Manager::getPlugin(const QString &uri)
{
	return getPlugin(uri.toStdString());
}

void Lv2Manager::initPlugins()
{
	const LilvPlugins* plugins = lilv_world_get_all_plugins(m_world);
	std::size_t pluginCount = 0, pluginsLoaded = 0;
	QElapsedTimer timer;
	timer.start();

	unsigned blacklisted = 0;
	LILV_FOREACH(plugins, itr, plugins)
	{
		const LilvPlugin* curPlug = lilv_plugins_get(plugins, itr);

		std::vector<PluginIssue> issues;
		Plugin::PluginTypes type = Lv2ControlBase::check(curPlug, issues);
		std::sort(issues.begin(), issues.end());
		auto last = std::unique(issues.begin(), issues.end());
		issues.erase(last, issues.end());
		if (m_debug && issues.size())
		{
			qDebug() << "Lv2 plugin"
				<< qStringFromPluginNode(curPlug, lilv_plugin_get_name)
				<< "(URI:"
				<< lilv_node_as_uri(lilv_plugin_get_uri(curPlug))
				<< ") can not be loaded:";
			for (const PluginIssue& iss : issues) { qDebug() << "  - " << iss; }
		}

		Lv2Info info(curPlug, type, issues.empty());

		m_lv2InfoMap[lilv_node_as_uri(lilv_plugin_get_uri(curPlug))]
			= std::move(info);
		if(issues.empty()) { ++pluginsLoaded; }
		else
		{
			if(std::any_of(issues.begin(), issues.end(),
				[](const PluginIssue& iss) {
				return iss.type() == PluginIssueType::blacklisted; }))
			{
				++blacklisted;
			}
		}
		++pluginCount;
	}

	qDebug() << "Lv2 plugin SUMMARY:"
		<< pluginsLoaded << "of" << pluginCount << " loaded in"
		<< timer.elapsed() << "msecs.";
	if(pluginsLoaded != pluginCount)
	{
		if (m_debug)
		{
			qDebug() <<
				"If you don't want to see all this debug output, please set\n"
				"  environment variable \"LMMS_LV2_DEBUG\" to empty or\n"
				"  do not set it.";
		}
		else
		{
			qDebug() <<
				"For details about not loaded plugins, please set\n"
				"  environment variable \"LMMS_LV2_DEBUG\" to nonempty.";
		}
	}

	// TODO: might be better in the LMMS core
	if(Engine::ignorePluginBlacklist())
	{
		qWarning() <<
			"WARNING! Plugin blacklist disabled! If you want to use the blacklist,\n"
			"  please set environment variable \"LMMS_IGNORE_BLACKLIST\" to empty or\n"
			"  do not set it.";
	}
	else if(blacklisted > 0)
	{
		qDebug() <<
			"Lv2 Plugins blacklisted:" << blacklisted << "of" << pluginCount << "\n"
			"  If you want to ignore the blacklist (dangerous!), please set\n"
			"  environment variable \"LMMS_IGNORE_BLACKLIST\" to nonempty.";
	}
}

bool Lv2Manager::CmpStr::operator()(const char *a, const char *b) const
{
	return std::strcmp(a, b) < 0;
}

bool Lv2Manager::isFeatureSupported(const char *featName) const
{
	return m_supportedFeatureURIs.find(featName) != m_supportedFeatureURIs.end();
}

AutoLilvNodes Lv2Manager::findNodes(const LilvNode *subject,
	const LilvNode *predicate, const LilvNode *object)
{
	return AutoLilvNodes(lilv_world_find_nodes (m_world, subject, predicate, object));
}

// unused + untested yet
bool Lv2Manager::isSubclassOf(const LilvPluginClass* clvss, const char* uriStr)
{
	const LilvPluginClasses* allClasses = lilv_world_get_plugin_classes(m_world);
	const LilvPluginClass* root = lilv_world_get_plugin_class(m_world);
	const LilvPluginClass* search = lilv_plugin_classes_get_by_uri(allClasses,
					uri(uriStr).get());

	auto clssEq = [](const LilvPluginClass* pc1,
		const LilvPluginClass* pc2) -> bool
	{
		return lilv_node_equals(
			lilv_plugin_class_get_uri(pc1),
			lilv_plugin_class_get_uri(pc2));
	};
	bool isFound = false;
	while (!(isFound = clssEq(clvss, search)) && !clssEq(clvss, root))
	{
		clvss = lilv_plugin_classes_get_by_uri(allClasses,
			lilv_plugin_class_get_parent_uri(clvss));
	}
	return isFound;
}

#endif // LMMS_HAVE_LV2


code:
/*
 * Lv2Options.cpp - Lv2Options implementation
 *
 * Copyright (c) 2020-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2Options.h"

#ifdef LMMS_HAVE_LV2

#include <QtGlobal>

std::set<LV2_URID> Lv2Options::s_supportedOptions;

bool Lv2Options::isOptionSupported(LV2_URID key)
{
	return s_supportedOptions.find(key) != s_supportedOptions.end();
}

void Lv2Options::supportOption(LV2_URID key)
{
	const auto result = s_supportedOptions.insert(key);
	Q_ASSERT(result.second);
}

void Lv2Options::createOptionVectors()
{
	// create vector of options
	for(LV2_URID urid : s_supportedOptions)
	{
		auto itr = m_optionByUrid.find(urid);
		Q_ASSERT(itr != m_optionByUrid.end());
		m_options.push_back(itr->second);
	}
	LV2_Options_Option nullOption;
	nullOption.key = 0;
	nullOption.value = nullptr;
	m_options.push_back(nullOption);
}

void Lv2Options::initOption(LV2_URID key, uint32_t size, LV2_URID type,
	std::shared_ptr<void> value,
	LV2_Options_Context context, uint32_t subject)
{
	Q_ASSERT(isOptionSupported(key));

	LV2_Options_Option opt;
	opt.key = key;
	opt.context = context;
	opt.subject = subject;
	opt.size = size;
	opt.type = type;
	opt.value = value.get();

	const auto optResult = m_optionByUrid.emplace(key, opt);
	const auto valResult = m_optionValues.emplace(key, std::move(value));
	Q_ASSERT(optResult.second);
	Q_ASSERT(valResult.second);
}

#endif // LMMS_HAVE_LV2


code:
/*
 * Lv2Ports.cpp - Lv2 port classes implementation
 *
 * Copyright (c) 2019-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2Ports.h"

#ifdef LMMS_HAVE_LV2

#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/port-props/port-props.h>

#include "Engine.h"
#include "Lv2Basics.h"
#include "Lv2Manager.h"
#include "Lv2Evbuf.h"

namespace Lv2Ports {

const char *toStr(Flow pf)
{
	switch(pf)
	{
		case Flow::Unknown: return "unknown";
		case Flow::Input: return "input";
		case Flow::Output: return "output";
	}
	return "";
}

const char *toStr(Type pt)
{
	switch(pt)
	{
		case Type::Unknown: return "unknown";
		case Type::Control: return "control";
		case Type::Audio: return "audio";
		case Type::AtomSeq: return "atom-sequence";
		case Type::Cv: return "cv";
	}
	return "";
}

const char *toStr(Vis pv)
{
	switch(pv)
	{
		case Vis::Toggled: return "toggled";
		case Vis::Enumeration: return "enumeration";
		case Vis::Integer: return "integer";
		case Vis::Generic: return "none";
	}
	return "";
}

std::vector<PluginIssue> Meta::get(const LilvPlugin *plugin,
									std::size_t portNum)
{
	std::vector<PluginIssue> portIssues;
	auto issue = [&portIssues](PluginIssueType i, std::string msg = "") {
		portIssues.emplace_back(i, std::move(msg)); };

	Lv2Manager* man = Engine::getLv2Manager();

	const LilvPort* lilvPort = lilv_plugin_get_port_by_index(
								plugin, static_cast<uint32_t>(portNum));

	auto portFunc = [&plugin, &lilvPort, &man](
		bool (*fptr)(const LilvPlugin*, const LilvPort*, const LilvNode*),
		const char* str) {
		return fptr(plugin, lilvPort, man->uri(str).get());
	};

	auto hasProperty = [&portFunc](const char* str) {
		return portFunc(lilv_port_has_property, str); };
	auto isA = [&portFunc](const char* str) {
		return portFunc(lilv_port_is_a, str); };

	const std::string portName = stdStringFromPortName(plugin, lilvPort);

	m_optional = hasProperty(LV2_CORE__connectionOptional);

	m_vis = hasProperty(LV2_CORE__integer)
		? Vis::Integer // WARNING: this may still be changed below
		: hasProperty(LV2_CORE__enumeration)
		? Vis::Enumeration
		: hasProperty(LV2_CORE__toggled)
		? Vis::Toggled
		: Vis::Generic;

	if (isA(LV2_CORE__InputPort)) { m_flow = Flow::Input; }
	else if (isA(LV2_CORE__OutputPort)) { m_flow = Flow::Output; }
	else {
		m_flow = Flow::Unknown;
		issue(unknownPortFlow, portName);
	}

	m_def = .0f;
	m_min = std::numeric_limits<decltype(m_min)>::lowest();
	m_max = std::numeric_limits<decltype(m_max)>::max();
	auto m_min_set = [this]{ return m_min != std::numeric_limits<decltype(m_min)>::lowest(); };
	auto m_max_set = [this]{ return m_max != std::numeric_limits<decltype(m_max)>::max(); };

	m_type = Type::Unknown;
	if (isA(LV2_CORE__ControlPort) || isA(LV2_CORE__CVPort))
	{
		// Read metadata for control ports
		// CV ports are mostly the same as control ports, so we take
		// mostly the same metadata

		if (isA(LV2_CORE__CVPort))
		{
			// currently not supported, but we can still check the metadata
			issue(badPortType, "cvPort");
		}

		m_type = isA(LV2_CORE__CVPort) ? Type::Cv : Type::Control;

		bool isToggle = m_vis == Vis::Toggled;

		LilvNode * defN, * minN = nullptr, * maxN = nullptr;
		lilv_port_get_range(plugin, lilvPort, &defN,
				isToggle ? nullptr : &minN,
				isToggle ? nullptr : &maxN);
		AutoLilvNode def(defN), min(minN), max(maxN);

		auto takeRangeValue = [&](LilvNode* node,
			float& storeHere, PluginIssueType it)
		{
			if (node) { storeHere = lilv_node_as_float(node); }
			else
			{
				// CV ports do not require ranges
				if(m_flow == Flow::Input && m_type != Type::Cv)
				{
					issue(it, portName);
				}
			}
		};

		takeRangeValue(def.get(), m_def, portHasNoDef);
		if (isToggle)
		{
			m_min = .0f;
			m_max = 1.f;
			if(def.get() && m_def != m_min && m_def != m_max)
			{
				issue(defaultValueNotInRange, portName);
			}
		}
		else
		{
			// take min/max
			takeRangeValue(min.get(), m_min, portHasNoMin);
			takeRangeValue(max.get(), m_max, portHasNoMax);
			if(m_type == Type::Cv)
			{
				// no range is allowed and bashed to [-1,+1],
				// but only min or only max does not make sense
				if(!m_min_set() && !m_max_set())
				{
					m_min = -1.f;
					m_max = +1.f;
				}
				else if(!m_min_set()) { issue(portHasNoMin, portName); }
				else if(!m_max_set()) { issue(portHasNoMax, portName); }
			}
			if(m_min > m_max) { issue(minGreaterMax, portName); }

			// sampleRate
			if (hasProperty(LV2_CORE__sampleRate)) { m_sampleRate = true; }

			// default value
			if (def.get())
			{
				if (m_def < m_min) { issue(defaultValueNotInRange, portName); }
				else if (m_def > m_max)
				{
					if(m_sampleRate)
					{
						// multiplying with sample rate will hopefully lead us
						// to a good default value
					}
					else { issue(defaultValueNotInRange, portName); }
				}
			}

			// visualization
			if (!m_min_set() ||
				!m_max_set() ||
				// if we get here, min and max are set, so max-min should not overflow:
				(m_vis == Vis::Integer && m_max - m_min > 15.0f))
			{
				// range too large for spinbox visualisation, use knobs
				// e.g. 0...15 would be OK
				m_vis = Vis::Generic;
			}
		}
	}
	else if (isA(LV2_CORE__AudioPort)) { m_type = Type::Audio; }
	else if (isA(LV2_ATOM__AtomPort))
	{
		AutoLilvNode uriAtomSequence(Engine::getLv2Manager()->uri(LV2_ATOM__Sequence));
		AutoLilvNode uriAtomBufferType(Engine::getLv2Manager()->uri(LV2_ATOM__bufferType));
		AutoLilvNodes bufferTypes(lilv_port_get_value(plugin, lilvPort, uriAtomBufferType.get()));

		if (lilv_nodes_contains(bufferTypes.get(), uriAtomSequence.get()))
		{
			// we accept all kinds of atom sequence ports, even if they take or
			// offer atom types that we do not support:
			// * atom input ports only say what *can* be input, but not what is
			//   required as input
			// * atom output ports only say what *can* be output, but not what must
			//   be evaluated
			m_type = Type::AtomSeq;
		}
	}

	if(m_type == Type::Unknown)
	{
		if (m_optional) { m_used = false; }
		else {
			issue(PluginIssueType::unknownPortType, portName);
		}
	}

	if (hasProperty(LV2_PORT_PROPS__logarithmic))
	{
		// check min/max available
		// we requre them anyways, but this will detect plugins that will
		// be non-Lv2-conforming
		if(m_min == std::numeric_limits<decltype(m_min)>::lowest())
		{
			issue(PluginIssueType::logScaleMinMissing, portName);
		}
		if(m_max == std::numeric_limits<decltype(m_max)>::max())
		{
			issue(PluginIssueType::logScaleMaxMissing, portName);
		}
		// forbid min < 0 < max
		if(m_min < 0.f && m_max > 0.f)
		{
			issue(PluginIssueType::logScaleMinMaxDifferentSigns, portName);
		}
		m_logarithmic = true;
	}

	return portIssues;
}

QString PortBase::name() const
{
	AutoLilvNode node(lilv_port_get_name(m_plugin, m_port));
	QString res = lilv_node_as_string(node.get());
	return res;
}

QString PortBase::uri() const
{
	return lilv_node_as_string(lilv_port_get_symbol(m_plugin, m_port));
}

Audio::Audio(std::size_t bufferSize, bool isSidechain)
	: m_buffer(bufferSize), m_sidechain(isSidechain)
{
}

void Audio::copyBuffersFromCore(const sampleFrame *lmmsBuf,
	unsigned channel, fpp_t frames)
{
	for (std::size_t f = 0; f < static_cast<unsigned>(frames); ++f)
	{
		m_buffer[f] = lmmsBuf[f][channel];
	}
}

void Audio::averageWithBuffersFromCore(const sampleFrame *lmmsBuf,
	unsigned channel, fpp_t frames)
{
	for (std::size_t f = 0; f < static_cast<unsigned>(frames); ++f)
	{
		m_buffer[f] = (m_buffer[f] + lmmsBuf[f][channel]) / 2.0f;
	}
}

void Audio::copyBuffersToCore(sampleFrame *lmmsBuf,
	unsigned channel, fpp_t frames) const
{
	for (std::size_t f = 0; f < static_cast<unsigned>(frames); ++f)
	{
		lmmsBuf[f][channel] = m_buffer[f];
	}
}

void AtomSeq::Lv2EvbufDeleter::operator()(LV2_Evbuf *n) { lv2_evbuf_free(n); }

// make the compiler happy, give each class with virtuals
// a function (the destructor here) which is in a cpp file
PortBase::~PortBase() {}
ConstVisitor::~ConstVisitor() {}
Visitor::~Visitor() {}

} // namespace Lv2Ports

#endif // LMMS_HAVE_LV2