Initial version of the GIG Player

It currently loads 16-bit mono and stereo GIG files. It plays the whole
note (no attack/delay/release), doesn't resample when exporting as a
different sample rate, and it doesn't do anything different based on
velocity.
This commit is contained in:
Garrett
2014-04-27 16:40:43 -07:00
parent dec3185d88
commit d4adf8cc31
18 changed files with 1797 additions and 0 deletions

View File

@@ -36,6 +36,7 @@ OPTION(WANT_PULSEAUDIO "Include PulseAudio support" ON)
OPTION(WANT_PORTAUDIO "Include PortAudio support" ON)
OPTION(WANT_SDL "Include SDL (Simple DirectMedia Layer) support" ON)
OPTION(WANT_SF2 "Include SoundFont2 player plugin" ON)
OPTION(WANT_GIG "Include GIG player plugin" ON)
OPTION(WANT_STK "Include Stk (Synthesis Toolkit) support" ON)
OPTION(WANT_SWH "Include Steve Harris's LADSPA plugins" ON)
OPTION(WANT_TAP "Include Tom's Audio Processing LADSPA plugins" ON)
@@ -315,6 +316,17 @@ IF(WANT_SF2)
ENDIF(FLUIDSYNTH_FOUND)
ENDIF(WANT_SF2)
# check for libgig
If(WANT_GIG)
PKG_CHECK_MODULES(GIG gig)
IF(GIG_FOUND)
SET(LMMS_HAVE_GIG TRUE)
SET(STATUS_GIG "OK")
ELSE(GIG_FOUND)
SET(STATUS_GIG "not found, libgig needed for decoding .gig files")
ENDIF(GIG_FOUND)
ENDIF(WANT_GIG)
# check for pthreads
IF(LMMS_BUILD_LINUX OR LMMS_BUILD_APPLE)
FIND_PACKAGE(Threads)
@@ -652,6 +664,7 @@ MESSAGE(
"* CMT LADSPA plugins : ${STATUS_CMT}\n"
"* TAP LADSPA plugins : ${STATUS_TAP}\n"
"* SWH LADSPA plugins : ${STATUS_SWH}\n"
"* GIG player : ${STATUS_GIG}\n"
)
MESSAGE(

View File

@@ -25,6 +25,7 @@ ADD_SUBDIRECTORY(peak_controller_effect)
IF(NOT LMMS_BUILD_APPLE)
ADD_SUBDIRECTORY(sf2_player)
ENDIF()
ADD_SUBDIRECTORY(gig_player)
ADD_SUBDIRECTORY(sfxr)
ADD_SUBDIRECTORY(sid)
ADD_SUBDIRECTORY(SpectrumAnalyzer)

View File

@@ -0,0 +1,8 @@
if(LMMS_HAVE_GIG)
INCLUDE(BuildPlugin)
INCLUDE_DIRECTORIES(${GIG_INCLUDE_DIRS}{)
LINK_DIRECTORIES(${GIG_LIBRARY_DIRS})
LINK_LIBRARIES(${GIG_LIBRARIES})
BUILD_PLUGIN(gigplayer gig_player.cpp gig_player.h patches_dialog.cpp patches_dialog.h patches_dialog.ui MOCFILES gig_player.h patches_dialog.h UICFILES patches_dialog.ui EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png")
endif(LMMS_HAVE_GIG)

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,832 @@
/*
* gig_player.cpp - a gig player using libgig
*
* Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
* Copyright (c) 2009-2014 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 <QtCore/QDebug>
#include <QtGui/QLayout>
#include <QtGui/QLabel>
#include <QtXml/QDomDocument>
#include <math.h> // sin, cos, ...
#include <cstring> // memset
#include "FileDialog.h"
#include "gig_player.h"
#include "engine.h"
#include "InstrumentTrack.h"
#include "InstrumentPlayHandle.h"
#include "NotePlayHandle.h"
#include "knob.h"
#include "song.h"
#include "patches_dialog.h"
#include "tooltip.h"
#include "LcdSpinBox.h"
#include "embed.cpp"
extern "C"
{
Plugin::Descriptor PLUGIN_EXPORT gigplayer_plugin_descriptor =
{
STRINGIFY( PLUGIN_NAME ),
"GIG Player",
QT_TRANSLATE_NOOP( "pluginBrowser", "Player for GIG files" ),
"Garrett Wilson <g/at/floft/dot/net>",
0x0100,
Plugin::Instrument,
new PluginPixmapLoader( "logo" ),
"gig",
NULL
} ;
}
struct GIGPluginData
{
int midiNote;
int lastPanning;
float lastVelocity;
} ;
// Static map of current GIG instances
QMap<QString, gigInstance*> gigInstrument::s_instances;
QMutex gigInstrument::s_instancesMutex;
gigInstrument::gigInstrument( InstrumentTrack * _instrument_track ) :
Instrument( _instrument_track, &gigplayer_plugin_descriptor ),
m_srcState( NULL ),
m_instance( NULL ),
m_filename( "" ),
m_lastMidiPitch( -1 ),
m_lastMidiPitchRange( -1 ),
m_channel( 1 ),
m_bankNum( 0, 0, 999, this, tr("Bank") ),
m_patchNum( 0, 0, 127, this, tr("Patch") ),
m_gain( 1.0f, 0.0f, 5.0f, 0.01f, this, tr( "Gain" ) )
{
InstrumentPlayHandle * iph = new InstrumentPlayHandle( this );
engine::mixer()->addPlayHandle( iph );
connect( &m_bankNum, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) );
connect( &m_patchNum, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) );
//connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) );
// Gain
//connect( &m_gain, SIGNAL( dataChanged() ), this, SLOT( updateGain() ) );
}
gigInstrument::~gigInstrument()
{
engine::mixer()->removePlayHandles( instrumentTrack() );
freeInstance();
if( m_srcState != NULL )
{
src_delete( m_srcState );
}
}
void gigInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
_this.setAttribute( "src", m_filename );
m_patchNum.saveSettings( _doc, _this, "patch" );
m_bankNum.saveSettings( _doc, _this, "bank" );
m_gain.saveSettings( _doc, _this, "gain" );
}
void gigInstrument::loadSettings( const QDomElement & _this )
{
openFile( _this.attribute( "src" ), false );
m_patchNum.loadSettings( _this, "patch" );
m_bankNum.loadSettings( _this, "bank" );
m_gain.loadSettings( _this, "gain" );
updatePatch();
}
void gigInstrument::loadFile( const QString & _file )
{
if( !_file.isEmpty() && QFileInfo( _file ).exists() )
{
openFile( _file, false );
updatePatch();
}
}
AutomatableModel * gigInstrument::childModel( const QString & _modelName )
{
if( _modelName == "bank" )
{
return &m_bankNum;
}
else if( _modelName == "patch" )
{
return &m_patchNum;
}
qCritical() << "requested unknown model " << _modelName;
return NULL;
}
QString gigInstrument::nodeName() const
{
return gigplayer_plugin_descriptor.name;
}
void gigInstrument::freeInstance()
{
m_synthMutex.lock();
if ( m_instance != NULL )
{
s_instancesMutex.lock();
--(m_instance->refCount);
// No more references
if( m_instance->refCount <= 0 )
{
qDebug() << "Really deleting " << m_filename;
// Need we do more to delete the gig instance?
s_instances.remove( m_filename );
delete m_instance;
}
// Just remove our reference
else
{
qDebug() << "un-referencing " << m_filename;
}
s_instancesMutex.unlock();
m_instance = NULL;
}
m_synthMutex.unlock();
}
void gigInstrument::openFile( const QString & _gigFile, bool updateTrackName )
{
emit fileLoading();
// Used for loading file
char * gigAscii = qstrdup( qPrintable( SampleBuffer::tryToMakeAbsolute( _gigFile ) ) );
QString relativePath = SampleBuffer::tryToMakeRelative( _gigFile );
// free reference to gig file if one is selected
freeInstance();
m_synthMutex.lock();
s_instancesMutex.lock();
// Increment Reference
if( s_instances.contains( relativePath ) )
{
qDebug() << "Using existing reference to " << relativePath;
m_instance = s_instances[ relativePath ];
m_instance->refCount++;
}
// Add to map, if doesn't exist.
else
{
// Grab this sf from the top of the stack and add to list
m_instance = new gigInstance( _gigFile );
s_instances.insert( relativePath, m_instance );
}
s_instancesMutex.unlock();
m_synthMutex.unlock();
// TODO: only do this if successfully loaded
if( true )
{
m_filename = relativePath;
emit fileChanged();
}
delete[] gigAscii;
if( updateTrackName )
{
instrumentTrack()->setName( QFileInfo( _gigFile ).baseName() );
}
}
void gigInstrument::updatePatch()
{
if( m_bankNum.value() >= 0 && m_patchNum.value() >= 0 )
{
// TODO: are there patches in GIG files?
//fluid_synth_program_select( m_synth, m_channel, m_fontId,
// m_bankNum.value(), m_patchNum.value() );
}
}
QString gigInstrument::getCurrentPatchName()
{
if (!m_instance)
return "";
int iBankSelected = m_bankNum.value();
int iProgSelected = m_patchNum.value();
gig::Instrument* pInstrument = m_instance->gig.GetFirstInstrument();
while (pInstrument) {
int iBank = pInstrument->MIDIBank;
int iProg = pInstrument->MIDIProgram;
if (iBank == iBankSelected && iProg == iProgSelected) {
QString name = QString::fromStdString(pInstrument->pInfo->Name);
if (name == "")
name = "<no name>";
return name;
}
pInstrument = m_instance->gig.GetNextInstrument();
}
return "";
}
void gigInstrument::playNote( NotePlayHandle * _n, sampleFrame * )
{
const float LOG440 = 2.643452676f;
const f_cnt_t tfp = _n->totalFramesPlayed();
int midiNote = (int)floor( 12.0 * ( log2( _n->unpitchedFrequency() ) - LOG440 ) - 4.0 );
// out of range?
if( midiNote <= 0 || midiNote >= 128 )
{
return;
}
if( tfp == 0 )
{
GIGPluginData * pluginData = new GIGPluginData;
pluginData->midiNote = midiNote;
pluginData->lastPanning = -1;
pluginData->lastVelocity = 127;
_n->m_pluginData = pluginData;
// TODO: Start the note here
//const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity();
//fluid_synth_noteon( m_synth, m_channel, midiNote, _n->midiVelocity( baseVelocity ) );
if (m_instance)
{
// Find instrument
int iBankSelected = m_bankNum.value();
int iProgSelected = m_patchNum.value();
gig::Instrument* pInstrument = m_instance->gig.GetFirstInstrument();
while (pInstrument) {
int iBank = pInstrument->MIDIBank;
int iProg = pInstrument->MIDIProgram;
if (iBank == iBankSelected && iProg == iProgSelected) {
break;
}
pInstrument = m_instance->gig.GetNextInstrument();
}
// Find sample
if (pInstrument)
{
gig::Region* pRegion = pInstrument->GetFirstRegion();
while (pRegion) {
gig::Sample* pSample = pRegion->GetSample();
if (pSample)
{
int rate = pSample->SamplesPerSecond;
QString name = QString::fromStdString(pSample->pInfo->Name);
int keyLow = pRegion->KeyRange.low;
int keyHigh = pRegion->KeyRange.high;
if (rate != engine::mixer()->processingSampleRate())
{
qDebug() << "Warning: wrong sample rate, conversion not implemented";
}
if (midiNote >= keyLow && midiNote <= keyHigh)
{
/*qDebug() << "Playing note " << midiNote << " between " << keyLow
<< " and " << keyHigh
<< " from sample " << name << " at " << rate << " Hz of size "
<< pSample->SamplesTotal;*/
gig::buffer_t buf = pSample->LoadSampleData();
gigNote note(pSample->SamplesTotal);
if (pSample->BitDepth == 24)
{
qDebug() << "Error: not 16 bit... not implemented";
/*int n = pSample->SamplesTotal * pSample->Channels;
for (int i = n-1; i>=0; i-=pSample->Channels)
for (int j = 0; j < pSample->Channels; ++j)
note.note[i][j] =
1.0/0x800000*(pWave[i*3+j]
| pWave[i*3+1+3*j]<<8
| pWave[i*3+2+3*j]<<16);*/
}
else
{
int16_t* pInt = reinterpret_cast<int16_t*>(buf.pStart);
if (pSample->Channels <= 2)
{
for (int i = 0; i < pSample->SamplesTotal/pSample->Channels; ++i)
{
note.note[i][0] = 1.0/0x10000*pInt[pSample->Channels*i];
if (pSample->Channels == 1)
note.note[i][1] = note.note[i][0];
else
note.note[i][1] = 1.0/0x10000*pInt[pSample->Channels*i+1];
}
}
else
{
qDebug() << "Error: not stereo... not implemented";
}
}
m_synthMutex.lock();
m_notes.push_back(note);
m_synthMutex.unlock();
pSample->ReleaseSampleData();
}
}
pRegion = pInstrument->GetNextRegion();
}
}
}
else
{
// TODO: eventually get rid of this
// By default just play a sine wave instead of a GIG file.
double freq = _n->unpitchedFrequency();
//int size = ceil(engine::mixer()->processingSampleRate()/freq);
int size = 1.0/2*engine::mixer()->processingSampleRate();
float scale = 0.25; // Default max volume for one note
gigNote note(size);
note.midiNote = midiNote;
for (int i = 0; i < size; ++i)
{
note.note[i][0] = sin(1.0*i*freq*2*M_PI/engine::mixer()->processingSampleRate())
* (1.0-1.0/size*i)*scale;
note.note[i][1] = note.note[i][0];
}
m_synthMutex.lock();
m_notes.push_back(note);
m_synthMutex.unlock();
}
}
/*GIGPluginData * pluginData = static_cast<GIGPluginData *>(
_n->m_pluginData );
const float currentVelocity = _n->volumeLevel( tfp ) * instrumentTrack()->midiPort()->baseVelocity();
if( pluginData->lastVelocity != currentVelocity )
{
m_synthMutex.lock();
// TODO: set the new velocity to currentvelocity
m_synthMutex.unlock();
pluginData->lastVelocity = currentVelocity;
}*/
}
// Could we get iph-based instruments support sample-exact models by using a
// frame-length of 1 while rendering?
void gigInstrument::play( sampleFrame * _working_buffer )
{
const fpp_t frames = engine::mixer()->framesPerPeriod();
// Initialize to zeros
for (int i = 0; i < frames; ++i)
{
_working_buffer[i][0] = float();
_working_buffer[i][1] = float();
}
m_synthMutex.lock();
const int currentMidiPitch = instrumentTrack()->midiPitch();
if( m_lastMidiPitch != currentMidiPitch )
{
m_lastMidiPitch = currentMidiPitch;
// TODO: pitch bend...
//fluid_synth_pitch_bend( m_synth, m_channel, m_lastMidiPitch );
}
const int currentMidiPitchRange = instrumentTrack()->midiPitchRange();
if( m_lastMidiPitchRange != currentMidiPitchRange )
{
m_lastMidiPitchRange = currentMidiPitchRange;
// TODO: pitch bend...
//fluid_synth_pitch_wheel_sens( m_synth, m_channel, m_lastMidiPitchRange );
}
if( m_internalSampleRate < engine::mixer()->processingSampleRate() &&
m_srcState != NULL )
{
const fpp_t f = frames * m_internalSampleRate / engine::mixer()->processingSampleRate();
#ifdef __GNUC__
sampleFrame tmp[f];
#else
sampleFrame * tmp = new sampleFrame[f];
#endif
qDebug() << "Different internal sample rate";
// TODO: get sample, interleave left/right channels
SRC_DATA src_data;
src_data.data_in = tmp[0];
src_data.data_out = _working_buffer[0];
src_data.input_frames = f;
src_data.output_frames = frames;
src_data.src_ratio = (double) frames / f;
src_data.end_of_input = 0;
int error = src_process( m_srcState, &src_data );
#ifndef __GNUC__
delete[] tmp;
#endif
if( error )
{
qCritical( "gigInstrument: error while resampling: %s", src_strerror( error ) );
}
if( src_data.output_frames_gen > frames )
{
qCritical( "gigInstrument: not enough frames: %ld / %d", src_data.output_frames_gen, frames );
}
}
else
{
// TODO: get sample, interleave left/right channels
//fluid_synth_write_float( m_synth, frames, _working_buffer, 0, 2, _working_buffer, 1, 2 );
for( int i = 0; i < frames; ++i )
{
for( std::list<gigNote>::iterator note = m_notes.begin(); note != m_notes.end(); ++note )
{
if( note->position < note->size && note->size > 0 )
{
_working_buffer[i][0] += note->note[note->position][0]*m_gain.value();
_working_buffer[i][1] += note->note[note->position][1]*m_gain.value();
++note->position;
}
}
}
}
m_synthMutex.unlock();
instrumentTrack()->processAudioBuffer( _working_buffer, frames, NULL );
}
void gigInstrument::deleteNotePluginData( NotePlayHandle * _n )
{
//GIGPluginData * pluginData = static_cast<GIGPluginData *>( _n->m_pluginData );
m_synthMutex.lock();
// Delete ended notes. Continue looping starting at the beginning. Erasing
// invalidates the iterator.
bool deleted = false;
do
{
deleted = false;
std::list<gigNote>::iterator i = m_notes.begin();
while( i != m_notes.end() )
{
if( i->position >= i->size )
{
m_notes.erase(i);
deleted = true;
break;
}
++i;
}
}
while( deleted );
m_synthMutex.unlock();
//delete pluginData;
}
PluginView * gigInstrument::instantiateView( QWidget * _parent )
{
return new gigInstrumentView( this, _parent );
}
class gigKnob : public knob
{
public:
gigKnob( QWidget * _parent ) :
// knob( knobStyled, _parent )
knob( knobBright_26, _parent )
{
setFixedSize( 31, 38 );
}
};
gigInstrumentView::gigInstrumentView( Instrument * _instrument, QWidget * _parent ) :
InstrumentView( _instrument, _parent )
{
gigInstrument* k = castModel<gigInstrument>();
connect( &k->m_bankNum, SIGNAL( dataChanged() ), this, SLOT( updatePatchName() ) );
connect( &k->m_patchNum, SIGNAL( dataChanged() ), this, SLOT( updatePatchName() ) );
// File Button
m_fileDialogButton = new pixmapButton( this );
m_fileDialogButton->setCursor( QCursor( Qt::PointingHandCursor ) );
m_fileDialogButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "fileselect_on" ) );
m_fileDialogButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "fileselect_off" ) );
m_fileDialogButton->move( 217, 107 );
connect( m_fileDialogButton, SIGNAL( clicked() ), this, SLOT( showFileDialog() ) );
toolTip::add( m_fileDialogButton, tr( "Open other GIG file" ) );
m_fileDialogButton->setWhatsThis( tr( "Click here to open another GIG file" ) );
// Patch Button
m_patchDialogButton = new pixmapButton( this );
m_patchDialogButton->setCursor( QCursor( Qt::PointingHandCursor ) );
m_patchDialogButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "patches_on" ) );
m_patchDialogButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "patches_off" ) );
m_patchDialogButton->setEnabled( false );
m_patchDialogButton->move( 217, 125 );
connect( m_patchDialogButton, SIGNAL( clicked() ), this, SLOT( showPatchDialog() ) );
toolTip::add( m_patchDialogButton, tr( "Choose the patch" ) );
// LCDs
m_bankNumLcd = new LcdSpinBox( 3, "21pink", this );
m_bankNumLcd->move(131, 62);
m_patchNumLcd = new LcdSpinBox( 3, "21pink", this );
m_patchNumLcd->move(190, 62);
// Next row
m_filenameLabel = new QLabel( this );
m_filenameLabel->setGeometry( 58, 109, 156, 11 );
m_patchLabel = new QLabel( this );
m_patchLabel->setGeometry( 58, 127, 156, 11 );
// Gain
m_gainKnob = new gigKnob( this );
m_gainKnob->setHintText( tr("Gain") + " ", "" );
m_gainKnob->move( 86, 55 );
setAutoFillBackground( true );
QPalette pal;
pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) );
setPalette( pal );
updateFilename();
}
gigInstrumentView::~gigInstrumentView()
{
}
void gigInstrumentView::modelChanged()
{
gigInstrument * k = castModel<gigInstrument>();
m_bankNumLcd->setModel( &k->m_bankNum );
m_patchNumLcd->setModel( &k->m_patchNum );
m_gainKnob->setModel( &k->m_gain );
connect( k, SIGNAL( fileChanged() ), this, SLOT( updateFilename() ) );
connect( k, SIGNAL( fileLoading() ), this, SLOT( invalidateFile() ) );
updateFilename();
}
void gigInstrumentView::updateFilename()
{
gigInstrument * i = castModel<gigInstrument>();
QFontMetrics fm( m_filenameLabel->font() );
QString file = i->m_filename.endsWith( ".gig", Qt::CaseInsensitive ) ?
i->m_filename.left( i->m_filename.length() - 4 ) :
i->m_filename;
m_filenameLabel->setText( fm.elidedText( file, Qt::ElideLeft, m_filenameLabel->width() ) );
m_patchDialogButton->setEnabled( !i->m_filename.isEmpty() );
updatePatchName();
update();
}
void gigInstrumentView::updatePatchName()
{
gigInstrument * i = castModel<gigInstrument>();
QFontMetrics fm( font() );
QString patch = i->getCurrentPatchName();
m_patchLabel->setText( fm.elidedText( patch, Qt::ElideLeft, m_patchLabel->width() ) );
update();
}
void gigInstrumentView::invalidateFile()
{
m_patchDialogButton->setEnabled( false );
}
void gigInstrumentView::showFileDialog()
{
gigInstrument * k = castModel<gigInstrument>();
FileDialog ofd( NULL, tr( "Open GIG file" ) );
ofd.setFileMode( FileDialog::ExistingFiles );
QStringList types;
types << tr( "GIG Files (*.gig)" );
ofd.setFilters( types );
QString dir;
if( k->m_filename != "" )
{
QString f = k->m_filename;
if( QFileInfo( f ).isRelative() )
{
f = configManager::inst()->userSamplesDir() + f;
if( QFileInfo( f ).exists() == false )
{
f = configManager::inst()->factorySamplesDir() + k->m_filename;
}
}
ofd.setDirectory( QFileInfo( f ).absolutePath() );
ofd.selectFile( QFileInfo( f ).fileName() );
}
else
{
ofd.setDirectory( configManager::inst()->userSamplesDir() );
}
m_fileDialogButton->setEnabled( false );
if( ofd.exec() == QDialog::Accepted && !ofd.selectedFiles().isEmpty() )
{
QString f = ofd.selectedFiles()[0];
if( f != "" )
{
k->openFile( f );
engine::getSong()->setModified();
}
}
m_fileDialogButton->setEnabled( true );
}
void gigInstrumentView::showPatchDialog()
{
// TODO: does it have patches?
gigInstrument * k = castModel<gigInstrument>();
patchesDialog pd( this );
pd.setup( k->m_instance, 1, k->instrumentTrack()->name(), &k->m_bankNum, &k->m_patchNum, m_patchLabel );
pd.exec();
}
extern "C"
{
// necessary for getting instance out of shared lib
Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data )
{
return new gigInstrument( static_cast<InstrumentTrack *>( _data ) );
}
}
#include "moc_gig_player.cxx"

