From e0e7cb9af8dc0b565aad6c46d02677c70654ce0d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 14 Dec 2008 02:39:12 +0000 Subject: [PATCH] Note panning editing support in the piano roll git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@1918 0778d3d1-df1d-0410-868b-ea421aaaa00d --- ChangeLog | 21 ++++ TODO | 6 +- include/midi.h | 18 +++- include/panning.h | 8 +- include/panning_constants.h | 34 +++++++ include/piano_roll.h | 20 ++-- src/gui/piano_roll.cpp | 167 +++++++++++++++++++------------- src/tracks/instrument_track.cpp | 28 ++++-- 8 files changed, 210 insertions(+), 92 deletions(-) create mode 100644 include/panning_constants.h diff --git a/ChangeLog b/ChangeLog index ada067693..d410ac85a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -31,6 +31,24 @@ - added support for MidiMetaEvents to midiEvent - added MidiMetaPanning event +2008-12-13 Andrew Kelley + + * include/piano_roll.h: + * src/gui/piano_roll.cpp: + * src/tracks/instrument_track.cpp: + * TODO: + - first draft of panning editing. (for now, click the area below + the piano to switch editing modes.) + - supports horizontal mouse wheel + + * include/panning_constants.h: + * include/midi.h: + * include/panning.h: + - separated panning constants from panningToVolumeVector so that midi.h + could have access to PanningLeft, PanningRight, etc + - added MidiNotePanning meta midi instruction to allow on the fly + panning changes to instrument notes + 2008-12-12 Andrew Kelley * include/note.h: @@ -42,6 +60,9 @@ is implemented more cleanly - editing note volume only affects selected notes (or all notes if none selected) + - clicking a note volume no longer affects the nearest volume handle. + Instead, select the note(s) whose volumes you want to change and then + scribble their volumes. - when "scribbling" note volumes, if there is a chord, it will play all 3 notes, rather than just the first one - if you click towards the end of the key in the piano roll, it plays diff --git a/TODO b/TODO index fc3d64b61..793bb0c8c 100644 --- a/TODO +++ b/TODO @@ -52,7 +52,6 @@ - add FLAC as export-format? Andrew Kelley's todo: -- 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 @@ -63,6 +62,10 @@ Andrew Kelley's todo: * quick slice * look through FL Studio's tools and implement some of them - if you press both controls at the same time, the piano roll gets stuck in selection mode +- you can still move volumes for notes if you click down right on it, even if the + note is not selected. +- change my modifier code to use mainwindows modifier info +- fix ctrl+mousewheel zooming in - recording automation - make knobs easier to tune (less sensitive) @@ -78,5 +81,4 @@ Andrew Kelley's todo: - the 'add beat+bassline' button in the beat+bassline editor is misleading - I say we remove it and rely on the song editor to add beat+basslines - make it so that 3xosc notes don't max out - implement note detuning (used to be ctrl+click to access note detuning) (need some other intuitive way to access note detuning as ctrl, shift, and alt are all being used) -- make the horizontal mouse wheel do the same thing as shift+vertical mouse wheel - copy-pasted automation patterns have to be manually linked back to their knob for some reason diff --git a/include/midi.h b/include/midi.h index 88b95cd74..adc88dfb2 100644 --- a/include/midi.h +++ b/include/midi.h @@ -26,8 +26,8 @@ #ifndef _MIDI_H #define _MIDI_H - #include "lmms_basics.h" +#include "panning_constants.h" #include @@ -87,6 +87,9 @@ const int MidiControllerCount = 128; const int MidiProgramCount = 128; const int MidiMaxVelocity = 127; +const int MidiMaxPanning = 127; +const int MidiMinPanning = -128; + struct midiEvent { @@ -145,11 +148,24 @@ struct midiEvent { return m_data.m_param[1]; } + + inline Sint16 midiPanning( void ) const + { + return m_data.m_param[1]; + } inline volume getVolume( void ) const { return (volume)( velocity() * 100 / MidiMaxVelocity ); } + + inline panning getPanning( void ) const + { + return (panning) ( PanningLeft + + ( (float)( midiPanning() - MidiMinPanning ) ) / + ( (float)( MidiMaxPanning - MidiMinPanning ) ) * + ( (float)( PanningRight - PanningLeft ) ) ); + } MidiEventTypes m_type; // MIDI event type diff --git a/include/panning.h b/include/panning.h index 0ff61c38e..07dfd94d3 100644 --- a/include/panning.h +++ b/include/panning.h @@ -1,5 +1,5 @@ /* - * panning.h - declaration of some constants and types, concerning the + * panning.h - declaration of some types, concerning the * panning of a note * * Copyright (c) 2004-2008 Tobias Doerffel @@ -29,11 +29,7 @@ #include "lmms_basics.h" #include "volume.h" #include "templates.h" - -const panning PanningRight = ( 0 + 100 ); -const panning PanningLeft = - PanningRight; -const panning PanningCenter = 0; -const panning DefaultPanning = PanningCenter; +#include "panning_constants.h" inline stereoVolumeVector panningToVolumeVector( panning _p, float _scale = 1.0f ) diff --git a/include/panning_constants.h b/include/panning_constants.h new file mode 100644 index 000000000..90fc01673 --- /dev/null +++ b/include/panning_constants.h @@ -0,0 +1,34 @@ +/* + * panning_constants.h - declaration of some constants, concerning the + * panning of a note + * + * 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. + * + */ + +#ifndef _PANNING_CONSTANTS_H +#define _PANNING_CONSTANTS_H + +const panning PanningRight = ( 0 + 100 ); +const panning PanningLeft = - PanningRight; +const panning PanningCenter = 0; +const panning DefaultPanning = PanningCenter; + +#endif diff --git a/include/piano_roll.h b/include/piano_roll.h index 8353787e0..1295559d0 100644 --- a/include/piano_roll.h +++ b/include/piano_roll.h @@ -139,7 +139,7 @@ private: ModeSelect, ModeMove, ModeOpen - } ; + }; enum actions { @@ -147,18 +147,23 @@ private: ActionMoveNote, ActionResizeNote, ActionSelectNotes, - ActionMoveSelection, - ActionChangeNoteVolume, - ActionChangeNotePanning, - ActionResizeNoteEditArea, - } ; + ActionChangeNoteProperty, + ActionResizeNoteEditArea + }; + + enum noteEditMode + { + NoteEditVolume, + NoteEditPanning, + NoteEditCount + }; enum pianoRollKeyTypes { PR_WHITE_KEY_SMALL, PR_WHITE_KEY_BIG, PR_BLACK_KEY - } ; + }; pianoRoll( void ); @@ -230,6 +235,7 @@ private: note * m_currentNote; actions m_action; + noteEditMode m_noteEditMode; Uint32 m_selectStartTick; int m_selectedTick; diff --git a/src/gui/piano_roll.cpp b/src/gui/piano_roll.cpp index 764e3f5ff..79ece54c5 100644 --- a/src/gui/piano_roll.cpp +++ b/src/gui/piano_roll.cpp @@ -144,6 +144,7 @@ pianoRoll::pianoRoll( void ) : m_recording( false ), m_currentNote( NULL ), m_action( ActionNone ), + m_noteEditMode( NoteEditVolume ), m_lastMouseX( 0 ), m_lastMouseY( 0 ), m_oldNotesEditHeight( 100 ), @@ -1144,8 +1145,6 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) if( _me->y() > PR_TOP_MARGIN ) { - volume vol = DefaultVolume; - bool edit_note = ( _me->y() > noteEditTop() ); int key_num = getKey( _me->y() ); @@ -1204,36 +1203,9 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) // 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 = ActionChangeNoteVolume; - key_num = ( *it )->key(); - - testPlayNote( *it ); - - } + // scribble note edit changes + mouseMoveEvent( _me ); + return; } // left button?? else if( _me->button() == Qt::LeftButton && @@ -1434,7 +1406,7 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) update(); } - else + else if( _me->y() < keyAreaBottom() ) { // clicked on keyboard on the left - play note m_lastKey = key_num; @@ -1446,6 +1418,16 @@ void pianoRoll::mousePressEvent( QMouseEvent * _me ) midiTime() ); } } + else + { + // clicked in the box below the keys to the left of note edit area + m_noteEditMode = (noteEditMode)(((int)m_noteEditMode)+1); + if( m_noteEditMode > 1 ) + { + m_noteEditMode = (noteEditMode)0; + } + repaint(); + } } } @@ -1662,11 +1644,10 @@ void pianoRoll::mouseMoveEvent( QMouseEvent * _me ) // 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 == ActionMoveNote || m_action == ActionMoveSelection ) - || ( x < WHITE_KEY_WIDTH && m_action == ActionNone ) ) && - edit_note == false && - _me->buttons() & Qt::LeftButton ) + if( key_num != m_lastKey && edit_note == false && + _me->buttons() & Qt::LeftButton && + ( m_action == ActionMoveNote || + ( x < WHITE_KEY_WIDTH && m_action == ActionNone ) ) ) { m_pattern->getInstrumentTrack()->processInEvent( midiEvent( MidiNoteOff, 0, m_lastKey, 0 ), @@ -1699,10 +1680,10 @@ void pianoRoll::mouseMoveEvent( QMouseEvent * _me ) // handle moving notes and resizing them dragNotes(_me->x(), _me->y(), _me->modifiers() & Qt::AltModifier); } - else if( ( edit_note == true || m_action == ActionChangeNoteVolume ) && + else if( ( edit_note == true || m_action == ActionChangeNoteProperty ) && _me->buttons() & Qt::LeftButton ) { - // Volume Bars + // editing note properties // Change notes within a certain pixel range of where // the mouse cursor is @@ -1718,12 +1699,17 @@ void pianoRoll::mouseMoveEvent( QMouseEvent * _me ) // get note-vector of current pattern const noteVector & notes = m_pattern->notes(); - // determine what volume to set note to - volume vol = tLimit( + // determine what volume/panning to set note to + volume vol = tLimit( MinVolume + ( ( (float)noteEditBottom() ) - ( (float)_me->y() ) ) / ( (float)( noteEditBottom() - noteEditTop() ) ) * ( MaxVolume - MinVolume ), MinVolume, MaxVolume ); + panning pan = tLimit( PanningLeft + + ( (float)( noteEditBottom() - _me->y() ) ) / + ( (float)( noteEditBottom() - noteEditTop() ) ) * + ( (float)( PanningRight - PanningLeft ) ), + PanningLeft, PanningRight); // loop through vector bool use_selection = isSelection(); @@ -1735,19 +1721,35 @@ void pianoRoll::mouseMoveEvent( QMouseEvent * _me ) && ( *it )->length().getTicks() > 0 && ( ( *it )->selected() || ! use_selection ) ) { - ( *it )->setVolume( vol ); m_pattern->dataChanged(); // play the note so that the user can tell how loud it is + // and where it is panned testPlayNote( *it ); - m_pattern->getInstrumentTrack()->processInEvent( - midiEvent( - MidiKeyPressure, - 0, - ( *it )->key(), - vol * 127 / 100 ), - midiTime() ); + if( m_noteEditMode == NoteEditVolume ) + { + ( *it )->setVolume( vol ); + m_pattern->getInstrumentTrack()->processInEvent( + midiEvent( + MidiKeyPressure, + 0, + ( *it )->key(), + vol * 127 / 100), + midiTime() ); + } + else if( m_noteEditMode == NoteEditPanning ) + { + ( *it )->setPanning( pan ); + midiEvent evt( MidiMetaEvent, 0, ( *it )->key(), + MidiMinPanning + + ( (float)( pan - PanningLeft ) ) / + ( (float)( PanningRight - PanningLeft ) ) * + ( (float)( MidiMaxPanning - MidiMinPanning ) ) ); + evt.m_metaEvent = MidiNotePanning; + m_pattern->getInstrumentTrack()->processInEvent( + evt, midiTime() ); + } } else { @@ -2381,7 +2383,8 @@ void pianoRoll::paintEvent( QPaintEvent * _pe ) - // following code draws all notes in visible area + volume-lines + // following code draws all notes in visible area + // and the note editing stuff (volume, panning, etc) // setup selection-vars int sel_pos_start = m_selectStartTick; @@ -2410,7 +2413,7 @@ void pianoRoll::paintEvent( QPaintEvent * _pe ) const int visible_keys = ( keyAreaBottom()-keyAreaTop() ) / KEY_LINE_HEIGHT + 2; - QPolygon volumeHandles; + QPolygon editHandles; for( noteVector::const_iterator it = notes.begin(); it != notes.end(); ++it ) @@ -2453,22 +2456,46 @@ void pianoRoll::paintEvent( QPaintEvent * _pe ) ( *it )->selected(), ( *it )->length() < 0 ); } - // draw volume-line of note - QColor color = QColor::fromHsv( 120, 221, - qMin(255, 60 + ( *it )->getVolume() ) ); - p.setPen( QPen( color, - NE_LINE_WIDTH ) ); - int volumeHandleTop = noteEditBottom() - - ( (float)( ( *it )->getVolume() - MinVolume ) ) / - ( (float)( MaxVolume - MinVolume ) ) * - ( (float)( noteEditBottom() - noteEditTop() ) ); - p.drawLine( noteEditLeft() + x, volumeHandleTop, - noteEditLeft() + x, noteEditBottom() ); + // draw note editing stuff + int editHandleTop = 0; + if( m_noteEditMode == NoteEditVolume ) + { + QColor color = QColor::fromHsv( 120, 221, + qMin(255, 60 + ( *it )->getVolume() ) ); + p.setPen( QPen( color, + NE_LINE_WIDTH ) ); + + editHandleTop = noteEditBottom() - + ( (float)( ( *it )->getVolume() - MinVolume ) ) / + ( (float)( MaxVolume - MinVolume ) ) * + ( (float)( noteEditBottom() - noteEditTop() ) ); + + p.drawLine( noteEditLeft() + x, editHandleTop, + noteEditLeft() + x, noteEditBottom() ); - - volumeHandles << QPoint( x + noteEditLeft() + 1, - volumeHandleTop+1 ); + } + else if( m_noteEditMode == NoteEditPanning ) + { + QColor color( 0xFF, 0xB0, 0x00 ); + if( ( *it )->selected() ) + { + color.setRgb( 0x00, 0x40, 0xC0 ); + } + + p.setPen( QPen( color, NE_LINE_WIDTH ) ); + + editHandleTop = noteEditBottom() - + ( (float)( ( *it )->getPanning() - PanningLeft ) ) / + ( (float)( (PanningRight - PanningLeft ) ) ) * + ( (float)( noteEditBottom() - noteEditTop() ) ); + + p.drawLine( noteEditLeft() + x, noteEditTop() + + ( (float)( noteEditBottom() - noteEditTop() ) ) / 2.0f, + noteEditLeft() + x, editHandleTop ); + } + editHandles << QPoint( x + noteEditLeft() + 1, + editHandleTop+1 ); if( ( *it )->hasDetuningInfo() ) { @@ -2480,7 +2507,7 @@ void pianoRoll::paintEvent( QPaintEvent * _pe ) p.setPen( QPen( QColor( 0xEA, 0xA1, 0x00 ), NE_LINE_WIDTH*2 ) ); - p.drawPoints( volumeHandles ); + p.drawPoints( editHandles ); } else @@ -2543,8 +2570,7 @@ void pianoRoll::paintEvent( QPaintEvent * _pe ) { cursor = s_toolErase; } - else if( m_action == ActionMoveSelection || - m_action == ActionMoveNote ) + else if( m_action == ActionMoveNote ) { cursor = s_toolMove; } @@ -2622,7 +2648,8 @@ void pianoRoll::wheelEvent( QWheelEvent * _we ) m_timeLine->setPixelsPerTact( m_ppt ); update(); } - else if( engine::getMainWindow()->isShiftPressed() ) + else if( engine::getMainWindow()->isShiftPressed() + || _we->orientation() == Qt::Horizontal ) { m_leftRightScroll->setValue( m_leftRightScroll->value() - _we->delta() * 2 / 15 ); diff --git a/src/tracks/instrument_track.cpp b/src/tracks/instrument_track.cpp index 33c3d57c8..42d400e1f 100644 --- a/src/tracks/instrument_track.cpp +++ b/src/tracks/instrument_track.cpp @@ -176,13 +176,12 @@ void instrumentTrack::processAudioBuffer( sampleFrame * _buf, } m_audioPort.setNextFxChannel( m_effectChannelModel.value() ); - engine::getMixer()->bufferToPort( _buf, - ( _n != NULL ) ? qMin( - _n->framesLeftForCurrentPeriod(), _frames ) : + engine::getMixer()->bufferToPort( _buf, ( _n != NULL ) ? qMin(_n->framesLeftForCurrentPeriod(), _frames ) : _frames, ( _n != NULL ) ? _n->offset() : 0, - panningToVolumeVector( (int) m_panningModel.value(), - v_scale ), + panningToVolumeVector( + (int) m_panningModel.value() + _n->getPanning(), + v_scale ), &m_audioPort ); } @@ -296,7 +295,24 @@ void instrumentTrack::processInEvent( const midiEvent & _me, case MidiProgramChange: m_instrument->handleMidiEvent( _me, _time ); break; - + + case MidiMetaEvent: + // handle special cases such as note panning + switch( _me.m_metaEvent ) + { + case MidiNotePanning: + if( m_notes[_me.key()] != NULL ) + { + m_notes[_me.key()]->setPanning( _me.getPanning() ); + } + break; + default: + printf( "instrument-track: unhandled " + "MIDI meta event: %i\n", _me.m_metaEvent ); + break; + } + break; + default: if( !m_instrument->handleMidiEvent( _me, _time ) ) {