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