diff --git a/AUTHORS b/AUTHORS index 56af547c9..9bdf47818 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,6 +14,10 @@ Javier Serrano Polo development +Andrew Kelley + + development + Andreas Brandmaier BitInvader plugin diff --git a/ChangeLog b/ChangeLog index d62ef0317..bce2a442f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +2008-11-13 Andrew Kelley + * include/piano_roll.h + * src/gui/piano_roll.cpp + - changed the way selection works + - you can select any combination of notes + - delete any combination of notes + - move multiple notes at once + - resize multiple notes at once + - etc + - hold alt to disable quantization + + * include/note.h + * src/core/note.cpp + - added m_selected so we know if the note is selected or not + - added operator< so we can sort notes vector by start time + + * include/pattern.h + * src/tracks/pattern.cpp + added rearrangeAllNotes for when we move multiple notes in the piano roll + + * AUTHORS + hope you don't mind :-) + 2008-11-10 Tobias Doerffel * src/core/track.cpp: @@ -25,7 +48,7 @@ 2008-11-10 Andrew Kelley - * src/gui/piano_roll.h: + * include/piano_roll.h: * src/gui/piano_roll.cpp: - made it so you can hold right click and drag around to delete notes - changed ctrl+click from opening automation window to shift+click diff --git a/include/note.h b/include/note.h index 5cbd287db..e126712c8 100644 --- a/include/note.h +++ b/include/note.h @@ -90,7 +90,8 @@ public: detuningHelper * _detuning = NULL ); note( const note & _note ); virtual ~note(); - + + void setSelected( const bool selected ); void setLength( const midiTime & _length ); void setPos( const midiTime & _pos ); void setKey( const int _key ); @@ -99,6 +100,16 @@ public: void quantizeLength( const int _q_grid ); void quantizePos( const int _q_grid ); + inline bool operator<(const note & rhs) + { + return m_pos < rhs.pos(); + } + + inline bool getSelected( void ) const + { + return m_selected; + } + inline midiTime endPos( void ) const { const int l = length(); @@ -177,7 +188,7 @@ private: ChangePosition } ;*/ - + bool m_selected; // for piano roll editing int m_key; volume m_volume; panning m_panning; diff --git a/include/pattern.h b/include/pattern.h index b26532d61..e7614b402 100644 --- a/include/pattern.h +++ b/include/pattern.h @@ -74,7 +74,7 @@ public: note * rearrangeNote( const note * _note_to_proc, const bool _quant_pos = TRUE ); - + void rearrangeAllNotes( void ); void clearNotes( void ); inline const noteVector & notes( void ) diff --git a/include/piano_roll.h b/include/piano_roll.h index 9533aa46d..df6673d82 100644 --- a/include/piano_roll.h +++ b/include/piano_roll.h @@ -250,8 +250,10 @@ private: bool mouseOverNote( void ); note * noteUnderMouse( void ); noteVector::const_iterator noteIteratorUnderMouse( void ); - - + + // turn a selection rectangle into selected notes + void computeSelectedNotes( bool shift ); + void clearSelectedNotes( void ); friend class engine; diff --git a/src/core/note.cpp b/src/core/note.cpp index c527ec99a..b854d7976 100644 --- a/src/core/note.cpp +++ b/src/core/note.cpp @@ -48,7 +48,7 @@ note::note( const midiTime & _length, const midiTime & _pos, { //saveJournallingState( FALSE ); // setJournalling( FALSE ); - + setSelected( false ); if( _detuning ) { m_detuning = sharedObject::ref( _detuning ); @@ -71,6 +71,7 @@ note::note( const note & _note ) : m_length( _note.m_length ), m_pos( _note.m_pos ) { + setSelected( false ); m_detuning = sharedObject::ref( _note.m_detuning ); } @@ -82,7 +83,10 @@ note::~note() sharedObject::unref( m_detuning ); } - +void note::setSelected( const bool selected ) +{ + m_selected = selected; +} void note::setLength( const midiTime & _length ) diff --git a/src/gui/piano_roll.cpp b/src/gui/piano_roll.cpp index d25b497d3..55de26914 100644 --- a/src/gui/piano_roll.cpp +++ b/src/gui/piano_roll.cpp @@ -653,8 +653,27 @@ void pianoRoll::removeSelection( void ) m_selectedTick = 0; m_selectStartKey = 0; m_selectedKeys = 0; + + } +void pianoRoll::clearSelectedNotes( void ) +{ + if( m_pattern != NULL ) + { + // 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(); + while( it != notes.end() ) + { + ( *it )->setSelected( false ); + + ++it; + } + } +} @@ -895,6 +914,7 @@ void pianoRoll::keyReleaseEvent( QKeyEvent * _ke ) switch( _ke->key() ) { case Qt::Key_Control: + computeSelectedNotes(_ke->modifiers() & Qt::ShiftModifier); m_editMode = m_ctrlMode; update(); break; @@ -1065,6 +1085,8 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) { ++it; } + + } m_currentNote = *it; @@ -1098,8 +1120,16 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) // set move-cursor QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); + } - + + + // if clicked on an unselected note, remove selection + if( ! m_currentNote->getSelected() ) + { + clearSelectedNotes(); + } + engine::getSong()->setModified(); } else if( ( _me->buttons() == Qt::RightButton && @@ -1135,6 +1165,10 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) m_action = ActionSelectNotes; play_note = false; + + // call mousemove to fix glitch where selection + // appears in wrong spot on mousedown + mouseMoveEvent( _me ); } else if( _me->button() == Qt::RightButton && m_editMode == ModeSelect ) @@ -1183,11 +1217,121 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) } } +void pianoRoll::computeSelectedNotes(bool shift) +{ + if( m_selectStartTick == 0 && + m_selectedTick == 0 && + m_selectStartKey == 0 && + m_selectedKeys == 0 ) + { + // don't bother, there's no selection + return; + } + + // 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 ) + { + 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 ) + { + // make a new selection unless they're holding shift + if( ! shift ) + { + ( *it )->setSelected( false ); + } + + 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 ) + { + // if the selection even barely overlaps the note + if( key > sel_key_start && + key <= sel_key_end && + pos_ticks + len_ticks > sel_pos_start && + pos_ticks < sel_pos_end ) + { + ( *it )->setSelected( true ); + } + + } + } + } + + removeSelection(); + update(); +} void pianoRoll::mouseReleaseEvent( QMouseEvent * _me ) { + if( _me->button() & Qt::LeftButton && + m_editMode == ModeSelect && + m_action == ActionSelectNotes ) + { + // select the notes within the selection rectangle and + // then destroy the selection rectangle + + computeSelectedNotes( _me->modifiers() & Qt::ShiftModifier ); + + } + else if( _me->button() & Qt::LeftButton && + m_action == ActionMoveNote ) + { + // we moved one or more notes so they have to be + // moved properly according to new starting- + // time in the note-array of pattern + + m_pattern->rearrangeAllNotes(); + } + if( validPattern() == true ) { if( m_action == ActionChangeNoteVolume && m_currentNote != NULL ) @@ -1364,46 +1508,88 @@ void pianoRoll::mouseMoveEvent( QMouseEvent * _me ) else if( m_currentNote != NULL && _me->buttons() & Qt::LeftButton && m_editMode == ModeDraw ) { - int key_num = getKey( _me->y() ); + //int key_num = getKey( _me->y() ); + int key_offset = getKey( _me->y() ) - m_currentNote->key(); if( m_action == ActionMoveNote ) { x -= m_moveXOffset; } - int pos_ticks = x * midiTime::ticksPerTact() / m_ppt + - m_currentPosition; - if( m_action == ActionMoveNote ) - { - // moving note - if( pos_ticks < 0 ) - { - pos_ticks = 0; - } - m_currentNote->setPos( midiTime( - pos_ticks ) ); - m_currentNote->setKey( key_num ); + + + int pos_ticks_offset = x * midiTime::ticksPerTact() / m_ppt + + m_currentPosition - m_currentNote->pos().getTicks(); + + + // get note-vector of current pattern + const noteVector & notes = m_pattern->notes(); - // 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 + // will be our iterator in the following loop + noteVector::const_iterator it = notes.begin(); + while( it != notes.end() ) { - // resizing note - int ticks_diff = pos_ticks - - m_currentNote->pos(); - if( ticks_diff <= 0 ) + if( ( *it )->getSelected() || m_currentNote == *it ) { - ticks_diff = 1; + + if( m_action == ActionMoveNote ) + { + // moving note + int pos_ticks = midiTime( ( *it )->pos().getTicks() + + pos_ticks_offset ); + int key_num = ( *it )->key() + key_offset; + + if( pos_ticks < 0 ) + { + pos_ticks = 0; + } + // TODO: upper/lower bound checks on key_num + /*if( key_num < lower bound ) + { + key_num = lower bound + } + else if( key_num > upper bound ) + { + key_num = upper bound + } */ + + ( *it )->setPos( midiTime( + pos_ticks ) ); + ( *it )->setKey( key_num ); + if( ! ( _me->modifiers() & Qt::AltModifier ) ) + { + ( *it )->quantizePos( quantization() ); + } + + + } + else + { + // resizing note + //int pos_ticks = midiTime( ( *it )->pos().getTicks() + // + pos_ticks_offset ); + //int ticks_diff = pos_ticks - + // m_currentNote->pos(); + + int ticks_diff = pos_ticks_offset; + + if( ticks_diff <= 0 ) + { + ticks_diff = 1; + } + ( *it )->setLength( midiTime( ticks_diff ) ); + + if( ! ( _me->modifiers() & Qt::AltModifier ) ) + { + ( *it )->quantizeLength( quantization() ); + } + + m_lenOfNewNotes = ( *it )->length(); + m_pattern->dataChanged(); + } } - m_currentNote->setLength( midiTime( ticks_diff ) ); - m_currentNote->quantizeLength( quantization() ); - m_lenOfNewNotes = m_currentNote->length(); - m_pattern->dataChanged(); + ++it; } - + engine::getSong()->setModified(); } @@ -2099,7 +2285,9 @@ void pianoRoll::paintEvent( QPaintEvent * _pe ) // is the note in visible area? if( key > 0 && key <= visible_keys ) { - bool is_selected = false; + /* changing the way selection works + + 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 @@ -2122,13 +2310,14 @@ void pianoRoll::paintEvent( QPaintEvent * _pe ) { is_selected = true; } + */ - // we've done and checked all, lets draw the + // we've done and checked all, let's draw the // note drawNoteRect( p, x + WHITE_KEY_WIDTH, y_base - key * KEY_LINE_HEIGHT, note_width, - is_selected, + ( *it )->getSelected(), ( *it )->length() < 0 ); } // draw volume-line of note @@ -2716,7 +2905,34 @@ void pianoRoll::deleteSelectedNotes( void ) { return; } + + bool update_after_delete = false; + + + // 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(); + while( it != notes.end() ) + { + if( ( *it )->getSelected() ) + { + // delete this note + m_pattern->removeNote( ( *it ) ); + update_after_delete = true; + + // start over, make sure we get all the notes + it = notes.begin(); + } + else + { + ++it; + } + } + + + /* noteVector selected_notes; getSelectedNotes( selected_notes ); @@ -2726,7 +2942,7 @@ void pianoRoll::deleteSelectedNotes( void ) { m_pattern->removeNote( selected_notes.front() ); selected_notes.erase( selected_notes.begin() ); - } + } */ if( update_after_delete == true ) { @@ -2734,6 +2950,7 @@ void pianoRoll::deleteSelectedNotes( void ) update(); engine::getSongEditor()->update(); } + } diff --git a/src/tracks/pattern.cpp b/src/tracks/pattern.cpp index 6176bbaad..cb7c9036a 100644 --- a/src/tracks/pattern.cpp +++ b/src/tracks/pattern.cpp @@ -269,7 +269,12 @@ note * pattern::rearrangeNote( const note * _note_to_proc, return( addNote( copy_of_note, _quant_pos ) ); } - +void pattern::rearrangeAllNotes( void ) +{ + // sort notes by start time + qSort(m_notes.begin(), m_notes.end()); + +} void pattern::clearNotes( void )