View File

@@ -0,0 +1,247 @@
/*
* gig_player.h - a gig player using libgig
*
* Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
* Copyright (c) 2009-2014 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.
*
*/
#ifndef _GIG_PLAYER_H
#define _GIG_PLAYER_H
#include <QtCore/QMutex>
#include <list>
#include "Instrument.h"
#include "pixmap_button.h"
#include "InstrumentView.h"
#include "knob.h"
#include "LcdSpinBox.h"
#include "led_checkbox.h"
#include "SampleBuffer.h"
#include "gig.h"
class gigInstrumentView;
class gigInstance;
class NotePlayHandle;
class patchesDialog;
class QLabel;
class gigInstance
{
public:
gigInstance( QString filename ) :
riff( filename.toUtf8().constData() ),
gig( &riff ),
refCount( 1 )
{}
private:
RIFF::File riff;
public:
gig::File gig;
int refCount;
};
class gigNote
{
public:
gigNote() :
position( 0 ),
midiNote( -1 ),
note( NULL ),
size( 0 )
{
}
gigNote(int size) :
position( 0 ),
midiNote( -1 ),
size( size )
{
note = new sampleFrame[size];
// Initialize to no sound
for (int i = 0; i < size; ++i)
{
note[i][0] = float();
note[i][1] = float();
}
}
gigNote( const gigNote& g ) :
position( g.position ),
midiNote( g.midiNote ),
note( NULL ),
size( g.size )
{
if (size > 0)
{
note = new sampleFrame[size];
std::copy(&g.note[0], &g.note[size], &note[0]);
}
}
~gigNote()
{
if (note)
delete[] note;
}
int position;
int midiNote;
sampleFrame* note;
int size; // Don't try changing this...
};
class gigInstrument : public Instrument
{
Q_OBJECT
mapPropertyFromModel(int,getBank,setBank,m_bankNum);
mapPropertyFromModel(int,getPatch,setPatch,m_patchNum);
public:
gigInstrument( InstrumentTrack * _instrument_track );
virtual ~gigInstrument();
virtual void play( sampleFrame * _working_buffer );
virtual void playNote( NotePlayHandle * _n,
sampleFrame * _working_buffer );
virtual void deleteNotePluginData( NotePlayHandle * _n );
virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent );
virtual void loadSettings( const QDomElement & _this );
virtual void loadFile( const QString & _file );
virtual AutomatableModel * childModel( const QString & _modelName );
virtual QString nodeName() const;
virtual f_cnt_t desiredReleaseFrames() const
{
return 0;
}
virtual Flags flags() const
{
return IsSingleStreamed | IsMidiBased;
}
virtual PluginView * instantiateView( QWidget * _parent );
QString getCurrentPatchName();
void setParameter( const QString & _param, const QString & _value );
public slots:
void openFile( const QString & _gigFile, bool updateTrackName = true );
void updatePatch();
private:
static QMutex s_instancesMutex;
static QMap<QString, gigInstance*> s_instances;
std::list<gigNote> m_notes;
SRC_STATE * m_srcState;
gigInstance* m_instance;
QString m_filename;
// Protect the array of active notes
QMutex m_notesRunningMutex;
// Protect synth when we are re-creating it.
QMutex m_synthMutex;
QMutex m_loadMutex;
sample_rate_t m_internalSampleRate;
int m_lastMidiPitch;
int m_lastMidiPitchRange;
int m_channel;
LcdSpinBoxModel m_bankNum;
LcdSpinBoxModel m_patchNum;
FloatModel m_gain;
private:
void freeInstance();
friend class gigInstrumentView;
signals:
void fileLoading();
void fileChanged();
void patchChanged();
} ;
class gigInstrumentView : public InstrumentView
{
Q_OBJECT
public:
gigInstrumentView( Instrument * _instrument,
QWidget * _parent );
virtual ~gigInstrumentView();
private:
virtual void modelChanged();
pixmapButton * m_fileDialogButton;
pixmapButton * m_patchDialogButton;
LcdSpinBox * m_bankNumLcd;
LcdSpinBox * m_patchNumLcd;
QLabel * m_filenameLabel;
QLabel * m_patchLabel;
knob * m_gainKnob;
static patchesDialog * s_patchDialog;
protected slots:
void invalidateFile();
void showFileDialog();
void showPatchDialog();
void updateFilename();
void updatePatchName();
} ;
#endif

