Adds support for MIDI CC events inside LMMS (#5581)

This commit is contained in:
IanCaio
2020-12-01 22:27:37 -03:00
committed by GitHub
parent 4f74151f00
commit 3c36365afa
12 changed files with 336 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

View File

@@ -50,6 +50,7 @@ public:
}
void setModel( Model* model, bool isOldModelValid = true ) override;
void unsetModel() override;
template<typename T>
inline T value() const

View File

@@ -30,6 +30,8 @@
#include "GroupBox.h"
#include "InstrumentFunctions.h"
#include "InstrumentSoundShaping.h"
#include "Midi.h"
#include "MidiCCRackView.h"
#include "MidiEventProcessor.h"
#include "MidiPort.h"
#include "NotePlayHandle.h"
@@ -228,7 +230,6 @@ signals:
void newNote();
void endNote();
protected:
QString nodeName() const override
{
@@ -247,6 +248,8 @@ protected slots:
private:
void processCCEvent(int controller);
MidiPort m_midiPort;
NotePlayHandle* m_notes[NumKeys];
@@ -286,11 +289,14 @@ private:
Piano m_piano;
std::unique_ptr<BoolModel> m_midiCCEnable;
std::unique_ptr<FloatModel> m_midiCCModel[MidiControllerCount];
friend class InstrumentTrackView;
friend class InstrumentTrackWindow;
friend class NotePlayHandle;
friend class InstrumentMiscView;
friend class MidiCCRackView;
} ;
@@ -334,6 +340,7 @@ protected:
private slots:
void toggleInstrumentWindow( bool _on );
void toggleMidiCCRack();
void activityIndicatorPressed();
void activityIndicatorReleased();
@@ -361,6 +368,8 @@ private:
QAction * m_midiInputAction;
QAction * m_midiOutputAction;
std::unique_ptr<MidiCCRackView> m_midiCCRackView;
QPoint m_lastPos;
FadeButton * getActivityIndicator() override

40
include/MidiCCRackView.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef MIDI_CC_RACK_VIEW_H
#define MIDI_CC_RACK_VIEW_H
#include <QWidget>
#include "GroupBox.h"
#include "Knob.h"
#include "Midi.h"
#include "SerializingObject.h"
class InstrumentTrack;
class MidiCCRackView : public QWidget, public SerializingObject
{
Q_OBJECT
public:
MidiCCRackView(InstrumentTrack * track);
~MidiCCRackView() override;
void saveSettings(QDomDocument & doc, QDomElement & parent) override;
void loadSettings(const QDomElement &) override;
inline QString nodeName() const override
{
return "MidiCCRackView";
}
private slots:
void renameWindow();
private:
InstrumentTrack *m_track;
GroupBox *m_midiCCGroupBox; // MIDI CC GroupBox (used to enable disable MIDI CC)
Knob *m_controllerKnob[MidiControllerCount]; // Holds the knob widgets for each controller
};
#endif

View File

@@ -33,27 +33,30 @@
class MidiEvent
{
public:
MidiEvent( MidiEventTypes type = MidiActiveSensing,
MidiEvent(MidiEventTypes type = MidiActiveSensing,
int8_t channel = 0,
int16_t param1 = 0,
int16_t param2 = 0,
const void* sourcePort = NULL ) :
const void* sourcePort = nullptr,
bool ignoreOnExport = true) :
m_type( type ),
m_metaEvent( MidiMetaInvalid ),
m_channel( channel ),
m_sysExData( NULL ),
m_sourcePort( sourcePort )
m_sourcePort(sourcePort),
m_ignoreOnExport(ignoreOnExport)
{
m_data.m_param[0] = param1;
m_data.m_param[1] = param2;
}
MidiEvent( MidiEventTypes type, const char* sysExData, int dataLen ) :
MidiEvent(MidiEventTypes type, const char* sysExData, int dataLen, bool ignoreOnExport = true) :
m_type( type ),
m_metaEvent( MidiMetaInvalid ),
m_channel( 0 ),
m_sysExData( sysExData ),
m_sourcePort( NULL )
m_sourcePort(nullptr),
m_ignoreOnExport(ignoreOnExport)
{
m_data.m_sysExDataLen = dataLen;
}
@@ -64,7 +67,8 @@ public:
m_channel( other.m_channel ),
m_data( other.m_data ),
m_sysExData( other.m_sysExData ),
m_sourcePort( other.m_sourcePort )
m_sourcePort(other.m_sourcePort),
m_ignoreOnExport(other.m_ignoreOnExport)
{
}
@@ -190,6 +194,16 @@ public:
setParam( 0, pitchBend );
}
bool ignoreOnExport() const
{
return m_ignoreOnExport;
}
void setIgnoreOnExport(bool value)
{
m_ignoreOnExport = value;
}
private:
MidiEventTypes m_type; // MIDI event type
@@ -205,6 +219,9 @@ private:
const char* m_sysExData;
const void* m_sourcePort;
// This helps us ignore MIDI events that shouldn't be processed
// during a project export, like physical controller events.
bool m_ignoreOnExport;
} ;
#endif

