Files
lmms/src/tracks/Pattern.cpp
2015-10-30 16:44:14 -04:00

1224 lines
26 KiB
C++

/*
* Pattern.cpp - implementation of class pattern which holds notes
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2005-2007 Danny McRae <khjklujn/at/yahoo.com>
*
* This file is part of LMMS - http://lmms.io
*
* 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.
*
*/
#include <QDomElement>
#include <QTimer>
#include <QMenu>
#include <QMessageBox>
#include <QMouseEvent>
#include <QPainter>
#include <QProgressBar>
#include <QPushButton>
#include <QtAlgorithms>
#include "Pattern.h"
#include "InstrumentTrack.h"
#include "templates.h"
#include "gui_templates.h"
#include "embed.h"
#include "GuiApplication.h"
#include "PianoRoll.h"
#include "TrackContainer.h"
#include "RenameDialog.h"
#include "SampleBuffer.h"
#include "AudioSampleRecorder.h"
#include "Song.h"
#include "ToolTip.h"
#include "BBTrackContainer.h"
#include "StringPairDrag.h"
#include "MainWindow.h"
QPixmap * PatternView::s_stepBtnOn = NULL;
QPixmap * PatternView::s_stepBtnOverlay = NULL;
QPixmap * PatternView::s_stepBtnOff = NULL;
QPixmap * PatternView::s_stepBtnOffLight = NULL;
Pattern::Pattern( InstrumentTrack * _instrument_track ) :
TrackContentObject( _instrument_track ),
m_instrumentTrack( _instrument_track ),
m_patternType( BeatPattern ),
m_steps( MidiTime::stepsPerTact() )
{
setName( _instrument_track->name() );
init();
setAutoResize( true );
}
Pattern::Pattern( const Pattern& other ) :
TrackContentObject( other.m_instrumentTrack ),
m_instrumentTrack( other.m_instrumentTrack ),
m_patternType( other.m_patternType ),
m_steps( other.m_steps )
{
for( NoteVector::ConstIterator it = other.m_notes.begin(); it != other.m_notes.end(); ++it )
{
m_notes.push_back( new Note( **it ) );
}
init();
switch( getTrack()->trackContainer()->type() )
{
case TrackContainer::BBContainer:
setAutoResize( true );
break;
case TrackContainer::SongContainer:
// move down
default:
setAutoResize( false );
break;
}
}
Pattern::~Pattern()
{
emit destroyedPattern( this );
for( NoteVector::Iterator it = m_notes.begin();
it != m_notes.end(); ++it )
{
delete *it;
}
m_notes.clear();
}
void Pattern::init()
{
connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ),
this, SLOT( changeTimeSignature() ) );
saveJournallingState( false );
ensureBeatNotes();
changeLength( length() );
restoreJournallingState();
}
MidiTime Pattern::length() const
{
if( m_patternType == BeatPattern )
{
return beatPatternLength();
}
tick_t max_length = MidiTime::ticksPerTact();
for( NoteVector::ConstIterator it = m_notes.begin();
it != m_notes.end(); ++it )
{
if( ( *it )->length() > 0 )
{
max_length = qMax<tick_t>( max_length,
( *it )->endPos() );
}
}
return MidiTime( max_length ).nextFullTact() *
MidiTime::ticksPerTact();
}
MidiTime Pattern::beatPatternLength() const
{
tick_t max_length = MidiTime::ticksPerTact();
for( NoteVector::ConstIterator it = m_notes.begin();
it != m_notes.end(); ++it )
{
if( ( *it )->length() < 0 )
{
max_length = qMax<tick_t>( max_length,
( *it )->pos() +
MidiTime::ticksPerTact() /
MidiTime::stepsPerTact() );
}
}
if( m_steps != MidiTime::stepsPerTact() )
{
max_length = m_steps * MidiTime::ticksPerTact() /
MidiTime::stepsPerTact() ;
}
return MidiTime( max_length ).nextFullTact() * MidiTime::ticksPerTact();
}
Note * Pattern::addNote( const Note & _new_note, const bool _quant_pos )
{
Note * new_note = new Note( _new_note );
if( _quant_pos && gui->pianoRoll() )
{
new_note->quantizePos( gui->pianoRoll()->quantization() );
}
instrumentTrack()->lock();
if( m_notes.size() == 0 || m_notes.back()->pos() <= new_note->pos() )
{
m_notes.push_back( new_note );
}
else
{
// simple algorithm for inserting the note between two
// notes with smaller and greater position
// maybe it could be optimized by starting in the middle and
// going forward or backward but note-inserting isn't that
// time-critical since it is usually not done while playing...
long new_note_abs_time = new_note->pos();
NoteVector::Iterator it = m_notes.begin();
while( it != m_notes.end() &&
( *it )->pos() < new_note_abs_time )
{
++it;
}
m_notes.insert( it, new_note );
}
instrumentTrack()->unlock();
checkType();
changeLength( length() );
emit dataChanged();
updateBBTrack();
return new_note;
}
void Pattern::removeNote( const Note * _note_to_del )
{
instrumentTrack()->lock();
NoteVector::Iterator it = m_notes.begin();
while( it != m_notes.end() )
{
if( *it == _note_to_del )
{
delete *it;
m_notes.erase( it );
break;
}
++it;
}
instrumentTrack()->unlock();
checkType();
changeLength( length() );
emit dataChanged();
updateBBTrack();
}
// returns a pointer to the note at specified step, or NULL if note doesn't exist
Note * Pattern::noteAtStep( int _step )
{
for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end();
++it )
{
if( ( *it )->pos() == ( _step * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact() )
{
return *it;
}
}
return NULL;
}
Note * Pattern::rearrangeNote( const Note * _note_to_proc,
const bool _quant_pos )
{
// just rearrange the position of the note by removing it and adding
// a copy of it -> addNote inserts it at the correct position
Note copy_of_note( *_note_to_proc );
removeNote( _note_to_proc );
return addNote( copy_of_note, _quant_pos );
}
void Pattern::rearrangeAllNotes()
{
// sort notes by start time
qSort(m_notes.begin(), m_notes.end(), Note::lessThan );
}
void Pattern::clearNotes()
{
instrumentTrack()->lock();
for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end();
++it )
{
delete *it;
}
m_notes.clear();
instrumentTrack()->unlock();
checkType();
emit dataChanged();
}
void Pattern::setStep( int _step, bool _enabled )
{
for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end();
++it )
{
if( ( *it )->pos() == ( _step * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact() &&
( *it )->length() <= 0 )
{
( *it )->setLength( _enabled ?
-DefaultTicksPerTact : 0 );
}
}
}
void Pattern::setType( PatternTypes _new_pattern_type )
{
if( _new_pattern_type == BeatPattern ||
_new_pattern_type == MelodyPattern )
{
m_patternType = _new_pattern_type;
}
}
void Pattern::checkType()
{
NoteVector::Iterator it = m_notes.begin();
while( it != m_notes.end() )
{
if( ( *it )->length() > 0 )
{
setType( Pattern::MelodyPattern );
return;
}
++it;
}
setType( Pattern::BeatPattern );
}
void Pattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
_this.setAttribute( "type", m_patternType );
_this.setAttribute( "name", name() );
// as the target of copied/dragged pattern is always an existing
// pattern, we must not store actual position, instead we store -1
// which tells loadSettings() not to mess around with position
if( _this.parentNode().nodeName() == "clipboard" ||
_this.parentNode().nodeName() == "dnddata" )
{
_this.setAttribute( "pos", -1 );
}
else
{
_this.setAttribute( "pos", startPosition() );
}
_this.setAttribute( "len", length() );
_this.setAttribute( "muted", isMuted() );
_this.setAttribute( "steps", m_steps );
// now save settings of all notes
for( NoteVector::Iterator it = m_notes.begin();
it != m_notes.end(); ++it )
{
if( ( *it )->length() )
{
( *it )->saveState( _doc, _this );
}
}
}
void Pattern::loadSettings( const QDomElement & _this )
{
m_patternType = static_cast<PatternTypes>( _this.attribute( "type"
).toInt() );
setName( _this.attribute( "name" ) );
if( _this.attribute( "pos" ).toInt() >= 0 )
{
movePosition( _this.attribute( "pos" ).toInt() );
}
changeLength( MidiTime( _this.attribute( "len" ).toInt() ) );
if( _this.attribute( "muted" ).toInt() != isMuted() )
{
toggleMute();
}
clearNotes();
QDomNode node = _this.firstChild();
while( !node.isNull() )
{
if( node.isElement() &&
!node.toElement().attribute( "metadata" ).toInt() )
{
Note * n = new Note;
n->restoreState( node.toElement() );
m_notes.push_back( n );
}
node = node.nextSibling();
}
m_steps = _this.attribute( "steps" ).toInt();
if( m_steps == 0 )
{
m_steps = MidiTime::stepsPerTact();
}
ensureBeatNotes();
checkType();
emit dataChanged();
updateBBTrack();
}
Pattern * Pattern::previousPattern() const
{
return adjacentPatternByOffset(-1);
}
Pattern * Pattern::nextPattern() const
{
return adjacentPatternByOffset(1);
}
Pattern * Pattern::adjacentPatternByOffset(int offset) const
{
QVector<TrackContentObject *> tcos = m_instrumentTrack->getTCOs();
int tcoNum = m_instrumentTrack->getTCONum(this);
return dynamic_cast<Pattern*>(tcos.value(tcoNum + offset, NULL));
}
void Pattern::clear()
{
addJournalCheckPoint();
clearNotes();
ensureBeatNotes();
}
void Pattern::addSteps()
{
m_steps += MidiTime::stepsPerTact();
ensureBeatNotes();
emit dataChanged();
updateBBTrack();
}
void Pattern::cloneSteps()
{
int oldLength = m_steps;
m_steps += MidiTime::stepsPerTact();
ensureBeatNotes();
for(int i = 0; i < MidiTime::stepsPerTact(); ++i )
{
Note *toCopy = noteAtStep( i );
if( toCopy )
{
setStep( oldLength + i, true );
Note *newNote = noteAtStep( oldLength + i );
newNote->setKey( toCopy->key() );
newNote->setLength( toCopy->length() );
newNote->setPanning( toCopy->getPanning() );
newNote->setVolume( toCopy->getVolume() );
}
}
ensureBeatNotes();
emit dataChanged();
updateBBTrack();
}
void Pattern::removeSteps()
{
int _n = MidiTime::stepsPerTact();
if( _n < m_steps )
{
for( int i = m_steps - _n; i < m_steps; ++i )
{
for( NoteVector::Iterator it = m_notes.begin();
it != m_notes.end(); ++it )
{
if( ( *it )->pos() ==
( i * MidiTime::ticksPerTact() ) /
MidiTime::stepsPerTact() &&
( *it )->length() <= 0 )
{
removeNote( *it );
break;
}
}
}
m_steps -= _n;
emit dataChanged();
}
updateBBTrack();
}
TrackContentObjectView * Pattern::createView( TrackView * _tv )
{
return new PatternView( this, _tv );
}
void Pattern::ensureBeatNotes()
{
// make sure, that all step-note exist
for( int i = 0; i < m_steps; ++i )
{
bool found = false;
NoteVector::Iterator it;
for( it = m_notes.begin(); it != m_notes.end(); ++it )
{
Note *note = *it;
// if a note in this position is the one we want
if( note->pos() ==
( i * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact()
&& note->length() <= 0 )
{
found = true;
break;
}
}
if( found == false )
{
addNote( Note( MidiTime( 0 ), MidiTime( ( i *
MidiTime::ticksPerTact() ) /
MidiTime::stepsPerTact() ) ), false );
}
}
// remove notes we no longer need:
// that is, disabled notes that no longer fall to the steps of the new time sig
for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); )
{
bool needed = false;
Note *note = *it;
for( int i = 0; i < m_steps; ++i )
{
if( note->pos() == ( i * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact()
|| note->length() != 0 )
{
needed = true;
break;
}
}
if( needed == false )
{
delete note;
it = m_notes.erase( it );
}
else {
++it;
}
}
}
void Pattern::updateBBTrack()
{
if( getTrack()->trackContainer() == Engine::getBBTrackContainer() )
{
Engine::getBBTrackContainer()->updateBBTrack( this );
}
if( gui && gui->pianoRoll() && gui->pianoRoll()->currentPattern() == this )
{
gui->pianoRoll()->update();
}
}
bool Pattern::empty()
{
for( NoteVector::ConstIterator it = m_notes.begin();
it != m_notes.end(); ++it )
{
if( ( *it )->length() != 0 )
{
return false;
}
}
return true;
}
void Pattern::changeTimeSignature()
{
MidiTime last_pos = MidiTime::ticksPerTact();
for( NoteVector::ConstIterator cit = m_notes.begin();
cit != m_notes.end(); ++cit )
{
if( ( *cit )->length() < 0 && ( *cit )->pos() > last_pos )
{
last_pos = ( *cit )->pos()+MidiTime::ticksPerTact() /
MidiTime::stepsPerTact();
}
}
last_pos = last_pos.nextFullTact() * MidiTime::ticksPerTact();
for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); )
{
if( ( *it )->length() == 0 && ( *it )->pos() >= last_pos )
{
delete *it;
it = m_notes.erase( it );
--m_steps;
}
else
{
++it;
}
}
m_steps = qMax<tick_t>(
qMax<tick_t>( m_steps, MidiTime::stepsPerTact() ),
last_pos.getTact() * MidiTime::stepsPerTact() );
ensureBeatNotes();
updateBBTrack();
}
PatternView::PatternView( Pattern* pattern, TrackView* parent ) :
TrackContentObjectView( pattern, parent ),
m_pat( pattern ),
m_paintPixmap(),
m_needsUpdate( true )
{
connect( gui->pianoRoll(), SIGNAL( currentPatternChanged() ),
this, SLOT( update() ) );
if( s_stepBtnOn == NULL )
{
s_stepBtnOn = new QPixmap( embed::getIconPixmap(
"step_btn_on_100" ) );
}
if( s_stepBtnOverlay == NULL )
{
s_stepBtnOverlay = new QPixmap( embed::getIconPixmap(
"step_btn_on_yellow" ) );
}
if( s_stepBtnOff == NULL )
{
s_stepBtnOff = new QPixmap( embed::getIconPixmap(
"step_btn_off" ) );
}
if( s_stepBtnOffLight == NULL )
{
s_stepBtnOffLight = new QPixmap( embed::getIconPixmap(
"step_btn_off_light" ) );
}
setFixedHeight( parentWidget()->height() - 2 );
ToolTip::add( this,
tr( "use mouse wheel to set volume of a step" ) );
setStyle( QApplication::style() );
}
PatternView::~PatternView()
{
}
void PatternView::update()
{
m_needsUpdate = true;
m_pat->changeLength( m_pat->length() );
TrackContentObjectView::update();
}
void PatternView::openInPianoRoll()
{
gui->pianoRoll()->setCurrentPattern( m_pat );
gui->pianoRoll()->parentWidget()->show();
gui->pianoRoll()->show();
gui->pianoRoll()->setFocus();
}
void PatternView::resetName()
{
m_pat->setName( m_pat->m_instrumentTrack->name() );
}
void PatternView::changeName()
{
QString s = m_pat->name();
RenameDialog rename_dlg( s );
rename_dlg.exec();
m_pat->setName( s );
}
void PatternView::constructContextMenu( QMenu * _cm )
{
QAction * a = new QAction( embed::getIconPixmap( "piano" ),
tr( "Open in piano-roll" ), _cm );
_cm->insertAction( _cm->actions()[0], a );
connect( a, SIGNAL( triggered( bool ) ),
this, SLOT( openInPianoRoll() ) );
_cm->insertSeparator( _cm->actions()[1] );
_cm->addSeparator();
_cm->addAction( embed::getIconPixmap( "edit_erase" ),
tr( "Clear all notes" ), m_pat, SLOT( clear() ) );
_cm->addSeparator();
_cm->addAction( embed::getIconPixmap( "reload" ), tr( "Reset name" ),
this, SLOT( resetName() ) );
_cm->addAction( embed::getIconPixmap( "edit_rename" ),
tr( "Change name" ),
this, SLOT( changeName() ) );
if ( m_pat->type() == Pattern::BeatPattern )
{
_cm->addSeparator();
_cm->addAction( embed::getIconPixmap( "step_btn_add" ),
tr( "Add steps" ), m_pat, SLOT( addSteps() ) );
_cm->addAction( embed::getIconPixmap( "step_btn_remove" ),
tr( "Remove steps" ), m_pat, SLOT( removeSteps() ) );
}
}
void PatternView::mousePressEvent( QMouseEvent * _me )
{
if( _me->button() == Qt::LeftButton &&
m_pat->m_patternType == Pattern::BeatPattern &&
( fixedTCOs() || pixelsPerTact() >= 96 ||
m_pat->m_steps != MidiTime::stepsPerTact() ) &&
_me->y() > height() - s_stepBtnOff->height() )
// when mouse button is pressed in beat/bassline -mode
{
// get the step number that was clicked on and
// do calculations in floats to prevent rounding errors...
float tmp = ( ( float(_me->x()) - TCO_BORDER_WIDTH ) *
float( m_pat -> m_steps ) ) / float(width() - TCO_BORDER_WIDTH*2);
int step = int( tmp );
// debugging to ensure we get the correct step...
// qDebug( "Step (%f) %d", tmp, step );
if( step >= m_pat->m_steps )
{
qDebug( "Something went wrong in pattern.cpp: step %d doesn't exist in pattern!", step );
return;
}
Note * n = m_pat->noteAtStep( step );
// if note at step not found, ensureBeatNotes and try again
if( n == NULL )
{
m_pat -> ensureBeatNotes();
n = m_pat->noteAtStep( step );
if( n == NULL ) // still can't find a note? bail!
{
qDebug( "Something went wrong in pattern.cpp: couldn't add note at step %d!", step );
return;
}
}
else // note at step found
{
m_pat->addJournalCheckPoint();
if( n->length() < 0 )
{
n->setLength( 0 ); // set note as enabled beat note
}
else
{
n->setLength( -DefaultTicksPerTact ); // set note as disabled beat note
}
}
Engine::getSong()->setModified();
update();
if( gui->pianoRoll()->currentPattern() == m_pat )
{
gui->pianoRoll()->update();
}
}
else
// if not in beat/bassline -mode, let parent class handle the event
{
TrackContentObjectView::mousePressEvent( _me );
}
}
void PatternView::mouseDoubleClickEvent(QMouseEvent *_me)
{
if( _me->button() != Qt::LeftButton )
{
_me->ignore();
return;
}
if( m_pat->m_patternType == Pattern::MelodyPattern || !fixedTCOs() )
{
openInPianoRoll();
}
}
void PatternView::wheelEvent( QWheelEvent * _we )
{
if( m_pat->m_patternType == Pattern::BeatPattern &&
( fixedTCOs() || pixelsPerTact() >= 96 ||
m_pat->m_steps != MidiTime::stepsPerTact() ) &&
_we->y() > height() - s_stepBtnOff->height() )
{
// get the step number that was wheeled on and
// do calculations in floats to prevent rounding errors...
float tmp = ( ( float(_we->x()) - TCO_BORDER_WIDTH ) *
float( m_pat -> m_steps ) ) / float(width() - TCO_BORDER_WIDTH*2);
int step = int( tmp );
if( step >= m_pat->m_steps )
{
return;
}
int vol = 0;
int len = 0;
Note * n = m_pat->noteAtStep( step );
if( n != NULL )
{
vol = n->getVolume();
len = n->length();
if( len == 0 && _we->delta() > 0 )
{
n->setLength( -DefaultTicksPerTact );
n->setVolume( 5 );
}
else if( _we->delta() > 0 )
{
n->setVolume( qMin( 100, vol + 5 ) );
}
else
{
n->setVolume( qMax( 0, vol - 5 ) );
}
Engine::getSong()->setModified();
update();
if( gui->pianoRoll()->currentPattern() == m_pat )
{
gui->pianoRoll()->update();
}
}
_we->accept();
}
else
{
TrackContentObjectView::wheelEvent( _we );
}
}
void PatternView::paintEvent( QPaintEvent * )
{
if( m_needsUpdate == false )
{
QPainter p( this );
p.drawPixmap( 0, 0, m_paintPixmap );
return;
}
QPainter _p( this );
const QColor styleColor = _p.pen().brush().color();
m_pat->changeLength( m_pat->length() );
m_needsUpdate = false;
if( m_paintPixmap.isNull() == true || m_paintPixmap.size() != size() )
{
m_paintPixmap = QPixmap( size() );
}
QPainter p( &m_paintPixmap );
QLinearGradient lingrad( 0, 0, 0, height() );
QColor c;
if(( m_pat->m_patternType != Pattern::BeatPattern ) &&
!( m_pat->getTrack()->isMuted() || m_pat->isMuted() ))
{
c = styleColor;
}
else
{
c = QColor( 80, 80, 80 );
}
if( isSelected() == true )
{
c.setRgb( qMax( c.red() - 128, 0 ), qMax( c.green() - 128, 0 ), 255 );
}
if( m_pat->m_patternType != Pattern::BeatPattern )
{
lingrad.setColorAt( 1, c.darker( 300 ) );
lingrad.setColorAt( 0, c );
}
else
{
lingrad.setColorAt( 0, c.darker( 300 ) );
lingrad.setColorAt( 1, c );
}
p.setBrush( lingrad );
if( gui->pianoRoll()->currentPattern() == m_pat && m_pat->m_patternType != Pattern::BeatPattern )
p.setPen( c.lighter( 130 ) );
else
p.setPen( c.darker( 300 ) );
p.drawRect( QRect( 0, 0, width() - 1, height() - 1 ) );
p.setBrush( QBrush() );
if( m_pat->m_patternType != Pattern::BeatPattern )
{
if( gui->pianoRoll()->currentPattern() == m_pat )
p.setPen( c.lighter( 160 ) );
else
p.setPen( c.lighter( 130 ) );
p.drawRect( QRect( 1, 1, width() - 3, height() - 3 ) );
}
const float ppt = fixedTCOs() ?
( parentWidget()->width() - 2 * TCO_BORDER_WIDTH )
/ (float) m_pat->length().getTact() :
( width() - 2 * TCO_BORDER_WIDTH )
/ (float) m_pat->length().getTact();
const int x_base = TCO_BORDER_WIDTH;
p.setPen( c.darker( 300 ) );
for( tact_t t = 1; t < m_pat->length().getTact(); ++t )
{
p.drawLine( x_base + static_cast<int>( ppt * t ) - 1,
TCO_BORDER_WIDTH, x_base + static_cast<int>(
ppt * t ) - 1, 5 );
p.drawLine( x_base + static_cast<int>( ppt * t ) - 1,
height() - ( 4 + 2 * TCO_BORDER_WIDTH ),
x_base + static_cast<int>( ppt * t ) - 1,
height() - 2 * TCO_BORDER_WIDTH );
}
// melody pattern paint event
if( m_pat->m_patternType == Pattern::MelodyPattern )
{
if( m_pat->m_notes.size() > 0 )
{
// first determine the central tone so that we can
// display the area where most of the m_notes are
// also calculate min/max tones so the tonal range can be
// properly stretched accross the pattern vertically
int central_key = 0;
int max_key = 0;
int min_key = 9999999;
int total_notes = 0;
for( NoteVector::Iterator it = m_pat->m_notes.begin();
it != m_pat->m_notes.end(); ++it )
{
if( ( *it )->length() > 0 )
{
max_key = qMax( max_key, ( *it )->key() );
min_key = qMin( min_key, ( *it )->key() );
central_key += ( *it )->key();
++total_notes;
}
}
if( total_notes > 0 )
{
central_key = central_key / total_notes;
const int keyrange = qMax( qMax( max_key - central_key, central_key - min_key ), 1 );
// debug code
// qDebug( "keyrange: %d", keyrange );
// determine height of the pattern view, sans borders
const int ht = (height() - 1 - TCO_BORDER_WIDTH * 2) -1;
// determine maximum height value for drawing bounds checking
const int max_ht = height() - 1 - TCO_BORDER_WIDTH;
// set colour based on mute status
if( m_pat->getTrack()->isMuted() ||
m_pat->isMuted() )
{
p.setPen( QColor( 160, 160, 160 ) );
}
else
{
p.setPen( fgColor() );
}
// scan through all the notes and draw them on the pattern
for( NoteVector::Iterator it =
m_pat->m_notes.begin();
it != m_pat->m_notes.end(); ++it )
{
// calculate relative y-position
const float y_key =
( float( central_key - ( *it )->key() ) / keyrange + 1.0f ) / 2;
// multiply that by pattern height
const int y_pos = static_cast<int>( TCO_BORDER_WIDTH + y_key * ht ) + 1;
// debug code
// if( ( *it )->length() > 0 ) qDebug( "key %d, central_key %d, y_key %f, y_pos %d", ( *it )->key(), central_key, y_key, y_pos );
// check that note isn't out of bounds, and has a length
if( ( *it )->length() > 0 &&
y_pos >= TCO_BORDER_WIDTH &&
y_pos <= max_ht )
{
// calculate start and end x-coords of the line to be drawn
const int x1 = x_base +
static_cast<int>
( ( *it )->pos() * ( ppt / MidiTime::ticksPerTact() ) );
const int x2 = x_base +
static_cast<int>
( ( ( *it )->pos() + ( *it )->length() ) * ( ppt / MidiTime::ticksPerTact() ) );
// check bounds, draw line
if( x1 < width() - TCO_BORDER_WIDTH )
p.drawLine( x1, y_pos,
qMin( x2, width() - TCO_BORDER_WIDTH ), y_pos );
}
}
}
}
}
// beat pattern paint event
else if( m_pat->m_patternType == Pattern::BeatPattern &&
( fixedTCOs() || ppt >= 96
|| m_pat->m_steps != MidiTime::stepsPerTact() ) )
{
QPixmap stepon;
QPixmap stepoverlay;
QPixmap stepoff;
QPixmap stepoffl;
const int steps = qMax( 1,
m_pat->m_steps );
const int w = width() - 2 * TCO_BORDER_WIDTH;
// scale step graphics to fit the beat pattern length
stepon = s_stepBtnOn->scaled( w / steps,
s_stepBtnOn->height(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation );
stepoverlay = s_stepBtnOverlay->scaled( w / steps,
s_stepBtnOn->height(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation );
stepoff = s_stepBtnOff->scaled( w / steps,
s_stepBtnOff->height(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation );
stepoffl = s_stepBtnOffLight->scaled( w / steps,
s_stepBtnOffLight->height(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation );
for( int it = 0; it < steps; it++ ) // go through all the steps in the beat pattern
{
Note * n = m_pat->noteAtStep( it );
// figure out x and y coordinates for step graphic
const int x = TCO_BORDER_WIDTH + static_cast<int>( it * w / steps );
const int y = height() - s_stepBtnOff->height() - 1;
// get volume and length of note, if noteAtStep returned null
// (meaning, note at step doesn't exist for some reason)
// then set both at zero, ie. treat as an off step
const int vol = ( n != NULL ? n->getVolume() : 0 );
const int len = ( n != NULL ? int( n->length() ) : 0 );
if( len < 0 )
{
p.drawPixmap( x, y, stepoff );
for( int i = 0; i < vol / 5 + 1; ++i )
{
p.drawPixmap( x, y, stepon );
}
for( int i = 0; i < ( 25 + ( vol - 75 ) ) / 5;
++i )
{
p.drawPixmap( x, y, stepoverlay );
}
}
else if( ( it / 4 ) % 2 )
{
p.drawPixmap( x, y, stepoffl );
}
else
{
p.drawPixmap( x, y, stepoff );
}
} // end for loop
}
p.setFont( pointSize<8>( p.font() ) );
QColor text_color = ( m_pat->isMuted() || m_pat->getTrack()->isMuted() )
? QColor( 30, 30, 30 )
: textColor();
if( m_pat->name() != m_pat->instrumentTrack()->name() )
{
p.setPen( QColor( 0, 0, 0 ) );
p.drawText( 4, p.fontMetrics().height()+1, m_pat->name() );
p.setPen( text_color );
p.drawText( 3, p.fontMetrics().height(), m_pat->name() );
}
if( m_pat->isMuted() )
{
p.drawPixmap( 3, p.fontMetrics().height() + 1,
embed::getIconPixmap( "muted", 16, 16 ) );
}
p.end();
_p.drawPixmap( 0, 0, m_paintPixmap );
}