Ghost notes for the automation editor (#6940)
Show ghost notes or sample track as a visual aid in the Automation Editor. --------- Co-authored-by: IanCaio <iancaio_dev@hotmail.com>
This commit is contained in:
@@ -25,13 +25,16 @@
|
||||
|
||||
#include "MidiClipView.h"
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <QApplication>
|
||||
#include <QInputDialog>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <cmath>
|
||||
|
||||
#include "AutomationEditor.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "DeprecationHelper.h"
|
||||
#include "GuiApplication.h"
|
||||
@@ -85,10 +88,11 @@ void MidiClipView::update()
|
||||
|
||||
void MidiClipView::openInPianoRoll()
|
||||
{
|
||||
getGUI()->pianoRoll()->setCurrentMidiClip( m_clip );
|
||||
getGUI()->pianoRoll()->parentWidget()->show();
|
||||
getGUI()->pianoRoll()->show();
|
||||
getGUI()->pianoRoll()->setFocus();
|
||||
auto pRoll = getGUI()->pianoRoll();
|
||||
pRoll->setCurrentMidiClip(m_clip);
|
||||
pRoll->parentWidget()->show();
|
||||
pRoll->show();
|
||||
pRoll->setFocus();
|
||||
}
|
||||
|
||||
|
||||
@@ -97,14 +101,21 @@ void MidiClipView::openInPianoRoll()
|
||||
|
||||
void MidiClipView::setGhostInPianoRoll()
|
||||
{
|
||||
getGUI()->pianoRoll()->setGhostMidiClip( m_clip );
|
||||
getGUI()->pianoRoll()->parentWidget()->show();
|
||||
getGUI()->pianoRoll()->show();
|
||||
getGUI()->pianoRoll()->setFocus();
|
||||
auto pRoll = getGUI()->pianoRoll();
|
||||
pRoll->setGhostMidiClip(m_clip);
|
||||
pRoll->parentWidget()->show();
|
||||
pRoll->show();
|
||||
pRoll->setFocus();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MidiClipView::setGhostInAutomationEditor()
|
||||
{
|
||||
auto aEditor = getGUI()->automationEditor();
|
||||
aEditor->setGhostMidiClip(m_clip);
|
||||
aEditor->parentWidget()->show();
|
||||
aEditor->show();
|
||||
aEditor->setFocus();
|
||||
}
|
||||
|
||||
void MidiClipView::resetName() { m_clip->setName(""); }
|
||||
|
||||
@@ -192,7 +203,13 @@ void MidiClipView::constructContextMenu( QMenu * _cm )
|
||||
_cm->insertAction( _cm->actions()[1], b );
|
||||
connect( b, SIGNAL(triggered(bool)),
|
||||
this, SLOT(setGhostInPianoRoll()));
|
||||
_cm->insertSeparator( _cm->actions()[2] );
|
||||
|
||||
auto c = new QAction(embed::getIconPixmap("automation_ghost_note"), tr("Set as ghost in automation editor"), _cm);
|
||||
if (m_clip->empty()) { c->setEnabled(false); }
|
||||
_cm->insertAction(_cm->actions()[2], c);
|
||||
connect(c, &QAction::triggered, this, &MidiClipView::setGhostInAutomationEditor);
|
||||
|
||||
_cm->insertSeparator(_cm->actions()[3]);
|
||||
_cm->addSeparator();
|
||||
|
||||
_cm->addAction( embed::getIconPixmap( "edit_erase" ),
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
|
||||
#include "GuiApplication.h"
|
||||
#include "AutomationEditor.h"
|
||||
#include "embed.h"
|
||||
#include "PathUtil.h"
|
||||
#include "SampleBuffer.h"
|
||||
@@ -83,6 +85,12 @@ void SampleClipView::constructContextMenu(QMenu* cm)
|
||||
SLOT(reverseSample())
|
||||
);
|
||||
|
||||
cm->addAction(
|
||||
embed::getIconPixmap("automation_ghost_note"),
|
||||
tr("Set as ghost in automation editor"),
|
||||
this,
|
||||
SLOT(setAutomationGhost())
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -321,6 +329,14 @@ void SampleClipView::reverseSample()
|
||||
|
||||
|
||||
|
||||
void SampleClipView::setAutomationGhost()
|
||||
{
|
||||
auto aEditor = gui::getGUI()->automationEditor();
|
||||
aEditor->setGhostSample(m_clip);
|
||||
aEditor->parentWidget()->show();
|
||||
aEditor->show();
|
||||
aEditor->setFocus();
|
||||
}
|
||||
|
||||
//! Split this Clip.
|
||||
/*! \param pos the position of the split, relative to the start of the clip */
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
|
||||
#include "AutomationEditor.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QInputDialog>
|
||||
#include <QKeyEvent>
|
||||
@@ -38,6 +36,9 @@
|
||||
#include <QScrollBar>
|
||||
#include <QStyleOption>
|
||||
#include <QToolTip>
|
||||
#include <cmath>
|
||||
|
||||
#include "SampleClip.h"
|
||||
|
||||
#ifndef __USE_XOPEN
|
||||
#define __USE_XOPEN
|
||||
@@ -46,20 +47,23 @@
|
||||
#include "ActionGroup.h"
|
||||
#include "AutomationNode.h"
|
||||
#include "ComboBox.h"
|
||||
#include "debug.h"
|
||||
#include "DeprecationHelper.h"
|
||||
#include "embed.h"
|
||||
#include "DetuningHelper.h"
|
||||
#include "Engine.h"
|
||||
#include "GuiApplication.h"
|
||||
#include "gui_templates.h"
|
||||
#include "Knob.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MidiClip.h"
|
||||
#include "PatternStore.h"
|
||||
#include "PianoRoll.h"
|
||||
#include "ProjectJournal.h"
|
||||
#include "SampleBuffer.h"
|
||||
#include "StringPairDrag.h"
|
||||
#include "TextFloat.h"
|
||||
#include "TimeLineWidget.h"
|
||||
#include "debug.h"
|
||||
#include "embed.h"
|
||||
#include "gui_templates.h"
|
||||
|
||||
|
||||
namespace lmms::gui
|
||||
@@ -101,7 +105,8 @@ AutomationEditor::AutomationEditor() :
|
||||
m_nodeTangentLineColor(0, 0, 0),
|
||||
m_scaleColor(Qt::SolidPattern),
|
||||
m_crossColor(0, 0, 0),
|
||||
m_backgroundShade(0, 0, 0)
|
||||
m_backgroundShade(0, 0, 0),
|
||||
m_ghostNoteColor(0, 0, 0)
|
||||
{
|
||||
connect( this, SIGNAL(currentClipChanged()),
|
||||
this, SLOT(updateAfterClipChange()),
|
||||
@@ -1032,8 +1037,19 @@ inline void AutomationEditor::drawAutomationTangents(QPainter& p, timeMap::itera
|
||||
p.drawEllipse(tx - 3, ty - 3, 6, 6);
|
||||
}
|
||||
|
||||
void AutomationEditor::setGhostMidiClip(MidiClip* newMidiClip)
|
||||
{
|
||||
// Expects a pointer to a MIDI clip or nullptr.
|
||||
m_ghostNotes = newMidiClip;
|
||||
m_renderSample = false;
|
||||
}
|
||||
|
||||
|
||||
void AutomationEditor::setGhostSample(SampleClip* newGhostSample)
|
||||
{
|
||||
// Expects a pointer to a Sample buffer or nullptr.
|
||||
m_ghostSample = newGhostSample;
|
||||
m_renderSample = true;
|
||||
}
|
||||
|
||||
void AutomationEditor::paintEvent(QPaintEvent * pe )
|
||||
{
|
||||
@@ -1219,6 +1235,81 @@ void AutomationEditor::paintEvent(QPaintEvent * pe )
|
||||
p.drawLine( x, grid_bottom, x, x_line_end );
|
||||
}
|
||||
|
||||
// draw ghost sample
|
||||
if (m_ghostSample != nullptr && m_ghostSample->sampleBuffer()->frames() > 1 && m_renderSample)
|
||||
{
|
||||
int sampleFrames = m_ghostSample->sampleBuffer()->frames();
|
||||
int length = static_cast<float>(sampleFrames) / Engine::framesPerTick();
|
||||
int editorHeight = grid_bottom - TOP_MARGIN;
|
||||
|
||||
int startPos = xCoordOfTick(0);
|
||||
int sampleWidth = xCoordOfTick(length) - startPos;
|
||||
int sampleHeight = std::min(editorHeight - SAMPLE_MARGIN, MAX_SAMPLE_HEIGHT);
|
||||
int yOffset = (editorHeight - sampleHeight) / 2.0f + TOP_MARGIN;
|
||||
|
||||
p.setPen(m_ghostSampleColor);
|
||||
m_ghostSample->sampleBuffer()->visualize(p, QRect(startPos, yOffset, sampleWidth, sampleHeight), 0, sampleFrames);
|
||||
}
|
||||
|
||||
// draw ghost notes
|
||||
if (m_ghostNotes != nullptr && !m_renderSample)
|
||||
{
|
||||
const NoteVector& notes = m_ghostNotes->notes();
|
||||
int minKey = 128;
|
||||
int maxKey = 0;
|
||||
int detuningOffset = 0;
|
||||
const Note* detuningNote = nullptr;
|
||||
|
||||
for (const Note* note : notes)
|
||||
{
|
||||
int noteKey = note->key();
|
||||
|
||||
if (note->detuning()->automationClip() == m_clip) {
|
||||
detuningOffset = note->pos();
|
||||
detuningNote = note;
|
||||
}
|
||||
|
||||
maxKey = std::max(maxKey, noteKey);
|
||||
minKey = std::min(minKey, noteKey);
|
||||
}
|
||||
|
||||
for (const Note* note : notes)
|
||||
{
|
||||
int lenTicks = note->length();
|
||||
int notePos = note->pos();
|
||||
|
||||
// offset note if detuning
|
||||
if (notePos+lenTicks < detuningOffset) { continue; }
|
||||
notePos -= detuningOffset;
|
||||
|
||||
// remove/change after #5902
|
||||
if (lenTicks == 0) { continue; }
|
||||
else if (lenTicks < 0) { lenTicks = 4; }
|
||||
|
||||
int note_width = lenTicks * m_ppb / TimePos::ticksPerBar();
|
||||
int keyRange = maxKey - minKey;
|
||||
|
||||
if (keyRange < MIN_NOTE_RANGE)
|
||||
{
|
||||
int padding = (MIN_NOTE_RANGE - keyRange) / 2.0f;
|
||||
maxKey += padding;
|
||||
minKey -= padding;
|
||||
keyRange = MIN_NOTE_RANGE;
|
||||
}
|
||||
|
||||
float absNoteHeight = static_cast<float>(note->key() - minKey) / (maxKey - minKey);
|
||||
int graphHeight = grid_bottom - NOTE_HEIGHT - NOTE_MARGIN - TOP_MARGIN;
|
||||
const int y = (graphHeight - graphHeight * absNoteHeight) + NOTE_HEIGHT / 2.0f + TOP_MARGIN;
|
||||
const int x = xCoordOfTick(notePos);
|
||||
|
||||
if (note == detuningNote) {
|
||||
p.fillRect(x, y, note_width, NOTE_HEIGHT, m_detuningNoteColor);
|
||||
} else {
|
||||
p.fillRect(x, y, note_width, NOTE_HEIGHT, m_ghostNoteColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// and finally bars
|
||||
for( tick = m_currentPosition - m_currentPosition % TimePos::ticksPerBar(),
|
||||
x = xCoordOfTick( tick );
|
||||
@@ -2117,8 +2208,18 @@ AutomationEditorWindow::AutomationEditorWindow() :
|
||||
quantizationActionsToolBar->addWidget( quantize_lbl );
|
||||
quantizationActionsToolBar->addWidget( m_quantizeComboBox );
|
||||
|
||||
m_resetGhostNotes = new QPushButton(m_toolBar);
|
||||
m_resetGhostNotes->setIcon(embed::getIconPixmap("clear_ghost_note"));
|
||||
m_resetGhostNotes->setToolTip(tr("Clear ghost notes"));
|
||||
m_resetGhostNotes->setEnabled(true);
|
||||
|
||||
connect(m_resetGhostNotes, &QPushButton::pressed, m_editor, &AutomationEditor::resetGhostNotes);
|
||||
|
||||
quantizationActionsToolBar->addSeparator();
|
||||
quantizationActionsToolBar->addWidget(m_resetGhostNotes);
|
||||
|
||||
// Setup our actual window
|
||||
setFocusPolicy( Qt::StrongFocus );
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setFocus();
|
||||
setWindowIcon( embed::getIconPixmap( "automation" ) );
|
||||
setAcceptDrops( true );
|
||||
|
||||
@@ -1635,6 +1635,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me )
|
||||
}
|
||||
detuningClip = n->detuning()->automationClip();
|
||||
connect(detuningClip.data(), SIGNAL(dataChanged()), this, SLOT(update()));
|
||||
getGUI()->automationEditor()->setGhostMidiClip(m_midiClip);
|
||||
getGUI()->automationEditor()->open(detuningClip);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user