diff --git a/include/InstrumentMidiIOView.h b/include/InstrumentMidiIOView.h index 7ca1339a8..3edd25e85 100644 --- a/include/InstrumentMidiIOView.h +++ b/include/InstrumentMidiIOView.h @@ -58,7 +58,8 @@ private: LcdSpinBox * m_fixedOutputNoteSpinBox; QToolButton * m_wpBtn; + LcdSpinBox* m_baseVelocitySpinBox; + } ; - #endif diff --git a/include/MidiEvent.h b/include/MidiEvent.h index 49e76841e..c87d042a4 100644 --- a/include/MidiEvent.h +++ b/include/MidiEvent.h @@ -140,9 +140,9 @@ public: return m_data.m_param[1]; } - volume_t volume() const + volume_t volume( int midiBaseVelocity ) const { - return (volume_t)( velocity() * MaxVolume / MidiMaxVelocity ); + return (volume_t)( velocity() * DefaultVolume / midiBaseVelocity ); } const void* sourcePort() const diff --git a/include/MidiPort.h b/include/MidiPort.h index 8888ee4a7..810165443 100644 --- a/include/MidiPort.h +++ b/include/MidiPort.h @@ -53,6 +53,7 @@ class MidiPort : public Model, public SerializingObject mapPropertyFromModel(int,fixedOutputVelocity,setFixedOutputVelocity,m_fixedOutputVelocityModel); mapPropertyFromModel(int,fixedOutputNote,setFixedOutputNote,m_fixedOutputNoteModel); mapPropertyFromModel(int,outputProgram,setOutputProgram,m_outputProgramModel); + mapPropertyFromModel(int,baseVelocity,setBaseVelocity,m_baseVelocityModel); mapPropertyFromModel(bool,isReadable,setReadable,m_readableModel); mapPropertyFromModel(bool,isWritable,setWritable,m_writableModel); public: @@ -151,6 +152,7 @@ private: IntModel m_fixedOutputVelocityModel; IntModel m_fixedOutputNoteModel; IntModel m_outputProgramModel; + IntModel m_baseVelocityModel; BoolModel m_readableModel; BoolModel m_writableModel; diff --git a/include/note.h b/include/note.h index e1c3146ef..924ae7bf6 100644 --- a/include/note.h +++ b/include/note.h @@ -176,9 +176,9 @@ public: return m_volume; } - int midiVelocity() const + int midiVelocity( int midiBaseVelocity ) const { - return qMin( MidiMaxVelocity, getVolume() * MidiMaxVelocity / MaxVolume ); + return qMin( MidiMaxVelocity, getVolume() * midiBaseVelocity / DefaultVolume ); } inline panning_t getPanning() const diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index 1aa09fa4a..a5b9e36d1 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -565,8 +565,9 @@ void sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) id[i] = fluid_voice_get_id( voices[i] ); } - fluid_synth_noteon( m_synth, m_channel, midiNote, - _n->midiVelocity() ); + const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity(); + + fluid_synth_noteon( m_synth, m_channel, midiNote, _n->midiVelocity( baseVelocity ) ); // get new voice and save it fluid_synth_get_voicelist( m_synth, voices, poly, -1 ); diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 3900b68eb..fa5f30974 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -106,9 +106,11 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, if( !isTopNote() || !instrumentTrack->isArpeggioEnabled() ) { + const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); + // send MidiNoteOn event m_instrumentTrack->processOutEvent( - MidiEvent( MidiNoteOn, midiChannel(), midiKey(), midiVelocity() ), + MidiEvent( MidiNoteOn, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ), MidiTime::fromFrames( offset(), engine::framesPerTick() ) ); } } @@ -152,7 +154,9 @@ void NotePlayHandle::setVolume( volume_t _volume ) { note::setVolume( _volume ); - m_instrumentTrack->processOutEvent( MidiEvent( MidiKeyPressure, midiChannel(), midiKey(), midiVelocity() ) ); + const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); + + m_instrumentTrack->processOutEvent( MidiEvent( MidiKeyPressure, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ) ); } diff --git a/src/core/midi/MidiPort.cpp b/src/core/midi/MidiPort.cpp index 0dd0ae760..3c5bd495f 100644 --- a/src/core/midi/MidiPort.cpp +++ b/src/core/midi/MidiPort.cpp @@ -50,6 +50,7 @@ MidiPort::MidiPort( const QString& name, m_fixedOutputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed output velocity" ) ), m_fixedOutputNoteModel( -1, -1, MidiMaxNote, this, tr( "Fixed output note" ) ), m_outputProgramModel( 1, 1, MidiProgramCount, this, tr( "Output MIDI program" ) ), + m_baseVelocityModel( MidiMaxVelocity/2, 1, MidiMaxVelocity, this, tr( "Base velocity" ) ), m_readableModel( false, this, tr( "Receive MIDI-events" ) ), m_writableModel( false, this, tr( "Send MIDI-events" ) ) { @@ -171,6 +172,7 @@ void MidiPort::saveSettings( QDomDocument& doc, QDomElement& thisElement ) m_fixedOutputVelocityModel.saveSettings( doc, thisElement, "fixedoutputvelocity" ); m_fixedOutputNoteModel.saveSettings( doc, thisElement, "fixedoutputnote" ); m_outputProgramModel.saveSettings( doc, thisElement, "outputprogram" ); + m_baseVelocityModel.saveSettings( doc, thisElement, "basevelocity" ); m_readableModel.saveSettings( doc, thisElement, "readable" ); m_writableModel.saveSettings( doc, thisElement, "writable" ); @@ -223,6 +225,7 @@ void MidiPort::loadSettings( const QDomElement& thisElement ) m_fixedInputVelocityModel.loadSettings( thisElement, "fixedinputvelocity" ); m_fixedOutputVelocityModel.loadSettings( thisElement, "fixedoutputvelocity" ); m_outputProgramModel.loadSettings( thisElement, "outputprogram" ); + m_baseVelocityModel.loadSettings( thisElement, "basevelocity" ); m_readableModel.loadSettings( thisElement, "readable" ); m_writableModel.loadSettings( thisElement, "writable" ); @@ -253,11 +256,20 @@ void MidiPort::loadSettings( const QDomElement& thisElement ) } emit writablePortsChanged(); } + + if( thisElement.hasAttribute( "basevelocity" ) == false ) + { + // for projects created by LMMS < 0.9.92 there's no value for the base + // velocity and for compat reasons we have to stick with maximum velocity + // which did not allow note volumes > 100% + m_baseVelocityModel.setValue( MidiMaxVelocity ); + } } + void MidiPort::subscribeReadablePort( const QString& port, bool subscribe ) { m_readablePorts[port] = subscribe; diff --git a/src/gui/PianoRoll.cpp b/src/gui/PianoRoll.cpp index 5f4ac5da9..c09524152 100644 --- a/src/gui/PianoRoll.cpp +++ b/src/gui/PianoRoll.cpp @@ -1864,7 +1864,10 @@ void PianoRoll::testPlayNote( note * n ) if( n->isPlaying() == false && m_recording == false ) { n->setIsPlaying( true ); - m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( n->key(), n->midiVelocity() ); + + const int baseVelocity = m_pattern->instrumentTrack()->midiPort()->baseVelocity(); + + m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( n->key(), n->midiVelocity( baseVelocity ) ); MidiEvent event( MidiMetaEvent, 0, n->key(), panningToMidi( n->getPanning() ) ); @@ -2225,7 +2228,10 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * _me ) if( m_noteEditMode == NoteEditVolume ) { n->setVolume( vol ); - m_pattern->instrumentTrack()->processInEvent( MidiEvent( MidiKeyPressure, 0, n->key(), n->midiVelocity() ) ); + + const int baseVelocity = m_pattern->instrumentTrack()->midiPort()->baseVelocity(); + + m_pattern->instrumentTrack()->processInEvent( MidiEvent( MidiKeyPressure, 0, n->key(), n->midiVelocity( baseVelocity ) ) ); } else if( m_noteEditMode == NoteEditPanning ) { diff --git a/src/gui/widgets/InstrumentMidiIOView.cpp b/src/gui/widgets/InstrumentMidiIOView.cpp index 55988a9d4..6f69d2fa0 100644 --- a/src/gui/widgets/InstrumentMidiIOView.cpp +++ b/src/gui/widgets/InstrumentMidiIOView.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include "InstrumentMidiIOView.h" @@ -135,6 +136,30 @@ InstrumentMidiIOView::InstrumentMidiIOView( QWidget* parent ) : midiOutputLayout->insertWidget( 0, m_wpBtn ); } +#define PROVIDE_CUSTOM_BASE_VELOCITY_UI +#ifdef PROVIDE_CUSTOM_BASE_VELOCITY_UI + groupBox* baseVelocityGroupBox = new groupBox( tr( "CUSTOM BASE VELOCITY" ) ); + layout->addWidget( baseVelocityGroupBox ); + + QVBoxLayout* baseVelocityLayout = new QVBoxLayout( baseVelocityGroupBox ); + baseVelocityLayout->setContentsMargins( 8, 18, 8, 8 ); + baseVelocityLayout->setSpacing( 6 ); + + QLabel* baseVelocityHelp = new QLabel( tr( "Specify the velocity normalization base for MIDI-based instruments at note volume 100%" ) ); + baseVelocityHelp->setWordWrap( true ); + baseVelocityHelp->setFont( pointSize<8>( baseVelocityHelp->font() ) ); + + baseVelocityLayout->addWidget( baseVelocityHelp ); + + m_baseVelocitySpinBox = new LcdSpinBox( 3, baseVelocityGroupBox ); + m_baseVelocitySpinBox->setLabel( tr( "BASE VELOCITY" ) ); + m_baseVelocitySpinBox->setEnabled( false ); + baseVelocityLayout->addWidget( m_baseVelocitySpinBox ); + + connect( baseVelocityGroupBox->ledButton(), SIGNAL( toggled( bool ) ), + m_baseVelocitySpinBox, SLOT( setEnabled( bool ) ) ); +#endif + layout->addStretch(); } @@ -162,6 +187,10 @@ void InstrumentMidiIOView::modelChanged() m_fixedOutputNoteSpinBox->setModel( &mp->m_fixedOutputNoteModel ); m_outputProgramSpinBox->setModel( &mp->m_outputProgramModel ); +#ifdef PROVIDE_CUSTOM_BASE_VELOCITY_UI + m_baseVelocitySpinBox->setModel( &mp->m_baseVelocityModel ); +#endif + if( m_rpBtn ) { m_rpBtn->setMenu( mp->m_readablePortsMenu ); diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index e1af5d761..c6df21662 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -185,13 +185,6 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames, float v_scale = (float) getVolume() / DefaultVolume; - // We play MIDI-based instruments at velocity 63 for volume=100%. In order - // to get the same output volume, we need to scale it by 2 - if( m_instrument->flags().testFlag( Instrument::IsMidiBased ) ) - { - v_scale *= 2; - } - // instruments using instrument-play-handles will call this method // without any knowledge about notes, so they pass NULL for n, which // is no problem for us since we just bypass the envelopes+LFOs @@ -260,7 +253,7 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const MidiTime& ti // create (timed) note-play-handle NotePlayHandle* nph = new NotePlayHandle( this, time.frames( engine::framesPerTick() ), typeInfo::max() / 2, - note( MidiTime(), MidiTime(), event.key(), event.volume() ), + note( MidiTime(), MidiTime(), event.key(), event.volume( midiPort()->baseVelocity() ) ), NULL, false, event.channel(), NotePlayHandle::OriginMidiInput ); if( engine::mixer()->addPlayHandle( nph ) ) @@ -289,7 +282,7 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const MidiTime& ti { // setVolume() calls processOutEvent() with MidiKeyPressure so the // attached instrument will receive the event as well - m_notes[event.key()]->setVolume( event.volume() ); + m_notes[event.key()]->setVolume( event.volume( midiPort()->baseVelocity() ) ); } eventHandled = true; break;