hydrogen 1.2.6
PatternEditor.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by the Hydrogen Team
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
23#include "PatternEditor.h"
24#include "PatternEditorRuler.h"
26#include "PatternEditorPanel.h"
28#include "../CommonStrings.h"
29#include "../HydrogenApp.h"
30#include "../EventListener.h"
31#include "../UndoActions.h"
32#include "../Skin.h"
33
34#include <core/Globals.h>
35#include <core/Basics/Song.h>
36#include <core/Hydrogen.h>
38#include <core/EventQueue.h>
43#include <core/Basics/Pattern.h>
45#include <core/Basics/Adsr.h>
46#include <core/Basics/Note.h>
48#include <core/Helpers/Xml.h>
49
50
51using namespace std;
52using namespace H2Core;
53
54
56 PatternEditorPanel *panel )
57 : Object()
58 , QWidget( pParent )
59 , m_selection( this )
60 , m_bEntered( false )
61 , m_pDraggedNote( nullptr )
62 , m_pPatternEditorPanel( panel )
63 , m_pPattern( nullptr )
64 , m_bSelectNewNotes( false )
65 , m_bFineGrained( false )
66 , m_bCopyNotMove( false )
67 , m_nTick( -1 )
68 , m_editor( Editor::None )
69 , m_mode( Mode::None )
70{
71
72 const auto pPref = H2Core::Preferences::get_instance();
73
74 m_nResolution = pPref->getPatternEditorGridResolution();
75 m_bUseTriplets = pPref->isPatternEditorUsingTriplets();
76 m_fGridWidth = pPref->getPatternEditorGridWidth();
79
80 setFocusPolicy(Qt::StrongFocus);
81
84
86
87 // Popup context menu
88 m_pPopupMenu = new QMenu( this );
89 m_pPopupMenu->addAction( tr( "&Cut" ), this, SLOT( cut() ) );
90 m_pPopupMenu->addAction( tr( "&Copy" ), this, SLOT( copy() ) );
91 m_pPopupMenu->addAction( tr( "&Paste" ), this, SLOT( paste() ) );
92 m_pPopupMenu->addAction( tr( "&Delete" ), this, SLOT( deleteSelection() ) );
93 m_pPopupMenu->addAction( tr( "Select &all" ), this, SLOT( selectAll() ) );
94 m_pPopupMenu->addAction( tr( "Clear selection" ), this, SLOT( selectNone() ) );
95
96 qreal pixelRatio = devicePixelRatio();
97 m_pBackgroundPixmap = new QPixmap( m_nEditorWidth * pixelRatio,
98 height() * pixelRatio );
99 m_pBackgroundPixmap->setDevicePixelRatio( pixelRatio );
101}
102
109
111{
112 if ( changes & H2Core::Preferences::Changes::Colors ) {
113
114 update( 0, 0, width(), height() );
115 m_pPatternEditorPanel->updateEditors();
116 }
117}
118
119void PatternEditor::setResolution(uint res, bool bUseTriplets)
120{
121 this->m_nResolution = res;
122 this->m_bUseTriplets = bUseTriplets;
123
124 // redraw all
125 update( 0, 0, width(), height() );
126 m_pPatternEditorPanel->updateEditors();
127}
128
130{
131 if (m_fGridWidth >= 3) {
132 m_fGridWidth *= 2;
133 } else {
134 m_fGridWidth *= 1.5;
135 }
136 updateEditor();
137}
138
140{
141 if ( m_fGridWidth > 1.5 ) {
142 if (m_fGridWidth > 3) {
143 m_fGridWidth /= 2;
144 } else {
145 m_fGridWidth /= 1.5;
146 }
147 updateEditor();
148 }
149}
150
151QColor PatternEditor::computeNoteColor( float fVelocity ) {
152 float fRed, fGreen, fBlue;
153
155
156 QColor fullColor = pPref->getColorTheme()->m_patternEditor_noteVelocityFullColor;
157 QColor defaultColor = pPref->getColorTheme()->m_patternEditor_noteVelocityDefaultColor;
158 QColor halfColor = pPref->getColorTheme()->m_patternEditor_noteVelocityHalfColor;
159 QColor zeroColor = pPref->getColorTheme()->m_patternEditor_noteVelocityZeroColor;
160
161 // The colors defined in the Preferences correspond to fixed
162 // velocity values. In case the velocity lies between two of those
163 // the corresponding colors will be interpolated.
164 float fWeightFull = 0;
165 float fWeightDefault = 0;
166 float fWeightHalf = 0;
167 float fWeightZero = 0;
168
169 if ( fVelocity >= 1.0 ) {
170 fWeightFull = 1.0;
171 } else if ( fVelocity >= 0.8 ) {
172 fWeightDefault = ( 1.0 - fVelocity )/ ( 1.0 - 0.8 );
173 fWeightFull = 1.0 - fWeightDefault;
174 } else if ( fVelocity >= 0.5 ) {
175 fWeightHalf = ( 0.8 - fVelocity )/ ( 0.8 - 0.5 );
176 fWeightDefault = 1.0 - fWeightHalf;
177 } else {
178 fWeightZero = ( 0.5 - fVelocity )/ ( 0.5 );
179 fWeightHalf = 1.0 - fWeightZero;
180 }
181
182 fRed = fWeightFull * fullColor.redF() +
183 fWeightDefault * defaultColor.redF() +
184 fWeightHalf * halfColor.redF() + fWeightZero * zeroColor.redF();
185 fGreen = fWeightFull * fullColor.greenF() +
186 fWeightDefault * defaultColor.greenF() +
187 fWeightHalf * halfColor.greenF() + fWeightZero * zeroColor.greenF();
188 fBlue = fWeightFull * fullColor.blueF() +
189 fWeightDefault * defaultColor.blueF() +
190 fWeightHalf * halfColor.blueF() + fWeightZero * zeroColor.blueF();
191
192 QColor color;
193 color.setRedF( fRed );
194 color.setGreenF( fGreen );
195 color.setBlueF( fBlue );
196
197 return color;
198}
199
200
201void PatternEditor::drawNoteSymbol( QPainter &p, QPoint pos, H2Core::Note *pNote, bool bIsForeground ) const
202{
203 if ( m_pPattern == nullptr ) {
204 return;
205 }
206
208
209 const QColor noteColor( pPref->getColorTheme()->m_patternEditor_noteVelocityDefaultColor );
210 const QColor noteInactiveColor( pPref->getColorTheme()->m_windowTextColor.darker( 150 ) );
211 const QColor noteoffColor( pPref->getColorTheme()->m_patternEditor_noteOffColor );
212 const QColor noteoffInactiveColor( pPref->getColorTheme()->m_windowTextColor );
213
214 p.setRenderHint( QPainter::Antialiasing );
215
216 QColor color = computeNoteColor( pNote->get_velocity() );
217
218 uint w = 8, h = 8;
219 uint x_pos = pos.x(), y_pos = pos.y();
220
221 bool bSelected = m_selection.isSelected( pNote );
222
223 if ( bSelected ) {
224 QPen selectedPen( selectedNoteColor() );
225 selectedPen.setWidth( 2 );
226 p.setPen( selectedPen );
227 p.setBrush( Qt::NoBrush );
228 }
229
230 bool bMoving = bSelected && m_selection.isMoving();
231 QPen movingPen( noteColor );
232 QPoint movingOffset;
233
234 if ( bMoving ) {
235 movingPen.setStyle( Qt::DotLine );
236 movingPen.setWidth( 2 );
237 QPoint delta = movingGridOffset();
238 movingOffset = QPoint( delta.x() * m_fGridWidth,
239 delta.y() * m_nGridHeight );
240 }
241
242 if ( pNote->get_note_off() == false ) { // trigger note
243 int width = w;
244
245 QBrush noteBrush( color );
246 QPen notePen( noteColor );
247 if ( !bIsForeground ) {
248
249 if ( x_pos >= m_nActiveWidth ) {
250 noteBrush.setColor( noteInactiveColor );
251 notePen.setColor( noteInactiveColor );
252 }
253
254 noteBrush.setStyle( Qt::Dense4Pattern );
255 notePen.setStyle( Qt::DotLine );
256 }
257
258 if ( bSelected ) {
259 p.drawEllipse( x_pos -4 -2, y_pos-2, w+4, h+4 );
260 }
261
262 // Draw tail
263 if ( pNote->get_length() != -1 ) {
264 float fNotePitch = pNote->get_octave() * 12 + pNote->get_key();
265 float fStep = Note::pitchToFrequency( ( double )fNotePitch );
266
267 // if there is a stop-note to the right of this note, only draw-
268 // its length till there.
269 int nLength = pNote->get_length();
270 auto notes = m_pPattern->get_notes();
271 for ( const auto& [ _, ppNote ] : *m_pPattern->get_notes() ) {
272 if ( ppNote != nullptr &&
273 // noteOff note
274 ppNote->get_note_off() &&
275 // located in the same row
276 ppNote->get_instrument() == pNote->get_instrument() &&
277 // left of the NoteOff
278 pNote->get_position() < ppNote->get_position() &&
279 // custom length reaches beyond NoteOff
280 pNote->get_position() + pNote->get_length() >
281 ppNote->get_position() ) {
282 // In case there are multiple stop-notes present, take the
283 // shortest distance.
284 nLength = std::min( ppNote->get_position() - pNote->get_position(), nLength );
285 }
286 }
287
288 width = m_fGridWidth * nLength / fStep;
289 width = width - 1; // lascio un piccolo spazio tra una nota ed un altra
290
291 if ( bSelected ) {
292 p.drawRoundedRect( x_pos-2, y_pos, width+4, 3+4, 4, 4 );
293 }
294 p.setPen( notePen );
295 p.setBrush( noteBrush );
296
297 // Since the note body is transparent for an inactive note, we try
298 // to start the tail at its boundary. For regular notes we do not
299 // care about an overlap, as it ensures that there are no white
300 // artifacts between tail and note body regardless of the scale
301 // factor.
302 int nRectOnsetX = x_pos;
303 int nRectWidth = width;
304 if ( ! bIsForeground ) {
305 nRectOnsetX = nRectOnsetX + w/2;
306 nRectWidth = nRectWidth - w/2;
307 }
308
309 p.drawRect( nRectOnsetX, y_pos +2, nRectWidth, 3 );
310 p.drawLine( x_pos+width, y_pos, x_pos+width, y_pos + h );
311 }
312
313 p.setPen( notePen );
314 p.setBrush( noteBrush );
315 p.drawEllipse( x_pos -4 , y_pos, w, h );
316
317 if ( bMoving ) {
318 p.setPen( movingPen );
319 p.setBrush( Qt::NoBrush );
320 p.drawEllipse( movingOffset.x() + x_pos -4 -2, movingOffset.y() + y_pos -2 , w + 4, h + 4 );
321 // Moving tail
322 if ( pNote->get_length() != -1 ) {
323 p.setPen( movingPen );
324 p.setBrush( Qt::NoBrush );
325 p.drawRoundedRect( movingOffset.x() + x_pos-2, movingOffset.y() + y_pos, width+4, 3+4, 4, 4 );
326 }
327 }
328 }
329 else if ( pNote->get_length() == 1 && pNote->get_note_off() == true ) {
330
331 QBrush noteOffBrush( noteoffColor );
332 if ( !bIsForeground ) {
333 noteOffBrush.setStyle( Qt::Dense4Pattern );
334
335 if ( x_pos >= m_nActiveWidth ) {
336 noteOffBrush.setColor( noteoffInactiveColor );
337 }
338 }
339
340 if ( bSelected ) {
341 p.drawEllipse( x_pos -4 -2, y_pos-2, w+4, h+4 );
342 }
343
344 p.setPen( Qt::NoPen );
345 p.setBrush( noteOffBrush );
346 p.drawEllipse( x_pos -4 , y_pos, w, h );
347
348 if ( bMoving ) {
349 p.setPen( movingPen );
350 p.setBrush( Qt::NoBrush );
351 p.drawEllipse( movingOffset.x() + x_pos -4 -2, movingOffset.y() + y_pos -2, w + 4, h + 4 );
352 }
353 }
354}
355
356
357int PatternEditor::getColumn( int x, bool bUseFineGrained ) const
358{
359 int nGranularity = 1;
360 if ( !( bUseFineGrained && m_bFineGrained ) ) {
361 nGranularity = granularity();
362 }
363 int nWidth = m_fGridWidth * nGranularity;
364 int nColumn = ( x - PatternEditor::nMargin + (nWidth / 2) ) / nWidth;
365 nColumn = nColumn * nGranularity;
366 if ( nColumn < 0 ) {
367 return 0;
368 } else {
369 return nColumn;
370 }
371}
372
374{
375 m_selection.clearSelection();
376 m_selection.updateWidgetGroup();
377}
378
383{
384 Hydrogen *pHydrogen = Hydrogen::get_instance();
385 auto pInstrumentList = pHydrogen->getSong()->getInstrumentList();
386 XMLDoc doc;
387 XMLNode selection = doc.set_root( "noteSelection" );
388 XMLNode noteList = selection.createNode( "noteList");
389 XMLNode positionNode = selection.createNode( "sourcePosition" );
390 bool bWroteNote = false;
391 // "Top left" of selection, in the three dimensional time*instrument*pitch space.
392 int nMinColumn, nMinRow, nMaxPitch;
393
394 for ( Note *pNote : m_selection ) {
395 const int nPitch = pNote->get_notekey_pitch();
396 const int nColumn = pNote->get_position();
397 const int nRow = pInstrumentList->index( pNote->get_instrument() );
398 if ( nRow == -1 ) {
399 // In versions prior to v2.0 all notes not belonging to any
400 // instrument will just be ignored.
401 continue;
402 }
403 if ( bWroteNote ) {
404 nMinColumn = std::min( nColumn, nMinColumn );
405 nMinRow = std::min( nRow, nMinRow );
406 nMaxPitch = std::max( nPitch, nMaxPitch );
407 } else {
408 nMinColumn = nColumn;
409 nMinRow = nRow;
410 nMaxPitch = nPitch;
411 bWroteNote = true;
412 }
413 XMLNode note_node = noteList.createNode( "note" );
414 pNote->save_to( &note_node );
415 }
416
417 if ( bWroteNote ) {
418 positionNode.write_int( "minColumn", nMinColumn );
419 positionNode.write_int( "minRow", nMinRow );
420 positionNode.write_int( "maxPitch", nMaxPitch );
421 } else {
422 positionNode.write_int( "minColumn",
423 m_pPatternEditorPanel->getCursorPosition() );
424 positionNode.write_int( "minRow",
425 pHydrogen->getSelectedInstrumentNumber() );
426 }
427
428 QClipboard *clipboard = QApplication::clipboard();
429 clipboard->setText( doc.toString() );
430
431 // This selection will probably be pasted at some point. So show the keyboard cursor as this is the place
432 // where the selection will be pasted.
434}
435
436
438{
439 copy();
441}
442
443
445{
446 if ( m_pPattern == nullptr ) {
447 return;
448 }
449
450 auto pInstrumentList = Hydrogen::get_instance()->getSong()->getInstrumentList();
451 auto pInstrument = pInstrumentList->get( nInstrument );
452
453 m_selection.clearSelection();
455 if ( it->second->get_instrument() == pInstrument ) {
456 m_selection.addToSelection( it->second );
457 }
458 }
459 m_selection.updateWidgetGroup();
460}
461
462void PatternEditor::setCurrentInstrument( int nInstrument ) {
464 m_pPatternEditorPanel->updateEditors();
465}
466
467void PatternEditor::mousePressEvent( QMouseEvent *ev )
468{
469 updateModifiers( ev );
470 m_selection.mousePressEvent( ev );
471}
472
473void PatternEditor::mouseMoveEvent( QMouseEvent *ev )
474{
475 updateModifiers( ev );
476 if ( m_selection.isMoving() ) {
477 updateEditor( true );
478 }
479 m_selection.mouseMoveEvent( ev );
480}
481
482void PatternEditor::mouseReleaseEvent( QMouseEvent *ev )
483{
484 updateModifiers( ev );
485 m_selection.mouseReleaseEvent( ev );
486}
487
488void PatternEditor::updateModifiers( QInputEvent *ev ) {
489 // Key: Alt + drag: move notes with fine-grained positioning
490 m_bFineGrained = ev->modifiers() & Qt::AltModifier;
491 // Key: Ctrl + drag: copy notes rather than moving
492 m_bCopyNotMove = ev->modifiers() & Qt::ControlModifier;
493
494 if ( QKeyEvent *pEv = dynamic_cast<QKeyEvent*>( ev ) ) {
495 // Keyboard events for press and release of modifier keys don't have those keys in the modifiers set,
496 // so explicitly update these.
497 bool bPressed = ev->type() == QEvent::KeyPress;
498 if ( pEv->key() == Qt::Key_Control ) {
499 m_bCopyNotMove = bPressed;
500 } else if ( pEv->key() == Qt::Key_Alt ) {
501 m_bFineGrained = bPressed;
502 }
503 }
504
505 if ( m_selection.isMouseGesture() && m_selection.isMoving() ) {
506 // If a selection is currently being moved, change the cursor
507 // appropriately. Selection will change it back after the move
508 // is complete (or abandoned)
509 if ( m_bCopyNotMove && cursor().shape() != Qt::DragCopyCursor ) {
510 setCursor( QCursor( Qt::DragCopyCursor ) );
511 } else if ( !m_bCopyNotMove && cursor().shape() != Qt::DragMoveCursor ) {
512 setCursor( QCursor( Qt::DragMoveCursor ) );
513 }
514 }
515}
516
517bool PatternEditor::notesMatchExactly( Note *pNoteA, Note *pNoteB ) const {
518 return ( pNoteA->match( pNoteB->get_instrument(), pNoteB->get_key(), pNoteB->get_octave() )
519 && pNoteA->get_position() == pNoteB->get_position()
520 && pNoteA->get_velocity() == pNoteB->get_velocity()
521 && pNoteA->getPan() == pNoteB->getPan()
522 && pNoteA->get_lead_lag() == pNoteB->get_lead_lag()
523 && pNoteA->get_probability() == pNoteB->get_probability() );
524}
525
526bool PatternEditor::checkDeselectElements( std::vector<SelectionIndex> &elements )
527{
528 if ( m_pPattern == nullptr ) {
529 return false;
530 }
531
532 auto pCommonStrings = HydrogenApp::get_instance()->getCommonStrings();
533
534 // Hydrogen *pH = Hydrogen::get_instance();
535 std::set< Note *> duplicates;
536 for ( Note *pNote : elements ) {
537 if ( duplicates.find( pNote ) != duplicates.end() ) {
538 // Already marked pNote as a duplicate of some other pNote. Skip it.
539 continue;
540 }
541 FOREACH_NOTE_CST_IT_BOUND_END( m_pPattern->get_notes(), it, pNote->get_position() ) {
542 // Duplicate note of a selected note is anything occupying the same position. Multiple notes
543 // sharing the same location might be selected; we count these as duplicates too. They will appear
544 // in both the duplicates and selection lists.
545 if ( it->second != pNote && pNote->match( it->second ) ) {
546 duplicates.insert( it->second );
547 }
548 }
549 }
550 if ( !duplicates.empty() ) {
551 Preferences *pPreferences = Preferences::get_instance();
552 bool bOk = true;
553
554 if ( pPreferences->getShowNoteOverwriteWarning() ) {
555 m_selection.cancelGesture();
556 QString sMsg ( tr( "Placing these notes here will overwrite %1 duplicate notes." ) );
557 QMessageBox messageBox ( QMessageBox::Warning, "Hydrogen", sMsg.arg( duplicates.size() ),
558 QMessageBox::Cancel | QMessageBox::Ok, this );
559 messageBox.setCheckBox( new QCheckBox( pCommonStrings->getMutableDialog() ) );
560 messageBox.checkBox()->setChecked( false );
561 bOk = messageBox.exec() == QMessageBox::Ok;
562 if ( messageBox.checkBox()->isChecked() ) {
563 pPreferences->setShowNoteOverwriteWarning( false );
564 }
565 }
566
567 if ( bOk ) {
568 QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack;
569
570 std::vector< Note *>overwritten;
571 for ( Note *pNote : duplicates ) {
572 overwritten.push_back( pNote );
573 }
574 pUndo->push( new SE_deselectAndOverwriteNotesAction( elements, overwritten ) );
575
576 } else {
577 return false;
578 }
579 }
580 return true;
581}
582
583
584void PatternEditor::deselectAndOverwriteNotes( std::vector< H2Core::Note *> &selected,
585 std::vector< H2Core::Note *> &overwritten )
586{
587 if ( m_pPattern == nullptr ) {
588 return;
589 }
590
591 // Iterate over all the notes in 'selected' and 'overwrite' by erasing any *other* notes occupying the
592 // same position.
593 m_pAudioEngine->lock( RIGHT_HERE );
594 Pattern::notes_t *pNotes = const_cast< Pattern::notes_t *>( m_pPattern->get_notes() );
595 for ( auto pSelectedNote : selected ) {
596 m_selection.removeFromSelection( pSelectedNote, /* bCheck=*/false );
597 bool bFoundExact = false;
598 int nPosition = pSelectedNote->get_position();
599 for ( auto it = pNotes->lower_bound( nPosition ); it != pNotes->end() && it->first == nPosition; ) {
600 Note *pNote = it->second;
601 if ( !bFoundExact && notesMatchExactly( pNote, pSelectedNote ) ) {
602 // Found an exact match. We keep this.
603 bFoundExact = true;
604 ++it;
605 } else if ( pSelectedNote->match( pNote ) && pNote->get_position() == pSelectedNote->get_position() ) {
606 // Something else occupying the same position (which may or may not be an exact duplicate)
607 it = pNotes->erase( it );
608 delete pNote;
609 } else {
610 // Any other note
611 ++it;
612 }
613 }
614 }
616 m_pAudioEngine->unlock();
617}
618
619
620void PatternEditor::undoDeselectAndOverwriteNotes( std::vector< H2Core::Note *> &selected,
621 std::vector< H2Core::Note *> &overwritten )
622{
623 if ( m_pPattern == nullptr ) {
624 return;
625 }
626
627 // Restore previously-overwritten notes, and select notes that were selected before.
628 m_selection.clearSelection( /* bCheck=*/false );
629 m_pAudioEngine->lock( RIGHT_HERE );
630 for ( auto pNote : overwritten ) {
631 Note *pNewNote = new Note( pNote );
632 m_pPattern->insert_note( pNewNote );
633 }
634 // Select the previously-selected notes
635 for ( auto pNote : selected ) {
636 FOREACH_NOTE_CST_IT_BOUND_END( m_pPattern->get_notes(), it, pNote->get_position() ) {
637 if ( notesMatchExactly( it->second, pNote ) ) {
638 m_selection.addToSelection( it->second );
639 break;
640 }
641 }
642 }
644 m_pAudioEngine->unlock();
645 m_pPatternEditorPanel->updateEditors();
646}
647
648
650 Hydrogen *pHydrogen = Hydrogen::get_instance();
651 std::shared_ptr<Song> pSong = pHydrogen->getSong();
652
653 m_pPattern = nullptr;
655
656 if ( pSong != nullptr ) {
657 PatternList *pPatternList = pSong->getPatternList();
659 m_pPattern = pPatternList->get( m_nSelectedPatternNumber );
660 }
661 }
662}
663
664
666 QPoint rawOffset = m_selection.movingOffset();
667 // Quantize offset to multiples of m_nGrid{Width,Height}
668 int nQuantX = m_fGridWidth, nQuantY = m_nGridHeight;
669 float nFactor = 1;
670 if ( ! m_bFineGrained ) {
671 nFactor = granularity();
672 nQuantX = m_fGridWidth * nFactor;
673 }
674 int x_bias = nQuantX / 2, y_bias = nQuantY / 2;
675 if ( rawOffset.y() < 0 ) {
676 y_bias = -y_bias;
677 }
678 if ( rawOffset.x() < 0 ) {
679 x_bias = -x_bias;
680 }
681 int x_off = (rawOffset.x() + x_bias) / nQuantX;
682 int y_off = (rawOffset.y() + y_bias) / nQuantY;
683 return QPoint( nFactor * x_off, y_off);
684}
685
686
688void PatternEditor::drawGridLines( QPainter &p, Qt::PenStyle style ) const
689{
691 const std::vector<QColor> colorsActive = {
692 QColor( pPref->getColorTheme()->m_patternEditor_line1Color ),
693 QColor( pPref->getColorTheme()->m_patternEditor_line2Color ),
694 QColor( pPref->getColorTheme()->m_patternEditor_line3Color ),
695 QColor( pPref->getColorTheme()->m_patternEditor_line4Color ),
696 QColor( pPref->getColorTheme()->m_patternEditor_line5Color ),
697 };
698 const std::vector<QColor> colorsInactive = {
699 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 170 ) ),
700 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 190 ) ),
701 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 210 ) ),
702 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 230 ) ),
703 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 250 ) ),
704 };
705
706 if ( !m_bUseTriplets ) {
707
708 // Draw vertical lines. To minimise pen colour changes (and
709 // avoid unnecessary division operations), we draw them in
710 // multiple passes, of successively finer spacing (and
711 // advancing the colour selection at each refinement) until
712 // we've drawn enough to satisfy the resolution setting.
713 //
714 // The drawing sequence looks something like:
715 // | | | | - first pass, all 1/4 notes
716 // | : | : | : | : - second pass, odd 1/8th notes
717 // | . : . | . : . | . : . | . : . - third pass, odd 1/16th notes
718
719 // First, quarter note markers. All the quarter note markers must be
720 // drawn. These will be drawn on all resolutions.
721 const int nRes = 4;
722 float fStep = MAX_NOTES / nRes * m_fGridWidth;
723 float x = PatternEditor::nMargin;
724 p.setPen( QPen( colorsActive[ 0 ], 1, style ) );
725 while ( x < m_nActiveWidth ) {
726 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
727 x += fStep;
728 }
729
730 p.setPen( QPen( colorsInactive[ 0 ], 1, style ) );
731 while ( x < m_nEditorWidth ) {
732 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
733 x += fStep;
734 }
735
736 // Resolution 4 was already taken into account above;
737 std::vector<int> availableResolutions = { 8, 16, 32, 64, MAX_NOTES };
738
739 // For each successive set of finer-spaced lines, the even
740 // lines will have already been drawn at the previous coarser
741 // pitch, so only the odd numbered lines need to be drawn.
742 int nColour = 1;
743 for ( int nnRes : availableResolutions ) {
744 if ( nnRes > m_nResolution ) {
745 break;
746 }
747
748 fStep = MAX_NOTES / nnRes * m_fGridWidth;
749 float x = PatternEditor::nMargin + fStep;
750 p.setPen( QPen( colorsActive[ std::min( nColour, static_cast<int>(colorsActive.size()) - 1 ) ],
751 1, style ) );
752
753 if ( nnRes != MAX_NOTES ) {
754 // With each increase of resolution 1/4 -> 1/8 -> 1/16 -> 1/32
755 // -> 1/64 the number of available notes doubles and all we need
756 // to do is to draw another grid line right between two existing
757 // ones.
758 while ( x < m_nActiveWidth + fStep ) {
759 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
760 x += fStep * 2;
761 }
762 }
763 else {
764 // When turning resolution off, things get a bit more tricky.
765 // Between 1/64 -> 1/192 (1/MAX_NOTES) the space between
766 // existing grid line will be filled by two instead of one new
767 // line.
768 while ( x < m_nActiveWidth + fStep ) {
769 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
770 x += fStep;
771 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
772 x += fStep * 2;
773 }
774 }
775
776 p.setPen( QPen( colorsInactive[ std::min( nColour, static_cast<int>(colorsInactive.size()) - 1 ) ],
777 1, style ) );
778 if ( nnRes != MAX_NOTES ) {
779 while ( x < m_nEditorWidth ) {
780 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
781 x += fStep * 2;
782 }
783 }
784 else {
785 while ( x < m_nEditorWidth ) {
786 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
787 x += fStep;
788 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
789 x += fStep * 2;
790 }
791 }
792
793 nColour++;
794 }
795
796 } else {
797
798 // Triplet style markers, we only differentiate colours on the
799 // first of every triplet.
800 float fStep = granularity() * m_fGridWidth;
801 float x = PatternEditor::nMargin;
802 p.setPen( QPen( colorsActive[ 0 ], 1, style ) );
803 while ( x < m_nActiveWidth ) {
804 p.drawLine(x, 1, x, m_nEditorHeight - 1);
805 x += fStep * 3;
806 }
807
808 p.setPen( QPen( colorsInactive[ 0 ], 1, style ) );
809 while ( x < m_nEditorWidth ) {
810 p.drawLine(x, 1, x, m_nEditorHeight - 1);
811 x += fStep * 3;
812 }
813
814 // Second and third marks
815 x = PatternEditor::nMargin + fStep;
816 p.setPen( QPen( colorsActive[ 2 ], 1, style ) );
817 while ( x < m_nActiveWidth + fStep ) {
818 p.drawLine(x, 1, x, m_nEditorHeight - 1);
819 p.drawLine(x + fStep, 1, x + fStep, m_nEditorHeight - 1);
820 x += fStep * 3;
821 }
822
823 p.setPen( QPen( colorsInactive[ 2 ], 1, style ) );
824 while ( x < m_nEditorWidth ) {
825 p.drawLine(x, 1, x, m_nEditorHeight - 1);
826 p.drawLine(x + fStep, 1, x + fStep, m_nEditorHeight - 1);
827 x += fStep * 3;
828 }
829 }
830
831}
832
833
835
837
838 if ( hasFocus() ) {
839 const QColor selectHighlightColor( pPref->getColorTheme()->m_selectionHighlightColor );
840 return selectHighlightColor;
841 } else {
842 const QColor selectInactiveColor( pPref->getColorTheme()->m_selectionInactiveColor );
843 return selectInactiveColor;
844 }
845}
846
847
852{
853 if ( m_pPattern == nullptr ) {
854 return;
855 }
856
857 // Rebuild selection from valid notes.
858 std::set<Note *> valid;
859 std::vector< Note *> invalidated;
860 FOREACH_NOTE_CST_IT_BEGIN_END(m_pPattern->get_notes(), it) {
861 if ( m_selection.isSelected( it->second ) ) {
862 valid.insert( it->second );
863 }
864 }
865 for (auto i : m_selection ) {
866 if ( valid.find(i) == valid.end()) {
867 // Keep the note to invalidate, but don't remove from the selection while walking the selection
868 // set.
869 invalidated.push_back( i );
870 }
871 }
872 for ( auto i : invalidated ) {
873 m_selection.removeFromSelection( i, /* bCheck=*/false );
874 }
875}
876
877void PatternEditor::scrolled( int nValue ) {
878 UNUSED( nValue );
879 update();
880}
881
882#ifdef H2CORE_HAVE_QT6
883void PatternEditor::enterEvent( QEnterEvent *ev ) {
884#else
885void PatternEditor::enterEvent( QEvent *ev ) {
886#endif
887 UNUSED( ev );
888 m_bEntered = true;
889 update();
890}
891
892void PatternEditor::leaveEvent( QEvent *ev ) {
893 UNUSED( ev );
894 m_bEntered = false;
895 update();
896}
897
898void PatternEditor::focusInEvent( QFocusEvent *ev ) {
899 UNUSED( ev );
900 if ( ev->reason() == Qt::TabFocusReason || ev->reason() == Qt::BacktabFocusReason ) {
902 m_pPatternEditorPanel->ensureCursorVisible();
903 }
904 if ( ! HydrogenApp::get_instance()->hideKeyboardCursor() ) {
905 // Immediate update to prevent visual delay.
906 m_pPatternEditorPanel->getPatternEditorRuler()->update();
907 m_pPatternEditorPanel->getInstrumentList()->update();
908 }
909
910 // Update to show the focus border highlight
911 update();
912}
913
914void PatternEditor::focusOutEvent( QFocusEvent *ev ) {
915 UNUSED( ev );
916 if ( ! HydrogenApp::get_instance()->hideKeyboardCursor() ) {
917 m_pPatternEditorPanel->getPatternEditorRuler()->update();
918 m_pPatternEditorPanel->getInstrumentList()->update();
919 }
920
921 // Update to remove the focus border highlight
922 update();
923}
924
928
931
935std::vector< Pattern *> PatternEditor::getPatternsToShow( void )
936{
937 Hydrogen *pHydrogen = Hydrogen::get_instance();
938 std::vector<Pattern *> patterns;
939
940 // When using song mode without the pattern editor being locked
941 // only the current pattern will be shown. In every other base
942 // remaining playing patterns not selected by the user are added
943 // as well.
944 if ( ! ( pHydrogen->getMode() == Song::Mode::Song &&
945 ! pHydrogen->isPatternEditorLocked() ) ) {
946 m_pAudioEngine->lock( RIGHT_HERE );
947 if ( m_pAudioEngine->getPlayingPatterns()->size() > 0 ) {
948 std::set< Pattern *> patternSet;
949
950 std::vector<const PatternList*> patternLists;
951 patternLists.push_back( m_pAudioEngine->getPlayingPatterns() );
952 if ( pHydrogen->getPatternMode() == Song::PatternMode::Stacked ) {
953 patternLists.push_back( m_pAudioEngine->getNextPatterns() );
954 }
955
956 for ( const PatternList *pPatternList : patternLists ) {
957 for ( int i = 0; i < pPatternList->size(); i++) {
958 Pattern *pPattern = pPatternList->get( i );
959 if ( pPattern != m_pPattern ) {
960 patternSet.insert( pPattern );
961 }
962 }
963 }
964 for ( Pattern *pPattern : patternSet ) {
965 patterns.push_back( pPattern );
966 }
967 }
968 m_pAudioEngine->unlock();
969 }
970 else if ( m_pPattern != nullptr &&
971 pHydrogen->getMode() == Song::Mode::Song &&
972 m_pPattern->get_virtual_patterns()->size() > 0 ) {
973 // A virtual pattern was selected in song mode without the
974 // pattern editor being locked. Virtual patterns in selected
975 // pattern mode are handled using the playing pattern above.
976 for ( const auto ppVirtualPattern : *m_pPattern ) {
977 patterns.push_back( ppVirtualPattern );
978 }
979 }
980
981
982 if ( m_pPattern != nullptr ) {
983 patterns.push_back( m_pPattern );
984 }
985
986 return patterns;
987}
988
990 auto pHydrogen = H2Core::Hydrogen::get_instance();
991
992 if ( pHydrogen->getPatternMode() == Song::PatternMode::Stacked ||
993 ( pPattern != nullptr && pPattern->isVirtual() ) ||
994 ( pHydrogen->getMode() == Song::Mode::Song &&
995 pHydrogen->isPatternEditorLocked() ) ) {
996 return true;
997 }
998
999 return false;
1000}
1001
1003 auto pHydrogen = H2Core::Hydrogen::get_instance();
1004
1005 if ( m_pPattern != nullptr ) {
1007 m_pPattern->get_length();
1008
1009 // In case there are other patterns playing which are longer
1010 // than the selected one, their notes will be placed using a
1011 // different color set between m_nActiveWidth and
1012 // m_nEditorWidth.
1013 if ( pHydrogen->getMode() == Song::Mode::Song &&
1014 m_pPattern != nullptr && m_pPattern->isVirtual() &&
1015 ! pHydrogen->isPatternEditorLocked() ) {
1018 m_pPattern->longestVirtualPatternLength() + 1,
1019 static_cast<float>(m_nActiveWidth) );
1020 }
1021 else if ( isUsingAdditionalPatterns( m_pPattern ) ) {
1024 pHydrogen->getAudioEngine()->getPlayingPatterns()->longest_pattern_length( false ) + 1,
1025 static_cast<float>(m_nActiveWidth) );
1026 }
1027 else {
1029 }
1030 }
1031 else {
1034 }
1035}
1036
1038{
1039 // May need to draw (or hide) other background patterns
1040 update();
1041}
1042
1044{
1045 UNUSED( nValue );
1046 // May need to draw (or hide) other background patterns
1047 update();
1048}
1049
1051 if ( m_nTick == (int)fTick ) {
1052 return;
1053 }
1054
1055 float fDiff = m_fGridWidth * (fTick - m_nTick);
1056
1057 m_nTick = fTick;
1058
1059 int nOffset = Skin::getPlayheadShaftOffset();
1060 int nX = static_cast<int>(static_cast<float>(PatternEditor::nMargin) +
1061 static_cast<float>(m_nTick) *
1062 m_fGridWidth );
1063
1064 QRect updateRect( nX -2, 0, 4 + Skin::nPlayheadWidth, height() );
1065 update( updateRect );
1066 if ( fDiff > 1.0 || fDiff < -1.0 ) {
1067 // New cursor is far enough away from the old one that the single update rect won't cover both. So
1068 // update at the old location as well.
1069 updateRect.translate( -fDiff, 0 );
1070 update( updateRect );
1071 }
1072}
1073
1075 if( pNote != nullptr ){
1076 m_nOldLength = pNote->get_length();
1077 //needed to undo note properties
1078 m_fOldVelocity = pNote->get_velocity();
1079 m_fOldPan = pNote->getPan();
1080
1081 m_fOldLeadLag = pNote->get_lead_lag();
1082
1084 m_fPan = m_fOldPan;
1086 }
1087 else {
1088 m_nOldLength = -1;
1089 }
1090}
1091
1092void PatternEditor::mouseDragStartEvent( QMouseEvent *ev ) {
1093
1094 auto pEv = static_cast<MouseEvent*>( ev );
1095
1096 auto pHydrogenApp = HydrogenApp::get_instance();
1097 auto pHydrogen = Hydrogen::get_instance();
1098
1099 // Move cursor.
1100 int nColumn = getColumn( pEv->position().x() );
1101 m_pPatternEditorPanel->setCursorPosition( nColumn );
1102
1103 // Hide cursor.
1104 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
1105
1106 pHydrogenApp->setHideKeyboardCursor( true );
1107
1108 // Cursor either just got hidden or was moved.
1109 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
1110 // Immediate update to prevent visual delay.
1111 m_pPatternEditorPanel->getInstrumentList()->repaintInstrumentLines();
1112 m_pPatternEditorPanel->getPatternEditorRuler()->update();
1113 }
1114
1115 int nRealColumn = 0;
1116
1117 if ( ev->button() == Qt::RightButton ) {
1118
1119 int nPressedLine = std::floor(static_cast<float>(pEv->position().y()) /
1120 static_cast<float>(m_nGridHeight));
1121 int nSelectedInstrumentNumber = pHydrogen->getSelectedInstrumentNumber();
1122
1123 if( pEv->position().x() > PatternEditor::nMargin ) {
1124 nRealColumn =
1125 static_cast<int>(std::floor(
1126 static_cast<float>((pEv->position().x() - PatternEditor::nMargin)) /
1127 m_fGridWidth));
1128 }
1129
1130 // Needed for undo changes in the note length
1131 m_nOldPoint = pEv->position().y();
1132 m_nRealColumn = nRealColumn;
1133 m_nColumn = nColumn;
1134 m_nPressedLine = nPressedLine;
1135 m_nSelectedInstrumentNumber = pHydrogen->getSelectedInstrumentNumber();
1136 }
1137
1138}
1139
1141
1142 if ( m_pPattern == nullptr || m_pDraggedNote == nullptr ) {
1143 return;
1144 }
1145
1146 if ( m_pDraggedNote->get_note_off() ) {
1147 return;
1148 }
1149
1150 auto pEv = static_cast<MouseEvent*>( ev );
1151
1152 int nTickColumn = getColumn( pEv->position().x() );
1153
1154 m_pAudioEngine->lock( RIGHT_HERE );
1155 int nLen = nTickColumn - m_pDraggedNote->get_position();
1156
1157 if ( nLen <= 0 ) {
1158 nLen = -1;
1159 }
1160
1161 float fNotePitch = m_pDraggedNote->get_notekey_pitch();
1162 float fStep = 0;
1163 if ( nLen > -1 ){
1164 fStep = Note::pitchToFrequency( ( double )fNotePitch );
1165 } else {
1166 fStep = 1.0;
1167 }
1168 m_pDraggedNote->set_length( nLen * fStep);
1169
1170 m_mode = m_pPatternEditorPanel->getNotePropertiesMode();
1171
1172 // edit note property. We do not support the note key property.
1173 if ( m_mode != Mode::NoteKey ) {
1174
1175 float fValue = 0.0;
1176 if ( m_mode == Mode::Velocity ) {
1177 fValue = m_pDraggedNote->get_velocity();
1178 }
1179 else if ( m_mode == Mode::Pan ) {
1180 fValue = m_pDraggedNote->getPanWithRangeFrom0To1();
1181 }
1182 else if ( m_mode == Mode::LeadLag ) {
1183 fValue = ( m_pDraggedNote->get_lead_lag() - 1.0 ) / -2.0 ;
1184 }
1185 else if ( m_mode == Mode::Probability ) {
1186 fValue = m_pDraggedNote->get_probability();
1187 }
1188
1189 float fMoveY = m_nOldPoint - pEv->position().y();
1190 fValue = fValue + (fMoveY / 100);
1191 if ( fValue > 1 ) {
1192 fValue = 1;
1193 }
1194 else if ( fValue < 0.0 ) {
1195 fValue = 0.0;
1196 }
1197
1198 if ( m_mode == Mode::Velocity ) {
1199 m_pDraggedNote->set_velocity( fValue );
1200 m_fVelocity = fValue;
1201 }
1202 else if ( m_mode == Mode::Pan ) {
1203 m_pDraggedNote->setPanWithRangeFrom0To1( fValue );
1204 m_fPan = m_pDraggedNote->getPan();
1205 }
1206 else if ( m_mode == Mode::LeadLag ) {
1207 m_pDraggedNote->set_lead_lag( ( fValue * -2.0 ) + 1.0 );
1208 m_fLeadLag = ( fValue * -2.0 ) + 1.0;
1209 }
1210 else if ( m_mode == Mode::Probability ) {
1211 m_pDraggedNote->set_probability( fValue );
1212 m_fProbability = fValue;
1213 }
1214
1216
1217 m_nOldPoint = pEv->position().y();
1218 }
1219
1220 m_pAudioEngine->unlock(); // unlock the audio engine
1222
1223 if ( m_pPatternEditorPanel != nullptr ) {
1224 m_pPatternEditorPanel->updateEditors( true );
1225 }
1226}
1227
1228void PatternEditor::mouseDragEndEvent( QMouseEvent* ev ) {
1229
1230 UNUSED( ev );
1231 unsetCursor();
1232
1233 if ( m_pPattern == nullptr || m_nSelectedPatternNumber == -1 ) {
1234 return;
1235 }
1236
1237 if ( m_pDraggedNote == nullptr || m_pDraggedNote->get_note_off() ) {
1238 return;
1239 }
1240
1241 if ( m_pDraggedNote->get_length() != m_nOldLength ) {
1242 SE_editNoteLengthAction *action =
1243 new SE_editNoteLengthAction( m_pDraggedNote->get_position(),
1244 m_pDraggedNote->get_position(),
1245 m_nRow,
1246 m_pDraggedNote->get_length(),
1250 m_editor );
1251 HydrogenApp::get_instance()->m_pUndoStack->push( action );
1252 }
1253
1254
1255 if( m_fVelocity == m_fOldVelocity &&
1256 m_fOldPan == m_fPan &&
1259 return;
1260 }
1261
1263 new SE_editNotePropertiesAction( m_pDraggedNote->get_position(),
1264 m_pDraggedNote->get_position(),
1265 m_nRow,
1268 m_mode,
1269 m_editor,
1272 m_fPan,
1273 m_fOldPan,
1274 m_fLeadLag,
1278 HydrogenApp::get_instance()->m_pUndoStack->push( action );
1279}
1280
1282 int nRealColumn,
1283 int nRow,
1284 int nLength,
1285 int nSelectedPatternNumber,
1286 int nSelectedInstrumentnumber,
1287 Editor editor)
1288{
1289
1290 auto pHydrogen = Hydrogen::get_instance();
1291 auto pSong = pHydrogen->getSong();
1292 auto pPatternList = pSong->getPatternList();
1293
1294 H2Core::Pattern* pPattern = nullptr;
1295 if ( nSelectedPatternNumber != -1 &&
1296 nSelectedPatternNumber < pPatternList->size() ) {
1297 pPattern = pPatternList->get( nSelectedPatternNumber );
1298 }
1299
1300 if ( pPattern == nullptr ) {
1301 return;
1302 }
1303
1304 m_pAudioEngine->lock( RIGHT_HERE );
1305
1306 // Find the note to edit
1307 Note* pDraggedNote = nullptr;
1308 if ( editor == Editor::PianoRoll ) {
1309 auto pSelectedInstrument =
1310 pSong->getInstrumentList()->get( nSelectedInstrumentnumber );
1311 if ( pSelectedInstrument == nullptr ) {
1312 ERRORLOG( "No instrument selected" );
1313 m_pAudioEngine->unlock();
1314 return;
1315 }
1316
1317 Note::Octave pressedOctave = Note::pitchToOctave( lineToPitch( nRow ) );
1318 Note::Key pressedNoteKey = Note::pitchToKey( lineToPitch( nRow ) );
1319
1320 auto pDraggedNote = pPattern->find_note( nColumn, nRealColumn,
1321 pSelectedInstrument,
1322 pressedNoteKey, pressedOctave,
1323 false );
1324 }
1325 else if ( editor == Editor::DrumPattern ) {
1326 auto pSelectedInstrument = pSong->getInstrumentList()->get( nRow );
1327 if ( pSelectedInstrument == nullptr ) {
1328 ERRORLOG( "No instrument selected" );
1329 m_pAudioEngine->unlock();
1330 return;
1331 }
1332 pDraggedNote = pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument, false );
1333 }
1334 else {
1335 ERRORLOG( QString( "Unsupported editor [%1]" )
1336 .arg( static_cast<int>(editor) ) );
1337 m_pAudioEngine->unlock();
1338 return;
1339 }
1340
1341 if ( pDraggedNote != nullptr ){
1342 pDraggedNote->set_length( nLength );
1343 }
1344
1345 m_pAudioEngine->unlock();
1346
1347 pHydrogen->setIsModified( true );
1348
1349 if ( m_pPatternEditorPanel != nullptr ) {
1350 m_pPatternEditorPanel->updateEditors( true );
1351 }
1352}
1353
1354
1356 int nRealColumn,
1357 int nRow,
1358 int nSelectedPatternNumber,
1359 int nSelectedInstrumentNumber,
1360 Mode mode,
1361 Editor editor,
1362 float fVelocity,
1363 float fPan,
1364 float fLeadLag,
1365 float fProbability )
1366{
1367
1368 auto pHydrogen = Hydrogen::get_instance();
1369 auto pSong = pHydrogen->getSong();
1370 auto pPatternList = pSong->getPatternList();
1371
1372 H2Core::Pattern* pPattern = nullptr;
1373 if ( nSelectedPatternNumber != -1 &&
1374 nSelectedPatternNumber < pPatternList->size() ) {
1375 pPattern = pPatternList->get( nSelectedPatternNumber );
1376 }
1377
1378 if ( pPattern == nullptr ) {
1379 return;
1380 }
1381
1382 m_pAudioEngine->lock( RIGHT_HERE );
1383
1384 // Find the note to edit
1385 Note* pDraggedNote = nullptr;
1386 if ( editor == Editor::PianoRoll ) {
1387
1388 auto pSelectedInstrument =
1389 pSong->getInstrumentList()->get( nSelectedInstrumentNumber );
1390 if ( pSelectedInstrument == nullptr ) {
1391 ERRORLOG( "No instrument selected" );
1392 m_pAudioEngine->unlock();
1393 return;
1394 }
1395
1396 Note::Octave pressedOctave = Note::pitchToOctave( lineToPitch( nRow ) );
1397 Note::Key pressedNoteKey = Note::pitchToKey( lineToPitch( nRow ) );
1398
1399 pDraggedNote = pPattern->find_note( nColumn, nRealColumn,
1400 pSelectedInstrument,
1401 pressedNoteKey, pressedOctave,
1402 false );
1403 }
1404 else if ( editor == Editor::DrumPattern ) {
1405 auto pSelectedInstrument = pSong->getInstrumentList()->get( nRow );
1406 if ( pSelectedInstrument == nullptr ) {
1407 ERRORLOG( "No instrument selected" );
1408 m_pAudioEngine->unlock();
1409 return;
1410 }
1411 pDraggedNote = pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument, false );
1412 }
1413 else {
1414 ERRORLOG( QString( "Unsupported editor [%1]" )
1415 .arg( static_cast<int>(editor) ) );
1416 m_pAudioEngine->unlock();
1417 return;
1418 }
1419
1420 bool bValueChanged = true;
1421
1422 if ( pDraggedNote != nullptr ){
1423 switch ( mode ) {
1424 case Mode::Velocity:
1425 pDraggedNote->set_velocity( fVelocity );
1426 break;
1427 case Mode::Pan:
1428 pDraggedNote->setPan( fPan );
1429 break;
1430 case Mode::LeadLag:
1431 pDraggedNote->set_lead_lag( fLeadLag );
1432 break;
1433 case Mode::Probability:
1434 pDraggedNote->set_probability( fProbability );
1435 break;
1436 }
1437 bValueChanged = true;
1438 PatternEditor::triggerStatusMessage( pDraggedNote, mode );
1439 } else {
1440 ERRORLOG("note could not be found");
1441 }
1442
1443 m_pAudioEngine->unlock();
1444
1445 if ( bValueChanged &&
1446 m_pPatternEditorPanel != nullptr ) {
1447 pHydrogen->setIsModified( true );
1448 m_pPatternEditorPanel->updateEditors( true );
1449 }
1450}
1451
1452
1454 QString s;
1455
1456 switch ( mode ) {
1458 s = "Velocity";
1459 break;
1461 s = "Pan";
1462 break;
1464 s = "LeadLag";
1465 break;
1467 s = "NoteKey";
1468 break;
1470 s = "Probability";
1471 break;
1472 default:
1473 s = QString( "Unknown mode [%1]" ).arg( static_cast<int>(mode) ) ;
1474 break;
1475 }
1476
1477 return s;
1478}
1479
1481 QString s;
1482 QString sCaller( _class_name() );
1483 QString sUnit( tr( "ticks" ) );
1484 float fValue;
1485
1486 switch ( mode ) {
1488 if ( ! pNote->get_note_off() ) {
1489 s = QString( tr( "Set note velocity" ) )
1490 .append( QString( ": [%1]")
1491 .arg( pNote->get_velocity(), 2, 'f', 2 ) );
1492 sCaller.append( ":Velocity" );
1493 }
1494 break;
1495
1497 if ( ! pNote->get_note_off() ) {
1498
1499 // Round the pan to not miss the center due to fluctuations
1500 fValue = pNote->getPan() * 100;
1501 fValue = std::round( fValue );
1502 fValue = fValue / 100;
1503
1504 if ( fValue > 0.0 ) {
1505 s = QString( tr( "Note panned to the right by" ) ).
1506 append( QString( ": [%1]" ).arg( fValue / 2, 2, 'f', 2 ) );
1507 } else if ( fValue < 0.0 ) {
1508 s = QString( tr( "Note panned to the left by" ) ).
1509 append( QString( ": [%1]" ).arg( -1 * fValue / 2, 2, 'f', 2 ) );
1510 } else {
1511 s = QString( tr( "Note centered" ) );
1512 }
1513 sCaller.append( ":Pan" );
1514 }
1515 break;
1516
1518 // Round the pan to not miss the center due to fluctuations
1519 fValue = pNote->get_lead_lag() * 100;
1520 fValue = std::round( fValue );
1521 fValue = fValue / 100;
1522 if ( fValue < 0.0 ) {
1523 s = QString( tr( "Leading beat by" ) )
1524 .append( QString( ": [%1] " )
1525 .arg( fValue * -1 *
1526 AudioEngine::getLeadLagInTicks() , 2, 'f', 2 ) )
1527 .append( sUnit );
1528 }
1529 else if ( fValue > 0.0 ) {
1530 s = QString( tr( "Lagging beat by" ) )
1531 .append( QString( ": [%1] " )
1532 .arg( fValue *
1533 AudioEngine::getLeadLagInTicks() , 2, 'f', 2 ) )
1534 .append( sUnit );
1535 }
1536 else {
1537 s = tr( "Note on beat" );
1538 }
1539 sCaller.append( ":LeadLag" );
1540 break;
1541
1543 s = QString( tr( "Set pitch" ) ).append( ": " ).append( tr( "key" ) )
1544 .append( QString( " [%1], " ).arg( Note::KeyToQString( pNote->get_key() ) ) )
1545 .append( tr( "octave" ) )
1546 .append( QString( ": [%1]" ).arg( pNote->get_octave() ) );
1547 sCaller.append( ":NoteKey" );
1548 break;
1549
1551 if ( ! pNote->get_note_off() ) {
1552 s = tr( "Set note probability to" )
1553 .append( QString( ": [%1]" ).arg( pNote->get_probability(), 2, 'f', 2 ) );
1554 }
1555 sCaller.append( ":Probability" );
1556 break;
1557
1558 default:
1560 }
1561
1562 if ( ! s.isEmpty() ) {
1564 }
1565}
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
Definition AudioEngine.h:61
#define ERRORLOG(x)
Definition Object.h:242
#define FOREACH_NOTE_CST_IT_BOUND_END(_notes, _it, _bound)
Iterate over all notes in column _bound in an immutable way.
Definition Pattern.h:272
#define FOREACH_NOTE_CST_IT_BEGIN_END(_notes, _it)
Iterate over all provided notes in an immutable way.
Definition Pattern.h:268
#define FOREACH_NOTE_CST_IT_BEGIN_LENGTH(_notes, _it, _pattern)
Iterate over all accessible notes between position 0 and length of _pattern in an immutable way.
Definition Pattern.h:285
static double getLeadLagInTicks()
Maximum lead lag factor in ticks.
static const char * _class_name()
return the class name
Definition Object.h:78
Hydrogen Audio Engine.
Definition Hydrogen.h:54
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
int getSelectedInstrumentNumber() const
Definition Hydrogen.h:678
Song::Mode getMode() const
int getSelectedPatternNumber() const
Definition Hydrogen.h:674
void setSelectedInstrumentNumber(int nInstrument, bool bTriggerEvent=true)
Definition Hydrogen.cpp:944
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
Song::PatternMode getPatternMode() const
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:663
void setIsModified(bool bIsModified)
Wrapper around Song::setIsModified() that checks whether a song is set.
bool isPatternEditorLocked() const
Convenience function checking whether using the Pattern Editor is locked in the song settings and the...
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:101
int get_position() const
__position accessor
Definition Note.h:534
static Key pitchToKey(int nPitch)
Definition Note.h:399
void set_length(int value)
__length setter
Definition Note.h:554
void set_lead_lag(float value)
__lead_lag setter
Definition Note.cpp:148
static QString KeyToQString(Key key)
Definition Note.cpp:650
static Octave pitchToOctave(int nPitch)
Definition Note.h:392
Octave get_octave()
__octave accessor
Definition Note.h:676
void set_probability(float value)
Definition Note.h:614
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
Definition Note.h:499
float get_lead_lag() const
__lead_lag accessor
Definition Note.h:549
int get_length() const
__length accessor
Definition Note.h:559
float get_velocity() const
__velocity accessor
Definition Note.h:539
Key
possible keys
Definition Note.h:105
bool get_note_off() const
__note_off accessor
Definition Note.h:579
void setPan(float val)
set pan of the note.
Definition Note.cpp:153
void set_velocity(float value)
__velocity setter
Definition Note.cpp:143
static double pitchToFrequency(double fPitch)
Convert a logarithmic pitch-space value in semitones to a frequency-domain value.
Definition Note.h:387
Key get_key()
__key accessor
Definition Note.h:671
float get_probability() const
Definition Note.h:609
Octave
possible octaves
Definition Note.h:109
float getPan() const
get pan of the note.
Definition Note.h:544
bool match(std::shared_ptr< Instrument > instrument, Key key, Octave octave) const
return true if instrument, key and octave matches with internal
Definition Note.h:713
PatternList is a collection of patterns.
Definition PatternList.h:43
Pattern * get(int idx)
get a pattern from the list
Pattern class is a Note container.
Definition Pattern.h:46
std::multimap< int, Note * > notes_t
< multimap note type
Definition Pattern.h:50
Note * find_note(int idx_a, int idx_b, std::shared_ptr< Instrument > instrument, bool strict=true) const
search for a note at a given index within __notes which correspond to the given arguments
Definition Pattern.cpp:212
bool isVirtual() const
Whether the pattern holds at least one virtual pattern.
Definition Pattern.cpp:363
Manager for User Preferences File (singleton)
Definition Preferences.h:79
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
void setShowNoteOverwriteWarning(bool bValue)
bool getShowNoteOverwriteWarning()
Changes
Bitwise or-able options showing which part of the Preferences were altered using the PreferencesDialo...
@ Colors
At least one of the colors has changed.
@ Stacked
An arbitrary number of pattern can be played.
Definition Song.h:103
XMLDoc is a subclass of QDomDocument with read and write methods.
Definition Xml.h:182
XMLNode set_root(const QString &node_name, const QString &xmlns=nullptr)
create the xml header and root node
Definition Xml.cpp:336
XMLNode is a subclass of QDomNode with read and write values methods.
Definition Xml.h:39
XMLNode createNode(const QString &name)
create a new XMLNode that has to be appended into de XMLDoc
Definition Xml.cpp:44
void write_int(const QString &node, const int value)
write an integer into a child node
Definition Xml.cpp:265
void setHideKeyboardCursor(bool bHidden)
void addEventListener(EventListener *pListener)
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
std::shared_ptr< CommonStrings > getCommonStrings()
QUndoStack * m_pUndoStack
void showStatusBarMessage(const QString &sMessage, const QString &sCaller="")
void preferencesChanged(H2Core::Preferences::Changes changes)
Propagates a change in the Preferences through the GUI.
Compatibility class to support QMouseEvent more esily in Qt5 and Qt6.
Definition MouseEvent.h:35
Pattern Editor Panel.
virtual bool checkDeselectElements(std::vector< SelectionIndex > &elements) override
Deselecting notes.
static QString modeToQString(Mode mode)
virtual void selectInstrumentNotes(int nInstrument)
virtual void mouseDragUpdateEvent(QMouseEvent *ev) override
virtual void mouseMoveEvent(QMouseEvent *ev) override
int lineToPitch(int nLine)
virtual void validateSelection() override
Ensure that the Selection contains only valid elements.
void editNotePropertiesAction(int nColumn, int nRealColumn, int nRow, int nSelectedPatternNumber, int nSelectedInstrumentNumber, Mode mode, Editor editor, float fVelocity, float fPan, float fLeadLag, float fProbability)
void scrolled(int nValue)
int m_nSelectedPatternNumber
static QColor computeNoteColor(float velocity)
Calculate colour to use for note representation based on note velocity.
virtual void mousePressEvent(QMouseEvent *ev) override
Raw Qt mouse events are passed to the Selection.
virtual void updateModifiers(QInputEvent *ev)
Update the status of modifier keys in response to input events.
virtual void selectNone()
int granularity() const
Granularity of grid positioning (in ticks)
static bool isUsingAdditionalPatterns(const H2Core::Pattern *pPattern)
Determines whether to pattern editor should show further patterns (determined by getPattersToShow()) ...
QColor selectedNoteColor() const
Colour to use for outlining selected notes.
virtual void stackedModeActivationEvent(int nValue) override
virtual void deleteSelection()=0
int m_nSelectedInstrumentNumber
Cached properties used when adjusting a note property via right-press mouse movement.
float m_fOldProbability
void storeNoteProperties(const H2Core::Note *pNote)
Stores the properties of pNote in member variables.
void updatePosition(float fTick)
Caches the AudioEngine::m_nPatternTickPosition in the member variable m_nTick and triggers an update(...
static void triggerStatusMessage(H2Core::Note *pNote, Mode mode)
void editNoteLengthAction(int nColumn, int nRealColumn, int nRow, int nLength, int nSelectedPatternNumber, int nSelectedInstrumentnumber, Editor editor)
void drawNoteSymbol(QPainter &p, QPoint pos, H2Core::Note *pNote, bool bIsForeground=true) const
Draw a note.
virtual void updateEditor(bool bPatternOnly=false)=0
bool notesMatchExactly(H2Core::Note *pNoteA, H2Core::Note *pNoteB) const
Do two notes match exactly, from the pattern editor's point of view?
void onPreferencesChanged(H2Core::Preferences::Changes changes)
virtual void createBackground()
Updates m_pBackgroundPixmap to show the latest content.
static constexpr int nMargin
virtual void songModeActivationEvent() override
PatternEditorPanel * m_pPatternEditorPanel
virtual void focusInEvent(QFocusEvent *ev) override
H2Core::AudioEngine * m_pAudioEngine
void zoomIn()
Zoom in / out on the time axis.
virtual void leaveEvent(QEvent *ev) override
H2Core::Note * m_pDraggedNote
virtual void mouseReleaseEvent(QMouseEvent *ev) override
QMenu * m_pPopupMenu
void deselectAndOverwriteNotes(std::vector< H2Core::Note * > &selected, std::vector< H2Core::Note * > &overwritten)
Deselect some notes, and "overwrite" some others.
void updatePatternInfo()
Update current pattern information.
virtual void mouseDragStartEvent(QMouseEvent *ev) override
unsigned m_nGridHeight
virtual void copy()
Copy selection to clipboard in XML.
virtual void focusOutEvent(QFocusEvent *ev) override
void setCurrentInstrument(int nInstrument)
std::vector< H2Core::Pattern * > getPatternsToShow(void)
Get notes to show in pattern editor.
void undoDeselectAndOverwriteNotes(std::vector< H2Core::Note * > &selected, std::vector< H2Core::Note * > &overwritten)
QPixmap * m_pBackgroundPixmap
void setResolution(uint res, bool bUseTriplets)
Set the editor grid resolution, dividing a whole note into res subdivisions.
virtual void mouseDragEndEvent(QMouseEvent *ev) override
PatternEditor(QWidget *pParent, PatternEditorPanel *panel)
virtual void selectAll()=0
virtual void cut()
virtual void paste()=0
int getColumn(int x, bool bUseFineGrained=false) const
H2Core::Pattern * m_pPattern
void invalidateBackground()
void updateWidth()
Adjusts m_nActiveWidth and m_nEditorWidth to the current state of the editor.
virtual void enterEvent(QEvent *ev) override
Selection< SelectionIndex > m_selection
The Selection object.
QPoint movingGridOffset() const
bool m_bEntered
Indicates whether the mouse pointer entered the widget.
bool m_bBackgroundInvalid
void drawGridLines(QPainter &p, Qt::PenStyle style=Qt::SolidLine) const
Draw lines for note grid.
static constexpr int nPlayheadWidth
Definition Skin.h:77
static int getPlayheadShaftOffset()
Definition Skin.h:79
#define MAX_NOTES
Maximum number of notes.
Definition config.dox:79
#define UNUSED(v)
Definition Globals.h:42