#ifndef SINGLE_SOURCE_COMPILE /* * piano_roll.cpp - implementation of piano-roll which is used for actual * writing of melodies * * Copyright (c) 2004-2008 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "piano_roll.h" #include #include #include #include #include #include #include #include #include #include #ifndef __USE_XOPEN #define __USE_XOPEN #endif #include #include "clipboard.h" #include "combobox.h" #include "debug.h" #include "detuning_helper.h" #include "embed.h" #include "gui_templates.h" #include "instrument_track.h" #include "main_window.h" #include "midi.h" #include "mmp.h" #include "pattern.h" #include "piano.h" #include "pixmap_button.h" #include "song.h" #include "song_editor.h" #include "templates.h" #include "text_float.h" #include "timeline.h" #include "tool_button.h" #include "tooltip.h" typedef automationPattern::timeMap timeMap; extern Keys whiteKeys[]; // defined in piano_widget.cpp // some constants... const int INITIAL_PIANOROLL_WIDTH = 640; const int INITIAL_PIANOROLL_HEIGHT = 480; const int SCROLLBAR_SIZE = 16; const int PIANO_X = 0; const int WHITE_KEY_WIDTH = 64; const int BLACK_KEY_WIDTH = 41; const int WHITE_KEY_SMALL_HEIGHT = 18; const int WHITE_KEY_BIG_HEIGHT = 24; const int BLACK_KEY_HEIGHT = 16; const int C_KEY_LABEL_X = WHITE_KEY_WIDTH - 19; const int KEY_LINE_HEIGHT = 12; const int OCTAVE_HEIGHT = KEY_LINE_HEIGHT * KeysPerOctave; // = 12 * 12; const int PR_BOTTOM_MARGIN = SCROLLBAR_SIZE; const int PR_TOP_MARGIN = 48; // width of area used for resizing (the grip at the end of a note) const int RESIZE_AREA_WIDTH = 4; // width of line for setting volume/panning of note const int NE_LINE_WIDTH = 3; // key where to start const int INITIAL_START_KEY = Key_C + Octave_3 * KeysPerOctave; QPixmap * pianoRoll::s_whiteKeySmallPm = NULL; QPixmap * pianoRoll::s_whiteKeyBigPm = NULL; QPixmap * pianoRoll::s_blackKeyPm = NULL; QPixmap * pianoRoll::s_toolDraw = NULL; QPixmap * pianoRoll::s_toolErase = NULL; QPixmap * pianoRoll::s_toolSelect = NULL; QPixmap * pianoRoll::s_toolMove = NULL; QPixmap * pianoRoll::s_toolOpen = NULL; // used for drawing of piano pianoRoll::pianoRollKeyTypes pianoRoll::prKeyOrder[] = { PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, PR_WHITE_KEY_SMALL, PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, PR_WHITE_KEY_SMALL } ; const int DEFAULT_PR_PPT = KEY_LINE_HEIGHT * DefaultStepsPerTact; pianoRoll::pianoRoll( void ) : m_zoomingModel( new comboBoxModel( /* this */ ) ), m_quantizeModel( new comboBoxModel( /* this */ ) ), m_noteLenModel( new comboBoxModel( /* this */ ) ), m_pattern( NULL ), m_currentPosition(), m_recording( FALSE ), m_currentNote( NULL ), m_action( NONE ), m_moveStartKey( 0 ), m_moveStartTick( 0 ), m_notesEditHeight( 100 ), m_ppt( DEFAULT_PR_PPT ), m_lenOfNewNotes( midiTime( 0, DefaultTicksPerTact/4 ) ), m_startKey( INITIAL_START_KEY ), m_lastKey( 0 ), m_editMode( DRAW ), m_scrollBack( FALSE ) { // init pixmaps if( s_whiteKeySmallPm == NULL ) { s_whiteKeySmallPm = new QPixmap( embed::getIconPixmap( "pr_white_key_small" ) ); } if( s_whiteKeyBigPm == NULL ) { s_whiteKeyBigPm = new QPixmap( embed::getIconPixmap( "pr_white_key_big" ) ); } if( s_blackKeyPm == NULL ) { s_blackKeyPm = new QPixmap( embed::getIconPixmap( "pr_black_key" ) ); } if( s_toolDraw == NULL ) { s_toolDraw = new QPixmap( embed::getIconPixmap( "edit_draw" ) ); } if( s_toolErase == NULL ) { s_toolErase= new QPixmap( embed::getIconPixmap( "edit_erase" ) ); } if( s_toolSelect == NULL ) { s_toolSelect = new QPixmap( embed::getIconPixmap( "edit_select" ) ); } if( s_toolMove == NULL ) { s_toolMove = new QPixmap( embed::getIconPixmap( "edit_move" ) ); } if( s_toolOpen == NULL ) { s_toolOpen = new QPixmap( embed::getIconPixmap( "automation" ) ); } // add time-line m_timeLine = new timeLine( WHITE_KEY_WIDTH, 32, m_ppt, engine::getSong()->getPlayPos( song::Mode_PlayPattern ), m_currentPosition, this ); connect( this, SIGNAL( positionChanged( const midiTime & ) ), m_timeLine, SLOT( updatePosition( const midiTime & ) ) ); connect( m_timeLine, SIGNAL( positionChanged( const midiTime & ) ), this, SLOT( updatePosition( const midiTime & ) ) ); m_toolBar = new QWidget( this ); m_toolBar->setFixedHeight( 32 ); m_toolBar->move( 0, 0 ); m_toolBar->setAutoFillBackground( TRUE ); QPalette pal; pal.setBrush( m_toolBar->backgroundRole(), embed::getIconPixmap( "toolbar_bg" ) ); m_toolBar->setPalette( pal ); QHBoxLayout * tb_layout = new QHBoxLayout( m_toolBar ); tb_layout->setMargin( 0 ); tb_layout->setSpacing( 0 ); // init control-buttons at the top m_playButton = new toolButton( embed::getIconPixmap( "play" ), tr( "Play/pause current pattern (Space)" ), this, SLOT( play() ), m_toolBar ); m_recordButton = new toolButton( embed::getIconPixmap( "record" ), tr( "Record notes from MIDI-device/channel-piano" ), this, SLOT( record() ), m_toolBar ); m_recordAccompanyButton = new toolButton( embed::getIconPixmap( "record_accompany" ), tr( "Record notes from MIDI-device/channel-piano while playing song or BB track" ), this, SLOT( recordAccompany() ), m_toolBar ); m_stopButton = new toolButton( embed::getIconPixmap( "stop" ), tr( "Stop playing of current pattern (Space)" ), this, SLOT( stop() ), m_toolBar ); m_playButton->setWhatsThis( tr( "Click here, if you want to play the current pattern. " "This is useful while editing it. The pattern is " "automatically looped when its end is reached." ) ); m_recordButton->setWhatsThis( tr( "Click here, if you want to record notes from a MIDI-" "device or the virtual test-piano of the according " "channel-window to the current pattern. When recording " "all notes you play will be written to this pattern " "and you can play and edit them afterwards." ) ); m_recordAccompanyButton->setWhatsThis( tr( "Click here, if you want to record notes from a MIDI-" "device or the virtual test-piano of the according " "channel-window to the current pattern. When recording " "all notes you play will be written to this pattern " "and you will hear the song or BB track in the background." ) ); m_stopButton->setWhatsThis( tr( "Click here, if you want to stop playing of current " "pattern." ) ); removeSelection(); // init scrollbars m_leftRightScroll = new QScrollBar( Qt::Horizontal, this ); m_leftRightScroll->setSingleStep( 1 ); connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ), this, SLOT( horScrolled( int ) ) ); m_topBottomScroll = new QScrollBar( Qt::Vertical, this ); m_topBottomScroll->setSingleStep( 1 ); m_topBottomScroll->setPageStep( 20 ); connect( m_topBottomScroll, SIGNAL( valueChanged( int ) ), this, SLOT( verScrolled( int ) ) ); // init edit-buttons at the top m_drawButton = new toolButton( embed::getIconPixmap( "edit_draw" ), tr( "Draw mode (Shift+D)" ), this, SLOT( drawButtonToggled() ), m_toolBar ); m_drawButton->setCheckable( TRUE ); m_drawButton->setChecked( TRUE ); m_eraseButton = new toolButton( embed::getIconPixmap( "edit_erase" ), tr( "Erase mode (Shift+E)" ), this, SLOT( eraseButtonToggled() ), m_toolBar ); m_eraseButton->setCheckable( TRUE ); m_selectButton = new toolButton( embed::getIconPixmap( "edit_select" ), tr( "Select mode (Shift+S)" ), this, SLOT( selectButtonToggled() ), m_toolBar ); m_selectButton->setCheckable( TRUE ); m_moveButton = new toolButton( embed::getIconPixmap( "edit_move" ), tr( "Move selection mode (Shift+M)" ), this, SLOT( moveButtonToggled() ), m_toolBar ); m_moveButton->setCheckable( TRUE ); QButtonGroup * tool_button_group = new QButtonGroup( this ); tool_button_group->addButton( m_drawButton ); tool_button_group->addButton( m_eraseButton ); tool_button_group->addButton( m_selectButton ); tool_button_group->addButton( m_moveButton ); tool_button_group->setExclusive( TRUE ); m_drawButton->setWhatsThis( tr( "If you click here, draw-mode will be activated. In this " "mode you can add, resize and move single notes. This " "is the default-mode which is used most of the time. " "You can also press 'Shift+D' on your keyboard to " "activate this mode." ) ); m_eraseButton->setWhatsThis( tr( "If you click here, erase-mode will be activated. In this " "mode you can erase single notes. You can also press " "'Shift+E' on your keyboard to activate this mode." ) ); m_selectButton->setWhatsThis( tr( "If you click here, select-mode will be activated. " "In this mode you can select notes. This is neccessary " "if you want to cut, copy, paste, delete or move " "notes. You can also press 'Shift+S' on your keyboard " "to activate this mode." ) ); m_moveButton->setWhatsThis( tr( "If you click here, move-mode will be activated. In this " "mode you can move the notes you selected in select-" "mode. You can also press 'Shift+M' on your keyboard " "to activate this mode." ) ); m_cutButton = new toolButton( embed::getIconPixmap( "edit_cut" ), tr( "Cut selected notes (Ctrl+X)" ), this, SLOT( cutSelectedNotes() ), m_toolBar ); m_copyButton = new toolButton( embed::getIconPixmap( "edit_copy" ), tr( "Copy selected notes (Ctrl+C)" ), this, SLOT( copySelectedNotes() ), m_toolBar ); m_pasteButton = new toolButton( embed::getIconPixmap( "edit_paste" ), tr( "Paste notes from clipboard " "(Ctrl+V)" ), this, SLOT( pasteNotes() ), m_toolBar ); m_cutButton->setWhatsThis( tr( "If you click here, selected notes will be cut into the " "clipboard. You can paste them anywhere in any pattern " "by clicking on the paste-button." ) ); m_copyButton->setWhatsThis( tr( "If you click here, selected notes will be copied into the " "clipboard. You can paste them anywhere in any pattern " "by clicking on the paste-button." ) ); m_pasteButton->setWhatsThis( tr( "If you click here, the notes from the clipboard will be " "pasted at the first visible tact." ) ); QLabel * zoom_lbl = new QLabel( m_toolBar ); zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) ); // setup zooming-stuff for( int i = 0; i < 6; ++i ) { m_zoomingModel->addItem( QString::number( 25 << i ) + "%" ); } m_zoomingModel->setValue( m_zoomingModel->findText( "100%" ) ); connect( m_zoomingModel, SIGNAL( dataChanged() ), this, SLOT( zoomingChanged() ) ); m_zoomingComboBox = new comboBox( m_toolBar ); m_zoomingComboBox->setModel( m_zoomingModel ); m_zoomingComboBox->setFixedSize( 80, 22 ); // setup quantize-stuff QLabel * quantize_lbl = new QLabel( m_toolBar ); quantize_lbl->setPixmap( embed::getIconPixmap( "quantize" ) ); for( int i = 0; i < 7; ++i ) { m_quantizeModel->addItem( "1/" + QString::number( 1 << i ) ); } m_quantizeModel->addItem( "1/192" ); m_quantizeModel->setValue( m_quantizeModel->findText( "1/16" ) ); m_quantizeComboBox = new comboBox( m_toolBar ); m_quantizeComboBox->setModel( m_quantizeModel ); m_quantizeComboBox->setFixedSize( 60, 22 ); // setup note-len-stuff QLabel * note_len_lbl = new QLabel( m_toolBar ); note_len_lbl->setPixmap( embed::getIconPixmap( "note" ) ); m_noteLenModel->addItem( tr( "Last note" ), new pixmapLoader( "edit_draw" ) ); const QString pixmaps[] = { "whole", "half", "quarter", "eighth", "sixteenth", "thirtysecond" } ; for( int i = 0; i < 6; ++i ) { m_noteLenModel->addItem( "1/" + QString::number( 1 << i ), new pixmapLoader( "note_" + pixmaps[i] ) ); } m_noteLenModel->addItem( "1/192" ); m_noteLenModel->setValue( 0 ); m_noteLenComboBox = new comboBox( m_toolBar ); m_noteLenComboBox->setModel( m_noteLenModel ); m_noteLenComboBox->setFixedSize( 120, 22 ); tb_layout->addSpacing( 5 ); tb_layout->addWidget( m_playButton ); tb_layout->addWidget( m_recordButton ); tb_layout->addWidget( m_recordAccompanyButton ); tb_layout->addWidget( m_stopButton ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_drawButton ); tb_layout->addWidget( m_eraseButton ); tb_layout->addWidget( m_selectButton ); tb_layout->addWidget( m_moveButton ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_cutButton ); tb_layout->addWidget( m_copyButton ); tb_layout->addWidget( m_pasteButton ); tb_layout->addSpacing( 10 ); m_timeLine->addToolButtons( m_toolBar ); tb_layout->addSpacing( 15 ); tb_layout->addWidget( zoom_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_zoomingComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( quantize_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_quantizeComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( note_len_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_noteLenComboBox ); tb_layout->addStretch(); // setup our actual window setFocusPolicy( Qt::StrongFocus ); setFocus(); setWindowIcon( embed::getIconPixmap( "piano" ) ); setCurrentPattern( NULL ); setMouseTracking( TRUE ); // add us to workspace if( engine::getMainWindow()->workspace() ) { engine::getMainWindow()->workspace()->addSubWindow( this ); parentWidget()->resize( INITIAL_PIANOROLL_WIDTH, INITIAL_PIANOROLL_HEIGHT ); parentWidget()->hide(); } else { resize( INITIAL_PIANOROLL_WIDTH, INITIAL_PIANOROLL_HEIGHT ); hide(); } connect( engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ), this, SLOT( update() ) ); } pianoRoll::~pianoRoll() { } void pianoRoll::setCurrentPattern( pattern * _new_pattern ) { m_pattern = _new_pattern; m_currentPosition = 0; m_currentNote = NULL; m_startKey = INITIAL_START_KEY; if( validPattern() == FALSE ) { //resizeEvent( NULL ); setWindowTitle( tr( "Piano-Roll - no pattern" ) ); update(); return; } m_leftRightScroll->setValue( 0 ); const noteVector & notes = m_pattern->notes(); int central_key = 0; if( notes.empty() == FALSE ) { // determine the central key so that we can scroll to it int total_notes = 0; for( noteVector::const_iterator it = notes.begin(); it != notes.end(); ++it ) { if( ( *it )->length() > 0 ) { central_key += ( *it )->key(); ++total_notes; } } if( total_notes > 0 ) { central_key = central_key / total_notes - ( KeysPerOctave * NumOctaves - m_totalKeysToScroll ) / 2; m_startKey = tLimit( central_key, 0, NumOctaves * KeysPerOctave ); } } // resizeEvent() does the rest for us (scrolling, range-checking // of start-notes and so on...) resizeEvent( NULL ); // remove all connections to other instrument-tracks disconnect( this, SLOT( recordNote( const note & ) ) ); // and now connect to noteDone()-signal of channel so that // we receive note-off-events from it's midi-port for recording it connect( m_pattern->getInstrumentTrack(), SIGNAL( noteDone( const note & ) ), this, SLOT( recordNote( const note & ) ) ); setWindowTitle( tr( "Piano-Roll - %1" ).arg( m_pattern->name() ) ); update(); } void pianoRoll::saveSettings( QDomDocument & _doc, QDomElement & _this ) { mainWindow::saveWidgetState( this, _this ); } void pianoRoll::loadSettings( const QDomElement & _this ) { mainWindow::restoreWidgetState( this, _this ); } inline void pianoRoll::drawNoteRect( QPainter & _p, int _x, int _y, int _width, const bool _is_selected, const bool _is_step_note ) { ++_x; ++_y; _width -= 2; if( _width <= 0 ) { _width = 2; } QColor current_color( 0xFF, 0xB0, 0x00 ); if( _is_step_note == TRUE ) { current_color.setRgb( 0, 255, 0 ); } else if( _is_selected == TRUE ) { current_color.setRgb( 0x00, 0x40, 0xC0 ); } _p.fillRect( _x, _y, _width, KEY_LINE_HEIGHT - 2, current_color ); _p.setPen( QColor( 0xFF, 0xDF, 0x20 ) ); _p.drawLine( _x, _y, _x + _width, _y ); _p.drawLine( _x, _y, _x, _y + KEY_LINE_HEIGHT - 2 ); _p.setPen( QColor( 0xFF, 0x9F, 0x00 ) ); _p.drawLine( _x + _width, _y, _x + _width, _y + KEY_LINE_HEIGHT - 2 ); _p.drawLine( _x, _y + KEY_LINE_HEIGHT - 2, _x + _width, _y + KEY_LINE_HEIGHT - 2 ); _p.setPen( QColor( 0xFF, 0xFF, 0x40 ) ); if( _width > 2 ) { _p.drawLine( _x + _width - 3, _y + 2, _x + _width - 3, _y + KEY_LINE_HEIGHT - 4 ); } _p.drawLine( _x + _width - 1, _y + 2, _x + _width - 1, _y + KEY_LINE_HEIGHT - 4 ); _p.drawLine( _x + _width - 2, _y + 2, _x + _width - 2, _y + KEY_LINE_HEIGHT - 4 ); } inline void pianoRoll::drawDetuningInfo( QPainter & _p, note * _n, int _x, int _y ) { int middle_y = _y + KEY_LINE_HEIGHT / 2; _p.setPen( QColor( 0xFF, 0xDF, 0x20 ) ); timeMap & map = _n->detuning()->getAutomationPattern()->getTimeMap(); timeMap::iterator it = map.end(); do { --it; Sint32 pos_ticks = -it.key(); if( pos_ticks > _n->length() ) { break; } int pos_x = _x + pos_ticks * m_ppt / midiTime::ticksPerTact(); const float level = it.value(); int pos_y = (int)( middle_y - level * KEY_LINE_HEIGHT / 10 ); _p.drawLine( pos_x - 1, pos_y, pos_x + 1, pos_y ); _p.drawLine( pos_x, pos_y - 1, pos_x, pos_y + 1 ); } while( it != map.begin() ); } void pianoRoll::removeSelection( void ) { m_selectStartTick = 0; m_selectedTick = 0; m_selectStartKey = 0; m_selectedKeys = 0; } void pianoRoll::closeEvent( QCloseEvent * _ce ) { QApplication::restoreOverrideCursor(); if( parentWidget() ) { parentWidget()->hide(); } else { hide(); } _ce->ignore(); } void pianoRoll::keyPressEvent( QKeyEvent * _ke ) { if( validPattern() && _ke->modifiers() == Qt::NoModifier ) { int key_num = pianoView::getKeyFromScancode( _ke->nativeScanCode() ) + ( DefaultOctave - 1 ) * KeysPerOctave; if( _ke->isAutoRepeat() == FALSE && key_num > -1 ) { m_pattern->getInstrumentTrack()-> getPiano()->handleKeyPress( key_num ); } } switch( _ke->key() ) { case Qt::Key_Up: m_topBottomScroll->setValue( m_topBottomScroll->value() - 1 ); break; case Qt::Key_Down: m_topBottomScroll->setValue( m_topBottomScroll->value() + 1 ); break; case Qt::Key_Left: { if( ( m_timeLine->pos() -= 16 ) < 0 ) { m_timeLine->pos().setTicks( 0 ); } m_timeLine->updatePosition(); break; } case Qt::Key_Right: { m_timeLine->pos() += 16; m_timeLine->updatePosition(); break; } case Qt::Key_C: if( _ke->modifiers() & Qt::ControlModifier ) { copySelectedNotes(); } else { _ke->ignore(); } break; case Qt::Key_X: if( _ke->modifiers() & Qt::ControlModifier ) { cutSelectedNotes(); } else { _ke->ignore(); } break; case Qt::Key_V: if( _ke->modifiers() & Qt::ControlModifier ) { pasteNotes(); } else { _ke->ignore(); } break; case Qt::Key_A: if( _ke->modifiers() & Qt::ControlModifier ) { m_selectButton->setChecked( TRUE ); selectAll(); update(); } else { _ke->ignore(); } break; case Qt::Key_D: if( _ke->modifiers() & Qt::ShiftModifier ) { m_drawButton->setChecked( TRUE ); } else { _ke->ignore(); } break; case Qt::Key_E: if( _ke->modifiers() & Qt::ShiftModifier ) { m_eraseButton->setChecked( TRUE ); } else { _ke->ignore(); } break; case Qt::Key_S: if( _ke->modifiers() & Qt::ShiftModifier ) { m_selectButton->setChecked( TRUE ); } else { _ke->ignore(); } break; case Qt::Key_M: if( _ke->modifiers() & Qt::ShiftModifier ) { m_moveButton->setChecked( TRUE ); } else { _ke->ignore(); } break; case Qt::Key_Delete: deleteSelectedNotes(); break; case Qt::Key_Space: if( engine::getSong()->isPlaying() ) { stop(); } else { play(); } break; case Qt::Key_Home: m_timeLine->pos().setTicks( 0 ); m_timeLine->updatePosition(); break; case Qt::Key_Control: if( mouseOverNote() ) { m_editMode = OPEN; QApplication::changeOverrideCursor( QCursor( Qt::ArrowCursor ) ); update(); } default: _ke->ignore(); break; } } void pianoRoll::keyReleaseEvent( QKeyEvent * _ke ) { if( validPattern() && _ke->modifiers() == Qt::NoModifier ) { int key_num = pianoView::getKeyFromScancode( _ke->nativeScanCode() ) + ( DefaultOctave - 1 ) * KeysPerOctave; if( _ke->isAutoRepeat() == FALSE && key_num > -1 ) { m_pattern->getInstrumentTrack()-> getPiano()->handleKeyRelease( key_num ); } } switch( _ke->key() ) { case Qt::Key_Control: if( m_editMode == OPEN ) { m_editMode = DRAW; update(); } } _ke->ignore(); } void pianoRoll::leaveEvent( QEvent * _e ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QWidget::leaveEvent( _e ); } void pianoRoll::mousePressEvent( QMouseEvent * _me ) { if( validPattern() == FALSE ) { return; } if( m_editMode == OPEN && noteUnderMouse() ) { noteUnderMouse()->editDetuningPattern(); return; } if( _me->y() > PR_TOP_MARGIN ) { bool play_note = TRUE; volume vol = DefaultVolume; bool edit_note = ( _me->y() > height() - PR_BOTTOM_MARGIN - m_notesEditHeight ); int key_num = getKey( _me->y() ); int x = _me->x(); if( x > WHITE_KEY_WIDTH ) { // set, move or resize note x -= WHITE_KEY_WIDTH; // get tick in which the user clicked int pos_ticks = x * midiTime::ticksPerTact() / m_ppt + m_currentPosition; // get note-vector of current pattern const noteVector & notes = m_pattern->notes(); // will be our iterator in the following loop noteVector::const_iterator it = notes.begin(); // loop through whole note-vector... while( it != notes.end() ) { midiTime len = ( *it )->length(); if( len < 0 ) { len = 4; } // and check whether the user clicked on an // existing note or an edit-line if( pos_ticks >= ( *it )->pos() && len > 0 && ( ( edit_note == FALSE && pos_ticks <= ( *it )->pos() + len && ( *it )->key() == key_num ) || ( edit_note == TRUE && pos_ticks <= ( *it )->pos() + NE_LINE_WIDTH * midiTime::ticksPerTact() / m_ppt ) ) ) { break; } ++it; } // first check whether the user clicked in note-edit- // area if( edit_note == TRUE ) { if( it != notes.end() ) { vol = 2 * ( -_me->y() + height() - PR_BOTTOM_MARGIN ); int shortVolumeDiff = MaxVolume - MinVolume; noteVector::const_iterator jt = notes.begin(); while( jt != notes.end() ) { if( (*jt)->pos() == (*it)->pos() && (*jt)->length().getTicks() > 0 ) { int volDiff = abs( vol - (*jt)->getVolume() ); if( volDiff <= shortVolumeDiff ) { shortVolumeDiff = volDiff; it = jt; } } ++jt; } ( *it )->setVolume( vol ); m_currentNote = *it; m_action = CHANGE_NOTE_VOLUME; key_num = ( *it )->key(); } else { play_note = FALSE; } } // left button?? else if( _me->button() == Qt::LeftButton && m_editMode == DRAW ) { // did it reach end of vector because // there's no note?? if( it == notes.end() ) { m_pattern->setType( pattern::MelodyPattern ); // then set new note // +32 to quanitize the note correctly when placing notes with // the mouse. We do this here instead of in note.quantized // because live notes should still be quantized at the half. midiTime note_pos( pos_ticks - ( quantization() / 2 ) ); midiTime note_len( newNoteLen() ); note new_note( note_len, note_pos, key_num ); note * created_new_note = m_pattern->addNote( new_note ); // reset it so that it can be used for // ops (move, resize) after this // code-block it = notes.begin(); while( it != notes.end() && *it != created_new_note ) { ++it; } } m_currentNote = *it; // clicked at the "tail" of the note? if( pos_ticks*m_ppt/midiTime::ticksPerTact() > ( m_currentNote->pos() + m_currentNote->length() )*m_ppt/ midiTime::ticksPerTact() - RESIZE_AREA_WIDTH && m_currentNote->length() > 0 ) { // then resize the note m_action = RESIZE_NOTE; // set resize-cursor QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); play_note = FALSE; } else { // otherwise move it m_action = MOVE_NOTE; int aligned_x = (int)( (float)( ( m_currentNote->pos() - m_currentPosition ) * m_ppt ) / midiTime::ticksPerTact() ); m_moveXOffset = x - aligned_x - 1; // set move-cursor QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); } engine::getSong()->setModified(); } else if( ( _me->button() == Qt::RightButton && m_editMode == DRAW ) || m_editMode == ERASE ) { // erase single note play_note = FALSE; if( it != notes.end() ) { if( ( *it )->length() > 0 ) { m_pattern->removeNote( *it ); } else { ( *it )->setLength( 0 ); m_pattern->dataChanged(); } engine::getSong()->setModified(); } } else if( _me->button() == Qt::LeftButton && m_editMode == SELECT ) { // select an area of notes m_selectStartTick = pos_ticks; m_selectedTick = 0; m_selectStartKey = key_num; m_selectedKeys = 1; m_action = SELECT_NOTES; play_note = FALSE; } else if( _me->button() == Qt::RightButton && m_editMode == SELECT ) { // when clicking right in select-move, we // switch to move-mode m_moveButton->setChecked( TRUE ); play_note = FALSE; } else if( _me->button() == Qt::LeftButton && m_editMode == MOVE ) { // move selection (including selected notes) // save position where move-process began m_moveStartTick = pos_ticks; m_moveStartKey = key_num; m_action = MOVE_SELECTION; play_note = FALSE; engine::getSong()->setModified(); } else if( _me->button() == Qt::RightButton && m_editMode == MOVE ) { // when clicking right in select-move, we // switch to draw-mode m_drawButton->setChecked( TRUE ); play_note = FALSE; } update(); } // was there an action where should be played the note? if( play_note == TRUE && m_recording == FALSE && engine::getSong()->isPlaying() == FALSE ) { m_lastKey = key_num; m_pattern->getInstrumentTrack()->processInEvent( midiEvent( MidiNoteOn, 0, key_num, vol * 127 / 100 ), midiTime() ); } } } void pianoRoll::mouseReleaseEvent( QMouseEvent * _me ) { if( validPattern() == TRUE ) { if( m_action == CHANGE_NOTE_VOLUME && m_currentNote != NULL ) { m_pattern->getInstrumentTrack()->processInEvent( midiEvent( MidiNoteOff, 0, m_currentNote->key(), 0 ), midiTime() ); } else { m_pattern->getInstrumentTrack()->processInEvent( midiEvent( MidiNoteOff, 0, m_lastKey, 0 ), midiTime() ); } } m_currentNote = NULL; m_action = NONE; if( m_editMode == DRAW ) { QApplication::restoreOverrideCursor(); } } void pianoRoll::mouseMoveEvent( QMouseEvent * _me ) { if( validPattern() == FALSE ) { update(); return; } if( _me->y() > PR_TOP_MARGIN ) { bool edit_note = ( _me->y() > height() - PR_BOTTOM_MARGIN - m_notesEditHeight ); int key_num = getKey( _me->y() ); int x = _me->x(); // is the calculated key different from current key? // (could be the user just moved the cursor one pixel up/down // but is still on the same key) if( key_num != m_lastKey && m_action != CHANGE_NOTE_VOLUME && m_action != MOVE_SELECTION && edit_note == FALSE && _me->buttons() & Qt::LeftButton ) { m_pattern->getInstrumentTrack()->processInEvent( midiEvent( MidiNoteOff, 0, m_lastKey, 0 ), midiTime() ); if( _me->buttons() & Qt::LeftButton && m_action != RESIZE_NOTE && m_action != SELECT_NOTES && m_action != MOVE_SELECTION && m_recording == FALSE && engine::getSong()->isPlaying() == FALSE ) { m_lastKey = key_num; m_pattern->getInstrumentTrack()->processInEvent( midiEvent( MidiNoteOn, 0, key_num, DefaultVolume * 127 / 100 ), midiTime() ); } } if( _me->x() < WHITE_KEY_WIDTH ) { update(); return; } x -= WHITE_KEY_WIDTH; // Volume Bars if( ( edit_note == TRUE || m_action == CHANGE_NOTE_VOLUME ) && _me->buttons() & Qt::LeftButton ) { // Use nearest-note when changing volume so the bars can // be "scribbled" int pos_ticks = ( x * midiTime::ticksPerTact() ) / m_ppt + m_currentPosition; // get note-vector of current pattern const noteVector & notes = m_pattern->notes(); note * shortNote = NULL; // Max "snap length" 1/8 note on either side int shortDistance = DefaultTicksPerTact/8; // loop through vector to find nearest note noteVector::const_iterator it = notes.begin(); while( it != notes.end() ) { int tmp = abs( pos_ticks - (int)( (*it)->pos() ) ); if( tmp <= shortDistance && (*it)->length().getTicks() > 0 ) { shortDistance = tmp; shortNote = *it ; } ++it; } volume vol = tLimit( 2 * ( -_me->y() + height() - PR_BOTTOM_MARGIN ), MinVolume, MaxVolume ); if( shortNote ) { // have short length - now check for volume difference and currentNote it = notes.begin(); int shortNotePos = shortNote->pos(); int shortVolumeDiff = MaxVolume-MinVolume; while( it != notes.end() ) { if( (*it)->pos() == shortNotePos && (*it)->length().getTicks() > 0 ) { if( *it == m_currentNote ) { shortNote = m_currentNote; break; } int volDiff = abs( vol - (*it)->getVolume() ); if( volDiff <= shortVolumeDiff ) { shortVolumeDiff = volDiff; shortNote = *it; } } ++it; } } if( shortNote != m_currentNote && engine::getSong()->isPlaying() == FALSE ) { if( m_currentNote != NULL ) { m_pattern->getInstrumentTrack()->processInEvent( midiEvent( MidiNoteOff, 0, m_currentNote->key(), 0 ), midiTime() ); } if( shortNote != NULL ) { m_lastKey = shortNote->key(); m_pattern->getInstrumentTrack()->processInEvent( midiEvent( MidiNoteOn, 0, shortNote->key(), shortNote->getVolume() ), midiTime() ); } } m_currentNote = shortNote; if( m_currentNote != NULL ) { m_currentNote->setVolume( vol ); m_pattern->dataChanged(); m_pattern->getInstrumentTrack()->processInEvent( midiEvent( MidiKeyPressure, 0, m_lastKey, vol * 127 / 100 ), midiTime() ); } } else if( m_currentNote != NULL && _me->buttons() & Qt::LeftButton && m_editMode == DRAW ) { int key_num = getKey( _me->y() ); if( m_action == MOVE_NOTE ) { x -= m_moveXOffset; } int pos_ticks = x * midiTime::ticksPerTact() / m_ppt + m_currentPosition; if( m_action == MOVE_NOTE ) { // moving note if( pos_ticks < 0 ) { pos_ticks = 0; } m_currentNote->setPos( midiTime( pos_ticks ) ); m_currentNote->setKey( key_num ); // we moved the note so the note has to be // moved properly according to new starting- // time in the note-array of pattern m_currentNote = m_pattern->rearrangeNote( m_currentNote ); } else { // resizing note int ticks_diff = pos_ticks - m_currentNote->pos(); if( ticks_diff <= 0 ) { ticks_diff = 1; } m_currentNote->setLength( midiTime( ticks_diff ) ); m_currentNote->quantizeLength( quantization() ); m_lenOfNewNotes = m_currentNote->length(); m_pattern->dataChanged(); } engine::getSong()->setModified(); } else if( _me->buttons() == Qt::NoButton && m_editMode == DRAW ) { // set move- or resize-cursor // get tick in which the cursor is posated int pos_ticks = ( x * midiTime::ticksPerTact() ) / m_ppt + m_currentPosition; // get note-vector of current pattern const noteVector & notes = m_pattern->notes(); // will be our iterator in the following loop noteVector::const_iterator it = notes.begin(); // loop through whole note-vector... while( it != notes.end() ) { // and check whether the cursor is over an // existing note if( pos_ticks >= ( *it )->pos() && pos_ticks <= ( *it )->pos() + ( *it )->length() && ( *it )->key() == key_num && ( *it )->length() > 0 ) { break; } ++it; } // did it reach end of vector because there's // no note?? if( it != notes.end() ) { if( _me->modifiers() & Qt::ControlModifier ) { m_editMode = OPEN; QApplication::changeOverrideCursor( QCursor( Qt::ArrowCursor ) ); } // cursor at the "tail" of the note? else if( ( *it )->length() > 0 && pos_ticks*m_ppt / midiTime::ticksPerTact() > ( ( *it )->pos() + ( *it )->length() )*m_ppt/ midiTime::ticksPerTact()- RESIZE_AREA_WIDTH ) { if( QApplication::overrideCursor() ) { if( QApplication::overrideCursor()->shape() != Qt::SizeHorCursor ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); } } else { QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); } } else { if( QApplication::overrideCursor() ) { if( QApplication::overrideCursor()->shape() != Qt::SizeAllCursor ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); } } else { QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); } } } else { // the cursor is over no note, so restore cursor while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } } } else if( _me->buttons() & Qt::LeftButton && m_editMode == SELECT && m_action == SELECT_NOTES ) { // change size of selection if( x < 0 && m_currentPosition > 0 ) { x = 0; QCursor::setPos( mapToGlobal( QPoint( WHITE_KEY_WIDTH, _me->y() ) ) ); if( m_currentPosition >= 4 ) { m_leftRightScroll->setValue( m_currentPosition - 4 ); } else { m_leftRightScroll->setValue( 0 ); } } else if( x > width() - WHITE_KEY_WIDTH ) { x = width() - WHITE_KEY_WIDTH; QCursor::setPos( mapToGlobal( QPoint( width(), _me->y() ) ) ); m_leftRightScroll->setValue( m_currentPosition + 4 ); } // get tick in which the cursor is posated int pos_ticks = x * midiTime::ticksPerTact() / m_ppt + m_currentPosition; m_selectedTick = pos_ticks - m_selectStartTick; if( (int) m_selectStartTick + m_selectedTick < 0 ) { m_selectedTick = -static_cast( m_selectStartTick ); } m_selectedKeys = key_num - m_selectStartKey; if( key_num <= m_selectStartKey ) { --m_selectedKeys; } } else if( _me->buttons() & Qt::LeftButton && m_editMode == MOVE && m_action == MOVE_SELECTION ) { // move selection + selected notes // do horizontal move-stuff int pos_ticks = x * midiTime::ticksPerTact() / m_ppt + m_currentPosition; int ticks_diff = pos_ticks - m_moveStartTick; if( m_selectedTick > 0 ) { if( (int) m_selectStartTick + ticks_diff < 0 ) { ticks_diff = -m_selectStartTick; } } else { if( (int) m_selectStartTick + m_selectedTick + ticks_diff < 0 ) { ticks_diff = -( m_selectStartTick + m_selectedTick ); } } m_selectStartTick += ticks_diff; int tact_diff = ticks_diff / midiTime::ticksPerTact(); ticks_diff = ticks_diff % midiTime::ticksPerTact(); // do vertical move-stuff int key_diff = key_num - m_moveStartKey; if( m_selectedKeys > 0 ) { if( m_selectStartKey + key_diff < -1 ) { key_diff = -m_selectStartKey - 1; } else if( m_selectStartKey + m_selectedKeys + key_diff >= KeysPerOctave * NumOctaves ) { key_diff = KeysPerOctave * NumOctaves - ( m_selectStartKey + m_selectedKeys ) - 1; } } else { if( m_selectStartKey + m_selectedKeys + key_diff < -1 ) { key_diff = -( m_selectStartKey + m_selectedKeys ) - 1; } else if( m_selectStartKey + key_diff >= KeysPerOctave * NumOctaves ) { key_diff = KeysPerOctave * NumOctaves - m_selectStartKey - 1; } } m_selectStartKey += key_diff; for( noteVector::iterator it = m_selNotesForMove.begin(); it != m_selNotesForMove.end(); ++it ) { int note_tact = ( *it )->pos().getTact() + tact_diff; int note_ticks = ( *it )->pos().getTicks() + ticks_diff; // ensure note_ticks range if( note_ticks > midiTime::ticksPerTact() ) { note_tact += (note_ticks/midiTime::ticksPerTact() ); note_ticks %= midiTime::ticksPerTact(); } /* Old 1/64th code if( note_ticks >> 6 ) { note_tact += note_ticks >> 6; note_ticks &= 63; } */ midiTime new_note_pos( note_tact, note_ticks ); ( *it )->setPos( new_note_pos ); ( *it )->setKey( ( *it )->key() + key_diff ); *it = m_pattern->rearrangeNote( *it, FALSE ); } m_moveStartTick = pos_ticks; m_moveStartKey = key_num; } else if( m_editMode == OPEN && !( mouseOverNote() && _me->modifiers() & Qt::ControlModifier ) ) { m_editMode = DRAW; } } else { if( _me->buttons() & Qt::LeftButton && m_editMode == SELECT && m_action == SELECT_NOTES ) { int x = _me->x() - WHITE_KEY_WIDTH; if( x < 0 && m_currentPosition > 0 ) { x = 0; QCursor::setPos( mapToGlobal( QPoint( WHITE_KEY_WIDTH, _me->y() ) ) ); if( m_currentPosition >= 4 ) { m_leftRightScroll->setValue( m_currentPosition - 4 ); } else { m_leftRightScroll->setValue( 0 ); } } else if( x > width() - WHITE_KEY_WIDTH ) { x = width() - WHITE_KEY_WIDTH; QCursor::setPos( mapToGlobal( QPoint( width(), _me->y() ) ) ); m_leftRightScroll->setValue( m_currentPosition + 4 ); } // get tick in which the cursor is posated int pos_ticks = x * midiTime::ticksPerTact()/ m_ppt + m_currentPosition; m_selectedTick = pos_ticks - m_selectStartTick; if( (int) m_selectStartTick + m_selectedTick < 0 ) { m_selectedTick = -static_cast( m_selectStartTick ); } int key_num = getKey( _me->y() ); int visible_keys = ( height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN - m_notesEditHeight ) / KEY_LINE_HEIGHT + 2; const int s_key = m_startKey - 1; if( key_num <= s_key ) { QCursor::setPos( mapToGlobal( QPoint( _me->x(), height() - PR_BOTTOM_MARGIN - m_notesEditHeight ) ) ); m_topBottomScroll->setValue( m_topBottomScroll->value() + 1 ); key_num = s_key; } else if( key_num >= s_key + visible_keys ) { QCursor::setPos( mapToGlobal( QPoint( _me->x(), PR_TOP_MARGIN ) ) ); m_topBottomScroll->setValue( m_topBottomScroll->value() - 1 ); key_num = s_key + visible_keys; } m_selectedKeys = key_num - m_selectStartKey; if( key_num <= m_selectStartKey ) { --m_selectedKeys; } } QApplication::restoreOverrideCursor(); } update(); } void pianoRoll::paintEvent( QPaintEvent * _pe ) { QStyleOption opt; opt.initFrom( this ); QPainter p( this ); style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this ); // set font-size to 8 p.setFont( pointSize<8>( p.font() ) ); // y_offset is used to align the piano-keys on the key-lines int y_offset = 0; // calculate y_offset according to first key switch( prKeyOrder[m_startKey % KeysPerOctave] ) { case PR_BLACK_KEY: y_offset = KEY_LINE_HEIGHT/4; break; case PR_WHITE_KEY_BIG: y_offset = KEY_LINE_HEIGHT/2; break; case PR_WHITE_KEY_SMALL: if( prKeyOrder[( ( m_startKey + 1 ) % KeysPerOctave)] != PR_BLACK_KEY ) { y_offset = KEY_LINE_HEIGHT / 2; } break; } // start drawing at the bottom int key_line_y = height() - PR_BOTTOM_MARGIN - m_notesEditHeight - 1; // used for aligning black-keys later int first_white_key_height = WHITE_KEY_SMALL_HEIGHT; // key-counter - only needed for finding out whether the processed // key is the first one int keys_processed = 0; int key = m_startKey; // draw all white keys... for( int y = key_line_y + 1 + y_offset; y > PR_TOP_MARGIN; key_line_y -= KEY_LINE_HEIGHT, ++keys_processed ) { // check for white key that is only half visible on the // bottom of piano-roll if( keys_processed == 0 && prKeyOrder[m_startKey % KeysPerOctave] == PR_BLACK_KEY ) { // draw it! p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT, *s_whiteKeySmallPm ); // update y-pos y -= WHITE_KEY_SMALL_HEIGHT / 2; // move first black key down (we didn't draw whole // white key so black key needs to be lifted down) // (default for first_white_key_height = // WHITE_KEY_SMALL_HEIGHT, so WHITE_KEY_SMALL_HEIGHT/2 // is smaller) first_white_key_height = WHITE_KEY_SMALL_HEIGHT / 2; } // check whether to draw a big or a small white key if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_SMALL ) { // draw a small one... p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT, *s_whiteKeySmallPm ); // update y-pos y -= WHITE_KEY_SMALL_HEIGHT; } else if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_BIG ) { // draw a big one... p.drawPixmap( PIANO_X, y-WHITE_KEY_BIG_HEIGHT, *s_whiteKeyBigPm ); // if a big white key has been the first key, // black keys needs to be lifted up if( keys_processed == 0 ) { first_white_key_height = WHITE_KEY_BIG_HEIGHT; } // update y-pos y -= WHITE_KEY_BIG_HEIGHT; } // label C-keys... if( static_cast( key % KeysPerOctave ) == Key_C ) { p.setPen( QColor( 240, 240, 240 ) ); p.drawText( C_KEY_LABEL_X + 1, y+14, "C" + QString::number( static_cast( key / KeysPerOctave ) ) ); p.setPen( QColor( 0, 0, 0 ) ); p.drawText( C_KEY_LABEL_X, y + 13, "C" + QString::number( static_cast( key / KeysPerOctave ) ) ); p.setPen( QColor( 0x4F, 0x4F, 0x4F ) ); } else { p.setPen( QColor( 0x3F, 0x3F, 0x3F ) ); } // draw key-line p.drawLine( WHITE_KEY_WIDTH, key_line_y, width(), key_line_y ); ++key; } // reset all values, because now we're going to draw all black keys key = m_startKey; keys_processed = 0; int white_cnt = 0; // and go! for( int y = height() - PR_BOTTOM_MARGIN - m_notesEditHeight + y_offset; y > PR_TOP_MARGIN; ++keys_processed ) { // check for black key that is only half visible on the bottom // of piano-roll if( keys_processed == 0 // current key may not be a black one && prKeyOrder[key % KeysPerOctave] != PR_BLACK_KEY // but the previous one must be black (we must check this // because there might be two white keys (E-F) && prKeyOrder[( key - 1 ) % KeysPerOctave] == PR_BLACK_KEY ) { // draw the black key! p.drawPixmap( PIANO_X, y - BLACK_KEY_HEIGHT / 2, *s_blackKeyPm ); // is the one after the start-note a black key?? if( prKeyOrder[( key + 1 ) % KeysPerOctave] != PR_BLACK_KEY ) { // no, then move it up! y -= KEY_LINE_HEIGHT / 2; } } // current key black? if( prKeyOrder[key % KeysPerOctave] == PR_BLACK_KEY ) { // then draw it (calculation of y very complicated, // but that's the only working solution, sorry...) p.drawPixmap( PIANO_X, y - ( first_white_key_height - WHITE_KEY_SMALL_HEIGHT ) - WHITE_KEY_SMALL_HEIGHT/2 - 1 - BLACK_KEY_HEIGHT, *s_blackKeyPm ); // update y-pos y -= WHITE_KEY_BIG_HEIGHT; // reset white-counter white_cnt = 0; } else { // simple workaround for increasing x if there were // two white keys (e.g. between E and F) ++white_cnt; if( white_cnt > 1 ) { y -= WHITE_KEY_BIG_HEIGHT/2; } } ++key; } // erase the area below the piano, because there might be keys that // should be only half-visible p.fillRect( QRect( 0, height() - PR_BOTTOM_MARGIN - m_notesEditHeight, WHITE_KEY_WIDTH, m_notesEditHeight ), QColor( 0, 0, 0 ) ); // set clipping area, because we are not allowed to paint over // keyboard... p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, width() - WHITE_KEY_WIDTH, height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN ); // draw vertical raster int tact_16th = m_currentPosition / DefaultBeatsPerTact; const int offset = ( m_currentPosition % DefaultBeatsPerTact ) * m_ppt / midiTime::ticksPerTact(); // we need float here as odd time signatures might produce rounding // errors else and thus an unusable grid for( float x = WHITE_KEY_WIDTH - offset; x < width(); x += (float) m_ppt / midiTime::stepsPerTact(), ++tact_16th ) { if( x >= WHITE_KEY_WIDTH ) { // every tact-start needs to be a bright line if( tact_16th % midiTime::stepsPerTact() == 0 ) { p.setPen( QColor( 0x7F, 0x7F, 0x7F ) ); } // normal line else if( tact_16th % 4 == 0 ) { p.setPen( QColor( 0x5F, 0x5F, 0x5F ) ); } // weak line else { p.setPen( QColor( 0x3F, 0x3F, 0x3F ) ); } p.drawLine( (int)x, PR_TOP_MARGIN, (int)x, height() - PR_BOTTOM_MARGIN ); } } // following code draws all notes in visible area + volume-lines // setup selection-vars int sel_pos_start = m_selectStartTick; int sel_pos_end = m_selectStartTick+m_selectedTick; if( sel_pos_start > sel_pos_end ) { qSwap( sel_pos_start, sel_pos_end ); } int sel_key_start = m_selectStartKey - m_startKey + 1; int sel_key_end = sel_key_start + m_selectedKeys; if( sel_key_start > sel_key_end ) { qSwap( sel_key_start, sel_key_end ); } int y_base = height() - PR_BOTTOM_MARGIN - m_notesEditHeight - 1; if( validPattern() == TRUE ) { p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, width() - WHITE_KEY_WIDTH, height() - PR_TOP_MARGIN ); const noteVector & notes = m_pattern->notes(); const int visible_keys = ( height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN - m_notesEditHeight ) / KEY_LINE_HEIGHT + 2; QPolygon volumeHandles; for( noteVector::const_iterator it = notes.begin(); it != notes.end(); ++it ) { Sint32 len_ticks = ( *it )->length(); if( len_ticks == 0 ) { continue; } else if( len_ticks < 0 ) { len_ticks = 4; } const int key = ( *it )->key() - m_startKey + 1; Sint32 pos_ticks = ( *it )->pos(); int note_width = len_ticks * m_ppt / midiTime::ticksPerTact(); const int x = ( pos_ticks - m_currentPosition ) * m_ppt / midiTime::ticksPerTact(); // skip this note if not in visible area at all if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) ) { continue; } // is the note in visible area? if( key > 0 && key <= visible_keys ) { bool is_selected = FALSE; // if we're in move-mode, we may only draw notes // in selected area, that have originally been // selected and not notes that are now in // selection because the user moved it... if( m_editMode == MOVE ) { if( qFind( m_selNotesForMove.begin(), m_selNotesForMove.end(), *it ) != m_selNotesForMove.end() ) { is_selected = TRUE; } } else if( key > sel_key_start && key <= sel_key_end && pos_ticks >= sel_pos_start && pos_ticks + len_ticks <= sel_pos_end ) { is_selected = TRUE; } // we've done and checked all, lets draw the // note drawNoteRect( p, x + WHITE_KEY_WIDTH, y_base - key * KEY_LINE_HEIGHT, note_width, is_selected, ( *it )->length() < 0 ); } // draw volume-line of note QColor color = QColor::fromHsv( 120, 221, tMin(255, 60 + ( *it )->getVolume() ) ); p.setPen( QPen( color, NE_LINE_WIDTH ) ); p.drawLine( x + WHITE_KEY_WIDTH, height() - PR_BOTTOM_MARGIN - ( *it )->getVolume() / 2, x + WHITE_KEY_WIDTH, height() - PR_BOTTOM_MARGIN ); volumeHandles << QPoint( x + WHITE_KEY_WIDTH + 1, height() - PR_BOTTOM_MARGIN - ( *it )->getVolume() / 2+1 ); if( ( *it )->hasDetuningInfo() ) { drawDetuningInfo( p, *it, x + WHITE_KEY_WIDTH, y_base - key * KEY_LINE_HEIGHT ); } } p.setPen( QPen( QColor( 0xEA, 0xA1, 0x00 ), NE_LINE_WIDTH*2 ) ); p.drawPoints( volumeHandles ); } else { QFont f = p.font(); f.setBold( TRUE ); p.setFont( pointSize<14>( f ) ); p.setPen( QColor( 0, 255, 0 ) ); p.drawText( WHITE_KEY_WIDTH + 20, PR_TOP_MARGIN + 40, tr( "Please open a pattern by double-clicking " "on it!" ) ); } p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, width() - WHITE_KEY_WIDTH, height() - PR_TOP_MARGIN - m_notesEditHeight - PR_BOTTOM_MARGIN ); // now draw selection-frame int x = ( ( sel_pos_start - m_currentPosition ) * m_ppt ) / midiTime::ticksPerTact(); int w = ( ( ( sel_pos_end - m_currentPosition ) * m_ppt ) / midiTime::ticksPerTact() ) - x; int y = (int) y_base - sel_key_start * KEY_LINE_HEIGHT; int h = (int) y_base - sel_key_end * KEY_LINE_HEIGHT - y; p.setPen( QColor( 0, 64, 192 ) ); p.drawRect( x + WHITE_KEY_WIDTH, y, w, h ); // TODO: Get this out of paint event int l = ( validPattern() == TRUE )? (int) m_pattern->length() : 0; // reset scroll-range if( m_leftRightScroll->maximum() != l ) { m_leftRightScroll->setRange( 0, l ); m_leftRightScroll->setPageStep( l ); } // horizontal line for the key under the cursor if( validPattern() == TRUE ) { int key_num = getKey( mapFromGlobal( QCursor::pos() ).y() ); p.fillRect( 10, height() + 3 - PR_BOTTOM_MARGIN - m_notesEditHeight - KEY_LINE_HEIGHT * ( key_num - m_startKey + 1 ), width() - 10, KEY_LINE_HEIGHT - 7, QColor( 64, 64, 64 ) ); } const QPixmap * cursor = NULL; // draw current edit-mode-icon below the cursor switch( m_editMode ) { case DRAW: cursor = s_toolDraw; break; case ERASE: cursor = s_toolErase; break; case SELECT: cursor = s_toolSelect; break; case MOVE: cursor = s_toolMove; break; case OPEN: cursor = s_toolOpen; break; } p.drawPixmap( mapFromGlobal( QCursor::pos() ) + QPoint( 8, 8 ), *cursor ); } // responsible for moving/resizing scrollbars after window-resizing void pianoRoll::resizeEvent( QResizeEvent * ) { m_leftRightScroll->setGeometry( WHITE_KEY_WIDTH, height() - SCROLLBAR_SIZE, width()-WHITE_KEY_WIDTH, SCROLLBAR_SIZE ); m_topBottomScroll->setGeometry( width() - SCROLLBAR_SIZE, PR_TOP_MARGIN, SCROLLBAR_SIZE, height() - PR_TOP_MARGIN - SCROLLBAR_SIZE ); int total_pixels = OCTAVE_HEIGHT * NumOctaves - ( height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN - m_notesEditHeight ); m_totalKeysToScroll = total_pixels * KeysPerOctave / OCTAVE_HEIGHT; m_topBottomScroll->setRange( 0, m_totalKeysToScroll ); if( m_startKey > m_totalKeysToScroll ) { m_startKey = m_totalKeysToScroll; } m_topBottomScroll->setValue( m_totalKeysToScroll - m_startKey ); engine::getSong()->getPlayPos( song::Mode_PlayPattern ).m_timeLine->setFixedWidth( width() ); m_toolBar->setFixedWidth( width() ); update(); } void pianoRoll::wheelEvent( QWheelEvent * _we ) { _we->accept(); if( engine::getMainWindow()->isCtrlPressed() == TRUE ) { if( _we->delta() > 0 ) { m_ppt = tMin( m_ppt * 2, KEY_LINE_HEIGHT * DefaultStepsPerTact * 8 ); } else if( m_ppt >= 72 ) { m_ppt /= 2; } // update combobox with zooming-factor m_zoomingModel->setValue( m_zoomingModel->findText( QString::number( static_cast( m_ppt * 100 / DEFAULT_PR_PPT ) ) +"%" ) ); // update timeline m_timeLine->setPixelsPerTact( m_ppt ); update(); } else if( engine::getMainWindow()->isShiftPressed() ) { m_leftRightScroll->setValue( m_leftRightScroll->value() - _we->delta() * 2 / 15 ); } else { m_topBottomScroll->setValue( m_topBottomScroll->value() - _we->delta() / 30 ); } } int pianoRoll::getKey( int _y ) const { int key_line_y = height() - PR_BOTTOM_MARGIN - m_notesEditHeight - 1; // pressed key on piano int key_num = ( key_line_y - _y ) / KEY_LINE_HEIGHT; key_num += m_startKey; // some range-checking-stuff if( key_num < 0 ) { key_num = 0; } if( key_num >= KeysPerOctave * NumOctaves ) { key_num = KeysPerOctave * NumOctaves - 1; } return( key_num ); } void pianoRoll::play( void ) { if( validPattern() == FALSE ) { return; } if( engine::getSong()->isPlaying() ) { if( engine::getSong()->playMode() != song::Mode_PlayPattern ) { engine::getSong()->stop(); engine::getSong()->playPattern( m_pattern ); m_playButton->setIcon( embed::getIconPixmap( "pause" ) ); } else { engine::getSong()->pause(); m_playButton->setIcon( embed::getIconPixmap( "play" ) ); } } else if( engine::getSong()->isPaused() ) { engine::getSong()->resumeFromPause(); m_playButton->setIcon( embed::getIconPixmap( "pause" ) ); } else { m_playButton->setIcon( embed::getIconPixmap( "pause" ) ); engine::getSong()->playPattern( m_pattern ); } } void pianoRoll::record( void ) { if( engine::getSong()->isPlaying() ) { stop(); } if( m_recording == TRUE || validPattern() == FALSE ) { return; } m_recording = TRUE; engine::getSong()->playPattern( m_pattern, FALSE ); } void pianoRoll::recordAccompany( void ) { if( engine::getSong()->isPlaying() ) { stop(); } if( m_recording == TRUE || validPattern() == FALSE ) { return; } m_recording = TRUE; if( m_pattern->getTrack()->getTrackContainer() == engine::getSong() ) { engine::getSong()->play(); } else { engine::getSong()->playBB(); } } void pianoRoll::stop( void ) { engine::getSong()->stop(); m_playButton->setIcon( embed::getIconPixmap( "play" ) ); m_playButton->update(); m_recording = FALSE; m_scrollBack = TRUE; } void pianoRoll::recordNote( const note & _n ) { if( m_recording == TRUE && validPattern() == TRUE ) { note n( _n.length(), engine::getSong()->getPlayPos( engine::getSong()->playMode() ) - _n.length(), _n.key(), _n.getVolume(), _n.getPanning() ); /* note n( _n.length(), engine::getSong()->getPlayPos( song::Mode_PlayPattern ) - _n.length(), _n.key(), _n.getVolume(), _n.getPanning() ); */ n.quantizeLength( quantization() ); m_pattern->addNote( n ); update(); engine::getSong()->setModified(); } } void pianoRoll::horScrolled( int _new_pos ) { m_currentPosition = _new_pos; emit positionChanged( m_currentPosition ); update(); } void pianoRoll::verScrolled( int _new_pos ) { // revert value m_startKey = m_totalKeysToScroll - _new_pos; update(); } void pianoRoll::drawButtonToggled( void ) { m_editMode = DRAW; removeSelection(); update(); } void pianoRoll::eraseButtonToggled( void ) { m_editMode = ERASE; removeSelection(); update(); } void pianoRoll::selectButtonToggled( void ) { m_editMode = SELECT; removeSelection(); update(); } void pianoRoll::moveButtonToggled( void ) { m_editMode = MOVE; m_selNotesForMove.clear(); getSelectedNotes( m_selNotesForMove ); update(); } void pianoRoll::selectAll( void ) { if( validPattern() == FALSE ) { return; } const noteVector & notes = m_pattern->notes(); // if first_time = TRUE, we HAVE to set the vars for select bool first_time = TRUE; for( noteVector::const_iterator it = notes.begin(); it != notes.end(); ++it ) { Uint32 len_ticks = ( *it )->length(); if( len_ticks > 0 ) { const int key = ( *it )->key(); Uint32 pos_ticks = ( *it )->pos(); if( key <= m_selectStartKey || first_time ) { // if we move start-key down, we have to add // the difference between old and new start-key // to m_selectedKeys, otherwise the selection // is just moved down... m_selectedKeys += m_selectStartKey - ( key - 1 ); m_selectStartKey = key - 1; } if( key >= m_selectedKeys+m_selectStartKey || first_time ) { m_selectedKeys = key - m_selectStartKey; } if( pos_ticks < m_selectStartTick || first_time ) { m_selectStartTick = pos_ticks; } if( pos_ticks + len_ticks > m_selectStartTick + m_selectedTick || first_time ) { m_selectedTick = pos_ticks + len_ticks - m_selectStartTick; } first_time = FALSE; } } } // returns vector with pointers to all selected notes void pianoRoll::getSelectedNotes( noteVector & _selected_notes ) { if( validPattern() == FALSE ) { return; } int sel_pos_start = m_selectStartTick; int sel_pos_end = sel_pos_start + m_selectedTick; if( sel_pos_start > sel_pos_end ) { qSwap( sel_pos_start, sel_pos_end ); } int sel_key_start = m_selectStartKey; int sel_key_end = sel_key_start + m_selectedKeys; if( sel_key_start > sel_key_end ) { qSwap( sel_key_start, sel_key_end ); } const noteVector & notes = m_pattern->notes(); for( noteVector::const_iterator it = notes.begin(); it != notes.end(); ++it ) { Sint32 len_ticks = ( *it )->length(); if( len_ticks > 0 ) { int key = ( *it )->key(); Sint32 pos_ticks = ( *it )->pos(); if( key > sel_key_start && key <= sel_key_end && pos_ticks >= sel_pos_start && pos_ticks+len_ticks <= sel_pos_end ) { _selected_notes.push_back( *it ); } } } } void pianoRoll::copy_to_clipboard( const noteVector & _notes ) const { multimediaProject mmp( multimediaProject::ClipboardData ); QDomElement note_list = mmp.createElement( "note-list" ); mmp.content().appendChild( note_list ); midiTime start_pos( _notes.front()->pos().getTact(), 0 ); for( noteVector::const_iterator it = _notes.begin(); it != _notes.end(); ++it ) { note clip_note( **it ); clip_note.setPos( clip_note.pos( start_pos ) ); clip_note.saveState( mmp, note_list ); } QMimeData * clip_content = new QMimeData; clip_content->setData( clipboard::mimeType(), mmp.toString().toUtf8() ); QApplication::clipboard()->setMimeData( clip_content, QClipboard::Clipboard ); } void pianoRoll::copySelectedNotes( void ) { noteVector selected_notes; getSelectedNotes( selected_notes ); if( selected_notes.empty() == FALSE ) { copy_to_clipboard( selected_notes ); textFloat::displayMessage( tr( "Notes copied" ), tr( "All selected notes were copied to the " "clipboard." ), embed::getIconPixmap( "edit_copy" ), 2000 ); } } void pianoRoll::cutSelectedNotes( void ) { if( validPattern() == FALSE ) { return; } noteVector selected_notes; getSelectedNotes( selected_notes ); if( selected_notes.empty() == FALSE ) { copy_to_clipboard( selected_notes ); engine::getSong()->setModified(); for( noteVector::iterator it = selected_notes.begin(); it != selected_notes.end(); ++it ) { // note (the memory of it) is also deleted by // pattern::removeNote(...) so we don't have to do that m_pattern->removeNote( *it ); } } update(); engine::getSongEditor()->update(); } void pianoRoll::pasteNotes( void ) { if( validPattern() == FALSE ) { return; } QString value = QApplication::clipboard() ->mimeData( QClipboard::Clipboard ) ->data( clipboard::mimeType() ); if( !value.isEmpty() ) { multimediaProject mmp( value, FALSE ); QDomNodeList list = mmp.elementsByTagName( note::classNodeName() ); for( int i = 0; !list.item( i ).isNull(); ++i ) { note cur_note; cur_note.restoreState( list.item( i ).toElement() ); cur_note.setPos( cur_note.pos() + m_currentPosition ); m_pattern->addNote( cur_note ); } // we only have to do the following lines if we pasted at // least one note... engine::getSong()->setModified(); update(); engine::getSongEditor()->update(); } } void pianoRoll::deleteSelectedNotes( void ) { if( validPattern() == FALSE ) { return; } noteVector selected_notes; getSelectedNotes( selected_notes ); const bool update_after_delete = !selected_notes.empty(); while( selected_notes.empty() == FALSE ) { m_pattern->removeNote( selected_notes.front() ); selected_notes.erase( selected_notes.begin() ); } if( update_after_delete == TRUE ) { engine::getSong()->setModified(); update(); engine::getSongEditor()->update(); } } void pianoRoll::updatePosition( const midiTime & _t ) { if( ( engine::getSong()->isPlaying() && engine::getSong()->playMode() == song::Mode_PlayPattern ) || m_scrollBack == TRUE ) { const int w = width() - WHITE_KEY_WIDTH; if( _t > m_currentPosition + w * midiTime::ticksPerTact() / m_ppt ) { m_leftRightScroll->setValue( _t.getTact() * midiTime::ticksPerTact() ); } else if( _t < m_currentPosition ) { midiTime t = tMax( _t - w * midiTime::ticksPerTact() * midiTime::ticksPerTact() / m_ppt, 0 ); m_leftRightScroll->setValue( t.getTact() * midiTime::ticksPerTact() ); } m_scrollBack = FALSE; } } void pianoRoll::zoomingChanged( void ) { const QString & zfac = m_zoomingModel->currentText(); m_ppt = zfac.left( zfac.length() - 1 ).toInt() * DEFAULT_PR_PPT / 100; #ifdef LMMS_DEBUG assert( m_ppt > 0 ); #endif m_timeLine->setPixelsPerTact( m_ppt ); update(); } int pianoRoll::quantization( void ) const { return( DefaultTicksPerTact / m_quantizeModel->currentText().right( m_quantizeModel->currentText().length() - 2 ).toInt() ); } midiTime pianoRoll::newNoteLen( void ) const { if( m_noteLenModel->value() == 0 ) { return( m_lenOfNewNotes ); } return( midiTime::ticksPerTact() / m_noteLenModel->currentText().right( m_noteLenModel->currentText().length() - 2 ).toInt() ); } bool pianoRoll::mouseOverNote( void ) { return( validPattern() && noteIteratorUnderMouse() != m_pattern->notes().end() ); } note * pianoRoll::noteUnderMouse( void ) { return( *noteIteratorUnderMouse() ); } noteVector::const_iterator pianoRoll::noteIteratorUnderMouse( void ) { QPoint pos = mapFromGlobal( QCursor::pos() ); // get note-vector of current pattern const noteVector & notes = m_pattern->notes(); if( pos.x() <= WHITE_KEY_WIDTH || pos.x() > width() - SCROLLBAR_SIZE || pos.y() < PR_TOP_MARGIN || pos.y() > height() - PR_BOTTOM_MARGIN - m_notesEditHeight ) { return( notes.end() ); } int key_num = getKey( pos.y() ); int pos_ticks = ( pos.x() - WHITE_KEY_WIDTH ) * midiTime::ticksPerTact() / m_ppt + m_currentPosition; // will be our iterator in the following loop noteVector::const_iterator it = notes.begin(); // loop through whole note-vector... while( it != notes.end() ) { // and check whether the cursor is over an // existing note if( pos_ticks >= ( *it )->pos() && pos_ticks <= ( *it )->pos() + ( *it )->length() && ( *it )->key() == key_num && ( *it )->length() > 0 ) { break; } ++it; } return( it ); } #ifdef LMMS_BUILD_LINUX bool pianoRoll::x11Event( XEvent * _xe ) { if( validPattern() ) { /* return( m_pattern->getInstrumentTrack()->getPianoWidget() ->x11Event( _xe ) );*/ } return( FALSE ); } #endif #include "piano_roll.moc" #endif