1 /* TIATracker, (c) 2016 Andre "Kylearan" Wichmann.
2  * Website: https://bitbucket.org/kylearan/tiatracker
3  * Email: andre.wichmann@gmx.de
4  * See the file "license.txt" for information on usage and redistribution
5  * of this file.
6  */
7 
8 #include "tracktab.h"
9 #include <QPushButton>
10 #include <QLayout>
11 #include <QButtonGroup>
12 #include <iostream>
13 #include "instrumentselector.h"
14 #include "timeline.h"
15 #include "patterneditor.h"
16 #include <QLabel>
17 #include <QSpinBox>
18 #include "setslidedialog.h"
19 #include <QMessageBox>
20 #include "setfrequencydialog.h"
21 #include "instrumentstab.h"
22 #include "renamepatterndialog.h"
23 #include "setgotodialog.h"
24 #include "mainwindow.h"
25 #include "insertpatterndialog.h"
26 #include "createpatterndialog.h"
27 #include <qcheckbox.h>
28 
29 
TrackTab(QWidget * parent)30 TrackTab::TrackTab(QWidget *parent) : QWidget(parent)
31 {
32     // Global
33     addShortcut(&actionMoveUp, "CursorUp");
34     addShortcut(&actionMoveDown, "CursorDown");
35     addShortcut(&actionLeftChannel, "CursorLeftChannel");
36     addShortcut(&actionRightChannel, "CursorRightChannel");
37     addShortcut(&actionSwitchChannel, "CursorSwitchChannel");
38     addShortcut(&actionFirstRow, "CursorFirstRow");
39     addShortcut(&actionLastRow, "CursorLastRow");
40     addShortcut(&actionNextPattern, "CursorNextPattern");
41     addShortcut(&actionPreviousPattern, "CursorPreviousPattern");
42 
43     // Pattern
44     addShortcut(&actionInsertPatternBefore, "PatternInsertBefore");
45     addShortcut(&actionInsertPatternAfter, "PatternInsertAfter");
46     patternContextMenu.addAction(&actionInsertPatternBefore);
47     patternContextMenu.addAction(&actionInsertPatternAfter);
48     addShortcut(&actionMovePatternUp, "PatternMoveUp");
49     patternContextMenu.addAction(&actionMovePatternUp);
50     addShortcut(&actionMovePatternDown, "PatternMoveDown");
51     patternContextMenu.addAction(&actionMovePatternDown);
52     addShortcut(&actionDuplicatePattern, "PatternDuplicate");
53     patternContextMenu.addAction(&actionDuplicatePattern);
54     addShortcut(&actionRemovePattern, "PatternRemove");
55     patternContextMenu.addAction(&actionRemovePattern);
56     addShortcut(&actionRenamePattern, "PatternRename");
57     patternContextMenu.addAction(&actionRenamePattern);
58     patternContextMenu.addSeparator();
59     addShortcut(&actionSetGoto, "PatternSetGoto");
60     patternContextMenu.addAction(&actionSetGoto);
61     addShortcut(&actionRemoveGoto, "PatternRemoveGoto");
62     patternContextMenu.addAction(&actionRemoveGoto);
63     patternContextMenu.addAction(&actionSetStartPattern);
64     // Channel
65     addShortcut(&actionPause, "NotePause");
66     channelContextMenu.addAction(&actionPause);
67     addShortcut(&actionHold, "NoteHold");
68     channelContextMenu.addAction(&actionHold);
69     addShortcut(&actionSlide, "NoteSlide");
70     channelContextMenu.addAction(&actionSlide);
71     addShortcut(&actionSetFrequency, "NoteFrequency");
72     channelContextMenu.addAction(&actionSetFrequency);
73     channelContextMenu.addSeparator();
74     addShortcut(&actionInsertRowBefore, "RowInsert");
75     channelContextMenu.addAction(&actionInsertRowBefore);
76     channelContextMenu.addAction(&actionInsertRowAfter);
77     addShortcut(&actionDeleteRow, "RowDelete");
78     channelContextMenu.addAction(&actionDeleteRow);
79     channelContextMenu.addSeparator();
80     actionMuteChannel.setCheckable(true);
81     addShortcut(&actionMuteChannel, "ChannelToggleMute");
82     channelContextMenu.addAction(&actionMuteChannel);
83 }
84 
85 /*************************************************************************/
86 
registerTrack(Track::Track * newTrack)87 void TrackTab::registerTrack(Track::Track *newTrack) {
88     pTrack = newTrack;
89 }
90 
91 /*************************************************************************/
92 
registerPitchGuide(TiaSound::PitchGuide * newGuide)93 void TrackTab::registerPitchGuide(TiaSound::PitchGuide *newGuide) {
94     pPitchGuide = newGuide;
95 }
96 
97 /*************************************************************************/
98 
registerPlayer(Emulation::Player * newPlayer)99 void TrackTab::registerPlayer(Emulation::Player *newPlayer) {
100     pPlayer = newPlayer;
101 }
102 
103 /*************************************************************************/
104 
initTrackTab()105 void TrackTab::initTrackTab() {
106     pTrack->updateFirstNoteNumbers();
107 
108     InstrumentSelector *insSel = findChild<InstrumentSelector *>("trackInstrumentSelector");
109     insSel->registerTrack(pTrack);
110     insSel->initSelector();
111     Timeline *timeline = findChild<Timeline *>("trackTimeline");
112     timeline->registerTrack(pTrack);
113     PatternEditor *editor = findChild<PatternEditor *>("trackEditor");
114     editor->registerTrack(pTrack);
115     editor->registerPitchGuide(pPitchGuide);
116     editor->registerMuteAction(&actionMuteChannel);
117     editor->registerInstrumentSelector(insSel);
118 
119     // Gloabl actions
120     QObject::connect(&actionMoveUp, SIGNAL(triggered(bool)), editor, SLOT(moveUp(bool)));
121     QObject::connect(&actionMoveDown, SIGNAL(triggered(bool)), editor, SLOT(moveDown(bool)));
122     QObject::connect(&actionLeftChannel, SIGNAL(triggered(bool)), editor, SLOT(moveLeft(bool)));
123     QObject::connect(&actionRightChannel, SIGNAL(triggered(bool)), editor, SLOT(moveRight(bool)));
124     QObject::connect(&actionSwitchChannel, SIGNAL(triggered(bool)), editor, SLOT(switchChannel(bool)));
125     QObject::connect(&actionFirstRow, SIGNAL(triggered(bool)), editor, SLOT(gotoFirstRow(bool)));
126     QObject::connect(&actionLastRow, SIGNAL(triggered(bool)), editor, SLOT(gotoLastRow(bool)));
127     QObject::connect(&actionNextPattern, SIGNAL(triggered(bool)), editor, SLOT(gotoNextPattern(bool)));
128     QObject::connect(&actionPreviousPattern, SIGNAL(triggered(bool)), editor, SLOT(gotoPreviousPattern(bool)));
129 
130     // Pattern context menu
131     QObject::connect(&actionSetStartPattern, SIGNAL(triggered(bool)), this, SLOT(setStartPattern(bool)));
132     QObject::connect(&actionRenamePattern, SIGNAL(triggered(bool)), this, SLOT(renamePattern(bool)));
133     QObject::connect(&actionSetGoto, SIGNAL(triggered(bool)), this, SLOT(setGoto(bool)));
134     QObject::connect(&actionRemoveGoto, SIGNAL(triggered(bool)), this, SLOT(removeGoto(bool)));
135     QObject::connect(&actionMovePatternUp, SIGNAL(triggered(bool)), this, SLOT(movePatternUp(bool)));
136     QObject::connect(&actionMovePatternDown, SIGNAL(triggered(bool)), this, SLOT(movePatternDown(bool)));
137     QObject::connect(&actionInsertPatternBefore, SIGNAL(triggered(bool)), this, SLOT(insertPatternBefore(bool)));
138     QObject::connect(&actionInsertPatternAfter, SIGNAL(triggered(bool)), this, SLOT(insertPatternAfter(bool)));
139     QObject::connect(&actionRemovePattern, SIGNAL(triggered(bool)), this, SLOT(removePattern(bool)));
140     QObject::connect(&actionDuplicatePattern, SIGNAL(triggered(bool)), this, SLOT(duplicatePattern(bool)));
141 
142     // Channel context menu
143     QObject::connect(&actionSlide, SIGNAL(triggered(bool)), this, SLOT(setSlideValue(bool)));
144     QObject::connect(&actionSetFrequency, SIGNAL(triggered(bool)), this, SLOT(setFrequency(bool)));
145     QObject::connect(&actionHold, SIGNAL(triggered(bool)), this, SLOT(setHold(bool)));
146     QObject::connect(&actionPause, SIGNAL(triggered(bool)), this, SLOT(setPause(bool)));
147     QObject::connect(&actionDeleteRow, SIGNAL(triggered(bool)), this, SLOT(deleteRow(bool)));
148     QObject::connect(&actionInsertRowBefore, SIGNAL(triggered(bool)), this, SLOT(insertRowBefore(bool)));
149     QObject::connect(&actionInsertRowAfter, SIGNAL(triggered(bool)), this, SLOT(insertRowAfter(bool)));
150     QObject::connect(&actionMuteChannel, SIGNAL(triggered(bool)), this, SLOT(toggleMute(bool)));
151 
152     editor->registerPatternMenu(&patternContextMenu);
153     editor->registerChannelMenu(&channelContextMenu);
154     timeline->registerPatternMenu(&patternContextMenu);
155 }
156 
157 /*************************************************************************/
158 
updateTrackTab()159 void TrackTab::updateTrackTab() {
160     // Set GUI elements
161     QCheckBox *cbGlobal = findChild<QCheckBox *>("checkBoxGlobalTempo");
162     cbGlobal->setChecked(pTrack->globalSpeed);
163     QSpinBox *spEven = findChild<QSpinBox *>("spinBoxEvenTempo");
164     QSpinBox *spOdd = findChild<QSpinBox *>("spinBoxOddTempo");
165     if (pTrack->globalSpeed) {
166         spEven->setValue(pTrack->evenSpeed);
167         spOdd->setValue(pTrack->oddSpeed);
168     } else {
169         PatternEditor *pe = findChild<PatternEditor *>("trackEditor");
170         int editPos = pe->getEditPos();
171         if (editPos < pTrack->getChannelNumRows(0)) {
172             int patternIndex = pTrack->getPatternIndex(0, editPos);
173             spEven->setValue(pTrack->patterns[patternIndex].evenSpeed);
174             spOdd->setValue(pTrack->patterns[patternIndex].oddSpeed);
175         }
176     }
177     QSpinBox *spRowsPerBeat = findChild<QSpinBox *>("spinBoxRowsPerBeat");
178     spRowsPerBeat->setValue(pTrack->rowsPerBeat);
179 
180     // Update individual sub-widgets
181     updateTrackStats();
182 }
183 
184 /*************************************************************************/
185 
toggleGlobalTempo(bool toggled)186 void TrackTab::toggleGlobalTempo(bool toggled) {
187     pTrack->globalSpeed = toggled;
188     updateTrackTab();
189     updateTrackStats();
190     updatePatternEditor();
191 }
192 
193 /*************************************************************************/
194 
setEvenSpeed(int value)195 void TrackTab::setEvenSpeed(int value) {
196     if (pTrack->globalSpeed) {
197         pTrack->evenSpeed = value;
198     } else {
199         PatternEditor *pe = findChild<PatternEditor *>("trackEditor");
200         int editPos = pe->getEditPos();
201         // Only the left channel is used for local tempo
202         int patternIndex = pTrack->getPatternIndex(0, editPos);
203         pTrack->patterns[patternIndex].evenSpeed = value;
204     }
205     updateTrackStats();
206     updatePatternEditor();
207 }
208 
209 /*************************************************************************/
210 
setOddSpeed(int value)211 void TrackTab::setOddSpeed(int value) {
212     if (pTrack->globalSpeed) {
213         pTrack->oddSpeed = value;
214     } else {
215         PatternEditor *pe = findChild<PatternEditor *>("trackEditor");
216         int editPos = pe->getEditPos();
217         int patternIndex = pTrack->getPatternIndex(0, editPos);
218         pTrack->patterns[patternIndex].oddSpeed = value;
219     }
220     updateTrackStats();
221     updatePatternEditor();
222 }
223 
224 /*************************************************************************/
225 
channelContextEvent(int channel,int noteIndex)226 void TrackTab::channelContextEvent(int channel, int noteIndex) {
227     contextEventChannel = channel;
228     contextEventNoteIndex = noteIndex;
229 }
230 
231 /*************************************************************************/
232 
toggleFollow(bool)233 void TrackTab::toggleFollow(bool) {
234     QCheckBox *cb = findChild<QCheckBox *>("checkBoxFollow");
235     cb->toggle();
236 }
237 
238 /*************************************************************************/
239 
toggleLoop(bool)240 void TrackTab::toggleLoop(bool) {
241     QCheckBox *cb = findChild<QCheckBox *>("checkBoxLoop");
242     cb->toggle();
243 }
244 
245 /*************************************************************************/
246 
setStartPattern(bool)247 void TrackTab::setStartPattern(bool) {
248     pTrack->lock();
249     pTrack->startPatterns[contextEventChannel] = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
250     pTrack->unlock();
251     update();
252 }
253 
254 /*************************************************************************/
255 
renamePattern(bool)256 void TrackTab::renamePattern(bool) {
257     RenamePatternDialog dialog(this);
258     int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
259     int patternIndex = pTrack->channelSequences[contextEventChannel].sequence[entryIndex].patternIndex;
260     dialog.setPatternName(pTrack->patterns[patternIndex].name);
261     if (dialog.exec() == QDialog::Accepted) {
262         pTrack->patterns[patternIndex].name = dialog.getPatternName();
263         update();
264     }
265 }
266 
267 /*************************************************************************/
268 
setGoto(bool)269 void TrackTab::setGoto(bool) {
270     int maxValue = pTrack->channelSequences[contextEventChannel].sequence.size();
271     SetGotoDialog dialog(this);
272     dialog.setMaxValue(maxValue);
273     int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
274     Track::SequenceEntry *entry = &(pTrack->channelSequences[contextEventChannel].sequence[entryIndex]);
275     dialog.setGotoValue(std::max(1, entry->gotoTarget + 1));
276     if (dialog.exec() == QDialog::Accepted) {
277         pTrack->lock();
278         entry->gotoTarget = dialog.getGotoValue() - 1;
279         pTrack->unlock();
280         update();
281     }
282 
283 }
284 
285 /*************************************************************************/
286 
removeGoto(bool)287 void TrackTab::removeGoto(bool) {
288     pTrack->lock();
289     int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
290     Track::SequenceEntry *entry = &(pTrack->channelSequences[contextEventChannel].sequence[entryIndex]);
291     entry->gotoTarget = -1;
292     pTrack->unlock();
293     update();
294 }
295 
296 /*************************************************************************/
297 
movePatternUp(bool)298 void TrackTab::movePatternUp(bool) {
299     int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
300     if (entryIndex > 0) {
301         emit stopTrack();
302         pTrack->channelSequences[contextEventChannel].sequence.swap(entryIndex, entryIndex - 1);
303         pTrack->updateFirstNoteNumbers();
304         update();
305     }
306 }
307 
308 /*************************************************************************/
309 
movePatternDown(bool)310 void TrackTab::movePatternDown(bool) {
311     int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
312     if (entryIndex != pTrack->channelSequences[contextEventChannel].sequence.size() - 1) {
313         emit stopTrack();
314         pTrack->channelSequences[contextEventChannel].sequence.swap(entryIndex, entryIndex + 1);
315         pTrack->updateFirstNoteNumbers();
316         update();
317     }
318 }
319 
320 /*************************************************************************/
321 
insertPatternBefore(bool)322 void TrackTab::insertPatternBefore(bool) {
323     emit stopTrack();
324     int patternIndex = choosePatternToInsert(true);
325     if (patternIndex == -1) {
326         return;
327     }
328     int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
329     Track::SequenceEntry newEntry(patternIndex);
330     pTrack->channelSequences[contextEventChannel].sequence.insert(entryIndex, newEntry);
331     pTrack->updateFirstNoteNumbers();
332     update();
333 }
334 
335 /*************************************************************************/
336 
insertPatternAfter(bool)337 void TrackTab::insertPatternAfter(bool) {
338     emit stopTrack();
339     int patternIndex = choosePatternToInsert(false);
340     if (patternIndex == -1) {
341         return;
342     }
343     int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
344     Track::SequenceEntry newEntry(patternIndex);
345     pTrack->channelSequences[contextEventChannel].sequence.insert(entryIndex + 1, newEntry);
346     pTrack->updateFirstNoteNumbers();
347     update();
348 }
349 
350 /*************************************************************************/
351 
removePattern(bool)352 void TrackTab::removePattern(bool) {
353     emit stopTrack();
354     if (pTrack->channelSequences[contextEventChannel].sequence.size() == 1) {
355         MainWindow::displayMessage("A channel must contain at least one pattern!");
356         return;
357     }
358 
359     int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
360     int patternIndex = pTrack->channelSequences[contextEventChannel].sequence[entryIndex].patternIndex;
361     pTrack->channelSequences[contextEventChannel].sequence.removeAt(entryIndex);
362     pTrack->updateFirstNoteNumbers();
363     // Check if pattern is no longer used anywhere
364     bool wasLast = true;
365     for (int channel = 0; channel < 2 && wasLast; ++channel) {
366         for (int i = 0; i < pTrack->channelSequences[channel].sequence.size(); ++i) {
367             if (pTrack->channelSequences[channel].sequence[i].patternIndex == patternIndex) {
368                 wasLast = false;
369                 break;
370             }
371         }
372     }
373     if (wasLast) {
374         QMessageBox msgBox(QMessageBox::NoIcon,
375                            "Delete Pattern?",
376                            "This pattern is no longer used in the track. Do you want to delete it?",
377                            QMessageBox::Yes | QMessageBox::No, this,
378                            Qt::FramelessWindowHint);
379         if (msgBox.exec() == QMessageBox::Yes) {
380             // Correct SequenceEntries
381             for (int channel = 0; channel < 2; ++channel) {
382                 for (int i = 0; i < pTrack->channelSequences[channel].sequence.size(); ++i) {
383                     if (pTrack->channelSequences[channel].sequence[i].patternIndex > patternIndex) {
384                         pTrack->channelSequences[channel].sequence[i].patternIndex--;
385                     }
386                 }
387             }
388             pTrack->patterns.removeAt(patternIndex);
389             pTrack->updateFirstNoteNumbers();
390         }
391     }
392     // Validate gotos
393     for (int i = 0; i < pTrack->channelSequences[contextEventChannel].sequence.size(); ++i) {
394         if (pTrack->channelSequences[contextEventChannel].sequence[i].gotoTarget >= entryIndex) {
395             pTrack->channelSequences[contextEventChannel].sequence[i].gotoTarget--;
396         }
397     }
398 
399     emit validateEditPos();
400     update();
401 }
402 
403 /*************************************************************************/
404 
duplicatePattern(bool)405 void TrackTab::duplicatePattern(bool) {
406     emit stopTrack();
407     RenamePatternDialog dialog(this);
408     int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
409     int patternIndex = pTrack->channelSequences[contextEventChannel].sequence[entryIndex].patternIndex;
410     dialog.setPatternName(pTrack->patterns[patternIndex].name);
411     if (dialog.exec() == QDialog::Accepted) {
412         pTrack->patterns.append(pTrack->patterns[patternIndex]);
413         pTrack->patterns.last().name = dialog.getPatternName();
414         int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
415         Track::SequenceEntry newEntry(pTrack->patterns.size() - 1);
416         pTrack->channelSequences[contextEventChannel].sequence.insert(entryIndex + 1, newEntry);
417         pTrack->updateFirstNoteNumbers();
418         update();
419     }
420 }
421 
422 /*************************************************************************/
423 
setSlideValue(bool)424 void TrackTab::setSlideValue(bool) {
425     emit stopTrack();
426     if (!pTrack->checkSlideValidity(contextEventChannel, contextEventNoteIndex)) {
427         MainWindow::displayMessage("A SLIDE can only follow a melodic instrument or another slide!");
428         return;
429     }
430 
431     SetSlideDialog dialog(this);
432     // If selected row already contains a SLIDE, pre-set dialog to that value
433     Track::Note *selectedNote = pTrack->getNote(contextEventChannel, contextEventNoteIndex);
434     if (selectedNote->type == Track::Note::instrumentType::Slide) {
435         dialog.setSlideValue(selectedNote->value);
436     }
437     if (dialog.exec() == QDialog::Accepted) {
438         selectedNote->type = Track::Note::instrumentType::Slide;
439         selectedNote->value = dialog.getSlideValue();
440         emit advanceEditPos();
441         updatePatternEditor();
442     }
443 }
444 
445 /*************************************************************************/
446 
setFrequency(bool)447 void TrackTab::setFrequency(bool) {
448     emit stopTrack();
449     Track::Note *selectedNote = pTrack->getNote(contextEventChannel, contextEventNoteIndex);
450     if (selectedNote->type != Track::Note::instrumentType::Instrument) {
451         MainWindow::displayMessage("Only a melodic instrument can have a frequency value!");
452         return;
453     }
454     SetFrequencyDialog dialog(this);
455     dialog.setFrequencyValue(selectedNote->value);
456     Track::Instrument *ins = &(pTrack->instruments[selectedNote->instrumentNumber]);
457     int maxFreq = ins->baseDistortion == TiaSound::Distortion::PURE_COMBINED ? 63 : 31;
458     dialog.setMaxFrequencyValue(maxFreq);
459     if (dialog.exec() == QDialog::Accepted) {
460         selectedNote->value = dialog.getFrequencyValue();
461         emit advanceEditPos();
462         updatePatternEditor();
463     }
464 }
465 
466 /*************************************************************************/
467 
setHold(bool)468 void TrackTab::setHold(bool) {
469     pTrack->lock();
470     pTrack->getNote(contextEventChannel, contextEventNoteIndex)->type = Track::Note::instrumentType::Hold;
471     pTrack->unlock();
472     emit advanceEditPos();
473     update();
474 }
475 
476 /*************************************************************************/
477 
setPause(bool)478 void TrackTab::setPause(bool) {
479     pTrack->lock();
480     pTrack->getNote(contextEventChannel, contextEventNoteIndex)->type = Track::Note::instrumentType::Pause;
481     pTrack->unlock();
482     emit advanceEditPos();
483     update();
484 }
485 
486 /*************************************************************************/
487 
deleteRow(bool)488 void TrackTab::deleteRow(bool) {
489     emit stopTrack();
490     int patternIndex = pTrack->getPatternIndex(contextEventChannel, contextEventNoteIndex);
491     Track::Pattern *pattern = &(pTrack->patterns[patternIndex]);
492     if (pattern->notes.size() == Track::Pattern::minSize) {
493         MainWindow::displayMessage("Pattern is already at minimum size!");
494         return;
495     }
496 
497     int noteInPattern = pTrack->getNoteIndexInPattern(contextEventChannel, contextEventNoteIndex);
498     pattern->notes.removeAt(noteInPattern);
499     pTrack->updateFirstNoteNumbers();
500     emit validateEditPos();
501     update();
502 }
503 
504 /*************************************************************************/
505 
insertRowBefore(bool)506 void TrackTab::insertRowBefore(bool) {
507     emit stopTrack();
508     int patternIndex = pTrack->getPatternIndex(contextEventChannel, contextEventNoteIndex);
509     Track::Pattern *pattern = &(pTrack->patterns[patternIndex]);
510     if (pattern->notes.size() == Track::Pattern::maxSize) {
511         MainWindow::displayMessage("Pattern is already at maximum size!");
512         return;
513     }
514 
515     int noteInPattern = pTrack->getNoteIndexInPattern(contextEventChannel, contextEventNoteIndex);
516     Track::Note newNote(Track::Note::instrumentType::Hold, 0, 0);
517     pattern->notes.insert(noteInPattern, newNote);
518     pTrack->updateFirstNoteNumbers();
519     update();
520 }
521 
522 /*************************************************************************/
523 
insertRowAfter(bool)524 void TrackTab::insertRowAfter(bool) {
525     emit stopTrack();
526     int patternIndex = pTrack->getPatternIndex(contextEventChannel, contextEventNoteIndex);
527     Track::Pattern *pattern = &(pTrack->patterns[patternIndex]);
528     if (pattern->notes.size() == Track::Pattern::maxSize) {
529         MainWindow::displayMessage("Pattern is already at maximum size!");
530         return;
531     }
532 
533     int noteInPattern = pTrack->getNoteIndexInPattern(contextEventChannel, contextEventNoteIndex);
534     Track::Note newNote(Track::Note::instrumentType::Hold, 0, 0);
535     pattern->notes.insert(noteInPattern + 1, newNote);
536     pTrack->updateFirstNoteNumbers();
537     update();
538 }
539 
540 /*************************************************************************/
541 
toggleMute(bool)542 void TrackTab::toggleMute(bool) {
543     pPlayer->channelMuted[contextEventChannel] = !pPlayer->channelMuted[contextEventChannel];
544 }
545 
546 /*************************************************************************/
547 
invalidNoteFound(int,int,int,QString reason)548 void TrackTab::invalidNoteFound(int, int, int, QString reason) {
549     MainWindow::displayMessage(reason);
550 }
551 
552 /*************************************************************************/
553 
updateTrackStats()554 void TrackTab::updateTrackStats() {
555     // Patterns
556     QLabel *statsLabel = findChild<QLabel *>("labelPatternsUsed");
557     int sequenceLength = pTrack->channelSequences[0].sequence.size() + pTrack->channelSequences[1].sequence.size();
558     int distinct = pTrack->patterns.size();
559     statsLabel->setText("Patterns used: " + QString::number(sequenceLength) + " of 255 ("
560                         + QString::number(distinct) + " distinct)");
561 
562     // Start patterns
563     QLabel *startLabel = findChild<QLabel *>("labelStartPatterns");
564     startLabel->setText("Start patterns: " + QString::number(pTrack->startPatterns[0] + 1)
565                         + "/" + QString::number(pTrack->startPatterns[1] + 1));
566 
567     // Time
568     int numRows = pTrack->getTrackNumRows();
569     long numOddTicks = int((numRows + 1)/2)*pTrack->oddSpeed;
570     long numEvenTicks = int(numRows/2)*pTrack->evenSpeed;
571     long numTicks = numOddTicks + numEvenTicks;
572     int ticksPerSecond = pTrack->getTvMode() == TiaSound::TvStandard::PAL ? 50 : 60;
573     int minutes = numTicks/(ticksPerSecond*60);
574     int seconds = (numTicks%(ticksPerSecond*60))/ticksPerSecond;
575     QString timeText = QString::number(minutes);
576     if (seconds < 10) {
577         timeText.append("m 0");
578     } else {
579         timeText.append("m ");
580     }
581     timeText.append(QString::number(seconds) + "s");
582     QLabel *timeLabel = findChild<QLabel *>("labelTotalLength");
583     timeLabel->setText("Total length: " + timeText);
584     // BPM
585     QLabel *bpmLabel = findChild<QLabel *>("labelBPM");
586     if (pTrack->globalSpeed) {
587         int numTicksSum = pTrack->oddSpeed + pTrack->evenSpeed;
588         double bpm = (60.0*ticksPerSecond)/((numTicksSum/2.0)*pTrack->rowsPerBeat);
589         bpmLabel->setText("(~" + QString::number(bpm, 'f', 2) + " bpm)");
590     } else {
591         bpmLabel->setText("n/a (local tempo)");
592     }
593 }
594 
595 /*************************************************************************/
596 
updatePatternEditor()597 void TrackTab::updatePatternEditor() {
598     PatternEditor *editor = findChild<PatternEditor *>("trackEditor");
599     editor->update();
600 }
601 
602 /*************************************************************************/
603 
choosePatternToInsert(bool doBefore)604 int TrackTab::choosePatternToInsert(bool doBefore) {
605     InsertPatternDialog dialog(this);
606     dialog.prepare(pTrack);
607     if (dialog.exec() == QDialog::Accepted) {
608         int result = dialog.getSelectedPattern();
609         // Check for "create new pattern"
610         if (result == pTrack->patterns.size()) {
611             // Calc index of first note of new pattern, for "align" button
612             int entryIndex = pTrack->getSequenceEntryIndex(contextEventChannel, contextEventNoteIndex);
613             int patternIndex = pTrack->channelSequences[contextEventChannel].sequence[entryIndex].patternIndex;
614             int newRow = pTrack->channelSequences[contextEventChannel].sequence[entryIndex].firstNoteNumber;
615             if (!doBefore) {
616                 newRow += pTrack->patterns[patternIndex].notes.size();
617             }
618             CreatePatternDialog newDialog(this);
619             newDialog.prepare(pTrack, lastNewPatternLength, contextEventChannel, newRow);
620             if (newDialog.exec() == QDialog::Accepted) {
621                 QString newName = newDialog.getName();
622                 int newLength = newDialog.getLength();
623                 Track::Pattern newPattern(newName);
624                 QSpinBox *spEven = findChild<QSpinBox *>("spinBoxEvenTempo");
625                 // Get even/odd speeds from GUI
626                 newPattern.evenSpeed = spEven->value();
627                 QSpinBox *spOdd = findChild<QSpinBox *>("spinBoxOddTempo");
628                 newPattern.oddSpeed = spOdd->value();
629                 // Create notes
630                 for (int i = 0; i < newLength; ++i) {
631                     Track::Note newNote(Track::Note::instrumentType::Hold, 0, 0);
632                     newPattern.notes.append(newNote);
633                 }
634                 // Add new pattern
635                 pTrack->patterns.append(newPattern);
636                 lastNewPatternLength = newLength;
637             } else {
638                 return -1;
639             }
640         }
641         return result;
642     } else {
643         return -1;
644     }
645 }
646 
647 /*************************************************************************/
648 
addShortcut(QAction * action,QString actionName)649 void TrackTab::addShortcut(QAction *action, QString actionName) {
650     if (MainWindow::keymap.contains(actionName)) {
651         QString shortcut = MainWindow::keymap[actionName].toString();
652         action->setShortcut(QKeySequence(shortcut));
653         addAction(action);
654     }
655 }
656