mirror of
https://github.com/LMMS/lmms.git
synced 2026-01-25 06:48:55 -05:00
git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@502 0778d3d1-df1d-0410-868b-ea421aaaa00d
664 lines
13 KiB
C++
664 lines
13 KiB
C++
/*
|
|
* lvsl_client.cpp - client for LVSL Server
|
|
*
|
|
* Copyright (c) 2005-2007 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.
|
|
*
|
|
*/
|
|
|
|
|
|
#include "qt3support.h"
|
|
|
|
#ifdef QT4
|
|
|
|
#include <QtCore/QLocale>
|
|
#include <QtCore/QTime>
|
|
#include <QtGui/QApplication>
|
|
#include <QtGui/QX11EmbedContainer>
|
|
#include <QtGui/QX11Info>
|
|
|
|
#else
|
|
|
|
#include <qapplication.h>
|
|
#include <qlocale.h>
|
|
#include <qdir.h>
|
|
|
|
#include "qxembed.h"
|
|
|
|
#define QX11EmbedContainer QXEmbed
|
|
#define embedClient embed
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SIGNAL_H
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
#include <cstdio>
|
|
|
|
#ifdef HAVE_SYS_IPC_H
|
|
#include <sys/ipc.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SHM_H
|
|
#include <sys/shm.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SELECT_H
|
|
#include <sys/select.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
|
|
#include "templates.h"
|
|
#include "config_mgr.h"
|
|
#include "engine.h"
|
|
#include "main_window.h"
|
|
#include "lvsl_client.h"
|
|
|
|
|
|
|
|
|
|
remoteVSTPlugin::remoteVSTPlugin( const QString & _plugin ) :
|
|
QObject(),
|
|
m_failed( TRUE ),
|
|
m_plugin( _plugin ),
|
|
m_pluginWidget( NULL ),
|
|
m_pluginXID( 0 ),
|
|
m_pluginPID( -1 ),
|
|
m_serverInFD( -1 ),
|
|
m_serverOutFD( -1 ),
|
|
m_serverMutex(),
|
|
m_name( "" ),
|
|
m_version( 0 ),
|
|
m_vendorString( "" ),
|
|
m_productString( "" ),
|
|
m_inputCount( 0 ),
|
|
m_outputCount( 0 ),
|
|
m_shmID( -1 ),
|
|
m_shm( NULL ),
|
|
m_shmSize( 0 ),
|
|
m_initialized( FALSE )
|
|
{
|
|
pipe( m_pipes[0] );
|
|
pipe( m_pipes[1] );
|
|
|
|
if( ( m_pluginPID = fork() ) < 0 )
|
|
{
|
|
printf( "fork() failed!\n" );
|
|
return;
|
|
}
|
|
else if( m_pluginPID == 0 )
|
|
{
|
|
dup2( m_pipes[0][0], 0 );
|
|
dup2( m_pipes[1][1], 1 );
|
|
QString lvsl_server_exec = configManager::inst()->pluginDir() +
|
|
QDir::separator() +
|
|
"lvsl_server";
|
|
execlp( lvsl_server_exec.
|
|
#ifdef QT4
|
|
toAscii().constData(),
|
|
#else
|
|
ascii(),
|
|
#endif
|
|
lvsl_server_exec.
|
|
#ifdef QT4
|
|
toAscii().constData(),
|
|
#else
|
|
ascii(),
|
|
#endif
|
|
"", NULL );
|
|
return;
|
|
}
|
|
m_serverInFD = m_pipes[1][0];
|
|
m_serverOutFD = m_pipes[0][1];
|
|
|
|
lock();
|
|
|
|
writeValueS<Sint16>( VST_LANGUAGE );
|
|
hostLanguages hlang = LanguageEnglish;
|
|
switch( QLocale::system().language() )
|
|
{
|
|
case QLocale::German: hlang = LanguageGerman; break;
|
|
case QLocale::French: hlang = LanguageFrench; break;
|
|
case QLocale::Italian: hlang = LanguageItalian; break;
|
|
case QLocale::Spanish: hlang = LanguageSpanish; break;
|
|
case QLocale::Japanese: hlang = LanguageJapanese; break;
|
|
default: break;
|
|
}
|
|
writeValueS<hostLanguages>( hlang );
|
|
|
|
QString p = m_plugin;
|
|
if( QFileInfo( p ).dir().isRelative() )
|
|
{
|
|
p = configManager::inst()->vstDir() + QDir::separator() + p;
|
|
}
|
|
|
|
writeValueS<Sint16>( VST_LOAD_PLUGIN );
|
|
writeStringS( p.
|
|
#ifdef QT4
|
|
toAscii().constData()
|
|
#else
|
|
ascii()
|
|
#endif
|
|
);
|
|
unlock();
|
|
|
|
while( 1 )
|
|
{
|
|
Sint16 cmd = VST_UNDEFINED_CMD;
|
|
if( messagesLeft() == TRUE )
|
|
{
|
|
cmd = processNextMessage();
|
|
}
|
|
if( cmd == VST_INITIALIZATION_DONE )
|
|
{
|
|
m_failed = FALSE;
|
|
break;
|
|
}
|
|
else if( cmd == VST_FAILED_LOADING_PLUGIN )
|
|
{
|
|
break;
|
|
}
|
|
#ifdef QT4
|
|
QApplication::processEvents( QEventLoop::AllEvents, 50 );
|
|
#else
|
|
qApp->processEvents( 50 );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
remoteVSTPlugin::~remoteVSTPlugin()
|
|
{
|
|
if( m_failed == FALSE )
|
|
{
|
|
setShmKeyAndSize( 0, 0 );
|
|
// tell server to quit and wait for acknowledge
|
|
writeValueS<Sint16>( VST_CLOSE_PLUGIN );
|
|
QTime t;
|
|
t.start();
|
|
while( t.elapsed() < 1000 )
|
|
{
|
|
if( messagesLeft() == TRUE &&
|
|
processNextMessage() == VST_QUIT_ACK )
|
|
{
|
|
//m_pluginPID = 0;
|
|
break;
|
|
}
|
|
}
|
|
if( m_pluginWidget != NULL )
|
|
{
|
|
m_pluginWidget->hide();
|
|
delete m_pluginWidget;
|
|
}
|
|
// timeout?
|
|
/* if( m_pluginPID != 0 )
|
|
{*/
|
|
kill( m_pluginPID, SIGTERM );
|
|
//}
|
|
|
|
// close all sides of our pipes
|
|
close( m_pipes[0][0] );
|
|
close( m_pipes[0][1] );
|
|
close( m_pipes[1][0] );
|
|
close( m_pipes[1][1] );
|
|
/* close( m_serverInFD );
|
|
close( m_serverOutFD );*/
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void remoteVSTPlugin::showEditor( void )
|
|
{
|
|
if( m_pluginWidget != NULL )
|
|
{
|
|
m_pluginWidget->show();
|
|
return;
|
|
}
|
|
|
|
if( m_pluginXID == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_pluginWidget = new QWidget( engine::getMainWindow()->workspace() );
|
|
m_pluginWidget->setFixedSize( m_pluginGeometry );
|
|
m_pluginWidget->setWindowTitle( name() );
|
|
#ifndef QT3
|
|
engine::getMainWindow()->workspace()->addWindow( m_pluginWidget );
|
|
#endif
|
|
m_pluginWidget->show();
|
|
|
|
QX11EmbedContainer * xe = new QX11EmbedContainer( m_pluginWidget );
|
|
xe->embedClient( m_pluginXID );
|
|
xe->setFixedSize( m_pluginGeometry );
|
|
//xe->setAutoDelete( FALSE );
|
|
xe->show();
|
|
|
|
lock();
|
|
writeValueS<Sint16>( VST_SHOW_EDITOR );
|
|
unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
void remoteVSTPlugin::hideEditor( void )
|
|
{
|
|
if( m_pluginWidget != NULL )
|
|
{
|
|
m_pluginWidget->hide();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool remoteVSTPlugin::process( const sampleFrame * _in_buf,
|
|
sampleFrame * _out_buf, bool _wait )
|
|
{
|
|
const fpp_t frames = engine::getMixer()->framesPerPeriod();
|
|
|
|
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 && messagesLeft() == TRUE )
|
|
{
|
|
(void) processNextMessage();
|
|
}
|
|
if( _out_buf != NULL )
|
|
{
|
|
engine::getMixer()->clearAudioBuffer( _out_buf,
|
|
frames );
|
|
}
|
|
return( FALSE );
|
|
}
|
|
|
|
memset( m_shm, 0, m_shmSize );
|
|
|
|
ch_cnt_t inputs = tMax<ch_cnt_t>( m_inputCount, DEFAULT_CHANNELS );
|
|
|
|
if( _in_buf != NULL && inputs > 0 )
|
|
{
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
|
|
lock();
|
|
writeValueS<Sint16>( VST_PROCESS );
|
|
unlock();
|
|
|
|
m_initialized = TRUE;
|
|
if( _wait )
|
|
{
|
|
waitForProcessingFinished( _out_buf );
|
|
}
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
|
|
|
|
bool remoteVSTPlugin::waitForProcessingFinished( sampleFrame * _out_buf )
|
|
{
|
|
if( !m_initialized || _out_buf == NULL || m_outputCount == 0 )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
|
|
// wait until server signals that process()ing is done
|
|
while( processNextMessage() != VST_PROCESS_DONE )
|
|
{
|
|
// hopefully scheduler gives process-time to plugin...
|
|
usleep( 10 );
|
|
}
|
|
|
|
const fpp_t frames = engine::getMixer()->framesPerPeriod();
|
|
const ch_cnt_t outputs = tMax<ch_cnt_t>( m_outputCount,
|
|
DEFAULT_CHANNELS );
|
|
if( outputs != DEFAULT_CHANNELS )
|
|
{
|
|
// clear buffer, if plugin didn't fill up both channels
|
|
engine::getMixer()->clearAudioBuffer( _out_buf, frames );
|
|
}
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
|
|
|
|
void remoteVSTPlugin::enqueueMidiEvent( const midiEvent & _event,
|
|
const f_cnt_t _frames_ahead )
|
|
{
|
|
lock();
|
|
writeValueS<Sint16>( VST_ENQUEUE_MIDI_EVENT );
|
|
writeValueS<midiEvent>( _event );
|
|
writeValueS<f_cnt_t>( _frames_ahead );
|
|
unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
void remoteVSTPlugin::loadSettings( const QDomElement & _this )
|
|
{
|
|
if( pluginWidget() != NULL )
|
|
{
|
|
if( _this.attribute( "guivisible" ).toInt() )
|
|
{
|
|
pluginWidget()->show();
|
|
}
|
|
else
|
|
{
|
|
pluginWidget()->hide();
|
|
}
|
|
}
|
|
const Sint32 num_params = _this.attribute( "numparams" ).toInt();
|
|
if( num_params > 0 )
|
|
{
|
|
QMap<QString, QString> dump;
|
|
for( Sint32 i = 0; i < num_params; ++i )
|
|
{
|
|
const QString key = "param" +
|
|
QString::number( i );
|
|
dump[key] = _this.attribute( key );
|
|
}
|
|
setParameterDump( dump );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void remoteVSTPlugin::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
|
{
|
|
if( pluginWidget() != NULL )
|
|
{
|
|
_this.setAttribute( "guivisible", pluginWidget()->isVisible() );
|
|
}
|
|
const QMap<QString, QString> & dump = parameterDump();
|
|
_this.setAttribute( "numparams", dump.size() );
|
|
for( QMap<QString, QString>::const_iterator it = dump.begin();
|
|
it != dump.end(); ++it )
|
|
{
|
|
#ifdef QT4
|
|
_this.setAttribute( it.key(), it.value() );
|
|
#else
|
|
_this.setAttribute( it.key(), it.data() );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void remoteVSTPlugin::setTempo( bpm_t _bpm )
|
|
{
|
|
lock();
|
|
writeValueS<Sint16>( VST_BPM );
|
|
writeValueS<bpm_t>( _bpm );
|
|
unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
void remoteVSTPlugin::updateSampleRate( void )
|
|
{
|
|
lock();
|
|
writeValueS<Sint16>( VST_SAMPLE_RATE );
|
|
writeValueS<sample_rate_t>( engine::getMixer()->sampleRate() );
|
|
unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
const QMap<QString, QString> & remoteVSTPlugin::parameterDump( void )
|
|
{
|
|
writeValueS<Sint16>( VST_GET_PARAMETER_DUMP );
|
|
|
|
while( processNextMessage() != VST_PARAMETER_DUMP )
|
|
{
|
|
}
|
|
|
|
return( m_parameterDump );
|
|
}
|
|
|
|
|
|
|
|
|
|
void remoteVSTPlugin::setParameterDump( const QMap<QString, QString> & _pdump )
|
|
{
|
|
writeValueS<Sint16>( VST_SET_PARAMETER_DUMP );
|
|
writeValueS<Sint32>( _pdump.size() );
|
|
for( QMap<QString, QString>::const_iterator it = _pdump.begin();
|
|
it != _pdump.end(); ++it )
|
|
{
|
|
const vstParameterDumpItem dump_item =
|
|
{
|
|
( *it ).section( ':', 0, 0 ).toInt(),
|
|
"",
|
|
( *it ).section( ':', 1, 1 ).toFloat()
|
|
} ;
|
|
writeValueS<vstParameterDumpItem>( dump_item );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void remoteVSTPlugin::setShmKeyAndSize( Uint16 _key, size_t _size )
|
|
{
|
|
if( m_shm != NULL && m_shmSize > 0 )
|
|
{
|
|
shmdt( m_shm );
|
|
m_shm = NULL;
|
|
m_shmSize = 0;
|
|
}
|
|
|
|
// only called for detaching SHM?
|
|
if( _size == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int shm_id = shmget( _key, _size, 0 );
|
|
if( shm_id == -1 )
|
|
{
|
|
printf( "failed getting shared memory\n" );
|
|
}
|
|
else
|
|
{
|
|
m_shm = (float *) shmat( shm_id, 0, 0 );
|
|
// TODO: error-checking
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool remoteVSTPlugin::messagesLeft( void ) const
|
|
{
|
|
fd_set rfds;
|
|
FD_ZERO( &rfds );
|
|
FD_SET( m_serverInFD, &rfds );
|
|
timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 1; // can we use 0 here?
|
|
return( select( m_serverInFD + 1, &rfds, NULL, NULL, &tv ) > 0 );
|
|
}
|
|
|
|
|
|
|
|
|
|
Sint16 remoteVSTPlugin::processNextMessage( void )
|
|
{
|
|
fd_set rfds;
|
|
FD_ZERO( &rfds );
|
|
FD_SET( m_serverInFD, &rfds );
|
|
if( select( m_serverInFD + 1, &rfds, NULL, NULL, NULL ) <= 0 )
|
|
{
|
|
return( VST_UNDEFINED_CMD );
|
|
}
|
|
|
|
lock();
|
|
Sint16 cmd = readValueS<Sint16>();
|
|
switch( cmd )
|
|
{
|
|
case VST_DEBUG_MSG:
|
|
printf( "debug message from server: %s\n",
|
|
readStringS().c_str() );
|
|
break;
|
|
|
|
case VST_GET_SAMPLE_RATE:
|
|
writeValueS<Sint16>( VST_SAMPLE_RATE );
|
|
// handle is the same
|
|
writeValueS<sample_rate_t>(
|
|
engine::getMixer()->sampleRate() );
|
|
break;
|
|
|
|
case VST_GET_BUFFER_SIZE:
|
|
writeValueS<Sint16>( VST_BUFFER_SIZE );
|
|
// handle is the same
|
|
writeValueS<fpp_t>(
|
|
engine::getMixer()->framesPerPeriod() );
|
|
break;
|
|
|
|
case VST_SHM_KEY_AND_SIZE:
|
|
{
|
|
Uint16 shm_key = readValueS<Uint16>();
|
|
size_t shm_size = readValueS<size_t>();
|
|
setShmKeyAndSize( shm_key, shm_size );
|
|
break;
|
|
}
|
|
|
|
case VST_INPUT_COUNT:
|
|
m_inputCount = readValueS<ch_cnt_t>();
|
|
break;
|
|
|
|
case VST_OUTPUT_COUNT:
|
|
m_outputCount = readValueS<ch_cnt_t>();
|
|
break;
|
|
|
|
case VST_PLUGIN_XID:
|
|
m_pluginXID = readValueS<Sint32>();
|
|
break;
|
|
|
|
case VST_PLUGIN_EDITOR_GEOMETRY:
|
|
{
|
|
const Sint16 w = readValueS<Sint16>();
|
|
const Sint16 h = readValueS<Sint16>();
|
|
m_pluginGeometry = QSize( w, h );
|
|
if( m_pluginWidget != NULL )
|
|
{
|
|
m_pluginWidget->setFixedSize(
|
|
m_pluginGeometry );
|
|
if( m_pluginWidget->childAt( 0, 0 ) != NULL )
|
|
{
|
|
m_pluginWidget->childAt( 0, 0
|
|
)->setFixedSize(
|
|
m_pluginGeometry );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case VST_PLUGIN_NAME:
|
|
m_name = readStringS().c_str();
|
|
break;
|
|
|
|
case VST_PLUGIN_VERSION:
|
|
m_version = readValueS<Sint32>();
|
|
break;
|
|
|
|
case VST_PLUGIN_VENDOR_STRING:
|
|
m_vendorString = readStringS().c_str();
|
|
break;
|
|
|
|
case VST_PLUGIN_PRODUCT_STRING:
|
|
m_productString = readStringS().c_str();
|
|
break;
|
|
|
|
case VST_PARAMETER_DUMP:
|
|
{
|
|
m_parameterDump.clear();
|
|
const Sint32 num_params = readValueS<Sint32>();
|
|
for( Sint32 i = 0; i < num_params; ++i )
|
|
{
|
|
vstParameterDumpItem dump_item =
|
|
readValueS<vstParameterDumpItem>();
|
|
m_parameterDump["param" + QString::number( dump_item.index )] =
|
|
QString::number( dump_item.index ) + ":" +
|
|
// QString( dump_item.shortLabel ) + ":" +
|
|
QString::number( dump_item.value );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case VST_PROCESS_DONE:
|
|
case VST_QUIT_ACK:
|
|
case VST_UNDEFINED_CMD:
|
|
default:
|
|
break;
|
|
}
|
|
unlock();
|
|
|
|
return( cmd );
|
|
}
|
|
|
|
|
|
|
|
#include "lvsl_client.moc"
|
|
|