183 changes: 148 additions & 35 deletions src/core/audio/AudioPort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,23 @@
#include "EffectChain.h"
#include "FxMixer.h"
#include "engine.h"
#include "MixHelpers.h"
#include "BufferManager.h"
#include "ValueBuffer.h"
#include "panning.h"


AudioPort::AudioPort( const QString & _name, bool _has_effect_chain ) :
m_bufferUsage( NoUsage ),
m_firstBuffer( new sampleFrame[engine::mixer()->framesPerPeriod()] ),
m_secondBuffer( new sampleFrame[
engine::mixer()->framesPerPeriod()] ),
AudioPort::AudioPort( const QString & _name, bool _has_effect_chain,
FloatModel * volumeModel, FloatModel * panningModel ) :
m_bufferUsage( false ),
m_portBuffer( NULL ),
m_extOutputEnabled( false ),
m_nextFxChannel( 0 ),
m_name( "unnamed port" ),
m_effects( _has_effect_chain ? new EffectChain( NULL ) : NULL )
m_effects( _has_effect_chain ? new EffectChain( NULL ) : NULL ),
m_volumeModel( volumeModel ),
m_panningModel( panningModel )
{
engine::mixer()->clearAudioBuffer( m_firstBuffer, engine::mixer()->framesPerPeriod() );
engine::mixer()->clearAudioBuffer( m_secondBuffer, engine::mixer()->framesPerPeriod() );
engine::mixer()->addAudioPort( this );
setExtOutputEnabled( true );
}
Expand All @@ -52,31 +55,12 @@ AudioPort::~AudioPort()
{
setExtOutputEnabled( false );
engine::mixer()->removeAudioPort( this );
delete[] m_firstBuffer;
delete[] m_secondBuffer;
delete m_effects;
}




void AudioPort::nextPeriod()
{
m_firstBufferLock.lock();
engine::mixer()->clearAudioBuffer( m_firstBuffer, engine::mixer()->framesPerPeriod() );
qSwap( m_firstBuffer, m_secondBuffer );

// this is how we decrease state of buffer-usage ;-)
m_bufferUsage = ( m_bufferUsage != NoUsage ) ?
( ( m_bufferUsage == FirstBuffer ) ?
NoUsage : FirstBuffer ) : NoUsage;

m_firstBufferLock.unlock();
}




void AudioPort::setExtOutputEnabled( bool _enabled )
{
if( _enabled != m_extOutputEnabled )
Expand Down Expand Up @@ -109,23 +93,152 @@ bool AudioPort::processEffects()
{
if( m_effects )
{
lockFirstBuffer();
bool hasInputNoise = m_bufferUsage != NoUsage;
bool more = m_effects->processAudioBuffer( m_firstBuffer, engine::mixer()->framesPerPeriod(), hasInputNoise );
unlockFirstBuffer();
bool more = m_effects->processAudioBuffer( m_portBuffer, engine::mixer()->framesPerPeriod(), m_bufferUsage );
return more;
}
return false;
}


void AudioPort::doProcessing( sampleFrame * )
void AudioPort::doProcessing()
{
const fpp_t fpp = engine::mixer()->framesPerPeriod();

m_portBuffer = BufferManager::acquire(); // get buffer for processing

engine::mixer()->clearAudioBuffer( m_portBuffer, fpp ); // clear the audioport buffer so we can use it

//qDebug( "Playhandles: %d", m_playHandles.size() );
foreach( PlayHandle * ph, m_playHandles ) // now we mix all playhandle buffers into the audioport buffer
{
if( ph->buffer() )
{
if( ph->usesBuffer() )
{
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 != NoUsage )
if( me || m_bufferUsage )
{
engine::fxMixer()->mixToChannel( firstBuffer(), nextFxChannel() );
nextPeriod();
engine::fxMixer()->mixToChannel( m_portBuffer, m_nextFxChannel ); // send output to fx mixer
// TODO: improve the flow here - convert to pull model
m_bufferUsage = false;
}

BufferManager::release( m_portBuffer ); // release buffer, we don't need it anymore
}


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 = qFind( m_playHandles.begin(), m_playHandles.end(), handle );
if( it != m_playHandles.end() )
{
m_playHandles.erase( it );
}
m_playHandleLock.unlock();
}
13 changes: 12 additions & 1 deletion src/core/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@
#include <unistd.h>
#endif

#include "MemoryManager.h"
#include "ConfigManager.h"
#include "NotePlayHandle.h"
#include "embed.h"
#include "engine.h"
#include "LmmsStyle.h"
Expand Down Expand Up @@ -97,6 +99,10 @@ inline void loadTranslation( const QString & _tname,

int main( int argc, char * * argv )
{
// initialize memory managers
MemoryManager::init();
NotePlayHandleManager::init();

// intialize RNG
srand( getpid() + time( 0 ) );

Expand Down Expand Up @@ -432,7 +438,7 @@ int main( int argc, char * * argv )

// init central engine which handles all components of LMMS
engine::init();

splashScreen.hide();

// re-intialize RNG - shared libraries might have srand() or
Expand Down Expand Up @@ -499,6 +505,7 @@ int main( int argc, char * * argv )
{
// we're going to render our song
engine::init( false );

printf( "loading project...\n" );
engine::getSong()->loadProject( file_to_load );
printf( "done\n" );
Expand Down Expand Up @@ -529,6 +536,10 @@ int main( int argc, char * * argv )

const int ret = app->exec();
delete app;

// cleanup memory managers
MemoryManager::cleanup();

return( ret );
}

Expand Down
60 changes: 17 additions & 43 deletions src/tracks/InstrumentTrack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
#include "tooltip.h"
#include "track_label_button.h"
#include "ValueBuffer.h"
#include "volume.h"


const char * volume_help = QT_TRANSLATE_NOOP( "InstrumentTrack",
Expand All @@ -94,7 +95,6 @@ const int INSTRUMENT_WINDOW_CACHE_SIZE = 8;
InstrumentTrack::InstrumentTrack( TrackContainer* tc ) :
track( track::InstrumentTrack, tc ),
MidiEventProcessor(),
m_audioPort( tr( "unnamed_track" ) ),
m_midiPort( tr( "unnamed_track" ), engine::mixer()->midiClient(),
this, this ),
m_notes(),
Expand All @@ -104,6 +104,7 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) :
tr( "Base note" ) ),
m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, tr( "Volume" ) ),
m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ),
m_audioPort( tr( "unnamed_track" ), true, &m_volumeModel, &m_panningModel ),
m_pitchModel( 0, MinPitchDefault, MaxPitchDefault, 1, this, tr( "Pitch" ) ),
m_pitchRangeModel( 1, 1, 24, this, tr( "Pitch range" ) ),
m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ),
Expand Down Expand Up @@ -148,7 +149,7 @@ InstrumentTrack::~InstrumentTrack()
silenceAllNotes( true );

