mirror of
https://github.com/LMMS/lmms.git
synced 2026-01-25 14:58:07 -05:00
git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@774 0778d3d1-df1d-0410-868b-ea421aaaa00d
1268 lines
29 KiB
C++
1268 lines
29 KiB
C++
/*
|
|
* lvsl_server.cpp - LMMS VST Support Layer Server
|
|
*
|
|
* Copyright (c) 2005-2008 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
|
*
|
|
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
|
|
*
|
|
* Code partly taken from (X)FST:
|
|
* Copyright (c) 2004 Paul Davis
|
|
* Copyright (c) 2004 Torben Hohn
|
|
* Copyright (c) 2002 Kjetil S. Matheussen
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_PTHREAD_H
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_IPC_H
|
|
#include <sys/ipc.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SHM_H
|
|
#include <sys/shm.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_STDARG_H
|
|
#include <stdarg.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SIGNAL_H
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SCHED_H
|
|
#include <sched.h>
|
|
#endif
|
|
|
|
|
|
#include <windows.h>
|
|
#include <wine/exception.h>
|
|
|
|
|
|
#include <list>
|
|
#include <string>
|
|
|
|
|
|
#include <aeffectx.h>
|
|
|
|
#if kVstVersion < 2400
|
|
|
|
#define OLD_VST_SDK
|
|
|
|
#define VstInt32 long int
|
|
#define VstIntPtr long int
|
|
|
|
struct ERect
|
|
{
|
|
short top;
|
|
short left;
|
|
short bottom;
|
|
short right;
|
|
} ;
|
|
|
|
#endif
|
|
|
|
|
|
#include "types.h"
|
|
#include "midi.h"
|
|
#include "communication.h"
|
|
|
|
|
|
#ifdef HAVE_TLS
|
|
static __thread int ejmpbuf_valid = false;
|
|
static __thread jmp_buf ejmpbuf;
|
|
#else
|
|
static pthread_key_t ejmpbuf_valid_key;
|
|
static pthread_key_t ejmpbuf_key;
|
|
#endif
|
|
|
|
|
|
static hostLanguages hlang = LanguageEnglish;
|
|
|
|
|
|
class VSTPlugin;
|
|
|
|
VSTPlugin * plugin = NULL;
|
|
|
|
|
|
|
|
void lvsMessage( const char * _fmt, ... )
|
|
{
|
|
va_list ap;
|
|
char buffer[512];
|
|
|
|
va_start( ap, _fmt );
|
|
vsnprintf( buffer, sizeof( buffer ), _fmt, ap );
|
|
writeValue<Sint16>( VST_DEBUG_MSG, 1 );
|
|
writeString( buffer, 1 );
|
|
va_end( ap );
|
|
}
|
|
|
|
|
|
|
|
|
|
class VSTPlugin
|
|
{
|
|
public:
|
|
VSTPlugin( void );
|
|
|
|
~VSTPlugin();
|
|
|
|
void init( const std::string & _plugin_file );
|
|
|
|
void showEditor( void )
|
|
{
|
|
if( m_window != NULL )
|
|
{
|
|
PostThreadMessageA( m_guiThreadID, WM_USER, ShowEditor,
|
|
0 );
|
|
}
|
|
}
|
|
|
|
void process( void );
|
|
|
|
// enqueue given MIDI-event to be processed the next time process() is
|
|
// called
|
|
void enqueueMidiEvent( const midiEvent & _event,
|
|
const f_cnt_t _frames_ahead );
|
|
|
|
// set given sample-rate for plugin
|
|
void setSampleRate( const sample_rate_t _rate )
|
|
{
|
|
m_plugin->dispatcher( m_plugin, effSetSampleRate, 0, 0, NULL,
|
|
(float) _rate );
|
|
m_sampleRate = _rate;
|
|
}
|
|
|
|
// set given block-size for plugin
|
|
void setBlockSize( const fpp_t _bsize );
|
|
|
|
// set given tempo
|
|
void setBPM( const bpm_t _bpm )
|
|
{
|
|
m_bpm = _bpm;
|
|
}
|
|
|
|
// determine VST-version the plugin uses
|
|
Sint32 pluginVersion( void ) const
|
|
{
|
|
return( m_plugin->dispatcher( m_plugin,
|
|
effGetVendorVersion, 0, 0, NULL, 0.0f ) );
|
|
}
|
|
|
|
// determine name of plugin
|
|
const char * pluginName( void ) const;
|
|
|
|
// determine vendor of plugin
|
|
const char * pluginVendorString( void ) const;
|
|
|
|
// determine product-string of plugin
|
|
const char * pluginProductString( void ) const;
|
|
|
|
// do a complete parameter-dump and post it
|
|
void getParameterDump( void ) const;
|
|
|
|
// read parameter-dump and set it for plugin
|
|
void setParameterDump( void );
|
|
|
|
// post properties of specified parameter
|
|
void getParameterProperties( const Sint32 _idx );
|
|
|
|
// number of inputs
|
|
ch_cnt_t inputCount( void ) const
|
|
{
|
|
return( m_plugin->numInputs );
|
|
}
|
|
|
|
// number of outputs
|
|
ch_cnt_t outputCount( void ) const
|
|
{
|
|
return( m_plugin->numOutputs );
|
|
}
|
|
|
|
// called once at initialization and everytime number of inputs/outputs
|
|
// or block-size changes and is responsible for resizing shared memory
|
|
// and inform client about changed shm-keys, input/output-count etc.
|
|
void resizeSharedMemory( void );
|
|
|
|
|
|
private:
|
|
enum guiThreadMessages
|
|
{
|
|
None, ShowEditor, ClosePlugin
|
|
} ;
|
|
|
|
// callback used by plugin for being able to communicate with it's host
|
|
static VstIntPtr hostCallback( AEffect * _effect, VstInt32 _opcode,
|
|
VstInt32 _index, VstIntPtr _value,
|
|
void * _ptr, float _opt );
|
|
|
|
static DWORD WINAPI guiEventLoop( LPVOID _param );
|
|
|
|
|
|
bool load( const std::string & _plugin_file );
|
|
|
|
|
|
std::string m_shortName;
|
|
|
|
HINSTANCE m_libInst;
|
|
|
|
AEffect * m_plugin;
|
|
HWND m_window;
|
|
Sint32 m_windowXID;
|
|
Sint16 m_windowWidth;
|
|
Sint16 m_windowHeight;
|
|
|
|
pthread_mutex_t m_lock;
|
|
pthread_cond_t m_windowStatusChange;
|
|
DWORD m_guiThreadID;
|
|
|
|
|
|
fpp_t m_blockSize;
|
|
float * m_shm;
|
|
|
|
float * * m_inputs;
|
|
float * * m_outputs;
|
|
|
|
std::list<VstMidiEvent> m_midiEvents;
|
|
|
|
bpm_t m_bpm;
|
|
sample_rate_t m_sampleRate;
|
|
double m_currentSamplePos;
|
|
|
|
} ;
|
|
|
|
|
|
|
|
|
|
VSTPlugin::VSTPlugin( void ) :
|
|
m_shortName( "" ),
|
|
m_libInst( NULL ),
|
|
m_plugin( NULL ),
|
|
m_window( NULL ),
|
|
m_windowXID( 0 ),
|
|
m_windowWidth( 0 ),
|
|
m_windowHeight( 0 ),
|
|
m_lock(),
|
|
m_windowStatusChange(),
|
|
m_guiThreadID( 0 ),
|
|
m_blockSize( 0 ),
|
|
m_shm( NULL ),
|
|
m_inputs( NULL ),
|
|
m_outputs( NULL ),
|
|
m_midiEvents(),
|
|
m_bpm( 0 ),
|
|
m_sampleRate( 44100 ),
|
|
m_currentSamplePos( 0 )
|
|
{
|
|
}
|
|
|
|
|
|
|
|
|
|
void VSTPlugin::init( const std::string & _plugin_file )
|
|
{
|
|
if( load( _plugin_file ) == false )
|
|
{
|
|
writeValue<Sint16>( VST_FAILED_LOADING_PLUGIN );
|
|
return;
|
|
}
|
|
|
|
/* set program to zero */
|
|
/* i comment this out because it breaks dfx Geometer
|
|
* looks like we cant set programs for it
|
|
*
|
|
m_plugin->dispatcher( m_plugin, effSetProgram, 0, 0, NULL, 0.0f); */
|
|
// request rate and blocksize
|
|
writeValue<Sint16>( VST_GET_SAMPLE_RATE );
|
|
writeValue<Sint16>( VST_GET_BUFFER_SIZE );
|
|
|
|
|
|
m_plugin->dispatcher( m_plugin, effMainsChanged, 0, 1, NULL, 0.0f );
|
|
|
|
|
|
if( CreateThread( NULL, 0, guiEventLoop, this, 0, NULL ) == NULL )
|
|
{
|
|
lvsMessage( "could not create GUI-thread" );
|
|
return;
|
|
}
|
|
pthread_cond_wait( &m_windowStatusChange, &m_lock);
|
|
|
|
|
|
// now post some information about our plugin
|
|
writeValue<Sint16>( VST_PLUGIN_XID );
|
|
writeValue<Sint32>( m_windowXID );
|
|
|
|
if( m_windowXID != 0 )
|
|
{
|
|
writeValue<Sint16>( VST_PLUGIN_EDITOR_GEOMETRY );
|
|
writeValue<Sint16>( m_windowWidth );
|
|
writeValue<Sint16>( m_windowHeight );
|
|
}
|
|
|
|
writeValue<Sint16>( VST_PLUGIN_NAME );
|
|
writeString( pluginName() );
|
|
|
|
writeValue<Sint16>( VST_PLUGIN_VERSION );
|
|
writeValue<Sint32>( pluginVersion() );
|
|
|
|
writeValue<Sint16>( VST_PLUGIN_VENDOR_STRING );
|
|
writeString( pluginVendorString() );
|
|
|
|
writeValue<Sint16>( VST_PLUGIN_PRODUCT_STRING );
|
|
writeString( pluginProductString() );
|
|
|
|
writeValue<Sint16>( VST_PARAMETER_COUNT );
|
|
writeValue<Sint32>( (Sint32) m_plugin->numParams );
|
|
|
|
// tell client that we've done everything so far
|
|
writeValue<Sint16>( VST_INITIALIZATION_DONE );
|
|
}
|
|
|
|
|
|
|
|
|
|
VSTPlugin::~VSTPlugin()
|
|
{
|
|
// acknowledge quit
|
|
writeValue<Sint16>( VST_QUIT_ACK );
|
|
|
|
if( m_window != NULL )
|
|
{
|
|
// notify GUI-thread
|
|
if( !PostThreadMessageA( m_guiThreadID, WM_USER,
|
|
ClosePlugin, 0 ) )
|
|
{
|
|
//lvsMessage( "could not post message to gui thread" );
|
|
}
|
|
pthread_cond_wait( &m_windowStatusChange, &m_lock );
|
|
m_plugin->dispatcher( m_plugin, effEditClose, 0, 0, NULL, 0.0 );
|
|
CloseWindow( m_window );
|
|
m_window = NULL;
|
|
}
|
|
|
|
if( m_libInst != NULL )
|
|
{
|
|
FreeLibrary( m_libInst );
|
|
m_libInst = NULL;
|
|
}
|
|
|
|
delete[] m_inputs;
|
|
delete[] m_outputs;
|
|
|
|
if( m_shm != NULL )
|
|
{
|
|
shmdt( m_shm );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool VSTPlugin::load( const std::string & _plugin_file )
|
|
{
|
|
if( ( m_libInst = LoadLibraryA( _plugin_file.c_str() ) ) ==
|
|
NULL )
|
|
{
|
|
return( false );
|
|
}
|
|
|
|
char * tmp = strdup( _plugin_file.c_str() );
|
|
m_shortName = basename( tmp );
|
|
free( tmp );
|
|
|
|
typedef AEffect * ( __stdcall * mainEntry )( audioMasterCallback );
|
|
mainEntry main_entry = (mainEntry) GetProcAddress( m_libInst,
|
|
"main" );
|
|
if( main_entry == NULL )
|
|
{
|
|
return( false );
|
|
}
|
|
|
|
m_plugin = main_entry( hostCallback );
|
|
if( m_plugin == NULL )
|
|
{
|
|
return( false );
|
|
}
|
|
|
|
m_plugin->user = this;
|
|
|
|
if( m_plugin->magic != kEffectMagic )
|
|
{
|
|
lvsMessage( "%s is not a VST plugin\n", _plugin_file.c_str() );
|
|
}
|
|
|
|
m_plugin->dispatcher( m_plugin, effOpen, 0, 0, 0, 0 );
|
|
|
|
return( true );
|
|
}
|
|
|
|
|
|
|
|
void VSTPlugin::process( void )
|
|
{
|
|
// first we gonna post all MIDI-events we enqueued so far
|
|
if( m_midiEvents.size() )
|
|
{
|
|
// since MIDI-events are not received immediately, we
|
|
// have to have them stored somewhere even after
|
|
// dispatcher-call, so we create static copies of the
|
|
// data and post them
|
|
#define MIDI_EVENT_BUFFER_COUNT 1024
|
|
static char event_buf[sizeof( VstMidiEvent * ) *
|
|
MIDI_EVENT_BUFFER_COUNT +
|
|
sizeof( VstEvents )];
|
|
static VstMidiEvent vme[MIDI_EVENT_BUFFER_COUNT];
|
|
VstEvents * events = (VstEvents *) event_buf;
|
|
events->reserved = 0;
|
|
events->numEvents = m_midiEvents.size();
|
|
Sint16 idx = 0;
|
|
for( std::list<VstMidiEvent>::iterator it =
|
|
m_midiEvents.begin();
|
|
it != m_midiEvents.end(); ++it, ++idx )
|
|
{
|
|
memcpy( &vme[idx], &*it, sizeof( VstMidiEvent ) );
|
|
events->events[idx] = (VstEvent *) &vme[idx];
|
|
}
|
|
|
|
m_midiEvents.clear();
|
|
m_plugin->dispatcher( m_plugin, effProcessEvents, 0, 0, events,
|
|
0.0f );
|
|
}
|
|
|
|
// now we're ready to fetch sound from VST-plugin
|
|
|
|
for( ch_cnt_t i = 0; i < inputCount(); ++i )
|
|
{
|
|
m_inputs[i] = &m_shm[i * m_blockSize];
|
|
}
|
|
|
|
for( ch_cnt_t i = 0; i < outputCount(); ++i )
|
|
{
|
|
m_outputs[i] = &m_shm[( i + inputCount() ) * m_blockSize];
|
|
memset( m_outputs[i], 0, m_blockSize * sizeof( float ) );
|
|
}
|
|
|
|
#ifdef OLD_VST_SDK
|
|
if( m_plugin->flags & effFlagsCanReplacing )
|
|
{
|
|
#endif
|
|
m_plugin->processReplacing( m_plugin, m_inputs,
|
|
m_outputs,
|
|
m_blockSize );
|
|
#ifdef OLD_VST_SDK
|
|
}
|
|
else
|
|
{
|
|
m_plugin->process( m_plugin, m_inputs, m_outputs,
|
|
m_blockSize );
|
|
}
|
|
#endif
|
|
|
|
m_currentSamplePos += m_blockSize;
|
|
|
|
writeValue<Sint16>( VST_PROCESS_DONE );
|
|
|
|
// give plugin some idle-time for GUI-update and so on...
|
|
m_plugin->dispatcher( m_plugin, effEditIdle, 0, 0, NULL, 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VSTPlugin::enqueueMidiEvent( const midiEvent & _event,
|
|
const f_cnt_t _frames_ahead )
|
|
{
|
|
VstMidiEvent event;
|
|
|
|
event.type = kVstMidiType;
|
|
event.byteSize = 24;
|
|
event.deltaFrames = _frames_ahead;
|
|
event.flags = 0;
|
|
event.detune = 0;
|
|
event.noteLength = 0;
|
|
event.noteOffset = 0;
|
|
event.noteOffVelocity = 0;
|
|
event.reserved1 = 0;
|
|
event.reserved2 = 0;
|
|
event.midiData[0] = _event.m_type + _event.m_channel;
|
|
switch( _event.m_type )
|
|
{
|
|
case PITCH_BEND:
|
|
event.midiData[1] = _event.m_data.m_param[0] & 0x7f;
|
|
event.midiData[2] = _event.m_data.m_param[0] >> 7;
|
|
break;
|
|
// TODO: handle more special cases
|
|
default:
|
|
event.midiData[1] = _event.key();
|
|
event.midiData[2] = _event.velocity();
|
|
break;
|
|
}
|
|
event.midiData[3] = 0;
|
|
m_midiEvents.push_back( event );
|
|
}
|
|
|
|
|
|
|
|
|
|
void VSTPlugin::setBlockSize( const fpp_t _bsize )
|
|
{
|
|
if( _bsize == m_blockSize )
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_blockSize = _bsize;
|
|
resizeSharedMemory();
|
|
m_plugin->dispatcher( m_plugin, effSetBlockSize, 0, _bsize, NULL,
|
|
0.0f );
|
|
}
|
|
|
|
|
|
|
|
|
|
const char * VSTPlugin::pluginName( void ) const
|
|
{
|
|
static char buf[32];
|
|
buf[0] = 0;
|
|
m_plugin->dispatcher( m_plugin, effGetEffectName, 0, 0, buf, 0.0f );
|
|
buf[31] = 0;
|
|
return( buf );
|
|
}
|
|
|
|
|
|
|
|
|
|
const char * VSTPlugin::pluginVendorString( void ) const
|
|
{
|
|
static char buf[64];
|
|
buf[0] = 0;
|
|
m_plugin->dispatcher( m_plugin, effGetVendorString, 0, 0, buf, 0.0f );
|
|
buf[63] = 0;
|
|
return( buf );
|
|
}
|
|
|
|
|
|
|
|
|
|
const char * VSTPlugin::pluginProductString( void ) const
|
|
{
|
|
static char buf[64];
|
|
buf[0] = 0;
|
|
m_plugin->dispatcher( m_plugin, effGetProductString, 0, 0, buf, 0.0f );
|
|
buf[63] = 0;
|
|
return( buf );
|
|
}
|
|
|
|
|
|
|
|
|
|
void VSTPlugin::getParameterDump( void ) const
|
|
{
|
|
VstParameterProperties vst_props;
|
|
vstParameterDumpItem dump_item;
|
|
writeValue<Sint16>( VST_PARAMETER_DUMP );
|
|
writeValue<Sint32>( (Sint32) m_plugin->numParams );
|
|
for( Sint32 i = 0; i < m_plugin->numParams; ++i )
|
|
{
|
|
dump_item.index = i;
|
|
m_plugin->dispatcher( m_plugin, effGetParameterProperties, i, 0,
|
|
&vst_props, 0.0f );
|
|
memcpy( dump_item.shortLabel, vst_props.shortLabel,
|
|
sizeof( dump_item.shortLabel ) );
|
|
dump_item.value = m_plugin->getParameter( m_plugin, i );
|
|
writeValue<vstParameterDumpItem>( dump_item );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void VSTPlugin::setParameterDump( void )
|
|
{
|
|
const Sint32 sz = readValue<Sint32>();
|
|
const Sint32 params = ( sz > m_plugin->numParams ) ?
|
|
m_plugin->numParams : sz;
|
|
for( Sint32 i = 0; i < params; ++i )
|
|
{
|
|
vstParameterDumpItem dump_item =
|
|
readValue<vstParameterDumpItem>();
|
|
m_plugin->setParameter( m_plugin, dump_item.index,
|
|
dump_item.value );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void VSTPlugin::getParameterProperties( const Sint32 _idx )
|
|
{
|
|
VstParameterProperties vst_props;
|
|
m_plugin->dispatcher( m_plugin, effGetParameterProperties, _idx, 0,
|
|
&vst_props, 0.0f );
|
|
vstParamProperties props;
|
|
memcpy( props.label, vst_props.label, sizeof( props.label ) );
|
|
memcpy( props.shortLabel, vst_props.shortLabel,
|
|
sizeof( props.shortLabel) );
|
|
#if kVstVersion > 2
|
|
memcpy( props.categoryLabel, vst_props.categoryLabel,
|
|
sizeof( props.categoryLabel ) );
|
|
#endif
|
|
props.minValue = vst_props.minInteger;
|
|
props.maxValue = vst_props.maxInteger;
|
|
props.step = ( vst_props.flags & kVstParameterUsesFloatStep ) ?
|
|
vst_props.stepFloat :
|
|
vst_props.stepInteger;
|
|
#if kVstVersion > 2
|
|
props.category = vst_props.category;
|
|
#endif
|
|
writeValue<Sint16>( VST_PARAMETER_PROPERTIES );
|
|
writeValue<vstParamProperties>( props );
|
|
}
|
|
|
|
|
|
|
|
|
|
void VSTPlugin::resizeSharedMemory( void )
|
|
{
|
|
delete[] m_inputs;
|
|
delete[] m_outputs;
|
|
|
|
size_t s = ( inputCount() + outputCount() ) * m_blockSize *
|
|
sizeof( float );
|
|
if( m_shm != NULL )
|
|
{
|
|
shmdt( m_shm );
|
|
}
|
|
|
|
int shm_id;
|
|
Uint16 shm_key = 0;
|
|
while( ( shm_id = shmget( ++shm_key, s, IPC_CREAT | IPC_EXCL |
|
|
0600 ) ) == -1 )
|
|
{
|
|
}
|
|
|
|
m_shm = (float *) shmat( shm_id, 0, 0 );
|
|
|
|
if( inputCount() > 0 )
|
|
{
|
|
m_inputs = new float * [inputCount()];
|
|
}
|
|
if( outputCount() > 0 )
|
|
{
|
|
m_outputs = new float * [outputCount()];
|
|
}
|
|
|
|
writeValue<Sint16>( VST_INPUT_COUNT );
|
|
writeValue<ch_cnt_t>( inputCount() );
|
|
|
|
writeValue<Sint16>( VST_OUTPUT_COUNT );
|
|
writeValue<ch_cnt_t>( outputCount() );
|
|
|
|
writeValue<Sint16>( VST_SHM_KEY_AND_SIZE );
|
|
writeValue<Uint16>( shm_key );
|
|
writeValue<Uint32>( s );
|
|
}
|
|
|
|
|
|
|
|
#define DEBUG_CALLBACKS
|
|
#ifdef DEBUG_CALLBACKS
|
|
#define SHOW_CALLBACK lvsMessage
|
|
#else
|
|
#define SHOW_CALLBACK(...)
|
|
#endif
|
|
|
|
|
|
/* TODO:
|
|
* - complete audioMasterGetTime-handling (bars etc.)
|
|
* - implement audioMasterProcessEvents
|
|
* - audioMasterGetVendorVersion: return LMMS-version (config.h!)
|
|
* - audioMasterGetDirectory: return either VST-plugin-dir or LMMS-workingdir
|
|
* - audioMasterOpenFileSelector: show QFileDialog?
|
|
*/
|
|
VstIntPtr VSTPlugin::hostCallback( AEffect * _effect, VstInt32 _opcode,
|
|
VstInt32 _index, VstIntPtr _value,
|
|
void * _ptr, float _opt )
|
|
{
|
|
static VstTimeInfo _timeInfo;
|
|
//SHOW_CALLBACK( "host-callback, opcode = %d\n", (int) _opcode );
|
|
switch( _opcode )
|
|
{
|
|
case audioMasterAutomate:
|
|
//SHOW_CALLBACK( "amc: audioMasterAutomate\n" );
|
|
// index, value, returns 0
|
|
_effect->setParameter( _effect, _index, _opt );
|
|
return( 0 );
|
|
|
|
case audioMasterVersion:
|
|
//SHOW_CALLBACK( "amc: audioMasterVersion\n" );
|
|
return( 2300 );
|
|
|
|
case audioMasterCurrentId:
|
|
SHOW_CALLBACK( "amc: audioMasterCurrentId\n" );
|
|
// returns the unique id of a plug that's currently
|
|
// loading
|
|
return( 0 );
|
|
|
|
case audioMasterIdle:
|
|
//SHOW_CALLBACK ("amc: audioMasterIdle\n" );
|
|
// call application idle routine (this will
|
|
// call effEditIdle for all open editors too)
|
|
_effect->dispatcher( _effect, effEditIdle, 0, 0, NULL,
|
|
0.0f );
|
|
return( 0 );
|
|
|
|
case audioMasterPinConnected:
|
|
//SHOW_CALLBACK( "amc: audioMasterPinConnected\n" );
|
|
// inquire if an input or output is beeing connected;
|
|
// index enumerates input or output counting from zero:
|
|
// value is 0 for input and != 0 otherwise. note: the
|
|
// return value is 0 for <true> such that older versions
|
|
// will always return true.
|
|
return( 1 );
|
|
|
|
case audioMasterGetTime:
|
|
//SHOW_CALLBACK( "amc: audioMasterGetTime\n" );
|
|
// returns const VstTimeInfo* (or 0 if not supported)
|
|
// <value> should contain a mask indicating which
|
|
// fields are required (see valid masks above), as some
|
|
// items may require extensive conversions
|
|
|
|
memset( &_timeInfo, 0, sizeof( _timeInfo ) );
|
|
|
|
_timeInfo.samplePos = plugin->m_currentSamplePos;
|
|
_timeInfo.sampleRate = plugin->m_sampleRate;
|
|
_timeInfo.flags = 0;
|
|
_timeInfo.tempo = plugin->m_bpm;
|
|
_timeInfo.timeSigNumerator = 4;
|
|
_timeInfo.timeSigDenominator = 4;
|
|
_timeInfo.flags |= (/* kVstBarsValid|*/kVstTempoValid );
|
|
_timeInfo.flags |= kVstTransportPlaying;
|
|
|
|
return( (long) &_timeInfo );
|
|
|
|
case audioMasterProcessEvents:
|
|
//SHOW_CALLBACK( "amc: audioMasterProcessEvents\n" );
|
|
// VstEvents* in <ptr>
|
|
return( 0 );
|
|
|
|
case audioMasterIOChanged:
|
|
plugin->resizeSharedMemory();
|
|
//SHOW_CALLBACK( "amc: audioMasterIOChanged\n" );
|
|
// numInputs and/or numOutputs has changed
|
|
return( 0 );
|
|
|
|
#ifdef OLD_VST_SDK
|
|
case audioMasterWantMidi:
|
|
//SHOW_CALLBACK( "amc: audioMasterWantMidi\n" );
|
|
// <value> is a filter which is currently ignored
|
|
return( 1 );
|
|
|
|
case audioMasterSetTime:
|
|
SHOW_CALLBACK( "amc: audioMasterSetTime\n" );
|
|
// VstTimenfo* in <ptr>, filter in <value>, not
|
|
// supported
|
|
return( 0 );
|
|
|
|
case audioMasterTempoAt:
|
|
//SHOW_CALLBACK( "amc: audioMasterTempoAt\n" );
|
|
return( plugin->m_bpm * 10000 );
|
|
|
|
case audioMasterGetNumAutomatableParameters:
|
|
SHOW_CALLBACK( "amc: audioMasterGetNumAutomatable"
|
|
"Parameters\n" );
|
|
return( 5000 );
|
|
|
|
case audioMasterGetParameterQuantization:
|
|
SHOW_CALLBACK( "amc: audioMasterGetParameter\n"
|
|
"Quantization\n" );
|
|
// returns the integer value for +1.0 representation,
|
|
// or 1 if full single float precision is maintained
|
|
// in automation. parameter index in <value> (-1: all,
|
|
// any)
|
|
return( 1 );
|
|
|
|
case audioMasterNeedIdle:
|
|
//SHOW_CALLBACK( "amc: audioMasterNeedIdle\n" );
|
|
// plug needs idle calls (outside its editor window)
|
|
return( 1 );
|
|
|
|
case audioMasterGetPreviousPlug:
|
|
SHOW_CALLBACK( "amc: audioMasterGetPreviousPlug\n" );
|
|
// input pin in <value> (-1: first to come), returns
|
|
// cEffect*
|
|
return( 0 );
|
|
|
|
case audioMasterGetNextPlug:
|
|
SHOW_CALLBACK( "amc: audioMasterGetNextPlug\n" );
|
|
// output pin in <value> (-1: first to come), returns
|
|
// cEffect*
|
|
return( 0 );
|
|
|
|
case audioMasterWillReplaceOrAccumulate:
|
|
SHOW_CALLBACK( "amc: audioMasterWillReplaceOr"
|
|
"Accumulate\n" );
|
|
// returns: 0: not supported, 1: replace, 2: accumulate
|
|
return( 0 );
|
|
|
|
case audioMasterGetSpeakerArrangement:
|
|
SHOW_CALLBACK( "amc: audioMasterGetSpeaker"
|
|
"Arrangement\n" );
|
|
// (long)input in <value>, output in <ptr>
|
|
return( 0 );
|
|
|
|
case audioMasterSetOutputSampleRate:
|
|
SHOW_CALLBACK( "amc: audioMasterSetOutputSample"
|
|
"Rate\n" );
|
|
// for variable i/o, sample rate in <opt>
|
|
return( 0 );
|
|
|
|
case audioMasterSetIcon:
|
|
SHOW_CALLBACK( "amc: audioMasterSetIcon\n" );
|
|
// TODO
|
|
// void* in <ptr>, format not defined yet
|
|
return( 0 );
|
|
|
|
case audioMasterOpenWindow:
|
|
SHOW_CALLBACK( "amc: audioMasterOpenWindow\n" );
|
|
// TODO
|
|
// returns platform specific ptr
|
|
return( 0 );
|
|
|
|
case audioMasterCloseWindow:
|
|
SHOW_CALLBACK( "amc: audioMasterCloseWindow\n" );
|
|
// TODO
|
|
// close window, platform specific handle in <ptr>
|
|
return( 0 );
|
|
#endif
|
|
|
|
case audioMasterSizeWindow:
|
|
//SHOW_CALLBACK( "amc: audioMasterSizeWindow\n" );
|
|
if( plugin->m_window == 0 )
|
|
{
|
|
return( 0 );
|
|
}
|
|
plugin->m_windowWidth = _index;
|
|
plugin->m_windowHeight = _value;
|
|
SetWindowPos( plugin->m_window, 0, 0, 0,
|
|
_index + 8, _value + 26,
|
|
SWP_NOACTIVATE | SWP_NOMOVE |
|
|
SWP_NOOWNERZORDER | SWP_NOZORDER );
|
|
writeValue<Sint16>( VST_PLUGIN_EDITOR_GEOMETRY );
|
|
writeValue<Sint16>( plugin->m_windowWidth );
|
|
writeValue<Sint16>( plugin->m_windowHeight );
|
|
return( 1 );
|
|
|
|
case audioMasterGetSampleRate:
|
|
//SHOW_CALLBACK( "amc: audioMasterGetSampleRate\n" );
|
|
return( plugin->m_sampleRate );
|
|
|
|
case audioMasterGetBlockSize:
|
|
//SHOW_CALLBACK( "amc: audioMasterGetBlockSize\n" );
|
|
return( plugin->m_blockSize );
|
|
|
|
case audioMasterGetInputLatency:
|
|
//SHOW_CALLBACK( "amc: audioMasterGetInputLatency\n" );
|
|
return( plugin->m_blockSize );
|
|
|
|
case audioMasterGetOutputLatency:
|
|
//SHOW_CALLBACK( "amc: audioMasterGetOutputLatency\n" );
|
|
return( plugin->m_blockSize );
|
|
|
|
case audioMasterGetCurrentProcessLevel:
|
|
SHOW_CALLBACK( "amc: audioMasterGetCurrentProcess"
|
|
"Level\n" );
|
|
// returns: 0: not supported,
|
|
// 1: currently in user thread (gui)
|
|
// 2: currently in audio thread (where process is
|
|
// called)
|
|
// 3: currently in 'sequencer' thread (midi, timer etc)
|
|
// 4: currently offline processing and thus in user
|
|
// thread
|
|
// other: not defined, but probably pre-empting user
|
|
// thread.
|
|
return( 0 );
|
|
|
|
case audioMasterGetAutomationState:
|
|
SHOW_CALLBACK( "amc: audioMasterGetAutomationState\n" );
|
|
// returns 0: not supported, 1: off, 2:read, 3:write,
|
|
// 4:read/write offline
|
|
return( 0 );
|
|
|
|
case audioMasterOfflineStart:
|
|
SHOW_CALLBACK( "amc: audioMasterOfflineStart\n" );
|
|
return( 0 );
|
|
|
|
case audioMasterOfflineRead:
|
|
SHOW_CALLBACK( "amc: audioMasterOfflineRead\n" );
|
|
// ptr points to offline structure, see below.
|
|
// return 0: error, 1 ok
|
|
return( 0 );
|
|
|
|
case audioMasterOfflineWrite:
|
|
SHOW_CALLBACK( "amc: audioMasterOfflineWrite\n" );
|
|
// same as read
|
|
return( 0 );
|
|
|
|
case audioMasterOfflineGetCurrentPass:
|
|
SHOW_CALLBACK( "amc: audioMasterOfflineGetCurrent"
|
|
"Pass\n" );
|
|
return( 0 );
|
|
|
|
case audioMasterOfflineGetCurrentMetaPass:
|
|
SHOW_CALLBACK( "amc: audioMasterOfflineGetCurrentMeta"
|
|
"Pass\n");
|
|
return( 0 );
|
|
|
|
case audioMasterGetVendorString:
|
|
//SHOW_CALLBACK( "amc: audioMasterGetVendorString\n" );
|
|
// fills <ptr> with a string identifying the vendor
|
|
// (max 64 char)
|
|
strcpy( (char *) _ptr, "Tobias Doerffel & others");
|
|
return( 1 );
|
|
|
|
case audioMasterGetProductString:
|
|
//SHOW_CALLBACK( "amc: audioMasterGetProductString\n" );
|
|
// fills <ptr> with a string with product name
|
|
// (max 64 char)
|
|
strcpy( (char *) _ptr,
|
|
"LMMS VST Support Layer (LVSL)" );
|
|
return( 1 );
|
|
|
|
case audioMasterGetVendorVersion:
|
|
SHOW_CALLBACK( "amc: audioMasterGetVendorVersion\n" );
|
|
// returns vendor-specific version
|
|
return( 1000 );
|
|
|
|
case audioMasterVendorSpecific:
|
|
SHOW_CALLBACK( "amc: audioMasterVendorSpecific\n" );
|
|
// no definition, vendor specific handling
|
|
return( 0 );
|
|
|
|
case audioMasterCanDo:
|
|
//SHOW_CALLBACK( "amc: audioMasterCanDo\n" );
|
|
return( !strcmp( (char *) _ptr, "sendVstEvents" ) ||
|
|
!strcmp( (char *) _ptr, "sendVstMidiEvent" ) ||
|
|
!strcmp( (char *) _ptr, "sendVstTimeInfo" ) ||
|
|
!strcmp( (char *) _ptr, "sizeWindow" ) );
|
|
|
|
case audioMasterGetLanguage:
|
|
//SHOW_CALLBACK( "amc: audioMasterGetLanguage\n" );
|
|
return( hlang );
|
|
|
|
case audioMasterGetDirectory:
|
|
SHOW_CALLBACK( "amc: audioMasterGetDirectory\n" );
|
|
// get plug directory, FSSpec on MAC, else char*
|
|
return( 0 );
|
|
|
|
case audioMasterUpdateDisplay:
|
|
//SHOW_CALLBACK( "amc: audioMasterUpdateDisplay\n" );
|
|
// something has changed, update 'multi-fx' display
|
|
_effect->dispatcher( _effect, effEditIdle, 0, 0, NULL,
|
|
0.0f );
|
|
return( 0 );
|
|
|
|
#if kVstVersion > 2
|
|
case audioMasterBeginEdit:
|
|
SHOW_CALLBACK( "amc: audioMasterBeginEdit\n" );
|
|
// begin of automation session (when mouse down),
|
|
// parameter index in <index>
|
|
return( 0 );
|
|
|
|
case audioMasterEndEdit:
|
|
SHOW_CALLBACK( "amc: audioMasterEndEdit\n" );
|
|
// end of automation session (when mouse up),
|
|
// parameter index in <index>
|
|
return( 0 );
|
|
|
|
case audioMasterOpenFileSelector:
|
|
SHOW_CALLBACK( "amc: audioMasterOpenFileSelector\n" );
|
|
// open a fileselector window with VstFileSelect*
|
|
// in <ptr>
|
|
return( 0 );
|
|
#endif
|
|
default:
|
|
SHOW_CALLBACK( "VST master dispatcher: undefined: "
|
|
"%d\n", (int) _opcode );
|
|
break;
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD WINAPI VSTPlugin::guiEventLoop( LPVOID _param )
|
|
{
|
|
VSTPlugin * _this = static_cast<VSTPlugin *>( _param );
|
|
_this->m_guiThreadID = GetCurrentThreadId();
|
|
|
|
// "guard point" to trap errors that occur during plugin loading
|
|
#ifdef HAVE_TLS
|
|
if( sigsetjmp( ejmpbuf, 1 ) )
|
|
{
|
|
lvsMessage( "creating the editor for %s failed",
|
|
_this->m_shortName.c_str() );
|
|
pthread_cond_signal( &_this->m_windowStatusChange );
|
|
return( 1 );
|
|
}
|
|
|
|
ejmpbuf_valid = true;
|
|
#else
|
|
jmp_buf * ejmpbuf = new jmp_buf[1];
|
|
int * ejmpbuf_valid = new int;
|
|
*ejmpbuf_valid = false;
|
|
|
|
pthread_key_create( &ejmpbuf_key, NULL );
|
|
pthread_setspecific( ejmpbuf_key, ejmpbuf );
|
|
pthread_key_create( &ejmpbuf_valid_key, NULL );
|
|
pthread_setspecific( ejmpbuf_valid_key, ejmpbuf_valid );
|
|
|
|
if( sigsetjmp( *ejmpbuf, 1 ) )
|
|
{
|
|
exit( 1 );
|
|
}
|
|
|
|
*ejmpbuf_valid = true;
|
|
#endif
|
|
|
|
// Note: m_lock is held while this function is called
|
|
if( !( _this->m_plugin->flags & effFlagsHasEditor ) )
|
|
{
|
|
pthread_cond_signal( &_this->m_windowStatusChange );
|
|
return( 1 );
|
|
}
|
|
|
|
|
|
HMODULE hInst = GetModuleHandleA( NULL );
|
|
if( hInst == NULL )
|
|
{
|
|
lvsMessage( "can't get module handle" );
|
|
pthread_cond_signal( &_this->m_windowStatusChange );
|
|
return( 1 );
|
|
}
|
|
|
|
if( ( _this->m_window = CreateWindowExA(
|
|
0, "LVSL", _this->m_shortName.c_str(),
|
|
( WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME &
|
|
~WS_MAXIMIZEBOX ),
|
|
0, 0, 1, 1, NULL, NULL, hInst, NULL ) ) == NULL )
|
|
{
|
|
lvsMessage( "cannot create editor window" );
|
|
pthread_cond_signal( &_this->m_windowStatusChange );
|
|
return( 1 );
|
|
}
|
|
ShowWindow( _this->m_window, SW_SHOWMINIMIZED );
|
|
ShowWindow( _this->m_window, SW_HIDE );
|
|
|
|
_this->m_windowXID = (Sint32) GetPropA( _this->m_window,
|
|
"__wine_x11_whole_window" );
|
|
|
|
|
|
_this->m_plugin->dispatcher( _this->m_plugin, effEditOpen, 0, 0,
|
|
_this->m_window, 0 );
|
|
|
|
ERect * er;
|
|
_this->m_plugin->dispatcher( _this->m_plugin, effEditGetRect, 0, 0,
|
|
&er, 0 );
|
|
|
|
_this->m_windowWidth = er->right - er->left;
|
|
_this->m_windowHeight = er->bottom - er->top;
|
|
|
|
SetWindowPos( _this->m_window, 0, 0, 0, _this->m_windowWidth + 8,
|
|
_this->m_windowHeight + 26, 0
|
|
#if 0
|
|
SWP_NOACTIVATE /*| SWP_NOREDRAW*/ | SWP_NOMOVE |
|
|
SWP_NOOWNERZORDER | SWP_NOZORDER
|
|
#endif
|
|
);
|
|
#ifdef HAVE_TLS
|
|
ejmpbuf_valid = false;
|
|
#else
|
|
*ejmpbuf_valid = false;
|
|
#endif
|
|
|
|
pthread_cond_signal( &_this->m_windowStatusChange );
|
|
|
|
|
|
MSG msg;
|
|
|
|
bool quit = false;
|
|
while( quit == false && GetMessageA( &msg, NULL, 0, 0 ) )
|
|
{
|
|
TranslateMessage( &msg );
|
|
DispatchMessageA( &msg );
|
|
|
|
if( msg.message == WM_USER )
|
|
{
|
|
switch( msg.wParam )
|
|
{
|
|
case ShowEditor:
|
|
ShowWindow( _this->m_window,
|
|
SW_SHOWNORMAL );
|
|
UpdateWindow( _this->m_window );
|
|
break;
|
|
|
|
case ClosePlugin:
|
|
quit = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pthread_cond_signal( &_this->m_windowStatusChange );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
|
|
|
|
|
|
int main( void )
|
|
{
|
|
// WINE-startup
|
|
HMODULE hInst = GetModuleHandleA( NULL );
|
|
if( hInst == NULL )
|
|
{
|
|
lvsMessage( "can't get module handle" );
|
|
return( -1 );
|
|
}
|
|
|
|
WNDCLASSA wc;
|
|
wc.style = 0;
|
|
wc.lpfnWndProc = DefWindowProcA;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
wc.hInstance = hInst;
|
|
wc.hIcon = LoadIconA( hInst, "LVSL" );
|
|
wc.hCursor = LoadCursorA( NULL, IDI_APPLICATION );
|
|
wc.hbrBackground = (HBRUSH) GetStockObject( BLACK_BRUSH );
|
|
wc.lpszMenuName = "MENU_LVSL";
|
|
wc.lpszClassName = "LVSL";
|
|
|
|
if( !RegisterClassA( &wc ) )
|
|
{
|
|
return( -1 );
|
|
}
|
|
|
|
#ifdef HAVE_SCHED_H
|
|
// try to set realtime-priority
|
|
struct sched_param sparam;
|
|
sparam.sched_priority = ( sched_get_priority_max( SCHED_FIFO ) +
|
|
sched_get_priority_min( SCHED_FIFO ) ) / 2;
|
|
if( sched_setscheduler( 0, SCHED_FIFO, &sparam ) == -1 )
|
|
{
|
|
lvsMessage( "could not set realtime priority for VST-server" );
|
|
}
|
|
#endif
|
|
|
|
Sint16 cmd;
|
|
while( ( cmd = readValue<Sint16>() ) != VST_CLOSE_PLUGIN )
|
|
{
|
|
switch( cmd )
|
|
{
|
|
case VST_LOAD_PLUGIN:
|
|
plugin = new VSTPlugin();
|
|
plugin->init( readString() );
|
|
break;
|
|
|
|
case VST_SHOW_EDITOR:
|
|
plugin->showEditor();
|
|
break;
|
|
|
|
case VST_PROCESS:
|
|
plugin->process();
|
|
break;
|
|
|
|
case VST_ENQUEUE_MIDI_EVENT:
|
|
{
|
|
midiEventTypes type =
|
|
readValue<midiEventTypes>();
|
|
Sint8 channel = readValue<Sint8>();
|
|
Uint16 param1 = readValue<Uint16>();
|
|
Uint16 param2 = readValue<Uint16>();
|
|
const midiEvent ev = midiEvent( type, channel,
|
|
param1, param2 );
|
|
|
|
const f_cnt_t fr_ahead = readValue<f_cnt_t>();
|
|
plugin->enqueueMidiEvent( ev, fr_ahead );
|
|
break;
|
|
}
|
|
|
|
case VST_SAMPLE_RATE:
|
|
plugin->setSampleRate(
|
|
readValue<sample_rate_t>() );
|
|
break;
|
|
|
|
|
|
case VST_BUFFER_SIZE:
|
|
plugin->setBlockSize( readValue<fpp_t>() );
|
|
break;
|
|
|
|
case VST_BPM:
|
|
plugin->setBPM( readValue<bpm_t>() );
|
|
break;
|
|
|
|
case VST_LANGUAGE:
|
|
hlang = readValue<hostLanguages>();
|
|
break;
|
|
|
|
case VST_GET_PARAMETER_DUMP:
|
|
plugin->getParameterDump();
|
|
break;
|
|
|
|
case VST_SET_PARAMETER_DUMP:
|
|
plugin->setParameterDump();
|
|
break;
|
|
|
|
case VST_GET_PARAMETER_PROPERTIES:
|
|
plugin->getParameterProperties(
|
|
readValue<Sint32>() );
|
|
break;
|
|
|
|
default:
|
|
lvsMessage( "unhandled message: %d\n",
|
|
(int) cmd );
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete plugin;
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|