View File

@@ -36,6 +36,7 @@ public:
virtual ~ModelView();
virtual void setModel( Model* model, bool isOldModelValid = true );
virtual void unsetModel();
Model* model()
{

View File

@@ -142,6 +142,31 @@ void AutomatableModelView::setModel( Model* model, bool isOldModelValid )
// Unsets the current model by setting a dummy empty model. The dummy model is marked as
// "defaultConstructed", so the next call to setModel will delete it.
void AutomatableModelView::unsetModel()
{
if (dynamic_cast<FloatModelView*>(this))
{
setModel(new FloatModel(0, 0, 0, 1, nullptr, QString(), true));
}
else if (dynamic_cast<IntModelView*>(this))
{
setModel(new IntModel(0, 0, 0, nullptr, QString(), true));
}
else if (dynamic_cast<BoolModelView*>(this))
{
setModel(new BoolModel(false, nullptr, QString(), true));
}
else
{
ModelView::unsetModel();
}
}
void AutomatableModelView::mousePressEvent( QMouseEvent* event )
{
if( event->button() == Qt::LeftButton && event->modifiers() & Qt::ControlModifier )

View File

@@ -22,6 +22,7 @@ SET(LMMS_SRCS
gui/Lv2ViewBase.cpp
gui/MainApplication.cpp
gui/MainWindow.cpp
gui/MidiCCRackView.cpp
gui/MidiSetupWidget.cpp
gui/ModelView.cpp
gui/PeakControllerDialog.cpp

133
src/gui/MidiCCRackView.cpp Normal file
View File

@@ -0,0 +1,133 @@
/*
* MidiCCRackView.cpp - implementation of the MIDI CC rack widget
*
* Copyright (c) 2020 Ian Caio <iancaio_dev/at/hotmail.com>
*
* 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 "MidiCCRackView.h"
#include <QGridLayout>
#include <QMdiSubWindow>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
#include "embed.h"
#include "GroupBox.h"
#include "GuiApplication.h"
#include "InstrumentTrack.h"
#include "Knob.h"
#include "MainWindow.h"
#include "Track.h"
MidiCCRackView::MidiCCRackView(InstrumentTrack * track) :
QWidget(),
m_track(track)
{
setWindowIcon(embed::getIconPixmap("midi_cc_rack"));
setWindowTitle(tr("MIDI CC Rack - %1").arg(m_track->name()));
QMdiSubWindow * subWin = gui->mainWindow()->addWindowedWidget(this);
// Remove maximize button
Qt::WindowFlags flags = subWin->windowFlags();
flags &= ~Qt::WindowMaximizeButtonHint;
subWin->setWindowFlags(flags);
// Adjust window attributes, sizing and position
subWin->setAttribute(Qt::WA_DeleteOnClose, false);
subWin->resize(350, 300);
subWin->setFixedWidth(350);
subWin->setMinimumHeight(300);
subWin->hide();
// Main window layout
QVBoxLayout *mainLayout = new QVBoxLayout(this);
// Knobs GroupBox - Here we have the MIDI CC controller knobs for the selected track
m_midiCCGroupBox = new GroupBox(tr("MIDI CC Knobs:"));
// Layout to keep scrollable area under the GroupBox header
QVBoxLayout *knobsGroupBoxLayout = new QVBoxLayout();
knobsGroupBoxLayout->setContentsMargins(5, 16, 5, 5);
m_midiCCGroupBox->setLayout(knobsGroupBoxLayout);
// Scrollable area + widget + its layout that will have all the knobs
QScrollArea *knobsScrollArea = new QScrollArea();
QWidget *knobsArea = new QWidget();
QGridLayout *knobsAreaLayout = new QGridLayout();
knobsArea->setLayout(knobsAreaLayout);
knobsScrollArea->setWidget(knobsArea);
knobsScrollArea->setWidgetResizable(true);
knobsGroupBoxLayout->addWidget(knobsScrollArea);
// Adds the controller knobs
for (int i = 0; i < MidiControllerCount; ++i)
{
m_controllerKnob[i] = new Knob(knobBright_26);
m_controllerKnob[i]->setLabel(tr("CC %1").arg(i));
knobsAreaLayout->addWidget(m_controllerKnob[i], i/4, i%4);
}
// Set all the models
// Set the LED button to enable/disable the track midi cc
m_midiCCGroupBox->setModel(m_track->m_midiCCEnable.get());
// Set the model for each Knob
for (int i = 0; i < MidiControllerCount; ++i)
{
m_controllerKnob[i]->setModel(m_track->m_midiCCModel[i].get());
}
// Connection to update the name of the track on the label
connect(m_track, SIGNAL(nameChanged()),
this, SLOT(renameWindow()));
// Adding everything to the main layout
mainLayout->addWidget(m_midiCCGroupBox);
}
MidiCCRackView::~MidiCCRackView()
{
if(parentWidget())
{
parentWidget()->hide();
parentWidget()->deleteLater();
}
}
void MidiCCRackView::renameWindow()
{
setWindowTitle(tr("MIDI CC Rack - %1").arg(m_track->name()));
}
void MidiCCRackView::saveSettings(QDomDocument & doc, QDomElement & parent)
{
}
void MidiCCRackView::loadSettings(const QDomElement &)
{
}

View File

@@ -74,6 +74,16 @@ void ModelView::setModel( Model* model, bool isOldModelValid )
// Unsets the current model by setting a dummy empty model. The dummy model is marked as
// "defaultConstructed", so the next call to setModel will delete it.
void ModelView::unsetModel()
{
setModel(new Model(nullptr, QString(), true));
}
void ModelView::doConnections()
{
if( m_model != NULL )

View File

@@ -73,6 +73,7 @@
#include "StringPairDrag.h"
#include "TrackContainerView.h"
#include "TrackLabelButton.h"
#include "MidiCCRackView.h"
const int INSTRUMENT_WIDTH = 254;
@@ -119,6 +120,21 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) :
}
// Initialize the m_midiCCEnabled variable, but it's actually going to be connected
// to a LedButton
m_midiCCEnable = std::make_unique<BoolModel>(false, nullptr, tr("Enable/Disable MIDI CC"));
// Initialize the MIDI CC controller models and connect them to the method that processes
// the midi cc events
for (int i = 0; i < MidiControllerCount; ++i)
{
m_midiCCModel[i] = std::make_unique<FloatModel>(0.0f, 0.0f, 127.0f, 1.0f,
nullptr, tr("CC Controller %1").arg(i));
connect(m_midiCCModel[i].get(), &FloatModel::dataChanged,
this, [this, i]{ processCCEvent(i); }, Qt::DirectConnection);
}
setName( tr( "Default preset" ) );
connect( &m_baseNoteModel, SIGNAL( dataChanged() ),
@@ -242,9 +258,27 @@ MidiEvent InstrumentTrack::applyMasterKey( const MidiEvent& event )
void InstrumentTrack::processCCEvent(int controller)
{
// Does nothing if the LED is disabled
if (!m_midiCCEnable->value()) { return; }
uint8_t channel = static_cast<uint8_t>(midiPort()->realOutputChannel());
uint16_t cc = static_cast<uint16_t>(controller);
uint16_t value = static_cast<uint16_t>(m_midiCCModel[controller]->value());
// Process the MIDI CC event as an input event but with ignoreOnExport set to false
// so we can know LMMS generated the event, not a controller, and can process it during
// the project export
processInEvent(MidiEvent(MidiControlChange, channel, cc, value, NULL, false));
}
void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset )
{
if( Engine::getSong()->isExporting() )
if (Engine::getSong()->isExporting() && event.ignoreOnExport())
{
return;
}
@@ -373,9 +407,11 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& tim
break;
}
if( eventHandled == false && instrument()->handleMidiEvent( event, time, offset ) == false )
// If the event wasn't handled, check if there's a loaded instrument and if so send the
// event to it. If it returns false means the instrument didn't handle the event, so we trigger a warning.
if (eventHandled == false && !(instrument() && instrument()->handleMidiEvent(event, time, offset)))
{
qWarning( "InstrumentTrack: unhandled MIDI event %d", event.type() );
qWarning("InstrumentTrack: unhandled MIDI event %d", event.type());
}
}
@@ -740,6 +776,15 @@ void InstrumentTrack::saveTrackSpecificSettings( QDomDocument& doc, QDomElement
m_baseNoteModel.saveSettings( doc, thisElement, "basenote" );
m_useMasterPitchModel.saveSettings( doc, thisElement, "usemasterpitch");
// Save MIDI CC stuff
m_midiCCEnable->saveSettings(doc, thisElement, "enablecc");
QDomElement midiCC = doc.createElement("midicontrollers");
thisElement.appendChild(midiCC);
for (int i = 0; i < MidiControllerCount; ++i)
{
m_midiCCModel[i]->saveSettings(doc, midiCC, "cc" + QString::number(i));
}
if( m_instrument != NULL )
{
QDomElement i = doc.createElement( "instrument" );
@@ -798,6 +843,10 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement
// clear effect-chain just in case we load an old preset without FX-data
m_audioPort.effects()->clear();
// We set MIDI CC enable to false so the knobs don't trigger MIDI CC events while
// they are being loaded. After all knobs are loaded we load the right value of m_midiCCEnable.
m_midiCCEnable->setValue(false);
QDomNode node = thisElement.firstChild();
while( !node.isNull() )
{
@@ -842,6 +891,13 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement
emit instrumentChanged();
}
}
else if (node.nodeName() == "midicontrollers")
{
for (int i = 0; i < MidiControllerCount; ++i)
{
m_midiCCModel[i]->loadSettings(node.toElement(), "cc" + QString::number(i));
}
}
// compat code - if node-name doesn't match any known
// one, we assume that it is an instrument-plugin
// which we'll try to load
@@ -862,6 +918,10 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement
}
node = node.nextSibling();
}
// Load the right value of m_midiCCEnable
m_midiCCEnable->loadSettings(thisElement, "enablecc");
updatePitchRange();
unlock();
}
@@ -997,7 +1057,7 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
m_panningKnob = new Knob( knobSmall_17, getTrackSettingsWidget(),
tr( "Panning" ) );
m_panningKnob->setModel( &_it->m_panningModel );
m_panningKnob->setHintText( tr( "Panning:" ), "%" );
m_panningKnob->setHintText(tr("Panning:"), "%");
m_panningKnob->move( widgetWidth-24, 2 );
m_panningKnob->setLabel( tr( "PAN" ) );
m_panningKnob->show();
@@ -1037,6 +1097,11 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
m_midiInputAction->setText( tr( "Input" ) );
m_midiOutputAction->setText( tr( "Output" ) );
QAction *midiRackAction = m_midiMenu->addAction(tr("Open/Close MIDI CC Rack"));
midiRackAction->setIcon(embed::getIconPixmap("midi_cc_rack"));
connect(midiRackAction, SIGNAL(triggered()),
this, SLOT(toggleMidiCCRack()));
m_activityIndicator = new FadeButton( QApplication::palette().color( QPalette::Active,
QPalette::Background),
QApplication::palette().color( QPalette::Active,
@@ -1074,6 +1139,29 @@ InstrumentTrackView::~InstrumentTrackView()
void InstrumentTrackView::toggleMidiCCRack()
{
// Lazy creation: midiCCRackView is only created when accessed the first time.
// this->model() returns pointer to the InstrumentTrack who owns this InstrumentTrackView.
if (!m_midiCCRackView)
{
m_midiCCRackView = std::unique_ptr<MidiCCRackView>(new MidiCCRackView(this->model()));
}
if (m_midiCCRackView->parentWidget()->isVisible())
{
m_midiCCRackView->parentWidget()->hide();
}
else
{
m_midiCCRackView->parentWidget()->show();
m_midiCCRackView->show();
}
}
InstrumentTrackWindow * InstrumentTrackView::topLevelInstrumentTrackWindow()
{
InstrumentTrackWindow * w = NULL;