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.
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
8
plugins/gig_player/CMakeLists.txt
Normal 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)
|
||||
|
||||
BIN
plugins/gig_player/artwork.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
plugins/gig_player/chorus_off.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
plugins/gig_player/chorus_on.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
plugins/gig_player/fileselect_off.png
Normal file
|
After Width: | Height: | Size: 936 B |
BIN
plugins/gig_player/fileselect_on.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
832
plugins/gig_player/gig_player.cpp
Normal 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"
|
||||
|
||||
247
plugins/gig_player/gig_player.h
Normal 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], ¬e[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
|
After Width: | Height: | Size: 3.1 KiB |
384
plugins/gig_player/patches_dialog.cpp
Normal 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"
|
||||
96
plugins/gig_player/patches_dialog.h
Normal 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
|
||||
|
||||
216
plugins/gig_player/patches_dialog.ui
Normal 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>
|
||||
BIN
plugins/gig_player/patches_off.png
Normal file
|
After Width: | Height: | Size: 884 B |
BIN
plugins/gig_player/patches_on.png
Normal file
|
After Width: | Height: | Size: 893 B |
BIN
plugins/gig_player/reverb_off.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
plugins/gig_player/reverb_on.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |