hydrogen 1.2.3
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-2024 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"
27#include "../CommonStrings.h"
28#include "../HydrogenApp.h"
29#include "../EventListener.h"
30#include "../UndoActions.h"
31#include "../Skin.h"
32
33#include <core/Globals.h>
34#include <core/Basics/Song.h>
35#include <core/Hydrogen.h>
37#include <core/EventQueue.h>
42#include <core/Basics/Pattern.h>
44#include <core/Basics/Adsr.h>
45#include <core/Basics/Note.h>
47#include <core/Helpers/Xml.h>
48
49
50using namespace std;
51using namespace H2Core;
52
53
55 PatternEditorPanel *panel )
56 : Object()
57 , QWidget( pParent )
58 , m_selection( this )
59 , m_bEntered( false )
60 , m_nResolution( 8 )
61 , m_bUseTriplets( false )
62 , m_pDraggedNote( nullptr )
63 , m_pPatternEditorPanel( panel )
64 , m_pPattern( nullptr )
65 , m_bSelectNewNotes( false )
66 , m_bFineGrained( false )
67 , m_bCopyNotMove( false )
68 , m_nTick( -1 )
69 , m_editor( Editor::None )
70 , m_mode( Mode::None )
71{
72
74
75 m_fGridWidth = pPref->getPatternEditorGridWidth();
78
79 setFocusPolicy(Qt::StrongFocus);
80
83
85
86 // Popup context menu
87 m_pPopupMenu = new QMenu( this );
88 m_pPopupMenu->addAction( tr( "&Cut" ), this, SLOT( cut() ) );
89 m_pPopupMenu->addAction( tr( "&Copy" ), this, SLOT( copy() ) );
90 m_pPopupMenu->addAction( tr( "&Paste" ), this, SLOT( paste() ) );
91 m_pPopupMenu->addAction( tr( "&Delete" ), this, SLOT( deleteSelection() ) );
92 m_pPopupMenu->addAction( tr( "Select &all" ), this, SLOT( selectAll() ) );
93 m_pPopupMenu->addAction( tr( "Clear selection" ), this, SLOT( selectNone() ) );
94
95 qreal pixelRatio = devicePixelRatio();
96 m_pBackgroundPixmap = new QPixmap( m_nEditorWidth * pixelRatio,
97 height() * pixelRatio );
98 m_pBackgroundPixmap->setDevicePixelRatio( pixelRatio );
100}
101
108
110{
111 if ( changes & H2Core::Preferences::Changes::Colors ) {
112
113 update( 0, 0, width(), height() );
115 }
116}
117
118void PatternEditor::setResolution(uint res, bool bUseTriplets)
119{
120 this->m_nResolution = res;
121 this->m_bUseTriplets = bUseTriplets;
122
123 // redraw all
124 update( 0, 0, width(), height() );
126}
127
129{
130 if (m_fGridWidth >= 3) {
131 m_fGridWidth *= 2;
132 } else {
133 m_fGridWidth *= 1.5;
134 }
135 updateEditor();
136}
137
139{
140 if ( m_fGridWidth > 1.5 ) {
141 if (m_fGridWidth > 3) {
142 m_fGridWidth /= 2;
143 } else {
144 m_fGridWidth /= 1.5;
145 }
146 updateEditor();
147 }
148}
149
150QColor PatternEditor::computeNoteColor( float fVelocity ) {
151 float fRed, fGreen, fBlue;
152
154
155 QColor fullColor = pPref->getColorTheme()->m_patternEditor_noteVelocityFullColor;
156 QColor defaultColor = pPref->getColorTheme()->m_patternEditor_noteVelocityDefaultColor;
157 QColor halfColor = pPref->getColorTheme()->m_patternEditor_noteVelocityHalfColor;
158 QColor zeroColor = pPref->getColorTheme()->m_patternEditor_noteVelocityZeroColor;
159
160 // The colors defined in the Preferences correspond to fixed
161 // velocity values. In case the velocity lies between two of those
162 // the corresponding colors will be interpolated.
163 float fWeightFull = 0;
164 float fWeightDefault = 0;
165 float fWeightHalf = 0;
166 float fWeightZero = 0;
167
168 if ( fVelocity >= 1.0 ) {
169 fWeightFull = 1.0;
170 } else if ( fVelocity >= 0.8 ) {
171 fWeightDefault = ( 1.0 - fVelocity )/ ( 1.0 - 0.8 );
172 fWeightFull = 1.0 - fWeightDefault;
173 } else if ( fVelocity >= 0.5 ) {
174 fWeightHalf = ( 0.8 - fVelocity )/ ( 0.8 - 0.5 );
175 fWeightDefault = 1.0 - fWeightHalf;
176 } else {
177 fWeightZero = ( 0.5 - fVelocity )/ ( 0.5 );
178 fWeightHalf = 1.0 - fWeightZero;
179 }
180
181 fRed = fWeightFull * fullColor.redF() +
182 fWeightDefault * defaultColor.redF() +
183 fWeightHalf * halfColor.redF() + fWeightZero * zeroColor.redF();
184 fGreen = fWeightFull * fullColor.greenF() +
185 fWeightDefault * defaultColor.greenF() +
186 fWeightHalf * halfColor.greenF() + fWeightZero * zeroColor.greenF();
187 fBlue = fWeightFull * fullColor.blueF() +
188 fWeightDefault * defaultColor.blueF() +
189 fWeightHalf * halfColor.blueF() + fWeightZero * zeroColor.blueF();
190
191 QColor color;
192 color.setRedF( fRed );
193 color.setGreenF( fGreen );
194 color.setBlueF( fBlue );
195
196 return color;
197}
198
199
200void PatternEditor::drawNoteSymbol( QPainter &p, QPoint pos, H2Core::Note *pNote, bool bIsForeground ) const
201{
202 if ( m_pPattern == nullptr ) {
203 return;
204 }
205
207
208 const QColor noteColor( pPref->getColorTheme()->m_patternEditor_noteVelocityDefaultColor );
209 const QColor noteInactiveColor( pPref->getColorTheme()->m_windowTextColor.darker( 150 ) );
210 const QColor noteoffColor( pPref->getColorTheme()->m_patternEditor_noteOffColor );
211 const QColor noteoffInactiveColor( pPref->getColorTheme()->m_windowTextColor );
212
213 p.setRenderHint( QPainter::Antialiasing );
214
215 QColor color = computeNoteColor( pNote->get_velocity() );
216
217 uint w = 8, h = 8;
218 uint x_pos = pos.x(), y_pos = pos.y();
219
220 bool bSelected = m_selection.isSelected( pNote );
221
222 if ( bSelected ) {
223 QPen selectedPen( selectedNoteColor() );
224 selectedPen.setWidth( 2 );
225 p.setPen( selectedPen );
226 p.setBrush( Qt::NoBrush );
227 }
228
229 bool bMoving = bSelected && m_selection.isMoving();
230 QPen movingPen( noteColor );
231 QPoint movingOffset;
232
233 if ( bMoving ) {
234 movingPen.setStyle( Qt::DotLine );
235 movingPen.setWidth( 2 );
236 QPoint delta = movingGridOffset();
237 movingOffset = QPoint( delta.x() * m_fGridWidth,
238 delta.y() * m_nGridHeight );
239 }
240
241 if ( pNote->get_note_off() == false ) { // trigger note
242 int width = w;
243
244 QBrush noteBrush( color );
245 QPen notePen( noteColor );
246 if ( !bIsForeground ) {
247
248 if ( x_pos >= m_nActiveWidth ) {
249 noteBrush.setColor( noteInactiveColor );
250 notePen.setColor( noteInactiveColor );
251 }
252
253 noteBrush.setStyle( Qt::Dense4Pattern );
254 notePen.setStyle( Qt::DotLine );
255 }
256
257 if ( bSelected ) {
258 p.drawEllipse( x_pos -4 -2, y_pos-2, w+4, h+4 );
259 }
260
261 // Draw tail
262 if ( pNote->get_length() != -1 ) {
263 float fNotePitch = pNote->get_octave() * 12 + pNote->get_key();
264 float fStep = Note::pitchToFrequency( ( double )fNotePitch );
265
266 // if there is a stop-note to the right of this note, only draw-
267 // its length till there.
268 int nLength = pNote->get_length();
269 auto notes = m_pPattern->get_notes();
270 for ( const auto& [ _, ppNote ] : *m_pPattern->get_notes() ) {
271 if ( ppNote != nullptr &&
272 // noteOff note
273 ppNote->get_note_off() &&
274 // located in the same row
275 ppNote->get_instrument() == pNote->get_instrument() &&
276 // left of the NoteOff
277 pNote->get_position() < ppNote->get_position() &&
278 // custom length reaches beyond NoteOff
279 pNote->get_position() + pNote->get_length() >
280 ppNote->get_position() ) {
281 // In case there are multiple stop-notes present, take the
282 // shortest distance.
283 nLength = std::min( ppNote->get_position() - pNote->get_position(), nLength );
284 }
285 }
286
287 width = m_fGridWidth * nLength / fStep;
288 width = width - 1; // lascio un piccolo spazio tra una nota ed un altra
289
290 if ( bSelected ) {
291 p.drawRoundedRect( x_pos-2, y_pos, width+4, 3+4, 4, 4 );
292 }
293 p.setPen( notePen );
294 p.setBrush( noteBrush );
295
296 // Since the note body is transparent for an inactive note, we try
297 // to start the tail at its boundary. For regular notes we do not
298 // care about an overlap, as it ensures that there are no white
299 // artifacts between tail and note body regardless of the scale
300 // factor.
301 int nRectOnsetX = x_pos;
302 int nRectWidth = width;
303 if ( ! bIsForeground ) {
304 nRectOnsetX = nRectOnsetX + w/2;
305 nRectWidth = nRectWidth - w/2;
306 }
307
308 p.drawRect( nRectOnsetX, y_pos +2, nRectWidth, 3 );
309 p.drawLine( x_pos+width, y_pos, x_pos+width, y_pos + h );
310 }
311
312 p.setPen( notePen );
313 p.setBrush( noteBrush );
314 p.drawEllipse( x_pos -4 , y_pos, w, h );
315
316 if ( bMoving ) {
317 p.setPen( movingPen );
318 p.setBrush( Qt::NoBrush );
319 p.drawEllipse( movingOffset.x() + x_pos -4 -2, movingOffset.y() + y_pos -2 , w + 4, h + 4 );
320 // Moving tail
321 if ( pNote->get_length() != -1 ) {
322 p.setPen( movingPen );
323 p.setBrush( Qt::NoBrush );
324 p.drawRoundedRect( movingOffset.x() + x_pos-2, movingOffset.y() + y_pos, width+4, 3+4, 4, 4 );
325 }
326 }
327 }
328 else if ( pNote->get_length() == 1 && pNote->get_note_off() == true ) {
329
330 QBrush noteOffBrush( noteoffColor );
331 if ( !bIsForeground ) {
332 noteOffBrush.setStyle( Qt::Dense4Pattern );
333
334 if ( x_pos >= m_nActiveWidth ) {
335 noteOffBrush.setColor( noteoffInactiveColor );
336 }
337 }
338
339 if ( bSelected ) {
340 p.drawEllipse( x_pos -4 -2, y_pos-2, w+4, h+4 );
341 }
342
343 p.setPen( Qt::NoPen );
344 p.setBrush( noteOffBrush );
345 p.drawEllipse( x_pos -4 , y_pos, w, h );
346
347 if ( bMoving ) {
348 p.setPen( movingPen );
349 p.setBrush( Qt::NoBrush );
350 p.drawEllipse( movingOffset.x() + x_pos -4 -2, movingOffset.y() + y_pos -2, w + 4, h + 4 );
351 }
352 }
353}
354
355
356int PatternEditor::getColumn( int x, bool bUseFineGrained ) const
357{
358 int nGranularity = 1;
359 if ( !( bUseFineGrained && m_bFineGrained ) ) {
360 nGranularity = granularity();
361 }
362 int nWidth = m_fGridWidth * nGranularity;
363 int nColumn = ( x - PatternEditor::nMargin + (nWidth / 2) ) / nWidth;
364 nColumn = nColumn * nGranularity;
365 if ( nColumn < 0 ) {
366 return 0;
367 } else {
368 return nColumn;
369 }
370}
371
377
382{
383 Hydrogen *pHydrogen = Hydrogen::get_instance();
384 auto pInstrumentList = pHydrogen->getSong()->getInstrumentList();
385 XMLDoc doc;
386 XMLNode selection = doc.set_root( "noteSelection" );
387 XMLNode noteList = selection.createNode( "noteList");
388 XMLNode positionNode = selection.createNode( "sourcePosition" );
389 bool bWroteNote = false;
390 // "Top left" of selection, in the three dimensional time*instrument*pitch space.
391 int nLowestPos, nLowestInstrument, nHighestPitch;
392
393 for ( Note *pNote : m_selection ) {
394 int nPitch = pNote->get_notekey_pitch() + 12*OCTAVE_OFFSET;
395 int nPos = pNote->get_position();
396 int nInstrument = pInstrumentList->index( pNote->get_instrument() );
397 if ( bWroteNote ) {
398 nLowestPos = std::min( nPos, nLowestPos );
399 nLowestInstrument = std::min( nInstrument, nLowestInstrument );
400 nHighestPitch = std::max( nPitch, nHighestPitch );
401 } else {
402 nLowestPos = nPos;
403 nLowestInstrument = nInstrument;
404 nHighestPitch = nPitch;
405 bWroteNote = true;
406 }
407 XMLNode note_node = noteList.createNode( "note" );
408 pNote->save_to( &note_node );
409 }
410
411 if ( bWroteNote ) {
412 positionNode.write_int( "position", nLowestPos );
413 positionNode.write_int( "instrument", nLowestInstrument );
414 positionNode.write_int( "note", nHighestPitch );
415 } else {
416 positionNode.write_int( "position", m_pPatternEditorPanel->getCursorPosition() );
417 positionNode.write_int( "instrument", pHydrogen->getSelectedInstrumentNumber() );
418 }
419
420 QClipboard *clipboard = QApplication::clipboard();
421 clipboard->setText( doc.toString() );
422
423 // This selection will probably be pasted at some point. So show the keyboard cursor as this is the place
424 // where the selection will be pasted.
426}
427
428
430{
431 copy();
433}
434
435
437{
438 if ( m_pPattern == nullptr ) {
439 return;
440 }
441
442 auto pInstrumentList = Hydrogen::get_instance()->getSong()->getInstrumentList();
443 auto pInstrument = pInstrumentList->get( nInstrument );
444
447 if ( it->second->get_instrument() == pInstrument ) {
448 m_selection.addToSelection( it->second );
449 }
450 }
452}
453
458
459void PatternEditor::mousePressEvent( QMouseEvent *ev )
460{
461 updateModifiers( ev );
463}
464
465void PatternEditor::mouseMoveEvent( QMouseEvent *ev )
466{
467 updateModifiers( ev );
468 if ( m_selection.isMoving() ) {
469 updateEditor( true );
470 }
472}
473
474void PatternEditor::mouseReleaseEvent( QMouseEvent *ev )
475{
476 updateModifiers( ev );
478}
479
480void PatternEditor::updateModifiers( QInputEvent *ev ) {
481 // Key: Alt + drag: move notes with fine-grained positioning
482 m_bFineGrained = ev->modifiers() & Qt::AltModifier;
483 // Key: Ctrl + drag: copy notes rather than moving
484 m_bCopyNotMove = ev->modifiers() & Qt::ControlModifier;
485
486 if ( QKeyEvent *pEv = dynamic_cast<QKeyEvent*>( ev ) ) {
487 // Keyboard events for press and release of modifier keys don't have those keys in the modifiers set,
488 // so explicitly update these.
489 bool bPressed = ev->type() == QEvent::KeyPress;
490 if ( pEv->key() == Qt::Key_Control ) {
491 m_bCopyNotMove = bPressed;
492 } else if ( pEv->key() == Qt::Key_Alt ) {
493 m_bFineGrained = bPressed;
494 }
495 }
496
498 // If a selection is currently being moved, change the cursor
499 // appropriately. Selection will change it back after the move
500 // is complete (or abandoned)
501 if ( m_bCopyNotMove && cursor().shape() != Qt::DragCopyCursor ) {
502 setCursor( QCursor( Qt::DragCopyCursor ) );
503 } else if ( !m_bCopyNotMove && cursor().shape() != Qt::DragMoveCursor ) {
504 setCursor( QCursor( Qt::DragMoveCursor ) );
505 }
506 }
507}
508
509bool PatternEditor::notesMatchExactly( Note *pNoteA, Note *pNoteB ) const {
510 return ( pNoteA->match( pNoteB->get_instrument(), pNoteB->get_key(), pNoteB->get_octave() )
511 && pNoteA->get_position() == pNoteB->get_position()
512 && pNoteA->get_velocity() == pNoteB->get_velocity()
513 && pNoteA->getPan() == pNoteB->getPan()
514 && pNoteA->get_lead_lag() == pNoteB->get_lead_lag()
515 && pNoteA->get_probability() == pNoteB->get_probability() );
516}
517
518bool PatternEditor::checkDeselectElements( std::vector<SelectionIndex> &elements )
519{
520 if ( m_pPattern == nullptr ) {
521 return false;
522 }
523
524 auto pCommonStrings = HydrogenApp::get_instance()->getCommonStrings();
525
526 // Hydrogen *pH = Hydrogen::get_instance();
527 std::set< Note *> duplicates;
528 for ( Note *pNote : elements ) {
529 if ( duplicates.find( pNote ) != duplicates.end() ) {
530 // Already marked pNote as a duplicate of some other pNote. Skip it.
531 continue;
532 }
533 FOREACH_NOTE_CST_IT_BOUND_END( m_pPattern->get_notes(), it, pNote->get_position() ) {
534 // Duplicate note of a selected note is anything occupying the same position. Multiple notes
535 // sharing the same location might be selected; we count these as duplicates too. They will appear
536 // in both the duplicates and selection lists.
537 if ( it->second != pNote && pNote->match( it->second ) ) {
538 duplicates.insert( it->second );
539 }
540 }
541 }
542 if ( !duplicates.empty() ) {
543 Preferences *pPreferences = Preferences::get_instance();
544 bool bOk = true;
545
546 if ( pPreferences->getShowNoteOverwriteWarning() ) {
548 QString sMsg ( tr( "Placing these notes here will overwrite %1 duplicate notes." ) );
549 QMessageBox messageBox ( QMessageBox::Warning, "Hydrogen", sMsg.arg( duplicates.size() ),
550 QMessageBox::Cancel | QMessageBox::Ok, this );
551 messageBox.setCheckBox( new QCheckBox( pCommonStrings->getMutableDialog() ) );
552 messageBox.checkBox()->setChecked( false );
553 bOk = messageBox.exec() == QMessageBox::Ok;
554 if ( messageBox.checkBox()->isChecked() ) {
555 pPreferences->setShowNoteOverwriteWarning( false );
556 }
557 }
558
559 if ( bOk ) {
560 QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack;
561
562 std::vector< Note *>overwritten;
563 for ( Note *pNote : duplicates ) {
564 overwritten.push_back( pNote );
565 }
566 pUndo->push( new SE_deselectAndOverwriteNotesAction( elements, overwritten ) );
567
568 } else {
569 return false;
570 }
571 }
572 return true;
573}
574
575
576void PatternEditor::deselectAndOverwriteNotes( std::vector< H2Core::Note *> &selected,
577 std::vector< H2Core::Note *> &overwritten )
578{
579 if ( m_pPattern == nullptr ) {
580 return;
581 }
582
583 // Iterate over all the notes in 'selected' and 'overwrite' by erasing any *other* notes occupying the
584 // same position.
586 Pattern::notes_t *pNotes = const_cast< Pattern::notes_t *>( m_pPattern->get_notes() );
587 for ( auto pSelectedNote : selected ) {
588 m_selection.removeFromSelection( pSelectedNote, /* bCheck=*/false );
589 bool bFoundExact = false;
590 int nPosition = pSelectedNote->get_position();
591 for ( auto it = pNotes->lower_bound( nPosition ); it != pNotes->end() && it->first == nPosition; ) {
592 Note *pNote = it->second;
593 if ( !bFoundExact && notesMatchExactly( pNote, pSelectedNote ) ) {
594 // Found an exact match. We keep this.
595 bFoundExact = true;
596 ++it;
597 } else if ( pSelectedNote->match( pNote ) && pNote->get_position() == pSelectedNote->get_position() ) {
598 // Something else occupying the same position (which may or may not be an exact duplicate)
599 it = pNotes->erase( it );
600 delete pNote;
601 } else {
602 // Any other note
603 ++it;
604 }
605 }
606 }
609}
610
611
612void PatternEditor::undoDeselectAndOverwriteNotes( std::vector< H2Core::Note *> &selected,
613 std::vector< H2Core::Note *> &overwritten )
614{
615 if ( m_pPattern == nullptr ) {
616 return;
617 }
618
619 // Restore previously-overwritten notes, and select notes that were selected before.
620 m_selection.clearSelection( /* bCheck=*/false );
622 for ( auto pNote : overwritten ) {
623 Note *pNewNote = new Note( pNote );
624 m_pPattern->insert_note( pNewNote );
625 }
626 // Select the previously-selected notes
627 for ( auto pNote : selected ) {
628 FOREACH_NOTE_CST_IT_BOUND_END( m_pPattern->get_notes(), it, pNote->get_position() ) {
629 if ( notesMatchExactly( it->second, pNote ) ) {
630 m_selection.addToSelection( it->second );
631 break;
632 }
633 }
634 }
638}
639
640
642 Hydrogen *pHydrogen = Hydrogen::get_instance();
643 std::shared_ptr<Song> pSong = pHydrogen->getSong();
644
645 m_pPattern = nullptr;
647
648 if ( pSong != nullptr ) {
649 PatternList *pPatternList = pSong->getPatternList();
650 if ( ( m_nSelectedPatternNumber != -1 ) && ( m_nSelectedPatternNumber < pPatternList->size() ) ) {
651 m_pPattern = pPatternList->get( m_nSelectedPatternNumber );
652 }
653 }
654}
655
656
658 QPoint rawOffset = m_selection.movingOffset();
659 // Quantize offset to multiples of m_nGrid{Width,Height}
660 int nQuantX = m_fGridWidth, nQuantY = m_nGridHeight;
661 float nFactor = 1;
662 if ( ! m_bFineGrained ) {
663 nFactor = granularity();
664 nQuantX = m_fGridWidth * nFactor;
665 }
666 int x_bias = nQuantX / 2, y_bias = nQuantY / 2;
667 if ( rawOffset.y() < 0 ) {
668 y_bias = -y_bias;
669 }
670 if ( rawOffset.x() < 0 ) {
671 x_bias = -x_bias;
672 }
673 int x_off = (rawOffset.x() + x_bias) / nQuantX;
674 int y_off = (rawOffset.y() + y_bias) / nQuantY;
675 return QPoint( nFactor * x_off, y_off);
676}
677
678
680void PatternEditor::drawGridLines( QPainter &p, Qt::PenStyle style ) const
681{
683 const std::vector<QColor> colorsActive = {
684 QColor( pPref->getColorTheme()->m_patternEditor_line1Color ),
685 QColor( pPref->getColorTheme()->m_patternEditor_line2Color ),
686 QColor( pPref->getColorTheme()->m_patternEditor_line3Color ),
687 QColor( pPref->getColorTheme()->m_patternEditor_line4Color ),
688 QColor( pPref->getColorTheme()->m_patternEditor_line5Color ),
689 };
690 const std::vector<QColor> colorsInactive = {
691 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 170 ) ),
692 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 190 ) ),
693 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 210 ) ),
694 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 230 ) ),
695 QColor( pPref->getColorTheme()->m_windowTextColor.darker( 250 ) ),
696 };
697
698 int nGranularity = granularity() * m_nResolution;
699
700 if ( !m_bUseTriplets ) {
701
702 // Draw vertical lines. To minimise pen colour changes (and
703 // avoid unnecessary division operations), we draw them in
704 // multiple passes, of successively finer spacing (and
705 // advancing the colour selection at each refinement) until
706 // we've drawn enough to satisfy the resolution setting.
707 //
708 // The drawing sequence looks something like:
709 // | | | | - first pass, all 1/4 notes
710 // | : | : | : | : - second pass, odd 1/8th notes
711 // | . : . | . : . | . : . | . : . - third pass, odd 1/16th notes
712
713 uint nRes = 4;
714 float fStep = nGranularity / nRes * m_fGridWidth;
715
716 // First, quarter note markers. All the quarter note markers must be drawn.
717 if ( m_nResolution >= nRes ) {
718 float x = PatternEditor::nMargin;
719 p.setPen( QPen( colorsActive[ 0 ], 1, style ) );
720 while ( x < m_nActiveWidth ) {
721 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
722 x += fStep;
723 }
724
725 p.setPen( QPen( colorsInactive[ 0 ], 1, style ) );
726 while ( x < m_nEditorWidth ) {
727 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
728 x += fStep;
729 }
730 }
731 nRes *= 2;
732 fStep /= 2;
733
734 // For each successive set of finer-spaced lines, the even
735 // lines will have already been drawn at the previous coarser
736 // pitch, so only the odd numbered lines need to be drawn.
737 int nColour = 1;
738 while ( m_nResolution >= nRes ) {
739 nColour++;
740 float x = PatternEditor::nMargin + fStep;
741 p.setPen( QPen( colorsActive[ std::min( nColour, static_cast<int>(colorsActive.size()) - 1 ) ],
742 1, style ) );
743 while ( x < m_nActiveWidth + fStep ) {
744 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
745 x += fStep * 2;
746 }
747
748 p.setPen( QPen( colorsInactive[ std::min( nColour, static_cast<int>(colorsInactive.size()) - 1 ) ],
749 1, style ) );
750 while ( x < m_nEditorWidth ) {
751 p.drawLine( x, 1, x, m_nEditorHeight - 1 );
752 x += fStep * 2;
753 }
754 nRes *= 2;
755 fStep /= 2;
756 }
757
758 } else {
759
760 // Triplet style markers, we only differentiate colours on the
761 // first of every triplet.
762 float fStep = granularity() * m_fGridWidth;
763 float x = PatternEditor::nMargin;
764 p.setPen( QPen( colorsActive[ 0 ], 1, style ) );
765 while ( x < m_nActiveWidth ) {
766 p.drawLine(x, 1, x, m_nEditorHeight - 1);
767 x += fStep * 3;
768 }
769
770 p.setPen( QPen( colorsInactive[ 0 ], 1, style ) );
771 while ( x < m_nEditorWidth ) {
772 p.drawLine(x, 1, x, m_nEditorHeight - 1);
773 x += fStep * 3;
774 }
775
776 // Second and third marks
777 x = PatternEditor::nMargin + fStep;
778 p.setPen( QPen( colorsActive[ 2 ], 1, style ) );
779 while ( x < m_nActiveWidth + fStep ) {
780 p.drawLine(x, 1, x, m_nEditorHeight - 1);
781 p.drawLine(x + fStep, 1, x + fStep, m_nEditorHeight - 1);
782 x += fStep * 3;
783 }
784
785 p.setPen( QPen( colorsInactive[ 2 ], 1, style ) );
786 while ( x < m_nEditorWidth ) {
787 p.drawLine(x, 1, x, m_nEditorHeight - 1);
788 p.drawLine(x + fStep, 1, x + fStep, m_nEditorHeight - 1);
789 x += fStep * 3;
790 }
791 }
792
793}
794
795
797
799
800 if ( hasFocus() ) {
801 const QColor selectHighlightColor( pPref->getColorTheme()->m_selectionHighlightColor );
802 return selectHighlightColor;
803 } else {
804 const QColor selectInactiveColor( pPref->getColorTheme()->m_selectionInactiveColor );
805 return selectInactiveColor;
806 }
807}
808
809
814{
815 if ( m_pPattern == nullptr ) {
816 return;
817 }
818
819 // Rebuild selection from valid notes.
820 std::set<Note *> valid;
821 std::vector< Note *> invalidated;
823 if ( m_selection.isSelected( it->second ) ) {
824 valid.insert( it->second );
825 }
826 }
827 for (auto i : m_selection ) {
828 if ( valid.find(i) == valid.end()) {
829 // Keep the note to invalidate, but don't remove from the selection while walking the selection
830 // set.
831 invalidated.push_back( i );
832 }
833 }
834 for ( auto i : invalidated ) {
835 m_selection.removeFromSelection( i, /* bCheck=*/false );
836 }
837}
838
839void PatternEditor::scrolled( int nValue ) {
840 UNUSED( nValue );
841 update();
842}
843
844void PatternEditor::enterEvent( QEvent *ev ) {
845 UNUSED( ev );
846 m_bEntered = true;
847 update();
848}
849
850void PatternEditor::leaveEvent( QEvent *ev ) {
851 UNUSED( ev );
852 m_bEntered = false;
853 update();
854}
855
856void PatternEditor::focusInEvent( QFocusEvent *ev ) {
857 UNUSED( ev );
858 if ( ev->reason() == Qt::TabFocusReason || ev->reason() == Qt::BacktabFocusReason ) {
861 }
862 if ( ! HydrogenApp::get_instance()->hideKeyboardCursor() ) {
863 // Immediate update to prevent visual delay.
866 }
867
868 // Update to show the focus border highlight
869 update();
870}
871
872void PatternEditor::focusOutEvent( QFocusEvent *ev ) {
873 UNUSED( ev );
874 if ( ! HydrogenApp::get_instance()->hideKeyboardCursor() ) {
877 }
878
879 // Update to remove the focus border highlight
880 update();
881}
882
886
889
893std::vector< Pattern *> PatternEditor::getPatternsToShow( void )
894{
895 Hydrogen *pHydrogen = Hydrogen::get_instance();
896 std::vector<Pattern *> patterns;
897
898 // When using song mode without the pattern editor being locked
899 // only the current pattern will be shown. In every other base
900 // remaining playing patterns not selected by the user are added
901 // as well.
902 if ( ! ( pHydrogen->getMode() == Song::Mode::Song &&
903 ! pHydrogen->isPatternEditorLocked() ) ) {
905 if ( m_pAudioEngine->getPlayingPatterns()->size() > 0 ) {
906 std::set< Pattern *> patternSet;
907
908 std::vector<const PatternList*> patternLists;
909 patternLists.push_back( m_pAudioEngine->getPlayingPatterns() );
910 if ( pHydrogen->getPatternMode() == Song::PatternMode::Stacked ) {
911 patternLists.push_back( m_pAudioEngine->getNextPatterns() );
912 }
913
914 for ( const PatternList *pPatternList : patternLists ) {
915 for ( int i = 0; i < pPatternList->size(); i++) {
916 Pattern *pPattern = pPatternList->get( i );
917 if ( pPattern != m_pPattern ) {
918 patternSet.insert( pPattern );
919 }
920 }
921 }
922 for ( Pattern *pPattern : patternSet ) {
923 patterns.push_back( pPattern );
924 }
925 }
927 }
928 else if ( m_pPattern != nullptr &&
929 pHydrogen->getMode() == Song::Mode::Song &&
930 m_pPattern->get_virtual_patterns()->size() > 0 ) {
931 // A virtual pattern was selected in song mode without the
932 // pattern editor being locked. Virtual patterns in selected
933 // pattern mode are handled using the playing pattern above.
934 for ( const auto ppVirtualPattern : *m_pPattern ) {
935 patterns.push_back( ppVirtualPattern );
936 }
937 }
938
939
940 if ( m_pPattern != nullptr ) {
941 patterns.push_back( m_pPattern );
942 }
943
944 return patterns;
945}
946
948 auto pHydrogen = H2Core::Hydrogen::get_instance();
949
950 if ( pHydrogen->getPatternMode() == Song::PatternMode::Stacked ||
951 ( pPattern != nullptr && pPattern->isVirtual() ) ||
952 ( pHydrogen->getMode() == Song::Mode::Song &&
953 pHydrogen->isPatternEditorLocked() ) ) {
954 return true;
955 }
956
957 return false;
958}
959
961 auto pHydrogen = H2Core::Hydrogen::get_instance();
962
963 if ( m_pPattern != nullptr ) {
966
967 // In case there are other patterns playing which are longer
968 // than the selected one, their notes will be placed using a
969 // different color set between m_nActiveWidth and
970 // m_nEditorWidth.
971 if ( pHydrogen->getMode() == Song::Mode::Song &&
972 m_pPattern != nullptr && m_pPattern->isVirtual() &&
973 ! pHydrogen->isPatternEditorLocked() ) {
977 static_cast<float>(m_nActiveWidth) );
978 }
982 pHydrogen->getAudioEngine()->getPlayingPatterns()->longest_pattern_length( false ) + 1,
983 static_cast<float>(m_nActiveWidth) );
984 }
985 else {
987 }
988 }
989 else {
992 }
993}
994
996{
997 // May need to draw (or hide) other background patterns
998 update();
999}
1000
1002{
1003 UNUSED( nValue );
1004 // May need to draw (or hide) other background patterns
1005 update();
1006}
1007
1009 if ( m_nTick == (int)fTick ) {
1010 return;
1011 }
1012
1013 float fDiff = m_fGridWidth * (fTick - m_nTick);
1014
1015 m_nTick = fTick;
1016
1017 int nOffset = Skin::getPlayheadShaftOffset();
1018 int nX = static_cast<int>(static_cast<float>(PatternEditor::nMargin) +
1019 static_cast<float>(m_nTick) *
1020 m_fGridWidth );
1021
1022 QRect updateRect( nX -2, 0, 4 + Skin::nPlayheadWidth, height() );
1023 update( updateRect );
1024 if ( fDiff > 1.0 || fDiff < -1.0 ) {
1025 // New cursor is far enough away from the old one that the single update rect won't cover both. So
1026 // update at the old location as well.
1027 updateRect.translate( -fDiff, 0 );
1028 update( updateRect );
1029 }
1030}
1031
1033 if( pNote != nullptr ){
1034 m_nOldLength = pNote->get_length();
1035 //needed to undo note properties
1036 m_fOldVelocity = pNote->get_velocity();
1037 m_fOldPan = pNote->getPan();
1038
1039 m_fOldLeadLag = pNote->get_lead_lag();
1040
1042 m_fPan = m_fOldPan;
1044 }
1045 else {
1046 m_nOldLength = -1;
1047 }
1048}
1049
1050void PatternEditor::mouseDragStartEvent( QMouseEvent *ev ) {
1051
1052 auto pHydrogenApp = HydrogenApp::get_instance();
1053 auto pHydrogen = Hydrogen::get_instance();
1054
1055 // Move cursor.
1056 int nColumn = getColumn( ev->x() );
1058
1059 // Hide cursor.
1060 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
1061
1062 pHydrogenApp->setHideKeyboardCursor( true );
1063
1064 // Cursor either just got hidden or was moved.
1065 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
1066 // Immediate update to prevent visual delay.
1069 }
1070
1071 int nRealColumn = 0;
1072
1073 if ( ev->button() == Qt::RightButton ) {
1074
1075 int nPressedLine =
1076 std::floor(static_cast<float>(ev->y()) / static_cast<float>(m_nGridHeight));
1077 int nSelectedInstrumentNumber = pHydrogen->getSelectedInstrumentNumber();
1078
1079 if( ev->x() > PatternEditor::nMargin ) {
1080 nRealColumn =
1081 static_cast<int>(std::floor(
1082 static_cast<float>((ev->x() - PatternEditor::nMargin)) /
1083 m_fGridWidth));
1084 }
1085
1086 // Needed for undo changes in the note length
1087 m_nOldPoint = ev->y();
1088 m_nRealColumn = nRealColumn;
1089 m_nColumn = nColumn;
1090 m_nPressedLine = nPressedLine;
1091 m_nSelectedInstrumentNumber = pHydrogen->getSelectedInstrumentNumber();
1092 }
1093
1094}
1095
1097
1098 if ( m_pPattern == nullptr || m_pDraggedNote == nullptr ) {
1099 return;
1100 }
1101
1102 if ( m_pDraggedNote->get_note_off() ) {
1103 return;
1104 }
1105
1106 int nTickColumn = getColumn( ev->x() );
1107
1109 int nLen = nTickColumn - m_pDraggedNote->get_position();
1110
1111 if ( nLen <= 0 ) {
1112 nLen = -1;
1113 }
1114
1115 float fNotePitch = m_pDraggedNote->get_notekey_pitch();
1116 float fStep = 0;
1117 if ( nLen > -1 ){
1118 fStep = Note::pitchToFrequency( ( double )fNotePitch );
1119 } else {
1120 fStep = 1.0;
1121 }
1122 m_pDraggedNote->set_length( nLen * fStep);
1123
1125
1126 // edit note property. We do not support the note key property.
1127 if ( m_mode != Mode::NoteKey ) {
1128
1129 float fValue = 0.0;
1130 if ( m_mode == Mode::Velocity ) {
1131 fValue = m_pDraggedNote->get_velocity();
1132 }
1133 else if ( m_mode == Mode::Pan ) {
1135 }
1136 else if ( m_mode == Mode::LeadLag ) {
1137 fValue = ( m_pDraggedNote->get_lead_lag() - 1.0 ) / -2.0 ;
1138 }
1139 else if ( m_mode == Mode::Probability ) {
1140 fValue = m_pDraggedNote->get_probability();
1141 }
1142
1143 float fMoveY = m_nOldPoint - ev->y();
1144 fValue = fValue + (fMoveY / 100);
1145 if ( fValue > 1 ) {
1146 fValue = 1;
1147 }
1148 else if ( fValue < 0.0 ) {
1149 fValue = 0.0;
1150 }
1151
1152 if ( m_mode == Mode::Velocity ) {
1153 m_pDraggedNote->set_velocity( fValue );
1154 m_fVelocity = fValue;
1155 }
1156 else if ( m_mode == Mode::Pan ) {
1159 }
1160 else if ( m_mode == Mode::LeadLag ) {
1161 m_pDraggedNote->set_lead_lag( ( fValue * -2.0 ) + 1.0 );
1162 m_fLeadLag = ( fValue * -2.0 ) + 1.0;
1163 }
1164 else if ( m_mode == Mode::Probability ) {
1166 m_fProbability = fValue;
1167 }
1168
1170
1171 m_nOldPoint = ev->y();
1172 }
1173
1174 m_pAudioEngine->unlock(); // unlock the audio engine
1176
1177 if ( m_pPatternEditorPanel != nullptr ) {
1179 }
1180}
1181
1182void PatternEditor::mouseDragEndEvent( QMouseEvent* ev ) {
1183
1184 UNUSED( ev );
1185 unsetCursor();
1186
1187 if ( m_pPattern == nullptr || m_nSelectedPatternNumber == -1 ) {
1188 return;
1189 }
1190
1191 if ( m_pDraggedNote == nullptr || m_pDraggedNote->get_note_off() ) {
1192 return;
1193 }
1194
1196 SE_editNoteLengthAction *action =
1199 m_nRow,
1204 m_editor );
1205 HydrogenApp::get_instance()->m_pUndoStack->push( action );
1206 }
1207
1208
1209 if( m_fVelocity == m_fOldVelocity &&
1210 m_fOldPan == m_fPan &&
1213 return;
1214 }
1215
1219 m_nRow,
1222 m_mode,
1223 m_editor,
1226 m_fPan,
1227 m_fOldPan,
1228 m_fLeadLag,
1232 HydrogenApp::get_instance()->m_pUndoStack->push( action );
1233}
1234
1236 int nRealColumn,
1237 int nRow,
1238 int nLength,
1239 int nSelectedPatternNumber,
1240 int nSelectedInstrumentnumber,
1241 Editor editor)
1242{
1243
1244 auto pHydrogen = Hydrogen::get_instance();
1245 auto pSong = pHydrogen->getSong();
1246 auto pPatternList = pSong->getPatternList();
1247
1248 H2Core::Pattern* pPattern = nullptr;
1249 if ( nSelectedPatternNumber != -1 &&
1250 nSelectedPatternNumber < pPatternList->size() ) {
1251 pPattern = pPatternList->get( nSelectedPatternNumber );
1252 }
1253
1254 if ( pPattern == nullptr ) {
1255 return;
1256 }
1257
1259
1260 // Find the note to edit
1261 Note* pDraggedNote = nullptr;
1262 if ( editor == Editor::PianoRoll ) {
1263 auto pSelectedInstrument =
1264 pSong->getInstrumentList()->get( nSelectedInstrumentnumber );
1265 if ( pSelectedInstrument == nullptr ) {
1266 ERRORLOG( "No instrument selected" );
1267 return;
1268 }
1269
1270 Note::Octave pressedOctave = Note::pitchToOctave( lineToPitch( nRow ) );
1271 Note::Key pressedNoteKey = Note::pitchToKey( lineToPitch( nRow ) );
1272
1273 auto pDraggedNote = pPattern->find_note( nColumn, nRealColumn,
1274 pSelectedInstrument,
1275 pressedNoteKey, pressedOctave,
1276 false );
1277 }
1278 else if ( editor == Editor::DrumPattern ) {
1279 auto pSelectedInstrument = pSong->getInstrumentList()->get( nRow );
1280 if ( pSelectedInstrument == nullptr ) {
1281 ERRORLOG( "No instrument selected" );
1282 return;
1283 }
1284 pDraggedNote = pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument, false );
1285 }
1286 else {
1287 ERRORLOG( QString( "Unsupported editor [%1]" )
1288 .arg( static_cast<int>(editor) ) );
1290 return;
1291 }
1292
1293 if ( pDraggedNote != nullptr ){
1294 pDraggedNote->set_length( nLength );
1295 }
1296
1298
1299 pHydrogen->setIsModified( true );
1300
1301 if ( m_pPatternEditorPanel != nullptr ) {
1303 }
1304}
1305
1306
1308 int nRealColumn,
1309 int nRow,
1310 int nSelectedPatternNumber,
1311 int nSelectedInstrumentNumber,
1312 Mode mode,
1313 Editor editor,
1314 float fVelocity,
1315 float fPan,
1316 float fLeadLag,
1317 float fProbability )
1318{
1319
1320 auto pHydrogen = Hydrogen::get_instance();
1321 auto pSong = pHydrogen->getSong();
1322 auto pPatternList = pSong->getPatternList();
1323
1324 H2Core::Pattern* pPattern = nullptr;
1325 if ( nSelectedPatternNumber != -1 &&
1326 nSelectedPatternNumber < pPatternList->size() ) {
1327 pPattern = pPatternList->get( nSelectedPatternNumber );
1328 }
1329
1330 if ( pPattern == nullptr ) {
1331 return;
1332 }
1333
1335
1336 // Find the note to edit
1337 Note* pDraggedNote = nullptr;
1338 if ( editor == Editor::PianoRoll ) {
1339
1340 auto pSelectedInstrument =
1341 pSong->getInstrumentList()->get( nSelectedInstrumentNumber );
1342 if ( pSelectedInstrument == nullptr ) {
1343 ERRORLOG( "No instrument selected" );
1344 return;
1345 }
1346
1347 Note::Octave pressedOctave = Note::pitchToOctave( lineToPitch( nRow ) );
1348 Note::Key pressedNoteKey = Note::pitchToKey( lineToPitch( nRow ) );
1349
1350 pDraggedNote = pPattern->find_note( nColumn, nRealColumn,
1351 pSelectedInstrument,
1352 pressedNoteKey, pressedOctave,
1353 false );
1354 }
1355 else if ( editor == Editor::DrumPattern ) {
1356 auto pSelectedInstrument = pSong->getInstrumentList()->get( nRow );
1357 if ( pSelectedInstrument == nullptr ) {
1358 ERRORLOG( "No instrument selected" );
1359 return;
1360 }
1361 pDraggedNote = pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument, false );
1362 }
1363 else {
1364 ERRORLOG( QString( "Unsupported editor [%1]" )
1365 .arg( static_cast<int>(editor) ) );
1367 return;
1368 }
1369
1370 bool bValueChanged = true;
1371
1372 if ( pDraggedNote != nullptr ){
1373 switch ( mode ) {
1374 case Mode::Velocity:
1375 pDraggedNote->set_velocity( fVelocity );
1376 break;
1377 case Mode::Pan:
1378 pDraggedNote->setPan( fPan );
1379 break;
1380 case Mode::LeadLag:
1381 pDraggedNote->set_lead_lag( fLeadLag );
1382 break;
1383 case Mode::Probability:
1384 pDraggedNote->set_probability( fProbability );
1385 break;
1386 }
1387 bValueChanged = true;
1388 PatternEditor::triggerStatusMessage( pDraggedNote, mode );
1389 } else {
1390 ERRORLOG("note could not be found");
1391 }
1392
1394
1395 if ( bValueChanged &&
1396 m_pPatternEditorPanel != nullptr ) {
1397 pHydrogen->setIsModified( true );
1399 }
1400}
1401
1402
1404 QString s;
1405
1406 switch ( mode ) {
1408 s = "Velocity";
1409 break;
1411 s = "Pan";
1412 break;
1414 s = "LeadLag";
1415 break;
1417 s = "NoteKey";
1418 break;
1420 s = "Probability";
1421 break;
1422 default:
1423 s = QString( "Unknown mode [%1]" ).arg( static_cast<int>(mode) ) ;
1424 break;
1425 }
1426
1427 return s;
1428}
1429
1431 QString s;
1432 QString sCaller( _class_name() );
1433 QString sUnit( tr( "ticks" ) );
1434 float fValue;
1435
1436 switch ( mode ) {
1438 if ( ! pNote->get_note_off() ) {
1439 s = QString( tr( "Set note velocity" ) )
1440 .append( QString( ": [%1]")
1441 .arg( pNote->get_velocity(), 2, 'f', 2 ) );
1442 sCaller.append( ":Velocity" );
1443 }
1444 break;
1445
1447 if ( ! pNote->get_note_off() ) {
1448
1449 // Round the pan to not miss the center due to fluctuations
1450 fValue = pNote->getPan() * 100;
1451 fValue = std::round( fValue );
1452 fValue = fValue / 100;
1453
1454 if ( fValue > 0.0 ) {
1455 s = QString( tr( "Note panned to the right by" ) ).
1456 append( QString( ": [%1]" ).arg( fValue / 2, 2, 'f', 2 ) );
1457 } else if ( fValue < 0.0 ) {
1458 s = QString( tr( "Note panned to the left by" ) ).
1459 append( QString( ": [%1]" ).arg( -1 * fValue / 2, 2, 'f', 2 ) );
1460 } else {
1461 s = QString( tr( "Note centered" ) );
1462 }
1463 sCaller.append( ":Pan" );
1464 }
1465 break;
1466
1468 // Round the pan to not miss the center due to fluctuations
1469 fValue = pNote->get_lead_lag() * 100;
1470 fValue = std::round( fValue );
1471 fValue = fValue / 100;
1472 if ( fValue < 0.0 ) {
1473 s = QString( tr( "Leading beat by" ) )
1474 .append( QString( ": [%1] " )
1475 .arg( fValue * -1 *
1476 AudioEngine::getLeadLagInTicks() , 2, 'f', 2 ) )
1477 .append( sUnit );
1478 }
1479 else if ( fValue > 0.0 ) {
1480 s = QString( tr( "Lagging beat by" ) )
1481 .append( QString( ": [%1] " )
1482 .arg( fValue *
1483 AudioEngine::getLeadLagInTicks() , 2, 'f', 2 ) )
1484 .append( sUnit );
1485 }
1486 else {
1487 s = tr( "Note on beat" );
1488 }
1489 sCaller.append( ":LeadLag" );
1490 break;
1491
1493 s = QString( tr( "Set pitch" ) ).append( ": " ).append( tr( "key" ) )
1494 .append( QString( " [%1], " ).arg( Note::KeyToQString( pNote->get_key() ) ) )
1495 .append( tr( "octave" ) )
1496 .append( QString( ": [%1]" ).arg( pNote->get_octave() ) );
1497 sCaller.append( ":NoteKey" );
1498 break;
1499
1501 if ( ! pNote->get_note_off() ) {
1502 s = tr( "Set note probability to" )
1503 .append( QString( ": [%1]" ).arg( pNote->get_probability(), 2, 'f', 2 ) );
1504 }
1505 sCaller.append( ":Probability" );
1506 break;
1507
1508 default:
1510 }
1511
1512 if ( ! s.isEmpty() ) {
1514 }
1515}
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
Definition AudioEngine.h:59
#define OCTAVE_OFFSET
Definition Note.h:36
#define ERRORLOG(x)
Definition Object.h:239
#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
const PatternList * getNextPatterns() const
void unlock()
Mutex unlocking of the AudioEngine.
void lock(const char *file, unsigned int line, const char *function)
Mutex locking of the AudioEngine.
const PatternList * getPlayingPatterns() const
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:122
int getSelectedInstrumentNumber() const
Definition Hydrogen.h:664
Song::Mode getMode() const
int getSelectedPatternNumber() const
Definition Hydrogen.h:660
void setSelectedInstrumentNumber(int nInstrument, bool bTriggerEvent=true)
Definition Hydrogen.cpp:918
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:83
Song::PatternMode getPatternMode() const
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:649
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:102
int get_position() const
__position accessor
Definition Note.h:535
static Key pitchToKey(int nPitch)
Definition Note.h:400
void set_length(int value)
__length setter
Definition Note.h:555
float getPanWithRangeFrom0To1() const
get pan of the note, scaling and translating the range from [-1;1] to [0;1]
Definition Note.h:197
void set_lead_lag(float value)
__lead_lag setter
Definition Note.cpp:148
static QString KeyToQString(Key key)
Definition Note.cpp:652
static Octave pitchToOctave(int nPitch)
Definition Note.h:393
Octave get_octave()
__octave accessor
Definition Note.h:677
void set_probability(float value)
Definition Note.h:615
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
Definition Note.h:500
float get_lead_lag() const
__lead_lag accessor
Definition Note.h:550
int get_length() const
__length accessor
Definition Note.h:560
float get_notekey_pitch() const
note key pitch accessor
Definition Note.h:695
float get_velocity() const
__velocity accessor
Definition Note.h:540
void setPanWithRangeFrom0To1(float fVal)
set pan of the note, assuming the input range in [0;1]
Definition Note.h:191
Key
possible keys
Definition Note.h:106
bool get_note_off() const
__note_off accessor
Definition Note.h:580
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:388
Key get_key()
__key accessor
Definition Note.h:672
float get_probability() const
Definition Note.h:610
Octave
possible octaves
Definition Note.h:110
float getPan() const
get pan of the note.
Definition Note.h:545
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
int size() const
returns the numbers of patterns
Pattern * get(int idx)
get a pattern from the list
Pattern class is a Note container.
Definition Pattern.h:46
const virtual_patterns_t * get_virtual_patterns() const
get the flattened virtual pattern set
Definition Pattern.h:360
int get_length() const
set the denominator of the pattern
Definition Pattern.h:340
const notes_t * get_notes() const
get the virtual pattern set
Definition Pattern.h:355
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:217
bool isVirtual() const
Whether the pattern holds at least one virtual pattern.
Definition Pattern.cpp:345
int longestVirtualPatternLength() const
Definition Pattern.cpp:333
void insert_note(Note *note)
insert a new note within __notes
Definition Pattern.h:370
Manager for User Preferences File (singleton)
Definition Preferences.h:78
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.
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:391
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:63
void write_int(const QString &node, const int value)
write an integer into a child node
Definition Xml.cpp:284
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.
Pattern Editor Panel.
NotePropertiesRuler::Mode getNotePropertiesMode() const
void setCursorPosition(int nCursorPosition)
void updateEditors(bool bPatternOnly=false)
PatternEditorInstrumentList * getInstrumentList()
PatternEditorRuler * getPatternEditorRuler()
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? They match if all user-editable ...
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.
void addToSelection(Elem e)
Definition Selection.h:346
void mouseReleaseEvent(QMouseEvent *ev)
Definition Selection.h:465
void removeFromSelection(Elem e, bool bCheck=true)
Definition Selection.h:339
void updateWidgetGroup()
Update any widgets in this selection group.
Definition Selection.h:375
void clearSelection(bool bCheck=true)
Definition Selection.h:350
void cancelGesture()
Cancel any selection gesture (lasso, move, with keyboard or mouse) in progress.
Definition Selection.h:382
bool isMouseGesture() const
Is there a mouse gesture in progress?
Definition Selection.h:319
bool isSelected(Elem e) const
Is an element in the set of currently selected elements?
Definition Selection.h:335
void mousePressEvent(QMouseEvent *ev)
Definition Selection.h:410
bool isMoving() const
Is there an ongoing (and incomplete) selection movement gesture?
Definition Selection.h:314
QPoint movingOffset() const
During a selection "move" gesture, return the current movement position relative to the start positio...
Definition Selection.h:325
void mouseMoveEvent(QMouseEvent *ev)
Definition Selection.h:451
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