/* * mixer.cpp - audio-device-independent mixer for LMMS * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * 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 #include "mixer.h" #include "FxMixer.h" #include "play_handle.h" #include "song.h" #include "templates.h" #include "EnvelopeAndLfoParameters.h" #include "note_play_handle.h" #include "InstrumentTrack.h" #include "debug.h" #include "engine.h" #include "config_mgr.h" #include "sample_play_handle.h" #include "piano_roll.h" #include "Cpu.h" #include "MicroTimer.h" // platform-specific audio-interface-classes #include "AudioAlsa.h" #include "AudioJack.h" #include "AudioOss.h" #include "AudioPortAudio.h" #include "AudioPulseAudio.h" #include "AudioSdl.h" #include "AudioDummy.h" // platform-specific midi-interface-classes #include "MidiAlsaRaw.h" #include "MidiAlsaSeq.h" #include "MidiOss.h" #include "MidiWinMM.h" #include "MidiDummy.h" #ifdef LMMS_HAVE_PTHREAD_H #include #endif static QVector __fx_channel_jobs( NumFxChannels ); class MixerWorkerThread : public QThread { public: enum JobTypes { InvalidJob, PlayHandle, AudioPortEffects, EffectChannel, NumJobTypes } ; struct JobQueueItem { JobQueueItem() : type( InvalidJob ), job( NULL ), param( 0 ), done( false ) { } JobQueueItem( JobTypes _type, void * _job, int _param = 0 ) : type( _type ), job( _job ), param( _param ), done( false ) { } JobTypes type; void * job; int param; QAtomicInt done; } ; struct JobQueue { #define JOB_QUEUE_SIZE 1024 JobQueue() : queueSize( 0 ) { } JobQueueItem items[JOB_QUEUE_SIZE]; int queueSize; QAtomicInt itemsDone; } ; static JobQueue s_jobQueue; MixerWorkerThread( int _worker_num, mixer * _mixer ) : QThread( _mixer ), m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ), m_workerNum( _worker_num ), m_quit( false ), m_mixer( _mixer ), m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond ) { } virtual ~MixerWorkerThread() { CPU::freeFrames( m_workingBuf ); } virtual void quit() { m_quit = true; } void processJobQueue(); private: virtual void run() { #if 0 #ifdef LMMS_BUILD_LINUX #ifdef LMMS_HAVE_PTHREAD_H cpu_set_t mask; CPU_ZERO( &mask ); CPU_SET( m_workerNum, &mask ); pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); #endif #endif #endif QMutex m; while( m_quit == false ) { m.lock(); m_queueReadyWaitCond->wait( &m ); processJobQueue(); m.unlock(); } } sampleFrame * m_workingBuf; int m_workerNum; volatile bool m_quit; mixer * m_mixer; QWaitCondition * m_queueReadyWaitCond; } ; MixerWorkerThread::JobQueue MixerWorkerThread::s_jobQueue; void MixerWorkerThread::processJobQueue() { for( int i = 0; i < s_jobQueue.queueSize; ++i ) { JobQueueItem * it = &s_jobQueue.items[i]; if( it->done.fetchAndStoreOrdered( 1 ) == 0 ) { switch( it->type ) { case PlayHandle: ( (playHandle *) it->job )-> play( m_workingBuf ); break; case AudioPortEffects: { AudioPort * a = (AudioPort *) it->job; const bool me = a->processEffects(); if( me || a->m_bufferUsage != AudioPort::NoUsage ) { engine::fxMixer()->mixToChannel( a->firstBuffer(), a->nextFxChannel() ); a->nextPeriod(); } } break; case EffectChannel: engine::fxMixer()->processChannel( (fx_ch_t) it->param ); break; default: break; } s_jobQueue.itemsDone.fetchAndAddOrdered( 1 ); } } } #define FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ MixerWorkerThread::s_jobQueue.queueSize = 0; \ MixerWorkerThread::s_jobQueue.itemsDone = 0; \ for( _vec_type::Iterator it = _vec.begin(); \ it != _vec.end(); ++it ) \ { \ if( _condition ) \ { #define FILL_JOB_QUEUE_END() \ ++MixerWorkerThread::s_jobQueue.queueSize; \ } \ } #define FILL_JOB_QUEUE(_vec_type,_vec,_job_type,_condition) \ FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ MixerWorkerThread::s_jobQueue.items \ [MixerWorkerThread::s_jobQueue.queueSize] = \ MixerWorkerThread::JobQueueItem( _job_type, \ (void *) *it ); \ FILL_JOB_QUEUE_END() #define FILL_JOB_QUEUE_PARAM(_vec_type,_vec,_job_type,_condition) \ FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ MixerWorkerThread::s_jobQueue.items \ [MixerWorkerThread::s_jobQueue.queueSize] = \ MixerWorkerThread::JobQueueItem( _job_type, \ NULL, *it ); \ FILL_JOB_QUEUE_END() #define START_JOBS() \ m_queueReadyWaitCond.wakeAll(); // define a pause instruction for spinlock-loop - merely useful on // HyperThreading systems with just one physical core (e.g. Intel Atom) #ifdef LMMS_HOST_X86 #define SPINLOCK_PAUSE() asm( "pause" ) #else #ifdef LMMS_HOST_X86_64 #define SPINLOCK_PAUSE() asm( "pause" ) #else #define SPINLOCK_PAUSE() #endif #endif #define WAIT_FOR_JOBS() \ m_workers[m_numWorkers]->processJobQueue(); \ while( MixerWorkerThread::s_jobQueue.itemsDone < \ MixerWorkerThread::s_jobQueue.queueSize ) \ { \ SPINLOCK_PAUSE(); \ } \ mixer::mixer() : m_framesPerPeriod( DEFAULT_BUFFER_SIZE ), m_workingBuf( NULL ), m_inputBufferRead( 0 ), m_inputBufferWrite( 1 ), m_readBuf( NULL ), m_writeBuf( NULL ), m_cpuLoad( 0 ), m_workers(), m_numWorkers( QThread::idealThreadCount()-1 ), m_queueReadyWaitCond(), m_qualitySettings( qualitySettings::Mode_Draft ), m_masterGain( 1.0f ), m_audioDev( NULL ), m_oldAudioDev( NULL ), m_globalMutex( QMutex::Recursive ) { for( int i = 0; i < 2; ++i ) { m_inputBufferFrames[i] = 0; m_inputBufferSize[i] = DEFAULT_BUFFER_SIZE * 100; m_inputBuffer[i] = CPU::allocFrames( DEFAULT_BUFFER_SIZE * 100 ); clearAudioBuffer( m_inputBuffer[i], m_inputBufferSize[i] ); } for( int i = 1; i < NumFxChannels+1; ++i ) { __fx_channel_jobs[i-1] = (fx_ch_t) i; } // just rendering? if( !engine::hasGUI() ) { m_framesPerPeriod = DEFAULT_BUFFER_SIZE; m_fifo = new fifo( 1 ); } else if( configManager::inst()->value( "mixer", "framesperaudiobuffer" ).toInt() >= 32 ) { m_framesPerPeriod = (fpp_t) configManager::inst()->value( "mixer", "framesperaudiobuffer" ).toInt(); if( m_framesPerPeriod > DEFAULT_BUFFER_SIZE ) { m_fifo = new fifo( m_framesPerPeriod / DEFAULT_BUFFER_SIZE ); m_framesPerPeriod = DEFAULT_BUFFER_SIZE; } else { m_fifo = new fifo( 1 ); } } else { configManager::inst()->setValue( "mixer", "framesperaudiobuffer", QString::number( m_framesPerPeriod ) ); m_fifo = new fifo( 1 ); } m_workingBuf = CPU::allocFrames( m_framesPerPeriod ); for( Uint8 i = 0; i < 3; i++ ) { m_readBuf = CPU::allocFrames( m_framesPerPeriod ); clearAudioBuffer( m_readBuf, m_framesPerPeriod ); m_bufferPool.push_back( m_readBuf ); } for( int i = 0; i < m_numWorkers+1; ++i ) { MixerWorkerThread * wt = new MixerWorkerThread( i, this ); if( i < m_numWorkers ) { wt->start( QThread::TimeCriticalPriority ); } m_workers.push_back( wt ); } m_poolDepth = 2; m_readBuffer = 0; m_writeBuffer = 1; } mixer::~mixer() { // distribute an empty job-queue so that worker-threads // get out of their processing-loop MixerWorkerThread::s_jobQueue.queueSize = 0; for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->quit(); } START_JOBS(); for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->wait( 500 ); } while( m_fifo->available() ) { delete[] m_fifo->read(); } delete m_fifo; delete m_audioDev; delete m_midiClient; for( Uint8 i = 0; i < 3; i++ ) { CPU::freeFrames( m_bufferPool[i] ); } CPU::freeFrames( m_workingBuf ); } void mixer::initDevices() { m_audioDev = tryAudioDevices(); m_midiClient = tryMidiClients(); } void mixer::startProcessing( bool _needs_fifo ) { if( _needs_fifo ) { m_fifoWriter = new fifoWriter( this, m_fifo ); m_fifoWriter->start( QThread::HighPriority ); } else { m_fifoWriter = NULL; } m_audioDev->startProcessing(); } void mixer::stopProcessing() { if( m_fifoWriter != NULL ) { m_fifoWriter->finish(); m_audioDev->stopProcessing(); m_fifoWriter->wait( 1000 ); m_fifoWriter->terminate(); delete m_fifoWriter; m_fifoWriter = NULL; } else { m_audioDev->stopProcessing(); } } sample_rate_t mixer::baseSampleRate() const { sample_rate_t sr = configManager::inst()->value( "mixer", "samplerate" ).toInt(); if( sr < 44100 ) { sr = 44100; } return sr; } sample_rate_t mixer::outputSampleRate() const { return m_audioDev != NULL ? m_audioDev->sampleRate() : baseSampleRate(); } sample_rate_t mixer::inputSampleRate() const { return m_audioDev != NULL ? m_audioDev->sampleRate() : baseSampleRate(); } sample_rate_t mixer::processingSampleRate() const { return outputSampleRate() * m_qualitySettings.sampleRateMultiplier(); } bool mixer::criticalXRuns() const { return m_cpuLoad >= 99 && engine::getSong()->realTimeTask() == true; } void mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) { lockInputFrames(); f_cnt_t frames = m_inputBufferFrames[ m_inputBufferWrite ]; int size = m_inputBufferSize[ m_inputBufferWrite ]; sampleFrame * buf = m_inputBuffer[ m_inputBufferWrite ]; if( frames + _frames > size ) { size = qMax( size * 2, frames + _frames ); sampleFrame * ab = CPU::allocFrames( size ); CPU::memCpy( ab, buf, frames * sizeof( sampleFrame ) ); CPU::freeFrames( buf ); m_inputBufferSize[ m_inputBufferWrite ] = size; m_inputBuffer[ m_inputBufferWrite ] = ab; buf = ab; } CPU::memCpy( &buf[ frames ], _ab, _frames * sizeof( sampleFrame ) ); m_inputBufferFrames[ m_inputBufferWrite ] += _frames; unlockInputFrames(); } sampleFrameA * mixer::renderNextBuffer() { MicroTimer timer; static song::playPos last_metro_pos = -1; song::playPos p = engine::getSong()->getPlayPos( song::Mode_PlayPattern ); if( engine::getSong()->playMode() == song::Mode_PlayPattern && engine::getPianoRoll()->isRecording() == true && p != last_metro_pos && p.getTicks() % (DefaultTicksPerTact / 4 ) == 0 ) { addPlayHandle( new samplePlayHandle( "misc/metronome01.ogg" ) ); last_metro_pos = p; } lockInputFrames(); // swap buffer m_inputBufferWrite = ( m_inputBufferWrite + 1 ) % 2; m_inputBufferRead = ( m_inputBufferRead + 1 ) % 2; // clear new write buffer m_inputBufferFrames[ m_inputBufferWrite ] = 0; unlockInputFrames(); // now we have to make sure no other thread does anything bad // while we're acting... lock(); // remove all play-handles that have to be deleted and delete // them if they still exist... // maybe this algorithm could be optimized... ConstPlayHandleList::Iterator it_rem = m_playHandlesToRemove.begin(); while( it_rem != m_playHandlesToRemove.end() ) { PlayHandleList::Iterator it = qFind( m_playHandles.begin(), m_playHandles.end(), *it_rem ); if( it != m_playHandles.end() ) { delete *it; m_playHandles.erase( it ); } it_rem = m_playHandlesToRemove.erase( it_rem ); } // rotate buffers m_writeBuffer = ( m_writeBuffer + 1 ) % m_poolDepth; m_readBuffer = ( m_readBuffer + 1 ) % m_poolDepth; m_writeBuf = m_bufferPool[m_writeBuffer]; m_readBuf = m_bufferPool[m_readBuffer]; // clear last audio-buffer clearAudioBuffer( m_writeBuf, m_framesPerPeriod ); // prepare master mix (clear internal buffers etc.) engine::fxMixer()->prepareMasterMix(); // create play-handles for new notes, samples etc. engine::getSong()->processNextBuffer(); // STAGE 1: run and render all play handles FILL_JOB_QUEUE(PlayHandleList,m_playHandles, MixerWorkerThread::PlayHandle, !( *it )->done()); START_JOBS(); WAIT_FOR_JOBS(); // removed all play handles which are done for( PlayHandleList::Iterator it = m_playHandles.begin(); it != m_playHandles.end(); ) { if( ( *it )->affinityMatters() && ( *it )->affinity() != QThread::currentThread() ) { ++it; continue; } if( ( *it )->done() ) { delete *it; it = m_playHandles.erase( it ); } else { ++it; } } // STAGE 2: process effects of all instrument- and sampletracks FILL_JOB_QUEUE(QVector,m_audioPorts, MixerWorkerThread::AudioPortEffects,1); START_JOBS(); WAIT_FOR_JOBS(); // STAGE 3: process effects in FX mixer FILL_JOB_QUEUE_PARAM(QVector,__fx_channel_jobs, MixerWorkerThread::EffectChannel,1); START_JOBS(); WAIT_FOR_JOBS(); // STAGE 4: do master mix in FX mixer engine::fxMixer()->masterMix( m_writeBuf ); unlock(); emit nextAudioBuffer(); // and trigger LFOs EnvelopeAndLfoParameters::triggerLfo(); Controller::triggerFrameCounter(); const float new_cpu_load = timer.elapsed() / 10000.0f * processingSampleRate() / m_framesPerPeriod; m_cpuLoad = tLimit( (int) ( new_cpu_load * 0.1f + m_cpuLoad * 0.9f ), 0, 100 ); return m_readBuf; } // removes all play-handles. this is neccessary, when the song is stopped -> // all remaining notes etc. would be played until their end void mixer::clear() { // TODO: m_midiClient->noteOffAll(); lock(); for( PlayHandleList::Iterator it = m_playHandles.begin(); it != m_playHandles.end(); ++it ) { // we must not delete instrument-play-handles as they exist // during the whole lifetime of an instrument if( ( *it )->type() != playHandle::InstrumentPlayHandle ) { m_playHandlesToRemove.push_back( *it ); } } unlock(); } void mixer::bufferToPort( const sampleFrame * _buf, const fpp_t _frames, const f_cnt_t _offset, stereoVolumeVector _vv, AudioPort * _port ) { const int start_frame = _offset % m_framesPerPeriod; int end_frame = start_frame + _frames; const int loop1_frame = qMin( end_frame, m_framesPerPeriod ); _port->lockFirstBuffer(); CPU::unalignedBufMixLRCoeff( _port->firstBuffer() + start_frame, _buf, _vv.vol[0], _vv.vol[1], loop1_frame - start_frame ); _port->unlockFirstBuffer(); _port->lockSecondBuffer(); if( end_frame > m_framesPerPeriod ) { const int frames_done = m_framesPerPeriod - start_frame; end_frame -= m_framesPerPeriod; end_frame = qMin( end_frame, m_framesPerPeriod ); CPU::unalignedBufMixLRCoeff( _port->secondBuffer(), _buf+frames_done, _vv.vol[0], _vv.vol[1], end_frame ); // we used both buffers so set flags _port->m_bufferUsage = AudioPort::BothBuffers; } else if( _port->m_bufferUsage == AudioPort::NoUsage ) { // only first buffer touched _port->m_bufferUsage = AudioPort::FirstBuffer; } _port->unlockSecondBuffer(); } void mixer::clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset ) { if( likely( (size_t)( _ab+_offset ) % 16 == 0 && _frames % 8 == 0 ) ) { CPU::memClear( _ab+_offset, sizeof( *_ab ) * _frames ); } else { memset( _ab+_offset, 0, sizeof( *_ab ) * _frames ); } } #ifndef LMMS_DISABLE_SURROUND void mixer::clearAudioBuffer( surroundSampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset ) { memset( _ab+_offset, 0, sizeof( *_ab ) * _frames ); } #endif float mixer::peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ) { float p = 0.0f; for( f_cnt_t f = 0; f < _frames; ++f ) { if( _ab[f][0] > p ) { p = _ab[f][0]; } else if( -_ab[f][0] > p ) { p = -_ab[f][0]; } } return p; } float mixer::peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ) { float p = 0.0f; for( f_cnt_t f = 0; f < _frames; ++f ) { if( _ab[f][1] > p ) { p = _ab[f][1]; } else if( -_ab[f][1] > p ) { p = -_ab[f][1]; } } return p; } void mixer::changeQuality( const struct qualitySettings & _qs ) { // don't delete the audio-device stopProcessing(); m_qualitySettings = _qs; m_audioDev->applyQualitySettings(); emit sampleRateChanged(); emit qualitySettingsChanged(); startProcessing(); } void mixer::setAudioDevice( AudioDevice * _dev ) { stopProcessing(); m_oldAudioDev = m_audioDev; if( _dev == NULL ) { printf( "param _dev == NULL in mixer::setAudioDevice(...). " "Trying any working audio-device\n" ); m_audioDev = tryAudioDevices(); } else { m_audioDev = _dev; } emit sampleRateChanged(); startProcessing(); } void mixer::setAudioDevice( AudioDevice * _dev, const struct qualitySettings & _qs, bool _needs_fifo ) { // don't delete the audio-device stopProcessing(); m_qualitySettings = _qs; m_oldAudioDev = m_audioDev; if( _dev == NULL ) { printf( "param _dev == NULL in mixer::setAudioDevice(...). " "Trying any working audio-device\n" ); m_audioDev = tryAudioDevices(); } else { m_audioDev = _dev; } emit qualitySettingsChanged(); emit sampleRateChanged(); startProcessing( _needs_fifo ); } void mixer::restoreAudioDevice() { if( m_oldAudioDev != NULL ) { stopProcessing(); delete m_audioDev; m_audioDev = m_oldAudioDev; emit sampleRateChanged(); m_oldAudioDev = NULL; startProcessing(); } } void mixer::removeAudioPort( AudioPort * _port ) { QVector::Iterator it = qFind( m_audioPorts.begin(), m_audioPorts.end(), _port ); if( it != m_audioPorts.end() ) { lock(); m_audioPorts.erase( it ); unlock(); } } void mixer::removePlayHandle( playHandle * _ph ) { lock(); // check thread affinity as we must not delete play-handles // which were created in a thread different than mixer thread if( _ph->affinityMatters() && _ph->affinity() == QThread::currentThread() ) { PlayHandleList::Iterator it = qFind( m_playHandles.begin(), m_playHandles.end(), _ph ); if( it != m_playHandles.end() ) { m_playHandles.erase( it ); delete _ph; } } else { m_playHandlesToRemove.push_back( _ph ); } unlock(); } void mixer::removePlayHandles( track * _track, playHandle::Type _type ) { lock(); PlayHandleList::Iterator it = m_playHandles.begin(); while( it != m_playHandles.end() ) { if( ( _type == playHandle::NumPlayHandleTypes || ( *it )->type() == _type ) && ( *it )->isFromTrack( _track ) ) { delete *it; it = m_playHandles.erase( it ); } else { ++it; } } unlock(); } AudioDevice * mixer::tryAudioDevices() { bool success_ful = false; AudioDevice * dev = NULL; QString dev_name = configManager::inst()->value( "mixer", "audiodev" ); if( dev_name == AudioDummy::name() ) { dev_name = ""; } #ifdef LMMS_HAVE_ALSA if( dev_name == AudioAlsa::name() || dev_name == "" ) { dev = new AudioAlsa( success_ful, this ); if( success_ful ) { m_audioDevName = AudioAlsa::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_PORTAUDIO if( dev_name == AudioPortAudio::name() || dev_name == "" ) { dev = new AudioPortAudio( success_ful, this ); if( success_ful ) { m_audioDevName = AudioPortAudio::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_PULSEAUDIO if( dev_name == AudioPulseAudio::name() || dev_name == "" ) { dev = new AudioPulseAudio( success_ful, this ); if( success_ful ) { m_audioDevName = AudioPulseAudio::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_OSS if( dev_name == AudioOss::name() || dev_name == "" ) { dev = new AudioOss( success_ful, this ); if( success_ful ) { m_audioDevName = AudioOss::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_JACK if( dev_name == AudioJack::name() || dev_name == "" ) { dev = new AudioJack( success_ful, this ); if( success_ful ) { m_audioDevName = AudioJack::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_SDL if( dev_name == AudioSdl::name() || dev_name == "" ) { dev = new AudioSdl( success_ful, this ); if( success_ful ) { m_audioDevName = AudioSdl::name(); return dev; } delete dev; } #endif // add more device-classes here... //dev = new audioXXXX( SAMPLE_RATES[m_qualityLevel], success_ful, this ); //if( sucess_ful ) //{ // return dev; //} //delete dev printf( "No audio-driver working - falling back to dummy-audio-" "driver\nYou can render your songs and listen to the output " "files...\n" ); m_audioDevName = AudioDummy::name(); return new AudioDummy( success_ful, this ); } MidiClient * mixer::tryMidiClients() { QString client_name = configManager::inst()->value( "mixer", "mididev" ); #ifdef LMMS_HAVE_ALSA if( client_name == MidiAlsaSeq::name() || client_name == "" ) { MidiAlsaSeq * malsas = new MidiAlsaSeq; if( malsas->isRunning() ) { m_midiClientName = MidiAlsaSeq::name(); return malsas; } delete malsas; } if( client_name == MidiAlsaRaw::name() || client_name == "" ) { MidiAlsaRaw * malsar = new MidiAlsaRaw; if( malsar->isRunning() ) { m_midiClientName = MidiAlsaRaw::name(); return malsar; } delete malsar; } #endif #ifdef LMMS_HAVE_OSS if( client_name == MidiOss::name() || client_name == "" ) { MidiOss * moss = new MidiOss; if( moss->isRunning() ) { m_midiClientName = MidiOss::name(); return moss; } delete moss; } #endif #ifdef LMMS_BUILD_WIN32 if( client_name == MidiWinMM::name() || client_name == "" ) { MidiWinMM * mwmm = new MidiWinMM; // if( moss->isRunning() ) { m_midiClientName = MidiWinMM::name(); return mwmm; } delete mwmm; } #endif printf( "Couldn't create MIDI-client, neither with ALSA nor with " "OSS. Will use dummy-MIDI-client.\n" ); m_midiClientName = MidiDummy::name(); return new MidiDummy; } mixer::fifoWriter::fifoWriter( mixer * _mixer, fifo * _fifo ) : m_mixer( _mixer ), m_fifo( _fifo ), m_writing( true ) { } void mixer::fifoWriter::finish() { m_writing = false; } void mixer::fifoWriter::run() { #if 0 #ifdef LMMS_BUILD_LINUX #ifdef LMMS_HAVE_PTHREAD_H cpu_set_t mask; CPU_ZERO( &mask ); CPU_SET( 0, &mask ); pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); #endif #endif #endif const fpp_t frames = m_mixer->framesPerPeriod(); while( m_writing ) { sampleFrameA * buffer = CPU::allocFrames( frames ); const sampleFrameA * b = m_mixer->renderNextBuffer(); CPU::memCpy( buffer, b, frames * sizeof( sampleFrameA ) ); m_fifo->write( buffer ); } m_fifo->write( NULL ); } #include "moc_mixer.cxx"