1 /*
2 * Pattern.cpp - implementation of class pattern which holds notes
3 *
4 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
5 * Copyright (c) 2005-2007 Danny McRae <khjklujn/at/yahoo.com>
6 *
7 * This file is part of LMMS - https://lmms.io
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public
20 * License along with this program (see COPYING); if not, write to the
21 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301 USA.
23 *
24 */
25 #include "Pattern.h"
26
27 #include <QTimer>
28 #include <QMenu>
29 #include <QMouseEvent>
30 #include <QPainter>
31 #include <QPushButton>
32
33 #include "InstrumentTrack.h"
34 #include "gui_templates.h"
35 #include "embed.h"
36 #include "GuiApplication.h"
37 #include "PianoRoll.h"
38 #include "RenameDialog.h"
39 #include "SampleBuffer.h"
40 #include "AudioSampleRecorder.h"
41 #include "BBTrackContainer.h"
42 #include "StringPairDrag.h"
43 #include "MainWindow.h"
44
45
46 QPixmap * PatternView::s_stepBtnOn0 = NULL;
47 QPixmap * PatternView::s_stepBtnOn200 = NULL;
48 QPixmap * PatternView::s_stepBtnOff = NULL;
49 QPixmap * PatternView::s_stepBtnOffLight = NULL;
50
51
52
Pattern(InstrumentTrack * _instrument_track)53 Pattern::Pattern( InstrumentTrack * _instrument_track ) :
54 TrackContentObject( _instrument_track ),
55 m_instrumentTrack( _instrument_track ),
56 m_patternType( BeatPattern ),
57 m_steps( MidiTime::stepsPerTact() )
58 {
59 setName( _instrument_track->name() );
60 if( _instrument_track->trackContainer()
61 == Engine::getBBTrackContainer() )
62 {
63 resizeToFirstTrack();
64 }
65 init();
66 setAutoResize( true );
67 }
68
69
70
71
Pattern(const Pattern & other)72 Pattern::Pattern( const Pattern& other ) :
73 TrackContentObject( other.m_instrumentTrack ),
74 m_instrumentTrack( other.m_instrumentTrack ),
75 m_patternType( other.m_patternType ),
76 m_steps( other.m_steps )
77 {
78 for( NoteVector::ConstIterator it = other.m_notes.begin(); it != other.m_notes.end(); ++it )
79 {
80 m_notes.push_back( new Note( **it ) );
81 }
82
83 init();
84 switch( getTrack()->trackContainer()->type() )
85 {
86 case TrackContainer::BBContainer:
87 setAutoResize( true );
88 break;
89
90 case TrackContainer::SongContainer:
91 // move down
92 default:
93 setAutoResize( false );
94 break;
95 }
96 }
97
98
~Pattern()99 Pattern::~Pattern()
100 {
101 emit destroyedPattern( this );
102
103 for( NoteVector::Iterator it = m_notes.begin();
104 it != m_notes.end(); ++it )
105 {
106 delete *it;
107 }
108
109 m_notes.clear();
110 }
111
112
113
114
resizeToFirstTrack()115 void Pattern::resizeToFirstTrack()
116 {
117 // Resize this track to be the same as existing tracks in the BB
118 const TrackContainer::TrackList & tracks =
119 m_instrumentTrack->trackContainer()->tracks();
120 for(unsigned int trackID = 0; trackID < tracks.size(); ++trackID)
121 {
122 if(tracks.at(trackID)->type() == Track::InstrumentTrack)
123 {
124 if(tracks.at(trackID) != m_instrumentTrack)
125 {
126 unsigned int currentTCO = m_instrumentTrack->
127 getTCOs().indexOf(this);
128 m_steps = static_cast<Pattern *>
129 (tracks.at(trackID)->getTCO(currentTCO))
130 ->m_steps;
131 }
132 break;
133 }
134 }
135 }
136
137
138
139
init()140 void Pattern::init()
141 {
142 connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ),
143 this, SLOT( changeTimeSignature() ) );
144 saveJournallingState( false );
145
146 updateLength();
147 restoreJournallingState();
148 }
149
150
151
152
updateLength()153 void Pattern::updateLength()
154 {
155 if( m_patternType == BeatPattern )
156 {
157 changeLength( beatPatternLength() );
158 updateBBTrack();
159 return;
160 }
161
162 tick_t max_length = MidiTime::ticksPerTact();
163
164 for( NoteVector::ConstIterator it = m_notes.begin();
165 it != m_notes.end(); ++it )
166 {
167 if( ( *it )->length() > 0 )
168 {
169 max_length = qMax<tick_t>( max_length,
170 ( *it )->endPos() );
171 }
172 }
173 changeLength( MidiTime( max_length ).nextFullTact() *
174 MidiTime::ticksPerTact() );
175 updateBBTrack();
176 }
177
178
179
180
beatPatternLength() const181 MidiTime Pattern::beatPatternLength() const
182 {
183 tick_t max_length = MidiTime::ticksPerTact();
184
185 for( NoteVector::ConstIterator it = m_notes.begin();
186 it != m_notes.end(); ++it )
187 {
188 if( ( *it )->length() < 0 )
189 {
190 max_length = qMax<tick_t>( max_length,
191 ( *it )->pos() + 1 );
192 }
193 }
194
195 if( m_steps != MidiTime::stepsPerTact() )
196 {
197 max_length = m_steps * MidiTime::ticksPerTact() /
198 MidiTime::stepsPerTact();
199 }
200
201 return MidiTime( max_length ).nextFullTact() * MidiTime::ticksPerTact();
202 }
203
204
205
206
addNote(const Note & _new_note,const bool _quant_pos)207 Note * Pattern::addNote( const Note & _new_note, const bool _quant_pos )
208 {
209 Note * new_note = new Note( _new_note );
210 if( _quant_pos && gui->pianoRoll() )
211 {
212 new_note->quantizePos( gui->pianoRoll()->quantization() );
213 }
214
215 instrumentTrack()->lock();
216 m_notes.insert(std::upper_bound(m_notes.begin(), m_notes.end(), new_note, Note::lessThan), new_note);
217 instrumentTrack()->unlock();
218
219 checkType();
220 updateLength();
221
222 emit dataChanged();
223
224 return new_note;
225 }
226
227
228
229
removeNote(Note * _note_to_del)230 void Pattern::removeNote( Note * _note_to_del )
231 {
232 instrumentTrack()->lock();
233 NoteVector::Iterator it = m_notes.begin();
234 while( it != m_notes.end() )
235 {
236 if( *it == _note_to_del )
237 {
238 delete *it;
239 m_notes.erase( it );
240 break;
241 }
242 ++it;
243 }
244 instrumentTrack()->unlock();
245
246 checkType();
247 updateLength();
248
249 emit dataChanged();
250 }
251
252
253 // returns a pointer to the note at specified step, or NULL if note doesn't exist
254
noteAtStep(int _step)255 Note * Pattern::noteAtStep( int _step )
256 {
257 for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end();
258 ++it )
259 {
260 if( ( *it )->pos() == MidiTime::stepPosition( _step )
261 && ( *it )->length() < 0 )
262 {
263 return *it;
264 }
265 }
266 return NULL;
267 }
268
269
270
rearrangeAllNotes()271 void Pattern::rearrangeAllNotes()
272 {
273 // sort notes by start time
274 std::sort(m_notes.begin(), m_notes.end(), Note::lessThan);
275 }
276
277
278
clearNotes()279 void Pattern::clearNotes()
280 {
281 instrumentTrack()->lock();
282 for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end();
283 ++it )
284 {
285 delete *it;
286 }
287 m_notes.clear();
288 instrumentTrack()->unlock();
289
290 checkType();
291 emit dataChanged();
292 }
293
294
295
296
addStepNote(int step)297 Note * Pattern::addStepNote( int step )
298 {
299 return addNote( Note( MidiTime( -DefaultTicksPerTact ),
300 MidiTime::stepPosition( step ) ), false );
301 }
302
303
304
305
setStep(int step,bool enabled)306 void Pattern::setStep( int step, bool enabled )
307 {
308 if( enabled )
309 {
310 if ( !noteAtStep( step ) )
311 {
312 addStepNote( step );
313 }
314 return;
315 }
316
317 while( Note * note = noteAtStep( step ) )
318 {
319 removeNote( note );
320 }
321 }
322
323
324
325
setType(PatternTypes _new_pattern_type)326 void Pattern::setType( PatternTypes _new_pattern_type )
327 {
328 if( _new_pattern_type == BeatPattern ||
329 _new_pattern_type == MelodyPattern )
330 {
331 m_patternType = _new_pattern_type;
332 }
333 }
334
335
336
337
checkType()338 void Pattern::checkType()
339 {
340 NoteVector::Iterator it = m_notes.begin();
341 while( it != m_notes.end() )
342 {
343 if( ( *it )->length() > 0 )
344 {
345 setType( MelodyPattern );
346 return;
347 }
348 ++it;
349 }
350 setType( BeatPattern );
351 }
352
353
354
355
saveSettings(QDomDocument & _doc,QDomElement & _this)356 void Pattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
357 {
358 _this.setAttribute( "type", m_patternType );
359 _this.setAttribute( "name", name() );
360 // as the target of copied/dragged pattern is always an existing
361 // pattern, we must not store actual position, instead we store -1
362 // which tells loadSettings() not to mess around with position
363 if( _this.parentNode().nodeName() == "clipboard" ||
364 _this.parentNode().nodeName() == "dnddata" )
365 {
366 _this.setAttribute( "pos", -1 );
367 }
368 else
369 {
370 _this.setAttribute( "pos", startPosition() );
371 }
372 _this.setAttribute( "muted", isMuted() );
373 _this.setAttribute( "steps", m_steps );
374
375 // now save settings of all notes
376 for( NoteVector::Iterator it = m_notes.begin();
377 it != m_notes.end(); ++it )
378 {
379 ( *it )->saveState( _doc, _this );
380 }
381 }
382
383
384
385
loadSettings(const QDomElement & _this)386 void Pattern::loadSettings( const QDomElement & _this )
387 {
388 m_patternType = static_cast<PatternTypes>( _this.attribute( "type"
389 ).toInt() );
390 setName( _this.attribute( "name" ) );
391 if( _this.attribute( "pos" ).toInt() >= 0 )
392 {
393 movePosition( _this.attribute( "pos" ).toInt() );
394 }
395 if( _this.attribute( "muted" ).toInt() != isMuted() )
396 {
397 toggleMute();
398 }
399
400 clearNotes();
401
402 QDomNode node = _this.firstChild();
403 while( !node.isNull() )
404 {
405 if( node.isElement() &&
406 !node.toElement().attribute( "metadata" ).toInt() )
407 {
408 Note * n = new Note;
409 n->restoreState( node.toElement() );
410 m_notes.push_back( n );
411 }
412 node = node.nextSibling();
413 }
414
415 m_steps = _this.attribute( "steps" ).toInt();
416 if( m_steps == 0 )
417 {
418 m_steps = MidiTime::stepsPerTact();
419 }
420
421 checkType();
422 updateLength();
423
424 emit dataChanged();
425 }
426
427
428
429
previousPattern() const430 Pattern * Pattern::previousPattern() const
431 {
432 return adjacentPatternByOffset(-1);
433 }
434
435
436
437
nextPattern() const438 Pattern * Pattern::nextPattern() const
439 {
440 return adjacentPatternByOffset(1);
441 }
442
443
444
445
adjacentPatternByOffset(int offset) const446 Pattern * Pattern::adjacentPatternByOffset(int offset) const
447 {
448 QVector<TrackContentObject *> tcos = m_instrumentTrack->getTCOs();
449 int tcoNum = m_instrumentTrack->getTCONum(this);
450 return dynamic_cast<Pattern*>(tcos.value(tcoNum + offset, NULL));
451 }
452
453
454
455
clear()456 void Pattern::clear()
457 {
458 addJournalCheckPoint();
459 clearNotes();
460 }
461
462
463
464
addSteps()465 void Pattern::addSteps()
466 {
467 m_steps += MidiTime::stepsPerTact();
468 updateLength();
469 emit dataChanged();
470 }
471
cloneSteps()472 void Pattern::cloneSteps()
473 {
474 int oldLength = m_steps;
475 m_steps *= 2; // cloning doubles the track
476 for(int i = 0; i < oldLength; ++i )
477 {
478 Note *toCopy = noteAtStep( i );
479 if( toCopy )
480 {
481 setStep( oldLength + i, true );
482 Note *newNote = noteAtStep( oldLength + i );
483 newNote->setKey( toCopy->key() );
484 newNote->setLength( toCopy->length() );
485 newNote->setPanning( toCopy->getPanning() );
486 newNote->setVolume( toCopy->getVolume() );
487 }
488 }
489 updateLength();
490 emit dataChanged();
491 }
492
493
494
495
removeSteps()496 void Pattern::removeSteps()
497 {
498 int n = MidiTime::stepsPerTact();
499 if( n < m_steps )
500 {
501 for( int i = m_steps - n; i < m_steps; ++i )
502 {
503 setStep( i, false );
504 }
505 m_steps -= n;
506 updateLength();
507 emit dataChanged();
508 }
509 }
510
511
512
513
createView(TrackView * _tv)514 TrackContentObjectView * Pattern::createView( TrackView * _tv )
515 {
516 return new PatternView( this, _tv );
517 }
518
519
520
521
updateBBTrack()522 void Pattern::updateBBTrack()
523 {
524 if( getTrack()->trackContainer() == Engine::getBBTrackContainer() )
525 {
526 Engine::getBBTrackContainer()->updateBBTrack( this );
527 }
528
529 if( gui && gui->pianoRoll() && gui->pianoRoll()->currentPattern() == this )
530 {
531 gui->pianoRoll()->update();
532 }
533 }
534
535
536
537
empty()538 bool Pattern::empty()
539 {
540 for( NoteVector::ConstIterator it = m_notes.begin();
541 it != m_notes.end(); ++it )
542 {
543 if( ( *it )->length() != 0 )
544 {
545 return false;
546 }
547 }
548 return true;
549 }
550
551
552
553
changeTimeSignature()554 void Pattern::changeTimeSignature()
555 {
556 MidiTime last_pos = MidiTime::ticksPerTact() - 1;
557 for( NoteVector::ConstIterator cit = m_notes.begin();
558 cit != m_notes.end(); ++cit )
559 {
560 if( ( *cit )->length() < 0 && ( *cit )->pos() > last_pos )
561 {
562 last_pos = ( *cit )->pos()+MidiTime::ticksPerTact() /
563 MidiTime::stepsPerTact();
564 }
565 }
566 last_pos = last_pos.nextFullTact() * MidiTime::ticksPerTact();
567 m_steps = qMax<tick_t>( MidiTime::stepsPerTact(),
568 last_pos.getTact() * MidiTime::stepsPerTact() );
569 updateLength();
570 }
571
572
573
574
575
PatternView(Pattern * pattern,TrackView * parent)576 PatternView::PatternView( Pattern* pattern, TrackView* parent ) :
577 TrackContentObjectView( pattern, parent ),
578 m_pat( pattern ),
579 m_paintPixmap()
580 {
581 connect( gui->pianoRoll(), SIGNAL( currentPatternChanged() ),
582 this, SLOT( update() ) );
583
584 if( s_stepBtnOn0 == NULL )
585 {
586 s_stepBtnOn0 = new QPixmap( embed::getIconPixmap(
587 "step_btn_on_0" ) );
588 }
589
590 if( s_stepBtnOn200 == NULL )
591 {
592 s_stepBtnOn200 = new QPixmap( embed::getIconPixmap(
593 "step_btn_on_200" ) );
594 }
595
596 if( s_stepBtnOff == NULL )
597 {
598 s_stepBtnOff = new QPixmap( embed::getIconPixmap(
599 "step_btn_off" ) );
600 }
601
602 if( s_stepBtnOffLight == NULL )
603 {
604 s_stepBtnOffLight = new QPixmap( embed::getIconPixmap(
605 "step_btn_off_light" ) );
606 }
607
608 update();
609
610 setStyle( QApplication::style() );
611 }
612
613
614
615
616
617
~PatternView()618 PatternView::~PatternView()
619 {
620 }
621
622
623
624
625
update()626 void PatternView::update()
627 {
628 if ( m_pat->m_patternType == Pattern::BeatPattern )
629 {
630 ToolTip::add( this,
631 tr( "use mouse wheel to set velocity of a step" ) );
632 }
633 else
634 {
635 ToolTip::add( this,
636 tr( "double-click to open in Piano Roll" ) );
637 }
638
639 TrackContentObjectView::update();
640 }
641
642
643
644
openInPianoRoll()645 void PatternView::openInPianoRoll()
646 {
647 gui->pianoRoll()->setCurrentPattern( m_pat );
648 gui->pianoRoll()->parentWidget()->show();
649 gui->pianoRoll()->show();
650 gui->pianoRoll()->setFocus();
651 }
652
653
654
655
resetName()656 void PatternView::resetName()
657 {
658 m_pat->setName( m_pat->m_instrumentTrack->name() );
659 }
660
661
662
663
changeName()664 void PatternView::changeName()
665 {
666 QString s = m_pat->name();
667 RenameDialog rename_dlg( s );
668 rename_dlg.exec();
669 m_pat->setName( s );
670 }
671
672
673
674
constructContextMenu(QMenu * _cm)675 void PatternView::constructContextMenu( QMenu * _cm )
676 {
677 QAction * a = new QAction( embed::getIconPixmap( "piano" ),
678 tr( "Open in piano-roll" ), _cm );
679 _cm->insertAction( _cm->actions()[0], a );
680 connect( a, SIGNAL( triggered( bool ) ),
681 this, SLOT( openInPianoRoll() ) );
682 _cm->insertSeparator( _cm->actions()[1] );
683
684 _cm->addSeparator();
685
686 _cm->addAction( embed::getIconPixmap( "edit_erase" ),
687 tr( "Clear all notes" ), m_pat, SLOT( clear() ) );
688 _cm->addSeparator();
689
690 _cm->addAction( embed::getIconPixmap( "reload" ), tr( "Reset name" ),
691 this, SLOT( resetName() ) );
692 _cm->addAction( embed::getIconPixmap( "edit_rename" ),
693 tr( "Change name" ),
694 this, SLOT( changeName() ) );
695
696 if ( m_pat->type() == Pattern::BeatPattern )
697 {
698 _cm->addSeparator();
699
700 _cm->addAction( embed::getIconPixmap( "step_btn_add" ),
701 tr( "Add steps" ), m_pat, SLOT( addSteps() ) );
702 _cm->addAction( embed::getIconPixmap( "step_btn_remove" ),
703 tr( "Remove steps" ), m_pat, SLOT( removeSteps() ) );
704 _cm->addAction( embed::getIconPixmap( "step_btn_duplicate" ),
705 tr( "Clone Steps" ), m_pat, SLOT( cloneSteps() ) );
706 }
707 }
708
709
710
711
mousePressEvent(QMouseEvent * _me)712 void PatternView::mousePressEvent( QMouseEvent * _me )
713 {
714 if( _me->button() == Qt::LeftButton &&
715 m_pat->m_patternType == Pattern::BeatPattern &&
716 ( fixedTCOs() || pixelsPerTact() >= 96 ) &&
717 _me->y() > height() - s_stepBtnOff->height() )
718
719 // when mouse button is pressed in beat/bassline -mode
720
721 {
722 // get the step number that was clicked on and
723 // do calculations in floats to prevent rounding errors...
724 float tmp = ( ( float(_me->x()) - TCO_BORDER_WIDTH ) *
725 float( m_pat -> m_steps ) ) / float(width() - TCO_BORDER_WIDTH*2);
726
727 int step = int( tmp );
728
729 // debugging to ensure we get the correct step...
730 // qDebug( "Step (%f) %d", tmp, step );
731
732 if( step >= m_pat->m_steps )
733 {
734 qDebug( "Something went wrong in pattern.cpp: step %d doesn't exist in pattern!", step );
735 return;
736 }
737
738 Note * n = m_pat->noteAtStep( step );
739
740 if( n == NULL )
741 {
742 m_pat->addStepNote( step );
743 }
744 else // note at step found
745 {
746 m_pat->addJournalCheckPoint();
747 m_pat->setStep( step, false );
748 }
749
750 Engine::getSong()->setModified();
751 update();
752
753 if( gui->pianoRoll()->currentPattern() == m_pat )
754 {
755 gui->pianoRoll()->update();
756 }
757 }
758 else
759
760 // if not in beat/bassline -mode, let parent class handle the event
761
762 {
763 TrackContentObjectView::mousePressEvent( _me );
764 }
765 }
766
mouseDoubleClickEvent(QMouseEvent * _me)767 void PatternView::mouseDoubleClickEvent(QMouseEvent *_me)
768 {
769 if( _me->button() != Qt::LeftButton )
770 {
771 _me->ignore();
772 return;
773 }
774 if( m_pat->m_patternType == Pattern::MelodyPattern || !fixedTCOs() )
775 {
776 openInPianoRoll();
777 }
778 }
779
780
781
782
wheelEvent(QWheelEvent * _we)783 void PatternView::wheelEvent( QWheelEvent * _we )
784 {
785 if( m_pat->m_patternType == Pattern::BeatPattern &&
786 ( fixedTCOs() || pixelsPerTact() >= 96 ) &&
787 _we->y() > height() - s_stepBtnOff->height() )
788 {
789 // get the step number that was wheeled on and
790 // do calculations in floats to prevent rounding errors...
791 float tmp = ( ( float(_we->x()) - TCO_BORDER_WIDTH ) *
792 float( m_pat -> m_steps ) ) / float(width() - TCO_BORDER_WIDTH*2);
793
794 int step = int( tmp );
795
796 if( step >= m_pat->m_steps )
797 {
798 return;
799 }
800
801 Note * n = m_pat->noteAtStep( step );
802 if( !n && _we->delta() > 0 )
803 {
804 n = m_pat->addStepNote( step );
805 n->setVolume( 0 );
806 }
807 if( n != NULL )
808 {
809 int vol = n->getVolume();
810
811 if( _we->delta() > 0 )
812 {
813 n->setVolume( qMin( 100, vol + 5 ) );
814 }
815 else
816 {
817 n->setVolume( qMax( 0, vol - 5 ) );
818 }
819
820 Engine::getSong()->setModified();
821 update();
822 if( gui->pianoRoll()->currentPattern() == m_pat )
823 {
824 gui->pianoRoll()->update();
825 }
826 }
827 _we->accept();
828 }
829 else
830 {
831 TrackContentObjectView::wheelEvent( _we );
832 }
833 }
834
835
836
837
paintEvent(QPaintEvent *)838 void PatternView::paintEvent( QPaintEvent * )
839 {
840 QPainter painter( this );
841
842 if( !needsUpdate() )
843 {
844 painter.drawPixmap( 0, 0, m_paintPixmap );
845 return;
846 }
847
848 setNeedsUpdate( false );
849
850 m_paintPixmap = m_paintPixmap.isNull() == true || m_paintPixmap.size() != size()
851 ? QPixmap( size() ) : m_paintPixmap;
852
853 QPainter p( &m_paintPixmap );
854
855 QLinearGradient lingrad( 0, 0, 0, height() );
856 QColor c;
857 bool muted = m_pat->getTrack()->isMuted() || m_pat->isMuted();
858 bool current = gui->pianoRoll()->currentPattern() == m_pat;
859 bool beatPattern = m_pat->m_patternType == Pattern::BeatPattern;
860
861 // state: selected, normal, beat pattern, muted
862 c = isSelected() ? selectedColor() : ( ( !muted && !beatPattern )
863 ? painter.background().color() : ( beatPattern
864 ? BBPatternBackground() : mutedBackgroundColor() ) );
865
866 // invert the gradient for the background in the B&B editor
867 lingrad.setColorAt( beatPattern ? 0 : 1, c.darker( 300 ) );
868 lingrad.setColorAt( beatPattern ? 1 : 0, c );
869
870 // paint a black rectangle under the pattern to prevent glitches with transparent backgrounds
871 p.fillRect( rect(), QColor( 0, 0, 0 ) );
872
873 if( gradient() )
874 {
875 p.fillRect( rect(), lingrad );
876 }
877 else
878 {
879 p.fillRect( rect(), c );
880 }
881
882 const float ppt = fixedTCOs() ?
883 ( parentWidget()->width() - 2 * TCO_BORDER_WIDTH )
884 / (float) m_pat->length().getTact() :
885 ( width() - TCO_BORDER_WIDTH )
886 / (float) m_pat->length().getTact();
887
888 const int x_base = TCO_BORDER_WIDTH;
889
890 // melody pattern paint event
891 if( m_pat->m_patternType == Pattern::MelodyPattern )
892 {
893 if( m_pat->m_notes.size() > 0 )
894 {
895 // first determine the central tone so that we can
896 // display the area where most of the m_notes are
897 // also calculate min/max tones so the tonal range can be
898 // properly stretched accross the pattern vertically
899
900 int central_key = 0;
901 int max_key = 0;
902 int min_key = 9999999;
903 int total_notes = 0;
904
905 for( NoteVector::Iterator it = m_pat->m_notes.begin();
906 it != m_pat->m_notes.end(); ++it )
907 {
908 max_key = qMax( max_key, ( *it )->key() );
909 min_key = qMin( min_key, ( *it )->key() );
910 central_key += ( *it )->key();
911 ++total_notes;
912 }
913
914 if( total_notes > 0 )
915 {
916 central_key = central_key / total_notes;
917 const int keyrange = qMax( qMax( max_key - central_key, central_key - min_key ), 1 );
918
919 // debug code
920 // qDebug( "keyrange: %d", keyrange );
921
922 // determine height of the pattern view, sans borders
923 const int ht = (height() - 1 - TCO_BORDER_WIDTH * 2) -1;
924
925 // determine maximum height value for drawing bounds checking
926 const int max_ht = height() - 1 - TCO_BORDER_WIDTH;
927
928 // set colour based on mute status
929 p.setPen( muted ? mutedColor() : painter.pen().brush().color() );
930
931 // scan through all the notes and draw them on the pattern
932 for( NoteVector::Iterator it =
933 m_pat->m_notes.begin();
934 it != m_pat->m_notes.end(); ++it )
935 {
936 // calculate relative y-position
937 const float y_key =
938 ( float( central_key - ( *it )->key() ) / keyrange + 1.0f ) / 2;
939 // multiply that by pattern height
940 const int y_pos = static_cast<int>( TCO_BORDER_WIDTH + y_key * ht ) + 1;
941
942 // debug code
943 // if( ( *it )->length() > 0 ) qDebug( "key %d, central_key %d, y_key %f, y_pos %d", ( *it )->key(), central_key, y_key, y_pos );
944
945 // check that note isn't out of bounds, and has a length
946 if( y_pos >= TCO_BORDER_WIDTH &&
947 y_pos <= max_ht )
948 {
949 // calculate start and end x-coords of the line to be drawn
950 int length = ( *it )->length();
951 length = length > 0 ? length : 4;
952 const int x1 = x_base +
953 static_cast<int>
954 ( ( *it )->pos() * ( ppt / MidiTime::ticksPerTact() ) );
955 const int x2 = x_base +
956 static_cast<int>
957 ( ( ( *it )->pos() + length ) * ( ppt / MidiTime::ticksPerTact() ) );
958
959 // check bounds, draw line
960 if( x1 < width() - TCO_BORDER_WIDTH )
961 p.drawLine( x1, y_pos,
962 qMin( x2, width() - TCO_BORDER_WIDTH ), y_pos );
963 }
964 }
965 }
966 }
967 }
968
969 // beat pattern paint event
970 else if( beatPattern && ( fixedTCOs() || ppt >= 96 ) )
971 {
972 QPixmap stepon0;
973 QPixmap stepon200;
974 QPixmap stepoff;
975 QPixmap stepoffl;
976 const int steps = qMax( 1,
977 m_pat->m_steps );
978 const int w = width() - 2 * TCO_BORDER_WIDTH;
979
980 // scale step graphics to fit the beat pattern length
981 stepon0 = s_stepBtnOn0->scaled( w / steps,
982 s_stepBtnOn0->height(),
983 Qt::IgnoreAspectRatio,
984 Qt::SmoothTransformation );
985 stepon200 = s_stepBtnOn200->scaled( w / steps,
986 s_stepBtnOn200->height(),
987 Qt::IgnoreAspectRatio,
988 Qt::SmoothTransformation );
989 stepoff = s_stepBtnOff->scaled( w / steps,
990 s_stepBtnOff->height(),
991 Qt::IgnoreAspectRatio,
992 Qt::SmoothTransformation );
993 stepoffl = s_stepBtnOffLight->scaled( w / steps,
994 s_stepBtnOffLight->height(),
995 Qt::IgnoreAspectRatio,
996 Qt::SmoothTransformation );
997
998 for( int it = 0; it < steps; it++ ) // go through all the steps in the beat pattern
999 {
1000 Note * n = m_pat->noteAtStep( it );
1001
1002 // figure out x and y coordinates for step graphic
1003 const int x = TCO_BORDER_WIDTH + static_cast<int>( it * w / steps );
1004 const int y = height() - s_stepBtnOff->height() - 1;
1005
1006 if( n )
1007 {
1008 const int vol = n->getVolume();
1009 p.drawPixmap( x, y, stepoffl );
1010 p.drawPixmap( x, y, stepon0 );
1011 p.setOpacity( sqrt( vol / 200.0 ) );
1012 p.drawPixmap( x, y, stepon200 );
1013 p.setOpacity( 1 );
1014 }
1015 else if( ( it / 4 ) % 2 )
1016 {
1017 p.drawPixmap( x, y, stepoffl );
1018 }
1019 else
1020 {
1021 p.drawPixmap( x, y, stepoff );
1022 }
1023 } // end for loop
1024
1025 // draw a transparent rectangle over muted patterns
1026 if ( muted )
1027 {
1028 p.setBrush( mutedBackgroundColor() );
1029 p.setOpacity( 0.5 );
1030 p.drawRect( 0, 0, width(), height() );
1031 }
1032 }
1033
1034 // bar lines
1035 const int lineSize = 3;
1036 p.setPen( c.darker( 200 ) );
1037
1038 for( tact_t t = 1; t < m_pat->length().getTact(); ++t )
1039 {
1040 p.drawLine( x_base + static_cast<int>( ppt * t ) - 1,
1041 TCO_BORDER_WIDTH, x_base + static_cast<int>(
1042 ppt * t ) - 1, TCO_BORDER_WIDTH + lineSize );
1043 p.drawLine( x_base + static_cast<int>( ppt * t ) - 1,
1044 rect().bottom() - ( lineSize + TCO_BORDER_WIDTH ),
1045 x_base + static_cast<int>( ppt * t ) - 1,
1046 rect().bottom() - TCO_BORDER_WIDTH );
1047 }
1048
1049 // pattern name
1050 p.setRenderHint( QPainter::TextAntialiasing );
1051
1052 bool isDefaultName = m_pat->name() == m_pat->instrumentTrack()->name();
1053
1054 if( !isDefaultName && m_staticTextName.text() != m_pat->name() )
1055 {
1056 m_staticTextName.setText( m_pat->name() );
1057 }
1058
1059 QFont font;
1060 font.setHintingPreference( QFont::PreferFullHinting );
1061 font.setPointSize( 8 );
1062 p.setFont( font );
1063
1064 const int textTop = TCO_BORDER_WIDTH + 1;
1065 const int textLeft = TCO_BORDER_WIDTH + 1;
1066
1067 if( !isDefaultName )
1068 {
1069 p.setPen( textShadowColor() );
1070 p.drawStaticText( textLeft + 1, textTop + 1, m_staticTextName );
1071 p.setPen( textColor() );
1072 p.drawStaticText( textLeft, textTop, m_staticTextName );
1073 }
1074
1075 // inner border
1076 if( !( fixedTCOs() && beatPattern ) )
1077 {
1078 p.setPen( c.lighter( current ? 160 : 130 ) );
1079 p.drawRect( 1, 1, rect().right() - TCO_BORDER_WIDTH,
1080 rect().bottom() - TCO_BORDER_WIDTH );
1081
1082 // outer border
1083 p.setPen( ( current && !beatPattern ) ? c.lighter( 130 ) : c.darker( 300 ) );
1084 p.drawRect( 0, 0, rect().right(), rect().bottom() );
1085 }
1086 // draw the 'muted' pixmap only if the pattern was manualy muted
1087 if( m_pat->isMuted() )
1088 {
1089 const int spacing = TCO_BORDER_WIDTH;
1090 const int size = 14;
1091 p.drawPixmap( spacing, height() - ( size + spacing ),
1092 embed::getIconPixmap( "muted", size, size ) );
1093 }
1094
1095 p.end();
1096
1097 painter.drawPixmap( 0, 0, m_paintPixmap );
1098
1099 }
1100