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 )