BIN
plugins/gig_player/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,384 @@
/*
* patches_dialog.cpp - display sf2 patches
*
* Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
*
* 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 "patches_dialog.h"
#include <QtGui/QHeaderView>
//#include <QFileInfo>
// Custom list-view item (as for numerical sort purposes...)
class patchItem : public QTreeWidgetItem
{
public:
// Constructor.
patchItem( QTreeWidget *pListView,
QTreeWidgetItem *pItemAfter )
: QTreeWidgetItem( pListView, pItemAfter ) {}
// Sort/compare overriden method.
bool operator< ( const QTreeWidgetItem& other ) const
{
int iColumn = QTreeWidgetItem::treeWidget()->sortColumn();
const QString& s1 = text( iColumn );
const QString& s2 = other.text( iColumn );
if( iColumn == 0 || iColumn == 2 )
{
return( s1.toInt() < s2.toInt() );
}
else
{
return( s1 < s2 );
}
}
};
// Constructor.
patchesDialog::patchesDialog( QWidget *pParent, Qt::WindowFlags wflags )
: QDialog( pParent, wflags )
{
// Setup UI struct...
setupUi( this );
m_pSynth = NULL;
m_iChan = 0;
m_iBank = 0;
m_iProg = 0;
// Soundfonts list view...
QHeaderView *pHeader = m_progListView->header();
// pHeader->setResizeMode(QHeaderView::Custom);
pHeader->setDefaultAlignment(Qt::AlignLeft);
// pHeader->setDefaultSectionSize(200);
pHeader->setMovable(false);
pHeader->setStretchLastSection(true);
m_progListView->resizeColumnToContents(0); // Prog.
//pHeader->resizeSection(1, 200); // Name.
// Initial sort order...
m_bankListView->sortItems(0, Qt::AscendingOrder);
m_progListView->sortItems(0, Qt::AscendingOrder);
// UI connections...
QObject::connect(m_bankListView,
SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
SLOT(bankChanged()));
QObject::connect(m_progListView,
SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
SLOT(progChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
QObject::connect(m_progListView,
SIGNAL(itemActivated(QTreeWidgetItem*,int)),
SLOT(accept()));
QObject::connect(m_okButton,
SIGNAL(clicked()),
SLOT(accept()));
QObject::connect(m_cancelButton,
SIGNAL(clicked()),
SLOT(reject()));
}
// Destructor.
patchesDialog::~patchesDialog()
{
}
// Dialog setup loader.
void patchesDialog::setup ( gigInstance * pSynth, int iChan,
const QString & _chanName,
LcdSpinBoxModel * _bankModel,
LcdSpinBoxModel * _progModel,
QLabel * _patchLabel )
{
// We'll going to changes the whole thing...
m_dirty = 0;
m_bankModel = _bankModel;
m_progModel = _progModel;
m_patchLabel = _patchLabel;
// Set the proper caption...
setWindowTitle( _chanName + " - GIG patches" );
// set m_pSynth to NULL so we don't trigger any progChanged events
m_pSynth = NULL;
// Load bank list from actual synth stack...
m_bankListView->setSortingEnabled(false);
m_bankListView->clear();
// now it should be safe to set internal stuff
m_pSynth = pSynth;
m_iChan = iChan;
//fluid_preset_t preset;
QTreeWidgetItem *pBankItem = NULL;
// For all soundfonts (in reversed stack order) fill the available banks...
/*int cSoundFonts = ::fluid_synth_sfcount(m_pSynth);
for (int i = 0; i < cSoundFonts; i++) {
fluid_sfont_t *pSoundFont = ::fluid_synth_get_sfont(m_pSynth, i);
if (pSoundFont) {
#ifdef CONFIG_FLUID_BANK_OFFSET
int iBankOffset = ::fluid_synth_get_bank_offset(m_pSynth, pSoundFont->id);
#endif
pSoundFont->iteration_start(pSoundFont);
while (pSoundFont->iteration_next(pSoundFont, &preset)) {
int iBank = preset.get_banknum(&preset);
#ifdef CONFIG_FLUID_BANK_OFFSET
iBank += iBankOffset;
#endif
if (!findBankItem(iBank)) {
pBankItem = new patchItem(m_bankListView, pBankItem);
if (pBankItem)
pBankItem->setText(0, QString::number(iBank));
}
}
}
}*/
// TODO: get rid of "banks" altogether
// Currently just use zero as the only bank
int iBankDefault = -1;
int iProgDefault = -1;
gig::Instrument* pInstrument = m_pSynth->gig.GetFirstInstrument();
while (pInstrument) {
int iBank = pInstrument->MIDIBank;
int iProg = pInstrument->MIDIProgram;
if (!findBankItem(iBank)) {
pBankItem = new patchItem(m_bankListView, pBankItem);
if (pBankItem)
{
pBankItem->setText(0, QString::number(iBank));
if (iBankDefault == -1)
{
iBankDefault = iBank;
iProgDefault = iProg;
}
}
}
pInstrument = m_pSynth->gig.GetNextInstrument();
}
m_bankListView->setSortingEnabled(true);
// Set the selected bank.
if (iBankDefault != -1)
m_iBank = iBankDefault;
pBankItem = findBankItem(m_iBank);
m_bankListView->setCurrentItem(pBankItem);
m_bankListView->scrollToItem(pBankItem);
bankChanged();
// Set the selected program.
if (iProgDefault != -1)
m_iProg = iProgDefault;
QTreeWidgetItem *pProgItem = findProgItem(m_iProg);
m_progListView->setCurrentItem(pProgItem);
m_progListView->scrollToItem(pProgItem);
// Done with setup...
//m_iDirtySetup--;
}
// Stabilize current state form.
void patchesDialog::stabilizeForm()
{
m_okButton->setEnabled(validateForm());
}
// Validate form fields.
bool patchesDialog::validateForm()
{
bool bValid = true;
bValid = bValid && (m_bankListView->currentItem() != NULL);
bValid = bValid && (m_progListView->currentItem() != NULL);
return bValid;
}
// Realize a bank-program selection preset.
void patchesDialog::setBankProg ( int iBank, int iProg )
{
if (m_pSynth == NULL)
return;
// just select the synth's program preset...
//::fluid_synth_bank_select(m_pSynth, m_iChan, iBank);
//::fluid_synth_program_change(m_pSynth, m_iChan, iProg);
// Maybe this is needed to stabilize things around.
//::fluid_synth_program_reset(m_pSynth);
}
// Validate form fields and accept it valid.
void patchesDialog::accept()
{
if (validateForm()) {
// Unload from current selected dialog items.
int iBank = (m_bankListView->currentItem())->text(0).toInt();
int iProg = (m_progListView->currentItem())->text(0).toInt();
// And set it right away...
setBankProg(iBank, iProg);
if (m_dirty > 0) {
m_bankModel->setValue( iBank );
m_progModel->setValue( iProg );
m_patchLabel->setText( m_progListView->
currentItem()->text( 1 ) );
}
// Do remember preview state...
// if (m_pOptions)
// m_pOptions->bPresetPreview = m_ui.PreviewCheckBox->isChecked();
// We got it.
QDialog::accept();
}
}
// Reject settings (Cancel button slot).
void patchesDialog::reject (void)
{
// Reset selection to initial selection, if applicable...
if (m_dirty > 0)
setBankProg(m_bankModel->value(), m_progModel->value());
// Done (hopefully nothing).
QDialog::reject();
}
// Find the bank item of given bank number id.
QTreeWidgetItem *patchesDialog::findBankItem ( int iBank )
{
QList<QTreeWidgetItem *> banks
= m_bankListView->findItems(
QString::number(iBank), Qt::MatchExactly, 0);
QListIterator<QTreeWidgetItem *> iter(banks);
if (iter.hasNext())
return iter.next();
else
return NULL;
}
// Find the program item of given program number id.
QTreeWidgetItem *patchesDialog::findProgItem ( int iProg )
{
QList<QTreeWidgetItem *> progs
= m_progListView->findItems(
QString::number(iProg), Qt::MatchExactly, 0);
QListIterator<QTreeWidgetItem *> iter(progs);
if (iter.hasNext())
return iter.next();
else
return NULL;
}
// Bank change slot.
void patchesDialog::bankChanged (void)
{
if (m_pSynth == NULL)
return;
QTreeWidgetItem *pBankItem = m_bankListView->currentItem();
if (pBankItem == NULL)
return;
int iBankSelected = pBankItem->text(0).toInt();
// Clear up the program listview.
m_progListView->setSortingEnabled(false);
m_progListView->clear();
QTreeWidgetItem *pProgItem = NULL;
gig::Instrument* pInstrument = m_pSynth->gig.GetFirstInstrument();
while (pInstrument) {
QString name = QString::fromStdString(pInstrument->pInfo->Name);
if (name == "")
name = "<no name>";
int iBank = pInstrument->MIDIBank;
int iProg = pInstrument->MIDIProgram;
if (iBank == iBankSelected && !findProgItem(iProg)) {
pProgItem = new patchItem(m_progListView, pProgItem);
if (pProgItem) {
pProgItem->setText(0, QString::number(iProg));
pProgItem->setText(1, name);
}
}
pInstrument = m_pSynth->gig.GetNextInstrument();
}
m_progListView->setSortingEnabled(true);
// Stabilize the form.
stabilizeForm();
}
// Program change slot.
void patchesDialog::progChanged (QTreeWidgetItem * _curr, QTreeWidgetItem * _prev)
{
if (m_pSynth == NULL || _curr == NULL)
return;
// Which preview state...
if( validateForm() ) {
// Set current selection.
int iBank = (m_bankListView->currentItem())->text(0).toInt();
int iProg = _curr->text(0).toInt();
// And set it right away...
setBankProg(iBank, iProg);
// Now we're dirty nuff.
m_dirty++;
}
// Stabilize the form.
stabilizeForm();
}
#include "moc_patches_dialog.cxx"