// now we're save deleting the instrument
delete m_instrument;
if( m_instrument ) delete m_instrument;
}


Expand Down Expand Up @@ -188,10 +189,10 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames,

// get volume knob data
static const float DefaultVolumeRatio = 1.0f / DefaultVolume;
ValueBuffer * volBuf = m_volumeModel.valueBuffer();
/*ValueBuffer * volBuf = m_volumeModel.valueBuffer();
float v_scale = volBuf
? 1.0f
: getVolume() * DefaultVolumeRatio;
: getVolume() * DefaultVolumeRatio;*/

// instruments using instrument-play-handles will call this method
// without any knowledge about notes, so they pass NULL for n, which
Expand All @@ -200,44 +201,19 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames,
{
const f_cnt_t offset = n->noteOffset();
m_soundShaping.processAudioBuffer( buf + offset, frames - offset, n );
v_scale *= ( (float) n->getVolume() * DefaultVolumeRatio );
}

m_audioPort.setNextFxChannel( m_effectChannelModel.value() );

// get panning knob data
ValueBuffer * panBuf = m_panningModel.valueBuffer();
int panning = panBuf
? 0
: m_panningModel.value();

if( n )
{
panning += n->getPanning();
panning = tLimit<int>( panning, PanningLeft, PanningRight );
}

// apply sample-exact volume/panning data
if( volBuf )
{
for( f_cnt_t f = 0; f < frames; ++f )
{
float v = volBuf->values()[ f ] * 0.01f;
buf[f][0] *= v;
buf[f][1] *= v;
}
}
if( panBuf )
{
for( f_cnt_t f = 0; f < frames; ++f )
const float vol = ( (float) n->getVolume() * DefaultVolumeRatio );
const panning_t pan = qBound( PanningLeft, n->getPanning(), PanningRight );
stereoVolumeVector vv = panningToVolumeVector( pan, vol );
for( f_cnt_t f = offset; f < frames; ++f )
{
float p = panBuf->values()[ f ] * 0.01f;
buf[f][0] *= ( p <= 0 ? 1.0f : 1.0f - p );
buf[f][1] *= ( p >= 0 ? 1.0f : 1.0f + p );
for( int c = 0; c < 2; ++c )
{
buf[f][c] *= vv.vol[c];
}
}
}

engine::mixer()->bufferToPort( buf, frames, panningToVolumeVector( panning, v_scale ), &m_audioPort );
m_audioPort.setNextFxChannel( m_effectChannelModel.value() );
}


Expand Down Expand Up @@ -278,7 +254,7 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const MidiTime& ti
m_notesMutex.lock();
if( m_notes[event.key()] == NULL )
{
nph = new NotePlayHandle( this, offset,
nph = NotePlayHandleManager::acquire( this, offset,
typeInfo<f_cnt_t>::max() / 2,
note( MidiTime(), MidiTime(), event.key(), event.volume( midiPort()->baseVelocity() ) ),
NULL, event.channel(),
Expand Down Expand Up @@ -538,9 +514,7 @@ void InstrumentTrack::updateBaseNote()
for( NotePlayHandleList::Iterator it = m_processHandles.begin();
it != m_processHandles.end(); ++it )
{
( *it )->lock();
( *it )->updateFrequency();
( *it )->unlock();
( *it )->setFrequencyUpdate();
}
}

Expand Down Expand Up @@ -670,7 +644,7 @@ bool InstrumentTrack::play( const MidiTime & _start, const fpp_t _frames,
cur_note->length().frames(
frames_per_tick );

NotePlayHandle* notePlayHandle = new NotePlayHandle( this, _offset, note_frames, *cur_note );
NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire( this, _offset, note_frames, *cur_note );
notePlayHandle->setBBTrack( bb_track );
// are we playing global song?
if( _tco_num < 0 )
Expand Down
4 changes: 2 additions & 2 deletions src/tracks/SampleTrack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,9 @@ void SampleTCOView::paintEvent( QPaintEvent * _pe )

SampleTrack::SampleTrack( TrackContainer* tc ) :
track( track::SampleTrack, tc ),
m_audioPort( tr( "Sample track" ) ),
m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 1.0, this,
tr( "Volume" ) )
tr( "Volume" ) ),
m_audioPort( tr( "Sample track" ), true, &m_volumeModel, NULL )
{
setName( tr( "Sample track" ) );
}
Expand Down