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
This commit is contained in:
Andrew Kelley
2008-12-14 02:39:12 +00:00
parent c23dcee37f
commit e0e7cb9af8
8 changed files with 210 additions and 92 deletions

View File

@@ -31,6 +31,24 @@
- added support for MidiMetaEvents to midiEvent
- added MidiMetaPanning event
2008-12-13 Andrew Kelley <superjoe30/at/gmail/dot/com>
* 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 <superjoe30/at/gmail/dot/com>
* 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

6
TODO
View File

@@ -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

View File

@@ -26,8 +26,8 @@
#ifndef _MIDI_H
#define _MIDI_H
#include "lmms_basics.h"
#include "panning_constants.h"
#include <cstdlib>
@@ -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

View File

@@ -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 <tobydox/at/users.sourceforge.net>
@@ -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 )

View File

@@ -0,0 +1,34 @@
/*
* panning_constants.h - declaration of some constants, concerning the
* panning of a note
*
* Copyright (c) 2004-2008 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* 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

View File

@@ -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;

View File

@@ -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<int>(
// determine what volume/panning to set note to
volume vol = tLimit<int>( MinVolume +
( ( (float)noteEditBottom() ) - ( (float)_me->y() ) ) /
( (float)( noteEditBottom() - noteEditTop() ) ) *
( MaxVolume - MinVolume ),
MinVolume, MaxVolume );
panning pan = tLimit<int>( 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 );

View File

@@ -176,13 +176,12 @@ void instrumentTrack::processAudioBuffer( sampleFrame * _buf,
}
m_audioPort.setNextFxChannel( m_effectChannelModel.value() );
engine::getMixer()->bufferToPort( _buf,
( _n != NULL ) ? qMin<f_cnt_t>(
_n->framesLeftForCurrentPeriod(), _frames ) :
engine::getMixer()->bufferToPort( _buf, ( _n != NULL ) ? qMin<f_cnt_t>(_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 ) )
{