View File

@@ -0,0 +1,96 @@
/*
* patches_dialog.h - display sf2 patches
*
* Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail/dot/com>
*
* 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.
*
*/
#ifndef _PATCHES_DIALOG_H
#define _PATCHES_DIALOG_H
#include "ui_patches_dialog.h"
#include "LcdSpinBox.h"
#include "gig_player.h"
#include <fluidsynth.h>
#include <QtGui/QWidget>
#include <QtGui/QLabel>
//----------------------------------------------------------------------------
// qsynthPresetForm -- UI wrapper form.
class patchesDialog : public QDialog, private Ui::patchesDialog
{
Q_OBJECT
public:
// Constructor.
patchesDialog(QWidget *pParent = 0, Qt::WindowFlags wflags = 0);
// Destructor.
virtual ~patchesDialog();
void setup(gigInstance * pSynth, int iChan, const QString & _chanName,
LcdSpinBoxModel * _bankModel, LcdSpinBoxModel * _progModel, QLabel *_patchLabel );
public slots:
void stabilizeForm();
void bankChanged();
void progChanged( QTreeWidgetItem * _curr, QTreeWidgetItem * _prev );
protected slots:
void accept();
void reject();
protected:
void setBankProg(int iBank, int iProg);
QTreeWidgetItem *findBankItem(int iBank);
QTreeWidgetItem *findProgItem(int iProg);
bool validateForm();
private:
// Instance variables.
gigInstance *m_pSynth;
int m_iChan;
int m_iBank;
int m_iProg;
//int m_iDirtySetup;
//int m_iDirtyCount;
int m_dirty;
LcdSpinBoxModel * m_bankModel;
LcdSpinBoxModel * m_progModel;
QLabel *m_patchLabel;
};
#endif

View File

@@ -0,0 +1,216 @@
<ui version="4.0" >
<author>rncbc aka Rui Nuno Capela</author>
<comment>qsynth - A fluidsynth Qt GUI Interface.
Copyright (C) 2003-2007, rncbc aka Rui Nuno Capela. All rights reserved.
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; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
</comment>
<class>patchesDialog</class>
<widget class="QDialog" name="patchesDialog" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>350</height>
</rect>
</property>
<property name="minimumSize" >
<size>
<width>300</width>
<height>150</height>
</size>
</property>
<property name="windowTitle" >
<string>Qsynth: Channel Preset</string>
</property>
<property name="windowIcon" >
<iconset/>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QSplitter" name="m_splitter" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTreeWidget" name="m_bankListView" >
<property name="minimumSize" >
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="maximumSize" >
<size>
<width>80</width>
<height>32767</height>
</size>
</property>
<property name="toolTip" >
<string>Bank selector</string>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="indentation" >
<number>4</number>
</property>
<property name="rootIsDecorated" >
<bool>false</bool>
</property>
<property name="uniformRowHeights" >
<bool>true</bool>
</property>
<property name="itemsExpandable" >
<bool>false</bool>
</property>
<property name="sortingEnabled" >
<bool>true</bool>
</property>
<property name="allColumnsShowFocus" >
<bool>true</bool>
</property>
<column>
<property name="text" >
<string>Bank</string>
</property>
</column>
</widget>
<widget class="QTreeWidget" name="m_progListView" >
<property name="toolTip" >
<string>Program selector</string>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="indentation" >
<number>4</number>
</property>
<property name="rootIsDecorated" >
<bool>false</bool>
</property>
<property name="uniformRowHeights" >
<bool>true</bool>
</property>
<property name="itemsExpandable" >
<bool>false</bool>
</property>
<property name="sortingEnabled" >
<bool>true</bool>
</property>
<property name="allColumnsShowFocus" >
<bool>true</bool>
</property>
<column>
<property name="text" >
<string>Patch</string>
</property>
</column>
<column>
<property name="text" >
<string>Name</string>
</property>
</column>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<property name="spacing" >
<number>4</number>
</property>
<property name="leftMargin" >
<number>0</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>0</number>
</property>
<property name="bottomMargin" >
<number>0</number>
</property>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType" >
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" >
<size>
<width>120</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="m_okButton" >
<property name="toolTip" >
<string/>
</property>
<property name="text" >
<string>OK</string>
</property>
<property name="iconSize" >
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="default" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="m_cancelButton" >
<property name="toolTip" >
<string/>
</property>
<property name="text" >
<string>Cancel</string>
</property>
<property name="iconSize" >
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<layoutdefault spacing="4" margin="4" />
<tabstops>
<tabstop>m_okButton</tabstop>
<tabstop>m_cancelButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB