123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647 |
- /*
- * patman.cpp - a GUS-compatible patch instrument plugin
- *
- * Copyright (c) 2007-2008 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
- * Copyright (c) 2009-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
- *
- * This file is part of LMMS - https://lmms.io
- *
- * 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 "patman.h"
- #include <QDragEnterEvent>
- #include <QPainter>
- #include <QDomElement>
- #include "ConfigManager.h"
- #include "endian_handling.h"
- #include "Engine.h"
- #include "FileDialog.h"
- #include "gui_templates.h"
- #include "InstrumentTrack.h"
- #include "NotePlayHandle.h"
- #include "PathUtil.h"
- #include "PixmapButton.h"
- #include "Song.h"
- #include "StringPairDrag.h"
- #include "ToolTip.h"
- #include "Clipboard.h"
- #include "embed.h"
- #include "plugin_export.h"
- extern "C"
- {
- Plugin::Descriptor PLUGIN_EXPORT patman_plugin_descriptor =
- {
- STRINGIFY( PLUGIN_NAME ),
- "PatMan",
- QT_TRANSLATE_NOOP( "PluginBrowser",
- "GUS-compatible patch instrument" ),
- "Javier Serrano Polo <jasp00/at/users.sourceforge.net>",
- 0x0100,
- Plugin::Instrument,
- new PluginPixmapLoader( "logo" ),
- "pat",
- nullptr,
- } ;
- // necessary for getting instance out of shared lib
- PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *m, void * )
- {
- return new patmanInstrument( static_cast<InstrumentTrack *>( m ) );
- }
- }
- patmanInstrument::patmanInstrument( InstrumentTrack * _instrument_track ) :
- Instrument( _instrument_track, &patman_plugin_descriptor ),
- m_loopedModel( true, this ),
- m_tunedModel( true, this )
- {
- }
- patmanInstrument::~patmanInstrument()
- {
- unloadCurrentPatch();
- }
- void patmanInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
- {
- _this.setAttribute( "src", m_patchFile );
- m_loopedModel.saveSettings( _doc, _this, "looped" );
- m_tunedModel.saveSettings( _doc, _this, "tuned" );
- }
- void patmanInstrument::loadSettings( const QDomElement & _this )
- {
- setFile( _this.attribute( "src" ), false );
- m_loopedModel.loadSettings( _this, "looped" );
- m_tunedModel.loadSettings( _this, "tuned" );
- }
- void patmanInstrument::loadFile( const QString & _file )
- {
- setFile( _file );
- }
- QString patmanInstrument::nodeName( void ) const
- {
- return( patman_plugin_descriptor.name );
- }
- void patmanInstrument::playNote( NotePlayHandle * _n,
- sampleFrame * _working_buffer )
- {
- if( m_patchFile == "" )
- {
- return;
- }
- const fpp_t frames = _n->framesLeftForCurrentPeriod();
- const f_cnt_t offset = _n->noteOffset();
- if( !_n->m_pluginData )
- {
- selectSample( _n );
- }
- handle_data * hdata = (handle_data *)_n->m_pluginData;
- float play_freq = hdata->tuned ? _n->frequency() :
- hdata->sample->frequency();
- if( hdata->sample->play( _working_buffer + offset, hdata->state, frames,
- play_freq, m_loopedModel.value() ? SampleBuffer::LoopOn : SampleBuffer::LoopOff ) )
- {
- applyRelease( _working_buffer, _n );
- instrumentTrack()->processAudioBuffer( _working_buffer,
- frames + offset, _n );
- }
- else
- {
- memset( _working_buffer, 0, ( frames + offset ) * sizeof( sampleFrame ) );
- }
- }
- void patmanInstrument::deleteNotePluginData( NotePlayHandle * _n )
- {
- handle_data * hdata = (handle_data *)_n->m_pluginData;
- sharedObject::unref( hdata->sample );
- delete hdata->state;
- delete hdata;
- }
- void patmanInstrument::setFile( const QString & _patch_file, bool _rename )
- {
- if( _patch_file.size() <= 0 )
- {
- m_patchFile = QString();
- return;
- }
- // is current instrument-track-name equal to previous-filename??
- if( _rename &&
- ( instrumentTrack()->name() ==
- QFileInfo( m_patchFile ).fileName() ||
- m_patchFile == "" ) )
- {
- // then set it to new one
- instrumentTrack()->setName( PathUtil::cleanName( _patch_file ) );
- }
- // else we don't touch the instrument-track-name, because the user
- // named it self
- m_patchFile = PathUtil::toShortestRelative( _patch_file );
- LoadErrors error = loadPatch( PathUtil::toAbsolute( _patch_file ) );
- if( error )
- {
- printf("Load error\n");
- }
- emit fileChanged();
- }
- patmanInstrument::LoadErrors patmanInstrument::loadPatch(
- const QString & _filename )
- {
- unloadCurrentPatch();
- FILE * fd = fopen( _filename.toUtf8().constData() , "rb" );
- if( !fd )
- {
- perror( "fopen" );
- return( LoadOpen );
- }
- unsigned char header[239];
- if( fread( header, 1, 239, fd ) != 239 ||
- ( memcmp( header, "GF1PATCH110\0ID#000002", 22 )
- && memcmp( header, "GF1PATCH100\0ID#000002", 22 ) ) )
- {
- fclose( fd );
- return( LoadNotGUS );
- }
- if( header[82] != 1 && header[82] != 0 )
- {
- fclose( fd );
- return( LoadInstruments );
- }
- if( header[151] != 1 && header[151] != 0 )
- {
- fclose( fd );
- return( LoadLayers );
- }
- int sample_count = header[198];
- for( int i = 0; i < sample_count; ++i )
- {
- unsigned short tmpshort;
- #define SKIP_BYTES( x ) \
- if ( fseek( fd, x, SEEK_CUR ) == -1 ) \
- { \
- fclose( fd ); \
- return( LoadIO ); \
- }
- #define READ_SHORT( x ) \
- if ( fread( &tmpshort, 2, 1, fd ) != 1 ) \
- { \
- fclose( fd ); \
- return( LoadIO ); \
- } \
- x = (unsigned short)swap16IfBE( tmpshort );
- #define READ_LONG( x ) \
- if ( fread( &x, 4, 1, fd ) != 1 ) \
- { \
- fclose( fd ); \
- return( LoadIO ); \
- } \
- x = (unsigned)swap32IfBE( x );
- // skip wave name, fractions
- SKIP_BYTES( 7 + 1 );
- unsigned data_length;
- READ_LONG( data_length );
- unsigned loop_start;
- READ_LONG( loop_start );
- unsigned loop_end;
- READ_LONG( loop_end );
- unsigned sample_rate;
- READ_SHORT( sample_rate );
- // skip low_freq, high_freq
- SKIP_BYTES( 4 + 4 );
- unsigned root_freq;
- READ_LONG( root_freq );
- // skip tuning, panning, envelope, tremolo, vibrato
- SKIP_BYTES( 2 + 1 + 12 + 3 + 3 );
- unsigned char modes;
- if ( fread( &modes, 1, 1, fd ) != 1 )
- {
- fclose( fd );
- return( LoadIO );
- }
- // skip scale frequency, scale factor, reserved space
- SKIP_BYTES( 2 + 2 + 36 );
- f_cnt_t frames;
- sample_t * wave_samples;
- if( modes & MODES_16BIT )
- {
- frames = data_length >> 1;
- wave_samples = new sample_t[frames];
- for( f_cnt_t frame = 0; frame < frames; ++frame )
- {
- short sample;
- if ( fread( &sample, 2, 1, fd ) != 1 )
- {
- delete[] wave_samples;
- fclose( fd );
- return( LoadIO );
- }
- sample = swap16IfBE( sample );
- if( modes & MODES_UNSIGNED )
- {
- sample ^= 0x8000;
- }
- wave_samples[frame] = sample / 32767.0f;
- }
- loop_start >>= 1;
- loop_end >>= 1;
- }
- else
- {
- frames = data_length;
- wave_samples = new sample_t[frames];
- for( f_cnt_t frame = 0; frame < frames; ++frame )
- {
- char sample;
- if ( fread( &sample, 1, 1, fd ) != 1 )
- {
- delete[] wave_samples;
- fclose( fd );
- return( LoadIO );
- }
- if( modes & MODES_UNSIGNED )
- {
- sample ^= 0x80;
- }
- wave_samples[frame] = sample / 127.0f;
- }
- }
- sampleFrame * data = new sampleFrame[frames];
- for( f_cnt_t frame = 0; frame < frames; ++frame )
- {
- for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS;
- ++chnl )
- {
- data[frame][chnl] = wave_samples[frame];
- }
- }
- SampleBuffer* psample = new SampleBuffer( data, frames );
- psample->setFrequency( root_freq / 1000.0f );
- psample->setSampleRate( sample_rate );
- if( modes & MODES_LOOPING )
- {
- psample->setLoopStartFrame( loop_start );
- psample->setLoopEndFrame( loop_end );
- }
- m_patchSamples.push_back( psample );
- delete[] wave_samples;
- delete[] data;
- }
- fclose( fd );
- return( LoadOK );
- }
- void patmanInstrument::unloadCurrentPatch( void )
- {
- while( !m_patchSamples.empty() )
- {
- sharedObject::unref( m_patchSamples.back() );
- m_patchSamples.pop_back();
- }
- }
- void patmanInstrument::selectSample( NotePlayHandle * _n )
- {
- const float freq = _n->frequency();
- float min_dist = HUGE_VALF;
- SampleBuffer* sample = nullptr;
- for( QVector<SampleBuffer *>::iterator it = m_patchSamples.begin(); it != m_patchSamples.end(); ++it )
- {
- float patch_freq = ( *it )->frequency();
- float dist = freq >= patch_freq ? freq / patch_freq :
- patch_freq / freq;
- if( dist < min_dist )
- {
- min_dist = dist;
- sample = *it;
- }
- }
- handle_data * hdata = new handle_data;
- hdata->tuned = m_tunedModel.value();
- if( sample )
- {
- hdata->sample = sharedObject::ref( sample );
- }
- else
- {
- hdata->sample = new SampleBuffer( nullptr, 0 );
- }
- hdata->state = new SampleBuffer::handleState( _n->hasDetuningInfo() );
- _n->m_pluginData = hdata;
- }
- PluginView * patmanInstrument::instantiateView( QWidget * _parent )
- {
- return( new PatmanView( this, _parent ) );
- }
- PatmanView::PatmanView( Instrument * _instrument, QWidget * _parent ) :
- InstrumentViewFixedSize( _instrument, _parent ),
- m_pi( nullptr )
- {
- setAutoFillBackground( true );
- QPalette pal;
- pal.setBrush( backgroundRole(),
- PLUGIN_NAME::getIconPixmap( "artwork" ) );
- setPalette( pal );
- m_openFileButton = new PixmapButton( this, nullptr );
- m_openFileButton->setObjectName( "openFileButton" );
- m_openFileButton->setCursor( QCursor( Qt::PointingHandCursor ) );
- m_openFileButton->move( 227, 86 );
- m_openFileButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
- "select_file_on" ) );
- m_openFileButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
- "select_file" ) );
- connect( m_openFileButton, SIGNAL( clicked() ),
- this, SLOT( openFile() ) );
- ToolTip::add( m_openFileButton, tr( "Open patch" ) );
- m_loopButton = new PixmapButton( this, tr( "Loop" ) );
- m_loopButton->setObjectName("loopButton");
- m_loopButton->setCheckable( true );
- m_loopButton->move( 195, 138 );
- m_loopButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
- "loop_on" ) );
- m_loopButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
- "loop_off" ) );
- ToolTip::add( m_loopButton, tr( "Loop mode" ) );
- m_tuneButton = new PixmapButton( this, tr( "Tune" ) );
- m_tuneButton->setObjectName("tuneButton");
- m_tuneButton->setCheckable( true );
- m_tuneButton->move( 223, 138 );
- m_tuneButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
- "tune_on" ) );
- m_tuneButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
- "tune_off" ) );
- ToolTip::add( m_tuneButton, tr( "Tune mode" ) );
- m_displayFilename = tr( "No file selected" );
- setAcceptDrops( true );
- }
- PatmanView::~PatmanView()
- {
- }
- void PatmanView::openFile( void )
- {
- FileDialog ofd( nullptr, tr( "Open patch file" ) );
- ofd.setFileMode( FileDialog::ExistingFiles );
- QStringList types;
- types << tr( "Patch-Files (*.pat)" );
- ofd.setNameFilters( types );
- if( m_pi->m_patchFile == "" )
- {
- if( QDir( "/usr/share/midi/freepats" ).exists() )
- {
- ofd.setDirectory( "/usr/share/midi/freepats" );
- }
- else
- {
- ofd.setDirectory(
- ConfigManager::inst()->userSamplesDir() );
- }
- }
- else if( QFileInfo( m_pi->m_patchFile ).isRelative() )
- {
- QString f = ConfigManager::inst()->userSamplesDir()
- + m_pi->m_patchFile;
- if( QFileInfo( f ).exists() == false )
- {
- f = ConfigManager::inst()->factorySamplesDir()
- + m_pi->m_patchFile;
- }
- ofd.selectFile( f );
- }
- else
- {
- ofd.selectFile( m_pi->m_patchFile );
- }
- if( ofd.exec() == QDialog::Accepted && !ofd.selectedFiles().isEmpty() )
- {
- QString f = ofd.selectedFiles()[0];
- if( f != "" )
- {
- m_pi->setFile( f );
- Engine::getSong()->setModified();
- }
- }
- }
- void PatmanView::updateFilename( void )
- {
- m_displayFilename = "";
- int idx = m_pi->m_patchFile.length();
- QFontMetrics fm( pointSize<8>( font() ) );
- // simple algorithm for creating a text from the filename that
- // matches in the white rectangle
- while( idx > 0 && fm.size( Qt::TextSingleLine,
- m_displayFilename + "..." ).width() < 225 )
- {
- m_displayFilename = m_pi->m_patchFile[--idx] +
- m_displayFilename;
- }
- if( idx > 0 )
- {
- m_displayFilename = "..." + m_displayFilename;
- }
- update();
- }
- void PatmanView::dragEnterEvent( QDragEnterEvent * _dee )
- {
- // For mimeType() and MimeType enum class
- using namespace Clipboard;
- if( _dee->mimeData()->hasFormat( mimeType( MimeType::StringPair ) ) )
- {
- QString txt = _dee->mimeData()->data(
- mimeType( MimeType::StringPair ) );
- if( txt.section( ':', 0, 0 ) == "samplefile" )
- {
- _dee->acceptProposedAction();
- }
- else
- {
- _dee->ignore();
- }
- }
- else
- {
- _dee->ignore();
- }
- }
- void PatmanView::dropEvent( QDropEvent * _de )
- {
- QString type = StringPairDrag::decodeKey( _de );
- QString value = StringPairDrag::decodeValue( _de );
- if( type == "samplefile" )
- {
- m_pi->setFile( value );
- _de->accept();
- return;
- }
- _de->ignore();
- }
- void PatmanView::paintEvent( QPaintEvent * )
- {
- QPainter p( this );
- p.setFont( pointSize<8>( font() ) );
- p.drawText( 8, 116, 235, 16,
- Qt::AlignLeft | Qt::TextSingleLine | Qt::AlignVCenter,
- m_displayFilename );
- }
- void PatmanView::modelChanged( void )
- {
- m_pi = castModel<patmanInstrument>();
- m_loopButton->setModel( &m_pi->m_loopedModel );
- m_tuneButton->setModel( &m_pi->m_tunedModel );
- connect( m_pi, SIGNAL( fileChanged() ),
- this, SLOT( updateFilename() ) );
- }
|