1 /*
2 * PianoRoll.cpp - implementation of piano roll which is used for actual
3 * writing of melodies
4 *
5 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
6 * Copyright (c) 2008 Andrew Kelley <superjoe30/at/gmail/dot/com>
7 *
8 * This file is part of LMMS - https://lmms.io
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public
21 * License along with this program (see COPYING); if not, write to the
22 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301 USA.
24 *
25 */
26
27 #include <QApplication>
28 #include <QClipboard>
29 #include <QKeyEvent>
30 #include <QLabel>
31 #include <QLayout>
32 #include <QMdiArea>
33 #include <QPainter>
34 #include <QPointer>
35 #include <QScrollBar>
36 #include <QStyleOption>
37 #include <QSignalMapper>
38
39 #ifndef __USE_XOPEN
40 #define __USE_XOPEN
41 #endif
42
43 #include <math.h>
44
45 #include "AutomationEditor.h"
46 #include "ActionGroup.h"
47 #include "ConfigManager.h"
48 #include "PianoRoll.h"
49 #include "BBTrackContainer.h"
50 #include "Clipboard.h"
51 #include "ComboBox.h"
52 #include "debug.h"
53 #include "DetuningHelper.h"
54 #include "embed.h"
55 #include "GuiApplication.h"
56 #include "gui_templates.h"
57 #include "InstrumentTrack.h"
58 #include "MainWindow.h"
59 #include "Pattern.h"
60 #include "SongEditor.h"
61 #include "TextFloat.h"
62 #include "TimeLineWidget.h"
63
64
65 #if QT_VERSION < 0x040800
66 #define MiddleButton MidButton
67 #endif
68
69
70 typedef AutomationPattern::timeMap timeMap;
71
72
73 // some constants...
74 const int INITIAL_PIANOROLL_WIDTH = 860;
75 const int INITIAL_PIANOROLL_HEIGHT = 480;
76
77 const int SCROLLBAR_SIZE = 12;
78 const int PIANO_X = 0;
79
80 const int WHITE_KEY_WIDTH = 64;
81 const int BLACK_KEY_WIDTH = 41;
82 const int WHITE_KEY_SMALL_HEIGHT = 18;
83 const int WHITE_KEY_BIG_HEIGHT = 24;
84 const int BLACK_KEY_HEIGHT = 16;
85 const int C_KEY_LABEL_X = WHITE_KEY_WIDTH - 19;
86 const int KEY_LINE_HEIGHT = 12;
87 const int OCTAVE_HEIGHT = KEY_LINE_HEIGHT * KeysPerOctave; // = 12 * 12;
88
89 const int NOTE_EDIT_RESIZE_BAR = 6;
90 const int NOTE_EDIT_MIN_HEIGHT = 50;
91 const int KEY_AREA_MIN_HEIGHT = 100;
92 const int PR_BOTTOM_MARGIN = SCROLLBAR_SIZE;
93 const int PR_TOP_MARGIN = 16;
94 const int PR_RIGHT_MARGIN = SCROLLBAR_SIZE;
95
96
97 // width of area used for resizing (the grip at the end of a note)
98 const int RESIZE_AREA_WIDTH = 9;
99
100 // width of line for setting volume/panning of note
101 const int NOTE_EDIT_LINE_WIDTH = 3;
102
103 // key where to start
104 const int INITIAL_START_KEY = Key_C + Octave_4 * KeysPerOctave;
105
106 // number of each note to provide in quantization and note lengths
107 const int NUM_EVEN_LENGTHS = 6;
108 const int NUM_TRIPLET_LENGTHS = 5;
109
110
111
112 QPixmap * PianoRoll::s_whiteKeySmallPm = NULL;
113 QPixmap * PianoRoll::s_whiteKeySmallPressedPm = NULL;
114 QPixmap * PianoRoll::s_whiteKeyBigPm = NULL;
115 QPixmap * PianoRoll::s_whiteKeyBigPressedPm = NULL;
116 QPixmap * PianoRoll::s_blackKeyPm = NULL;
117 QPixmap * PianoRoll::s_blackKeyPressedPm = NULL;
118 QPixmap * PianoRoll::s_toolDraw = NULL;
119 QPixmap * PianoRoll::s_toolErase = NULL;
120 QPixmap * PianoRoll::s_toolSelect = NULL;
121 QPixmap * PianoRoll::s_toolMove = NULL;
122 QPixmap * PianoRoll::s_toolOpen = NULL;
123
124 TextFloat * PianoRoll::s_textFloat = NULL;
125
126 static QString s_noteStrings[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
127
getNoteString(int key)128 static QString getNoteString( int key )
129 {
130 return s_noteStrings[key % 12] + QString::number( static_cast<int>( key / KeysPerOctave ) );
131 }
132
133
134
135 // used for drawing of piano
136 PianoRoll::PianoRollKeyTypes PianoRoll::prKeyOrder[] =
137 {
138 PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY,
139 PR_WHITE_KEY_SMALL, PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG,
140 PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, PR_WHITE_KEY_SMALL
141 } ;
142
143
144 const int DEFAULT_PR_PPT = KEY_LINE_HEIGHT * DefaultStepsPerTact;
145
146 const QVector<double> PianoRoll::m_zoomLevels =
147 { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f };
148
149
PianoRoll()150 PianoRoll::PianoRoll() :
151 m_nemStr( QVector<QString>() ),
152 m_noteEditMenu( NULL ),
153 m_semiToneMarkerMenu( NULL ),
154 m_zoomingModel(),
155 m_quantizeModel(),
156 m_noteLenModel(),
157 m_scaleModel(),
158 m_chordModel(),
159 m_pattern( NULL ),
160 m_currentPosition(),
161 m_recording( false ),
162 m_currentNote( NULL ),
163 m_action( ActionNone ),
164 m_noteEditMode( NoteEditVolume ),
165 m_moveBoundaryLeft( 0 ),
166 m_moveBoundaryTop( 0 ),
167 m_moveBoundaryRight( 0 ),
168 m_moveBoundaryBottom( 0 ),
169 m_mouseDownKey( 0 ),
170 m_mouseDownTick( 0 ),
171 m_lastMouseX( 0 ),
172 m_lastMouseY( 0 ),
173 m_oldNotesEditHeight( 100 ),
174 m_notesEditHeight( 100 ),
175 m_ppt( DEFAULT_PR_PPT ),
176 m_lenOfNewNotes( MidiTime( 0, DefaultTicksPerTact/4 ) ),
177 m_lastNoteVolume( DefaultVolume ),
178 m_lastNotePanning( DefaultPanning ),
179 m_startKey( INITIAL_START_KEY ),
180 m_lastKey( 0 ),
181 m_editMode( ModeDraw ),
182 m_ctrlMode( ModeDraw ),
183 m_mouseDownRight( false ),
184 m_scrollBack( false ),
185 m_barLineColor( 0, 0, 0 ),
186 m_beatLineColor( 0, 0, 0 ),
187 m_lineColor( 0, 0, 0 ),
188 m_noteModeColor( 0, 0, 0 ),
189 m_noteColor( 0, 0, 0 ),
190 m_barColor( 0, 0, 0 ),
191 m_selectedNoteColor( 0, 0, 0 ),
192 m_textColor( 0, 0, 0 ),
193 m_textColorLight( 0, 0, 0 ),
194 m_textShadow( 0, 0, 0 ),
195 m_markedSemitoneColor( 0, 0, 0 ),
196 m_noteOpacity( 255 ),
197 m_noteBorders( true ),
198 m_backgroundShade( 0, 0, 0 )
199 {
200 // gui names of edit modes
201 m_nemStr.push_back( tr( "Note Velocity" ) );
202 m_nemStr.push_back( tr( "Note Panning" ) );
203
204 QSignalMapper * signalMapper = new QSignalMapper( this );
205 m_noteEditMenu = new QMenu( this );
206 m_noteEditMenu->clear();
207 for( int i = 0; i < m_nemStr.size(); ++i )
208 {
209 QAction * act = new QAction( m_nemStr.at(i), this );
210 connect( act, SIGNAL(triggered()), signalMapper, SLOT(map()) );
211 signalMapper->setMapping( act, i );
212 m_noteEditMenu->addAction( act );
213 }
214 connect( signalMapper, SIGNAL(mapped(int)),
215 this, SLOT(changeNoteEditMode(int)) );
216
217 signalMapper = new QSignalMapper( this );
218 m_semiToneMarkerMenu = new QMenu( this );
219
220 QAction* markSemitoneAction = new QAction( tr("Mark/unmark current semitone"), this );
221 QAction* markAllOctaveSemitonesAction = new QAction( tr("Mark/unmark all corresponding octave semitones"), this );
222 QAction* markScaleAction = new QAction( tr("Mark current scale"), this );
223 QAction* markChordAction = new QAction( tr("Mark current chord"), this );
224 QAction* unmarkAllAction = new QAction( tr("Unmark all"), this );
225 QAction* copyAllNotesAction = new QAction( tr("Select all notes on this key"), this);
226
227 connect( markSemitoneAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
228 connect( markAllOctaveSemitonesAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
229 connect( markScaleAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
230 connect( markChordAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
231 connect( unmarkAllAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
232 connect( copyAllNotesAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
233
234 signalMapper->setMapping( markSemitoneAction, static_cast<int>( stmaMarkCurrentSemiTone ) );
235 signalMapper->setMapping( markAllOctaveSemitonesAction, static_cast<int>( stmaMarkAllOctaveSemiTones ) );
236 signalMapper->setMapping( markScaleAction, static_cast<int>( stmaMarkCurrentScale ) );
237 signalMapper->setMapping( markChordAction, static_cast<int>( stmaMarkCurrentChord ) );
238 signalMapper->setMapping( unmarkAllAction, static_cast<int>( stmaUnmarkAll ) );
239 signalMapper->setMapping( copyAllNotesAction, static_cast<int>( stmaCopyAllNotesOnKey ) );
240
241 markScaleAction->setEnabled( false );
242 markChordAction->setEnabled( false );
243
244 connect( this, SIGNAL(semiToneMarkerMenuScaleSetEnabled(bool)), markScaleAction, SLOT(setEnabled(bool)) );
245 connect( this, SIGNAL(semiToneMarkerMenuChordSetEnabled(bool)), markChordAction, SLOT(setEnabled(bool)) );
246
247 connect( signalMapper, SIGNAL(mapped(int)), this, SLOT(markSemiTone(int)) );
248
249 m_semiToneMarkerMenu->addAction( markSemitoneAction );
250 m_semiToneMarkerMenu->addAction( markAllOctaveSemitonesAction );
251 m_semiToneMarkerMenu->addAction( markScaleAction );
252 m_semiToneMarkerMenu->addAction( markChordAction );
253 m_semiToneMarkerMenu->addAction( unmarkAllAction );
254 m_semiToneMarkerMenu->addAction( copyAllNotesAction );
255
256 // init pixmaps
257 if( s_whiteKeySmallPm == NULL )
258 {
259 s_whiteKeySmallPm = new QPixmap( embed::getIconPixmap(
260 "pr_white_key_small" ) );
261 }
262 if( s_whiteKeySmallPressedPm == NULL )
263 {
264 s_whiteKeySmallPressedPm = new QPixmap( embed::getIconPixmap(
265 "pr_white_key_small_pressed" ) );
266 }
267 if( s_whiteKeyBigPm == NULL )
268 {
269 s_whiteKeyBigPm = new QPixmap( embed::getIconPixmap(
270 "pr_white_key_big" ) );
271 }
272 if( s_whiteKeyBigPressedPm == NULL )
273 {
274 s_whiteKeyBigPressedPm = new QPixmap( embed::getIconPixmap(
275 "pr_white_key_big_pressed" ) );
276 }
277 if( s_blackKeyPm == NULL )
278 {
279 s_blackKeyPm = new QPixmap( embed::getIconPixmap(
280 "pr_black_key" ) );
281 }
282 if( s_blackKeyPressedPm == NULL )
283 {
284 s_blackKeyPressedPm = new QPixmap( embed::getIconPixmap(
285 "pr_black_key_pressed" ) );
286 }
287 if( s_toolDraw == NULL )
288 {
289 s_toolDraw = new QPixmap( embed::getIconPixmap( "edit_draw" ) );
290 }
291 if( s_toolErase == NULL )
292 {
293 s_toolErase= new QPixmap( embed::getIconPixmap( "edit_erase" ) );
294 }
295 if( s_toolSelect == NULL )
296 {
297 s_toolSelect = new QPixmap( embed::getIconPixmap( "edit_select" ) );
298 }
299 if( s_toolMove == NULL )
300 {
301 s_toolMove = new QPixmap( embed::getIconPixmap( "edit_move" ) );
302 }
303 if( s_toolOpen == NULL )
304 {
305 s_toolOpen = new QPixmap( embed::getIconPixmap( "automation" ) );
306 }
307
308 // init text-float
309 if( s_textFloat == NULL )
310 {
311 s_textFloat = new TextFloat;
312 }
313
314 setAttribute( Qt::WA_OpaquePaintEvent, true );
315
316 // add time-line
317 m_timeLine = new TimeLineWidget( WHITE_KEY_WIDTH, 0, m_ppt,
318 Engine::getSong()->getPlayPos(
319 Song::Mode_PlayPattern ),
320 m_currentPosition, this );
321 connect( this, SIGNAL( positionChanged( const MidiTime & ) ),
322 m_timeLine, SLOT( updatePosition( const MidiTime & ) ) );
323 connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ),
324 this, SLOT( updatePosition( const MidiTime & ) ) );
325
326 // update timeline when in record-accompany mode
327 connect( Engine::getSong()->getPlayPos( Song::Mode_PlaySong ).m_timeLine,
328 SIGNAL( positionChanged( const MidiTime & ) ),
329 this,
330 SLOT( updatePositionAccompany( const MidiTime & ) ) );
331 // TODO
332 /* connect( engine::getSong()->getPlayPos( Song::Mode_PlayBB ).m_timeLine,
333 SIGNAL( positionChanged( const MidiTime & ) ),
334 this,
335 SLOT( updatePositionAccompany( const MidiTime & ) ) );*/
336
337 removeSelection();
338
339 // init scrollbars
340 m_leftRightScroll = new QScrollBar( Qt::Horizontal, this );
341 m_leftRightScroll->setSingleStep( 1 );
342 connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ), this,
343 SLOT( horScrolled( int ) ) );
344
345 m_topBottomScroll = new QScrollBar( Qt::Vertical, this );
346 m_topBottomScroll->setSingleStep( 1 );
347 m_topBottomScroll->setPageStep( 20 );
348 connect( m_topBottomScroll, SIGNAL( valueChanged( int ) ), this,
349 SLOT( verScrolled( int ) ) );
350
351 // setup zooming-stuff
352 for( float const & zoomLevel : m_zoomLevels )
353 {
354 m_zoomingModel.addItem( QString( "%1\%" ).arg( zoomLevel * 100 ) );
355 }
356 m_zoomingModel.setValue( m_zoomingModel.findText( "100%" ) );
357 connect( &m_zoomingModel, SIGNAL( dataChanged() ),
358 this, SLOT( zoomingChanged() ) );
359
360 // Set up quantization model
361 m_quantizeModel.addItem( tr( "Note lock" ) );
362 for( int i = 0; i <= NUM_EVEN_LENGTHS; ++i )
363 {
364 m_quantizeModel.addItem( "1/" + QString::number( 1 << i ) );
365 }
366 for( int i = 0; i < NUM_TRIPLET_LENGTHS; ++i )
367 {
368 m_quantizeModel.addItem( "1/" + QString::number( (1 << i) * 3 ) );
369 }
370 m_quantizeModel.addItem( "1/192" );
371 m_quantizeModel.setValue( m_quantizeModel.findText( "1/16" ) );
372
373 connect( &m_quantizeModel, SIGNAL( dataChanged() ),
374 this, SLOT( quantizeChanged() ) );
375
376 // Set up note length model
377 m_noteLenModel.addItem( tr( "Last note" ),
378 new PixmapLoader( "edit_draw" ) );
379 const QString pixmaps[] = { "whole", "half", "quarter", "eighth",
380 "sixteenth", "thirtysecond", "triplethalf",
381 "tripletquarter", "tripleteighth",
382 "tripletsixteenth", "tripletthirtysecond" } ;
383
384 for( int i = 0; i < NUM_EVEN_LENGTHS; ++i )
385 {
386 PixmapLoader *loader = new PixmapLoader( "note_" + pixmaps[i] );
387 m_noteLenModel.addItem( "1/" + QString::number( 1 << i ), loader );
388 }
389 for( int i = 0; i < NUM_TRIPLET_LENGTHS; ++i )
390 {
391 PixmapLoader *loader = new PixmapLoader( "note_" + pixmaps[i+NUM_EVEN_LENGTHS] );
392 m_noteLenModel.addItem( "1/" + QString::number( (1 << i) * 3 ), loader );
393 }
394 m_noteLenModel.setValue( 0 );
395
396 // Note length change can cause a redraw if Q is set to lock
397 connect( &m_noteLenModel, SIGNAL( dataChanged() ),
398 this, SLOT( quantizeChanged() ) );
399
400 // Set up scale model
401 const InstrumentFunctionNoteStacking::ChordTable& chord_table =
402 InstrumentFunctionNoteStacking::ChordTable::getInstance();
403
404 m_scaleModel.addItem( tr("No scale") );
405 for( const InstrumentFunctionNoteStacking::Chord& chord : chord_table )
406 {
407 if( chord.isScale() )
408 {
409 m_scaleModel.addItem( chord.getName() );
410 }
411 }
412
413 m_scaleModel.setValue( 0 );
414 // change can update m_semiToneMarkerMenu
415 connect( &m_scaleModel, SIGNAL( dataChanged() ),
416 this, SLOT( updateSemiToneMarkerMenu() ) );
417
418 // Set up chord model
419 m_chordModel.addItem( tr("No chord") );
420 for( const InstrumentFunctionNoteStacking::Chord& chord : chord_table )
421 {
422 if( ! chord.isScale() )
423 {
424 m_chordModel.addItem( chord.getName() );
425 }
426 }
427
428 m_chordModel.setValue( 0 );
429
430 // change can update m_semiToneMarkerMenu
431 connect( &m_chordModel, SIGNAL( dataChanged() ),
432 this, SLOT( updateSemiToneMarkerMenu() ) );
433
434 setFocusPolicy( Qt::StrongFocus );
435 setFocus();
436 setMouseTracking( true );
437
438 connect( &m_scaleModel, SIGNAL( dataChanged() ),
439 this, SLOT( updateSemiToneMarkerMenu() ) );
440
441 connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ),
442 this, SLOT( update() ) );
443
444 //connection for selecion from timeline
445 connect( m_timeLine, SIGNAL( regionSelectedFromPixels( int, int ) ),
446 this, SLOT( selectRegionFromPixels( int, int ) ) );
447 }
448
449
450
reset()451 void PianoRoll::reset()
452 {
453 m_lastNoteVolume = DefaultVolume;
454 m_lastNotePanning = DefaultPanning;
455 }
456
showTextFloat(const QString & text,const QPoint & pos,int timeout)457 void PianoRoll::showTextFloat(const QString &text, const QPoint &pos, int timeout)
458 {
459 s_textFloat->setText( text );
460 // show the float, offset slightly so as to not obscure anything
461 s_textFloat->moveGlobal( this, pos + QPoint(4, 16) );
462 if (timeout == -1)
463 {
464 s_textFloat->show();
465 }
466 else
467 {
468 s_textFloat->setVisibilityTimeOut( timeout );
469 }
470 }
471
472
showVolTextFloat(volume_t vol,const QPoint & pos,int timeout)473 void PianoRoll::showVolTextFloat(volume_t vol, const QPoint &pos, int timeout)
474 {
475 //! \todo display velocity for MIDI-based instruments
476 // possibly dBFS values too? not sure if it makes sense for note volumes...
477 showTextFloat( tr("Velocity: %1%").arg( vol ), pos, timeout );
478 }
479
480
showPanTextFloat(panning_t pan,const QPoint & pos,int timeout)481 void PianoRoll::showPanTextFloat(panning_t pan, const QPoint &pos, int timeout)
482 {
483 QString text;
484 if( pan < 0 )
485 {
486 text = tr("Panning: %1% left").arg( qAbs( pan ) );
487 }
488 else if( pan > 0 )
489 {
490 text = tr("Panning: %1% right").arg( qAbs( pan ) );
491 }
492 else
493 {
494 text = tr("Panning: center");
495 }
496 showTextFloat( text, pos, timeout );
497 }
498
499
500
changeNoteEditMode(int i)501 void PianoRoll::changeNoteEditMode( int i )
502 {
503 m_noteEditMode = (NoteEditMode) i;
504 repaint();
505 }
506
507
markSemiTone(int i)508 void PianoRoll::markSemiTone( int i )
509 {
510 const int key = m_pianoKeySelected;
511 const InstrumentFunctionNoteStacking::Chord * chord = nullptr;
512
513 switch( static_cast<SemiToneMarkerAction>( i ) )
514 {
515 case stmaUnmarkAll:
516 m_markedSemiTones.clear();
517 break;
518 case stmaMarkCurrentSemiTone:
519 {
520 QList<int>::iterator it = qFind( m_markedSemiTones.begin(), m_markedSemiTones.end(), key );
521 if( it != m_markedSemiTones.end() )
522 {
523 m_markedSemiTones.erase( it );
524 }
525 else
526 {
527 m_markedSemiTones.push_back( key );
528 }
529 break;
530 }
531 case stmaMarkAllOctaveSemiTones:
532 {
533 QList<int> aok = getAllOctavesForKey(key);
534
535 if ( m_markedSemiTones.contains(key) )
536 {
537 // lets erase all of the ones that match this by octave
538 QList<int>::iterator i;
539 for (int ix = 0; ix < aok.size(); ++ix)
540 {
541 i = qFind(m_markedSemiTones.begin(), m_markedSemiTones.end(), aok.at(ix));
542 if (i != m_markedSemiTones.end())
543 {
544 m_markedSemiTones.erase(i);
545 }
546 }
547 }
548 else
549 {
550 // we should add all of the ones that match this by octave
551 m_markedSemiTones.append(aok);
552 }
553
554 break;
555 }
556 case stmaMarkCurrentScale:
557 chord = & InstrumentFunctionNoteStacking::ChordTable::getInstance()
558 .getScaleByName( m_scaleModel.currentText() );
559 case stmaMarkCurrentChord:
560 {
561 if( ! chord )
562 {
563 chord = & InstrumentFunctionNoteStacking::ChordTable::getInstance()
564 .getChordByName( m_chordModel.currentText() );
565 }
566
567 if( chord->isEmpty() )
568 {
569 break;
570 }
571 else if( chord->isScale() )
572 {
573 m_markedSemiTones.clear();
574 }
575
576 const int first = chord->isScale() ? 0 : key;
577 const int last = chord->isScale() ? NumKeys : key + chord->last();
578 const int cap = ( chord->isScale() || chord->last() == 0 ) ? KeysPerOctave : chord->last();
579
580 for( int i = first; i <= last; i++ )
581 {
582 if( chord->hasSemiTone( ( i + cap - ( key % cap ) ) % cap ) )
583 {
584 m_markedSemiTones.push_back( i );
585 }
586 }
587 break;
588 }
589 case stmaCopyAllNotesOnKey:
590 {
591 selectNotesOnKey();
592 break;
593 }
594 default:
595 ;
596 }
597
598 qSort( m_markedSemiTones.begin(), m_markedSemiTones.end(), qGreater<int>() );
599 QList<int>::iterator new_end = std::unique( m_markedSemiTones.begin(), m_markedSemiTones.end() );
600 m_markedSemiTones.erase( new_end, m_markedSemiTones.end() );
601 }
602
603
~PianoRoll()604 PianoRoll::~PianoRoll()
605 {
606 }
607
608
setCurrentPattern(Pattern * newPattern)609 void PianoRoll::setCurrentPattern( Pattern* newPattern )
610 {
611 if( hasValidPattern() )
612 {
613 m_pattern->instrumentTrack()->disconnect( this );
614 }
615
616 // force the song-editor to stop playing if it played pattern before
617 if( Engine::getSong()->isPlaying() &&
618 Engine::getSong()->playMode() == Song::Mode_PlayPattern )
619 {
620 Engine::getSong()->playPattern( NULL );
621 }
622
623 // set new data
624 m_pattern = newPattern;
625 m_currentPosition = 0;
626 m_currentNote = NULL;
627 m_startKey = INITIAL_START_KEY;
628
629 if( ! hasValidPattern() )
630 {
631 //resizeEvent( NULL );
632
633 update();
634 emit currentPatternChanged();
635 return;
636 }
637
638 m_leftRightScroll->setValue( 0 );
639
640 // determine the central key so that we can scroll to it
641 int central_key = 0;
642 int total_notes = 0;
643 for( const Note *note : m_pattern->notes() )
644 {
645 if( note->length() > 0 )
646 {
647 central_key += note->key();
648 ++total_notes;
649 }
650 }
651
652 if( total_notes > 0 )
653 {
654 central_key = central_key / total_notes -
655 ( KeysPerOctave * NumOctaves - m_totalKeysToScroll ) / 2;
656 m_startKey = tLimit( central_key, 0, NumOctaves * KeysPerOctave );
657 }
658
659 // resizeEvent() does the rest for us (scrolling, range-checking
660 // of start-notes and so on...)
661 resizeEvent( NULL );
662
663 // make sure to always get informed about the pattern being destroyed
664 connect( m_pattern, SIGNAL( destroyedPattern( Pattern* ) ), this, SLOT( hidePattern( Pattern* ) ) );
665
666 connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOn( const Note& ) ), this, SLOT( startRecordNote( const Note& ) ) );
667 connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOff( const Note& ) ), this, SLOT( finishRecordNote( const Note& ) ) );
668 connect( m_pattern->instrumentTrack()->pianoModel(), SIGNAL( dataChanged() ), this, SLOT( update() ) );
669
670 update();
671 emit currentPatternChanged();
672 }
673
674
675
hidePattern(Pattern * pattern)676 void PianoRoll::hidePattern( Pattern* pattern )
677 {
678 if( m_pattern == pattern )
679 {
680 setCurrentPattern( NULL );
681 }
682 }
683
selectRegionFromPixels(int xStart,int xEnd)684 void PianoRoll::selectRegionFromPixels( int xStart, int xEnd )
685 {
686
687 xStart -= WHITE_KEY_WIDTH;
688 xEnd -= WHITE_KEY_WIDTH;
689
690 // select an area of notes
691 int pos_ticks = xStart * MidiTime::ticksPerTact() / m_ppt +
692 m_currentPosition;
693 int key_num = 0;
694 m_selectStartTick = pos_ticks;
695 m_selectedTick = 0;
696 m_selectStartKey = key_num;
697 m_selectedKeys = 1;
698 // change size of selection
699
700 // get tick in which the cursor is posated
701 pos_ticks = xEnd * MidiTime::ticksPerTact() / m_ppt +
702 m_currentPosition;
703 key_num = 120;
704
705 m_selectedTick = pos_ticks - m_selectStartTick;
706 if( (int) m_selectStartTick + m_selectedTick < 0 )
707 {
708 m_selectedTick = -static_cast<int>(
709 m_selectStartTick );
710 }
711 m_selectedKeys = key_num - m_selectStartKey;
712 if( key_num <= m_selectStartKey )
713 {
714 --m_selectedKeys;
715 }
716
717 computeSelectedNotes( false );
718 }
719
720
721
722
723
724
725 /** \brief qproperty access implementation */
726
barLineColor() const727 QColor PianoRoll::barLineColor() const
728 { return m_barLineColor; }
729
setBarLineColor(const QColor & c)730 void PianoRoll::setBarLineColor( const QColor & c )
731 { m_barLineColor = c; }
732
beatLineColor() const733 QColor PianoRoll::beatLineColor() const
734 { return m_beatLineColor; }
735
setBeatLineColor(const QColor & c)736 void PianoRoll::setBeatLineColor( const QColor & c )
737 { m_beatLineColor = c; }
738
lineColor() const739 QColor PianoRoll::lineColor() const
740 { return m_lineColor; }
741
setLineColor(const QColor & c)742 void PianoRoll::setLineColor( const QColor & c )
743 { m_lineColor = c; }
744
noteModeColor() const745 QColor PianoRoll::noteModeColor() const
746 { return m_noteModeColor; }
747
setNoteModeColor(const QColor & c)748 void PianoRoll::setNoteModeColor( const QColor & c )
749 { m_noteModeColor = c; }
750
noteColor() const751 QColor PianoRoll::noteColor() const
752 { return m_noteColor; }
753
setNoteColor(const QColor & c)754 void PianoRoll::setNoteColor( const QColor & c )
755 { m_noteColor = c; }
756
barColor() const757 QColor PianoRoll::barColor() const
758 { return m_barColor; }
759
setBarColor(const QColor & c)760 void PianoRoll::setBarColor( const QColor & c )
761 { m_barColor = c; }
762
selectedNoteColor() const763 QColor PianoRoll::selectedNoteColor() const
764 { return m_selectedNoteColor; }
765
setSelectedNoteColor(const QColor & c)766 void PianoRoll::setSelectedNoteColor( const QColor & c )
767 { m_selectedNoteColor = c; }
768
textColor() const769 QColor PianoRoll::textColor() const
770 { return m_textColor; }
771
setTextColor(const QColor & c)772 void PianoRoll::setTextColor( const QColor & c )
773 { m_textColor = c; }
774
textColorLight() const775 QColor PianoRoll::textColorLight() const
776 { return m_textColorLight; }
777
setTextColorLight(const QColor & c)778 void PianoRoll::setTextColorLight( const QColor & c )
779 { m_textColorLight = c; }
780
textShadow() const781 QColor PianoRoll::textShadow() const
782 { return m_textShadow; }
783
setTextShadow(const QColor & c)784 void PianoRoll::setTextShadow( const QColor & c )
785 { m_textShadow = c; }
786
markedSemitoneColor() const787 QColor PianoRoll::markedSemitoneColor() const
788 { return m_markedSemitoneColor; }
789
setMarkedSemitoneColor(const QColor & c)790 void PianoRoll::setMarkedSemitoneColor( const QColor & c )
791 { m_markedSemitoneColor = c; }
792
noteOpacity() const793 int PianoRoll::noteOpacity() const
794 { return m_noteOpacity; }
795
setNoteOpacity(const int i)796 void PianoRoll::setNoteOpacity( const int i )
797 { m_noteOpacity = i; }
798
noteBorders() const799 bool PianoRoll::noteBorders() const
800 { return m_noteBorders; }
801
setNoteBorders(const bool b)802 void PianoRoll::setNoteBorders( const bool b )
803 { m_noteBorders = b; }
804
backgroundShade() const805 QColor PianoRoll::backgroundShade() const
806 { return m_backgroundShade; }
807
setBackgroundShade(const QColor & c)808 void PianoRoll::setBackgroundShade( const QColor & c )
809 { m_backgroundShade = c; }
810
811
812
813
814
drawNoteRect(QPainter & p,int x,int y,int width,const Note * n,const QColor & noteCol,const QColor & selCol,const int noteOpc,const bool borders)815 void PianoRoll::drawNoteRect( QPainter & p, int x, int y,
816 int width, const Note * n, const QColor & noteCol,
817 const QColor & selCol, const int noteOpc, const bool borders )
818 {
819 ++x;
820 ++y;
821 width -= 2;
822
823 if( width <= 0 )
824 {
825 width = 2;
826 }
827
828 int volVal = qMin( 255, 100 + (int) ( ( (float)( n->getVolume() - MinVolume ) ) /
829 ( (float)( MaxVolume - MinVolume ) ) * 155.0f) );
830 float rightPercent = qMin<float>( 1.0f,
831 ( (float)( n->getPanning() - PanningLeft ) ) /
832 ( (float)( PanningRight - PanningLeft ) ) * 2.0f );
833
834 float leftPercent = qMin<float>( 1.0f,
835 ( (float)( PanningRight - n->getPanning() ) ) /
836 ( (float)( PanningRight - PanningLeft ) ) * 2.0f );
837
838 QColor col = QColor( noteCol );
839 QPen pen;
840
841 if( n->selected() )
842 {
843 col = QColor( selCol );
844 }
845
846 const int borderWidth = borders ? 1 : 0;
847
848 const int noteHeight = KEY_LINE_HEIGHT - 1 - borderWidth;
849 int noteWidth = width + 1 - borderWidth;
850
851 // adjust note to make it a bit faded if it has a lower volume
852 // in stereo using gradients
853 QColor lcol = QColor::fromHsv( col.hue(), col.saturation(),
854 volVal * leftPercent, noteOpc );
855 QColor rcol = QColor::fromHsv( col.hue(), col.saturation(),
856 volVal * rightPercent, noteOpc );
857
858 QLinearGradient gradient( x, y, x, y + noteHeight );
859 gradient.setColorAt( 0, rcol );
860 gradient.setColorAt( 1, lcol );
861 p.setBrush( gradient );
862
863 if ( borders )
864 {
865 p.setPen( col );
866 }
867 else
868 {
869 p.setPen( Qt::NoPen );
870 }
871
872 p.drawRect( x, y, noteWidth, noteHeight );
873
874 // draw the note endmark, to hint the user to resize
875 p.setBrush( col );
876 if( width > 2 )
877 {
878 const int endmarkWidth = 3 - borderWidth;
879 p.drawRect( x + noteWidth - endmarkWidth, y, endmarkWidth, noteHeight );
880 }
881 }
882
883
884
885
drawDetuningInfo(QPainter & _p,const Note * _n,int _x,int _y) const886 void PianoRoll::drawDetuningInfo( QPainter & _p, const Note * _n, int _x,
887 int _y ) const
888 {
889 int middle_y = _y + KEY_LINE_HEIGHT / 2;
890 _p.setPen( noteColor() );
891 _p.setClipRect(WHITE_KEY_WIDTH, PR_TOP_MARGIN,
892 width() - WHITE_KEY_WIDTH,
893 keyAreaBottom() - PR_TOP_MARGIN);
894
895 int old_x = 0;
896 int old_y = 0;
897
898 timeMap & map = _n->detuning()->automationPattern()->getTimeMap();
899 for( timeMap::ConstIterator it = map.begin(); it != map.end(); ++it )
900 {
901 int pos_ticks = it.key();
902 int pos_x = _x + pos_ticks * m_ppt / MidiTime::ticksPerTact();
903
904 const float level = it.value();
905
906 int pos_y = middle_y - level * KEY_LINE_HEIGHT;
907
908 if( old_x != 0 && old_y != 0 )
909 {
910 switch( _n->detuning()->automationPattern()->progressionType() )
911 {
912 case AutomationPattern::DiscreteProgression:
913 _p.drawLine( old_x, old_y, pos_x, old_y );
914 _p.drawLine( pos_x, old_y, pos_x, pos_y );
915 break;
916 case AutomationPattern::CubicHermiteProgression: /* TODO */
917 case AutomationPattern::LinearProgression:
918 _p.drawLine( old_x, old_y, pos_x, pos_y );
919 break;
920 }
921 }
922
923 _p.drawLine( pos_x - 1, pos_y, pos_x + 1, pos_y );
924 _p.drawLine( pos_x, pos_y - 1, pos_x, pos_y + 1 );
925
926 old_x = pos_x;
927 old_y = pos_y;
928 }
929 }
930
931
932
933
removeSelection()934 void PianoRoll::removeSelection()
935 {
936 m_selectStartTick = 0;
937 m_selectedTick = 0;
938 m_selectStartKey = 0;
939 m_selectedKeys = 0;
940 }
941
942
943
944
clearSelectedNotes()945 void PianoRoll::clearSelectedNotes()
946 {
947 if( m_pattern != NULL )
948 {
949 for( Note *note : m_pattern->notes() )
950 {
951 note->setSelected( false );
952 }
953 }
954 }
955
956
957
958
shiftSemiTone(int amount)959 void PianoRoll::shiftSemiTone( int amount ) // shift notes by amount semitones
960 {
961 if (!hasValidPattern()) {return;}
962
963 m_pattern->addJournalCheckPoint();
964 bool useAllNotes = ! isSelection();
965 for( Note *note : m_pattern->notes() )
966 {
967 // if none are selected, move all notes, otherwise
968 // only move selected notes
969 if( useAllNotes || note->selected() )
970 {
971 note->setKey( note->key() + amount );
972 }
973 }
974
975 m_pattern->rearrangeAllNotes();
976 m_pattern->dataChanged();
977
978 // we modified the song
979 update();
980 gui->songEditor()->update();
981 }
982
983
984
985
shiftPos(int amount)986 void PianoRoll::shiftPos( int amount ) //shift notes pos by amount
987 {
988 if (!hasValidPattern()) {return;}
989
990 m_pattern->addJournalCheckPoint();
991 bool useAllNotes = ! isSelection();
992
993 bool first = true;
994 for( Note *note : m_pattern->notes() )
995 {
996 // if none are selected, move all notes, otherwise
997 // only move selected notes
998 if( note->selected() || (useAllNotes && note->length() > 0) )
999 {
1000 // don't let notes go to out of bounds
1001 if( first )
1002 {
1003 m_moveBoundaryLeft = note->pos();
1004 if( m_moveBoundaryLeft + amount < 0 )
1005 {
1006 amount += 0 - (amount + m_moveBoundaryLeft);
1007 }
1008 first = false;
1009 }
1010 note->setPos( note->pos() + amount );
1011 }
1012 }
1013
1014 m_pattern->rearrangeAllNotes();
1015 m_pattern->updateLength();
1016 m_pattern->dataChanged();
1017
1018 // we modified the song
1019 update();
1020 gui->songEditor()->update();
1021 }
1022
1023
1024
1025
isSelection() const1026 bool PianoRoll::isSelection() const // are any notes selected?
1027 {
1028 for( const Note *note : m_pattern->notes() )
1029 {
1030 if( note->selected() )
1031 {
1032 return true;
1033 }
1034 }
1035
1036 return false;
1037 }
1038
1039
1040
selectionCount() const1041 int PianoRoll::selectionCount() const // how many notes are selected?
1042 {
1043 int sum = 0;
1044
1045 for( const Note *note : m_pattern->notes() )
1046 {
1047 if( note->selected() )
1048 {
1049 ++sum;
1050 }
1051 }
1052
1053 return sum;
1054 }
1055
1056
1057
keyPressEvent(QKeyEvent * ke)1058 void PianoRoll::keyPressEvent(QKeyEvent* ke )
1059 {
1060 if( hasValidPattern() && ke->modifiers() == Qt::NoModifier )
1061 {
1062 const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave;
1063
1064 if(! ke->isAutoRepeat() && key_num > -1)
1065 {
1066 m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key_num );
1067 ke->accept();
1068 }
1069 }
1070
1071 switch( ke->key() )
1072 {
1073 case Qt::Key_Up:
1074 case Qt::Key_Down:
1075 {
1076 int direction = (ke->key() == Qt::Key_Up ? +1 : -1);
1077 if( ( ke->modifiers() & Qt::ControlModifier ) && m_action == ActionNone )
1078 {
1079 // shift selection up an octave
1080 // if nothing selected, shift _everything_
1081 if (hasValidPattern())
1082 {
1083 shiftSemiTone( 12 * direction );
1084 }
1085 }
1086 else if((ke->modifiers() & Qt::ShiftModifier) && m_action == ActionNone)
1087 {
1088 // Move selected notes up by one semitone
1089 if (hasValidPattern())
1090 {
1091 shiftSemiTone( 1 * direction );
1092 }
1093 }
1094 else
1095 {
1096 // scroll
1097 m_topBottomScroll->setValue( m_topBottomScroll->value() -
1098 cm_scrollAmtVert * direction );
1099
1100 // if they are moving notes around or resizing,
1101 // recalculate the note/resize position
1102 if( m_action == ActionMoveNote ||
1103 m_action == ActionResizeNote )
1104 {
1105 dragNotes( m_lastMouseX, m_lastMouseY,
1106 ke->modifiers() & Qt::AltModifier,
1107 ke->modifiers() & Qt::ShiftModifier,
1108 ke->modifiers() & Qt::ControlModifier );
1109 }
1110 }
1111 ke->accept();
1112 break;
1113 }
1114
1115 case Qt::Key_Right:
1116 case Qt::Key_Left:
1117 {
1118 int direction = (ke->key() == Qt::Key_Right ? +1 : -1);
1119 if( ke->modifiers() & Qt::ControlModifier && m_action == ActionNone )
1120 {
1121 // Move selected notes by one bar to the left
1122 if (hasValidPattern())
1123 {
1124 shiftPos( direction * MidiTime::ticksPerTact() );
1125 }
1126 }
1127 else if( ke->modifiers() & Qt::ShiftModifier && m_action == ActionNone)
1128 {
1129 // move notes
1130 if (hasValidPattern())
1131 {
1132 bool quantized = ! ( ke->modifiers() & Qt::AltModifier );
1133 int amt = quantized ? quantization() : 1;
1134 shiftPos( direction * amt );
1135 }
1136 }
1137 else if( ke->modifiers() & Qt::AltModifier)
1138 {
1139 // switch to editing a pattern adjacent to this one in the song editor
1140 if (hasValidPattern())
1141 {
1142 Pattern * p = direction > 0 ? m_pattern->nextPattern()
1143 : m_pattern->previousPattern();
1144 if(p != NULL)
1145 {
1146 setCurrentPattern(p);
1147 }
1148 }
1149 }
1150 else
1151 {
1152 // scroll
1153 m_leftRightScroll->setValue( m_leftRightScroll->value() +
1154 direction * cm_scrollAmtHoriz );
1155
1156 // if they are moving notes around or resizing,
1157 // recalculate the note/resize position
1158 if( m_action == ActionMoveNote ||
1159 m_action == ActionResizeNote )
1160 {
1161 dragNotes( m_lastMouseX, m_lastMouseY,
1162 ke->modifiers() & Qt::AltModifier,
1163 ke->modifiers() & Qt::ShiftModifier,
1164 ke->modifiers() & Qt::ControlModifier );
1165 }
1166
1167 }
1168 ke->accept();
1169 break;
1170 }
1171
1172 case Qt::Key_A:
1173 if( ke->modifiers() & Qt::ControlModifier )
1174 {
1175 ke->accept();
1176 if (ke->modifiers() & Qt::ShiftModifier)
1177 {
1178 // Ctrl + Shift + A = deselect all notes
1179 clearSelectedNotes();
1180 }
1181 else
1182 {
1183 // Ctrl + A = select all notes
1184 selectAll();
1185 }
1186 update();
1187 }
1188 break;
1189
1190 case Qt::Key_Escape:
1191 // Same as Ctrl + Shift + A
1192 clearSelectedNotes();
1193 break;
1194
1195 case Qt::Key_Backspace:
1196 case Qt::Key_Delete:
1197 deleteSelectedNotes();
1198 ke->accept();
1199 break;
1200
1201 case Qt::Key_Home:
1202 m_timeLine->pos().setTicks( 0 );
1203 m_timeLine->updatePosition();
1204 ke->accept();
1205 break;
1206
1207 case Qt::Key_0:
1208 case Qt::Key_1:
1209 case Qt::Key_2:
1210 case Qt::Key_3:
1211 case Qt::Key_4:
1212 case Qt::Key_5:
1213 case Qt::Key_6:
1214 case Qt::Key_7:
1215 case Qt::Key_8:
1216 case Qt::Key_9:
1217 {
1218 int len = 1 + ke->key() - Qt::Key_0;
1219 if( len == 10 )
1220 {
1221 len = 0;
1222 }
1223 if( ke->modifiers() & ( Qt::ControlModifier | Qt::KeypadModifier ) )
1224 {
1225 m_noteLenModel.setValue( len );
1226 ke->accept();
1227 }
1228 else if( ke->modifiers() & Qt::AltModifier )
1229 {
1230 m_quantizeModel.setValue( len );
1231 ke->accept();
1232 }
1233 break;
1234 }
1235
1236 case Qt::Key_Control:
1237 // Enter selection mode if:
1238 // -> this window is active
1239 // -> shift is not pressed
1240 // (<S-C-drag> is shortcut for sticky note resize)
1241 if ( !( ke->modifiers() & Qt::ShiftModifier ) && isActiveWindow() )
1242 {
1243 m_ctrlMode = m_editMode;
1244 m_editMode = ModeSelect;
1245 QApplication::changeOverrideCursor( Qt::ArrowCursor );
1246 ke->accept();
1247 }
1248 break;
1249 default:
1250 break;
1251 }
1252
1253 update();
1254 }
1255
1256
1257
1258
keyReleaseEvent(QKeyEvent * ke)1259 void PianoRoll::keyReleaseEvent(QKeyEvent* ke )
1260 {
1261 if( hasValidPattern() && ke->modifiers() == Qt::NoModifier )
1262 {
1263 const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave;
1264
1265 if( ! ke->isAutoRepeat() && key_num > -1 )
1266 {
1267 m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( key_num );
1268 ke->accept();
1269 }
1270 }
1271
1272 switch( ke->key() )
1273 {
1274 case Qt::Key_Control:
1275 computeSelectedNotes( ke->modifiers() & Qt::ShiftModifier);
1276 m_editMode = m_ctrlMode;
1277 update();
1278 break;
1279
1280 // update after undo/redo
1281 case Qt::Key_Z:
1282 case Qt::Key_R:
1283 if( hasValidPattern() && ke->modifiers() == Qt::ControlModifier )
1284 {
1285 update();
1286 }
1287 break;
1288 }
1289
1290 update();
1291 }
1292
1293
1294
1295
leaveEvent(QEvent * e)1296 void PianoRoll::leaveEvent(QEvent * e )
1297 {
1298 while( QApplication::overrideCursor() != NULL )
1299 {
1300 QApplication::restoreOverrideCursor();
1301 }
1302
1303 QWidget::leaveEvent( e );
1304 s_textFloat->hide();
1305 update(); // cleaning inner mouse-related graphics
1306 }
1307
1308
1309
1310
noteEditTop() const1311 int PianoRoll::noteEditTop() const
1312 {
1313 return height() - PR_BOTTOM_MARGIN -
1314 m_notesEditHeight + NOTE_EDIT_RESIZE_BAR;
1315 }
1316
1317
1318
1319
noteEditBottom() const1320 int PianoRoll::noteEditBottom() const
1321 {
1322 return height() - PR_BOTTOM_MARGIN;
1323 }
1324
1325
1326
1327
noteEditRight() const1328 int PianoRoll::noteEditRight() const
1329 {
1330 return width() - PR_RIGHT_MARGIN;
1331 }
1332
1333
1334
1335
noteEditLeft() const1336 int PianoRoll::noteEditLeft() const
1337 {
1338 return WHITE_KEY_WIDTH;
1339 }
1340
1341
1342
1343
keyAreaTop() const1344 int PianoRoll::keyAreaTop() const
1345 {
1346 return PR_TOP_MARGIN;
1347 }
1348
1349
1350
1351
keyAreaBottom() const1352 int PianoRoll::keyAreaBottom() const
1353 {
1354 return height() - PR_BOTTOM_MARGIN - m_notesEditHeight;
1355 }
1356
1357
1358
1359
mousePressEvent(QMouseEvent * me)1360 void PianoRoll::mousePressEvent(QMouseEvent * me )
1361 {
1362 m_startedWithShift = me->modifiers() & Qt::ShiftModifier;
1363
1364 if( ! hasValidPattern() )
1365 {
1366 return;
1367 }
1368
1369 if( m_editMode == ModeEditDetuning && noteUnderMouse() )
1370 {
1371 static QPointer<AutomationPattern> detuningPattern = nullptr;
1372 if (detuningPattern.data() != nullptr)
1373 {
1374 detuningPattern->disconnect(this);
1375 }
1376 Note* n = noteUnderMouse();
1377 if (n->detuning() == nullptr)
1378 {
1379 n->createDetuning();
1380 }
1381 detuningPattern = n->detuning()->automationPattern();
1382 connect(detuningPattern.data(), SIGNAL(dataChanged()), this, SLOT(update()));
1383 gui->automationEditor()->open(detuningPattern);
1384 return;
1385 }
1386
1387 // if holding control, go to selection mode unless shift is also pressed
1388 if( me->modifiers() & Qt::ControlModifier && m_editMode != ModeSelect )
1389 {
1390 m_ctrlMode = m_editMode;
1391 m_editMode = ModeSelect;
1392 QApplication::changeOverrideCursor( QCursor( Qt::ArrowCursor ) );
1393 update();
1394 }
1395
1396 // keep track of the point where the user clicked down
1397 if( me->button() == Qt::LeftButton )
1398 {
1399 m_moveStartX = me->x();
1400 m_moveStartY = me->y();
1401 }
1402
1403 if( me->y() > keyAreaBottom() && me->y() < noteEditTop() )
1404 {
1405 // resizing the note edit area
1406 m_action = ActionResizeNoteEditArea;
1407 m_oldNotesEditHeight = m_notesEditHeight;
1408 return;
1409 }
1410
1411 if( me->y() > PR_TOP_MARGIN )
1412 {
1413 bool edit_note = ( me->y() > noteEditTop() );
1414
1415 int key_num = getKey( me->y() );
1416
1417 int x = me->x();
1418
1419
1420 if( x > WHITE_KEY_WIDTH )
1421 {
1422 // set, move or resize note
1423
1424 x -= WHITE_KEY_WIDTH;
1425
1426 // get tick in which the user clicked
1427 int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
1428 m_currentPosition;
1429
1430
1431 // get note-vector of current pattern
1432 const NoteVector & notes = m_pattern->notes();
1433
1434 // will be our iterator in the following loop
1435 NoteVector::ConstIterator it = notes.begin()+notes.size()-1;
1436
1437 // loop through whole note-vector...
1438 for( int i = 0; i < notes.size(); ++i )
1439 {
1440 Note *note = *it;
1441 MidiTime len = note->length();
1442 if( len < 0 )
1443 {
1444 len = 4;
1445 }
1446 // and check whether the user clicked on an
1447 // existing note or an edit-line
1448 if( pos_ticks >= note->pos() &&
1449 len > 0 &&
1450 (
1451 ( ! edit_note &&
1452 pos_ticks <= note->pos() + len &&
1453 note->key() == key_num )
1454 ||
1455 ( edit_note &&
1456 pos_ticks <= note->pos() +
1457 NOTE_EDIT_LINE_WIDTH * MidiTime::ticksPerTact() / m_ppt )
1458 )
1459 )
1460 {
1461 break;
1462 }
1463 --it;
1464 }
1465
1466 // first check whether the user clicked in note-edit-
1467 // area
1468 if( edit_note )
1469 {
1470 m_pattern->addJournalCheckPoint();
1471 // scribble note edit changes
1472 mouseMoveEvent( me );
1473 return;
1474 }
1475 // left button??
1476 else if( me->button() == Qt::LeftButton &&
1477 m_editMode == ModeDraw )
1478 {
1479 // whether this action creates new note(s) or not
1480 bool is_new_note = false;
1481
1482 Note * created_new_note = NULL;
1483 // did it reach end of vector because
1484 // there's no note??
1485 if( it == notes.begin()-1 )
1486 {
1487 is_new_note = true;
1488 m_pattern->addJournalCheckPoint();
1489
1490 // then set new note
1491
1492 // clear selection and select this new note
1493 clearSelectedNotes();
1494
1495 // +32 to quanitize the note correctly when placing notes with
1496 // the mouse. We do this here instead of in note.quantized
1497 // because live notes should still be quantized at the half.
1498 MidiTime note_pos( pos_ticks - ( quantization() / 2 ) );
1499 MidiTime note_len( newNoteLen() );
1500
1501 Note new_note( note_len, note_pos, key_num );
1502 new_note.setSelected( true );
1503 new_note.setPanning( m_lastNotePanning );
1504 new_note.setVolume( m_lastNoteVolume );
1505 created_new_note = m_pattern->addNote( new_note );
1506
1507 const InstrumentFunctionNoteStacking::Chord & chord = InstrumentFunctionNoteStacking::ChordTable::getInstance()
1508 .getChordByName( m_chordModel.currentText() );
1509
1510 if( ! chord.isEmpty() )
1511 {
1512 // if a chord is selected, create following notes in chord
1513 // or arpeggio mode
1514 const bool arpeggio = me->modifiers() & Qt::ShiftModifier;
1515 for( int i = 1; i < chord.size(); i++ )
1516 {
1517 if( arpeggio )
1518 {
1519 note_pos += note_len;
1520 }
1521 Note new_note( note_len, note_pos, key_num + chord[i] );
1522 new_note.setSelected( true );
1523 new_note.setPanning( m_lastNotePanning );
1524 new_note.setVolume( m_lastNoteVolume );
1525 m_pattern->addNote( new_note );
1526 }
1527 }
1528
1529 // reset it so that it can be used for
1530 // ops (move, resize) after this
1531 // code-block
1532 it = notes.begin();
1533 while( it != notes.end() && *it != created_new_note )
1534 {
1535 ++it;
1536 }
1537 }
1538
1539 Note *current_note = *it;
1540 m_currentNote = current_note;
1541 m_lastNotePanning = current_note->getPanning();
1542 m_lastNoteVolume = current_note->getVolume();
1543 m_lenOfNewNotes = current_note->length();
1544
1545 // remember which key and tick we started with
1546 m_mouseDownKey = m_startKey;
1547 m_mouseDownTick = m_currentPosition;
1548
1549 bool first = true;
1550 for( it = notes.begin(); it != notes.end(); ++it )
1551 {
1552 Note *note = *it;
1553
1554 // remember note starting positions
1555 note->setOldKey( note->key() );
1556 note->setOldPos( note->pos() );
1557 note->setOldLength( note->length() );
1558
1559 if( note->selected() )
1560 {
1561
1562 // figure out the bounding box of all the selected notes
1563 if( first )
1564 {
1565 m_moveBoundaryLeft = note->pos().getTicks();
1566 m_moveBoundaryRight = note->endPos();
1567 m_moveBoundaryBottom = note->key();
1568 m_moveBoundaryTop = note->key();
1569
1570 first = false;
1571 }
1572 else
1573 {
1574 m_moveBoundaryLeft = qMin(
1575 note->pos().getTicks(),
1576 (tick_t) m_moveBoundaryLeft );
1577 m_moveBoundaryRight = qMax( (int) note->endPos(),
1578 m_moveBoundaryRight );
1579 m_moveBoundaryBottom = qMin( note->key(),
1580 m_moveBoundaryBottom );
1581 m_moveBoundaryTop = qMax( note->key(),
1582 m_moveBoundaryTop );
1583 }
1584 }
1585 }
1586
1587 // if clicked on an unselected note, remove selection
1588 // and select that new note
1589 if( ! m_currentNote->selected() )
1590 {
1591 clearSelectedNotes();
1592 m_currentNote->setSelected( true );
1593 m_moveBoundaryLeft = m_currentNote->pos().getTicks();
1594 m_moveBoundaryRight = m_currentNote->endPos();
1595 m_moveBoundaryBottom = m_currentNote->key();
1596 m_moveBoundaryTop = m_currentNote->key();
1597 }
1598
1599
1600 // clicked at the "tail" of the note?
1601 if( pos_ticks * m_ppt / MidiTime::ticksPerTact() >
1602 m_currentNote->endPos() * m_ppt / MidiTime::ticksPerTact() - RESIZE_AREA_WIDTH
1603 && m_currentNote->length() > 0 )
1604 {
1605 m_pattern->addJournalCheckPoint();
1606 // then resize the note
1607 m_action = ActionResizeNote;
1608
1609 for (Note *note : getSelectedNotes())
1610 {
1611 if (note->oldLength() <= 0) { note->setOldLength(4); }
1612 }
1613
1614 // set resize-cursor
1615 QCursor c( Qt::SizeHorCursor );
1616 QApplication::setOverrideCursor( c );
1617 }
1618 else
1619 {
1620 if( ! created_new_note )
1621 {
1622 m_pattern->addJournalCheckPoint();
1623 }
1624
1625 // otherwise move it
1626 m_action = ActionMoveNote;
1627
1628 // set move-cursor
1629 QCursor c( Qt::SizeAllCursor );
1630 QApplication::setOverrideCursor( c );
1631
1632 // if they're holding shift, copy all selected notes
1633 if( ! is_new_note && me->modifiers() & Qt::ShiftModifier )
1634 {
1635 // vector to hold new notes until we're through the loop
1636 QVector<Note> newNotes;
1637 for( Note* const& note : notes )
1638 {
1639 if( note->selected() )
1640 {
1641 // copy this note
1642 Note noteCopy( *note );
1643 newNotes.push_back( noteCopy );
1644 }
1645 ++it;
1646 }
1647
1648 if( newNotes.size() != 0 )
1649 {
1650 //put notes from vector into piano roll
1651 for( int i = 0; i < newNotes.size(); ++i)
1652 {
1653 Note * newNote = m_pattern->addNote( newNotes[i], false );
1654 newNote->setSelected( false );
1655 }
1656
1657 // added new notes, so must update engine, song, etc
1658 Engine::getSong()->setModified();
1659 update();
1660 gui->songEditor()->update();
1661 }
1662 }
1663
1664 // play the note
1665 testPlayNote( m_currentNote );
1666 }
1667
1668 Engine::getSong()->setModified();
1669 }
1670 else if( ( me->buttons() == Qt::RightButton &&
1671 m_editMode == ModeDraw ) ||
1672 m_editMode == ModeErase )
1673 {
1674 // erase single note
1675 m_mouseDownRight = true;
1676 if( it != notes.begin()-1 )
1677 {
1678 m_pattern->addJournalCheckPoint();
1679 m_pattern->removeNote( *it );
1680 Engine::getSong()->setModified();
1681 }
1682 }
1683 else if( me->button() == Qt::LeftButton &&
1684 m_editMode == ModeSelect )
1685 {
1686 // select an area of notes
1687
1688 m_selectStartTick = pos_ticks;
1689 m_selectedTick = 0;
1690 m_selectStartKey = key_num;
1691 m_selectedKeys = 1;
1692 m_action = ActionSelectNotes;
1693
1694 // call mousemove to fix glitch where selection
1695 // appears in wrong spot on mousedown
1696 mouseMoveEvent( me );
1697 }
1698
1699 update();
1700 }
1701 else if( me->y() < keyAreaBottom() )
1702 {
1703 // reference to last key needed for both
1704 // right click (used for copy all keys on note)
1705 // and for playing the key when left-clicked
1706 m_lastKey = key_num;
1707
1708 // clicked on keyboard on the left
1709 if( me->buttons() == Qt::RightButton )
1710 {
1711 // right click, tone marker contextual menu
1712 m_pianoKeySelected = getKey( me->y() );
1713 m_semiToneMarkerMenu->popup( mapToGlobal( QPoint( me->x(), me->y() ) ) );
1714 }
1715 else
1716 {
1717 // left click - play the note
1718 int v = ( (float) x ) / ( (float) WHITE_KEY_WIDTH ) * MidiDefaultVelocity;
1719 m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key_num, v );
1720 }
1721 }
1722 else
1723 {
1724 if( me->buttons() == Qt::LeftButton )
1725 {
1726 // clicked in the box below the keys to the left of note edit area
1727 m_noteEditMode = (NoteEditMode)(((int)m_noteEditMode)+1);
1728 if( m_noteEditMode == NoteEditCount )
1729 {
1730 m_noteEditMode = (NoteEditMode) 0;
1731 }
1732 repaint();
1733 }
1734 else if( me->buttons() == Qt::RightButton )
1735 {
1736 // pop menu asking which one they want to edit
1737 m_noteEditMenu->popup( mapToGlobal( QPoint( me->x(), me->y() ) ) );
1738 }
1739 }
1740 }
1741 }
1742
1743
1744
1745
mouseDoubleClickEvent(QMouseEvent * me)1746 void PianoRoll::mouseDoubleClickEvent(QMouseEvent * me )
1747 {
1748 if( ! hasValidPattern() )
1749 {
1750 return;
1751 }
1752
1753 // if they clicked in the note edit area, enter value for the volume bar
1754 if( me->x() > noteEditLeft() && me->x() < noteEditRight()
1755 && me->y() > noteEditTop() && me->y() < noteEditBottom() )
1756 {
1757 // get values for going through notes
1758 int pixel_range = 4;
1759 int x = me->x() - WHITE_KEY_WIDTH;
1760 const int ticks_start = ( x-pixel_range/2 ) *
1761 MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
1762 const int ticks_end = ( x+pixel_range/2 ) *
1763 MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
1764 const int ticks_middle = x * MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
1765
1766 // go through notes to figure out which one we want to change
1767 bool altPressed = me->modifiers() & Qt::AltModifier;
1768 NoteVector nv;
1769 for ( Note * i : m_pattern->notes() )
1770 {
1771 if( i->withinRange( ticks_start, ticks_end ) || ( i->selected() && !altPressed ) )
1772 {
1773 nv += i;
1774 }
1775 }
1776 // make sure we're on a note
1777 if( nv.size() > 0 )
1778 {
1779 const Note * closest = NULL;
1780 int closest_dist = 9999999;
1781 // if we caught multiple notes and we're not editing a
1782 // selection, find the closest...
1783 if( nv.size() > 1 && !isSelection() )
1784 {
1785 for ( const Note * i : nv )
1786 {
1787 const int dist = qAbs( i->pos().getTicks() - ticks_middle );
1788 if( dist < closest_dist ) { closest = i; closest_dist = dist; }
1789 }
1790 // ... then remove all notes from the vector that aren't on the same exact time
1791 NoteVector::Iterator it = nv.begin();
1792 while( it != nv.end() )
1793 {
1794 const Note *note = *it;
1795 if( note->pos().getTicks() != closest->pos().getTicks() )
1796 {
1797 it = nv.erase( it );
1798 }
1799 else
1800 {
1801 it++;
1802 }
1803 }
1804 }
1805 enterValue( &nv );
1806 }
1807 }
1808 else
1809 {
1810 QWidget::mouseDoubleClickEvent(me);
1811 }
1812 }
1813
1814
1815
1816
testPlayNote(Note * n)1817 void PianoRoll::testPlayNote( Note * n )
1818 {
1819 m_lastKey = n->key();
1820
1821 if( ! n->isPlaying() && ! m_recording )
1822 {
1823 n->setIsPlaying( true );
1824
1825 const int baseVelocity = m_pattern->instrumentTrack()->midiPort()->baseVelocity();
1826
1827 m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( n->key(), n->midiVelocity( baseVelocity ) );
1828
1829 MidiEvent event( MidiMetaEvent, -1, n->key(), panningToMidi( n->getPanning() ) );
1830
1831 event.setMetaEvent( MidiNotePanning );
1832
1833 m_pattern->instrumentTrack()->processInEvent( event, 0 );
1834 }
1835 }
1836
1837
1838
1839
pauseTestNotes(bool pause)1840 void PianoRoll::pauseTestNotes( bool pause )
1841 {
1842 for (Note *note : m_pattern->notes())
1843 {
1844 if( note->isPlaying() )
1845 {
1846 if( pause )
1847 {
1848 // stop note
1849 m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( note->key() );
1850 }
1851 else
1852 {
1853 // start note
1854 note->setIsPlaying( false );
1855 testPlayNote( note );
1856 }
1857 }
1858 }
1859 }
1860
1861
1862
1863
testPlayKey(int key,int velocity,int pan)1864 void PianoRoll::testPlayKey( int key, int velocity, int pan )
1865 {
1866 // turn off old key
1867 m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( m_lastKey );
1868
1869 // remember which one we're playing
1870 m_lastKey = key;
1871
1872 // play new key
1873 m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key, velocity );
1874 }
1875
1876
1877
1878
computeSelectedNotes(bool shift)1879 void PianoRoll::computeSelectedNotes(bool shift)
1880 {
1881 if( m_selectStartTick == 0 &&
1882 m_selectedTick == 0 &&
1883 m_selectStartKey == 0 &&
1884 m_selectedKeys == 0 )
1885 {
1886 // don't bother, there's no selection
1887 return;
1888 }
1889
1890 // setup selection-vars
1891 int sel_pos_start = m_selectStartTick;
1892 int sel_pos_end = m_selectStartTick+m_selectedTick;
1893 if( sel_pos_start > sel_pos_end )
1894 {
1895 qSwap<int>( sel_pos_start, sel_pos_end );
1896 }
1897
1898 int sel_key_start = m_selectStartKey - m_startKey + 1;
1899 int sel_key_end = sel_key_start + m_selectedKeys;
1900 if( sel_key_start > sel_key_end )
1901 {
1902 qSwap<int>( sel_key_start, sel_key_end );
1903 }
1904
1905 //int y_base = noteEditTop() - 1;
1906 if( hasValidPattern() )
1907 {
1908 for( Note *note : m_pattern->notes() )
1909 {
1910 // make a new selection unless they're holding shift
1911 if( ! shift )
1912 {
1913 note->setSelected( false );
1914 }
1915
1916 int len_ticks = note->length();
1917
1918 if( len_ticks == 0 )
1919 {
1920 continue;
1921 }
1922 else if( len_ticks < 0 )
1923 {
1924 len_ticks = 4;
1925 }
1926
1927 const int key = note->key() - m_startKey + 1;
1928
1929 int pos_ticks = note->pos();
1930
1931 // if the selection even barely overlaps the note
1932 if( key > sel_key_start &&
1933 key <= sel_key_end &&
1934 pos_ticks + len_ticks > sel_pos_start &&
1935 pos_ticks < sel_pos_end )
1936 {
1937 // remove from selection when holding shift
1938 bool selected = shift && note->selected();
1939 note->setSelected( ! selected);
1940 }
1941 }
1942 }
1943
1944 removeSelection();
1945 update();
1946 }
1947
1948
1949
1950
mouseReleaseEvent(QMouseEvent * me)1951 void PianoRoll::mouseReleaseEvent( QMouseEvent * me )
1952 {
1953 bool mustRepaint = false;
1954
1955 s_textFloat->hide();
1956
1957 if( me->button() & Qt::LeftButton )
1958 {
1959 mustRepaint = true;
1960
1961 if( m_action == ActionSelectNotes && m_editMode == ModeSelect )
1962 {
1963 // select the notes within the selection rectangle and
1964 // then destroy the selection rectangle
1965 computeSelectedNotes(
1966 me->modifiers() & Qt::ShiftModifier );
1967 }
1968 else if( m_action == ActionMoveNote )
1969 {
1970 // we moved one or more notes so they have to be
1971 // moved properly according to new starting-
1972 // time in the note-array of pattern
1973 m_pattern->rearrangeAllNotes();
1974
1975 }
1976
1977 if( m_action == ActionMoveNote || m_action == ActionResizeNote )
1978 {
1979 // if we only moved one note, deselect it so we can
1980 // edit the notes in the note edit area
1981 if( selectionCount() == 1 )
1982 {
1983 clearSelectedNotes();
1984 }
1985 }
1986 }
1987
1988 if( me->button() & Qt::RightButton )
1989 {
1990 m_mouseDownRight = false;
1991 mustRepaint = true;
1992 }
1993
1994 if( hasValidPattern() )
1995 {
1996 // turn off all notes that are playing
1997 for ( Note *note : m_pattern->notes() )
1998 {
1999 if( note->isPlaying() )
2000 {
2001 m_pattern->instrumentTrack()->pianoModel()->
2002 handleKeyRelease( note->key() );
2003 note->setIsPlaying( false );
2004 }
2005 }
2006
2007 // stop playing keys that we let go of
2008 m_pattern->instrumentTrack()->pianoModel()->
2009 handleKeyRelease( m_lastKey );
2010 }
2011
2012 m_currentNote = NULL;
2013 m_action = ActionNone;
2014
2015 if( m_editMode == ModeDraw )
2016 {
2017 QApplication::restoreOverrideCursor();
2018 }
2019
2020 if( mustRepaint )
2021 {
2022 repaint();
2023 }
2024 }
2025
2026
2027
2028
mouseMoveEvent(QMouseEvent * me)2029 void PianoRoll::mouseMoveEvent( QMouseEvent * me )
2030 {
2031 if( ! hasValidPattern() )
2032 {
2033 update();
2034 return;
2035 }
2036
2037 if( m_action == ActionNone && me->buttons() == 0 )
2038 {
2039 if( me->y() > keyAreaBottom() && me->y() < noteEditTop() )
2040 {
2041 QApplication::setOverrideCursor(
2042 QCursor( Qt::SizeVerCursor ) );
2043 return;
2044 }
2045 }
2046 else if( m_action == ActionResizeNoteEditArea )
2047 {
2048 // change m_notesEditHeight and then repaint
2049 m_notesEditHeight = tLimit<int>(
2050 m_oldNotesEditHeight - ( me->y() - m_moveStartY ),
2051 NOTE_EDIT_MIN_HEIGHT,
2052 height() - PR_TOP_MARGIN - NOTE_EDIT_RESIZE_BAR -
2053 PR_BOTTOM_MARGIN - KEY_AREA_MIN_HEIGHT );
2054 repaint();
2055 return;
2056 }
2057
2058 if( me->y() > PR_TOP_MARGIN || m_action != ActionNone )
2059 {
2060 bool edit_note = ( me->y() > noteEditTop() )
2061 && m_action != ActionSelectNotes;
2062
2063
2064 int key_num = getKey( me->y() );
2065 int x = me->x();
2066
2067 // see if they clicked on the keyboard on the left
2068 if( x < WHITE_KEY_WIDTH && m_action == ActionNone
2069 && ! edit_note && key_num != m_lastKey
2070 && me->buttons() & Qt::LeftButton )
2071 {
2072 // clicked on a key, play the note
2073 testPlayKey( key_num, ( (float) x ) / ( (float) WHITE_KEY_WIDTH ) * MidiDefaultVelocity, 0 );
2074 update();
2075 return;
2076 }
2077
2078 x -= WHITE_KEY_WIDTH;
2079
2080 if( me->buttons() & Qt::LeftButton
2081 && m_editMode == ModeDraw
2082 && (m_action == ActionMoveNote || m_action == ActionResizeNote ) )
2083 {
2084 // handle moving notes and resizing them
2085 bool replay_note = key_num != m_lastKey
2086 && m_action == ActionMoveNote;
2087
2088 if( replay_note || ( m_action == ActionMoveNote && ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) )
2089 {
2090 pauseTestNotes();
2091 }
2092
2093 dragNotes( me->x(), me->y(),
2094 me->modifiers() & Qt::AltModifier,
2095 me->modifiers() & Qt::ShiftModifier,
2096 me->modifiers() & Qt::ControlModifier );
2097
2098 if( replay_note && m_action == ActionMoveNote && ! ( ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) )
2099 {
2100 pauseTestNotes( false );
2101 }
2102 }
2103 else if( m_editMode != ModeErase &&
2104 ( edit_note || m_action == ActionChangeNoteProperty ) &&
2105 ( me->buttons() & Qt::LeftButton || me->buttons() & Qt::MiddleButton
2106 || ( me->buttons() & Qt::RightButton && me->modifiers() & Qt::ShiftModifier ) ) )
2107 {
2108 // editing note properties
2109
2110 // Change notes within a certain pixel range of where
2111 // the mouse cursor is
2112 int pixel_range = 14;
2113
2114 // convert to ticks so that we can check which notes
2115 // are in the range
2116 int ticks_start = ( x-pixel_range/2 ) *
2117 MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
2118 int ticks_end = ( x+pixel_range/2 ) *
2119 MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
2120
2121 // get note-vector of current pattern
2122 const NoteVector & notes = m_pattern->notes();
2123
2124 // determine what volume/panning to set note to
2125 // if middle-click, set to defaults
2126 volume_t vol = DefaultVolume;
2127 panning_t pan = DefaultPanning;
2128
2129 if( me->buttons() & Qt::LeftButton )
2130 {
2131 vol = tLimit<int>( MinVolume +
2132 ( ( (float)noteEditBottom() ) - ( (float)me->y() ) ) /
2133 ( (float)( noteEditBottom() - noteEditTop() ) ) *
2134 ( MaxVolume - MinVolume ),
2135 MinVolume, MaxVolume );
2136 pan = tLimit<int>( PanningLeft +
2137 ( (float)( noteEditBottom() - me->y() ) ) /
2138 ( (float)( noteEditBottom() - noteEditTop() ) ) *
2139 ( (float)( PanningRight - PanningLeft ) ),
2140 PanningLeft, PanningRight);
2141 }
2142
2143 if( m_noteEditMode == NoteEditVolume )
2144 {
2145 m_lastNoteVolume = vol;
2146 showVolTextFloat( vol, me->pos() );
2147 }
2148 else if( m_noteEditMode == NoteEditPanning )
2149 {
2150 m_lastNotePanning = pan;
2151 showPanTextFloat( pan, me->pos() );
2152 }
2153
2154 // When alt is pressed we only edit the note under the cursor
2155 bool altPressed = me->modifiers() & Qt::AltModifier;
2156 // We iterate from last note in pattern to the first,
2157 // chronologically
2158 NoteVector::ConstIterator it = notes.begin()+notes.size()-1;
2159 for( int i = 0; i < notes.size(); ++i )
2160 {
2161 Note* n = *it;
2162
2163 bool isUnderPosition = n->withinRange( ticks_start, ticks_end );
2164 // Play note under the cursor
2165 if ( isUnderPosition ) { testPlayNote( n ); }
2166 // If note is:
2167 // Under the cursor, when there is no selection
2168 // Selected, and alt is not pressed
2169 // Under the cursor, selected, and alt is pressed
2170 if ( ( isUnderPosition && !isSelection() ) ||
2171 ( n->selected() && !altPressed ) ||
2172 ( isUnderPosition && n->selected() && altPressed )
2173 )
2174 {
2175 if( m_noteEditMode == NoteEditVolume )
2176 {
2177 n->setVolume( vol );
2178
2179 const int baseVelocity = m_pattern->instrumentTrack()->midiPort()->baseVelocity();
2180
2181 m_pattern->instrumentTrack()->processInEvent( MidiEvent( MidiKeyPressure, -1, n->key(), n->midiVelocity( baseVelocity ) ) );
2182 }
2183 else if( m_noteEditMode == NoteEditPanning )
2184 {
2185 n->setPanning( pan );
2186 MidiEvent evt( MidiMetaEvent, -1, n->key(), panningToMidi( pan ) );
2187 evt.setMetaEvent( MidiNotePanning );
2188 m_pattern->instrumentTrack()->processInEvent( evt );
2189 }
2190 }
2191 else if( n->isPlaying() && !isSelection() )
2192 {
2193 // mouse not over this note, stop playing it.
2194 m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( n->key() );
2195
2196 n->setIsPlaying( false );
2197 }
2198
2199
2200 --it;
2201 }
2202
2203 // Emit pattern has changed
2204 m_pattern->dataChanged();
2205 }
2206
2207 else if( me->buttons() == Qt::NoButton && m_editMode == ModeDraw )
2208 {
2209 // set move- or resize-cursor
2210
2211 // get tick in which the cursor is posated
2212 int pos_ticks = ( x * MidiTime::ticksPerTact() ) /
2213 m_ppt + m_currentPosition;
2214
2215 // get note-vector of current pattern
2216 const NoteVector & notes = m_pattern->notes();
2217
2218 // will be our iterator in the following loop
2219 NoteVector::ConstIterator it = notes.begin()+notes.size()-1;
2220
2221 // loop through whole note-vector...
2222 for( int i = 0; i < notes.size(); ++i )
2223 {
2224 Note *note = *it;
2225 // and check whether the cursor is over an
2226 // existing note
2227 if( pos_ticks >= note->pos() &&
2228 pos_ticks <= note->pos() +
2229 note->length() &&
2230 note->key() == key_num &&
2231 note->length() > 0 )
2232 {
2233 break;
2234 }
2235 --it;
2236 }
2237
2238 // did it reach end of vector because there's
2239 // no note??
2240 if( it != notes.begin()-1 )
2241 {
2242 Note *note = *it;
2243 // x coordinate of the right edge of the note
2244 int noteRightX = ( note->pos() + note->length() -
2245 m_currentPosition) * m_ppt/MidiTime::ticksPerTact();
2246 // cursor at the "tail" of the note?
2247 bool atTail = note->length() > 0 && x > noteRightX -
2248 RESIZE_AREA_WIDTH;
2249 Qt::CursorShape cursorShape = atTail ? Qt::SizeHorCursor :
2250 Qt::SizeAllCursor;
2251 if( QApplication::overrideCursor() )
2252 {
2253 if( QApplication::overrideCursor()->shape() != cursorShape )
2254 {
2255 while( QApplication::overrideCursor() != NULL )
2256 {
2257 QApplication::restoreOverrideCursor();
2258 }
2259 QApplication::setOverrideCursor(QCursor(cursorShape));
2260 }
2261 }
2262 else
2263 {
2264 QApplication::setOverrideCursor(QCursor(cursorShape));
2265 }
2266 }
2267 else
2268 {
2269 // the cursor is over no note, so restore cursor
2270 while( QApplication::overrideCursor() != NULL )
2271 {
2272 QApplication::restoreOverrideCursor();
2273 }
2274 }
2275 }
2276 else if( me->buttons() & Qt::LeftButton &&
2277 m_editMode == ModeSelect &&
2278 m_action == ActionSelectNotes )
2279 {
2280
2281 // change size of selection
2282
2283 // get tick in which the cursor is posated
2284 int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
2285 m_currentPosition;
2286
2287 m_selectedTick = pos_ticks - m_selectStartTick;
2288 if( (int) m_selectStartTick + m_selectedTick < 0 )
2289 {
2290 m_selectedTick = -static_cast<int>(
2291 m_selectStartTick );
2292 }
2293 m_selectedKeys = key_num - m_selectStartKey;
2294 if( key_num <= m_selectStartKey )
2295 {
2296 --m_selectedKeys;
2297 }
2298 }
2299 else if( ( m_editMode == ModeDraw && me->buttons() & Qt::RightButton )
2300 || ( m_editMode == ModeErase && me->buttons() ) )
2301 {
2302 // holding down right-click to delete notes or holding down
2303 // any key if in erase mode
2304
2305 // get tick in which the user clicked
2306 int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
2307 m_currentPosition;
2308
2309
2310 // get note-vector of current pattern
2311 const NoteVector & notes = m_pattern->notes();
2312
2313 // will be our iterator in the following loop
2314 NoteVector::ConstIterator it = notes.begin();
2315
2316 // loop through whole note-vector...
2317 while( it != notes.end() )
2318 {
2319 Note *note = *it;
2320 MidiTime len = note->length();
2321 if( len < 0 )
2322 {
2323 len = 4;
2324 }
2325 // and check whether the user clicked on an
2326 // existing note or an edit-line
2327 if( pos_ticks >= note->pos() &&
2328 len > 0 &&
2329 (
2330 ( ! edit_note &&
2331 pos_ticks <= note->pos() + len &&
2332 note->key() == key_num )
2333 ||
2334 ( edit_note &&
2335 pos_ticks <= note->pos() +
2336 NOTE_EDIT_LINE_WIDTH *
2337 MidiTime::ticksPerTact() /
2338 m_ppt )
2339 )
2340 )
2341 {
2342 // delete this note
2343 m_pattern->removeNote( note );
2344 Engine::getSong()->setModified();
2345 }
2346 else
2347 {
2348 ++it;
2349 }
2350 }
2351 }
2352 }
2353 else
2354 {
2355 if( me->buttons() & Qt::LeftButton &&
2356 m_editMode == ModeSelect &&
2357 m_action == ActionSelectNotes )
2358 {
2359
2360 int x = me->x() - WHITE_KEY_WIDTH;
2361 if( x < 0 && m_currentPosition > 0 )
2362 {
2363 x = 0;
2364 QCursor::setPos( mapToGlobal( QPoint(
2365 WHITE_KEY_WIDTH,
2366 me->y() ) ) );
2367 if( m_currentPosition >= 4 )
2368 {
2369 m_leftRightScroll->setValue(
2370 m_currentPosition - 4 );
2371 }
2372 else
2373 {
2374 m_leftRightScroll->setValue( 0 );
2375 }
2376 }
2377 else if( x > width() - WHITE_KEY_WIDTH )
2378 {
2379 x = width() - WHITE_KEY_WIDTH;
2380 QCursor::setPos( mapToGlobal( QPoint( width(),
2381 me->y() ) ) );
2382 m_leftRightScroll->setValue( m_currentPosition +
2383 4 );
2384 }
2385
2386 // get tick in which the cursor is posated
2387 int pos_ticks = x * MidiTime::ticksPerTact()/ m_ppt +
2388 m_currentPosition;
2389
2390 m_selectedTick = pos_ticks -
2391 m_selectStartTick;
2392 if( (int) m_selectStartTick + m_selectedTick <
2393 0 )
2394 {
2395 m_selectedTick = -static_cast<int>(
2396 m_selectStartTick );
2397 }
2398
2399
2400 int key_num = getKey( me->y() );
2401 int visible_keys = ( height() - PR_TOP_MARGIN -
2402 PR_BOTTOM_MARGIN -
2403 m_notesEditHeight ) /
2404 KEY_LINE_HEIGHT + 2;
2405 const int s_key = m_startKey - 1;
2406
2407 if( key_num <= s_key )
2408 {
2409 QCursor::setPos( mapToGlobal( QPoint( me->x(),
2410 keyAreaBottom() ) ) );
2411 m_topBottomScroll->setValue(
2412 m_topBottomScroll->value() + 1 );
2413 key_num = s_key;
2414 }
2415 else if( key_num >= s_key + visible_keys )
2416 {
2417 QCursor::setPos( mapToGlobal( QPoint( me->x(),
2418 PR_TOP_MARGIN ) ) );
2419 m_topBottomScroll->setValue(
2420 m_topBottomScroll->value() - 1 );
2421 key_num = s_key + visible_keys;
2422 }
2423
2424 m_selectedKeys = key_num - m_selectStartKey;
2425 if( key_num <= m_selectStartKey )
2426 {
2427 --m_selectedKeys;
2428 }
2429 }
2430 QApplication::restoreOverrideCursor();
2431 }
2432
2433 m_lastMouseX = me->x();
2434 m_lastMouseY = me->y();
2435
2436 update();
2437 }
2438
2439
2440
2441
dragNotes(int x,int y,bool alt,bool shift,bool ctrl)2442 void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
2443 {
2444 // dragging one or more notes around
2445
2446 // convert pixels to ticks and keys
2447 int off_x = x - m_moveStartX;
2448 int off_ticks = off_x * MidiTime::ticksPerTact() / m_ppt;
2449 int off_key = getKey( y ) - getKey( m_moveStartY );
2450
2451 // handle scroll changes while dragging
2452 off_ticks -= m_mouseDownTick - m_currentPosition;
2453 off_key -= m_mouseDownKey - m_startKey;
2454
2455
2456 // if they're not holding alt, quantize the offset
2457 if( ! alt )
2458 {
2459 off_ticks = floor( off_ticks / quantization() )
2460 * quantization();
2461 }
2462
2463 // make sure notes won't go outside boundary conditions
2464 if( m_action == ActionMoveNote && ! ( shift && ! m_startedWithShift ) )
2465 {
2466 if( m_moveBoundaryLeft + off_ticks < 0 )
2467 {
2468 off_ticks -= (off_ticks + m_moveBoundaryLeft);
2469 }
2470 if( m_moveBoundaryTop + off_key > NumKeys )
2471 {
2472 off_key -= NumKeys - (m_moveBoundaryTop + off_key);
2473 }
2474 if( m_moveBoundaryBottom + off_key < 0 )
2475 {
2476 off_key -= (m_moveBoundaryBottom + off_key);
2477 }
2478 }
2479
2480
2481 // get note-vector of current pattern
2482 const NoteVector & notes = m_pattern->notes();
2483
2484 if (m_action == ActionMoveNote)
2485 {
2486 for (Note *note : notes)
2487 {
2488 if( note->selected() )
2489 {
2490 if( shift && ! m_startedWithShift )
2491 {
2492 // quick resize, toggled by holding shift after starting a note move, but not before
2493 int ticks_new = note->oldLength().getTicks() + off_ticks;
2494 if( ticks_new <= 0 )
2495 {
2496 ticks_new = 1;
2497 }
2498 note->setLength( MidiTime( ticks_new ) );
2499 m_lenOfNewNotes = note->length();
2500 }
2501 else
2502 {
2503 // moving note
2504 int pos_ticks = note->oldPos().getTicks() + off_ticks;
2505 int key_num = note->oldKey() + off_key;
2506
2507 // ticks can't be negative
2508 pos_ticks = qMax(0, pos_ticks);
2509 // upper/lower bound checks on key_num
2510 key_num = qMax(0, key_num);
2511 key_num = qMin(key_num, NumKeys);
2512
2513 note->setPos( MidiTime( pos_ticks ) );
2514 note->setKey( key_num );
2515 }
2516 }
2517 }
2518 }
2519 else if (m_action == ActionResizeNote)
2520 {
2521 // When resizing notes:
2522 // If shift is not pressed, resize the selected notes but do not rearrange them
2523 // If shift is pressed we resize and rearrange only the selected notes
2524 // If shift + ctrl then we also rearrange all posterior notes (sticky)
2525 // If shift is pressed but only one note is selected, apply sticky
2526
2527 if (shift)
2528 {
2529 // Algorithm:
2530 // Relative to the starting point of the left-most selected note,
2531 // all selected note start-points and *endpoints* (not length) should be scaled by a calculated factor.
2532 // This factor is such that the endpoint of the note whose handle is being dragged should lie under the cursor.
2533 // first, determine the start-point of the left-most selected note:
2534 int stretchStartTick = -1;
2535 for (const Note *note : notes)
2536 {
2537 if (note->selected() && (stretchStartTick < 0 || note->oldPos().getTicks() < stretchStartTick))
2538 {
2539 stretchStartTick = note->oldPos().getTicks();
2540 }
2541 }
2542 // determine the ending tick of the right-most selected note
2543 const Note *posteriorNote = nullptr;
2544 for (const Note *note : notes)
2545 {
2546 if (note->selected() && (posteriorNote == nullptr ||
2547 note->oldPos().getTicks() + note->oldLength().getTicks() >
2548 posteriorNote->oldPos().getTicks() + posteriorNote->oldLength().getTicks()))
2549 {
2550 posteriorNote = note;
2551 }
2552 }
2553 int posteriorEndTick = posteriorNote->pos().getTicks() + posteriorNote->length().getTicks();
2554 // end-point of the note whose handle is being dragged:
2555 int stretchEndTick = m_currentNote->oldPos().getTicks() + m_currentNote->oldLength().getTicks();
2556 // Calculate factor by which to scale the start-point and end-point of all selected notes
2557 float scaleFactor = (float)(stretchEndTick - stretchStartTick + off_ticks) / qMax(1, stretchEndTick - stretchStartTick);
2558 scaleFactor = qMax(0.0f, scaleFactor);
2559
2560 // process all selected notes & determine how much the endpoint of the right-most note was shifted
2561 int posteriorDeltaThisFrame = 0;
2562 for (Note *note : notes)
2563 {
2564 if(note->selected())
2565 {
2566 // scale relative start and end positions by scaleFactor
2567 int newStart = stretchStartTick + scaleFactor *
2568 (note->oldPos().getTicks() - stretchStartTick);
2569 int newEnd = stretchStartTick + scaleFactor *
2570 (note->oldPos().getTicks()+note->oldLength().getTicks() - stretchStartTick);
2571 // if not holding alt, quantize the offsets
2572 if(!alt)
2573 {
2574 // quantize start time
2575 int oldStart = note->oldPos().getTicks();
2576 int startDiff = newStart - oldStart;
2577 startDiff = floor(startDiff / quantization()) * quantization();
2578 newStart = oldStart + startDiff;
2579 // quantize end time
2580 int oldEnd = oldStart + note->oldLength().getTicks();
2581 int endDiff = newEnd - oldEnd;
2582 endDiff = floor(endDiff / quantization()) * quantization();
2583 newEnd = oldEnd + endDiff;
2584 }
2585 int newLength = qMax(1, newEnd-newStart);
2586 if (note == posteriorNote)
2587 {
2588 posteriorDeltaThisFrame = (newStart+newLength) -
2589 (note->pos().getTicks() + note->length().getTicks());
2590 }
2591 note->setLength( MidiTime(newLength) );
2592 note->setPos( MidiTime(newStart) );
2593
2594 m_lenOfNewNotes = note->length();
2595 }
2596 }
2597 if (ctrl || selectionCount() == 1)
2598 {
2599 // if holding ctrl or only one note is selected, reposition posterior notes
2600 for (Note *note : notes)
2601 {
2602 if (!note->selected() && note->pos().getTicks() >= posteriorEndTick)
2603 {
2604 int newStart = note->pos().getTicks() + posteriorDeltaThisFrame;
2605 note->setPos( MidiTime(newStart) );
2606 }
2607 }
2608 }
2609 }
2610 else
2611 {
2612 // shift is not pressed; stretch length of selected notes but not their position
2613 for (Note *note : notes)
2614 {
2615 if (note->selected())
2616 {
2617 int newLength = note->oldLength() + off_ticks;
2618 newLength = qMax(1, newLength);
2619 note->setLength( MidiTime(newLength) );
2620
2621 m_lenOfNewNotes = note->length();
2622 }
2623 }
2624 }
2625 }
2626
2627 m_pattern->updateLength();
2628 m_pattern->dataChanged();
2629 Engine::getSong()->setModified();
2630 }
2631
xCoordOfTick(int tick)2632 int PianoRoll::xCoordOfTick(int tick )
2633 {
2634 return WHITE_KEY_WIDTH + ( ( tick - m_currentPosition )
2635 * m_ppt / MidiTime::ticksPerTact() );
2636 }
2637
paintEvent(QPaintEvent * pe)2638 void PianoRoll::paintEvent(QPaintEvent * pe )
2639 {
2640 bool drawNoteNames = ConfigManager::inst()->value( "ui", "printnotelabels").toInt();
2641
2642 QStyleOption opt;
2643 opt.initFrom( this );
2644 QPainter p( this );
2645 style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this );
2646
2647 QBrush bgColor = p.background();
2648
2649 // fill with bg color
2650 p.fillRect( 0, 0, width(), height(), bgColor );
2651
2652 // set font-size to 8
2653 p.setFont( pointSize<8>( p.font() ) );
2654
2655 // y_offset is used to align the piano-keys on the key-lines
2656 int y_offset = 0;
2657
2658 // calculate y_offset according to first key
2659 switch( prKeyOrder[m_startKey % KeysPerOctave] )
2660 {
2661 case PR_BLACK_KEY: y_offset = KEY_LINE_HEIGHT / 4; break;
2662 case PR_WHITE_KEY_BIG: y_offset = KEY_LINE_HEIGHT / 2; break;
2663 case PR_WHITE_KEY_SMALL:
2664 if( prKeyOrder[( ( m_startKey + 1 ) %
2665 KeysPerOctave)] != PR_BLACK_KEY )
2666 {
2667 y_offset = KEY_LINE_HEIGHT / 2;
2668 }
2669 break;
2670 }
2671 // start drawing at the bottom
2672 int key_line_y = qMin(keyAreaBottom() - 1, KEY_LINE_HEIGHT * NumKeys);
2673 // we need to set m_notesEditHeight here because it needs to fill in the
2674 // rest of the window if key_line_y is bound to KEY_LINE_HEIGHT * NumKeys
2675 if (key_line_y == KEY_LINE_HEIGHT * NumKeys) {
2676 m_notesEditHeight = height() - (PR_TOP_MARGIN + KEY_LINE_HEIGHT * NumKeys);
2677 }
2678 // used for aligning black-keys later
2679 int first_white_key_height = WHITE_KEY_SMALL_HEIGHT;
2680 // key-counter - only needed for finding out whether the processed
2681 // key is the first one
2682 int keys_processed = 0;
2683
2684 int key = m_startKey;
2685
2686 // draw all white keys...
2687 for( int y = key_line_y + 1 + y_offset; y > PR_TOP_MARGIN;
2688 key_line_y -= KEY_LINE_HEIGHT, ++keys_processed )
2689 {
2690 // check for white key that is only half visible on the
2691 // bottom of piano-roll
2692 if( keys_processed == 0 &&
2693 prKeyOrder[m_startKey % KeysPerOctave] ==
2694 PR_BLACK_KEY )
2695 {
2696 // draw it!
2697 p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT,
2698 *s_whiteKeySmallPm );
2699 // update y-pos
2700 y -= WHITE_KEY_SMALL_HEIGHT / 2;
2701 // move first black key down (we didn't draw whole
2702 // white key so black key needs to be lifted down)
2703 // (default for first_white_key_height =
2704 // WHITE_KEY_SMALL_HEIGHT, so WHITE_KEY_SMALL_HEIGHT/2
2705 // is smaller)
2706 first_white_key_height = WHITE_KEY_SMALL_HEIGHT / 2;
2707 }
2708 // check whether to draw a big or a small white key
2709 if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_SMALL )
2710 {
2711 // draw a small one while checking if it is pressed or not
2712 if( hasValidPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) )
2713 {
2714 p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT, *s_whiteKeySmallPressedPm );
2715 }
2716 else
2717 {
2718 p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT, *s_whiteKeySmallPm );
2719 }
2720 // update y-pos
2721 y -= WHITE_KEY_SMALL_HEIGHT;
2722
2723 }
2724 else if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_BIG )
2725 {
2726 // draw a big one while checking if it is pressed or not
2727 if( hasValidPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) )
2728 {
2729 p.drawPixmap( PIANO_X, y - WHITE_KEY_BIG_HEIGHT, *s_whiteKeyBigPressedPm );
2730 }
2731 else
2732 {
2733 p.drawPixmap( PIANO_X, y-WHITE_KEY_BIG_HEIGHT, *s_whiteKeyBigPm );
2734 }
2735 // if a big white key has been the first key,
2736 // black keys needs to be lifted up
2737 if( keys_processed == 0 )
2738 {
2739 first_white_key_height = WHITE_KEY_BIG_HEIGHT;
2740 }
2741 // update y-pos
2742 y -= WHITE_KEY_BIG_HEIGHT;
2743 }
2744
2745 // Compute the corrections for the note names
2746 int yCorrectionForNoteLabels = 0;
2747
2748 int keyCode = key % KeysPerOctave;
2749 switch( keyCode )
2750 {
2751 case 0:
2752 case 5:
2753 yCorrectionForNoteLabels = -4;
2754 break;
2755 case 2:
2756 case 7:
2757 case 9:
2758 yCorrectionForNoteLabels = -2;
2759 break;
2760 case 4:
2761 case 11:
2762 yCorrectionForNoteLabels = 2;
2763 break;
2764 }
2765
2766 if( Piano::isWhiteKey( key ) )
2767 {
2768 // Draw note names if activated in the preferences, C notes are always drawn
2769 if ( key % 12 == 0 || drawNoteNames )
2770 {
2771 QString noteString = getNoteString( key );
2772
2773 QPoint textStart( WHITE_KEY_WIDTH - 18, key_line_y );
2774 textStart += QPoint( 0, yCorrectionForNoteLabels );
2775
2776 p.setPen( textShadow() );
2777 p.drawText( textStart + QPoint( 1, 1 ), noteString );
2778 // The C key is painted darker than the other ones
2779 if ( key % 12 == 0 )
2780 {
2781 p.setPen( textColor() );
2782 }
2783 else
2784 {
2785 p.setPen( textColorLight() );
2786 }
2787 p.drawText( textStart, noteString );
2788 }
2789 }
2790 ++key;
2791 }
2792
2793 // reset all values, because now we're going to draw all black keys
2794 key = m_startKey;
2795 keys_processed = 0;
2796 int white_cnt = 0;
2797 key_line_y = qMin(keyAreaBottom(), KEY_LINE_HEIGHT * NumKeys);
2798
2799 // and go!
2800 for( int y = key_line_y + y_offset;
2801 y > PR_TOP_MARGIN; ++keys_processed )
2802 {
2803 // check for black key that is only half visible on the bottom
2804 // of piano-roll
2805 if( keys_processed == 0
2806 // current key may not be a black one
2807 && prKeyOrder[key % KeysPerOctave] != PR_BLACK_KEY
2808 // but the previous one must be black (we must check this
2809 // because there might be two white keys (E-F)
2810 && prKeyOrder[( key - 1 ) % KeysPerOctave] ==
2811 PR_BLACK_KEY )
2812 {
2813 // draw the black key!
2814 p.drawPixmap( PIANO_X, y - BLACK_KEY_HEIGHT / 2,
2815 *s_blackKeyPm );
2816 // is the one after the start-note a black key??
2817 if( prKeyOrder[( key + 1 ) % KeysPerOctave] !=
2818 PR_BLACK_KEY )
2819 {
2820 // no, then move it up!
2821 y -= KEY_LINE_HEIGHT / 2;
2822 }
2823 }
2824 // current key black?
2825 if( prKeyOrder[key % KeysPerOctave] == PR_BLACK_KEY)
2826 {
2827 // then draw it (calculation of y very complicated,
2828 // but that's the only working solution, sorry...)
2829 // check if the key is pressed or not
2830 if( hasValidPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) )
2831 {
2832 p.drawPixmap( PIANO_X, y - ( first_white_key_height -
2833 WHITE_KEY_SMALL_HEIGHT ) -
2834 WHITE_KEY_SMALL_HEIGHT/2 - 1 -
2835 BLACK_KEY_HEIGHT, *s_blackKeyPressedPm );
2836 }
2837 else
2838 {
2839 p.drawPixmap( PIANO_X, y - ( first_white_key_height -
2840 WHITE_KEY_SMALL_HEIGHT ) -
2841 WHITE_KEY_SMALL_HEIGHT/2 - 1 -
2842 BLACK_KEY_HEIGHT, *s_blackKeyPm );
2843 }
2844 // update y-pos
2845 y -= WHITE_KEY_BIG_HEIGHT;
2846 // reset white-counter
2847 white_cnt = 0;
2848 }
2849 else
2850 {
2851 // simple workaround for increasing x if there were
2852 // two white keys (e.g. between E and F)
2853 ++white_cnt;
2854 if( white_cnt > 1 )
2855 {
2856 y -= WHITE_KEY_BIG_HEIGHT/2;
2857 }
2858 }
2859
2860 ++key;
2861 }
2862
2863
2864 // erase the area below the piano, because there might be keys that
2865 // should be only half-visible
2866 p.fillRect( QRect( 0, key_line_y,
2867 WHITE_KEY_WIDTH, noteEditBottom() - key_line_y ), bgColor );
2868
2869 // display note editing info
2870 QFont f = p.font();
2871 f.setBold( false );
2872 p.setFont( pointSize<10>( f ) );
2873 p.setPen( noteModeColor() );
2874 p.drawText( QRect( 0, key_line_y,
2875 WHITE_KEY_WIDTH, noteEditBottom() - key_line_y ),
2876 Qt::AlignCenter | Qt::TextWordWrap,
2877 m_nemStr.at( m_noteEditMode ) + ":" );
2878
2879 // set clipping area, because we are not allowed to paint over
2880 // keyboard...
2881 p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN,
2882 width() - WHITE_KEY_WIDTH,
2883 height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN );
2884
2885 // draw the grid
2886 if( hasValidPattern() )
2887 {
2888 int q, x, tick;
2889
2890 if( m_zoomingModel.value() > 3 )
2891 {
2892 // If we're over 100% zoom, we allow all quantization level grids
2893 q = quantization();
2894 }
2895 else if( quantization() % 3 != 0 )
2896 {
2897 // If we're under 100% zoom, we allow quantization grid up to 1/24 for triplets
2898 // to ensure a dense doesn't fill out the background
2899 q = quantization() < 8 ? 8 : quantization();
2900 }
2901 else {
2902 // If we're under 100% zoom, we allow quantization grid up to 1/32 for normal notes
2903 q = quantization() < 6 ? 6 : quantization();
2904 }
2905
2906 // First we draw the vertical quantization lines
2907 for( tick = m_currentPosition - m_currentPosition % q, x = xCoordOfTick( tick );
2908 x <= width(); tick += q, x = xCoordOfTick( tick ) )
2909 {
2910 p.setPen( lineColor() );
2911 p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN );
2912 }
2913
2914 // Draw horizontal lines
2915 key = m_startKey;
2916 for( int y = key_line_y - 1; y > PR_TOP_MARGIN;
2917 y -= KEY_LINE_HEIGHT )
2918 {
2919 if( static_cast<Keys>( key % KeysPerOctave ) == Key_C )
2920 {
2921 // C note gets accented
2922 p.setPen( beatLineColor() );
2923 }
2924 else
2925 {
2926 p.setPen( lineColor() );
2927 }
2928 p.drawLine( WHITE_KEY_WIDTH, y, width(), y );
2929 ++key;
2930 }
2931
2932
2933 // Draw alternating shades on bars
2934 float timeSignature = static_cast<float>( Engine::getSong()->getTimeSigModel().getNumerator() )
2935 / static_cast<float>( Engine::getSong()->getTimeSigModel().getDenominator() );
2936 float zoomFactor = m_zoomLevels[m_zoomingModel.value()];
2937 //the bars which disappears at the left side by scrolling
2938 int leftBars = m_currentPosition * zoomFactor / MidiTime::ticksPerTact();
2939
2940 //iterates the visible bars and draw the shading on uneven bars
2941 for( int x = WHITE_KEY_WIDTH, barCount = leftBars; x < width() + m_currentPosition * zoomFactor / timeSignature; x += m_ppt, ++barCount )
2942 {
2943 if( ( barCount + leftBars ) % 2 != 0 )
2944 {
2945 p.fillRect( x - m_currentPosition * zoomFactor / timeSignature, PR_TOP_MARGIN, m_ppt,
2946 height() - ( PR_BOTTOM_MARGIN + PR_TOP_MARGIN ), backgroundShade() );
2947 }
2948 }
2949
2950 // Draw the vertical beat lines
2951 int ticksPerBeat = DefaultTicksPerTact /
2952 Engine::getSong()->getTimeSigModel().getDenominator();
2953
2954 for( tick = m_currentPosition - m_currentPosition % ticksPerBeat,
2955 x = xCoordOfTick( tick ); x <= width();
2956 tick += ticksPerBeat, x = xCoordOfTick( tick ) )
2957 {
2958 p.setPen( beatLineColor() );
2959 p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN );
2960 }
2961
2962 // Draw the vertical bar lines
2963 for( tick = m_currentPosition - m_currentPosition % MidiTime::ticksPerTact(),
2964 x = xCoordOfTick( tick ); x <= width();
2965 tick += MidiTime::ticksPerTact(), x = xCoordOfTick( tick ) )
2966 {
2967 p.setPen( barLineColor() );
2968 p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN );
2969 }
2970
2971 // draw marked semitones after the grid
2972 for( int i = 0; i < m_markedSemiTones.size(); i++ )
2973 {
2974 const int key_num = m_markedSemiTones.at( i );
2975 const int y = key_line_y + 5
2976 - KEY_LINE_HEIGHT * ( key_num - m_startKey + 1 );
2977
2978 if( y > key_line_y )
2979 {
2980 break;
2981 }
2982
2983 p.fillRect( WHITE_KEY_WIDTH + 1, y - KEY_LINE_HEIGHT / 2, width() - 10, KEY_LINE_HEIGHT + 1,
2984 markedSemitoneColor() );
2985 }
2986 }
2987
2988 // following code draws all notes in visible area
2989 // and the note editing stuff (volume, panning, etc)
2990
2991 // setup selection-vars
2992 int sel_pos_start = m_selectStartTick;
2993 int sel_pos_end = m_selectStartTick+m_selectedTick;
2994 if( sel_pos_start > sel_pos_end )
2995 {
2996 qSwap<int>( sel_pos_start, sel_pos_end );
2997 }
2998
2999 int sel_key_start = m_selectStartKey - m_startKey + 1;
3000 int sel_key_end = sel_key_start + m_selectedKeys;
3001 if( sel_key_start > sel_key_end )
3002 {
3003 qSwap<int>( sel_key_start, sel_key_end );
3004 }
3005
3006 int y_base = key_line_y - 1;
3007 if( hasValidPattern() )
3008 {
3009 p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN,
3010 width() - WHITE_KEY_WIDTH,
3011 height() - PR_TOP_MARGIN );
3012
3013 const int visible_keys = ( key_line_y-keyAreaTop() ) /
3014 KEY_LINE_HEIGHT + 2;
3015
3016 QPolygonF editHandles;
3017
3018 for( const Note *note : m_pattern->notes() )
3019 {
3020 int len_ticks = note->length();
3021
3022 if( len_ticks == 0 )
3023 {
3024 continue;
3025 }
3026 else if( len_ticks < 0 )
3027 {
3028 len_ticks = 4;
3029 }
3030
3031 const int key = note->key() - m_startKey + 1;
3032
3033 int pos_ticks = note->pos();
3034
3035 int note_width = len_ticks * m_ppt / MidiTime::ticksPerTact();
3036 const int x = ( pos_ticks - m_currentPosition ) *
3037 m_ppt / MidiTime::ticksPerTact();
3038 // skip this note if not in visible area at all
3039 if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) )
3040 {
3041 continue;
3042 }
3043
3044 // is the note in visible area?
3045 if( key > 0 && key <= visible_keys )
3046 {
3047
3048 // we've done and checked all, let's draw the
3049 // note
3050 drawNoteRect( p, x + WHITE_KEY_WIDTH,
3051 y_base - key * KEY_LINE_HEIGHT,
3052 note_width, note, noteColor(), selectedNoteColor(),
3053 noteOpacity(), noteBorders() );
3054 }
3055
3056 // draw note editing stuff
3057 int editHandleTop = 0;
3058 if( m_noteEditMode == NoteEditVolume )
3059 {
3060 QColor color = barColor().lighter( 30 + ( note->getVolume() * 90 / MaxVolume ) );
3061 if( note->selected() )
3062 {
3063 color = selectedNoteColor();
3064 }
3065 p.setPen( QPen( color, NOTE_EDIT_LINE_WIDTH ) );
3066
3067 editHandleTop = noteEditBottom() -
3068 ( (float)( note->getVolume() - MinVolume ) ) /
3069 ( (float)( MaxVolume - MinVolume ) ) *
3070 ( (float)( noteEditBottom() - noteEditTop() ) );
3071
3072 p.drawLine( QLineF ( noteEditLeft() + x + 0.5, editHandleTop + 0.5,
3073 noteEditLeft() + x + 0.5, noteEditBottom() + 0.5 ) );
3074
3075 }
3076 else if( m_noteEditMode == NoteEditPanning )
3077 {
3078 QColor color = noteColor();
3079 if( note->selected() )
3080 {
3081 color = selectedNoteColor();
3082 }
3083
3084 p.setPen( QPen( color, NOTE_EDIT_LINE_WIDTH ) );
3085
3086 editHandleTop = noteEditBottom() -
3087 ( (float)( note->getPanning() - PanningLeft ) ) /
3088 ( (float)( (PanningRight - PanningLeft ) ) ) *
3089 ( (float)( noteEditBottom() - noteEditTop() ) );
3090
3091 p.drawLine( QLine( noteEditLeft() + x, noteEditTop() +
3092 ( (float)( noteEditBottom() - noteEditTop() ) ) / 2.0f,
3093 noteEditLeft() + x , editHandleTop ) );
3094 }
3095 editHandles << QPoint ( x + noteEditLeft(),
3096 editHandleTop );
3097
3098 if( note->hasDetuningInfo() )
3099 {
3100 drawDetuningInfo( p, note,
3101 x + WHITE_KEY_WIDTH,
3102 y_base - key * KEY_LINE_HEIGHT );
3103 p.setClipRect(WHITE_KEY_WIDTH, PR_TOP_MARGIN,
3104 width() - WHITE_KEY_WIDTH,
3105 height() - PR_TOP_MARGIN);
3106 }
3107 }
3108
3109
3110 p.setPen( QPen( noteColor(), NOTE_EDIT_LINE_WIDTH + 2 ) );
3111 p.drawPoints( editHandles );
3112
3113 }
3114 else
3115 {
3116 QFont f = p.font();
3117 f.setBold( true );
3118 p.setFont( pointSize<14>( f ) );
3119 p.setPen( QApplication::palette().color( QPalette::Active,
3120 QPalette::BrightText ) );
3121 p.drawText( WHITE_KEY_WIDTH + 20, PR_TOP_MARGIN + 40,
3122 tr( "Please open a pattern by double-clicking "
3123 "on it!" ) );
3124 }
3125
3126 p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, width() -
3127 WHITE_KEY_WIDTH, height() - PR_TOP_MARGIN -
3128 m_notesEditHeight - PR_BOTTOM_MARGIN );
3129
3130 // now draw selection-frame
3131 int x = ( ( sel_pos_start - m_currentPosition ) * m_ppt ) /
3132 MidiTime::ticksPerTact();
3133 int w = ( ( ( sel_pos_end - m_currentPosition ) * m_ppt ) /
3134 MidiTime::ticksPerTact() ) - x;
3135 int y = (int) y_base - sel_key_start * KEY_LINE_HEIGHT;
3136 int h = (int) y_base - sel_key_end * KEY_LINE_HEIGHT - y;
3137 p.setPen( selectedNoteColor() );
3138 p.setBrush( Qt::NoBrush );
3139 p.drawRect( x + WHITE_KEY_WIDTH, y, w, h );
3140
3141 // TODO: Get this out of paint event
3142 int l = ( hasValidPattern() )? (int) m_pattern->length() : 0;
3143
3144 // reset scroll-range
3145 if( m_leftRightScroll->maximum() != l )
3146 {
3147 m_leftRightScroll->setRange( 0, l );
3148 m_leftRightScroll->setPageStep( l );
3149 }
3150
3151 // set line colors
3152 QColor editAreaCol = QColor( lineColor() );
3153 QColor currentKeyCol = QColor( beatLineColor() );
3154
3155 editAreaCol.setAlpha( 64 );
3156 currentKeyCol.setAlpha( 64 );
3157
3158 // horizontal line for the key under the cursor
3159 if( hasValidPattern() )
3160 {
3161 int key_num = getKey( mapFromGlobal( QCursor::pos() ).y() );
3162 p.fillRect( 10, key_line_y + 3 - KEY_LINE_HEIGHT *
3163 ( key_num - m_startKey + 1 ), width() - 10, KEY_LINE_HEIGHT - 7, currentKeyCol );
3164 }
3165
3166 // bar to resize note edit area
3167 p.setClipRect( 0, 0, width(), height() );
3168 p.fillRect( QRect( 0, key_line_y,
3169 width()-PR_RIGHT_MARGIN, NOTE_EDIT_RESIZE_BAR ), editAreaCol );
3170
3171 const QPixmap * cursor = NULL;
3172 // draw current edit-mode-icon below the cursor
3173 switch( m_editMode )
3174 {
3175 case ModeDraw:
3176 if( m_mouseDownRight )
3177 {
3178 cursor = s_toolErase;
3179 }
3180 else if( m_action == ActionMoveNote )
3181 {
3182 cursor = s_toolMove;
3183 }
3184 else
3185 {
3186 cursor = s_toolDraw;
3187 }
3188 break;
3189 case ModeErase: cursor = s_toolErase; break;
3190 case ModeSelect: cursor = s_toolSelect; break;
3191 case ModeEditDetuning: cursor = s_toolOpen; break;
3192 }
3193 QPoint mousePosition = mapFromGlobal( QCursor::pos() );
3194 if( cursor != NULL && mousePosition.y() > keyAreaTop() && mousePosition.x() > noteEditLeft())
3195 {
3196 p.drawPixmap( mousePosition + QPoint( 8, 8 ), *cursor );
3197 }
3198 }
3199
3200
3201
3202
3203 // responsible for moving/resizing scrollbars after window-resizing
resizeEvent(QResizeEvent * re)3204 void PianoRoll::resizeEvent(QResizeEvent * re)
3205 {
3206 m_leftRightScroll->setGeometry( WHITE_KEY_WIDTH,
3207 height() -
3208 SCROLLBAR_SIZE,
3209 width()-WHITE_KEY_WIDTH,
3210 SCROLLBAR_SIZE );
3211 m_topBottomScroll->setGeometry( width() - SCROLLBAR_SIZE, PR_TOP_MARGIN,
3212 SCROLLBAR_SIZE,
3213 height() - PR_TOP_MARGIN -
3214 SCROLLBAR_SIZE );
3215
3216 int total_pixels = OCTAVE_HEIGHT * NumOctaves - ( height() -
3217 PR_TOP_MARGIN - PR_BOTTOM_MARGIN -
3218 m_notesEditHeight );
3219 m_totalKeysToScroll = total_pixels * KeysPerOctave / OCTAVE_HEIGHT;
3220
3221 m_topBottomScroll->setRange( 0, m_totalKeysToScroll );
3222
3223 if( m_startKey > m_totalKeysToScroll )
3224 {
3225 m_startKey = m_totalKeysToScroll;
3226 }
3227 m_topBottomScroll->setValue( m_totalKeysToScroll - m_startKey );
3228
3229 Engine::getSong()->getPlayPos( Song::Mode_PlayPattern
3230 ).m_timeLine->setFixedWidth( width() );
3231
3232 update();
3233 }
3234
3235
3236
3237
wheelEvent(QWheelEvent * we)3238 void PianoRoll::wheelEvent(QWheelEvent * we )
3239 {
3240 we->accept();
3241 // handle wheel events for note edit area - for editing note vol/pan with mousewheel
3242 if( we->x() > noteEditLeft() && we->x() < noteEditRight()
3243 && we->y() > noteEditTop() && we->y() < noteEditBottom() )
3244 {
3245 if (!hasValidPattern()) {return;}
3246 // get values for going through notes
3247 int pixel_range = 8;
3248 int x = we->x() - WHITE_KEY_WIDTH;
3249 int ticks_start = ( x - pixel_range / 2 ) *
3250 MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
3251 int ticks_end = ( x + pixel_range / 2 ) *
3252 MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
3253
3254 // When alt is pressed we only edit the note under the cursor
3255 bool altPressed = we->modifiers() & Qt::AltModifier;
3256 // go through notes to figure out which one we want to change
3257 NoteVector nv;
3258 for ( Note * i : m_pattern->notes() )
3259 {
3260 if( i->withinRange( ticks_start, ticks_end ) || ( i->selected() && !altPressed ) )
3261 {
3262 nv += i;
3263 }
3264 }
3265 if( nv.size() > 0 )
3266 {
3267 const int step = we->delta() > 0 ? 1.0 : -1.0;
3268 if( m_noteEditMode == NoteEditVolume )
3269 {
3270 for ( Note * n : nv )
3271 {
3272 volume_t vol = tLimit<int>( n->getVolume() + step, MinVolume, MaxVolume );
3273 n->setVolume( vol );
3274 }
3275 bool allVolumesEqual = std::all_of( nv.begin(), nv.end(),
3276 [nv](const Note *note)
3277 {
3278 return note->getVolume() == nv[0]->getVolume();
3279 });
3280 if ( allVolumesEqual )
3281 {
3282 // show the volume hover-text only if all notes have the
3283 // same volume
3284 showVolTextFloat( nv[0]->getVolume(), we->pos(), 1000 );
3285 }
3286 }
3287 else if( m_noteEditMode == NoteEditPanning )
3288 {
3289 for ( Note * n : nv )
3290 {
3291 panning_t pan = tLimit<int>( n->getPanning() + step, PanningLeft, PanningRight );
3292 n->setPanning( pan );
3293 }
3294 bool allPansEqual = std::all_of( nv.begin(), nv.end(),
3295 [nv](const Note *note)
3296 {
3297 return note->getPanning() == nv[0]->getPanning();
3298 });
3299 if ( allPansEqual )
3300 {
3301 // show the pan hover-text only if all notes have the same
3302 // panning
3303 showPanTextFloat( nv[0]->getPanning(), we->pos(), 1000 );
3304 }
3305 }
3306 update();
3307 }
3308 }
3309
3310 // not in note edit area, so handle scrolling/zooming and quantization change
3311 else
3312 if( we->modifiers() & Qt::ControlModifier && we->modifiers() & Qt::AltModifier )
3313 {
3314 int q = m_quantizeModel.value();
3315 if( we->delta() > 0 )
3316 {
3317 q--;
3318 }
3319 else if( we->delta() < 0 )
3320 {
3321 q++;
3322 }
3323 q = qBound( 0, q, m_quantizeModel.size() - 1 );
3324 m_quantizeModel.setValue( q );
3325 }
3326 else if( we->modifiers() & Qt::ControlModifier && we->modifiers() & Qt::ShiftModifier )
3327 {
3328 int l = m_noteLenModel.value();
3329 if( we->delta() > 0 )
3330 {
3331 l--;
3332 }
3333 else if( we->delta() < 0 )
3334 {
3335 l++;
3336 }
3337 l = qBound( 0, l, m_noteLenModel.size() - 1 );
3338 m_noteLenModel.setValue( l );
3339 }
3340 else if( we->modifiers() & Qt::ControlModifier )
3341 {
3342 int z = m_zoomingModel.value();
3343 if( we->delta() > 0 )
3344 {
3345 z++;
3346 }
3347 else if( we->delta() < 0 )
3348 {
3349 z--;
3350 }
3351 z = qBound( 0, z, m_zoomingModel.size() - 1 );
3352 // update combobox with zooming-factor
3353 m_zoomingModel.setValue( z );
3354 }
3355 else if( we->modifiers() & Qt::ShiftModifier
3356 || we->orientation() == Qt::Horizontal )
3357 {
3358 m_leftRightScroll->setValue( m_leftRightScroll->value() -
3359 we->delta() * 2 / 15 );
3360 }
3361 else
3362 {
3363 m_topBottomScroll->setValue( m_topBottomScroll->value() -
3364 we->delta() / 30 );
3365 }
3366 }
3367
3368
3369
3370
focusOutEvent(QFocusEvent *)3371 void PianoRoll::focusOutEvent( QFocusEvent * )
3372 {
3373 if( hasValidPattern() )
3374 {
3375 for( int i = 0; i < NumKeys; ++i )
3376 {
3377 m_pattern->instrumentTrack()->pianoModel()->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, -1, i, 0 ) );
3378 m_pattern->instrumentTrack()->pianoModel()->setKeyState( i, false );
3379 }
3380 }
3381 m_editMode = m_ctrlMode;
3382 update();
3383 }
3384
3385
3386
3387
getKey(int y) const3388 int PianoRoll::getKey(int y ) const
3389 {
3390 int key_line_y = keyAreaBottom() - 1;
3391 // pressed key on piano
3392 int key_num = ( key_line_y - y ) / KEY_LINE_HEIGHT;
3393 key_num += m_startKey;
3394
3395 // some range-checking-stuff
3396 if( key_num < 0 )
3397 {
3398 key_num = 0;
3399 }
3400
3401 if( key_num >= KeysPerOctave * NumOctaves )
3402 {
3403 key_num = KeysPerOctave * NumOctaves - 1;
3404 }
3405
3406 return key_num;
3407 }
3408
getAllOctavesForKey(int keyToMirror) const3409 QList<int> PianoRoll::getAllOctavesForKey( int keyToMirror ) const
3410 {
3411 QList<int> keys;
3412
3413 for (int i=keyToMirror % KeysPerOctave; i < NumKeys; i += KeysPerOctave)
3414 {
3415 keys.append(i);
3416 }
3417
3418 return keys;
3419 }
3420
desiredPlayModeForAccompany() const3421 Song::PlayModes PianoRoll::desiredPlayModeForAccompany() const
3422 {
3423 if( m_pattern->getTrack()->trackContainer() ==
3424 Engine::getBBTrackContainer() )
3425 {
3426 return Song::Mode_PlayBB;
3427 }
3428 return Song::Mode_PlaySong;
3429 }
3430
3431
3432
3433
play()3434 void PianoRoll::play()
3435 {
3436 if( ! hasValidPattern() )
3437 {
3438 return;
3439 }
3440
3441 if( Engine::getSong()->playMode() != Song::Mode_PlayPattern )
3442 {
3443 Engine::getSong()->playPattern( m_pattern );
3444 }
3445 else
3446 {
3447 Engine::getSong()->togglePause();
3448 }
3449 }
3450
3451
3452
3453
record()3454 void PianoRoll::record()
3455 {
3456 if( Engine::getSong()->isPlaying() )
3457 {
3458 stop();
3459 }
3460 if( m_recording || ! hasValidPattern() )
3461 {
3462 return;
3463 }
3464
3465 m_pattern->addJournalCheckPoint();
3466 m_recording = true;
3467
3468 Engine::getSong()->playPattern( m_pattern, false );
3469 }
3470
3471
3472
3473
recordAccompany()3474 void PianoRoll::recordAccompany()
3475 {
3476 if( Engine::getSong()->isPlaying() )
3477 {
3478 stop();
3479 }
3480 if( m_recording || ! hasValidPattern() )
3481 {
3482 return;
3483 }
3484
3485 m_pattern->addJournalCheckPoint();
3486 m_recording = true;
3487
3488 if( m_pattern->getTrack()->trackContainer() == Engine::getSong() )
3489 {
3490 Engine::getSong()->playSong();
3491 }
3492 else
3493 {
3494 Engine::getSong()->playBB();
3495 }
3496 }
3497
3498
3499
3500
3501
stop()3502 void PianoRoll::stop()
3503 {
3504 Engine::getSong()->stop();
3505 m_recording = false;
3506 m_scrollBack = ( m_timeLine->autoScroll() == TimeLineWidget::AutoScrollEnabled );
3507 }
3508
3509
3510
3511
startRecordNote(const Note & n)3512 void PianoRoll::startRecordNote(const Note & n )
3513 {
3514 if( m_recording && hasValidPattern() &&
3515 Engine::getSong()->isPlaying() &&
3516 (Engine::getSong()->playMode() == desiredPlayModeForAccompany() ||
3517 Engine::getSong()->playMode() == Song::Mode_PlayPattern ))
3518 {
3519 MidiTime sub;
3520 if( Engine::getSong()->playMode() == Song::Mode_PlaySong )
3521 {
3522 sub = m_pattern->startPosition();
3523 }
3524 Note n1( 1, Engine::getSong()->getPlayPos(
3525 Engine::getSong()->playMode() ) - sub,
3526 n.key(), n.getVolume(), n.getPanning() );
3527 if( n1.pos() >= 0 )
3528 {
3529 m_recordingNotes << n1;
3530 }
3531 }
3532 }
3533
3534
3535
3536
finishRecordNote(const Note & n)3537 void PianoRoll::finishRecordNote(const Note & n )
3538 {
3539 if( m_recording && hasValidPattern() &&
3540 Engine::getSong()->isPlaying() &&
3541 ( Engine::getSong()->playMode() ==
3542 desiredPlayModeForAccompany() ||
3543 Engine::getSong()->playMode() ==
3544 Song::Mode_PlayPattern ) )
3545 {
3546 for( QList<Note>::Iterator it = m_recordingNotes.begin();
3547 it != m_recordingNotes.end(); ++it )
3548 {
3549 if( it->key() == n.key() )
3550 {
3551 Note n1( n.length(), it->pos(),
3552 it->key(), it->getVolume(),
3553 it->getPanning() );
3554 n1.quantizeLength( quantization() );
3555
3556 //Get selected chord
3557 const InstrumentFunctionNoteStacking::Chord & chord = InstrumentFunctionNoteStacking::ChordTable::getInstance()
3558 .getChordByName( m_chordModel.currentText() );
3559
3560 if( !chord.isEmpty() )
3561 {
3562 for( int i = 1; i < chord.size(); i++ )
3563 {
3564 Note new_note( n.length(), it->pos(), it->key() + chord[i] );
3565 new_note.setPanning( it->getPanning() );
3566 new_note.setVolume( it->getVolume() );
3567 m_pattern->addNote( new_note );
3568 }
3569 }
3570
3571 m_pattern->addNote( n1 );
3572 update();
3573 m_recordingNotes.erase( it );
3574 break;
3575 }
3576 }
3577 }
3578 }
3579
3580
3581
3582
horScrolled(int new_pos)3583 void PianoRoll::horScrolled(int new_pos )
3584 {
3585 m_currentPosition = new_pos;
3586 emit positionChanged( m_currentPosition );
3587 update();
3588 }
3589
3590
3591
3592
verScrolled(int new_pos)3593 void PianoRoll::verScrolled( int new_pos )
3594 {
3595 // revert value
3596 m_startKey = m_totalKeysToScroll - new_pos;
3597
3598 update();
3599 }
3600
3601
3602
3603
setEditMode(int mode)3604 void PianoRoll::setEditMode(int mode)
3605 {
3606 m_ctrlMode = m_editMode = (EditModes) mode;
3607 }
3608
3609
3610
3611
selectAll()3612 void PianoRoll::selectAll()
3613 {
3614 if( ! hasValidPattern() )
3615 {
3616 return;
3617 }
3618
3619 // if first_time = true, we HAVE to set the vars for select
3620 bool first_time = true;
3621
3622 for( const Note *note : m_pattern->notes() )
3623 {
3624 int len_ticks = static_cast<int>( note->length() ) > 0 ?
3625 static_cast<int>( note->length() ) : 1;
3626
3627 const int key = note->key();
3628
3629 int pos_ticks = note->pos();
3630 if( key <= m_selectStartKey || first_time )
3631 {
3632 // if we move start-key down, we have to add
3633 // the difference between old and new start-key
3634 // to m_selectedKeys, otherwise the selection
3635 // is just moved down...
3636 m_selectedKeys += m_selectStartKey
3637 - ( key - 1 );
3638 m_selectStartKey = key - 1;
3639 }
3640 if( key >= m_selectedKeys + m_selectStartKey ||
3641 first_time )
3642 {
3643 m_selectedKeys = key - m_selectStartKey;
3644 }
3645 if( pos_ticks < m_selectStartTick ||
3646 first_time )
3647 {
3648 m_selectStartTick = pos_ticks;
3649 }
3650 if( pos_ticks + len_ticks >
3651 m_selectStartTick + m_selectedTick ||
3652 first_time )
3653 {
3654 m_selectedTick = pos_ticks +
3655 len_ticks -
3656 m_selectStartTick;
3657 }
3658 first_time = false;
3659 }
3660 }
3661
3662
3663
3664
3665 // returns vector with pointers to all selected notes
getSelectedNotes()3666 NoteVector PianoRoll::getSelectedNotes()
3667 {
3668 NoteVector selectedNotes;
3669
3670 if (hasValidPattern())
3671 {
3672 for( Note *note : m_pattern->notes() )
3673 {
3674 if( note->selected() )
3675 {
3676 selectedNotes.push_back( note );
3677 }
3678 }
3679 }
3680 return selectedNotes;
3681 }
3682
3683 // selects all notess associated with m_lastKey
selectNotesOnKey()3684 void PianoRoll::selectNotesOnKey()
3685 {
3686 if (hasValidPattern()) {
3687 for (Note * note : m_pattern->notes()) {
3688 if (note->key() == m_lastKey) {
3689 note->setSelected(true);
3690 }
3691 }
3692 }
3693 }
3694
enterValue(NoteVector * nv)3695 void PianoRoll::enterValue( NoteVector* nv )
3696 {
3697
3698 if( m_noteEditMode == NoteEditVolume )
3699 {
3700 bool ok;
3701 int new_val;
3702 new_val = QInputDialog::getInt( this, "Piano roll: note velocity",
3703 tr( "Please enter a new value between %1 and %2:" ).
3704 arg( MinVolume ).arg( MaxVolume ),
3705 (*nv)[0]->getVolume(),
3706 MinVolume, MaxVolume, 1, &ok );
3707
3708 if( ok )
3709 {
3710 for ( Note * n : *nv )
3711 {
3712 n->setVolume( new_val );
3713 }
3714 m_lastNoteVolume = new_val;
3715 }
3716 }
3717 else if( m_noteEditMode == NoteEditPanning )
3718 {
3719 bool ok;
3720 int new_val;
3721 new_val = QInputDialog::getInt( this, "Piano roll: note panning",
3722 tr( "Please enter a new value between %1 and %2:" ).
3723 arg( PanningLeft ).arg( PanningRight ),
3724 (*nv)[0]->getPanning(),
3725 PanningLeft, PanningRight, 1, &ok );
3726
3727 if( ok )
3728 {
3729 for ( Note * n : *nv )
3730 {
3731 n->setPanning( new_val );
3732 }
3733 m_lastNotePanning = new_val;
3734 }
3735
3736 }
3737 }
3738
3739
copyToClipboard(const NoteVector & notes) const3740 void PianoRoll::copyToClipboard( const NoteVector & notes ) const
3741 {
3742 DataFile dataFile( DataFile::ClipboardData );
3743 QDomElement note_list = dataFile.createElement( "note-list" );
3744 dataFile.content().appendChild( note_list );
3745
3746 MidiTime start_pos( notes.front()->pos().getTact(), 0 );
3747 for( const Note *note : notes )
3748 {
3749 Note clip_note( *note );
3750 clip_note.setPos( clip_note.pos( start_pos ) );
3751 clip_note.saveState( dataFile, note_list );
3752 }
3753
3754 QMimeData * clip_content = new QMimeData;
3755 clip_content->setData( Clipboard::mimeType(), dataFile.toString().toUtf8() );
3756 QApplication::clipboard()->setMimeData( clip_content,
3757 QClipboard::Clipboard );
3758 }
3759
3760
3761
3762
copySelectedNotes()3763 void PianoRoll::copySelectedNotes()
3764 {
3765 NoteVector selected_notes = getSelectedNotes();
3766
3767 if( ! selected_notes.empty() )
3768 {
3769 copyToClipboard( selected_notes );
3770 }
3771 }
3772
3773
3774
3775
cutSelectedNotes()3776 void PianoRoll::cutSelectedNotes()
3777 {
3778 if( ! hasValidPattern() )
3779 {
3780 return;
3781 }
3782
3783 NoteVector selected_notes = getSelectedNotes();
3784
3785 if( ! selected_notes.empty() )
3786 {
3787 copyToClipboard( selected_notes );
3788
3789 Engine::getSong()->setModified();
3790
3791 for( Note *note : selected_notes )
3792 {
3793 // note (the memory of it) is also deleted by
3794 // pattern::removeNote(...) so we don't have to do that
3795 m_pattern->removeNote( note );
3796 }
3797 }
3798
3799 update();
3800 gui->songEditor()->update();
3801 }
3802
3803
3804
3805
pasteNotes()3806 void PianoRoll::pasteNotes()
3807 {
3808 if( ! hasValidPattern() )
3809 {
3810 return;
3811 }
3812
3813 QString value = QApplication::clipboard()
3814 ->mimeData( QClipboard::Clipboard )
3815 ->data( Clipboard::mimeType() );
3816
3817 if( ! value.isEmpty() )
3818 {
3819 DataFile dataFile( value.toUtf8() );
3820
3821 QDomNodeList list = dataFile.elementsByTagName( Note::classNodeName() );
3822
3823 // remove selection and select the newly pasted notes
3824 clearSelectedNotes();
3825
3826 if( ! list.isEmpty() )
3827 {
3828 m_pattern->addJournalCheckPoint();
3829 }
3830
3831 for( int i = 0; ! list.item( i ).isNull(); ++i )
3832 {
3833 // create the note
3834 Note cur_note;
3835 cur_note.restoreState( list.item( i ).toElement() );
3836 cur_note.setPos( cur_note.pos() + Note::quantized( m_timeLine->pos(), quantization() ) );
3837
3838 // select it
3839 cur_note.setSelected( true );
3840
3841 // add to pattern
3842 m_pattern->addNote( cur_note, false );
3843 }
3844
3845 // we only have to do the following lines if we pasted at
3846 // least one note...
3847 Engine::getSong()->setModified();
3848 update();
3849 gui->songEditor()->update();
3850 }
3851 }
3852
3853
3854
3855
deleteSelectedNotes()3856 void PianoRoll::deleteSelectedNotes()
3857 {
3858 if( ! hasValidPattern() )
3859 {
3860 return;
3861 }
3862
3863 bool update_after_delete = false;
3864
3865 m_pattern->addJournalCheckPoint();
3866
3867 // get note-vector of current pattern
3868 const NoteVector & notes = m_pattern->notes();
3869
3870 // will be our iterator in the following loop
3871 NoteVector::ConstIterator it = notes.begin();
3872 while( it != notes.end() )
3873 {
3874 Note *note = *it;
3875 if( note->selected() )
3876 {
3877 // delete this note
3878 m_pattern->removeNote( note );
3879 update_after_delete = true;
3880
3881 // start over, make sure we get all the notes
3882 it = notes.begin();
3883 }
3884 else
3885 {
3886 ++it;
3887 }
3888 }
3889
3890 if( update_after_delete )
3891 {
3892 Engine::getSong()->setModified();
3893 update();
3894 gui->songEditor()->update();
3895 }
3896
3897 }
3898
3899
3900
3901
autoScroll(const MidiTime & t)3902 void PianoRoll::autoScroll( const MidiTime & t )
3903 {
3904 const int w = width() - WHITE_KEY_WIDTH;
3905 if( t > m_currentPosition + w * MidiTime::ticksPerTact() / m_ppt )
3906 {
3907 m_leftRightScroll->setValue( t.getTact() * MidiTime::ticksPerTact() );
3908 }
3909 else if( t < m_currentPosition )
3910 {
3911 MidiTime t2 = qMax( t - w * MidiTime::ticksPerTact() *
3912 MidiTime::ticksPerTact() / m_ppt, (tick_t) 0 );
3913 m_leftRightScroll->setValue( t2.getTact() * MidiTime::ticksPerTact() );
3914 }
3915 m_scrollBack = false;
3916 }
3917
3918
3919
3920
updatePosition(const MidiTime & t)3921 void PianoRoll::updatePosition( const MidiTime & t )
3922 {
3923 if( ( Engine::getSong()->isPlaying()
3924 && Engine::getSong()->playMode() == Song::Mode_PlayPattern
3925 && m_timeLine->autoScroll() == TimeLineWidget::AutoScrollEnabled
3926 ) || m_scrollBack )
3927 {
3928 autoScroll( t );
3929 }
3930 }
3931
3932
3933
3934
updatePositionAccompany(const MidiTime & t)3935 void PianoRoll::updatePositionAccompany( const MidiTime & t )
3936 {
3937 Song * s = Engine::getSong();
3938
3939 if( m_recording && hasValidPattern() &&
3940 s->playMode() != Song::Mode_PlayPattern )
3941 {
3942 MidiTime pos = t;
3943 if( s->playMode() != Song::Mode_PlayBB )
3944 {
3945 pos -= m_pattern->startPosition();
3946 }
3947 if( (int) pos > 0 )
3948 {
3949 s->getPlayPos( Song::Mode_PlayPattern ).setTicks( pos );
3950 autoScroll( pos );
3951 }
3952 }
3953 }
3954
3955
3956
3957
zoomingChanged()3958 void PianoRoll::zoomingChanged()
3959 {
3960 m_ppt = m_zoomLevels[m_zoomingModel.value()] * DEFAULT_PR_PPT;
3961
3962 assert( m_ppt > 0 );
3963
3964 m_timeLine->setPixelsPerTact( m_ppt );
3965 update();
3966 }
3967
3968
3969
3970
quantizeChanged()3971 void PianoRoll::quantizeChanged()
3972 {
3973 update();
3974 }
3975
3976
3977
3978
quantization() const3979 int PianoRoll::quantization() const
3980 {
3981 if( m_quantizeModel.value() == 0 )
3982 {
3983 if( m_noteLenModel.value() > 0 )
3984 {
3985 return newNoteLen();
3986 }
3987 else
3988 {
3989 return DefaultTicksPerTact / 16;
3990 }
3991 }
3992
3993 QString text = m_quantizeModel.currentText();
3994 return DefaultTicksPerTact / text.right( text.length() - 2 ).toInt();
3995 }
3996
3997
quantizeNotes()3998 void PianoRoll::quantizeNotes()
3999 {
4000 if( ! hasValidPattern() )
4001 {
4002 return;
4003 }
4004
4005 m_pattern->addJournalCheckPoint();
4006
4007 NoteVector notes = getSelectedNotes();
4008
4009 if( notes.empty() )
4010 {
4011 for( Note* n : m_pattern->notes() )
4012 {
4013 notes.push_back( n );
4014 }
4015 }
4016
4017 for( Note* n : notes )
4018 {
4019 if( n->length() == MidiTime( 0 ) )
4020 {
4021 continue;
4022 }
4023
4024 Note copy(*n);
4025 m_pattern->removeNote( n );
4026 copy.quantizePos( quantization() );
4027 m_pattern->addNote( copy );
4028 }
4029
4030 update();
4031 gui->songEditor()->update();
4032 Engine::getSong()->setModified();
4033 }
4034
4035
4036
4037
updateSemiToneMarkerMenu()4038 void PianoRoll::updateSemiToneMarkerMenu()
4039 {
4040 const InstrumentFunctionNoteStacking::ChordTable& chord_table =
4041 InstrumentFunctionNoteStacking::ChordTable::getInstance();
4042 const InstrumentFunctionNoteStacking::Chord& scale =
4043 chord_table.getScaleByName( m_scaleModel.currentText() );
4044 const InstrumentFunctionNoteStacking::Chord& chord =
4045 chord_table.getChordByName( m_chordModel.currentText() );
4046
4047 emit semiToneMarkerMenuScaleSetEnabled( ! scale.isEmpty() );
4048 emit semiToneMarkerMenuChordSetEnabled( ! chord.isEmpty() );
4049 }
4050
4051
4052
4053
newNoteLen() const4054 MidiTime PianoRoll::newNoteLen() const
4055 {
4056 if( m_noteLenModel.value() == 0 )
4057 {
4058 return m_lenOfNewNotes;
4059 }
4060
4061 QString text = m_noteLenModel.currentText();
4062 return DefaultTicksPerTact / text.right( text.length() - 2 ).toInt();
4063 }
4064
4065
4066
4067
mouseOverNote()4068 bool PianoRoll::mouseOverNote()
4069 {
4070 return hasValidPattern() && noteUnderMouse() != NULL;
4071 }
4072
4073
4074
4075
noteUnderMouse()4076 Note * PianoRoll::noteUnderMouse()
4077 {
4078 QPoint pos = mapFromGlobal( QCursor::pos() );
4079
4080 if( pos.x() <= WHITE_KEY_WIDTH
4081 || pos.x() > width() - SCROLLBAR_SIZE
4082 || pos.y() < PR_TOP_MARGIN
4083 || pos.y() > keyAreaBottom() )
4084 {
4085 return NULL;
4086 }
4087
4088 int key_num = getKey( pos.y() );
4089 int pos_ticks = ( pos.x() - WHITE_KEY_WIDTH ) *
4090 MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
4091
4092 // loop through whole note-vector...
4093 for( Note* const& note : m_pattern->notes() )
4094 {
4095 // and check whether the cursor is over an
4096 // existing note
4097 if( pos_ticks >= note->pos()
4098 && pos_ticks <= note->endPos()
4099 && note->key() == key_num
4100 && note->length() > 0 )
4101 {
4102 return note;
4103 }
4104 }
4105
4106 return NULL;
4107 }
4108
4109
4110
4111
PianoRollWindow()4112 PianoRollWindow::PianoRollWindow() :
4113 Editor(true),
4114 m_editor(new PianoRoll())
4115 {
4116 setCentralWidget( m_editor );
4117
4118 m_playAction->setToolTip(tr( "Play/pause current pattern (Space)" ) );
4119 m_recordAction->setToolTip(tr( "Record notes from MIDI-device/channel-piano" ) );
4120 m_recordAccompanyAction->setToolTip( tr( "Record notes from MIDI-device/channel-piano while playing song or BB track" ) );
4121 m_stopAction->setToolTip( tr( "Stop playing of current pattern (Space)" ) );
4122
4123 m_playAction->setWhatsThis(
4124 tr( "Click here to play the current pattern. "
4125 "This is useful while editing it. The pattern is "
4126 "automatically looped when its end is reached." ) );
4127 m_recordAction->setWhatsThis(
4128 tr( "Click here to record notes from a MIDI-"
4129 "device or the virtual test-piano of the according "
4130 "channel-window to the current pattern. When recording "
4131 "all notes you play will be written to this pattern "
4132 "and you can play and edit them afterwards." ) );
4133 m_recordAccompanyAction->setWhatsThis(
4134 tr( "Click here to record notes from a MIDI-"
4135 "device or the virtual test-piano of the according "
4136 "channel-window to the current pattern. When recording "
4137 "all notes you play will be written to this pattern "
4138 "and you will hear the song or BB track in the background." ) );
4139 m_stopAction->setWhatsThis(
4140 tr( "Click here to stop playback of current pattern." ) );
4141
4142 DropToolBar *notesActionsToolBar = addDropToolBarToTop( tr( "Edit actions" ) );
4143
4144 // init edit-buttons at the top
4145 ActionGroup* editModeGroup = new ActionGroup( this );
4146 QAction* drawAction = editModeGroup->addAction( embed::getIconPixmap( "edit_draw" ), tr( "Draw mode (Shift+D)" ) );
4147 QAction* eraseAction = editModeGroup->addAction( embed::getIconPixmap( "edit_erase" ), tr("Erase mode (Shift+E)" ) );
4148 QAction* selectAction = editModeGroup->addAction( embed::getIconPixmap( "edit_select" ), tr( "Select mode (Shift+S)" ) );
4149 QAction* pitchBendAction = editModeGroup->addAction( embed::getIconPixmap( "automation" ), tr("Pitch Bend mode (Shift+T)" ) );
4150
4151 drawAction->setChecked( true );
4152
4153 drawAction->setShortcut( Qt::SHIFT | Qt::Key_D );
4154 eraseAction->setShortcut( Qt::SHIFT | Qt::Key_E );
4155 selectAction->setShortcut( Qt::SHIFT | Qt::Key_S );
4156 pitchBendAction->setShortcut( Qt::SHIFT | Qt::Key_T );
4157
4158 drawAction->setWhatsThis(
4159 tr( "Click here and draw mode will be activated. In this "
4160 "mode you can add, resize and move notes. This "
4161 "is the default mode which is used most of the time. "
4162 "You can also press 'Shift+D' on your keyboard to "
4163 "activate this mode. In this mode, hold %1 to "
4164 "temporarily go into select mode." ).arg(
4165 #ifdef LMMS_BUILD_APPLE
4166 "⌘" ) );
4167 #else
4168 "Ctrl" ) );
4169 #endif
4170 eraseAction->setWhatsThis(
4171 tr( "Click here and erase mode will be activated. In this "
4172 "mode you can erase notes. You can also press "
4173 "'Shift+E' on your keyboard to activate this mode." ) );
4174 selectAction->setWhatsThis(
4175 tr( "Click here and select mode will be activated. "
4176 "In this mode you can select notes. Alternatively, "
4177 "you can hold %1 in draw mode to temporarily use "
4178 "select mode." ).arg(
4179 #ifdef LMMS_BUILD_APPLE
4180 "⌘" ) );
4181 #else
4182 "Ctrl" ) );
4183 #endif
4184 pitchBendAction->setWhatsThis(
4185 tr( "Click here and Pitch Bend mode will be activated. "
4186 "In this mode you can click a note to open its "
4187 "automation detuning. You can utilize this to slide "
4188 "notes from one to another. You can also press "
4189 "'Shift+T' on your keyboard to activate this mode." ) );
4190
4191 connect( editModeGroup, SIGNAL( triggered( int ) ), m_editor, SLOT( setEditMode( int ) ) );
4192
4193 QAction* quantizeAction = new QAction(embed::getIconPixmap( "quantize" ), tr( "Quantize" ), this );
4194 connect( quantizeAction, SIGNAL( triggered() ), m_editor, SLOT( quantizeNotes() ) );
4195
4196 notesActionsToolBar->addAction( drawAction );
4197 notesActionsToolBar->addAction( eraseAction );
4198 notesActionsToolBar->addAction( selectAction );
4199 notesActionsToolBar->addAction( pitchBendAction );
4200 notesActionsToolBar->addSeparator();
4201 notesActionsToolBar->addAction( quantizeAction );
4202
4203 // Copy + paste actions
4204 DropToolBar *copyPasteActionsToolBar = addDropToolBarToTop( tr( "Copy paste controls" ) );
4205
4206 QAction* cutAction = new QAction(embed::getIconPixmap( "edit_cut" ),
4207 tr( "Cut selected notes (%1+X)" ).arg(
4208 #ifdef LMMS_BUILD_APPLE
4209 "⌘" ), this );
4210 #else
4211 "Ctrl" ), this );
4212 #endif
4213
4214 QAction* copyAction = new QAction(embed::getIconPixmap( "edit_copy" ),
4215 tr( "Copy selected notes (%1+C)" ).arg(
4216 #ifdef LMMS_BUILD_APPLE
4217 "⌘"), this);
4218 #else
4219 "Ctrl" ), this );
4220 #endif
4221
4222 QAction* pasteAction = new QAction(embed::getIconPixmap( "edit_paste" ),
4223 tr( "Paste notes from clipboard (%1+V)" ).arg(
4224 #ifdef LMMS_BUILD_APPLE
4225 "⌘" ), this );
4226 #else
4227 "Ctrl" ), this );
4228 #endif
4229
4230 cutAction->setWhatsThis(
4231 tr( "Click here and the selected notes will be cut into the "
4232 "clipboard. You can paste them anywhere in any pattern "
4233 "by clicking on the paste button." ) );
4234 copyAction->setWhatsThis(
4235 tr( "Click here and the selected notes will be copied into the "
4236 "clipboard. You can paste them anywhere in any pattern "
4237 "by clicking on the paste button." ) );
4238 pasteAction->setWhatsThis(
4239 tr( "Click here and the notes from the clipboard will be "
4240 "pasted at the first visible measure." ) );
4241
4242 cutAction->setShortcut( Qt::CTRL | Qt::Key_X );
4243 copyAction->setShortcut( Qt::CTRL | Qt::Key_C );
4244 pasteAction->setShortcut( Qt::CTRL | Qt::Key_V );
4245
4246 connect( cutAction, SIGNAL( triggered() ), m_editor, SLOT( cutSelectedNotes() ) );
4247 connect( copyAction, SIGNAL( triggered() ), m_editor, SLOT( copySelectedNotes() ) );
4248 connect( pasteAction, SIGNAL( triggered() ), m_editor, SLOT( pasteNotes() ) );
4249
4250 copyPasteActionsToolBar->addAction( cutAction );
4251 copyPasteActionsToolBar->addAction( copyAction );
4252 copyPasteActionsToolBar->addAction( pasteAction );
4253
4254
4255 DropToolBar *timeLineToolBar = addDropToolBarToTop( tr( "Timeline controls" ) );
4256 m_editor->m_timeLine->addToolButtons( timeLineToolBar );
4257
4258
4259 addToolBarBreak();
4260
4261
4262 DropToolBar *zoomAndNotesToolBar = addDropToolBarToTop( tr( "Zoom and note controls" ) );
4263
4264 QLabel * zoom_lbl = new QLabel( m_toolBar );
4265 zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) );
4266
4267 m_zoomingComboBox = new ComboBox( m_toolBar );
4268 m_zoomingComboBox->setModel( &m_editor->m_zoomingModel );
4269 m_zoomingComboBox->setFixedSize( 64, 22 );
4270
4271 // setup quantize-stuff
4272 QLabel * quantize_lbl = new QLabel( m_toolBar );
4273 quantize_lbl->setPixmap( embed::getIconPixmap( "quantize" ) );
4274
4275 m_quantizeComboBox = new ComboBox( m_toolBar );
4276 m_quantizeComboBox->setModel( &m_editor->m_quantizeModel );
4277 m_quantizeComboBox->setFixedSize( 64, 22 );
4278
4279 // setup note-len-stuff
4280 QLabel * note_len_lbl = new QLabel( m_toolBar );
4281 note_len_lbl->setPixmap( embed::getIconPixmap( "note" ) );
4282
4283 m_noteLenComboBox = new ComboBox( m_toolBar );
4284 m_noteLenComboBox->setModel( &m_editor->m_noteLenModel );
4285 m_noteLenComboBox->setFixedSize( 105, 22 );
4286
4287 // setup scale-stuff
4288 QLabel * scale_lbl = new QLabel( m_toolBar );
4289 scale_lbl->setPixmap( embed::getIconPixmap( "scale" ) );
4290
4291 m_scaleComboBox = new ComboBox( m_toolBar );
4292 m_scaleComboBox->setModel( &m_editor->m_scaleModel );
4293 m_scaleComboBox->setFixedSize( 105, 22 );
4294
4295 // setup chord-stuff
4296 QLabel * chord_lbl = new QLabel( m_toolBar );
4297 chord_lbl->setPixmap( embed::getIconPixmap( "chord" ) );
4298
4299 m_chordComboBox = new ComboBox( m_toolBar );
4300 m_chordComboBox->setModel( &m_editor->m_chordModel );
4301 m_chordComboBox->setFixedSize( 105, 22 );
4302
4303
4304 zoomAndNotesToolBar->addWidget( zoom_lbl );
4305 zoomAndNotesToolBar->addWidget( m_zoomingComboBox );
4306
4307 zoomAndNotesToolBar->addSeparator();
4308 zoomAndNotesToolBar->addWidget( quantize_lbl );
4309 zoomAndNotesToolBar->addWidget( m_quantizeComboBox );
4310
4311 zoomAndNotesToolBar->addSeparator();
4312 zoomAndNotesToolBar->addWidget( note_len_lbl );
4313 zoomAndNotesToolBar->addWidget( m_noteLenComboBox );
4314
4315 zoomAndNotesToolBar->addSeparator();
4316 zoomAndNotesToolBar->addWidget( scale_lbl );
4317 zoomAndNotesToolBar->addWidget( m_scaleComboBox );
4318
4319 zoomAndNotesToolBar->addSeparator();
4320 zoomAndNotesToolBar->addWidget( chord_lbl );
4321 zoomAndNotesToolBar->addWidget( m_chordComboBox );
4322
4323 m_zoomingComboBox->setWhatsThis(
4324 tr(
4325 "This controls the magnification of an axis. "
4326 "It can be helpful to choose magnification for a specific "
4327 "task. For ordinary editing, the magnification should be "
4328 "fitted to your smallest notes. "
4329 ) );
4330
4331 m_quantizeComboBox->setWhatsThis(
4332 tr(
4333 "The 'Q' stands for quantization, and controls the grid size "
4334 "notes and control points snap to. "
4335 "With smaller quantization values, you can draw shorter notes "
4336 "in Piano Roll, and more exact control points in the "
4337 "Automation Editor."
4338
4339 ) );
4340
4341 m_noteLenComboBox->setWhatsThis(
4342 tr(
4343 "This lets you select the length of new notes. "
4344 "'Last Note' means that LMMS will use the note length of "
4345 "the note you last edited"
4346 ) );
4347
4348 m_scaleComboBox->setWhatsThis(
4349 tr(
4350 "The feature is directly connected to the context-menu "
4351 "on the virtual keyboard, to the left in Piano Roll. "
4352 "After you have chosen the scale you want "
4353 "in this drop-down menu, "
4354 "you can right click on a desired key in the virtual keyboard, "
4355 "and then choose 'Mark current Scale'. "
4356 "LMMS will highlight all notes that belongs to the chosen scale, "
4357 "and in the key you have selected!"
4358 ) );
4359
4360 m_chordComboBox->setWhatsThis(
4361 tr(
4362 "Let you select a chord which LMMS then can draw or highlight."
4363 "You can find the most common chords in this drop-down menu. "
4364 "After you have selected a chord, click anywhere to place the chord, and right "
4365 "click on the virtual keyboard to open context menu and highlight the chord. "
4366 "To return to single note placement, you need to choose 'No chord' "
4367 "in this drop-down menu."
4368 ) );
4369
4370 // setup our actual window
4371 setFocusPolicy( Qt::StrongFocus );
4372 setFocus();
4373 setWindowIcon( embed::getIconPixmap( "piano" ) );
4374 setCurrentPattern( NULL );
4375
4376 // Connections
4377 connect( m_editor, SIGNAL( currentPatternChanged() ), this, SIGNAL( currentPatternChanged() ) );
4378 connect( m_editor, SIGNAL( currentPatternChanged() ), this, SLOT( patternRenamed() ) );
4379 }
4380
4381
4382
4383
currentPattern() const4384 const Pattern* PianoRollWindow::currentPattern() const
4385 {
4386 return m_editor->currentPattern();
4387 }
4388
4389
4390
4391
setCurrentPattern(Pattern * pattern)4392 void PianoRollWindow::setCurrentPattern( Pattern* pattern )
4393 {
4394 m_editor->setCurrentPattern( pattern );
4395
4396 if ( pattern )
4397 {
4398 setWindowTitle( tr( "Piano-Roll - %1" ).arg( pattern->name() ) );
4399 connect( pattern->instrumentTrack(), SIGNAL( nameChanged() ), this, SLOT( patternRenamed()) );
4400 connect( pattern, SIGNAL( dataChanged() ), this, SLOT( patternRenamed() ) );
4401 }
4402 else
4403 {
4404 setWindowTitle( tr( "Piano-Roll - no pattern" ) );
4405 }
4406 }
4407
4408
4409
4410
isRecording() const4411 bool PianoRollWindow::isRecording() const
4412 {
4413 return m_editor->isRecording();
4414 }
4415
4416
4417
4418
quantization() const4419 int PianoRollWindow::quantization() const
4420 {
4421 return m_editor->quantization();
4422 }
4423
4424
4425
4426
play()4427 void PianoRollWindow::play()
4428 {
4429 m_editor->play();
4430 }
4431
4432
4433
4434
stop()4435 void PianoRollWindow::stop()
4436 {
4437 m_editor->stop();
4438 }
4439
4440
4441
4442
record()4443 void PianoRollWindow::record()
4444 {
4445 m_editor->record();
4446 }
4447
4448
4449
4450
recordAccompany()4451 void PianoRollWindow::recordAccompany()
4452 {
4453 m_editor->recordAccompany();
4454 }
4455
4456
4457
4458
stopRecording()4459 void PianoRollWindow::stopRecording()
4460 {
4461 m_editor->stopRecording();
4462 }
4463
4464
4465
4466
reset()4467 void PianoRollWindow::reset()
4468 {
4469 m_editor->reset();
4470 }
4471
4472
4473
4474
saveSettings(QDomDocument & doc,QDomElement & de)4475 void PianoRollWindow::saveSettings( QDomDocument & doc, QDomElement & de )
4476 {
4477 MainWindow::saveWidgetState( this, de );
4478 }
4479
4480
4481
4482
loadSettings(const QDomElement & de)4483 void PianoRollWindow::loadSettings( const QDomElement & de )
4484 {
4485 MainWindow::restoreWidgetState( this, de );
4486 }
4487
4488
4489
4490
sizeHint() const4491 QSize PianoRollWindow::sizeHint() const
4492 {
4493 return { INITIAL_PIANOROLL_WIDTH, INITIAL_PIANOROLL_HEIGHT };
4494 }
4495
4496
4497
4498
patternRenamed()4499 void PianoRollWindow::patternRenamed()
4500 {
4501 if ( currentPattern() )
4502 {
4503 setWindowTitle( tr( "Piano-Roll - %1" ).arg( currentPattern()->name() ) );
4504 }
4505 else
4506 {
4507 setWindowTitle( tr( "Piano-Roll - no pattern" ) );
4508 }
4509 }
4510
4511
4512
4513
focusInEvent(QFocusEvent * event)4514 void PianoRollWindow::focusInEvent( QFocusEvent * event )
4515 {
4516 // when the window is given focus, also give focus to the actual piano roll
4517 m_editor->setFocus( event->reason() );
4518 }
4519