diff --git a/ChangeLog b/ChangeLog index c17a5e22d..c04ca13d5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2008-02-28 Paul Giblock + + * plugins/sf2_player: + * plugins/sf2_player/patches_dialog.ui: + * plugins/sf2_player/logo.png: + * plugins/sf2_player/patches_dialog.cpp: + * plugins/sf2_player/sf2_player.cpp: + * plugins/sf2_player/Makefile.am: + * plugins/sf2_player/artwork.png: + * plugins/sf2_player/patches_dialog.h: + * plugins/sf2_player/sf2_player.h: + * plugins/Makefile.am: + * configure.in: + added beta of SoundFont player + 2008-02-28 Danny McRae * plugins/stk/Makefile.am: diff --git a/configure.in b/configure.in index d21e18ba8..65fc7665b 100644 --- a/configure.in +++ b/configure.in @@ -655,6 +655,7 @@ AC_CONFIG_FILES([Makefile plugins/midi_import/Makefile plugins/organic/Makefile plugins/patman/Makefile + plugins/sf2_player/Makefile plugins/singerbot/Makefile plugins/stk/Makefile plugins/stk/mallets/Makefile diff --git a/plugins/Makefile.am b/plugins/Makefile.am index d1613a503..8446e9c05 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -23,6 +23,7 @@ SUBDIRS = \ midi_import \ organic \ patman \ + sf2_player \ $(SINGERBOT_DIR) \ $(STK_DIR) \ triple_oscillator \ diff --git a/plugins/sf2_player/Makefile.am b/plugins/sf2_player/Makefile.am new file mode 100644 index 000000000..37afb4754 --- /dev/null +++ b/plugins/sf2_player/Makefile.am @@ -0,0 +1,39 @@ +AUTOMAKE_OPTIONS = foreign 1.4 + + +INCLUDES = -I$(top_srcdir)/include -I$(top_srcdir)/src/lib -I. + + +AM_CXXFLAGS := $(AM_CXXFLAGS) $(QT_CXXFLAGS) -DPLUGIN_NAME="sf2player" + + +%.moc: ./%.h + $(MOC) -o $@ $< + +%.ui: ./%.ui + $(UIC) -o ui_$@ $< + + +MOC_FILES = ./sf2_player.moc ./patches_dialog.moc + +UIC_FILES = ./ui_patches_dialog.h + +BUILT_SOURCES = $(MOC_FILES) ${UIC_FILES} ./embedded_resources.h +EMBEDDED_RESOURCES = $(wildcard *png) + +./embedded_resources.h: $(EMBEDDED_RESOURCES) + $(BIN2RES) $(EMBEDDED_RESOURCES) > $@ + +EXTRA_DIST = $(EMBEDDED_RESOURCES) + + +CLEANFILES = $(MOC_FILES) ./embedded_resources.h + + + +pkglib_LTLIBRARIES = libsf2player.la + +libsf2player_la_SOURCES = sf2_player.cpp sf2_player.h patches_dialog.cpp patches_dialog.h ui_patches_dialog.h +libsf2player_la_LDFLAGS = -L /usr/lib -lfluidsynth + +$(libsf2player_la_SOURCES): ./embedded_resources.h diff --git a/plugins/sf2_player/artwork.png b/plugins/sf2_player/artwork.png new file mode 100644 index 000000000..807c444d0 Binary files /dev/null and b/plugins/sf2_player/artwork.png differ diff --git a/plugins/sf2_player/logo.png b/plugins/sf2_player/logo.png new file mode 100644 index 000000000..8cb2718f4 Binary files /dev/null and b/plugins/sf2_player/logo.png differ diff --git a/plugins/sf2_player/patches_dialog.cpp b/plugins/sf2_player/patches_dialog.cpp new file mode 100644 index 000000000..518bc26ed --- /dev/null +++ b/plugins/sf2_player/patches_dialog.cpp @@ -0,0 +1,364 @@ +/* + * patches_dialog.cpp - display sf2 patches + * + * Copyright (c) 2008 Paul Giblock + * + * 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 +//#include + + +// 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 ( fluid_synth_t *pSynth, int iChan, const QString & _chanName, + lcdSpinBoxModel * _bankModel, lcdSpinBoxModel * _progModel ) +{ + + // We'll going to changes the whole thing... + m_dirty = 0; + m_bankModel = _bankModel; + m_progModel = _progModel; + + // Set the proper caption... + setWindowTitle( _chanName + " - Soundfont 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)); + } + } + } + } + m_bankListView->setSortingEnabled(true); + + // Set the selected bank. + m_iBank = 0; + fluid_preset_t *pPreset = ::fluid_synth_get_channel_preset(m_pSynth, m_iChan); + if (pPreset) { + m_iBank = pPreset->get_banknum(pPreset); +#ifdef CONFIG_FLUID_BANK_OFFSET + m_iBank += ::fluid_synth_get_bank_offset(m_pSynth, (pPreset->sfont)->id); +#endif + } + + pBankItem = findBankItem(m_iBank); + m_bankListView->setCurrentItem(pBankItem); + m_bankListView->scrollToItem(pBankItem); + bankChanged(); + + // Set the selected program. + if (pPreset) + m_iProg = pPreset->get_num(pPreset); + 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); + } + + // 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 banks + = m_bankListView->findItems( + QString::number(iBank), Qt::MatchExactly, 0); + + QListIterator 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 progs + = m_progListView->findItems( + QString::number(iProg), Qt::MatchExactly, 0); + + QListIterator 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(); + fluid_preset_t preset; + QTreeWidgetItem *pProgItem = NULL; + // For all soundfonts (in reversed stack order) fill the available programs... + int cSoundFonts = ::fluid_synth_sfcount(m_pSynth); + for (int i = 0; i < cSoundFonts && !pProgItem; 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 + int iProg = preset.get_num(&preset); + if (iBank == iBankSelected && !findProgItem(iProg)) { + pProgItem = new patchItem(m_progListView, pProgItem); + if (pProgItem) { + pProgItem->setText(0, QString::number(iProg)); + pProgItem->setText(1, preset.get_name(&preset)); + //pProgItem->setText(2, QString::number(pSoundFont->id)); + //pProgItem->setText(3, QFileInfo( + // pSoundFont->get_name(pSoundFont)).baseName()); + } + } + } + } + } + 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 "patches_dialog.moc" diff --git a/plugins/sf2_player/patches_dialog.h b/plugins/sf2_player/patches_dialog.h new file mode 100644 index 000000000..3f4aada0a --- /dev/null +++ b/plugins/sf2_player/patches_dialog.h @@ -0,0 +1,93 @@ +/* + * patches_dialog.h - display sf2 patches + * + * Copyright (c) 2008 Paul Giblock + * + * 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 "lcd_spinbox.h" + +#include +#include + +//---------------------------------------------------------------------------- +// 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(fluid_synth_t *pSynth, int iChan, const QString & _chanName, + lcdSpinBoxModel * _bankModel, lcdSpinBoxModel * _progModel ); + +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. + fluid_synth_t *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; +}; + + +#endif + diff --git a/plugins/sf2_player/patches_dialog.ui b/plugins/sf2_player/patches_dialog.ui new file mode 100644 index 000000000..5204dc09d --- /dev/null +++ b/plugins/sf2_player/patches_dialog.ui @@ -0,0 +1,216 @@ + + rncbc aka Rui Nuno Capela + 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. + + + patchesDialog + + + + 0 + 0 + 480 + 350 + + + + + 300 + 150 + + + + Qsynth: Channel Preset + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 20 + 0 + + + + + 80 + 32767 + + + + Bank selector + + + true + + + 4 + + + false + + + true + + + false + + + true + + + true + + + + Bank + + + + + + Program selector + + + true + + + 4 + + + false + + + true + + + false + + + true + + + true + + + + Patch + + + + + Name + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 120 + 8 + + + + + + + + + + + OK + + + + 0 + 0 + + + + true + + + + + + + + + + Cancel + + + + 0 + 0 + + + + + + + + + + + m_okButton + m_cancelButton + + + + diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp new file mode 100644 index 000000000..0225bbc2c --- /dev/null +++ b/plugins/sf2_player/sf2_player.cpp @@ -0,0 +1,421 @@ +/* + * sf2_player.cpp - a soundfont2 player using fluidSynth + * + * Copyright (c) 2008 Paul Giblock + * + * 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 +#include +#include +#include + +#include "sf2_player.h" +#include "engine.h" +#include "instrument_track.h" +#include "instrument_play_handle.h" +#include "note_play_handle.h" +#include "knob.h" +#include "song.h" +#include "automatable_model_templates.h" + +#include "main_window.h" +#include "patches_dialog.h" +#include "tooltip.h" +#include "lcd_spinbox.h" + +#undef SINGLE_SOURCE_COMPILE +#include "embed.cpp" + + +extern "C" +{ + +plugin::descriptor sf2_plugin_descriptor = +{ + STRINGIFY_PLUGIN_NAME( PLUGIN_NAME ), + "Sf2Player", + QT_TRANSLATE_NOOP( "pluginBrowser", + "SoundFont synthesizer" ), + "Paul Giblock ", + 0x0100, + plugin::Instrument, + new QPixmap( PLUGIN_NAME::getIconPixmap( "logo" ) ), + NULL +} ; + +} + + +sf2Instrument::sf2Instrument( instrumentTrack * _instrument_track ) : + instrument( _instrument_track, &sf2_plugin_descriptor ), + m_bankNum( -1, -1, 999, 1, this ), + m_patchNum( -1, -1, 127, 1, this ), + m_filename( "" ), + m_fontId( 0 ) +{ + + m_settings = new_fluid_settings(); + + /* Set the synthesizer settings, if necessary */ + + + m_synth = new_fluid_synth( m_settings ); + + + //fluid_settings_setstr(settings, "audio.driver", "jack"); + + //adriver = new_fluid_audio_driver(settings, synth); + + instrumentPlayHandle * iph = new instrumentPlayHandle( this ); + engine::getMixer()->addPlayHandle( iph ); + + connect( &m_bankNum, SIGNAL( dataChanged() ), + this, SLOT( updatePatch() ) ); + + connect( &m_patchNum, SIGNAL( dataChanged() ), + this, SLOT( updatePatch() ) ); +} + + + + +sf2Instrument::~sf2Instrument() +{ + engine::getMixer()->removePlayHandles( getInstrumentTrack() ); + delete_fluid_synth( m_synth ); + delete_fluid_settings( m_settings ); +} + + + + +void sf2Instrument::saveSettings( QDomDocument & _doc, + QDomElement & _this ) +{ + _this.setAttribute( "src", m_filename ); + m_patchNum.saveSettings( _doc, _this, "patch" ); + m_bankNum.saveSettings( _doc, _this, "bank" ); +} + + + + +void sf2Instrument::loadSettings( const QDomElement & _this ) +{ + openFile( _this.attribute( "src" ) ); + m_patchNum.loadSettings( _this, "patch" ); + m_bankNum.loadSettings( _this, "bank" ); +} + + + + +QString sf2Instrument::nodeName( void ) const +{ + return( sf2_plugin_descriptor.name ); +} + + +void sf2Instrument::openFile( const QString & _sf2File ) +{ + if( m_filename != "") + { + fluid_synth_sfunload( m_synth, m_fontId, TRUE); + } + + m_fontId = fluid_synth_sfload( m_synth, _sf2File.toLocal8Bit(), TRUE ); + + if( m_fontId >= 0) + { + m_patchNum.setValue(0); + m_bankNum.setValue(0); + m_filename = _sf2File; + + emit fileChanged(); + } +} + +void sf2Instrument::updatePatch( void ) +{ + printf("update patch\n"); + if( m_bankNum.value() >= 0 && m_patchNum.value() >= 0 ) { + fluid_synth_program_select( m_synth, 1, m_fontId, + m_bankNum.value(), m_patchNum.value() ); + } +} + + + +void sf2Instrument::playNote( notePlayHandle * _n, bool ) +{ + const float LOG440 = 2.64345267649f; + + const int defaultVelocity = 80; + + const f_cnt_t tfp = _n->totalFramesPlayed(); + + int midiNote = (int)floor( ( log2( _n->frequency() ) - LOG440 ) * 12 +69-58)+0.5; + + if ( tfp == 0 ) + { + _n->m_pluginData = new int( midiNote ); + fluid_synth_noteon( m_synth, 1, midiNote, defaultVelocity ); + } + else if( _n->released() ) + { + // Doesn't happen with release frames = 0 + } + +} + + +void sf2Instrument::waitForWorkerThread( void ) +{ + // No waiting required, at least not that I know of + return; +} + + +void sf2Instrument::play( bool _try_parallelizing ) +{ + const fpp_t frames = engine::getMixer()->framesPerPeriod(); + + sampleFrame * buf = new sampleFrame[frames]; + + // Assumes stereo and float sample_t + + fluid_synth_write_float( m_synth, frames, buf, 0, 2, buf, 1, 2 ); + + getInstrumentTrack()->processAudioBuffer( buf, frames, NULL ); + + delete[] buf; + + if( !_try_parallelizing ) + { + waitForWorkerThread(); + } +} + + + +void sf2Instrument::deleteNotePluginData( notePlayHandle * _n ) +{ + int * midiNote = static_cast( _n->m_pluginData ); + + fluid_synth_noteoff( m_synth, 1, *midiNote ); + + delete midiNote; + + _n->noteOff(); +} + + +pluginView * sf2Instrument::instantiateView( QWidget * _parent ) +{ + return( new sf2InstrumentView( this, _parent ) ); +} + + + + +sf2InstrumentView::sf2InstrumentView( instrument * _instrument, + QWidget * _parent ) : + instrumentView( _instrument, _parent ) +{ + QVBoxLayout * vl = new QVBoxLayout( this ); + QHBoxLayout * hl = new QHBoxLayout(); + + // File Button + m_fileDialogButton = new pixmapButton( this, NULL ); + m_fileDialogButton->setCursor( QCursor( Qt::PointingHandCursor ) ); + m_fileDialogButton->setActiveGraphic( embed::getIconPixmap( + "project_open_down" ) ); + m_fileDialogButton->setInactiveGraphic( embed::getIconPixmap( + "project_open" ) ); + connect( m_fileDialogButton, SIGNAL( clicked() ), + this, SLOT( showFileDialog() ) ); + toolTip::add( m_fileDialogButton, tr( "Open other SoundFont file" ) ); + + m_fileDialogButton->setWhatsThis( + tr( "Click here to open another SF2 file" ) ); + + // Patch Button + m_patchDialogButton = new pixmapButton( this, NULL ); + m_patchDialogButton->setCursor( QCursor( Qt::PointingHandCursor ) ); + m_patchDialogButton->setActiveGraphic( embed::getIconPixmap( + "track_op_menu" ) ); + m_patchDialogButton->setInactiveGraphic( embed::getIconPixmap( + "track_op_menu" ) ); + connect( m_patchDialogButton, SIGNAL( clicked() ), + this, SLOT( showPatchDialog() ) ); + toolTip::add( m_patchDialogButton, tr( "Choose the patch" ) ); + + + // LCDs + m_bankNumLcd = new lcdSpinBox( 3, this, "Bank" ); + m_bankNumLcd->setLabel( "Bank:" ); + m_bankNumLcd->addTextForValue( -1, "---" ); + m_bankNumLcd->setEnabled( FALSE ); + + m_patchNumLcd = new lcdSpinBox( 3, this, "Patch" ); + m_patchNumLcd->setLabel( "Patch:" ); + m_patchNumLcd->addTextForValue( -1, "---" ); + m_patchNumLcd->setEnabled( FALSE ); + + hl->addWidget( m_fileDialogButton ); + hl->addWidget( m_bankNumLcd ); + hl->addWidget( m_patchNumLcd ); + hl->addWidget( m_patchDialogButton ); + + vl->addLayout( hl ); + + // Next row + + hl = new QHBoxLayout(); + + m_filenameLabel = new QLabel( this ); + + hl->addWidget( m_filenameLabel ); + + vl->addLayout( hl ); + + setAutoFillBackground( TRUE ); + QPalette pal; + pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( + "artwork" ) ); + setPalette( pal ); + + updateFilename(); +} + + + + +sf2InstrumentView::~sf2InstrumentView() +{ +} + + +void sf2InstrumentView::modelChanged( void ) +{ + sf2Instrument * k = castModel(); + m_bankNumLcd->setModel( &k->m_bankNum ); + m_patchNumLcd->setModel( &k->m_patchNum ); + + connect(k, SIGNAL( fileChanged( void ) ), + this, SLOT( updateFilename( void ) ) ); + + updateFilename(); + +} + + +void sf2InstrumentView::updateFilename( void ) +{ + m_filenameLabel->setText("File: " + + castModel()->m_filename + + "\nPatch: TODO"); + update(); +} + + +void sf2InstrumentView::showFileDialog( void ) +{ + sf2Instrument * k = castModel(); + + QFileDialog ofd( NULL, tr( "Open SoundFont file" ) ); + ofd.setFileMode( QFileDialog::ExistingFiles ); + + QStringList types; + types << tr( "SoundFont2-Files (*.sf2)" ); + ofd.setFilters( types ); + + if( k->m_filename == "" ) + { + ofd.setDirectory( + configManager::inst()->userSamplesDir() ); + } + else if( QFileInfo( k->m_filename ).isRelative() ) + { + QString f = configManager::inst()->userSamplesDir() + + k->m_filename; + if( QFileInfo( f ).exists() == FALSE ) + { + f = configManager::inst()->factorySamplesDir() + + k->m_filename; + } + + ofd.selectFile( f ); + } + else + { + ofd.selectFile( k->m_filename ); + } + +// HACK + ofd.setDirectory("/home/llama"); + + if( ofd.exec() == QDialog::Accepted && !ofd.selectedFiles().isEmpty() ) + { + QString f = ofd.selectedFiles()[0]; + if( f != "" ) + { + k->openFile( f ); + engine::getSong()->setModified(); + } + } + + +} + +patchesDialog * sf2InstrumentView::s_patchDialog = NULL; + +void sf2InstrumentView::showPatchDialog( void ) { + + sf2Instrument * k = castModel(); + + if( s_patchDialog == NULL ) { + printf("Creating patchDialog\n"); + s_patchDialog = new patchesDialog(this); + } + s_patchDialog->setup( k->m_synth, 1, k->getInstrumentTrack()->name(), + &k->m_bankNum, &k->m_patchNum ); + + s_patchDialog->exec(); +} + + +extern "C" +{ + +// neccessary for getting instance out of shared lib +plugin * lmms_plugin_main( model *, void * _data ) +{ + return( new sf2Instrument( + static_cast( _data ) ) ); +} + + +} + +#include "sf2_player.moc" + diff --git a/plugins/sf2_player/sf2_player.h b/plugins/sf2_player/sf2_player.h new file mode 100644 index 000000000..9a8ea6330 --- /dev/null +++ b/plugins/sf2_player/sf2_player.h @@ -0,0 +1,143 @@ +/* + * sf2_player.h - a soundfont2 player using fluidSynth + * + * Copyright (c) 2008 Paul Giblock + * + * 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 _SF2_PLAYER_H +#define _SF2_PLAYER_H + +#include "instrument.h" +#include "pixmap_button.h" +#include "instrument_view.h" +#include "knob.h" +#include "lcd_spinbox.h" +#include "fluidsynth.h" + +class sf2InstrumentView; +class notePlayHandle; + +class patchesDialog; +class QLabel; + +class sf2Instrument : public instrument +{ + Q_OBJECT +public: + sf2Instrument( instrumentTrack * _instrument_track ); + virtual ~sf2Instrument(); + + virtual void play( bool _try_parallelizing ); + + virtual void FASTCALL playNote( notePlayHandle * _n, + bool _try_parallelizing ); + virtual void FASTCALL deleteNotePluginData( notePlayHandle * _n ); + + + virtual void FASTCALL saveSettings( QDomDocument & _doc, + QDomElement & _parent ); + virtual void FASTCALL loadSettings( const QDomElement & _this ); + + virtual QString nodeName( void ) const; + + virtual f_cnt_t desiredReleaseFrames( void ) const + { + return( 0 ); + } + + virtual bool notePlayHandleBased( void ) const + { + return( FALSE ); + } + + + virtual bool supportsParallelizing( void ) const + { + return( FALSE ); + } + + virtual void waitForWorkerThread( void ); + + + virtual pluginView * instantiateView( QWidget * _parent ); + +public slots: + void openFile( const QString & _sf2File ); + void updatePatch( void ); + + +private: + fluid_settings_t* m_settings; + + fluid_synth_t* m_synth; + + fluid_audio_driver_t* m_adriver; + + int m_fontId; + + + + QString m_filename; + + lcdSpinBoxModel m_bankNum; + lcdSpinBoxModel m_patchNum; + + friend class sf2InstrumentView; + +signals: + void fileChanged( void ); + void patchChanged( void ); + +} ; + + + +class sf2InstrumentView : public instrumentView +{ + Q_OBJECT +public: + sf2InstrumentView( instrument * _instrument, + QWidget * _parent ); + virtual ~sf2InstrumentView(); + +private: + virtual void modelChanged( void ); + + pixmapButton * m_fileDialogButton; + pixmapButton * m_patchDialogButton; + + lcdSpinBox * m_bankNumLcd; + lcdSpinBox * m_patchNumLcd; + + QLabel * m_filenameLabel; + + static patchesDialog * s_patchDialog; + +protected slots: + void showFileDialog( void ); + void showPatchDialog( void ); + void updateFilename( void ); +} ; + + + +#endif