mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-16 13:08:04 -04:00
src/core/RemotePlugin.cpp @ RemotePlugin::process Above function should be now more thread safe, but functionality remains. This prevent lmms locks, when automation is connected to lmms VST plugin wrappers controler / knob on Linux. On win32 build whenever is lmms wrapper parameter controler / knob amended. plugins/vst_base/VstPlugin.cpp @ VstPlugin::setParam plugins/vst_base/RemoteVstPlugin.cpp @ RemoteVstPlugin::processMessage In above functions we dont wait for message confirmation when parameter setter function finish, and dont send such confirmations. This workaround prevent locks on Linux, whenever there is automation connected and if you try to move with VST plugin (not via lmms plugins wrapper, but with VSTs internal window) than it freezes, on win32 build it will freeze whenever you connect automation and than if you move your mouse above this VST plugin window. Signed-off-by: Tobias Doerffel <tobias.doerffel@gmail.com>
399 lines
7.7 KiB
C++
399 lines
7.7 KiB
C++
/*
|
|
* RemotePlugin.cpp - base class providing RPC like mechanisms
|
|
*
|
|
* Copyright (c) 2008-2010 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#define COMPILE_REMOTE_PLUGIN_BASE
|
|
//#define DEBUG_REMOTE_PLUGIN
|
|
#ifdef DEBUG_REMOTE_PLUGIN
|
|
#include <QtCore/QDebug>
|
|
#endif
|
|
|
|
#include "RemotePlugin.h"
|
|
#include "mixer.h"
|
|
#include "engine.h"
|
|
#include "config_mgr.h"
|
|
|
|
#include <QtCore/QDir>
|
|
|
|
#ifdef LMMS_HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
|
|
// simple helper thread monitoring our RemotePlugin - if process terminates
|
|
// unexpectedly invalidate plugin so LMMS doesn't lock up
|
|
ProcessWatcher::ProcessWatcher( RemotePlugin * _p ) :
|
|
QThread(),
|
|
m_plugin( _p ),
|
|
m_quit( false )
|
|
{
|
|
}
|
|
|
|
|
|
void ProcessWatcher::run()
|
|
{
|
|
while( !m_quit && m_plugin->isRunning() )
|
|
{
|
|
msleep( 200 );
|
|
}
|
|
if( !m_quit )
|
|
{
|
|
fprintf( stderr,
|
|
"remote plugin died! invalidating now.\n" );
|
|
m_plugin->invalidate();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RemotePlugin::RemotePlugin() :
|
|
RemotePluginBase( new shmFifo(), new shmFifo() ),
|
|
m_failed( true ),
|
|
m_process(),
|
|
m_watcher( this ),
|
|
m_commMutex( QMutex::Recursive ),
|
|
m_splitChannels( false ),
|
|
#ifdef USE_QT_SHMEM
|
|
m_shmObj(),
|
|
#else
|
|
m_shmID( 0 ),
|
|
#endif
|
|
m_shmSize( 0 ),
|
|
m_shm( NULL ),
|
|
m_inputCount( DEFAULT_CHANNELS ),
|
|
m_outputCount( DEFAULT_CHANNELS )
|
|
{
|
|
}
|
|
|
|
|
|
|
|
|
|
RemotePlugin::~RemotePlugin()
|
|
{
|
|
m_watcher.quit();
|
|
m_watcher.wait();
|
|
|
|
if( m_failed == false )
|
|
{
|
|
if( isRunning() )
|
|
{
|
|
lock();
|
|
sendMessage( IdQuit );
|
|
|
|
m_process.waitForFinished( 1000 );
|
|
if( m_process.state() != QProcess::NotRunning )
|
|
{
|
|
m_process.terminate();
|
|
m_process.kill();
|
|
}
|
|
unlock();
|
|
}
|
|
|
|
#ifndef USE_QT_SHMEM
|
|
shmdt( m_shm );
|
|
shmctl( m_shmID, IPC_RMID, NULL );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RemotePlugin::init( const QString &pluginExecutable,
|
|
bool waitForInitDoneMsg )
|
|
{
|
|
lock();
|
|
if( m_failed )
|
|
{
|
|
reset( new shmFifo(), new shmFifo() );
|
|
m_failed = false;
|
|
}
|
|
QString exec = configManager::inst()->pluginDir() +
|
|
QDir::separator() + pluginExecutable;
|
|
|
|
QStringList args;
|
|
// swap in and out for bidirectional communication
|
|
args << QString::number( out()->shmKey() );
|
|
args << QString::number( in()->shmKey() );
|
|
#ifndef DEBUG_REMOTE_PLUGIN
|
|
m_process.setProcessChannelMode( QProcess::ForwardedChannels );
|
|
m_process.start( exec, args );
|
|
m_watcher.start( QThread::LowestPriority );
|
|
#else
|
|
qDebug() << exec << args;
|
|
#endif
|
|
|
|
resizeSharedProcessingMemory();
|
|
|
|
if( waitForInitDoneMsg )
|
|
{
|
|
waitForInitDone();
|
|
}
|
|
unlock();
|
|
|
|
return failed();
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RemotePlugin::process( const sampleFrame * _in_buf,
|
|
sampleFrame * _out_buf )
|
|
{
|
|
const fpp_t frames = engine::getMixer()->framesPerPeriod();
|
|
|
|
if( m_failed || !isRunning() )
|
|
{
|
|
if( _out_buf != NULL )
|
|
{
|
|
engine::getMixer()->clearAudioBuffer( _out_buf,
|
|
frames );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if( m_shm == NULL )
|
|
{
|
|
// m_shm being zero means we didn't initialize everything so
|
|
// far so process one message each time (and hope we get
|
|
// information like SHM-key etc.) until we process messages
|
|
// in a later stage of this procedure
|
|
if( m_shmSize == 0 )
|
|
{
|
|
lock();
|
|
fetchAndProcessAllMessages();
|
|
unlock();
|
|
}
|
|
if( _out_buf != NULL )
|
|
{
|
|
engine::getMixer()->clearAudioBuffer( _out_buf,
|
|
frames );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
memset( m_shm, 0, m_shmSize );
|
|
|
|
ch_cnt_t inputs = qMin<ch_cnt_t>( m_inputCount, DEFAULT_CHANNELS );
|
|
|
|
if( _in_buf != NULL && inputs > 0 )
|
|
{
|
|
if( m_splitChannels )
|
|
{
|
|
for( ch_cnt_t ch = 0; ch < inputs; ++ch )
|
|
{
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
m_shm[ch * frames + frame] =
|
|
_in_buf[frame][ch];
|
|
}
|
|
}
|
|
}
|
|
else if( inputs == DEFAULT_CHANNELS )
|
|
{
|
|
memcpy( m_shm, _in_buf, frames * BYTES_PER_FRAME );
|
|
}
|
|
else
|
|
{
|
|
sampleFrame * o = (sampleFrame *) m_shm;
|
|
for( ch_cnt_t ch = 0; ch < inputs; ++ch )
|
|
{
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
o[frame][ch] = _in_buf[frame][ch];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
lock();
|
|
sendMessage( IdStartProcessing );
|
|
|
|
if( m_failed || _out_buf == NULL || m_outputCount == 0 )
|
|
{
|
|
unlock();
|
|
return false;
|
|
}
|
|
|
|
waitForMessage( IdProcessingDone );
|
|
unlock();
|
|
|
|
const ch_cnt_t outputs = qMin<ch_cnt_t>( m_outputCount,
|
|
DEFAULT_CHANNELS );
|
|
if( m_splitChannels )
|
|
{
|
|
for( ch_cnt_t ch = 0; ch < outputs; ++ch )
|
|
{
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
_out_buf[frame][ch] = m_shm[( m_inputCount+ch )*
|
|
frames + frame];
|
|
}
|
|
}
|
|
}
|
|
else if( outputs == DEFAULT_CHANNELS )
|
|
{
|
|
memcpy( _out_buf, m_shm + m_inputCount * frames,
|
|
frames * BYTES_PER_FRAME );
|
|
}
|
|
else
|
|
{
|
|
sampleFrame * o = (sampleFrame *) ( m_shm +
|
|
m_inputCount*frames );
|
|
// clear buffer, if plugin didn't fill up both channels
|
|
engine::getMixer()->clearAudioBuffer( _out_buf, frames );
|
|
|
|
for( ch_cnt_t ch = 0; ch <
|
|
qMin<int>( DEFAULT_CHANNELS, outputs ); ++ch )
|
|
{
|
|
for( fpp_t frame = 0; frame < frames; ++frame )
|
|
{
|
|
_out_buf[frame][ch] = o[frame][ch];
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
void RemotePlugin::processMidiEvent( const midiEvent & _e,
|
|
const f_cnt_t _offset )
|
|
{
|
|
message m( IdMidiEvent );
|
|
m.addInt( _e.m_type );
|
|
m.addInt( _e.m_channel );
|
|
m.addInt( _e.m_data.m_param[0] );
|
|
m.addInt( _e.m_data.m_param[1] );
|
|
m.addInt( _offset );
|
|
lock();
|
|
sendMessage( m );
|
|
unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
void RemotePlugin::resizeSharedProcessingMemory()
|
|
{
|
|
const size_t s = ( m_inputCount+m_outputCount ) *
|
|
engine::getMixer()->framesPerPeriod() *
|
|
sizeof( float );
|
|
if( m_shm != NULL )
|
|
{
|
|
#ifdef USE_QT_SHMEM
|
|
m_shmObj.detach();
|
|
#else
|
|
shmdt( m_shm );
|
|
shmctl( m_shmID, IPC_RMID, NULL );
|
|
#endif
|
|
}
|
|
|
|
static int shm_key = 0;
|
|
#ifdef USE_QT_SHMEM
|
|
do
|
|
{
|
|
m_shmObj.setKey( QString( "%1" ).arg( ++shm_key ) );
|
|
m_shmObj.create( s );
|
|
} while( m_shmObj.error() != QSharedMemory::NoError );
|
|
|
|
m_shm = (float *) m_shmObj.data();
|
|
#else
|
|
while( ( m_shmID = shmget( ++shm_key, s, IPC_CREAT | IPC_EXCL |
|
|
0600 ) ) == -1 )
|
|
{
|
|
}
|
|
|
|
m_shm = (float *) shmat( m_shmID, 0, 0 );
|
|
#endif
|
|
m_shmSize = s;
|
|
sendMessage( message( IdChangeSharedMemoryKey ).
|
|
addInt( shm_key ).addInt( m_shmSize ) );
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RemotePlugin::processMessage( const message & _m )
|
|
{
|
|
lock();
|
|
message reply_message( _m.id );
|
|
bool reply = false;
|
|
switch( _m.id )
|
|
{
|
|
case IdUndefined:
|
|
return false;
|
|
|
|
case IdInitDone:
|
|
reply = true;
|
|
break;
|
|
|
|
case IdSampleRateInformation:
|
|
reply = true;
|
|
reply_message.addInt( engine::getMixer()->processingSampleRate() );
|
|
break;
|
|
|
|
case IdBufferSizeInformation:
|
|
reply = true;
|
|
reply_message.addInt( engine::getMixer()->framesPerPeriod() );
|
|
break;
|
|
|
|
case IdChangeInputCount:
|
|
m_inputCount = _m.getInt( 0 );
|
|
resizeSharedProcessingMemory();
|
|
break;
|
|
|
|
case IdChangeOutputCount:
|
|
m_outputCount = _m.getInt( 0 );
|
|
resizeSharedProcessingMemory();
|
|
break;
|
|
|
|
case IdDebugMessage:
|
|
fprintf( stderr, "RemotePlugin::DebugMessage: %s",
|
|
_m.getString( 0 ).c_str() );
|
|
break;
|
|
|
|
case IdProcessingDone:
|
|
case IdQuit:
|
|
default:
|
|
break;
|
|
}
|
|
if( reply )
|
|
{
|
|
sendMessage( reply_message );
|
|
}
|
|
unlock();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
#include "moc_RemotePlugin.cxx"
|
|
|