hydrogen 1.2.3
DrumPatternEditor.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
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 "DrumPatternEditor.h"
24#include "PatternEditorPanel.h"
25#include "PatternEditorRuler.h"
27#include "../CommonStrings.h"
28
29#include <core/Globals.h>
30#include <core/Basics/Song.h>
31#include <core/Hydrogen.h>
32#include <core/EventQueue.h>
37#include <core/Basics/Pattern.h>
39#include <core/Basics/Adsr.h>
40#include <core/Basics/Note.h>
42#include <core/Helpers/Xml.h>
44
45#include "UndoActions.h"
46#include "../HydrogenApp.h"
47#include "../Mixer/Mixer.h"
48#include "../Skin.h"
49
50#include <math.h>
51#include <cassert>
52#include <algorithm>
53#include <stack>
54
55using namespace H2Core;
56
71
75
76void DrumPatternEditor::updateEditor( bool bPatternOnly )
77{
78 auto pHydrogen = H2Core::Hydrogen::get_instance();
79 auto pAudioEngine = pHydrogen->getAudioEngine();
80 if ( pAudioEngine->getState() != H2Core::AudioEngine::State::Ready &&
81 pAudioEngine->getState() != H2Core::AudioEngine::State::Playing ) {
82 ERRORLOG( "FIXME: skipping pattern editor update (state should be READY or PLAYING)" );
83 return;
84 }
85
88
89 auto pSong = pHydrogen->getSong();
90 int nInstruments = pSong->getInstrumentList()->size();
91
92 if ( m_nEditorHeight != (int)( m_nGridHeight * nInstruments ) ) {
93 // the number of instruments is changed...recreate all
94 m_nEditorHeight = m_nGridHeight * nInstruments;
95 }
97
98 // redraw all
100 update();
101}
102
103
104void DrumPatternEditor::addOrRemoveNote( int nColumn, int nRealColumn, int nRow,
105 bool bDoAdd, bool bDoDelete,
106 bool bIsNoteOff ) {
107
108 if ( m_pPattern == nullptr || m_nSelectedPatternNumber == -1 ) {
109 // No pattern selected.
110 return;
111 }
112
113 auto pSong = Hydrogen::get_instance()->getSong();
114 if ( pSong == nullptr ) {
115 ERRORLOG( "No song set" );
116 return;
117 }
118
119 auto pSelectedInstrument = pSong->getInstrumentList()->get( nRow );
120 if ( pSelectedInstrument == nullptr ) {
121 ERRORLOG( QString( "Couldn't find instrument [%1]" )
122 .arg( nRow ) );
123 return;
124 }
125
126 H2Core::Note *pOldNote = m_pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument );
127
128 int oldLength = -1;
129 float oldVelocity = 0.8f;
130 float fOldPan = 0.f;
131 float oldLeadLag = 0.0f;
132 float fProbability = 1.0f;
133 Note::Key oldNoteKeyVal = Note::C;
134 Note::Octave oldOctaveKeyVal = Note::P8;
135 bool isNoteOff = bIsNoteOff;
136
137 if ( pOldNote && !bDoDelete ) {
138 // Found an old note, but we don't want to delete, so just return.
139 return;
140 } else if ( !pOldNote && !bDoAdd ) {
141 // No note there, but we don't want to add a new one, so return.
142 return;
143 }
144
145 if ( pOldNote ) {
146 oldLength = pOldNote->get_length();
147 oldVelocity = pOldNote->get_velocity();
148 fOldPan = pOldNote->getPan();
149 oldLeadLag = pOldNote->get_lead_lag();
150 oldNoteKeyVal = pOldNote->get_key();
151 oldOctaveKeyVal = pOldNote->get_octave();
152 isNoteOff = pOldNote->get_note_off();
153 fProbability = pOldNote->get_probability();
154 }
155
157 nRow,
159 oldLength,
160 oldVelocity,
161 fOldPan,
162 oldLeadLag,
163 oldNoteKeyVal,
164 oldOctaveKeyVal,
165 fProbability,
166 pOldNote != nullptr,
167 Preferences::get_instance()->getHearNewNotes(),
168 false,
169 false,
170 isNoteOff );
171
172 HydrogenApp::get_instance()->m_pUndoStack->push( action );
173
174
175}
176
177
179{
180 auto pHydrogenApp = HydrogenApp::get_instance();
181 Hydrogen *pHydrogen = Hydrogen::get_instance();
182 if ( m_pPattern == nullptr || m_nSelectedPatternNumber == -1 ) {
183 return;
184 }
185 std::shared_ptr<Song> pSong = pHydrogen->getSong();
186 if ( pSong == nullptr ) {
187 return;
188 }
189 int nInstruments = pSong->getInstrumentList()->size();
190 int row = (int)( ev->y() / (float)m_nGridHeight);
191 if (row >= nInstruments) {
192 return;
193 }
194 int nColumn = getColumn( ev->x(), /* bUseFineGrained=*/ true );
195 int nRealColumn = 0;
196
197 if( ev->x() > PatternEditor::nMargin ) {
198 nRealColumn = ( ev->x() - PatternEditor::nMargin) / static_cast<float>(m_fGridWidth);
199 }
200
201 if ( nColumn >= (int)m_pPattern->get_length() ) {
202 return;
203 }
204 auto pSelectedInstrument = pSong->getInstrumentList()->get( row );
205 if ( pSelectedInstrument == nullptr ) {
206 ERRORLOG( QString( "Couldn't find instrument [%1]" )
207 .arg( row ) );
208 return;
209 }
210
211 if ( ev->button() == Qt::LeftButton ) {
212
213 // Pressing Shift causes the added note to be of NoteOff type.
214 addOrRemoveNote( nColumn, nRealColumn, row, true, true,
215 ev->modifiers() & Qt::ShiftModifier );
217
218 } else if ( ev->button() == Qt::RightButton ) {
219
220 m_pPopupMenu->popup( ev->globalPos() );
221 }
222
224
225 // Cursor either just got hidden or was moved.
226 if ( ! pHydrogenApp->hideKeyboardCursor() ) {
227 // Immediate update to prevent visual delay.
230 }
231 update();
232}
233
234void DrumPatternEditor::mousePressEvent( QMouseEvent* ev ) {
235
236 if ( ev->x() > m_nActiveWidth ) {
237 return;
238 }
239
241
242 auto pHydrogenApp = HydrogenApp::get_instance();
243 auto pHydrogen = Hydrogen::get_instance();
244 auto pSong = pHydrogen->getSong();
245 int nInstruments = pSong->getInstrumentList()->size();
246 int nRow = static_cast<int>( ev->y() / static_cast<float>(m_nGridHeight) );
247 if ( nRow >= nInstruments || nRow < 0 ) {
248 return;
249 }
250
251 pHydrogen->setSelectedInstrumentNumber( nRow );
252
253 // Hide cursor in case this behavior was selected in the
254 // Preferences.
255 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
256 pHydrogenApp->setHideKeyboardCursor( true );
257
258 // Cursor just got hidden.
259 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
260 // Immediate update to prevent visual delay.
263 update();
264 }
265
266 // Update cursor position
267 if ( ! HydrogenApp::get_instance()->hideKeyboardCursor() ) {
268 int nColumn = getColumn( ev->x(), /* bUseFineGrained=*/ true );
269 if ( ( m_pPattern != nullptr &&
270 nColumn >= (int)m_pPattern->get_length() ) ||
271 nColumn >= MAX_INSTRUMENTS ) {
272 return;
273 }
274
275 pHydrogen->setSelectedInstrumentNumber( nRow );
277
278 update();
281 }
282}
283
284
286{
287 if ( m_pPattern == nullptr ) {
288 return;
289 }
290
291 auto pHydrogen = Hydrogen::get_instance();
292 auto pSong = pHydrogen->getSong();
293
294 // Set the selected instrument _before_ it will be stored in
295 // PatternEditor::mouseDragStartEvent.
296 int nRow = std::floor(static_cast<float>(ev->y()) /
297 static_cast<float>(m_nGridHeight));
298 pHydrogen->setSelectedInstrumentNumber( nRow );
299 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
300
301 if ( pSelectedInstrument == nullptr ) {
302 ERRORLOG( QString( "Couldn't find instrument [%1]" )
303 .arg( nRow ) );
304 return;
305 }
306
307 // Handles cursor repositioning and hiding and stores general
308 // properties.
310
311 int nColumn = getColumn( ev->x() );
312
313 if ( ev->button() == Qt::RightButton ) {
314 // Right button drag: adjust note length
315 int nRealColumn = 0;
316
317 if( ev->x() > PatternEditor::nMargin ) {
318 nRealColumn =
319 static_cast<int>(std::floor(
320 static_cast<float>((ev->x() - PatternEditor::nMargin)) /
321 m_fGridWidth));
322 }
323
324 m_pDraggedNote = m_pPattern->find_note( nColumn, nRealColumn,
325 pSelectedInstrument, false );
326
327 // Store note-specific properties.
329
330 m_nRow = nRow;
331 }
332}
333
338{
339 int nRow = MAX_INSTRUMENTS - 1 -
340 static_cast<int>(std::floor(static_cast<float>(ev->y()) /
341 static_cast<float>(m_nGridHeight)));
342 if ( nRow >= MAX_INSTRUMENTS ) {
343 return;
344 }
345
347}
348
350 int nInstrumentRow,
351 int nSelectedPatternNumber,
352 int oldLength,
353 float oldVelocity,
354 float fOldPan,
355 float oldLeadLag,
356 int oldNoteKeyVal,
357 int oldOctaveKeyVal,
358 float fProbability,
359 bool listen,
360 bool isMidi,
361 bool isInstrumentMode, // TODO not used arg
362 bool isNoteOff,
363 bool isDelete )
364{
365 Hydrogen *pHydrogen = Hydrogen::get_instance();
366 auto pSong = pHydrogen->getSong();
367
368 if ( pSong == nullptr ) {
369 ERRORLOG( "No song set yet" );
370 return;
371 }
372
373 PatternList *pPatternList = pSong->getPatternList();
374
375 if ( nSelectedPatternNumber < 0 ||
376 nSelectedPatternNumber >= pPatternList->size() ) {
377 ERRORLOG( QString( "Invalid pattern number [%1]" )
378 .arg( nSelectedPatternNumber ) );
379 return;
380 }
381
382 auto pPattern = pPatternList->get( nSelectedPatternNumber );
383 if ( pPattern == nullptr ) {
384 ERRORLOG( QString( "Pattern found for pattern number [%1] is not valid" )
385 .arg( nSelectedPatternNumber ) );
386 return;
387 }
388
389 auto pSelectedInstrument = pSong->getInstrumentList()->get( nInstrumentRow );
390 if ( pSelectedInstrument == nullptr ) {
391 ERRORLOG( QString( "Couldn't find instrument [%1]" )
392 .arg( nInstrumentRow ) );
393 return;
394 }
395
396 m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine
397
398 if ( isDelete ) {
399
400 // Find and delete an existing (matching) note.
401 Pattern::notes_t *notes = (Pattern::notes_t *)pPattern->get_notes();
402 bool bFound = false;
403 FOREACH_NOTE_IT_BOUND_END( notes, it, nColumn ) {
404 Note *pNote = it->second;
405 if ( pNote == nullptr ) {
406 ERRORLOG( "Invalid note" );
407 continue;
408 }
409 if ( pNote->get_instrument()->get_id() == pSelectedInstrument->get_id() &&
410 ( ( isNoteOff && pNote->get_note_off() ) ||
411 ( pNote->get_key() == oldNoteKeyVal &&
412 pNote->get_octave() == oldOctaveKeyVal &&
413 pNote->get_velocity() == oldVelocity &&
414 pNote->get_probability() == fProbability ) ) ) {
415 notes->erase( it );
416 delete pNote;
417 bFound = true;
418 break;
419 }
420 }
421 if ( !bFound ) {
422 ERRORLOG( "Did not find note to delete" );
423 }
424
425 } else {
426 // create the new note
427 unsigned nPosition = nColumn;
428 float fVelocity = oldVelocity;
429 float fPan = fOldPan ;
430 int nLength = oldLength;
431
432
433 if ( isNoteOff ) {
434 fVelocity = 0.0f;
435 fPan = 0.f;
436 nLength = 1;
437 fProbability = 1.0;
438 }
439
440 Note *pNote = new Note( pSelectedInstrument, nPosition, fVelocity, fPan, nLength );
441 pNote->set_note_off( isNoteOff );
442 if ( !isNoteOff ) {
443 pNote->set_lead_lag( oldLeadLag );
444 pNote->set_probability( fProbability );
445 }
446 pNote->set_key_octave( (Note::Key)oldNoteKeyVal, (Note::Octave)oldOctaveKeyVal );
447 pPattern->insert_note( pNote );
448
449 if ( m_bSelectNewNotes ) {
451 }
452
453 if ( isMidi ) {
454 pNote->set_just_recorded(true);
455 }
456 // hear note
457 if ( listen && !isNoteOff && pSelectedInstrument->hasSamples() ) {
458 Note *pNote2 = new Note( pSelectedInstrument, 0, fVelocity, fPan, nLength);
459 m_pAudioEngine->getSampler()->noteOn(pNote2);
460 }
461 }
462 pHydrogen->setIsModified( true );
463 m_pAudioEngine->unlock(); // unlock the audio engine
464
466}
467
468
469// Find a note that matches pNote, and move it from (nColumn, nRow) to (nNewColumn, nNewRow)
471 int nRow,
472 int nPattern,
473 int nNewColumn,
474 int nNewRow,
475 Note *pNote)
476{
477 if ( m_pPattern == nullptr ) {
478 return;
479 }
480
481 Hydrogen *pHydrogen = Hydrogen::get_instance();
482 std::shared_ptr<Song> pSong = pHydrogen->getSong();
483
485 PatternList *pPatternList = pSong->getPatternList();
486 auto pInstrumentList = pSong->getInstrumentList();
487 Pattern *pPattern = m_pPattern;
488 Note *pFoundNote = nullptr;
489
490 if ( nPattern < 0 || nPattern > pPatternList->size() ) {
491 ERRORLOG( "Invalid pattern number" );
493 return;
494 }
495
496 auto pFromInstrument = pInstrumentList->get( nRow );
497 auto pToInstrument = pInstrumentList->get( nNewRow );
498
499 FOREACH_NOTE_IT_BOUND_END((Pattern::notes_t *)pPattern->get_notes(), it, nColumn) {
500 Note *pCandidateNote = it->second;
501 if ( pCandidateNote->get_instrument() == pFromInstrument
502 && pCandidateNote->get_key() == pNote->get_key()
503 && pCandidateNote->get_octave() == pNote->get_octave()
504 && pCandidateNote->get_velocity() == pNote->get_velocity()
505 && pCandidateNote->get_lead_lag() == pNote->get_lead_lag()
506 && pCandidateNote->getPan() == pNote->getPan()
507 && pCandidateNote->get_note_off() == pNote->get_note_off() ) {
508 pFoundNote = pCandidateNote;
509 if ( m_selection.isSelected( pCandidateNote ) ) {
510 // If a candidate note is in the selection, this will be the one to move.
511 break;
512 }
513 }
514 }
515 if ( pFoundNote == nullptr ) {
516 ERRORLOG( "Couldn't find note to move" );
518 return;
519 }
520
521 pPattern->remove_note( pFoundNote );
522 if ( pFromInstrument == pToInstrument ) {
523 // Note can simply be moved.
524 pFoundNote->set_position( nNewColumn );
525 pPattern->insert_note( pFoundNote );
526 } else {
527 pPattern->remove_note( pFoundNote );
528 Note *pNewNote = new Note( pFoundNote, pToInstrument );
529
530 if ( m_selection.isSelected( pFoundNote) ) {
531 m_selection.removeFromSelection( pFoundNote, /* bCheck=*/false );
532 m_selection.addToSelection( pNewNote );
533 }
534 pNewNote->set_position( nNewColumn );
535 m_selection.addToSelection( pNewNote );
536 pPattern->insert_note( pNewNote );
537 delete pFoundNote;
538 }
539
540 pHydrogen->setIsModified( true );
542
544}
545
553{
554 if ( m_pPattern == nullptr || m_nSelectedPatternNumber == -1 ) {
555 // No pattern selected.
556 return;
557 }
558
559 updateModifiers( ev );
560 QPoint offset = movingGridOffset();
561 if ( offset.x() == 0 && offset.y() == 0 ) {
562 // Move with no effect.
563 return;
564 }
565 auto pInstrumentList = Hydrogen::get_instance()->getSong()->getInstrumentList();
566
568
569 QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack;
570 if (m_bCopyNotMove) {
571 pUndo->beginMacro( "copy notes" );
572 } else {
573 pUndo->beginMacro( "move notes" );
574 }
575 std::list< Note * > selectedNotes;
576 for ( auto pNote : m_selection ) {
577 selectedNotes.push_back( pNote );
578 }
579
580 if ( m_bCopyNotMove ) {
581 // Clear selection so the new notes can be selection instead
582 // of the originals.
584 }
585
586 m_bSelectNewNotes = true;
587
588 for ( auto pNote : selectedNotes ) {
589 int nInstrument = pInstrumentList->index( pNote->get_instrument() );
590 int nPosition = pNote->get_position();
591 int nNewInstrument = nInstrument + offset.y();
592 int nNewPosition = nPosition + offset.x();
593 if ( nNewInstrument < 0 || nNewInstrument >= pInstrumentList->size()
594 || nNewPosition < 0 || nNewPosition >= m_pPattern->get_length() ) {
595
596 if ( m_bCopyNotMove ) {
597 // Copying a note to an out-of-range location. Nothing to do.
598 } else {
599 // Note is moved out of range. Delete it.
600 pUndo->push( new SE_addOrDeleteNoteAction( nPosition,
601 nInstrument,
603 pNote->get_length(),
604 pNote->get_velocity(),
605 pNote->getPan(),
606 pNote->get_lead_lag(),
607 pNote->get_key(),
608 pNote->get_octave(),
609 pNote->get_probability(),
610 true,
611 false,
612 false,
613 false,
614 pNote->get_note_off() ) );
615 }
616
617 } else {
618 if ( m_bCopyNotMove ) {
619 // Copy note to a new note.
620 pUndo->push( new SE_addOrDeleteNoteAction( nNewPosition,
621 nNewInstrument,
623 pNote->get_length(),
624 pNote->get_velocity(),
625 pNote->getPan(),
626 pNote->get_lead_lag(),
627 pNote->get_key(),
628 pNote->get_octave(),
629 pNote->get_probability(),
630 false,
631 false,
632 false,
633 false,
634 pNote->get_note_off() ) );
635 } else {
636 // Move note
637 pUndo->push( new SE_moveNoteAction( nPosition, nInstrument, m_nSelectedPatternNumber,
638 nNewPosition, nNewInstrument, pNote ) );
639 }
640 }
641 }
642 m_bSelectNewNotes = false;
643 pUndo->endMacro();
644}
645
646
653{
654 if ( m_pPattern == nullptr ) {
655 return;
656 }
657
658 auto pHydrogenApp = HydrogenApp::get_instance();
659 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
660
661 const int nBlockSize = 5, nWordSize = 5;
662 Hydrogen *pHydrogen = Hydrogen::get_instance();
663 int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber();
664 int nMaxInstrument = pHydrogen->getSong()->getInstrumentList()->size();
665 bool bUnhideCursor = true;
666
667 bool bIsSelectionKey = m_selection.keyPressEvent( ev );
668 updateModifiers( ev );
669
670 if ( bIsSelectionKey ) {
671 // Key was claimed by Selection
672 } else if ( ev->matches( QKeySequence::MoveToNextChar ) || ev->matches( QKeySequence::SelectNextChar ) ) {
673 // ->
675
676 } else if ( ev->matches( QKeySequence::MoveToNextWord ) || ev->matches( QKeySequence::SelectNextWord ) ) {
677 // ->
679
680 } else if ( ev->matches( QKeySequence::MoveToEndOfLine ) || ev->matches( QKeySequence::SelectEndOfLine ) ) {
681 // -->|
683
684 } else if ( ev->matches( QKeySequence::MoveToPreviousChar ) || ev->matches( QKeySequence::SelectPreviousChar ) ) {
685 // <-
687
688 } else if ( ev->matches( QKeySequence::MoveToPreviousWord ) || ev->matches( QKeySequence::SelectPreviousWord ) ) {
689 // <-
691
692 } else if ( ev->matches( QKeySequence::MoveToStartOfLine ) || ev->matches( QKeySequence::SelectStartOfLine ) ) {
693 // |<--
695
696 } else if ( ev->matches( QKeySequence::MoveToNextLine ) || ev->matches( QKeySequence::SelectNextLine ) ) {
697 if ( nSelectedInstrument + 1 < nMaxInstrument ) {
698 pHydrogen->setSelectedInstrumentNumber( nSelectedInstrument + 1 );
699 }
700 } else if ( ev->matches( QKeySequence::MoveToEndOfBlock ) || ev->matches( QKeySequence::SelectEndOfBlock ) ) {
701 pHydrogen->setSelectedInstrumentNumber( std::min( nSelectedInstrument + nBlockSize,
702 nMaxInstrument-1 ) );
703
704 } else if ( ev->matches( QKeySequence::MoveToNextPage ) || ev->matches( QKeySequence::SelectNextPage ) ) {
705 // Page down, scroll by the number of instruments that fit into the viewport
706 QWidget *pParent = dynamic_cast< QWidget *>( parent() );
707 assert( pParent );
708 nSelectedInstrument += pParent->height() / m_nGridHeight;
709
710 if ( nSelectedInstrument >= nMaxInstrument ) {
711 nSelectedInstrument = nMaxInstrument - 1;
712 }
713 pHydrogen->setSelectedInstrumentNumber( nSelectedInstrument );
714
715 } else if ( ev->matches( QKeySequence::MoveToEndOfDocument ) || ev->matches( QKeySequence::SelectEndOfDocument ) ) {
716 pHydrogen->setSelectedInstrumentNumber( nMaxInstrument-1 );
717
718 } else if ( ev->matches( QKeySequence::MoveToPreviousLine ) || ev->matches( QKeySequence::SelectPreviousLine ) ) {
719 if ( nSelectedInstrument > 0 ) {
720 pHydrogen->setSelectedInstrumentNumber( nSelectedInstrument - 1 );
721 }
722 } else if ( ev->matches( QKeySequence::MoveToStartOfBlock ) || ev->matches( QKeySequence::SelectStartOfBlock ) ) {
723 pHydrogen->setSelectedInstrumentNumber( std::max( nSelectedInstrument - nBlockSize, 0 ) );
724
725 } else if ( ev->matches( QKeySequence::MoveToPreviousPage ) || ev->matches( QKeySequence::SelectPreviousPage ) ) {
726 QWidget *pParent = dynamic_cast< QWidget *>( parent() );
727 assert( pParent );
728 nSelectedInstrument -= pParent->height() / m_nGridHeight;
729 if ( nSelectedInstrument < 0 ) {
730 nSelectedInstrument = 0;
731 }
732 pHydrogen->setSelectedInstrumentNumber( nSelectedInstrument );
733
734 } else if ( ev->matches( QKeySequence::MoveToStartOfDocument ) || ev->matches( QKeySequence::SelectStartOfDocument ) ) {
735 pHydrogen->setSelectedInstrumentNumber( 0 );
736
737 } else if ( ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return ) {
738 // Key: Enter / Return: add or remove note at current position
740 addOrRemoveNote( m_pPatternEditorPanel->getCursorPosition(), -1, nSelectedInstrument );
741
742 } else if ( ev->key() == Qt::Key_Delete ) {
743 // Key: Delete / Backspace: delete selected notes, or note under keyboard cursor
744 bUnhideCursor = false;
745 if ( m_selection.begin() != m_selection.end() ) {
746 // Delete selected notes if any
748 } else {
749 // Delete note under the keyboard cursor.
750 addOrRemoveNote( m_pPatternEditorPanel->getCursorPosition(), -1, nSelectedInstrument,
751 /*bDoAdd=*/false, /*bDoDelete=*/true);
752
753 }
754
755 } else if ( ev->matches( QKeySequence::SelectAll ) ) {
756 bUnhideCursor = false;
757 selectAll();
758
759 } else if ( ev->matches( QKeySequence::Deselect ) ) {
760 bUnhideCursor = false;
761 selectNone();
762
763 } else if ( ev->matches( QKeySequence::Copy ) ) {
764 bUnhideCursor = false;
765 copy();
766
767 } else if ( ev->matches( QKeySequence::Paste ) ) {
768 bUnhideCursor = false;
769 paste();
770
771 } else if ( ev->matches( QKeySequence::Cut ) ) {
772 bUnhideCursor = false;
773 cut();
774
775 } else {
776 ev->ignore();
777 pHydrogenApp->setHideKeyboardCursor( true );
778
779 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
782 update();
783 }
784 return;
785 }
786 if ( bUnhideCursor ) {
787 pHydrogenApp->setHideKeyboardCursor( false );
788 }
791
792 if ( m_selection.isLasso() ) {
793 // Since event was used to alter the note selection, we invalidate
794 // background and force a repainting of all note symbols (including
795 // whether or not they are selected).
797 }
798
799 if ( ! pHydrogenApp->hideKeyboardCursor() ) {
800 // Immediate update to prevent visual delay.
803 }
804 update();
805 ev->accept();
806
807}
808
810 updateModifiers( ev );
811}
812
813
814
818std::vector<DrumPatternEditor::SelectionIndex> DrumPatternEditor::elementsIntersecting( QRect r )
819{
820 std::vector<SelectionIndex> result;
821 if ( m_pPattern == nullptr ) {
822 return std::move( result );
823 }
824
825 std::shared_ptr<Song> pSong = Hydrogen::get_instance()->getSong();
826 auto pInstrList = pSong->getInstrumentList();
827 uint h = m_nGridHeight / 3;
828
829 // Expand the region by approximately the size of the note
830 // ellipse, equivalent to testing for intersection between `r'
831 // and the equivalent rect around the note. We'll also allow
832 // a few extra pixels if it's a single point click, to make it
833 // easier to grab notes.
834
835 r = r.normalized();
836 if ( r.top() == r.bottom() && r.left() == r.right() ) {
837 r += QMargins( 2, 2, 2, 2 );
838 }
839 r += QMargins( 4, h/2, 4, h/2 );
840
841
842 // Calculate the first and last position values that this rect will intersect with
843 int x_min = (r.left() - PatternEditor::nMargin - 1) / m_fGridWidth;
844 int x_max = (r.right() - PatternEditor::nMargin) / m_fGridWidth;
845
846 const Pattern::notes_t* notes = m_pPattern->get_notes();
847
848 for (auto it = notes->lower_bound( x_min ); it != notes->end() && it->first <= x_max; ++it ) {
849 Note *note = it->second;
850 int nInstrument = pInstrList->index( note->get_instrument() );
851 uint x_pos = PatternEditor::nMargin + (it->first * m_fGridWidth);
852 uint y_pos = ( nInstrument * m_nGridHeight) + (m_nGridHeight / 2) - 3;
853
854 if ( r.contains( QPoint( x_pos, y_pos + h/2) ) ) {
855 result.push_back( note );
856 }
857 }
858
859 return std::move( result );
860}
861
866{
867
869 int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber();
870 uint y = nSelectedInstrument * m_nGridHeight;
871 return QRect( x-m_fGridWidth*3, y+2, m_fGridWidth*6, m_nGridHeight-3 );
872
873}
874
876{
877 if ( m_pPattern == nullptr ) {
878 return;
879 }
880
883 m_selection.addToSelection( it->second );
884 }
886}
887
888
890{
891 if ( m_nSelectedPatternNumber == -1 ) {
892 // No pattern selected.
893 return;
894 }
895
896 if ( m_selection.begin() != m_selection.end() ) {
897 // Selection exists, delete it.
898 Hydrogen *pHydrogen = Hydrogen::get_instance();
899 auto pInstrumentList = pHydrogen->getSong()->getInstrumentList();
900 QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack;
902
903 // Construct list of UndoActions to perform before performing any of them, as the
904 // addOrDeleteNoteAction may delete duplicate notes in undefined order.
905 std::list< QUndoCommand *> actions;
906 for ( Note *pNote : m_selection ) {
907 if ( m_selection.isSelected( pNote ) ) {
908 actions.push_back( new SE_addOrDeleteNoteAction( pNote->get_position(),
909 pInstrumentList->index( pNote->get_instrument() ),
911 pNote->get_length(),
912 pNote->get_velocity(),
913 pNote->getPan(),
914 pNote->get_lead_lag(),
915 pNote->get_key(),
916 pNote->get_octave(),
917 pNote->get_probability(),
918 true, // noteExisted
919 false, // listen
920 false,
921 false,
922 pNote->get_note_off() ) );
923 }
924 }
926
927 pUndo->beginMacro("delete notes");
928 for ( QUndoCommand *pAction : actions ) {
929 pUndo->push( pAction );
930 }
931 pUndo->endMacro();
932 }
933}
934
935
942{
943 if ( m_pPattern == nullptr || m_nSelectedPatternNumber == -1 ) {
944 // No pattern selected.
945 return;
946 }
947
948 QClipboard *clipboard = QApplication::clipboard();
949 QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack;
950 auto pInstrList = Hydrogen::get_instance()->getSong()->getInstrumentList();
951 XMLNode noteList;
952 int nDeltaPos = 0, nDeltaInstrument = 0;
953
954 XMLDoc doc;
955 if ( ! doc.setContent( clipboard->text() ) ) {
956 // Pasted something that's not valid XML.
957 return;
958 }
959
960 XMLNode selection = doc.firstChildElement( "noteSelection" );
961 if ( ! selection.isNull() ) {
962 // Found a noteSelection. Structure is:
963 // <noteSelection>
964 // <noteList>
965 // <note> ...
966 noteList = selection.firstChildElement( "noteList" );
967 if ( noteList.isNull() ) {
968 return;
969 }
970
971 XMLNode positionNode = selection.firstChildElement( "sourcePosition" );
972
973 // If position information is supplied in the selection, use
974 // it to adjust the location relative to the current keyboard
975 // input cursor.
976 if ( !positionNode.isNull() ) {
977 int nCurrentPos = m_pPatternEditorPanel->getCursorPosition();
978 int nCurrentInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber();
979
980 nDeltaPos = nCurrentPos - positionNode.read_int( "position", nCurrentPos );
981 nDeltaInstrument = nCurrentInstrument - positionNode.read_int( "instrument", nCurrentInstrument );
982 }
983
984 } else {
985 XMLNode instrumentLine = doc.firstChildElement( "instrument_line" );
986 if ( ! instrumentLine.isNull() ) {
987 // Found 'instrument_line', structure is:
988 // <instrument_line>
989 // <patternList>
990 // <pattern>
991 // <noteList>
992 // <note> ...
993 XMLNode patternList = instrumentLine.firstChildElement( "patternList" );
994 if ( patternList.isNull() ) {
995 return;
996 }
997 XMLNode pattern = patternList.firstChildElement( "pattern" );
998 if ( pattern.isNull() ) {
999 return;
1000 }
1001 // Don't attempt to paste multiple patterns
1002 if ( ! pattern.nextSiblingElement( "pattern" ).isNull() ) {
1003 QMessageBox::information( this, "Hydrogen", tr( "Cannot paste multi-pattern selection" ) );
1004 return;
1005 }
1006 noteList = pattern.firstChildElement( "noteList" );
1007 if ( noteList.isNull() ) {
1008 return;
1009 }
1010 }
1011 }
1012
1014 m_bSelectNewNotes = true;
1015
1016 if ( noteList.hasChildNodes() ) {
1017
1018 pUndo->beginMacro( "paste notes" );
1019 for ( XMLNode n = noteList.firstChildElement( "note" ); ! n.isNull(); n = n.nextSiblingElement() ) {
1020 Note *pNote = Note::load_from( &n, pInstrList );
1021 int nPos = pNote->get_position() + nDeltaPos;
1022 int nInstrument = pInstrList->index( pNote->get_instrument() ) + nDeltaInstrument;
1023
1024 if ( nPos >= 0 && nPos < m_pPattern->get_length()
1025 && nInstrument >= 0 && nInstrument < pInstrList->size() ) {
1026 pUndo->push( new SE_addOrDeleteNoteAction( nPos,
1027 nInstrument,
1029 pNote->get_length(),
1030 pNote->get_velocity(),
1031 pNote->getPan(),
1032 pNote->get_lead_lag(),
1033 pNote->get_key(),
1034 pNote->get_octave(),
1035 pNote->get_probability(),
1036 false, // isDelete
1037 false, // listen
1038 false, // isMidi
1039 false, // isInstrumentMode
1040 pNote->get_note_off()
1041 ) );
1042 }
1043 delete pNote;
1044 }
1045 pUndo->endMacro();
1046 }
1047
1048 m_bSelectNewNotes = false;
1049}
1050
1051
1055void DrumPatternEditor::drawPattern(QPainter& painter)
1056{
1057 if ( m_pPattern == nullptr ) {
1058 return;
1059 }
1060 auto pPref = H2Core::Preferences::get_instance();
1061
1062 std::shared_ptr<Song> pSong = Hydrogen::get_instance()->getSong();
1063 auto pInstrList = pSong->getInstrumentList();
1064
1065 /*
1066 BUGFIX
1067
1068 if m_pPattern is not renewed every time we draw a note,
1069 hydrogen will crash after you save a song and create a new one.
1070 -smoors
1071 */
1074
1075
1076 for ( Pattern *pPattern : getPatternsToShow() ) {
1077 const Pattern::notes_t *pNotes = pPattern->get_notes();
1078 if ( pNotes->size() == 0 ) {
1079 continue;
1080 }
1081 bool bIsForeground = ( pPattern == m_pPattern );
1082
1083 std::vector< int > noteCount; // instrument_id -> count
1084 std::stack<std::shared_ptr<Instrument>> instruments;
1085
1086 // Process notes in batches by note position, counting the notes at each instrument so we can display
1087 // markers for instruments which have more than one note in the same position (a chord or genuine
1088 // duplicates)
1089 for ( auto posIt = pNotes->begin(); posIt != pNotes->end(); ) {
1090 if ( posIt->first >= pPattern->get_length() ) {
1091 // Notes are located beyond the active length of the
1092 // editor and aren't visible even when drawn.
1093 break;
1094 }
1095
1096 int nPosition = posIt->second->get_position();
1097
1098 // Process all notes at this position
1099 auto noteIt = posIt;
1100 while ( noteIt != pNotes->end() && noteIt->second->get_position() == nPosition ) {
1101 Note *pNote = noteIt->second;
1102
1103 int nInstrumentID = pNote->get_instrument_id();
1104 if ( nInstrumentID >= noteCount.size() ) {
1105 noteCount.resize( nInstrumentID+1, 0 );
1106 }
1107
1108 if ( ++noteCount[ nInstrumentID ] == 1) {
1109 instruments.push( pNote->get_instrument() );
1110 }
1111
1112 drawNote( pNote, painter, bIsForeground );
1113 ++noteIt;
1114 }
1115
1116 // Go through used instruments list, drawing markers for superimposed notes and zero'ing the
1117 // counts.
1118 while ( ! instruments.empty() ) {
1119 auto pInstrument = instruments.top();
1120 int nInstrumentID = pInstrument->get_id();
1121 if ( noteCount[ nInstrumentID ] > 1 ) {
1122 // Draw "2x" text to the left of the note
1123 int nInstrument = pInstrList->index( pInstrument );
1124 int x = PatternEditor::nMargin + (nPosition * m_fGridWidth);
1125 int y = ( nInstrument * m_nGridHeight);
1126 const int boxWidth = 128;
1127
1128 QFont font( pPref->getApplicationFontFamily(), getPointSize( pPref->getFontSize() ) );
1129 painter.setFont( font );
1130 painter.setPen( QColor( 0, 0, 0 ) );
1131
1132 painter.drawText( QRect( x-boxWidth-6, y, boxWidth, m_nGridHeight),
1133 Qt::AlignRight | Qt::AlignVCenter,
1134 ( QString( "%1" ) + QChar( 0x00d7 )).arg( noteCount[ nInstrumentID ] ) );
1135 }
1136 noteCount[ nInstrumentID ] = 0;
1137 instruments.pop();
1138 }
1139
1140 posIt = noteIt;
1141 }
1142 }
1143}
1144
1145
1146
1150void DrumPatternEditor::drawNote( Note *note, QPainter& p, bool bIsForeground )
1151{
1152 if ( m_pPattern == nullptr ) {
1153 return;
1154 }
1155 auto pInstrList = Hydrogen::get_instance()->getSong()->getInstrumentList();
1156 int nInstrument = pInstrList->index( note->get_instrument() );
1157 if ( nInstrument == -1 ) {
1158 ERRORLOG( "Instrument not found..skipping note" );
1159 return;
1160 }
1161
1162 QPoint pos ( PatternEditor::nMargin + note->get_position() * m_fGridWidth,
1163 ( nInstrument * m_nGridHeight) + (m_nGridHeight / 2) - 3 );
1164
1165 drawNoteSymbol( p, pos, note, bIsForeground );
1166}
1167
1169{
1170 auto pPref = H2Core::Preferences::get_instance();
1171 auto pHydrogen = H2Core::Hydrogen::get_instance();
1172
1173 const QColor backgroundColor( pPref->getColorTheme()->m_patternEditor_backgroundColor );
1174 const QColor backgroundInactiveColor( pPref->getColorTheme()->m_windowColor );
1175 const QColor alternateRowColor( pPref->getColorTheme()->m_patternEditor_alternateRowColor );
1176 const QColor selectedRowColor( pPref->getColorTheme()->m_patternEditor_selectedRowColor );
1177 const QColor lineColor( pPref->getColorTheme()->m_patternEditor_lineColor );
1178 const QColor lineInactiveColor( pPref->getColorTheme()->m_windowTextColor.darker( 170 ) );
1179
1180 std::shared_ptr<Song> pSong = pHydrogen->getSong();
1181 int nInstruments = pSong->getInstrumentList()->size();
1182 int nSelectedInstrument = pHydrogen->getSelectedInstrumentNumber();
1183
1184 p.fillRect(0, 0, m_nActiveWidth, m_nEditorHeight, backgroundColor);
1187 backgroundInactiveColor);
1188 }
1189
1190 for ( int ii = 0; ii < nInstruments; ii++ ) {
1191 int y = static_cast<int>(m_nGridHeight) * ii;
1192 if ( ii == nSelectedInstrument ) {
1193 p.fillRect( 0, y, m_nActiveWidth, m_nGridHeight,
1194 selectedRowColor );
1195 }
1196 else if ( ( ii % 2 ) != 0 ) {
1197 p.fillRect( 0, y, m_nActiveWidth, m_nGridHeight, alternateRowColor );
1198 }
1199 }
1200
1201 // We skip the grid and cursor in case there is no pattern. This
1202 // way it may be more obvious that it is not armed and does not
1203 // expect user interaction.
1204 if ( m_pPattern == nullptr ) {
1205 return;
1206 }
1207 drawGridLines( p );
1208
1209 // The grid lines above are drawn full height. We will erase the
1210 // upper part.
1211 for ( int ii = 0; ii < nInstruments; ii++ ) {
1212 int y = static_cast<int>(m_nGridHeight) * ii;
1213 if ( ii == nSelectedInstrument ) {
1214 p.fillRect( 0, y, m_nActiveWidth, (int)( m_nGridHeight * 0.7 ), selectedRowColor );
1215 } else {
1216 if ( ( ii % 2 ) == 0 ) {
1217 p.fillRect( 0, y, m_nActiveWidth, (int)( m_nGridHeight * 0.7 ), backgroundColor );
1218 } else {
1219 p.fillRect( 0, y, m_nActiveWidth,
1220 (int)( m_nGridHeight * 0.7 ), alternateRowColor );
1221 }
1222 }
1223
1225 (int)( m_nGridHeight * 0.7 ), backgroundInactiveColor );
1226 }
1227
1228 // horizontal lines
1229 p.setPen( QPen( lineColor, 1, Qt::SolidLine ) );
1230 for ( uint i = 0; i < (uint)nInstruments; i++ ) {
1231 uint y = m_nGridHeight * i + m_nGridHeight;
1232 p.drawLine( 0, y, m_nActiveWidth, y);
1233 }
1234
1235 if ( m_nActiveWidth + 1 < m_nEditorWidth ) {
1236 p.setPen( QPen( lineInactiveColor, 1, Qt::SolidLine ) );
1237 for ( uint i = 0; i < (uint)nInstruments; i++ ) {
1238 uint y = m_nGridHeight * i + m_nGridHeight;
1239 p.drawLine( m_nActiveWidth, y, m_nEditorWidth, y);
1240 }
1241 }
1242
1243 // borders
1244 p.setPen( lineColor );
1245 p.drawLine( 0, m_nEditorHeight -1 , m_nActiveWidth - 1, m_nEditorHeight - 1 );
1246
1247 if ( m_nEditorWidth > m_nActiveWidth + 1 ) {
1248 p.setPen( lineInactiveColor );
1249 p.drawLine( m_nActiveWidth - 1, m_nEditorHeight - 1, m_nEditorWidth - 1, m_nEditorHeight - 1 );
1250 }
1251
1252 p.setPen( QPen( lineColor, 2, Qt::SolidLine ) );
1254
1255}
1256
1258 m_bBackgroundInvalid = false;
1259
1260 // Resize pixmap if pixel ratio has changed
1261 qreal pixelRatio = devicePixelRatio();
1262 if ( m_pBackgroundPixmap->width() != m_nEditorWidth ||
1263 m_pBackgroundPixmap->height() != m_nEditorHeight ||
1264 m_pBackgroundPixmap->devicePixelRatio() != pixelRatio ) {
1265 delete m_pBackgroundPixmap;
1266 m_pBackgroundPixmap = new QPixmap( width() * pixelRatio, height() * pixelRatio );
1267 m_pBackgroundPixmap->setDevicePixelRatio( pixelRatio );
1268 }
1269
1270 QPainter painter( m_pBackgroundPixmap );
1271
1272 drawBackground( painter );
1273
1274 drawPattern( painter );
1275}
1276
1277void DrumPatternEditor::paintEvent( QPaintEvent* ev )
1278{
1279 if (!isVisible()) {
1280 return;
1281 }
1282
1283 auto pPref = Preferences::get_instance();
1284
1285 qreal pixelRatio = devicePixelRatio();
1286 if ( pixelRatio != m_pBackgroundPixmap->devicePixelRatio() || m_bBackgroundInvalid ) {
1288 }
1289
1290 QPainter painter( this );
1291 painter.drawPixmap( ev->rect(), *m_pBackgroundPixmap, QRectF( pixelRatio * ev->rect().x(),
1292 pixelRatio * ev->rect().y(),
1293 pixelRatio * ev->rect().width(),
1294 pixelRatio * ev->rect().height() ) );
1295
1296 // Draw playhead
1297 if ( m_nTick != -1 ) {
1298
1299 int nOffset = Skin::getPlayheadShaftOffset();
1300 int nX = static_cast<int>(static_cast<float>(PatternEditor::nMargin) +
1301 static_cast<float>(m_nTick) *
1302 m_fGridWidth );
1303 Skin::setPlayheadPen( &painter, false );
1304 painter.drawLine( nX, 0, nX, height() );
1305 }
1306
1307 drawFocus( painter );
1308
1309 m_selection.paintSelection( &painter );
1310
1311 // Draw cursor
1312 if ( hasFocus() && !HydrogenApp::get_instance()->hideKeyboardCursor() ) {
1314 int nSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrumentNumber();
1315 uint y = nSelectedInstrument * m_nGridHeight;
1316 QPen p( pPref->getColorTheme()->m_cursorColor );
1317 p.setWidth( 2 );
1318 painter.setPen( p );
1319 painter.setBrush( Qt::NoBrush );
1320 painter.setRenderHint( QPainter::Antialiasing );
1321 painter.drawRoundedRect( QRect( x-m_fGridWidth*3, y+2, m_fGridWidth*6, m_nGridHeight-3 ), 4, 4 );
1322 }
1323
1324}
1325
1326void DrumPatternEditor::drawFocus( QPainter& painter ) {
1327
1328 if ( ! m_bEntered && ! hasFocus() ) {
1329 return;
1330 }
1331
1332 auto pPref = H2Core::Preferences::get_instance();
1333
1334 QColor color = pPref->getColorTheme()->m_highlightColor;
1335
1336 // If the mouse is placed on the widget but the user hasn't
1337 // clicked it yet, the highlight will be done more transparent to
1338 // indicate that keyboard inputs are not accepted yet.
1339 if ( ! hasFocus() ) {
1340 color.setAlpha( 125 );
1341 }
1342
1345 int nEndY = std::min( static_cast<int>( m_nGridHeight ) * Hydrogen::get_instance()->getSong()->getInstrumentList()->size(),
1346 nStartY + HydrogenApp::get_instance()->getPatternEditorPanel()->getDrumPatternEditorScrollArea()->viewport()->size().height() );
1347 int nEndX = std::min( nStartX + HydrogenApp::get_instance()->getPatternEditorPanel()->getDrumPatternEditorScrollArea()->viewport()->size().width(), width() );
1348
1349 QPen pen( color );
1350 pen.setWidth( 4 );
1351 painter.setPen( pen );
1352 painter.drawLine( QPoint( nStartX, nStartY ), QPoint( nEndX, nStartY ) );
1353 painter.drawLine( QPoint( nStartX, nStartY ), QPoint( nStartX, nEndY ) );
1354 painter.drawLine( QPoint( nEndX, nStartY ), QPoint( nEndX, nEndY ) );
1355 painter.drawLine( QPoint( nEndX, nEndY ), QPoint( nStartX, nEndY ) );
1356}
1357
1358void DrumPatternEditor::showEvent ( QShowEvent *ev )
1359{
1360 UNUSED( ev );
1361 updateEditor();
1362}
1363
1364
1365
1366void DrumPatternEditor::hideEvent ( QHideEvent *ev )
1367{
1368 UNUSED( ev );
1369}
1370
1375
1380
1384
1388
1392 int nSelectedPatternNumber,
1393 int nSelectedInstrument,
1394 float velocity,
1395 float fPan,
1396 float leadLag,
1397 float probability,
1398 int noteKeyVal,
1399 int octaveKeyVal)
1400{
1401 Hydrogen *pHydrogen = Hydrogen::get_instance();
1402 std::shared_ptr<Song> pSong = pHydrogen->getSong();
1403 Pattern *pPattern = nullptr;
1404 PatternList *pPatternList = pHydrogen->getSong()->getPatternList();
1405
1406 if ( (nSelectedPatternNumber != -1) && ( (uint)nSelectedPatternNumber < pPatternList->size() ) ) {
1407 pPattern = pPatternList->get( nSelectedPatternNumber );
1408 }
1409
1410 if( pPattern != nullptr ) {
1411 const Pattern::notes_t* notes = pPattern->get_notes();
1412 FOREACH_NOTE_CST_IT_BOUND_END(notes,it,column) {
1413 Note *pNote = it->second;
1414 assert( pNote );
1415 assert( (int)pNote->get_position() == column );
1416 if ( pNote->get_instrument() != pSong->getInstrumentList()->get( nSelectedInstrument ) ) {
1417 continue;
1418 }
1419
1420 if ( mode == PatternEditor::Mode::Velocity &&
1421 !pNote->get_note_off() ) {
1422 pNote->set_velocity( velocity );
1423 }
1424 else if ( mode == PatternEditor::Mode::Pan ){
1425 pNote->setPan( fPan );
1426 }
1427 else if ( mode == PatternEditor::Mode::LeadLag ){
1428 pNote->set_lead_lag( leadLag );
1429 }
1430 else if ( mode == PatternEditor::Mode::NoteKey ){
1431 pNote->set_key_octave( (Note::Key)noteKeyVal, (Note::Octave)octaveKeyVal );
1432 }
1433 else if ( mode == PatternEditor::Mode::Probability ){
1434 pNote->set_probability( probability );
1435 }
1436
1437 pHydrogen->setIsModified( true );
1439 break;
1440 }
1441
1443 }
1444}
1445
1453
1454
1457
1458void DrumPatternEditor::functionClearNotesRedoAction( int nSelectedInstrument, int nPatternNumber )
1459{
1460 Hydrogen* pHydrogen = Hydrogen::get_instance();
1461 auto pSong = pHydrogen->getSong();
1462 PatternList* pPatternList = pSong->getPatternList();
1463 Pattern* pPattern = pPatternList->get( nPatternNumber );
1464 if ( pPattern == nullptr ) {
1465 ERRORLOG( QString( "Couldn't find pattern [%1]" )
1466 .arg( nPatternNumber ) );
1467 return;
1468 }
1469
1470 auto pSelectedInstrument = pSong->getInstrumentList()->get( nSelectedInstrument );
1471 if ( pSelectedInstrument == nullptr ) {
1472 ERRORLOG( QString( "Couldn't find instrument [%1]" )
1473 .arg( nSelectedInstrument ) );
1474 return;
1475 }
1476
1477 pPattern->purge_instrument( pSelectedInstrument );
1479}
1480
1481
1482void DrumPatternEditor::functionClearNotesUndoAction( std::list< H2Core::Note* > noteList, int nSelectedInstrument, int patternNumber )
1483{
1484 Hydrogen* pHydrogen = Hydrogen::get_instance();
1485 PatternList* pPatternList = pHydrogen->getSong()->getPatternList();
1486 Pattern* pPattern = pPatternList->get( patternNumber );
1487 if ( pPattern == nullptr ) {
1488 ERRORLOG( QString( "Couldn't find pattern [%1]" )
1489 .arg( patternNumber ) );
1490 return;
1491 }
1492
1493 std::list < H2Core::Note *>::const_iterator pos;
1494 for ( pos = noteList.begin(); pos != noteList.end(); ++pos){
1495 Note *pNote;
1496 pNote = new Note(*pos);
1497 assert( pNote );
1498 pPattern->insert_note( pNote );
1499 }
1501
1503}
1504
1505void DrumPatternEditor::functionPasteNotesUndoAction(std::list<H2Core::Pattern*> & appliedList)
1506{
1507 // Get song's pattern list
1509 PatternList *patternList = H->getSong()->getPatternList();
1510
1511 m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine
1512
1513 while (appliedList.size() > 0)
1514 {
1515 // Get next applied pattern
1516 Pattern *pApplied = appliedList.front();
1517 assert(pApplied);
1518
1519 // Find destination pattern to perform undo
1520 Pattern *pat = patternList->find(pApplied->get_name());
1521
1522 if (pat != nullptr)
1523 {
1524 // Remove all notes of applied pattern from destination pattern
1525 const Pattern::notes_t* notes = pApplied->get_notes();
1527 {
1528 // Get note to remove
1529 Note *pNote = it->second;
1530 assert(pNote);
1531
1532 // Check if note is not present
1533 Pattern::notes_t* notes = (Pattern::notes_t *)pat->get_notes();
1534 FOREACH_NOTE_IT_BOUND_END(notes, it, pNote->get_position())
1535 {
1536 Note *pFoundNote = it->second;
1537 if (pFoundNote->get_instrument() == pNote->get_instrument())
1538 {
1539 notes->erase(it);
1540 delete pFoundNote;
1541 break;
1542 }
1543 }
1544 }
1545 }
1546
1547 // Remove applied pattern;
1548 delete pApplied;
1549 appliedList.pop_front();
1550 }
1551
1552 m_pAudioEngine->unlock(); // unlock the audio engine
1553
1554 // Update editors
1557}
1558
1559void DrumPatternEditor::functionPasteNotesRedoAction(std::list<H2Core::Pattern*> & changeList, std::list<H2Core::Pattern*> & appliedList)
1560{
1562 PatternList *patternList = H->getSong()->getPatternList();
1563
1564 m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine
1565
1566 // Add notes to pattern
1567 std::list < H2Core::Pattern *>::iterator pos;
1568 for ( pos = changeList.begin(); pos != changeList.end(); ++pos)
1569 {
1570 Pattern *pPattern = *pos;
1571 assert(pPattern);
1572
1573 Pattern *pat = patternList->find(pPattern->get_name()); // Destination pattern
1574
1575 if (pat != nullptr)
1576 {
1577 // Create applied pattern
1578 Pattern *pApplied = new Pattern(
1579 pat->get_name(),
1580 pat->get_info(),
1581 pat->get_category(),
1582 pat->get_length());
1583
1584 // Add all notes of source pattern to destination pattern
1585 // and store all applied notes in applied pattern
1586 const Pattern::notes_t* notes = pPattern->get_notes();
1588 {
1589 Note *pNote = it->second;
1590 assert(pNote);
1591
1592 // Check if note is not present
1593 bool noteExists = false;
1594 const Pattern::notes_t* notes = pat->get_notes();
1595 FOREACH_NOTE_CST_IT_BOUND_END(notes, it, pNote->get_position())
1596 {
1597 Note *pFoundNote = it->second;
1598 if (pFoundNote->get_instrument() == pNote->get_instrument())
1599 {
1600 // note already exists
1601 noteExists = true;
1602 break;
1603 }
1604 }
1605
1606 // Apply note and store it as applied
1607 if (!noteExists)
1608 {
1609 pat->insert_note(new Note(pNote));
1610 pApplied->insert_note(new Note(pNote));
1611 }
1612 }
1613
1614 // Add applied pattern to applied list
1615 appliedList.push_back(pApplied);
1616 }
1617 }
1618 m_pAudioEngine->unlock(); // unlock the audio engine
1619
1621 // Update editors
1623}
1624
1625
1626
1627void DrumPatternEditor::functionFillNotesUndoAction( QStringList noteList, int nSelectedInstrument, int patternNumber )
1628{
1629 Hydrogen* pHydrogen = Hydrogen::get_instance();
1630 auto pSong = pHydrogen->getSong();
1631 PatternList* pPatternList = pSong->getPatternList();
1632 Pattern* pPattern = pPatternList->get( patternNumber );
1633 if ( pPattern == nullptr ) {
1634 ERRORLOG( QString( "Couldn't find pattern [%1]" )
1635 .arg( patternNumber ) );
1636 return;
1637 }
1638
1639 auto pSelectedInstrument = pSong->getInstrumentList()->get( nSelectedInstrument );
1640 if ( pSelectedInstrument == nullptr ) {
1641 ERRORLOG( QString( "Couldn't find instrument [%1]" )
1642 .arg( nSelectedInstrument ) );
1643 return;
1644 }
1645
1646 m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine
1647
1648 for (int i = 0; i < noteList.size(); i++ ) {
1649 int nColumn = noteList.value(i).toInt();
1650 Pattern::notes_t* notes = (Pattern::notes_t*)pPattern->get_notes();
1651 FOREACH_NOTE_IT_BOUND_END(notes,it,nColumn) {
1652 Note *pNote = it->second;
1653 assert( pNote );
1654 if ( pNote->get_instrument() == pSelectedInstrument ) {
1655 // the note exists...remove it!
1656 notes->erase( it );
1657 delete pNote;
1658 break;
1659 }
1660 }
1661 }
1662 m_pAudioEngine->unlock(); // unlock the audio engine
1663
1666}
1667
1668
1669void DrumPatternEditor::functionFillNotesRedoAction( QStringList noteList, int nSelectedInstrument, int patternNumber )
1670{
1671 Hydrogen* pHydrogen = Hydrogen::get_instance();
1672 auto pSong = pHydrogen->getSong();
1673 PatternList* pPatternList = pSong->getPatternList();
1674 Pattern* pPattern = pPatternList->get( patternNumber );
1675 if ( pPattern == nullptr ) {
1676 ERRORLOG( QString( "Couldn't find pattern [%1]" )
1677 .arg( patternNumber ) );
1678 return;
1679 }
1680
1681 auto pSelectedInstrument = pSong->getInstrumentList()->get( nSelectedInstrument );
1682 if ( pSelectedInstrument == nullptr ) {
1683 ERRORLOG( QString( "Couldn't find instrument [%1]" )
1684 .arg( nSelectedInstrument ) );
1685 return;
1686 }
1687
1688 m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine
1689 for (int i = 0; i < noteList.size(); i++ ) {
1690
1691 // create the new note
1692 int position = noteList.value(i).toInt();
1693 Note *pNote = new Note( pSelectedInstrument, position );
1694 pPattern->insert_note( pNote );
1695 }
1696 m_pAudioEngine->unlock(); // unlock the audio engine
1697
1700}
1701
1702
1703void DrumPatternEditor::functionRandomVelocityAction( QStringList noteVeloValue, int nSelectedInstrument, int selectedPatternNumber )
1704{
1705 Hydrogen* pHydrogen = Hydrogen::get_instance();
1706 auto pSong = pHydrogen->getSong();
1707 PatternList* pPatternList = pSong->getPatternList();
1708 Pattern* pPattern = pPatternList->get( selectedPatternNumber );
1709 if ( pPattern == nullptr ) {
1710 ERRORLOG( QString( "Couldn't find pattern [%1]" )
1711 .arg( selectedPatternNumber ) );
1712 return;
1713 }
1714
1715 auto pSelectedInstrument = pSong->getInstrumentList()->get( nSelectedInstrument );
1716 if ( pSelectedInstrument == nullptr ) {
1717 ERRORLOG( QString( "Couldn't find instrument [%1]" )
1718 .arg( nSelectedInstrument ) );
1719 return;
1720 }
1721
1722 m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine
1723
1724 int nResolution = granularity();
1725 int positionCount = 0;
1726 for (int i = 0; i < pPattern->get_length(); i += nResolution) {
1727 const Pattern::notes_t* notes = pPattern->get_notes();
1728 FOREACH_NOTE_CST_IT_BOUND_LENGTH(notes,it,i, pPattern) {
1729 Note *pNote = it->second;
1730 if ( pNote->get_instrument() == pSelectedInstrument) {
1731 float velocity = noteVeloValue.value( positionCount ).toFloat();
1732 pNote->set_velocity(velocity);
1733 positionCount++;
1734 }
1735 }
1736 }
1737 pHydrogen->setIsModified( true );
1738 m_pAudioEngine->unlock(); // unlock the audio engine
1739
1742}
1743
1744
1745void DrumPatternEditor::functionMoveInstrumentAction( int nSourceInstrument, int nTargetInstrument )
1746{
1747 auto pHydrogen = Hydrogen::get_instance();
1749
1750 std::shared_ptr<Song> pSong = pHydrogen->getSong();
1751 auto pInstrumentList = pSong->getInstrumentList();
1752
1753 if ( ( nTargetInstrument > (int)pInstrumentList->size() ) || ( nTargetInstrument < 0) ) {
1755 return;
1756 }
1757
1758 pInstrumentList->move( nSourceInstrument, nTargetInstrument );
1759
1760 pHydrogen->renameJackPorts( pSong );
1761
1763 pHydrogen->setSelectedInstrumentNumber( nTargetInstrument );
1764
1765 pHydrogen->setIsModified( true );
1766}
1767
1768
1769void DrumPatternEditor::functionDropInstrumentUndoAction( int nTargetInstrument, std::vector<int>* AddedComponents )
1770{
1771 Hydrogen *pHydrogen = Hydrogen::get_instance();
1772 pHydrogen->removeInstrument( nTargetInstrument );
1773
1774 auto pDrumkitComponents = pHydrogen->getSong()->getComponents();
1775
1776 for ( const auto& nComponent : *AddedComponents ) {
1777
1778 for ( int n = 0 ; n < pDrumkitComponents->size() ; n++ ) {
1779 auto pTmpDrumkitComponent = pDrumkitComponents->at( n );
1780 if ( pTmpDrumkitComponent->get_id() == nComponent ) {
1781 pDrumkitComponents->erase( pDrumkitComponents->begin() + n );
1782 break;
1783 }
1784 }
1785 }
1786
1787 if ( pHydrogen->hasJackAudioDriver() ) {
1789 pHydrogen->renameJackPorts( pHydrogen->getSong() );
1791 }
1792
1793 updateEditor();
1794}
1795
1796
1797void DrumPatternEditor::functionDropInstrumentRedoAction( QString sDrumkitPath, QString sInstrumentName, int nTargetInstrument, std::vector<int>* pAddedComponents)
1798{
1799 auto pCommonString = HydrogenApp::get_instance()->getCommonStrings();
1800 auto pHydrogen = Hydrogen::get_instance();
1801 auto pSong = pHydrogen->getSong();
1802
1803 auto pNewInstrument = Instrument::load_instrument( sDrumkitPath, sInstrumentName );
1804 if ( pNewInstrument == nullptr ||
1805 ( pNewInstrument->get_name() == "Empty Instrument" &&
1806 pNewInstrument->get_drumkit_path().isEmpty() ) ){
1807 // Under normal circumstances this should not been reached.
1808 QMessageBox::critical( this, "Hydrogen", pCommonString->getInstrumentLoadError() );
1809 return;
1810 }
1811
1812 auto pNewDrumkit =
1813 pHydrogen->getSoundLibraryDatabase()->getDrumkit( sDrumkitPath );
1814 if( pNewDrumkit == nullptr ){
1815 ERRORLOG( QString( "Unable to load drumkit [%1]" ).arg( sDrumkitPath ) );
1816 return;
1817 }
1818
1819
1821
1822 // Ensure the components of the loaded drumkit are present in
1823 // the current song as well.
1824 auto pOldInstrumentComponents = new std::vector<std::shared_ptr<InstrumentComponent>>( pNewInstrument->get_components()->begin(), pNewInstrument->get_components()->end() );
1825 pNewInstrument->get_components()->clear();
1826
1827 for ( auto pComponent : *(pNewDrumkit->get_components()) ) {
1828 int nOldID = pComponent->get_id();
1829
1830 // Gets the ID of the drumkit component registered to the
1831 // current song that matches the name of the pComponent.
1832 int nNewID = pSong->findExistingComponent( pComponent->get_name() );
1833
1834 if ( nNewID == -1 ) {
1835 // No component in the currently loaded drumkit found
1836 // matching pComponent.
1837 //
1838 // Get an ID not used as drumkit component ID by the
1839 // drumkit currently loaded.
1840 nNewID = pSong->findFreeComponentID();
1841
1842 pAddedComponents->push_back( nNewID );
1843
1844 pComponent->set_id( nNewID );
1845 pComponent->set_name( pSong->makeComponentNameUnique( pComponent->get_name() ) );
1846 auto pNewComponent = std::make_shared<DrumkitComponent>( pComponent );
1847 pSong->getComponents()->push_back( pNewComponent );
1848 }
1849
1850 for ( auto pOldInstrCompo : *pOldInstrumentComponents ) {
1851 if( pOldInstrCompo->get_drumkit_componentID() == nOldID ) {
1852 auto pNewInstrCompo = std::make_shared<InstrumentComponent>( pOldInstrCompo );
1853 pNewInstrCompo->set_drumkit_componentID( nNewID );
1854
1855 pNewInstrument->get_components()->push_back( pNewInstrCompo );
1856 }
1857 }
1858 }
1859
1860 pOldInstrumentComponents->clear();
1861 delete pOldInstrumentComponents;
1862
1863 // create a new valid ID for this instrument
1864 int nID = -1;
1865 for ( uint i = 0; i < pSong->getInstrumentList()->size(); ++i ) {
1866 auto pInstr = pSong->getInstrumentList()->get( i );
1867 if ( pInstr->get_id() > nID ) {
1868 nID = pInstr->get_id();
1869 }
1870 }
1871 ++nID;
1872
1873 pNewInstrument->set_id( nID );
1874
1875 pSong->getInstrumentList()->add( pNewInstrument );
1876
1877 pHydrogen->renameJackPorts( pSong );
1878
1879 pHydrogen->setIsModified( true );
1881
1882 //move instrument to the position where it was dropped
1883 functionMoveInstrumentAction(pSong->getInstrumentList()->size() - 1 , nTargetInstrument );
1884
1885 // select the new instrument
1886 pHydrogen->setSelectedInstrumentNumber(nTargetInstrument);
1888 updateEditor();
1889}
1890
1891void DrumPatternEditor::functionDeleteInstrumentUndoAction( std::list< H2Core::Note* > noteList, int nSelectedInstrument, QString sInstrumentName, QString sDrumkitPath )
1892{
1893 Hydrogen *pHydrogen = Hydrogen::get_instance();
1894 auto pSong = pHydrogen->getSong();
1895 std::shared_ptr<Instrument> pNewInstrument;
1896 if( sDrumkitPath == "" ){
1897 pNewInstrument = std::make_shared<Instrument>( pSong->getInstrumentList()->size() -1, sInstrumentName );
1898 } else {
1899 pNewInstrument = Instrument::load_instrument( sDrumkitPath, sInstrumentName );
1900 }
1901 if( pNewInstrument == nullptr ) {
1902 return;
1903 }
1904
1905 // create a new valid ID for this instrument
1906 int nID = -1;
1907 for ( uint i = 0; i < pSong->getInstrumentList()->size(); ++i ) {
1908 auto pInstr = pSong->getInstrumentList()->get( i );
1909 if ( pInstr->get_id() > nID ) {
1910 nID = pInstr->get_id();
1911 }
1912 }
1913 ++nID;
1914
1915 pNewInstrument->set_id( nID );
1916
1918 pSong->getInstrumentList()->add( pNewInstrument );
1919
1920 pHydrogen->renameJackPorts( pSong );
1921
1922 pHydrogen->setIsModified( true );
1923 m_pAudioEngine->unlock(); // unlock the audio engine
1924
1925 //move instrument to the position where it was dropped
1926 functionMoveInstrumentAction(pSong->getInstrumentList()->size() - 1 , nSelectedInstrument );
1927
1928 // select the new instrument
1929 pHydrogen->setSelectedInstrumentNumber( nSelectedInstrument );
1930
1931 H2Core::Pattern *pPattern;
1932 PatternList *pPatternList = pSong->getPatternList();
1933
1934 updateEditor();
1936
1937 //restore all deleted instrument notes
1939 if(noteList.size() > 0 ){
1940 std::list < H2Core::Note *>::const_iterator pos;
1941 for ( pos = noteList.begin(); pos != noteList.end(); ++pos){
1942 Note *pNote = new Note( *pos, pNewInstrument );
1943 assert( pNote );
1944 pPattern = pPatternList->get( pNote->get_pattern_idx() );
1945 assert (pPattern);
1946 pPattern->insert_note( pNote );
1947 //delete pNote;
1948 }
1949 }
1950 m_pAudioEngine->unlock(); // unlock the audio engine
1951}
1952
1954{
1955
1956 Hydrogen* pHydrogen = Hydrogen::get_instance();
1957 auto pSong = pHydrogen->getSong();
1958
1959 if ( pSong == nullptr ) {
1960 ERRORLOG( "Invalid song" );
1961 return;
1962 }
1963
1964 pHydrogen->removeInstrument( pSong->getInstrumentList()->size() -1 );
1965
1966 if ( pHydrogen->hasJackAudioDriver() ) {
1968 pHydrogen->renameJackPorts( pSong );
1970 }
1971
1972 pHydrogen->setIsModified( true );
1973
1974 updateEditor();
1975}
1976
1977
1979{
1980 auto pHydrogen = Hydrogen::get_instance();
1981 auto pSong = pHydrogen->getSong();
1982
1983 if ( pSong == nullptr ) {
1984 ERRORLOG( "Invalid song" );
1985 return;
1986 }
1987
1988 auto pList = pSong->getInstrumentList();
1989
1991
1992 // create a new valid ID for this instrument
1993 int nID = -1;
1994 for ( uint i = 0; i < pList->size(); ++i ) {
1995 auto pInstr = pList->get( i );
1996 if ( pInstr != nullptr &&
1997 ( pInstr->get_id() > nID ) ) {
1998 nID = pInstr->get_id();
1999 }
2000 }
2001 ++nID;
2002
2003 auto pNewInstr = std::make_shared<Instrument>( nID, "New instrument");
2004 pNewInstr->set_drumkit_path( pSong->getLastLoadedDrumkitPath() );
2005 pNewInstr->set_drumkit_name( pSong->getLastLoadedDrumkitName() );
2006
2007 pList->add( pNewInstr );
2008
2009 pHydrogen->renameJackPorts( pSong );
2010
2011 pHydrogen->setIsModified( true );
2013
2014 pHydrogen->setSelectedInstrumentNumber( pList->size() - 1 );
2015
2016 updateEditor();
2017
2018}
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
Definition AudioEngine.h:59
#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_BOUND_LENGTH(_notes, _it, _bound, _pattern)
Iterate over all notes in column _bound in an immutable way if it is contained in _pattern.
Definition Pattern.h:290
#define FOREACH_NOTE_IT_BOUND_END(_notes, _it, _bound)
Iterate over all notes in column _bound in a mutable way.
Definition Pattern.h:280
#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
void functionClearNotesUndoAction(std::list< H2Core::Note * > noteList, int nSelectedInstrument, int patternNumber)
virtual void hideEvent(QHideEvent *ev) override
void functionRandomVelocityAction(QStringList noteVeloValue, int nSelectedInstrument, int selectedPatternNumber)
virtual void mouseDragUpdateEvent(QMouseEvent *ev) override
Update the state during a Selection drag.
void drawNote(H2Core::Note *pNote, QPainter &painter, bool bIsForeground=true)
Draw a note.
virtual void keyReleaseEvent(QKeyEvent *ev) override
virtual void updateEditor(bool bPatternOnly=false) override
virtual void mousePressEvent(QMouseEvent *ev) override
Raw Qt mouse events are passed to the Selection.
void functionDropInstrumentUndoAction(int nTargetInstrument, std::vector< int > *AddedComponents)
virtual std::vector< SelectionIndex > elementsIntersecting(QRect r) override
Find all elements which intersect a selection area.
virtual void selectionMoveEndEvent(QInputEvent *ev) override
Move or copy notes.
DrumPatternEditor(QWidget *parent, PatternEditorPanel *panel)
void drawPattern(QPainter &painter)
Draws a pattern.
virtual QRect getKeyboardCursorRect() override
The screen area occupied by the keyboard cursor.
void functionMoveInstrumentAction(int nSourceInstrument, int nTargetInstrument)
void addOrRemoveNote(int nColumn, int nRealColumn, int row, bool bDoAdd=true, bool bDoDelete=true, bool bIsNoteOff=false)
virtual void selectAll() override
void onPreferencesChanged(H2Core::Preferences::Changes changes)
void drawBackground(QPainter &pointer)
void moveNoteAction(int nColumn, int nRow, int nPattern, int nNewColumn, int nNewRow, H2Core::Note *note)
virtual void songModeActivationEvent() override
virtual void drumkitLoadedEvent() override
virtual void keyPressEvent(QKeyEvent *ev) override
Handle key press events.
virtual void paste() override
Paste selection.
virtual void showEvent(QShowEvent *ev) override
virtual void mouseDragStartEvent(QMouseEvent *ev) override
void functionFillNotesRedoAction(QStringList noteList, int nSelectedInstrument, int patternNumber)
void createBackground() override
Updates m_pBackgroundPixmap to show the latest content.
virtual void selectedInstrumentChangedEvent() override
void addOrDeleteNoteAction(int nColumn, int row, int selectedPatternNumber, int oldLength, float oldVelocity, float fOldPan, float oldLeadLag, int oldNoteKeyVal, int oldOctaveKeyVal, float probability, bool listen, bool isMidi, bool isInstrumentMode, bool isNoteOff, bool isDelete)
void functionFillNotesUndoAction(QStringList noteList, int nSelectedInstrument, int patternNumber)
virtual void mouseClickEvent(QMouseEvent *ev) override
virtual void paintEvent(QPaintEvent *ev) override
virtual void deleteSelection() override
void undoRedoAction(int column, NotePropertiesRuler::Mode mode, int nSelectedPatternNumber, int nSelectedInstrument, float velocity, float pan, float leadLag, float probability, int noteKeyVal, int octaveKeyVal)
NotePropertiesRuler undo redo action.
void functionPasteNotesUndoAction(std::list< H2Core::Pattern * > &appliedList)
void functionClearNotesRedoAction(int nSelectedInstrument, int selectedPatternNumber)
========================================================== undo / redo actions from pattern editor in...
virtual void selectedPatternChangedEvent() override
void functionDeleteInstrumentUndoAction(std::list< H2Core::Note * > noteList, int nSelectedInstrument, QString instrumentName, QString drumkitName)
void drawFocus(QPainter &painter)
void functionPasteNotesRedoAction(std::list< H2Core::Pattern * > &changeList, std::list< H2Core::Pattern * > &appliedList)
void functionDropInstrumentRedoAction(QString sDrumkitPath, QString sInstrumentName, int nTargetInstrument, std::vector< int > *pAddedComponents)
@ Playing
Transport is rolling.
@ Ready
Ready to process audio.
void unlock()
Mutex unlocking of the AudioEngine.
void lock(const char *file, unsigned int line, const char *function)
Mutex locking of the AudioEngine.
Sampler * getSampler() const
static EventQueue * get_instance()
Returns a pointer to the current EventQueue singleton stored in __instance.
Definition EventQueue.h:224
void push_event(const EventType type, const int nValue)
Queues the next event into the EventQueue.
Hydrogen Audio Engine.
Definition Hydrogen.h:54
bool hasJackAudioDriver() const
void renameJackPorts(std::shared_ptr< Song > pSong)
Calls audioEngine_renameJackPorts() if Preferences::m_bJackTrackOuts is set to true.
Definition Hydrogen.cpp:931
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:122
void removeInstrument(int nInstrumentNumber)
Delete an #Instrument.
Definition Hydrogen.cpp:754
int getSelectedInstrumentNumber() const
Definition Hydrogen.h:664
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
void setIsModified(bool bIsModified)
Wrapper around Song::setIsModified() that checks whether a song is set.
static std::shared_ptr< Instrument > load_instrument(const QString &drumkit_path, const QString &instrument_name)
creates a new Instrument, loads samples from a given instrument within a given drumkit
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
void set_lead_lag(float value)
__lead_lag setter
Definition Note.cpp:148
void set_key_octave(const QString &str)
parse str and set __key and __octave
Definition Note.cpp:202
static Note * load_from(XMLNode *node, std::shared_ptr< InstrumentList > instruments, bool bSilent=false)
load a note from an XMLNode
Definition Note.cpp:502
Octave get_octave()
__octave accessor
Definition Note.h:677
void set_probability(float value)
Definition Note.h:615
void set_position(int value)
__position setter
Definition Note.h:530
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
Definition Note.h:500
float get_lead_lag() const
__lead_lag accessor
Definition Note.h:550
void set_just_recorded(bool value)
__just_recorded setter
Definition Note.h:600
int get_length() const
__length accessor
Definition Note.h:560
int get_pattern_idx() const
__pattern_idx accessor
Definition Note.h:595
float get_velocity() const
__velocity accessor
Definition Note.h:540
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
void set_note_off(bool value)
__note_off setter
Definition Note.h:575
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
int get_instrument_id() const
__instrument_id accessor
Definition Note.h:515
PatternList is a collection of patterns.
Definition PatternList.h:43
Pattern * find(const QString &name)
find a pattern within the patterns
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 QString & get_info() const
get the category of the pattern
Definition Pattern.h:320
const QString & get_name() const
set the category of the pattern
Definition Pattern.h:310
const QString & get_category() const
set the length of the pattern
Definition Pattern.h:330
void purge_instrument(std::shared_ptr< Instrument > instr, bool bRequiredLock=true)
delete the notes referencing the given instrument The function is thread safe (it locks the audio dat...
Definition Pattern.cpp:267
void remove_note(Note *note)
removes a given note from __notes, it's not deleted
Definition Pattern.cpp:244
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
void insert_note(Note *note)
insert a new note within __notes
Definition Pattern.h:370
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
Changes
Bitwise or-able options showing which part of the Preferences were altered using the PreferencesDialo...
@ Font
Either the font size or font family have changed.
@ Colors
At least one of the colors has changed.
void noteOn(Note *pNote)
Start playing a note.
Definition Sampler.cpp:190
XMLDoc is a subclass of QDomDocument with read and write methods.
Definition Xml.h:182
XMLNode is a subclass of QDomNode with read and write values methods.
Definition Xml.h:39
int read_int(const QString &node, int default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads an integer stored into a child node
Definition Xml.cpp:170
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
std::shared_ptr< CommonStrings > getCommonStrings()
QUndoStack * m_pUndoStack
PatternEditorPanel * getPatternEditorPanel()
virtual void selectedInstrumentChangedEvent() override
Pattern Editor Panel.
void setCursorPosition(int nCursorPosition)
const QScrollBar * getVerticalScrollBar() const
void updateEditors(bool bPatternOnly=false)
PatternEditorInstrumentList * getInstrumentList()
PatternEditorRuler * getPatternEditorRuler()
const QScrollBar * getHorizontalScrollBar() const
Pattern Editor.
virtual void mouseDragUpdateEvent(QMouseEvent *ev) override
virtual void validateSelection() override
Ensure that the Selection contains only valid elements.
int m_nSelectedPatternNumber
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)
void storeNoteProperties(const H2Core::Note *pNote)
Stores the properties of pNote in member variables.
static void triggerStatusMessage(H2Core::Note *pNote, Mode mode)
void drawNoteSymbol(QPainter &p, QPoint pos, H2Core::Note *pNote, bool bIsForeground=true) const
Draw a note.
static constexpr int nMargin
PatternEditorPanel * m_pPatternEditorPanel
H2Core::AudioEngine * m_pAudioEngine
H2Core::Note * m_pDraggedNote
QMenu * m_pPopupMenu
void updatePatternInfo()
Update current pattern information.
virtual void mouseDragStartEvent(QMouseEvent *ev) override
unsigned m_nGridHeight
virtual void copy()
Copy selection to clipboard in XML.
std::vector< H2Core::Pattern * > getPatternsToShow(void)
Get notes to show in pattern editor.
QPixmap * m_pBackgroundPixmap
virtual void cut()
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.
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 paintSelection(QPainter *painter)
Paint selection-related elements (ie lasso)
Definition Selection.h:487
bool isLasso() const
Is there an ongoing lasso gesture?
Definition Selection.h:330
void addToSelection(Elem e)
Definition Selection.h:346
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
bool keyPressEvent(QKeyEvent *ev)
Key press event filter.
Definition Selection.h:659
void updateKeyboardCursorPosition(QRect cursor)
Update the keyboard cursor.
Definition Selection.h:756
bool isSelected(Elem e) const
Is an element in the set of currently selected elements?
Definition Selection.h:335
iterator end()
Definition Selection.h:371
iterator begin()
Definition Selection.h:370
static void setPlayheadPen(QPainter *p, bool bHovered=false)
Definition Skin.cpp:190
static int getPlayheadShaftOffset()
Definition Skin.h:79
constexpr int getPointSize(H2Core::FontTheme::FontSize fontSize) const
#define MAX_INSTRUMENTS
Maximum number of instruments allowed in Hydrogen.
Definition config.dox:70
#define UNUSED(v)
Definition Globals.h:42
@ EVENT_SELECTED_INSTRUMENT_CHANGED
Definition EventQueue.h:77