From b7f4f7be6d3a95324a0deb1ebfd834ed528d47b7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 13 Dec 2008 02:28:24 +0000 Subject: [PATCH] editing note volume and hearing clicked on notes is implemented more cleanly, editing note volume only affects selected notes (or all notes if none selected), when "scribbling" note volumes, if there is a chord, it will play all 3 notes, rather than just the first one git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@1909 0778d3d1-df1d-0410-868b-ea421aaaa00d --- ChangeLog | 14 +++ TODO | 22 +++-- include/note.h | 13 +++ include/piano_roll.h | 1 + src/core/note.cpp | 2 + src/gui/piano_roll.cpp | 201 +++++++++++++++++------------------------ 6 files changed, 126 insertions(+), 127 deletions(-) diff --git a/ChangeLog b/ChangeLog index d0d52d537..304940c05 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2008-12-12 Andrew Kelley + + * include/note.h: + * src/core/note.cpp: + * include/piano_roll.h: + * src/gui/piano_roll.cpp: + * TODO: + - editing note volume and hearing clicked on notes + is implemented more cleanly + - editing note volume only affects selected notes (or all notes if + none selected) + - when "scribbling" note volumes, if there is a chord, it will play all + 3 notes, rather than just the first one + 2008-12-11 Tobias Doerffel * include/automation_pattern.h: diff --git a/TODO b/TODO index eb710adbb..c4d28e262 100644 --- a/TODO +++ b/TODO @@ -52,21 +52,23 @@ - add FLAC as export-format? Andrew Kelley's todo: -- recording automation -- make knobs easier to tune (less sensitive) -- make the menu for a channel happen when you right click, instead of renaming, and make the midi input a top-level menu item -- add a "Set exact value" to a right clicked automation menu -- enable "auto detect" by default when you bring up the "connect to controller" window - +- piano roll: make the note volume section have adjustable height +- add note panning ability to piano roll +- paint piano roll notes with a darker color based on how high volume the note is +- paint note volume blue if selected +- multiview button - show notes from every instrument in the current beat+bassline with different colors +- undo/redo for piano roll - add a tools menu to piano roll * put some of the tools on there that already have keyboard shortcuts (ctrl+up/down, shift+left/right) * humanizing tool * quick slice * look through FL Studio's tools and implement some of them -- undo/redo for piano roll -- make copy/paste work beyond inside piano roll - it didn't work when I copied notes from one pattern and then opened another pattern and pasted. -- piano roll: make the note volume section have adjustable height -- add note panning ability to piano roll + +- recording automation +- make knobs easier to tune (less sensitive) +- make the menu for a channel happen when you right click, instead of renaming, and make the midi input a top-level menu item +- add a "Set exact value" to a right clicked automation menu +- enable "auto detect" by default when you bring up the "connect to controller" window - "paintbrush" tool for the song editor, to easily "paint" beat+basslines - when you clone a track in the song editor, rename the track so that it doesn't have the same name (increment the number if necessary) diff --git a/include/note.h b/include/note.h index e5a6017a3..9927bcc11 100644 --- a/include/note.h +++ b/include/note.h @@ -91,6 +91,7 @@ public: note( const note & _note ); virtual ~note(); + // used by GUI inline void setSelected( const bool _selected ){ m_selected = _selected; } inline void setOldKey( const int _oldKey ){ m_oldKey = _oldKey; } inline void setOldPos( const midiTime & _oldPos ){ m_oldPos = _oldPos; } @@ -98,6 +99,11 @@ public: { m_oldLength = _oldLength; } + inline void setIsPlaying( const bool _isPlaying ) + { + m_isPlaying = _isPlaying; + } + void setLength( const midiTime & _length ); void setPos( const midiTime & _pos ); @@ -133,6 +139,11 @@ public: { return m_oldLength; } + + inline bool isPlaying( void ) const + { + return m_isPlaying; + } inline midiTime endPos( void ) const { @@ -212,11 +223,13 @@ private: ChangePosition } ;*/ + // for piano roll editing bool m_selected; int m_oldKey; midiTime m_oldPos; midiTime m_oldLength; + bool m_isPlaying; int m_key; volume m_volume; diff --git a/include/piano_roll.h b/include/piano_roll.h index 0d49d0591..3bd540923 100644 --- a/include/piano_roll.h +++ b/include/piano_roll.h @@ -169,6 +169,7 @@ private: void shiftPos(int amount); void shiftSemiTone(int amount); bool isSelection() const; + void testPlayNote( note * n ); void dragNotes( int x, int y, bool alt ); diff --git a/src/core/note.cpp b/src/core/note.cpp index 9801d2d59..f37c32403 100644 --- a/src/core/note.cpp +++ b/src/core/note.cpp @@ -44,6 +44,7 @@ note::note( const midiTime & _length, const midiTime & _pos, m_oldKey( tLimit( _key, 0, NumKeys ) ), m_oldPos( _pos ), m_oldLength( _length ), + m_isPlaying( false ), m_key( tLimit( _key, 0, NumKeys ) ), m_volume( tLimit( _volume, MinVolume, MaxVolume ) ), m_panning( tLimit( _panning, PanningLeft, PanningRight ) ), @@ -72,6 +73,7 @@ note::note( const note & _note ) : m_oldKey( _note.m_oldKey ), m_oldPos( _note.m_oldPos ), m_oldLength( _note.m_oldLength ), + m_isPlaying( _note.m_isPlaying ), m_key( _note.m_key), m_volume( _note.m_volume ), m_panning( _note.m_panning ), diff --git a/src/gui/piano_roll.cpp b/src/gui/piano_roll.cpp index 2321dc574..4ad4dd4c6 100644 --- a/src/gui/piano_roll.cpp +++ b/src/gui/piano_roll.cpp @@ -1081,7 +1081,6 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) if( _me->y() > PR_TOP_MARGIN ) { - bool play_note = ! engine::getSong()->isPlaying(); volume vol = DefaultVolume; bool edit_note = ( _me->y() > height() - @@ -1169,10 +1168,9 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) m_currentNote = *it; m_action = ActionChangeNoteVolume; key_num = ( *it )->key(); - } - else - { - play_note = false; + + testPlayNote( *it ); + } } // left button?? @@ -1280,17 +1278,12 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) // set resize-cursor QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); - play_note = false; } else { // otherwise move it m_action = ActionMoveNote; - - - - // set move-cursor QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); @@ -1328,6 +1321,9 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) engine::getSongEditor()->update(); } } + + // play the note + testPlayNote( m_currentNote ); } @@ -1346,7 +1342,6 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) { // erase single note m_mouseDownRight = true; - play_note = false; if( it != notes.end() ) { if( ( *it )->length() > 0 ) @@ -1372,36 +1367,35 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) m_selectedKeys = 1; 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 == ModeMove ) - { - // 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 ) - { - m_lastKey = key_num; - m_pattern->getInstrumentTrack()->processInEvent( - midiEvent( MidiNoteOn, 0, key_num, - vol * 127 / 100 ), - midiTime() ); - } } } + + +void pianoRoll::testPlayNote( note * n ) +{ + m_lastKey = n->key(); + + if(! n->isPlaying() && ! m_recording && ! engine::getSong()->isPlaying() ) + { + n->setIsPlaying( true ); + m_pattern->getInstrumentTrack()->processInEvent( + midiEvent( MidiNoteOn, 0, n->key(), + n->getVolume() * 127 / 100 ), midiTime() ); + + } +} + + + void pianoRoll::computeSelectedNotes(bool shift) { if( m_selectStartTick == 0 && @@ -1512,17 +1506,21 @@ void pianoRoll::mouseReleaseEvent( QMouseEvent * _me ) if( validPattern() == true ) { - if( m_action == ActionChangeNoteVolume && m_currentNote != NULL ) + // turn off all notes that are playing + const noteVector & notes = m_pattern->notes(); + + noteVector::const_iterator it = notes.begin(); + while( it != notes.end() ) { - m_pattern->getInstrumentTrack()->processInEvent( - midiEvent( MidiNoteOff, 0, - m_currentNote->key(), 0 ), midiTime() ); - } - else - { - m_pattern->getInstrumentTrack()->processInEvent( - midiEvent( MidiNoteOff, 0, m_lastKey, 0 ), - midiTime() ); + if( ( *it )->isPlaying() ) + { + m_pattern->getInstrumentTrack()->processInEvent( + midiEvent( MidiNoteOff, 0, ( *it )->key(), 0 ), + midiTime() ); + ( *it )->setIsPlaying( false ); + } + + ++it; } } @@ -1566,8 +1564,7 @@ void pianoRoll::mouseMoveEvent( QMouseEvent * _me ) // (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 != ActionChangeNoteVolume && - m_action != ActionMoveSelection && + ( m_action == ActionMoveNote || m_action == ActionMoveSelection ) && edit_note == false && _me->buttons() & Qt::LeftButton ) { @@ -1606,96 +1603,66 @@ void pianoRoll::mouseMoveEvent( QMouseEvent * _me ) { // Volume Bars - // Use nearest-note when changing volume so the bars can - // be "scribbled" - int pos_ticks = ( x * midiTime::ticksPerTact() ) / - m_ppt + m_currentPosition; + // Change notes within a certain pixel range of where + // the mouse cursor is + int pixel_range = 20; + + // convert to ticks so that we can check which notes + // are in the range + int ticks_start = (x-pixel_range/2) * + midiTime::ticksPerTact() / m_ppt + m_currentPosition; + int ticks_end = (x+pixel_range/2) * + 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; - - } - + // determine what volume to set note to volume vol = tLimit( 2 * ( -_me->y() + height() - PR_BOTTOM_MARGIN ), MinVolume, MaxVolume ); - - if( shortNote ) + + // loop through vector + bool use_selection = isSelection(); + noteVector::const_iterator it = notes.begin(); + while( it != notes.end() ) { - // 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().getTicks() >= ticks_start + && ( *it )->pos().getTicks() <= ticks_end + && ( *it )->length().getTicks() > 0 + && ( ( *it )->selected() || ! use_selection ) ) { - 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 ) { + ( *it )->setVolume( vol ); + m_pattern->dataChanged(); + + // play the note so that the user can tell how loud it is + testPlayNote( *it ); + m_pattern->getInstrumentTrack()->processInEvent( - midiEvent( MidiNoteOff, 0, - m_currentNote->key(), 0 ), midiTime() ); + midiEvent( + MidiKeyPressure, + 0, + ( *it )->key(), + vol * 127 / 100 ), + midiTime() ); + } + else + { + if( ( *it )->isPlaying() ) + { + // mouse not over this note, stop playing it. + m_pattern->getInstrumentTrack()->processInEvent( + midiEvent( MidiNoteOff, 0, + ( *it )->key(), 0 ), midiTime() ); + + ( *it )->setIsPlaying( false ); + } } - if( shortNote != NULL ) { - m_lastKey = shortNote->key(); + ++it; - 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( _me->buttons() == Qt::NoButton && m_editMode == ModeDraw )