MidiPort: introduced internal base velocity property

In order to keep compatibility with projects created with LMMS < 1.0.0
we maintain a property specifying the base velocity (i.e. the velocity
sent to MIDI-based instruments at volume=100%). For new projects this
always will be 64 while compat code enforces a value of 127 for old
projects.

We can also think about hiding the new groupbox in order to hide
complexity from the user.

Closes #430.
This commit is contained in:
Tobias Doerffel
2014-03-17 21:55:20 +01:00
parent f32c89bd13
commit 5e2d299360
10 changed files with 68 additions and 20 deletions

View File

@@ -58,7 +58,8 @@ private:
LcdSpinBox * m_fixedOutputNoteSpinBox;
QToolButton * m_wpBtn;
LcdSpinBox* m_baseVelocitySpinBox;
} ;
#endif

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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 );

View File

@@ -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 ) ) );
}

View File

@@ -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;

View File

@@ -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 )
{

View File

@@ -24,6 +24,7 @@
#include <QtGui/QMenu>
#include <QtGui/QToolButton>
#include <QtGui/QLabel>
#include <QtGui/QLayout>
#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 );

View File

@@ -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<f_cnt_t>::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;