hydrogen 1.2.6
PianoRollEditor.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 "PianoRollEditor.h"
24#include "PatternEditorPanel.h"
25#include "PatternEditorRuler.h"
27#include "UndoActions.h"
28#include <cassert>
29
30#include <core/Hydrogen.h>
33#include <core/Basics/Note.h>
34#include <core/Basics/Pattern.h>
37#include <core/Helpers/Xml.h>
38using namespace H2Core;
39
41#include "../HydrogenApp.h"
42#include "../Skin.h"
43
44
46 QScrollArea *pScrollView)
47 : PatternEditor( pParent, panel )
48 , m_pScrollView( pScrollView )
49{
51
52 m_nGridHeight = 10;
53 m_nOctaves = 7;
54
55 setAttribute(Qt::WA_OpaquePaintEvent);
56
58
59 m_pTemp = new QPixmap( m_nEditorWidth, m_nEditorHeight );
60
62
64
66
68
69 m_bNeedsUpdate = true;
71 m_bSelectNewNotes = false;
72}
73
74
75
77{
78 INFOLOG( "DESTROY" );
79 delete m_pTemp;
80}
81
82
83void PianoRollEditor::updateEditor( bool bPatternOnly )
84{
85 // Ensure that m_pPattern is up to date.
88
89 if ( !bPatternOnly ) {
91 }
92 if ( !m_bNeedsUpdate ) {
93 m_bNeedsUpdate = true;
94 update();
95 }
96}
97
99{
100 assert( m_bNeedsUpdate );
101 resize( m_nEditorWidth, height() );
102
103 // Ensure that m_pPattern is up to date.
105
108 } else {
109 drawPattern();
110 }
111
112 // ERRORLOG(QString("update editor %1").arg(m_nEditorWidth));
113 m_bNeedsUpdate = false;
115}
116
118{
119 // Update pattern only
120 updateEditor( true );
121}
122
127
131
132void PianoRollEditor::paintEvent(QPaintEvent *ev)
133{
134 if (!isVisible()) {
135 return;
136 }
137
138 auto pPref = Preferences::get_instance();
139
140 qreal pixelRatio = devicePixelRatio();
141 if ( pixelRatio != m_pBackgroundPixmap->devicePixelRatio() || m_bBackgroundInvalid ) {
143 }
144
145 QPainter painter( this );
146 if ( m_bNeedsUpdate ) {
148 }
149 painter.drawPixmap( ev->rect(), *m_pTemp,
150 QRectF( pixelRatio * ev->rect().x(),
151 pixelRatio * ev->rect().y(),
152 pixelRatio * ev->rect().width(),
153 pixelRatio * ev->rect().height() ) );
154
155 // Draw playhead
156 if ( m_nTick != -1 ) {
157
158 int nOffset = Skin::getPlayheadShaftOffset();
159 int nX = static_cast<int>(static_cast<float>(PatternEditor::nMargin) +
160 static_cast<float>(m_nTick) *
161 m_fGridWidth );
162 Skin::setPlayheadPen( &painter, false );
163 painter.drawLine( nX, 2, nX, height() - 2 );
164 }
165
166 drawFocus( painter );
167
168 m_selection.paintSelection( &painter );
169
170 // Draw cursor
171 if ( hasFocus() && !HydrogenApp::get_instance()->hideKeyboardCursor() ) {
172 QPoint pos = cursorPosition();
173
174 QPen pen( pPref->getColorTheme()->m_cursorColor );
175 pen.setWidth( 2 );
176 painter.setPen( pen );
177 painter.setBrush( Qt::NoBrush );
178 painter.setRenderHint( QPainter::Antialiasing );
179 painter.drawRoundedRect( getKeyboardCursorRect(), 4, 4 );
180 }
181}
182
183void PianoRollEditor::drawFocus( QPainter& painter ) {
184
186
187 if ( ! m_bEntered && ! hasFocus() ) {
188 return;
189 }
190
191 QColor color = pPref->getColorTheme()->m_highlightColor;
192
193 // If the mouse is placed on the widget but the user hasn't
194 // clicked it yet, the highlight will be done more transparent to
195 // indicate that keyboard inputs are not accepted yet.
196 if ( ! hasFocus() ) {
197 color.setAlpha( 125 );
198 }
199
200 int nStartY = HydrogenApp::get_instance()->getPatternEditorPanel()->getPianoRollEditorScrollArea()->verticalScrollBar()->value();
201 int nStartX = HydrogenApp::get_instance()->getPatternEditorPanel()->getPianoRollEditorScrollArea()->horizontalScrollBar()->value();
202 int nEndY = nStartY + HydrogenApp::get_instance()->getPatternEditorPanel()->getPianoRollEditorScrollArea()->viewport()->size().height();
203 int nEndX = std::min( nStartX + HydrogenApp::get_instance()->getPatternEditorPanel()->getPianoRollEditorScrollArea()->viewport()->size().width(), width() );
204
205 QPen pen( color );
206 pen.setWidth( 4 );
207 painter.setPen( pen );
208 painter.drawLine( QPoint( nStartX, nStartY ), QPoint( nEndX, nStartY ) );
209 painter.drawLine( QPoint( nStartX, nStartY ), QPoint( nStartX, nEndY ) );
210 painter.drawLine( QPoint( nEndX, nStartY ), QPoint( nEndX, nEndY ) );
211 painter.drawLine( QPoint( nEndX, nEndY ), QPoint( nStartX, nEndY ) );
212}
213
215{
217
218 const QColor backgroundColor = pPref->getColorTheme()->m_patternEditor_backgroundColor;
219 const QColor backgroundInactiveColor = pPref->getColorTheme()->m_windowColor;
220 const QColor alternateRowColor = pPref->getColorTheme()->m_patternEditor_alternateRowColor;
221 const QColor octaveColor = pPref->getColorTheme()->m_patternEditor_octaveRowColor;
222 // The line corresponding to the default pitch set to new notes
223 // will be highlighted.
224 const QColor baseNoteColor = octaveColor.lighter( 119 );
225 const QColor lineColor( pPref->getColorTheme()->m_patternEditor_lineColor );
226 const QColor lineInactiveColor( pPref->getColorTheme()->m_windowTextColor.darker( 170 ) );
227
228 unsigned start_x = 0;
229 unsigned end_x = m_nActiveWidth;
230
231 // Resize pixmap if pixel ratio has changed
232 qreal pixelRatio = devicePixelRatio();
233 if ( m_pBackgroundPixmap->width() != m_nEditorWidth ||
235 m_pBackgroundPixmap->devicePixelRatio() != pixelRatio ) {
236 delete m_pBackgroundPixmap;
237 m_pBackgroundPixmap = new QPixmap( width() * pixelRatio , height() * pixelRatio );
238 m_pBackgroundPixmap->setDevicePixelRatio( pixelRatio );
239 delete m_pTemp;
240 m_pTemp = new QPixmap( width() * pixelRatio , height() * pixelRatio );
241 m_pTemp->setDevicePixelRatio( pixelRatio );
242 }
243
244 m_pBackgroundPixmap->fill( backgroundInactiveColor );
245
246 QPainter p( m_pBackgroundPixmap );
247
248 for ( uint ooctave = 0; ooctave < m_nOctaves; ++ooctave ) {
249 unsigned start_y = ooctave * 12 * m_nGridHeight;
250
251 for ( int ii = 0; ii < 12; ++ii ) {
252 if ( ii == 0 || ii == 2 || ii == 4 || ii == 6 || ii == 7 ||
253 ii == 9 || ii == 11 ) {
254 if ( ooctave % 2 != 0 ) {
255 p.fillRect( start_x, start_y + ii * m_nGridHeight,
256 end_x - start_x, start_y + ( ii + 1 ) * m_nGridHeight,
257 octaveColor );
258 } else {
259 p.fillRect( start_x, start_y + ii * m_nGridHeight,
260 end_x - start_x, start_y + ( ii + 1 ) * m_nGridHeight,
261 backgroundColor );
262 }
263 } else {
264 p.fillRect( start_x, start_y + ii * m_nGridHeight,
265 end_x - start_x, start_y + ( ii + 1 ) * m_nGridHeight,
266 alternateRowColor );
267 }
268 }
269
270 // Highlight base note pitch
271 if ( ooctave == 3 ) {
272 p.fillRect( start_x, start_y + 11 * m_nGridHeight,
273 end_x - start_x, start_y + 12 * m_nGridHeight,
274 baseNoteColor );
275 }
276 }
277
278
279 // horiz lines
280 p.setPen( lineColor );
281 for ( uint row = 0; row < ( 12 * m_nOctaves ); ++row ) {
282 unsigned y = row * m_nGridHeight;
283 p.drawLine( start_x, y, end_x, y );
284 }
285
286 if ( m_nActiveWidth + 1 < m_nEditorWidth ) {
287 p.setPen( lineInactiveColor );
288 for ( uint row = 0; row < ( 12 * m_nOctaves ); ++row ) {
289 unsigned y = row * m_nGridHeight;
290 p.drawLine( m_nActiveWidth, y, m_nEditorWidth, y );
291 }
292 }
293
294 //draw text
295 QFont font( pPref->getApplicationFontFamily(), getPointSize( pPref->getFontSize() ) );
296 p.setFont( font );
297 p.setPen( pPref->getColorTheme()->m_patternEditor_textColor );
298
299 int offset = 0;
300 int insertx = 3;
301 for ( int oct = 0; oct < (int)m_nOctaves; oct++ ){
302 if( oct > 3 ){
303 p.drawText( insertx, m_nGridHeight + offset, "B" );
304 p.drawText( insertx, 10 + m_nGridHeight + offset, "A#" );
305 p.drawText( insertx, 20 + m_nGridHeight + offset, "A" );
306 p.drawText( insertx, 30 + m_nGridHeight + offset, "G#" );
307 p.drawText( insertx, 40 + m_nGridHeight + offset, "G" );
308 p.drawText( insertx, 50 + m_nGridHeight + offset, "F#" );
309 p.drawText( insertx, 60 + m_nGridHeight + offset, "F" );
310 p.drawText( insertx, 70 + m_nGridHeight + offset, "E" );
311 p.drawText( insertx, 80 + m_nGridHeight + offset, "D#" );
312 p.drawText( insertx, 90 + m_nGridHeight + offset, "D" );
313 p.drawText( insertx, 100 + m_nGridHeight + offset, "C#" );
314 p.drawText( insertx, 110 + m_nGridHeight + offset, "C" );
315 offset += 12 * m_nGridHeight;
316 }else
317 {
318 p.drawText( insertx, m_nGridHeight + offset, "b" );
319 p.drawText( insertx, 10 + m_nGridHeight + offset, "a#" );
320 p.drawText( insertx, 20 + m_nGridHeight + offset, "a" );
321 p.drawText( insertx, 30 + m_nGridHeight + offset, "g#" );
322 p.drawText( insertx, 40 + m_nGridHeight + offset, "g" );
323 p.drawText( insertx, 50 + m_nGridHeight + offset, "f#" );
324 p.drawText( insertx, 60 + m_nGridHeight + offset, "f" );
325 p.drawText( insertx, 70 + m_nGridHeight + offset, "e" );
326 p.drawText( insertx, 80 + m_nGridHeight + offset, "d#" );
327 p.drawText( insertx, 90 + m_nGridHeight + offset, "d" );
328 p.drawText( insertx, 100 + m_nGridHeight + offset, "c#" );
329 p.drawText( insertx, 110 + m_nGridHeight + offset, "c" );
330 offset += 12 * m_nGridHeight;
331 }
332 }
333
334 drawGridLines( p, Qt::DashLine );
335 drawPattern();
336
337 p.setPen( QPen( lineColor, 2, Qt::SolidLine ) );
339
340 m_bBackgroundInvalid = false;
341}
342
343
345{
346 //INFOLOG( "draw pattern" );
347
349
350 qreal pixelRatio = devicePixelRatio();
351
352 QPainter p( m_pTemp );
353 // copy the background image
354 p.drawPixmap( rect(), *m_pBackgroundPixmap,
355 QRectF( pixelRatio * rect().x(),
356 pixelRatio * rect().y(),
357 pixelRatio * rect().width(),
358 pixelRatio * rect().height() ) );
359
360 // for each note...
361 for ( Pattern *pPattern : getPatternsToShow() ) {
362 bool bIsForeground = ( pPattern == m_pPattern );
363 const Pattern::notes_t* notes = pPattern->get_notes();
364 FOREACH_NOTE_CST_IT_BEGIN_LENGTH( notes, it, pPattern ) {
365 Note *note = it->second;
366 assert( note );
367 drawNote( note, &p, bIsForeground );
368 }
369 }
370
371}
372
373
374void PianoRollEditor::drawNote( Note *pNote, QPainter *pPainter, bool bIsForeground )
375{
376 Hydrogen *pHydrogen = Hydrogen::get_instance();
377 if ( pNote->get_instrument() == pHydrogen->getSelectedInstrument() ) {
378 QPoint pos ( PatternEditor::nMargin + pNote->get_position() * m_fGridWidth,
380 drawNoteSymbol( *pPainter, pos, pNote, bIsForeground );
381 }
382}
383
384
385void PianoRollEditor::addOrRemoveNote( int nColumn, int nRealColumn, int nLine,
386 int nNotekey, int nOctave,
387 bool bDoAdd, bool bDoDelete )
388{
389 if ( m_pPattern == nullptr || m_nSelectedPatternNumber == -1 ) {
390 // No pattern selected.
391 return;
392 }
393
394 Note::Octave octave = (Note::Octave)nOctave;
395 Note::Key notekey = (Note::Key)nNotekey;
396 Hydrogen *pHydrogen = Hydrogen::get_instance();
397 int nSelectedInstrumentnumber = pHydrogen->getSelectedInstrumentNumber();
398 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
399 if ( pSelectedInstrument == nullptr ) {
400 DEBUGLOG( "No instrument selected" );
401 return;
402 }
403
404 Note* pOldNote = m_pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument,
405 notekey, octave );
406
407 int nLength = -1;
408 float fVelocity = 0.8f;
409 float fPan = 0.0f;
410 float fLeadLag = 0.0f;
411 float fProbability = 1.0f;
412
413 if ( pOldNote && !bDoDelete ) {
414 // Found an old note, but we don't want to delete, so just return.
415 return;
416 } else if ( !pOldNote && !bDoAdd ) {
417 // No note there, but we don't want to add a new one, so return.
418 return;
419 }
420
421 if ( pOldNote ) {
422 nLength = pOldNote->get_length();
423 fVelocity = pOldNote->get_velocity();
424 fPan = pOldNote->getPan();
425 fLeadLag = pOldNote->get_lead_lag();
426 notekey = pOldNote->get_key();
427 octave = pOldNote->get_octave();
428 fProbability = pOldNote->get_probability();
429 }
430
431 if ( pOldNote == nullptr ) {
432 // hear note
434 if ( pref->getHearNewNotes() && pSelectedInstrument->hasSamples() ) {
435 Note *pNote2 = new Note( pSelectedInstrument );
436 pNote2->set_key_octave( notekey, octave );
437 m_pAudioEngine->getSampler()->noteOn( pNote2 );
438 }
439 }
440
442 nLine,
444 nSelectedInstrumentnumber,
445 nLength,
446 fVelocity,
447 fPan,
448 fLeadLag,
449 notekey,
450 octave,
451 fProbability,
452 pOldNote != nullptr );
453 HydrogenApp::get_instance()->m_pUndoStack->push( action );
454
455}
456
457
458void PianoRollEditor::mouseClickEvent( QMouseEvent *ev ) {
459
460 if ( m_pPattern == nullptr || m_nSelectedPatternNumber == -1 ) {
461 return;
462 }
463
464 auto pEv = static_cast<MouseEvent*>( ev );
465
466 auto pHydrogenApp = HydrogenApp::get_instance();
467 std::shared_ptr<Song> pSong = Hydrogen::get_instance()->getSong();
468
469 int nPressedLine = ((int) pEv->position().y()) / ((int) m_nGridHeight);
470 if ( nPressedLine >= (int) m_nOctaves * 12 ) {
471 return;
472 }
473
474 int nColumn = getColumn( pEv->position().x(), /* bUseFineGrained=*/ true );
475
476 if ( nColumn >= (int)m_pPattern->get_length() ) {
477 update( 0, 0, width(), height() );
478 return;
479 }
480 m_pPatternEditorPanel->setCursorPosition( nColumn );
481
482 auto pSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrument();
483 int nSelectedInstrumentnumber = Hydrogen::get_instance()->getSelectedInstrumentNumber();
484
485 if ( pSelectedInstrument == nullptr ) {
486 ERRORLOG( "No instrument selected" );
487 return;
488 }
489
490 int nPitch = lineToPitch( nPressedLine );
491 Note::Octave pressedoctave = Note::pitchToOctave( nPitch );
492 Note::Key pressednotekey = Note::pitchToKey( nPitch );
493 m_nCursorPitch = nPitch;
494
495 if (ev->button() == Qt::LeftButton ) {
496
497 unsigned nRealColumn = 0;
498 if( pEv->position().x() > PatternEditor::nMargin ) {
499 nRealColumn = (pEv->position().x() - PatternEditor::nMargin) / static_cast<float>(m_fGridWidth);
500 }
501
502 if ( ev->modifiers() & Qt::ShiftModifier ) {
503 H2Core::Note *pNote = m_pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument, pressednotekey, pressedoctave );
504 if ( pNote != nullptr ) {
506 nPressedLine,
508 nSelectedInstrumentnumber,
509 pNote->get_length(),
510 pNote->get_velocity(),
511 pNote->getPan(),
512 pNote->get_lead_lag(),
513 pNote->get_key(),
514 pNote->get_octave(),
515 pNote->get_probability(),
516 pNote != nullptr );
517 pHydrogenApp->m_pUndoStack->push( action );
518 } else {
519 SE_addPianoRollNoteOffAction *action = new SE_addPianoRollNoteOffAction( nColumn, nPressedLine, m_nSelectedPatternNumber, nSelectedInstrumentnumber );
520 pHydrogenApp->m_pUndoStack->push( action );
521 }
522 return;
523 }
524
525 addOrRemoveNote( nColumn, nRealColumn, nPressedLine, pressednotekey, pressedoctave );
526
527 } else if ( ev->button() == Qt::RightButton ) {
528 // Show context menu
529 m_pPopupMenu->popup( pEv->globalPosition().toPoint() );
530
531 }
532
533}
534
535void PianoRollEditor::mousePressEvent( QMouseEvent* ev ) {
536 auto pEv = static_cast<MouseEvent*>( ev );
537
538 if ( pEv->position().x() > m_nActiveWidth ) {
539 return;
540 }
541
543
544 auto pHydrogenApp = HydrogenApp::get_instance();
545
546 // Hide cursor in case this behavior was selected in the
547 // Preferences.
548 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
549 pHydrogenApp->setHideKeyboardCursor( true );
550
551 // Cursor just got hidden.
552 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
553 // Immediate update to prevent visual delay.
554 m_pPatternEditorPanel->getPatternEditorRuler()->update();
555 update();
556 }
557
558 // Update cursor position
559 if ( ! pHydrogenApp->hideKeyboardCursor() ) {
560 int nPressedLine = ((int) pEv->position().y()) / ((int) m_nGridHeight);
561 if ( nPressedLine >= (int) m_nOctaves * 12 ) {
562 return;
563 }
564 m_nCursorPitch = lineToPitch( nPressedLine );
565
566 int nColumn = getColumn( pEv->position().x(), /* bUseFineGrained=*/ true );
567 if ( ( m_pPattern != nullptr &&
568 nColumn >= (int)m_pPattern->get_length() ) ||
569 nColumn >= MAX_INSTRUMENTS ) {
570 return;
571 }
572
573 m_pPatternEditorPanel->setCursorPosition( nColumn );
574
575 update();
576 m_pPatternEditorPanel->getPatternEditorRuler()->update();
577 }
578}
579
581{
582 if ( m_pPattern == nullptr ) {
583 return;
584 }
585
586 auto pEv = static_cast<MouseEvent*>( ev );
587
588 // Handles cursor repositioning and hiding and stores general
589 // properties.
591
592 m_pDraggedNote = nullptr;
593 Hydrogen *pHydrogen = Hydrogen::get_instance();
594 int nColumn = getColumn( pEv->position().x() );
595 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
596 if ( pSelectedInstrument == nullptr ) {
597 DEBUGLOG( "No instrument selected" );
598 return;
599 }
600
601 int nRow = std::floor(static_cast<float>(pEv->position().y()) /
602 static_cast<float>(m_nGridHeight));
603
604 Note::Octave pressedOctave = Note::pitchToOctave( lineToPitch( nRow ) );
605 Note::Key pressedNoteKey = Note::pitchToKey( lineToPitch( nRow ) );
606 m_nCursorPitch = lineToPitch( nRow );
607
608 if (ev->button() == Qt::RightButton ) {
609
610 int nRealColumn = 0;
611 if( pEv->position().x() > PatternEditor::nMargin ) {
612 nRealColumn =
613 static_cast<int>(std::floor(
614 static_cast<float>((pEv->position().x() - PatternEditor::nMargin)) /
615 m_fGridWidth));
616 }
617
618 m_pDraggedNote = m_pPattern->find_note( nColumn, nRealColumn,
619 pSelectedInstrument,
620 pressedNoteKey,
621 pressedOctave, false );
622
623 // Store note-specific properties.
625
626 m_nRow = nRow;
627 }
628}
629
631{
632 auto pEv = static_cast<MouseEvent*>( ev );
633
634 int nRow = std::floor(static_cast<float>(pEv->position().y()) /
635 static_cast<float>(m_nGridHeight));
636 if ( nRow >= (int) m_nOctaves * 12 ) {
637 return;
638 }
639
641}
642
644 int pressedLine,
645 int selectedPatternNumber,
646 int selectedinstrument,
647 int oldLength,
648 float oldVelocity,
649 float fOldPan,
650 float oldLeadLag,
651 int oldNoteKeyVal,
652 int oldOctaveKeyVal,
653 float fProbability,
654 bool noteOff,
655 bool isDelete )
656{
657 if ( m_pPattern == nullptr ) {
658 return;
659 }
660
661 Hydrogen *pHydrogen = Hydrogen::get_instance();
662 std::shared_ptr<Song> pSong = pHydrogen->getSong();
663 PatternList *pPatternList = pHydrogen->getSong()->getPatternList();
664
665 auto pSelectedInstrument = pSong->getInstrumentList()->get( selectedinstrument );
666 if ( pSelectedInstrument == nullptr ) {
667 ERRORLOG( QString( "Instrument [%1] could not be found" )
668 .arg( selectedinstrument ) );
669 return;
670 }
671
672 Pattern *pPattern = nullptr;
673 if ( ( selectedPatternNumber != -1 ) && ( (uint)selectedPatternNumber < pPatternList->size() ) ) {
674 pPattern = pPatternList->get( selectedPatternNumber );
675 }
676
677 Note::Octave pressedoctave = Note::pitchToOctave( lineToPitch( pressedLine ) );
678 Note::Key pressednotekey = Note::pitchToKey( lineToPitch( pressedLine ) );
679
680 m_pAudioEngine->lock( RIGHT_HERE ); // lock the audio engine
681
682 if ( isDelete ) {
683 Note* note = m_pPattern->find_note( nColumn, -1, pSelectedInstrument, pressednotekey, pressedoctave );
684 if ( note ) {
685 // the note exists...remove it!
686 m_pPattern->remove_note( note );
687 delete note;
688 } else {
689 ERRORLOG( "Could not find note to delete" );
690 }
691 } else {
692 // create the new note
693 unsigned nPosition = nColumn;
694 float fVelocity = oldVelocity;
695 float fPan = fOldPan;
696 int nLength = oldLength;
697
698 if ( noteOff ) {
699 fVelocity = 0.0f;
700 fPan = 0.f;
701 nLength = 1;
702 }
703
704 if ( pPattern != nullptr ) {
705 Note *pNote = new Note( pSelectedInstrument, nPosition, fVelocity, fPan, nLength );
706 pNote->set_note_off( noteOff );
707 if( ! noteOff ) {
708 pNote->set_lead_lag( oldLeadLag );
709 }
710 pNote->set_key_octave( pressednotekey, pressedoctave );
711 pNote->set_probability( fProbability );
712 pPattern->insert_note( pNote );
713 if ( m_bSelectNewNotes ) {
714 m_selection.addToSelection( pNote );
715 }
716 }
717 }
718 pHydrogen->setIsModified( true );
719 m_pAudioEngine->unlock(); // unlock the audio engine
720
721 m_pPatternEditorPanel->updateEditors( true );
722}
723
724
725// Find a note that matches pNote, and move it from (nColumn, nRow) to (nNewColumn, nNewRow)
727 Note::Octave octave,
728 Note::Key key,
729 int nPattern,
730 int nNewColumn,
731 Note::Octave newOctave,
732 Note::Key newKey,
733 Note *pNote)
734{
735 Hydrogen *pHydrogen = Hydrogen::get_instance();
736 std::shared_ptr<Song> pSong = pHydrogen->getSong();
737
738 m_pAudioEngine->lock( RIGHT_HERE );
739 PatternList *pPatternList = pSong->getPatternList();
740 Note *pFoundNote = nullptr;
741
742 if ( nPattern < 0 || nPattern > pPatternList->size() ) {
743 ERRORLOG( "Invalid pattern number" );
744 m_pAudioEngine->unlock();
745 return;
746 }
747
748 Pattern *pPattern = pPatternList->get( nPattern );
749
750 FOREACH_NOTE_IT_BOUND_END((Pattern::notes_t *)pPattern->get_notes(), it, nColumn) {
751 Note *pCandidateNote = it->second;
752 if ( pCandidateNote->get_instrument() == pNote->get_instrument()
753 && pCandidateNote->get_octave() == octave
754 && pCandidateNote->get_key() == key
755 && pCandidateNote->get_velocity() == pNote->get_velocity()
756 && pCandidateNote->get_lead_lag() == pNote->get_lead_lag()
757 && pCandidateNote->getPan() == pNote->getPan()
758 && pCandidateNote->get_note_off() == pNote->get_note_off() ) {
759 pFoundNote = pCandidateNote;
760 if ( m_selection.isSelected( pFoundNote ) ) {
761 // If a candidate note is in the selection, this will be the one to move.
762 break;
763 }
764 }
765 }
766 if ( pFoundNote == nullptr ) {
767 ERRORLOG( "Couldn't find note to move" );
768 m_pAudioEngine->unlock();
769 return;
770 }
771
772 // Remove and insert at new position
773 pPattern->remove_note( pFoundNote );
774 pFoundNote->set_position( nNewColumn );
775 pPattern->insert_note( pFoundNote );
776 pFoundNote->set_key_octave( newKey, newOctave );
777
778 pHydrogen->setIsModified( true );
779 m_pAudioEngine->unlock();
780
781 m_pPatternEditorPanel->updateEditors( true );
782}
783
784
786{
787 uint x = PatternEditor::nMargin + m_pPatternEditorPanel->getCursorPosition() * m_fGridWidth;
788 uint y = m_nGridHeight * pitchToLine( m_nCursorPitch ) + 1;
789 return QPoint(x, y);
790}
791
793{
794 selectInstrumentNotes( Hydrogen::get_instance()->getSelectedInstrumentNumber() );
795}
796
797
799{
800 if ( m_nSelectedPatternNumber == -1 ) {
801 // No pattern selected.
802 return;
803 }
804
805 if ( m_selection.begin() != m_selection.end() ) {
806 // Delete a selection.
807 Hydrogen *pHydrogen = Hydrogen::get_instance();
808 int nSelectedInstrumentNumber = pHydrogen->getSelectedInstrumentNumber();
809 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
810 if ( pSelectedInstrument == nullptr ) {
811 DEBUGLOG( "No instrument selected" );
812 return;
813 }
814 QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack;
816 std::list< QUndoCommand * > actions;
817 for ( Note *pNote : m_selection ) {
818 if ( m_selection.isSelected( pNote ) ) {
819 if ( pNote->get_instrument() == pSelectedInstrument ) {
820 int nLine = pitchToLine( pNote->get_notekey_pitch() );
821 actions.push_back( new SE_addOrDeleteNotePianoRollAction( pNote->get_position(),
822 nLine,
824 nSelectedInstrumentNumber,
825 pNote->get_length(),
826 pNote->get_velocity(),
827 pNote->getPan(),
828 pNote->get_lead_lag(),
829 pNote->get_key(),
830 pNote->get_octave(),
831 pNote->get_probability(),
832 true ) );
833 }
834 }
835 }
836 m_selection.clearSelection();
837
838 pUndo->beginMacro("delete notes");
839 for ( QUndoCommand * pAction : actions ) {
840 pUndo->push( pAction );
841 }
842 pUndo->endMacro();
843 }
844}
845
846
853{
854 if ( m_pPattern == nullptr || m_nSelectedPatternNumber == -1 ) {
855 // No pattern selected.
856 return;
857 }
858
859 QClipboard *clipboard = QApplication::clipboard();
860 QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack;
861 auto pInstrList = Hydrogen::get_instance()->getSong()->getInstrumentList();
863 XMLNode noteList;
864 int nDeltaPos = 0, nDeltaPitch = 0;
865
866
867 XMLDoc doc;
868 if ( ! doc.setContent( clipboard->text() ) ) {
869 // Pasted something that's not valid XML.
870 return;
871 }
872
873 XMLNode selection = doc.firstChildElement( "noteSelection" );
874 if ( ! selection.isNull() ) {
875
876 // Got a noteSelection.
877 // <noteSelection>
878 // <noteList>
879 // <note> ...
880 noteList = selection.firstChildElement( "noteList" );
881 if ( noteList.isNull() ) {
882 return;
883 }
884
885 XMLNode positionNode = selection.firstChildElement( "sourcePosition" );
886
887 // If position information is supplied in the selection, use
888 // it to adjust the location relative to the current keyboard
889 // input cursor.
890 if ( !positionNode.isNull() ) {
891 int nCurrentPos = m_pPatternEditorPanel->getCursorPosition();
892
893 nDeltaPos = nCurrentPos -
894 positionNode.read_int( "minColumn", nCurrentPos );
895 nDeltaPitch = m_nCursorPitch -
896 positionNode.read_int( "maxPitch", m_nCursorPitch );
897 }
898 } else {
899
900 XMLNode instrumentLine = doc.firstChildElement( "instrument_line" );
901 if ( ! instrumentLine.isNull() ) {
902 // Found 'instrument_line', structure is:
903 // <instrument_line>
904 // <patternList>
905 // <pattern>
906 // <noteList>
907 // <note> ...
908 XMLNode patternList = instrumentLine.firstChildElement( "patternList" );
909 if ( patternList.isNull() ) {
910 return;
911 }
912 XMLNode pattern = patternList.firstChildElement( "pattern" );
913 if ( pattern.isNull() ) {
914 return;
915 }
916 // Don't attempt to paste multiple patterns
917 if ( ! pattern.nextSiblingElement( "pattern" ).isNull() ) {
918 QMessageBox::information( this, "Hydrogen", tr( "Cannot paste multi-pattern selection" ) );
919 return;
920 }
921 noteList = pattern.firstChildElement( "noteList" );
922 if ( noteList.isNull() ) {
923 return;
924 }
925 }
926 }
927
928 m_selection.clearSelection();
929 m_bSelectNewNotes = true;
930
931 if ( noteList.hasChildNodes() ) {
932
933 pUndo->beginMacro( "paste notes" );
934 for ( XMLNode n = noteList.firstChildElement( "note" ); ! n.isNull(); n = n.nextSiblingElement() ) {
935 Note *pNote = Note::load_from( &n, pInstrList );
936 int nPos = pNote->get_position() + nDeltaPos;
937 int nPitch = pNote->get_notekey_pitch() + nDeltaPitch;
938
939 if ( nPos >= 0 && nPos < m_pPattern->get_length() && nPitch >= 12 * OCTAVE_MIN && nPitch < 12 * (OCTAVE_MAX+1) ) {
940 int nLine = pitchToLine( nPitch );
941 pUndo->push( new SE_addOrDeleteNotePianoRollAction( nPos,
942 nLine,
944 nInstrument,
945 pNote->get_length(),
946 pNote->get_velocity(),
947 pNote->getPan(),
948 pNote->get_lead_lag(),
949 0,
950 0,
951 pNote->get_probability(),
952 false ) );
953 }
954 delete pNote;
955 }
956 pUndo->endMacro();
957 }
958
959 m_bSelectNewNotes = false;
960}
961
962
963void PianoRollEditor::keyPressEvent( QKeyEvent * ev )
964{
965 if ( m_pPattern == nullptr ) {
966 return;
967 }
968
969 auto pHydrogenApp = HydrogenApp::get_instance();
970 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
971
972 const int nBlockSize = 5, nWordSize = 5;
973 bool bIsSelectionKey = m_selection.keyPressEvent( ev );
974 bool bUnhideCursor = true;
975 updateModifiers( ev );
976
977 if ( bIsSelectionKey ) {
978 // Selection key, nothing more to do (other than update editor)
979 } else if ( ev->matches( QKeySequence::MoveToNextChar ) || ev->matches( QKeySequence::SelectNextChar ) ) {
980 // ->
981 m_pPatternEditorPanel->moveCursorRight();
982
983 } else if ( ev->matches( QKeySequence::MoveToNextWord ) || ev->matches( QKeySequence::SelectNextWord ) ) {
984 // ->
985 m_pPatternEditorPanel->moveCursorRight( nWordSize );
986
987 } else if ( ev->matches( QKeySequence::MoveToEndOfLine ) || ev->matches( QKeySequence::SelectEndOfLine ) ) {
988 // -->|
989 m_pPatternEditorPanel->setCursorPosition( m_pPattern->get_length() );
990
991 } else if ( ev->matches( QKeySequence::MoveToPreviousChar ) || ev->matches( QKeySequence::SelectPreviousChar ) ) {
992 // <-
993 m_pPatternEditorPanel->moveCursorLeft();
994
995 } else if ( ev->matches( QKeySequence::MoveToPreviousWord ) || ev->matches( QKeySequence::SelectPreviousWord ) ) {
996 // <-
997 m_pPatternEditorPanel->moveCursorLeft( nWordSize );
998
999 } else if ( ev->matches( QKeySequence::MoveToStartOfLine ) || ev->matches( QKeySequence::SelectStartOfLine ) ) {
1000 // |<--
1001 m_pPatternEditorPanel->setCursorPosition( 0 );
1002
1003 } else if ( ev->matches( QKeySequence::MoveToNextLine ) || ev->matches( QKeySequence::SelectNextLine ) ) {
1005 m_nCursorPitch --;
1006 }
1007
1008 } else if ( ev->matches( QKeySequence::MoveToEndOfBlock ) || ev->matches( QKeySequence::SelectEndOfBlock ) ) {
1010 m_nCursorPitch - nBlockSize );
1011
1012 } else if ( ev->matches( QKeySequence::MoveToNextPage ) || ev->matches( QKeySequence::SelectNextPage ) ) {
1013 // Page down -- move down by a whole octave
1015 m_nCursorPitch -= 12;
1016 if ( m_nCursorPitch < nMinPitch ) {
1017 m_nCursorPitch = nMinPitch;
1018 }
1019
1020 } else if ( ev->matches( QKeySequence::MoveToEndOfDocument ) || ev->matches( QKeySequence::SelectEndOfDocument ) ) {
1022
1023 } else if ( ev->matches( QKeySequence::MoveToPreviousLine ) || ev->matches( QKeySequence::SelectPreviousLine ) ) {
1025 m_nCursorPitch ++;
1026 }
1027
1028 } else if ( ev->matches( QKeySequence::MoveToStartOfBlock ) || ev->matches( QKeySequence::SelectStartOfBlock ) ) {
1030 m_nCursorPitch + nBlockSize );
1031
1032 } else if ( ev->matches( QKeySequence::MoveToPreviousPage ) || ev->matches( QKeySequence::SelectPreviousPage ) ) {
1034 m_nCursorPitch += 12;
1035 if ( m_nCursorPitch >= nMaxPitch ) {
1036 m_nCursorPitch = nMaxPitch;
1037 }
1038
1039 } else if ( ev->matches( QKeySequence::MoveToStartOfDocument ) || ev->matches( QKeySequence::SelectStartOfDocument ) ) {
1041
1042 } else if ( ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return ) {
1043 // Key: Enter/Return : Place or remove note at current position
1044 int pressedline = pitchToLine( m_nCursorPitch );
1045 int nPitch = lineToPitch( pressedline );
1046 addOrRemoveNote( m_pPatternEditorPanel->getCursorPosition(), -1, pressedline,
1047 Note::pitchToKey( nPitch ), Note::pitchToOctave( nPitch ) );
1048
1049 } else if ( ev->matches( QKeySequence::SelectAll ) ) {
1050 // Key: Ctrl + A: Select all
1051 bUnhideCursor = false;
1052 selectAll();
1053
1054 } else if ( ev->matches( QKeySequence::Deselect ) ) {
1055 // Key: Shift + Ctrl + A: clear selection
1056 bUnhideCursor = false;
1057 selectNone();
1058
1059 } else if ( ev->key() == Qt::Key_Delete ) {
1060 // Key: Delete: delete selection or note at keyboard cursor
1061 bUnhideCursor = false;
1062 if ( m_selection.begin() != m_selection.end() ) {
1064 } else {
1065 // Delete a note under the keyboard cursor
1066 int pressedline = pitchToLine( m_nCursorPitch );
1067 int nPitch = lineToPitch( pressedline );
1068 addOrRemoveNote( m_pPatternEditorPanel->getCursorPosition(), -1, pressedline,
1069 Note::pitchToKey( nPitch ), Note::pitchToOctave( nPitch ),
1070 /*bDoAdd=*/false, /*bDoDelete=*/true );
1071 }
1072
1073 } else if ( ev->matches( QKeySequence::Copy ) ) {
1074 bUnhideCursor = false;
1075 copy();
1076
1077 } else if ( ev->matches( QKeySequence::Paste ) ) {
1078 bUnhideCursor = false;
1079 paste();
1080
1081 } else if ( ev->matches( QKeySequence::Cut ) ) {
1082 bUnhideCursor = false;
1083 cut();
1084
1085 } else {
1086 ev->ignore();
1087 pHydrogenApp->setHideKeyboardCursor( true );
1088
1089 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
1090 m_pPatternEditorPanel->getPatternEditorRuler()->update();
1091 update();
1092 }
1093 return;
1094 }
1095
1096 // Update editor
1097 QPoint pos = cursorPosition();
1098 if ( bUnhideCursor ) {
1100 }
1101 m_pScrollView->ensureVisible( pos.x(), pos.y() );
1102 m_selection.updateKeyboardCursorPosition( getKeyboardCursorRect() );
1103
1104 if ( ! HydrogenApp::get_instance()->hideKeyboardCursor() ) {
1105 // Immediate update to prevent visual delay.
1106 m_pPatternEditorPanel->getPatternEditorRuler()->update();
1107 }
1108
1109 updateEditor( true );
1110 ev->accept();
1111}
1112
1113// Selection manager interface
1115{
1116 updateModifiers( ev );
1117
1118 QPoint offset = movingGridOffset();
1119 if ( offset.x() == 0 && offset.y() == 0 ) {
1120 // Move with no effect.
1121 return;
1122 }
1123
1125
1126 Hydrogen *pHydrogen = Hydrogen::get_instance();
1127 int nSelectedPatternNumber = pHydrogen->getSelectedPatternNumber();
1128 int nSelectedInstrumentNumber = pHydrogen->getSelectedInstrumentNumber();
1129
1130 if ( m_pPattern == nullptr || nSelectedPatternNumber == -1 ) {
1131 // No pattern selected. Nothing to be selected.
1132 return;
1133 }
1134
1135 QUndoStack *pUndo = HydrogenApp::get_instance()->m_pUndoStack;
1136
1137 if (m_bCopyNotMove) {
1138 pUndo->beginMacro( "copy notes" );
1139 } else {
1140 pUndo->beginMacro( "move notes" );
1141 }
1142 std::list< Note * > selectedNotes;
1143 for ( auto pNote : m_selection ) {
1144 selectedNotes.push_back( pNote );
1145 }
1146
1147 if ( m_bCopyNotMove ) {
1148 // Clear selection so the new notes can be selection instead
1149 // of the originals.
1150 m_selection.clearSelection();
1151 }
1152 m_bSelectNewNotes = true;
1153
1154 for ( auto pNote : selectedNotes ) {
1155 int nPosition = pNote->get_position();
1156 int nNewPosition = nPosition + offset.x();
1157
1158 Note::Octave octave = pNote->get_octave();
1159 Note::Key key = pNote->get_key();
1160 // Transpose note
1161 int nNewPitch = pNote->get_notekey_pitch() - offset.y();
1162 int nLine = pitchToLine( nNewPitch );
1163 Note::Octave newOctave = Note::pitchToOctave( nNewPitch );
1164 Note::Key newKey = Note::pitchToKey( nNewPitch );
1165 bool bNoteInRange = ( newOctave >= OCTAVE_MIN && newOctave <= OCTAVE_MAX && nNewPosition >= 0
1166 && nNewPosition < m_pPattern->get_length() );
1167
1168 if ( m_bCopyNotMove ) {
1169 if ( bNoteInRange ) {
1170 pUndo->push( new SE_addOrDeleteNotePianoRollAction( nNewPosition,
1171 nLine,
1172 nSelectedPatternNumber,
1173 nSelectedInstrumentNumber,
1174 pNote->get_length(),
1175 pNote->get_velocity(),
1176 pNote->getPan(),
1177 pNote->get_lead_lag(),
1178 newKey,
1179 newOctave,
1180 pNote->get_probability(),
1181 false ) );
1182 }
1183 } else {
1184 if ( bNoteInRange ) {
1185 pUndo->push( new SE_moveNotePianoRollAction( nPosition, octave, key, nSelectedPatternNumber, nNewPosition, newOctave, newKey, pNote ) );
1186 } else {
1187 pUndo->push( new SE_addOrDeleteNotePianoRollAction( pNote->get_position(),
1188 nLine - offset.y(),
1189 nSelectedPatternNumber,
1190 nSelectedInstrumentNumber,
1191 pNote->get_length(),
1192 pNote->get_velocity(),
1193 pNote->getPan(),
1194 pNote->get_lead_lag(),
1195 key,
1196 octave,
1197 pNote->get_probability(),
1198 true ) );
1199 }
1200 }
1201 }
1202
1203 m_bSelectNewNotes = false;
1204 pUndo->endMacro();
1205}
1206
1207std::vector<PianoRollEditor::SelectionIndex> PianoRollEditor::elementsIntersecting( QRect r )
1208{
1209 std::vector<SelectionIndex> result;
1210 if ( m_pPattern == nullptr ) {
1211 return std::move( result );
1212 }
1213
1214 int w = 8;
1215 int h = m_nGridHeight - 2;
1216 auto pSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrument();
1217 if ( pSelectedInstrument == nullptr ) {
1218 DEBUGLOG( "No instrument selected" );
1219 return std::move( result );
1220 }
1221
1222 r = r.normalized();
1223 if ( r.top() == r.bottom() && r.left() == r.right() ) {
1224 r += QMargins( 2, 2, 2, 2 );
1225 }
1226
1227 // Calculate the first and last position values that this rect will intersect with
1228 int x_min = (r.left() - w - PatternEditor::nMargin) / m_fGridWidth;
1229 int x_max = (r.right() + w - PatternEditor::nMargin) / m_fGridWidth;
1230
1231 const Pattern::notes_t* pNotes = m_pPattern->get_notes();
1232
1233 for ( auto it = pNotes->lower_bound( x_min ); it != pNotes->end() && it->first <= x_max; ++it ) {
1234 Note *pNote = it->second;
1235 if ( pNote->get_instrument() == pSelectedInstrument ) {
1236 uint start_x = PatternEditor::nMargin + pNote->get_position() * m_fGridWidth;
1237 uint start_y = m_nGridHeight * pitchToLine( pNote->get_notekey_pitch() ) + 1;
1238
1239 if ( r.intersects( QRect( start_x -4 , start_y, w, h ) ) ) {
1240 result.push_back( pNote );
1241 }
1242 }
1243 }
1244 updateEditor( true );
1245 return std::move( result );
1246}
1247
1252{
1253 const QPoint pos = cursorPosition();
1254 float fHalfWidth;
1255 if ( m_nResolution != MAX_NOTES ) {
1256 // Corresponds to the distance between grid lines on 1/64 resolution.
1257 fHalfWidth = m_fGridWidth * 3;
1258 } else {
1259 // Corresponds to the distance between grid lines set to resolution
1260 // "off".
1261 fHalfWidth = m_fGridWidth;
1262 }
1263 return QRect( pos.x() - fHalfWidth, pos.y()-2,
1264 fHalfWidth * 2, m_nGridHeight+3 );
1265}
1266
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
Definition AudioEngine.h:61
#define KEY_MAX
Definition Note.h:35
#define OCTAVE_MIN
Definition Note.h:36
#define OCTAVE_MAX
Definition Note.h:37
#define KEY_MIN
Definition Note.h:34
#define INFOLOG(x)
Definition Object.h:240
#define ERRORLOG(x)
Definition Object.h:242
#define DEBUGLOG(x)
Definition Object.h:239
#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_LENGTH(_notes, _it, _pattern)
Iterate over all accessible notes between position 0 and length of _pattern in an immutable way.
Definition Pattern.h:285
Hydrogen Audio Engine.
Definition Hydrogen.h:54
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
int getSelectedInstrumentNumber() const
Definition Hydrogen.h:678
int getSelectedPatternNumber() const
Definition Hydrogen.h:674
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
std::shared_ptr< Instrument > getSelectedInstrument() const
void setIsModified(bool bIsModified)
Wrapper around Song::setIsModified() that checks whether a song is set.
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:101
int get_position() const
__position accessor
Definition Note.h:534
static int octaveKeyToPitch(Octave octave, Key key)
Definition Note.h:402
static Key pitchToKey(int nPitch)
Definition Note.h:399
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 Octave pitchToOctave(int nPitch)
Definition Note.h:392
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
int get_length() const
__length accessor
Definition Note.h:559
float get_notekey_pitch() const
note key pitch accessor
Definition Note.h:695
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 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
PatternList is a collection of patterns.
Definition PatternList.h:43
int size() const
returns the numbers of patterns
Pattern * get(int idx)
get a pattern from the list
Pattern class is a Note container.
Definition Pattern.h:46
void remove_note(Note *note)
removes a given note from __notes, it's not deleted
Definition Pattern.cpp:239
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
Manager for User Preferences File (singleton)
Definition Preferences.h:79
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
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
void setHideKeyboardCursor(bool bHidden)
void addEventListener(EventListener *pListener)
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
QUndoStack * m_pUndoStack
PatternEditorPanel * getPatternEditorPanel()
Compatibility class to support QMouseEvent more esily in Qt5 and Qt6.
Definition MouseEvent.h:35
Pattern Editor Panel.
const QScrollArea * getPianoRollEditorScrollArea() const
virtual void selectInstrumentNotes(int nInstrument)
virtual void mouseDragUpdateEvent(QMouseEvent *ev) override
int lineToPitch(int nLine)
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 pitchToLine(int nPitch)
void storeNoteProperties(const H2Core::Note *pNote)
Stores the properties of pNote in member variables.
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()
unsigned m_nOctaves
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.
virtual std::vector< SelectionIndex > elementsIntersecting(QRect r) override
Selections are indexed by Note pointers.
virtual void mouseDragUpdateEvent(QMouseEvent *ev) override
void moveNoteAction(int nColumn, H2Core::Note::Octave octave, H2Core::Note::Key key, int nPattern, int nNewColumn, H2Core::Note::Octave newOctave, H2Core::Note::Key newKey, H2Core::Note *pNote)
virtual void updateEditor(bool bPatternOnly=false) override
virtual void mousePressEvent(QMouseEvent *ev) override
Raw Qt mouse events are passed to the Selection.
virtual void selectionMoveEndEvent(QInputEvent *ev) override
virtual QRect getKeyboardCursorRect() override
Position of keyboard input cursor on screen.
void addOrDeleteNoteAction(int nColumn, int pressedLine, int selectedPatternNumber, int selectedinstrument, int oldLength, float oldVelocity, float fOldPan, float oldLeadLag, int oldNoteKeyVal, int oldOctaveKeyVal, float fProbability, bool noteOff, bool isDelete)
void addOrRemoveNote(int nColumn, int nRealColumn, int nLine, int nNotekey, int nOctave, bool bDoAdd=true, bool bDoDelete=true)
virtual void selectAll() override
void onPreferencesChanged(H2Core::Preferences::Changes changes)
virtual void songModeActivationEvent() override
virtual void keyPressEvent(QKeyEvent *ev) override
PianoRollEditor(QWidget *pParent, PatternEditorPanel *panel, QScrollArea *pScrollView)
QScrollArea * m_pScrollView
virtual void paste() override
Paste selection.
virtual void mouseDragStartEvent(QMouseEvent *ev) override
void createBackground() override
Updates m_pBackgroundPixmap to show the latest content.
virtual void selectedInstrumentChangedEvent() override
void drawNote(H2Core::Note *pNote, QPainter *pPainter, bool bIsForeground)
Draw a note.
virtual void mouseClickEvent(QMouseEvent *ev) override
virtual void paintEvent(QPaintEvent *ev) override
virtual void deleteSelection() override
virtual void selectedPatternChangedEvent() override
void drawFocus(QPainter &painter)
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