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

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;