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