1 /*
2 * Copyright (C) 2018-2020 Rerrah
3 *
4 * Permission is hereby granted, free of charge, to any person
5 * obtaining a copy of this software and associated documentation
6 * files (the "Software"), to deal in the Software without
7 * restriction, including without limitation the rights to use,
8 * copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following
11 * conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 * OTHER DEALINGS IN THE SOFTWARE.
24 */
25
26 #include "mainwindow.hpp"
27 #include "ui_mainwindow.h"
28 #include <algorithm>
29 #include <unordered_map>
30 #include <numeric>
31 #include <QString>
32 #include <QClipboard>
33 #include <QMenu>
34 #include <QAction>
35 #include <QDialog>
36 #include <QRegularExpression>
37 #include <QFileDialog>
38 #include <QFile>
39 #include <QFileInfo>
40 #include <QMimeData>
41 #include <QProgressDialog>
42 #include <QRect>
43 #include <QDesktopWidget>
44 #include <QMetaMethod>
45 #include <QScreen>
46 #include <QComboBox>
47 #include <QToolButton>
48 #include <QSignalBlocker>
49 #include <QTextCodec>
50 #include "jam_manager.hpp"
51 #include "song.hpp"
52 #include "track.hpp"
53 #include "instrument.hpp"
54 #include "bank.hpp"
55 #include "bank_io.hpp"
56 #include "file_io.hpp"
57 #include "version.hpp"
58 #include "gui/command/commands_qt.hpp"
59 #include "gui/instrument_editor/instrument_editor_fm_form.hpp"
60 #include "gui/instrument_editor/instrument_editor_ssg_form.hpp"
61 #include "gui/instrument_editor/instrument_editor_adpcm_form.hpp"
62 #include "gui/instrument_editor/instrument_editor_drumkit_form.hpp"
63 #include "gui/module_properties_dialog.hpp"
64 #include "gui/groove_settings_dialog.hpp"
65 #include "gui/configuration_dialog.hpp"
66 #include "gui/wave_export_settings_dialog.hpp"
67 #include "gui/vgm_export_settings_dialog.hpp"
68 #include "gui/instrument_selection_dialog.hpp"
69 #include "gui/s98_export_settings_dialog.hpp"
70 #include "gui/configuration_handler.hpp"
71 #include "gui/jam_layout.hpp"
72 #include "chips/scci/SCCIDefines.hpp"
73 #include "chips/c86ctl/c86ctl_wrapper.hpp"
74 #include "gui/file_history_handler.hpp"
75 #include "midi/midi.hpp"
76 #include "audio_stream_rtaudio.hpp"
77 #include "color_palette_handler.hpp"
78 #include "binary_container.hpp"
79 #include "wav_container.hpp"
80 #include "enum_hash.hpp"
81 #include "gui/go_to_dialog.hpp"
82 #include "gui/transpose_song_dialog.hpp"
83 #include "gui/swap_tracks_dialog.hpp"
84 #include "gui/hide_tracks_dialog.hpp"
85 #include "gui/track_visibility_memory_handler.hpp"
86 #include "gui/file_io_error_message_box.hpp"
87 #include "gui/gui_util.hpp"
88
MainWindow(std::weak_ptr<Configuration> config,QString filePath,QWidget * parent)89 MainWindow::MainWindow(std::weak_ptr<Configuration> config, QString filePath, QWidget *parent) :
90 QMainWindow(parent),
91 ui(new Ui::MainWindow),
92 config_(config),
93 palette_(std::make_shared<ColorPalette>()),
94 bt_(std::make_shared<BambooTracker>(config)),
95 comStack_(std::make_shared<QUndoStack>(this)),
96 fileHistory_(std::make_shared<FileHistory>()),
97 scciDll_(std::make_unique<QLibrary>("scci")),
98 c86ctlDll_(std::make_unique<QLibrary>("c86ctl")),
99 instForms_(std::make_shared<InstrumentFormManager>()),
100 renamingInstItem_(nullptr),
101 renamingInstEdit_(nullptr),
102 isModifiedForNotCommand_(false),
103 hasLockedWigets_(false),
104 isEditedPattern_(true),
105 isEditedOrder_(false),
106 isEditedInstList_(false),
107 isSelectedPattern_(false),
108 isSelectedOrder_(false),
109 hasShownOnce_(false),
110 firstViewUpdateRequest_(false),
111 octUpSc_(nullptr),
112 octDownSc_(nullptr),
113 focusPtnSc_(this),
114 focusOdrSc_(this),
115 focusInstSc_(this),
116 playAndStopSc_(nullptr),
117 playStepSc_(nullptr),
118 goPrevOdrSc_(nullptr),
119 goNextOdrSc_(nullptr),
120 prevInstSc_(nullptr),
121 nextInstSc_(nullptr),
122 incPtnSizeSc_(nullptr),
123 decPtnSizeSc_(nullptr),
124 incEditStepSc_(nullptr),
125 decEditStepSc_(nullptr),
126 prevSongSc_(nullptr),
127 nextSongSc_(nullptr),
128 jamVolUpSc_(nullptr),
129 jamVolDownSc_(nullptr),
130 bankJamMidiCtrl_(false)
131 {
132 ui->setupUi(this);
133
134 if (config.lock()->getMainWindowX() == -1) { // When unset
135 QRect rec = geometry();
136 rec.moveCenter(QGuiApplication::screens().front()->geometry().center());
137 setGeometry(rec);
138 config.lock()->setMainWindowX(x());
139 config.lock()->setMainWindowY(y());
140 }
141 else {
142 move(config.lock()->getMainWindowX(), config.lock()->getMainWindowY());
143 }
144 resize(config.lock()->getMainWindowWidth(), config.lock()->getMainWindowHeight());
145 if (config.lock()->getMainWindowMaximized()) showMaximized();
146 ui->action_Status_Bar->setChecked(config.lock()->getVisibleStatusBar());
147 ui->statusBar->setVisible(config.lock()->getVisibleStatusBar());
148 ui->actionFollow_Mode->setChecked(config.lock()->getFollowMode());
149 ui->action_Instrument_Mask->setChecked(config.lock()->getInstrumentMask());
150 ui->action_Volume_Mask->setChecked(config.lock()->getVolumeMask());
151 ui->action_Wave_View->setChecked(config.lock()->getVisibleWaveView());
152 ui->waveVisual->setVisible(config.lock()->getVisibleWaveView());
153 bt_->setFollowPlay(config.lock()->getFollowMode());
154 if (config.lock()->getPatternEditorHeaderFont().empty()) {
155 config.lock()->setPatternEditorHeaderFont(ui->patternEditor->getHeaderFont().toStdString());
156 }
157 if (config.lock()->getPatternEditorRowsFont().empty()) {
158 config.lock()->setPatternEditorRowsFont(ui->patternEditor->getRowsFont().toStdString());
159 }
160 if (config.lock()->getOrderListHeaderFont().empty()) {
161 config.lock()->setOrderListHeaderFont(ui->orderList->getHeaderFont().toStdString());
162 }
163 if (config.lock()->getOrderListRowsFont().empty()) {
164 config.lock()->setOrderListRowsFont(ui->orderList->getRowsFont().toStdString());
165 }
166 ui->patternEditor->setConfiguration(config_.lock());
167 ui->orderList->setConfiguration(config_.lock());
168 updateFonts();
169 ui->orderList->setHorizontalScrollMode(config.lock()->getMoveCursorByHorizontalScroll(), false);
170 ui->patternEditor->setHorizontalScrollMode(config.lock()->getMoveCursorByHorizontalScroll(), false);
171 ui->patternEditor->setCore(bt_);
172 ui->orderList->setCore(bt_);
173 ColorPaletteHandler::loadPalette(palette_.get());
174 ui->patternEditor->setColorPallete(palette_);
175 ui->orderList->setColorPallete(palette_);
176 updateInstrumentListColors();
177 ui->waveVisual->setColorPalette(palette_);
178
179 /* Command stack */
180 QObject::connect(comStack_.get(), &QUndoStack::indexChanged,
181 this, [&](int idx) {
182 setWindowModified(idx || isModifiedForNotCommand_);
183 ui->actionUndo->setEnabled(comStack_->canUndo());
184 ui->actionRedo->setEnabled(comStack_->canRedo());
185 });
186
187 /* File history */
188 FileHistoryHandler::loadFileHistory(fileHistory_);
189 for (size_t i = 0; i < fileHistory_->size(); ++i) {
190 // Leave Before Qt5.7.0 style due to windows xp
191 QAction* action = ui->menu_Recent_Files->addAction(QString("&%1 %2").arg(i + 1).arg(fileHistory_->at(i)));
192 action->setData(fileHistory_->at(i));
193 }
194 QObject::connect(ui->menu_Recent_Files, &QMenu::triggered, this, [&](QAction* action) {
195 if (action != ui->actionClear) {
196 if (isWindowModified()) {
197 QString modTitle = utf8ToQString(bt_->getModuleTitle());
198 if (modTitle.isEmpty()) modTitle = tr("Untitled");
199 QMessageBox dialog(QMessageBox::Warning,
200 "BambooTracker",
201 tr("Save changes to %1?").arg(modTitle),
202 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
203 switch (dialog.exec()) {
204 case QMessageBox::Yes:
205 if (!on_actionSave_triggered()) return;
206 break;
207 case QMessageBox::No:
208 break;
209 case QMessageBox::Cancel:
210 return;
211 default:
212 break;
213 }
214 }
215 openModule(action->data().toString());
216 }
217 });
218
219 /* Menus */
220 pasteModeGroup_ = std::make_unique<QActionGroup>(this);
221 pasteModeGroup_->addAction(ui->action_Cursor);
222 QObject::connect(ui->action_Cursor, &QAction::triggered, this, [&](bool checked) {
223 if (checked) config_.lock()->setPasteMode(Configuration::CURSOR);
224 });
225 pasteModeGroup_->addAction(ui->action_Selection);
226 QObject::connect(ui->action_Selection, &QAction::triggered, this, [&](bool checked) {
227 if (checked) config_.lock()->setPasteMode(Configuration::SELECTION);
228 });
229 pasteModeGroup_->addAction(ui->action_Fill);
230 QObject::connect(ui->action_Fill, &QAction::triggered, this, [&](bool checked) {
231 if (checked) config_.lock()->setPasteMode(Configuration::FILL);
232 });
233 switch (config.lock()->getPasteMode()) {
234 case Configuration::CURSOR: ui->action_Cursor->setChecked(true); break;
235 case Configuration::SELECTION: ui->action_Selection->setChecked(true); break;
236 case Configuration::FILL: ui->action_Fill->setChecked(true); break;
237 }
238
239 /* Tool bars */
240 auto octLab = new QLabel(tr("Octave"));
241 octLab->setMargin(6);
242 ui->subToolBar->addWidget(octLab);
243 octave_ = new QSpinBox();
244 octave_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
245 octave_->setMaximumWidth(80);
246 octave_->setMinimum(0);
247 octave_->setMaximum(7);
248 octave_->setValue(bt_->getCurrentOctave());
249 // Leave Before Qt5.7.0 style due to windows xp
250 QObject::connect(octave_, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
251 this, [&](int octave) { bt_->setCurrentOctave(octave); });
252 ui->subToolBar->addWidget(octave_);
253 auto volLab = new QLabel(tr("Volume"));
254 volLab->setMargin(6);
255 ui->subToolBar->addWidget(volLab);
256 volume_ = new QSpinBox();
257 volume_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
258 volume_->setMaximumWidth(100);
259 volume_->setMinimum(0);
260 volume_->setMaximum(255);
261 volume_->setDisplayIntegerBase(16);
262 volume_->setValue(bt_->getCurrentVolume());
263 // Leave Before Qt5.7.0 style due to windows xp
264 QObject::connect(volume_, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
265 this, [&](int volume) { bt_->setCurrentVolume(volume); });
266 ui->subToolBar->addWidget(volume_);
267 ui->subToolBar->addSeparator();
268 ui->subToolBar->addAction(ui->actionFollow_Mode);
269 ui->subToolBar->addSeparator();
270 auto hlLab1 = new QLabel(tr("Step highlight 1st"));
271 hlLab1->setMargin(6);
272 ui->subToolBar->addWidget(hlLab1);
273 highlight1_ = new QSpinBox();
274 highlight1_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
275 highlight1_->setMaximumWidth(80);
276 highlight1_->setMinimum(1);
277 highlight1_->setMaximum(256);
278 highlight1_->setValue(8);
279 // Leave Before Qt5.7.0 style due to windows xp
280 QObject::connect(highlight1_, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
281 this, [&](int count) {
282 bt_->setModuleStepHighlight1Distance(static_cast<size_t>(count));
283 ui->patternEditor->setPatternHighlight1Count(count);
284 });
285 ui->subToolBar->addWidget(highlight1_);
286 auto hlLab2 = new QLabel(tr("2nd"));
287 hlLab2->setMargin(6);
288 ui->subToolBar->addWidget(hlLab2);
289 highlight2_ = new QSpinBox();
290 highlight2_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
291 highlight2_->setMaximumWidth(80);
292 highlight2_->setMinimum(1);
293 highlight2_->setMaximum(256);
294 highlight2_->setValue(8);
295 // Leave Before Qt5.7.0 style due to windows xp
296 QObject::connect(highlight2_, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
297 this, [&](int count) {
298 bt_->setModuleStepHighlight2Distance(static_cast<size_t>(count));
299 ui->patternEditor->setPatternHighlight2Count(count);
300 });
301 ui->subToolBar->addWidget(highlight2_);
302 ui->subToolBar->addSeparator();
303 auto& mainTbConfig = config.lock()->getMainToolbarConfiguration();
304 if (mainTbConfig.getPosition() == Configuration::ToolbarConfiguration::FLOAT_POS) {
305 ui->mainToolBar->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
306 ui->mainToolBar->move(mainTbConfig.getX(), mainTbConfig.getY());
307 }
308 else {
309 addToolBar(TB_POS_.at(mainTbConfig.getPosition()), ui->mainToolBar);
310 }
311 auto& subTbConfig = config.lock()->getSubToolbarConfiguration();
312 if (subTbConfig.getPosition() == Configuration::ToolbarConfiguration::FLOAT_POS) {
313 ui->subToolBar->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
314 ui->subToolBar->move(subTbConfig.getX(), subTbConfig.getY());
315 }
316 else {
317 auto pos = TB_POS_.at(subTbConfig.getPosition());
318 if (subTbConfig.getNumber()) {
319 if (subTbConfig.hasBreakBefore()) addToolBarBreak(pos);
320 addToolBar(pos, ui->subToolBar);
321 }
322 else {
323 if (mainTbConfig.getPosition() == subTbConfig.getPosition()) {
324 insertToolBar(ui->mainToolBar, ui->subToolBar);
325 if (mainTbConfig.hasBreakBefore()) insertToolBarBreak(ui->mainToolBar);
326 }
327 else {
328 addToolBar(pos, ui->subToolBar);
329 }
330 }
331 }
332 ui->action_Toolbar->setChecked(config.lock()->getVisibleToolbar());
333 ui->mainToolBar->setVisible(config.lock()->getVisibleToolbar());
334 ui->subToolBar->setVisible(config.lock()->getVisibleToolbar());
335
336 /* Splitter */
337 ui->splitter->setStretchFactor(0, 0);
338 ui->splitter->setStretchFactor(1, 1);
339
340 /* Module settings */
341 QObject::connect(ui->modTitleLineEdit, &QLineEdit::textEdited,
342 this, [&](QString str) {
343 bt_->setModuleTitle(str.toUtf8().toStdString());
344 setModifiedTrue();
345 setWindowTitle();
346 });
347 QObject::connect(ui->authorLineEdit, &QLineEdit::textEdited,
348 this, [&](QString str) {
349 bt_->setModuleAuthor(str.toUtf8().toStdString());
350 setModifiedTrue();
351 });
352 QObject::connect(ui->copyrightLineEdit, &QLineEdit::textEdited,
353 this, [&](QString str) {
354 bt_->setModuleCopyright(str.toUtf8().toStdString());
355 setModifiedTrue();
356 });
357
358 /* Edit settings */
359 // Leave Before Qt5.7.0 style due to windows xp
360 QObject::connect(ui->editableStepSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
361 this, [&](int n) {
362 ui->patternEditor->setEditableStep(n);
363 config_.lock()->setEditableStep(static_cast<size_t>(n));
364 });
365 ui->editableStepSpinBox->setValue(static_cast<int>(config.lock()->getEditableStep()));
366 ui->patternEditor->setEditableStep(static_cast<int>(config.lock()->getEditableStep()));
367
368 ui->keyRepeatCheckBox->setCheckState(config.lock()->getKeyRepetition() ? Qt::Checked : Qt::Unchecked);
369
370 /* Song */
371 // Leave Before Qt5.7.0 style due to windows xp
372 QObject::connect(ui->songComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
373 this, [&](int num) {
374 if (num == -1) return;
375 freezeViews();
376 if (!timer_) stream_->stop();
377 bt_->setCurrentSongNumber(num);
378 loadSong();
379 if (!timer_) stream_->start();
380 });
381
382 /* Song settings */
383 // Leave Before Qt5.7.0 style due to windows xp
384 QObject::connect(ui->tempoSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
385 this, [&](int tempo) {
386 int curSong = bt_->getCurrentSongNumber();
387 if (tempo != bt_->getSongTempo(curSong)) {
388 bt_->setSongTempo(curSong, tempo);
389 setModifiedTrue();
390 }
391 });
392 // Leave Before Qt5.7.0 style due to windows xp
393 QObject::connect(ui->speedSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
394 this, [&](int speed) {
395 int curSong = bt_->getCurrentSongNumber();
396 if (speed != bt_->getSongSpeed(curSong)) {
397 bt_->setSongSpeed(curSong, speed);
398 setModifiedTrue();
399 }
400 });
401 // Leave Before Qt5.7.0 style due to windows xp
402 QObject::connect(ui->patternSizeSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
403 this, [&](int size) {
404 bt_->setDefaultPatternSize(bt_->getCurrentSongNumber(), static_cast<size_t>(size));
405 ui->patternEditor->onDefaultPatternSizeChanged();
406 setModifiedTrue();
407 });
408 // Leave Before Qt5.7.0 style due to windows xp
409 QObject::connect(ui->grooveSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
410 this, [&](int n) {
411 bt_->setSongGroove(bt_->getCurrentSongNumber(), n);
412 setModifiedTrue();
413 });
414
415 /* Instrument list */
416 auto instToolBar = new QToolBar();
417 instToolBar->setIconSize(QSize(16, 16));
418 auto addMenu = new QMenu();
419 addMenu->addActions({ ui->actionNew_Instrument, ui->actionNew_Drumki_t });
420 auto addTB = new QToolButton();
421 addTB->setPopupMode(QToolButton::MenuButtonPopup);
422 addTB->setMenu(addMenu);
423 addTB->setDefaultAction(ui->actionNew_Instrument);
424 instToolBar->addWidget(addTB);
425 instToolBar->addActions({ ui->actionRemove_Instrument, ui->actionClone_Instrument });
426 instToolBar->addSeparator();
427 instToolBar->addActions({ ui->actionLoad_From_File, ui->actionSave_To_File });
428 instToolBar->addSeparator();
429 instToolBar->addAction(ui->actionEdit);
430 instToolBar->addSeparator();
431 instToolBar->addAction(ui->actionRename_Instrument);
432 ui->instrumentListGroupBox->layout()->addWidget(instToolBar);
433 ui->instrumentList->installEventFilter(this);
434 QObject::connect(ui->instrumentList, &DropDetectListWidget::itemDroppedAtItemIndex,
435 this, &MainWindow::swapInstruments);
436
437 /* Pattern editor */
438 ui->patternEditor->setCommandStack(comStack_);
439 ui->patternEditor->installEventFilter(this);
440 QObject::connect(ui->patternEditor, &PatternEditor::currentTrackChanged,
441 ui->orderList, &OrderListEditor::onPatternEditorCurrentTrackChanged);
442 QObject::connect(ui->patternEditor, &PatternEditor::currentOrderChanged,
443 ui->orderList, &OrderListEditor::onPatternEditorCurrentOrderChanged);
444 QObject::connect(ui->patternEditor, &PatternEditor::focusIn,
445 this, &MainWindow::updateMenuByPattern);
446 QObject::connect(ui->patternEditor, &PatternEditor::selected,
447 this, &MainWindow::updateMenuByPatternSelection);
448 QObject::connect(ui->patternEditor, &PatternEditor::instrumentEntered,
449 this, [&](int num) {
450 auto list = ui->instrumentList;
451 if (num != -1) {
452 for (int i = 0; i < list->count(); ++i) {
453 if (list->item(i)->data(Qt::UserRole).toInt() == num) {
454 list->setCurrentRow(i);
455 return ;
456 }
457 }
458 }
459 });
460 QObject::connect(ui->patternEditor, &PatternEditor::volumeEntered,
461 this, [&](int volume) { volume_->setValue(volume); });
462 QObject::connect(ui->patternEditor, &PatternEditor::effectEntered,
463 this, [&](QString text) { statusDetail_->setText(text); });
464 QObject::connect(ui->patternEditor, &PatternEditor::currentTrackChanged,
465 this, &MainWindow::onCurrentTrackChanged);
466
467 /* Order List */
468 ui->orderList->setCommandStack(comStack_);
469 ui->orderList->installEventFilter(this);
470 QObject::connect(ui->orderList, &OrderListEditor::currentTrackChanged,
471 ui->patternEditor, &PatternEditor::onOrderListCurrentTrackChanged);
472 QObject::connect(ui->orderList, &OrderListEditor::currentOrderChanged,
473 ui->patternEditor, &PatternEditor::onOrderListCrrentOrderChanged);
474 QObject::connect(ui->orderList, &OrderListEditor::orderEdited,
475 ui->patternEditor, &PatternEditor::onOrderListEdited);
476 QObject::connect(ui->orderList, &OrderListEditor::focusIn,
477 this, &MainWindow::updateMenuByOrder);
478 QObject::connect(ui->orderList, &OrderListEditor::selected,
479 this, &MainWindow::updateMenuByOrderSelection);
480 QObject::connect(ui->orderList, &OrderListEditor::currentTrackChanged,
481 this, &MainWindow::onCurrentTrackChanged);
482
483 /* Wave view */
484 visualTimer_.reset(new QTimer);
485 QObject::connect(visualTimer_.get(), &QTimer::timeout, this, &MainWindow::updateVisuals);
486 if (config.lock()->getVisibleWaveView())
487 visualTimer_->start(static_cast<int>(std::round(1000. / config.lock()->getWaveViewFrameRate())));
488
489 /* Status bar */
490 statusDetail_ = new QLabel();
491 statusDetail_->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
492 statusStyle_ = new QLabel();
493 statusStyle_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
494 statusInst_ = new QLabel();
495 statusInst_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
496 statusOctave_ = new QLabel();
497 statusOctave_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
498 statusIntr_ = new QLabel();
499 statusIntr_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
500 statusMixer_ = new QLabel();
501 statusMixer_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
502 statusBpm_ = new QLabel();
503 statusBpm_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
504 statusPlayPos_ = new QLabel();
505 statusPlayPos_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
506 ui->statusBar->addWidget(statusDetail_, 4);
507 ui->statusBar->addPermanentWidget(statusStyle_, 1);
508 ui->statusBar->addPermanentWidget(statusInst_, 1);
509 ui->statusBar->addPermanentWidget(statusOctave_, 1);
510 ui->statusBar->addPermanentWidget(statusIntr_, 1);
511 ui->statusBar->addPermanentWidget(statusMixer_, 1);
512 ui->statusBar->addPermanentWidget(statusBpm_, 1);
513 ui->statusBar->addPermanentWidget(statusPlayPos_, 1);
514 statusOctave_->setText(tr("Octave: %1").arg(bt_->getCurrentOctave()));
515 statusIntr_->setText(QString::number(bt_->getModuleTickFrequency()) + QString("Hz"));
516
517 /* Bookmark */
518 bmManForm_ = std::make_unique<BookmarkManagerForm>(bt_, config_.lock()->getShowRowNumberInHex());
519 QObject::connect(bmManForm_.get(), &BookmarkManagerForm::positionJumpRequested,
520 this, [&](int order, int step) {
521 if (bt_->isPlaySong()) return;
522 int song = bt_->getCurrentSongNumber();
523 if (static_cast<int>(bt_->getOrderSize(song)) <= order) return;
524 if (static_cast<int>(bt_->getPatternSizeFromOrderNumber(song, order)) <= step) return;
525 bt_->setCurrentOrderNumber(order);
526 bt_->setCurrentStepNumber(step);
527 ui->orderList->updatePositionByPositionJump();
528 ui->patternEditor->updatepositionByPositionJump();
529 activateWindow();
530 });
531 QObject::connect(bmManForm_.get(), &BookmarkManagerForm::modified, this, &MainWindow::setModifiedTrue);
532
533 // Shortcuts
534 auto linkShortcut = [&](QAction* ptr) {
535 ptr->setShortcutContext(Qt::WidgetWithChildrenShortcut);
536 ui->orderList->addAction(ptr);
537 ui->patternEditor->addAction(ptr);
538 };
539
540 linkShortcut(&octUpSc_);
541 QObject::connect(&octUpSc_, &QAction::triggered, this, [&] { changeOctave(true); });
542 linkShortcut(&octDownSc_);
543 QObject::connect(&octDownSc_, &QAction::triggered, this, [&] { changeOctave(false); });
544 QObject::connect(&focusPtnSc_, &QShortcut::activated, this, [&] { ui->patternEditor->setFocus(); });
545 QObject::connect(&focusOdrSc_, &QShortcut::activated, this, [&] { ui->orderList->setFocus(); });
546 QObject::connect(&focusInstSc_, &QShortcut::activated, this, [&] {
547 ui->instrumentList->setFocus();
548 updateMenuByInstrumentList();
549 });
550 auto playLinkShortcut = [&](QAction* ptr) {
551 ptr->setShortcutContext(Qt::WidgetShortcut);
552 ui->instrumentList->addAction(ptr);
553 ui->orderList->addActionToPanel(ptr);
554 ui->patternEditor->addActionToPanel(ptr);
555 };
556 playLinkShortcut(ui->actionPlay);
557 playLinkShortcut(&playAndStopSc_);
558 QObject::connect(&playAndStopSc_, &QAction::triggered, this, [&] {
559 if (bt_->isPlaySong()) stopPlaySong();
560 else startPlaySong();
561 });
562 playLinkShortcut(&playStepSc_);
563 QObject::connect(&playStepSc_, &QAction::triggered, this, &MainWindow::playStep);
564 playLinkShortcut(ui->actionPlay_From_Start);
565 playLinkShortcut(ui->actionPlay_Pattern);
566 playLinkShortcut(ui->actionPlay_From_Cursor);
567 playLinkShortcut(ui->actionPlay_From_Marker);
568 playLinkShortcut(ui->actionStop);
569 instAddSc_ = std::make_unique<QShortcut>(Qt::Key_Insert, ui->instrumentList,
570 nullptr, nullptr, Qt::WidgetShortcut);
571 QObject::connect(instAddSc_.get(), &QShortcut::activated, this, &MainWindow::addInstrument);
572 linkShortcut(&goPrevOdrSc_);
573 QObject::connect(&goPrevOdrSc_, &QAction::triggered, this, [&] {
574 ui->orderList->onGoOrderRequested(false);
575 });
576 linkShortcut(&goNextOdrSc_);
577 QObject::connect(&goNextOdrSc_, &QAction::triggered, this, [&] {
578 ui->orderList->onGoOrderRequested(true);
579 });
580 linkShortcut(&prevInstSc_);
581 QObject::connect(&prevInstSc_, &QAction::triggered, this, [&] {
582 if (ui->instrumentList->count()) {
583 int row = ui->instrumentList->currentRow();
584 if (row == -1) ui->instrumentList->setCurrentRow(0);
585 else if (row > 0) ui->instrumentList->setCurrentRow(row - 1);
586 }
587 });
588 linkShortcut(&nextInstSc_);
589 QObject::connect(&nextInstSc_, &QAction::triggered, this, [&] {
590 int cnt = ui->instrumentList->count();
591 if (cnt) {
592 int row = ui->instrumentList->currentRow();
593 if (row == -1) ui->instrumentList->setCurrentRow(cnt - 1);
594 else if (row < cnt - 1) ui->instrumentList->setCurrentRow(row + 1);
595 }
596 });
597 linkShortcut(&incPtnSizeSc_);
598 QObject::connect(&incPtnSizeSc_, &QAction::triggered, this, [&] {
599 ui->patternSizeSpinBox->setValue(ui->patternSizeSpinBox->value() + 1);
600 });
601 linkShortcut(&decPtnSizeSc_);
602 QObject::connect(&decPtnSizeSc_, &QAction::triggered, this, [&] {
603 ui->patternSizeSpinBox->setValue(ui->patternSizeSpinBox->value() - 1);
604 });
605 linkShortcut(&incEditStepSc_);
606 QObject::connect(&incEditStepSc_, &QAction::triggered, this, [&] {
607 ui->editableStepSpinBox->setValue(ui->editableStepSpinBox->value() + 1);
608 });
609 linkShortcut(&decEditStepSc_);
610 QObject::connect(&decEditStepSc_, &QAction::triggered, this, [&] {
611 ui->editableStepSpinBox->setValue(ui->editableStepSpinBox->value() - 1);
612 });
613 linkShortcut(&prevSongSc_);
614 QObject::connect(&prevSongSc_, &QAction::triggered, this, [&] {
615 if (ui->songComboBox->isEnabled()) {
616 ui->songComboBox->setCurrentIndex(std::max(ui->songComboBox->currentIndex() - 1, 0));
617 }
618 });
619 linkShortcut(&nextSongSc_);
620 QObject::connect(&nextSongSc_, &QAction::triggered, this, [&] {
621 if (ui->songComboBox->isEnabled()) {
622 ui->songComboBox->setCurrentIndex(std::min(ui->songComboBox->currentIndex() + 1,
623 ui->songComboBox->count() - 1));
624 }
625 });
626 linkShortcut(&jamVolUpSc_);
627 QObject::connect(&jamVolUpSc_, &QAction::triggered, this, [&] {
628 volume_->setValue(volume_->value() + 1);
629 });
630 linkShortcut(&jamVolDownSc_);
631 QObject::connect(&jamVolDownSc_, &QAction::triggered, this, [&] {
632 volume_->setValue(volume_->value() - 1);
633 });
634 setShortcuts();
635
636 /* Clipboard */
637 QObject::connect(QApplication::clipboard(), &QClipboard::dataChanged,
638 this, [&]() {
639 if (isEditedOrder_) updateMenuByOrder();
640 else if (isEditedPattern_) updateMenuByPattern();
641 });
642
643 /* MIDI */
644 setMidiConfiguration();
645 midiKeyEventMethod_ = metaObject()->indexOfSlot("midiKeyEvent(uchar,uchar,uchar)");
646 Q_ASSERT(midiKeyEventMethod_ != -1);
647 midiProgramEventMethod_ = metaObject()->indexOfSlot("midiProgramEvent(uchar,uchar)");
648 Q_ASSERT(midiProgramEventMethod_ != -1);
649 MidiInterface::instance().installInputHandler(&midiThreadReceivedEvent, this);
650
651 /* Audio stream */
652 stream_ = std::make_shared<AudioStreamRtAudio>();
653 stream_->setTickUpdateCallback(+[](void* cbPtr) -> int {
654 auto bt = reinterpret_cast<BambooTracker*>(cbPtr);
655 return bt->streamCountUp();
656 }, bt_.get());
657 stream_->setGenerateCallback(+[](int16_t* container, size_t nSamples, void* cbPtr) {
658 auto bt = reinterpret_cast<BambooTracker*>(cbPtr);
659 bt->getStreamSamples(container, nSamples);
660 }, bt_.get());
661 QObject::connect(stream_.get(), &AudioStream::streamInterrupted, this, &MainWindow::onNewTickSignaled);
662 QString audioApi = utf8ToQString(config.lock()->getSoundAPI());
663 if (audioApi.isEmpty()) { // On the first launch
664 bool streamState = false;
665 QString streamErr;
666 for (const QString& audioApi : stream_->getAvailableBackends()) {
667 config.lock()->setSoundAPI(audioApi.toUtf8().toStdString());
668 QString audioDevice = stream_->getDefaultOutputDevice(audioApi);
669 config.lock()->setSoundDevice(audioDevice.toUtf8().toStdString());
670 streamState = stream_->initialize(
671 static_cast<uint32_t>(bt_->getStreamRate()),
672 static_cast<uint32_t>(bt_->getStreamDuration()),
673 bt_->getModuleTickFrequency(), audioApi, audioDevice, &streamErr);
674 if (streamState) break;
675 }
676 if (streamState) {
677 uint32_t sr = stream_->getStreamRate();
678 if (config.lock()->getSampleRate() != sr) {
679 showStreamRateWarningDialog(sr);
680 bt_->setStreamRate(sr);
681 }
682 }
683 else {
684 showStreamFailedDialog(streamErr);
685 }
686 }
687 else { // Ordinary launch
688 bool savedApiExists = false;
689 const std::vector<QString> audioApis = stream_->getAvailableBackends();
690 for (const QString& api : audioApis) {
691 if (api.toUtf8().toStdString() == config.lock()->getSoundAPI()) {
692 savedApiExists = true;
693 break;
694 }
695 }
696 if (!savedApiExists) {
697 audioApi = audioApis.front();
698 config.lock()->setSoundAPI(audioApi.toUtf8().toStdString());
699 }
700 QString audioDevice = utf8ToQString(config.lock()->getSoundDevice());
701 bool savedDeviceExists = false;
702 for (const QString& device : stream_->getAvailableDevices(audioApi)) {
703 if (device.toUtf8().toStdString() == config.lock()->getSoundDevice()) {
704 savedDeviceExists = true;
705 break;
706 }
707 }
708 if (!savedDeviceExists) {
709 audioDevice = stream_->getDefaultOutputDevice(audioApi);
710 config.lock()->setSoundDevice(audioDevice.toUtf8().toStdString());
711 }
712 QString streamErr;
713 bool streamState = stream_->initialize(
714 static_cast<uint32_t>(bt_->getStreamRate()),
715 static_cast<uint32_t>(bt_->getStreamDuration()),
716 bt_->getModuleTickFrequency(), audioApi, audioDevice, &streamErr);
717 if (streamState) {
718 uint32_t sr = stream_->getStreamRate();
719 if (config.lock()->getSampleRate() != sr) {
720 showStreamRateWarningDialog(sr);
721 bt_->setStreamRate(sr);
722 }
723 }
724 else {
725 showStreamFailedDialog(streamErr);
726 }
727 }
728 RealChipInterface intf = config.lock()->getRealChipInterface();
729 if (intf == RealChipInterface::NONE) {
730 bt_->useSCCI(nullptr);
731 bt_->useC86CTL(nullptr);
732 }
733 else {
734 timer_ = std::make_unique<Timer>();
735 timer_->setInterval(1000000 / bt_->getModuleTickFrequency());
736 tickEventMethod_ = metaObject()->indexOfSlot("onNewTickSignaledRealChip()");
737 Q_ASSERT(tickEventMethod_ != -1);
738 timer_->setFunction([&]{
739 QMetaMethod method = this->metaObject()->method(this->tickEventMethod_);
740 method.invoke(this, Qt::QueuedConnection);
741 });
742
743 setRealChipInterface(intf);
744
745 timer_->start();
746 }
747
748 /* Load module */
749 if (filePath.isEmpty()) {
750 loadModule();
751 setInitialSelectedInstrument();
752 assignADPCMSamples();
753 if (!timer_) stream_->start();
754 }
755 else {
756 openModule(filePath); // If use emulation, stream stars
757 }
758
759 /* Track visibility */
760 SongType memSongType;
761 std::vector<int> visTracks;
762 if (config.lock()->getRestoreTrackVisibility()
763 && TrackVisibilityMemoryHandler::loadTrackVisibilityMemory(memSongType, visTracks)) {
764 SongType songType = bt_->getSongStyle(bt_->getCurrentSongNumber()).type;
765 visTracks = adaptVisibleTrackList(visTracks, songType, songType);
766 }
767 else {
768 visTracks.resize(bt_->getSongStyle(0).trackAttribs.size());
769 std::iota(visTracks.begin(), visTracks.end(), 0);
770 }
771 setTrackVisibility(visTracks);
772 }
773
~MainWindow()774 MainWindow::~MainWindow()
775 {
776 MidiInterface::instance().uninstallInputHandler(&midiThreadReceivedEvent, this);
777 stream_->shutdown();
778 }
779
eventFilter(QObject * watched,QEvent * event)780 bool MainWindow::eventFilter(QObject* watched, QEvent* event)
781 {
782 if (auto fmForm = qobject_cast<InstrumentEditorFMForm*>(watched)) {
783 // Change current instrument by activating FM editor
784 if (event->type() == QEvent::WindowActivate) {
785 int row = findRowFromInstrumentList(fmForm->getInstrumentNumber());
786 ui->instrumentList->setCurrentRow(row);
787 }
788 else if (event->type() == QEvent::Resize) {
789 config_.lock()->setInstrumentFMWindowWidth(fmForm->width());
790 config_.lock()->setInstrumentFMWindowHeight(fmForm->height());
791 }
792 return false;
793 }
794
795 if (auto ssgForm = qobject_cast<InstrumentEditorSSGForm*>(watched)) {
796 // Change current instrument by activating SSG editor
797 if (event->type() == QEvent::WindowActivate) {
798 int row = findRowFromInstrumentList(ssgForm->getInstrumentNumber());
799 ui->instrumentList->setCurrentRow(row);
800 }
801 else if (event->type() == QEvent::Resize) {
802 config_.lock()->setInstrumentSSGWindowWidth(ssgForm->width());
803 config_.lock()->setInstrumentSSGWindowHeight(ssgForm->height());
804 }
805 return false;
806 }
807
808 if (auto adpcmForm = qobject_cast<InstrumentEditorADPCMForm*>(watched)) {
809 // Change current instrument by activating ADPCM editor
810 if (event->type() == QEvent::WindowActivate) {
811 int row = findRowFromInstrumentList(adpcmForm->getInstrumentNumber());
812 ui->instrumentList->setCurrentRow(row);
813 }
814 else if (event->type() == QEvent::Resize) {
815 config_.lock()->setInstrumentADPCMWindowWidth(adpcmForm->width());
816 config_.lock()->setInstrumentADPCMWindowHeight(adpcmForm->height());
817 }
818 return false;
819 }
820
821 if (auto kitForm = qobject_cast<InstrumentEditorDrumkitForm*>(watched)) {
822 // Change current instrument by activating drumkit editor
823 if (event->type() == QEvent::WindowActivate) {
824 int row = findRowFromInstrumentList(kitForm->getInstrumentNumber());
825 ui->instrumentList->setCurrentRow(row);
826 }
827 else if (event->type() == QEvent::Resize) {
828 config_.lock()->setInstrumentDrumkitWindowWidth(kitForm->width());
829 config_.lock()->setInstrumentDrumkitWindowHeight(kitForm->height());
830 }
831 return false;
832 }
833
834 if (watched == ui->instrumentList) {
835 if (event->type() == QEvent::FocusIn) updateMenuByInstrumentList();
836 return false;
837 }
838
839 if (watched == renamingInstEdit_) {
840 if (event->type() == QEvent::FocusOut) finishRenamingInstrument();
841 return false;
842 }
843
844 return false;
845 }
846
showEvent(QShowEvent * event)847 void MainWindow::showEvent(QShowEvent* event)
848 {
849 Q_UNUSED(event)
850
851 if (!hasShownOnce_) {
852 int y = config_.lock()->getMainWindowVerticalSplit();
853 if (y == -1) {
854 config_.lock()->setMainWindowVerticalSplit(ui->splitter->sizes().front());
855 }
856 else {
857 ui->splitter->setSizes({ y, ui->splitter->height() - ui->splitter->handleWidth() - y });
858 }
859 hasShownOnce_ = true;
860 }
861 }
862
keyPressEvent(QKeyEvent * event)863 void MainWindow::keyPressEvent(QKeyEvent *event)
864 {
865 if (!event->isAutoRepeat()) {
866 // Musical keyboard
867 Qt::Key qtKey = static_cast<Qt::Key>(event->key());
868 try {
869 bt_->jamKeyOn(getJamKeyFromLayoutMapping(qtKey, config_),
870 !config_.lock()->getFixJammingVolume());
871 } catch (std::invalid_argument&) {}
872 }
873 }
874
keyReleaseEvent(QKeyEvent * event)875 void MainWindow::keyReleaseEvent(QKeyEvent *event)
876 {
877 int key = event->key();
878
879 if (!event->isAutoRepeat()) {
880 // Musical keyboard
881 Qt::Key qtKey = static_cast<Qt::Key>(key);
882 try {
883 bt_->jamKeyOff(getJamKeyFromLayoutMapping(qtKey, config_));
884 } catch (std::invalid_argument&) {}
885 }
886 }
887
dragEnterEvent(QDragEnterEvent * event)888 void MainWindow::dragEnterEvent(QDragEnterEvent* event)
889 {
890 auto mime = event->mimeData();
891 if (mime->hasUrls() && mime->urls().length() == 1) {
892 switch (FileIO::judgeFileTypeFromExtension(
893 QFileInfo(mime->urls().first().toLocalFile()).suffix().toStdString())) {
894 case FileIO::FileType::Mod:
895 case FileIO::FileType::Inst:
896 case FileIO::FileType::Bank:
897 event->acceptProposedAction();
898 break;
899 default:
900 break;
901 }
902 }
903 }
904
dropEvent(QDropEvent * event)905 void MainWindow::dropEvent(QDropEvent* event)
906 {
907 QString file = event->mimeData()->urls().first().toLocalFile();
908
909 switch (FileIO::judgeFileTypeFromExtension(QFileInfo(file).suffix().toStdString())) {
910 case FileIO::FileType::Mod:
911 {
912 if (isWindowModified()) {
913 QString modTitle = utf8ToQString(bt_->getModuleTitle());
914 if (modTitle.isEmpty()) modTitle = tr("Untitled");
915 QMessageBox dialog(QMessageBox::Warning,
916 "BambooTracker",
917 tr("Save changes to %1?").arg(modTitle),
918 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
919 switch (dialog.exec()) {
920 case QMessageBox::Yes:
921 if (!on_actionSave_triggered()) return;
922 break;
923 case QMessageBox::No:
924 break;
925 case QMessageBox::Cancel:
926 return;
927 default:
928 break;
929 }
930 }
931
932 bt_->stopPlaySong();
933 lockWidgets(false);
934
935 openModule(file);
936 break;
937 }
938 case FileIO::FileType::Inst:
939 {
940 funcLoadInstrument(file);
941 break;
942 }
943 case FileIO::FileType::Bank:
944 {
945 funcImportInstrumentsFromBank(file);
946 break;
947 }
948 default:
949 break;
950 }
951 }
952
resizeEvent(QResizeEvent * event)953 void MainWindow::resizeEvent(QResizeEvent* event)
954 {
955 QWidget::resizeEvent(event);
956
957 if (!isMaximized()) { // Check previous size
958 config_.lock()->setMainWindowWidth(event->oldSize().width());
959 config_.lock()->setMainWindowHeight(event->oldSize().height());
960 }
961 }
962
moveEvent(QMoveEvent * event)963 void MainWindow::moveEvent(QMoveEvent* event)
964 {
965 QWidget::moveEvent(event);
966
967 if (!isMaximized()) { // Check previous position
968 config_.lock()->setMainWindowX(event->oldPos().x());
969 config_.lock()->setMainWindowY(event->oldPos().y());
970 }
971 }
972
closeEvent(QCloseEvent * event)973 void MainWindow::closeEvent(QCloseEvent *event)
974 {
975 if (isWindowModified()) {
976 QString modTitle = utf8ToQString(bt_->getModuleTitle());
977 if (modTitle.isEmpty()) modTitle = tr("Untitled");
978 QMessageBox dialog(QMessageBox::Warning,
979 "BambooTracker",
980 tr("Save changes to %1?").arg(modTitle),
981 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
982 switch (dialog.exec()) {
983 case QMessageBox::Yes:
984 if (!on_actionSave_triggered()) {
985 event->ignore();
986 return;
987 }
988 break;
989 case QMessageBox::No:
990 break;
991 case QMessageBox::Cancel:
992 event->ignore();
993 return;
994 default:
995 break;
996 }
997 }
998
999 if (isMaximized()) {
1000 config_.lock()->setMainWindowMaximized(true);
1001 }
1002 else {
1003 config_.lock()->setMainWindowMaximized(false);
1004 config_.lock()->setMainWindowWidth(width());
1005 config_.lock()->setMainWindowHeight(height());
1006 config_.lock()->setMainWindowX(x());
1007 config_.lock()->setMainWindowY(y());
1008 }
1009 config_.lock()->setMainWindowVerticalSplit(ui->splitter->sizes().front());
1010
1011 auto& mainTbConfig = config_.lock()->getMainToolbarConfiguration();
1012 auto& subTbConfig = config_.lock()->getSubToolbarConfiguration();
1013 auto mainTbArea = toolBarArea(ui->mainToolBar);
1014 auto subTbArea = toolBarArea(ui->subToolBar);
1015 if (ui->mainToolBar->isFloating()) {
1016 mainTbConfig.setPosition(ToolbarPos::FLOAT_POS);
1017 mainTbConfig.setX(ui->mainToolBar->x());
1018 mainTbConfig.setY(ui->mainToolBar->y());
1019 }
1020 else {
1021 mainTbConfig.setPosition(std::find_if(TB_POS_.begin(), TB_POS_.end(), [mainTbArea](auto pair) {
1022 return (pair.second == mainTbArea);
1023 })->first);
1024 mainTbConfig.setNumber(0);
1025 mainTbConfig.setBreakBefore(false);
1026 }
1027 if (ui->subToolBar->isFloating()) {
1028 subTbConfig.setPosition(ToolbarPos::FLOAT_POS);
1029 subTbConfig.setX(ui->subToolBar->x());
1030 subTbConfig.setY(ui->subToolBar->y());
1031 }
1032 else {
1033 subTbConfig.setPosition(std::find_if(TB_POS_.begin(), TB_POS_.end(), [subTbArea](auto pair) {
1034 return (pair.second == subTbArea);
1035 })->first);
1036 subTbConfig.setNumber(0);
1037 subTbConfig.setBreakBefore(false);
1038 }
1039 if (mainTbArea == subTbArea) {
1040 auto mainPos = ui->mainToolBar->pos();
1041 auto subPos = ui->subToolBar->pos();
1042 switch (mainTbArea) {
1043 case Qt::TopToolBarArea:
1044 {
1045 if (mainPos.x() == subPos.x()) {
1046 bool cond = (mainPos.y() < subPos.y());
1047 mainTbConfig.setNumber(cond ? 0 : 1);
1048 mainTbConfig.setBreakBefore(!cond);
1049 subTbConfig.setNumber(cond ? 1 : 0);
1050 subTbConfig.setBreakBefore(cond);
1051 }
1052 else {
1053 bool cond = (mainPos.x() < subPos.x());
1054 mainTbConfig.setNumber(cond ? 0 : 1);
1055 mainTbConfig.setBreakBefore(false);
1056 subTbConfig.setNumber(cond ? 1 : 0);
1057 subTbConfig.setBreakBefore(false);
1058 }
1059 break;
1060 }
1061 case Qt::BottomToolBarArea:
1062 {
1063 if (mainPos.x() == subPos.x()) {
1064 bool cond = (subPos.y() < mainPos.y());
1065 mainTbConfig.setNumber(cond ? 0 : 1);
1066 mainTbConfig.setBreakBefore(!cond);
1067 subTbConfig.setNumber(cond ? 1 : 0);
1068 subTbConfig.setBreakBefore(cond);
1069 }
1070 else {
1071 bool cond = (mainPos.x() < subPos.x());
1072 mainTbConfig.setNumber(cond ? 0 : 1);
1073 mainTbConfig.setBreakBefore(false);
1074 subTbConfig.setNumber(cond ? 1 : 0);
1075 subTbConfig.setBreakBefore(false);
1076 }
1077 break;
1078 }
1079 case Qt::LeftToolBarArea:
1080 {
1081 if (mainPos.x() == subPos.x()) {
1082 bool cond = (mainPos.y() < subPos.y());
1083 mainTbConfig.setNumber(cond ? 0 : 1);
1084 mainTbConfig.setBreakBefore(false);
1085 subTbConfig.setNumber(cond ? 1 : 0);
1086 subTbConfig.setBreakBefore(false);
1087 }
1088 else {
1089 bool cond = (mainPos.x() < subPos.x());
1090 mainTbConfig.setNumber(cond ? 0 : 1);
1091 mainTbConfig.setBreakBefore(!cond);
1092 subTbConfig.setNumber(cond ? 1 : 0);
1093 subTbConfig.setBreakBefore(cond);
1094 }
1095 break;
1096 }
1097 case Qt::RightToolBarArea:
1098 {
1099 if (mainPos.x() == subPos.x()) {
1100 bool cond = (mainPos.y() < subPos.y());
1101 mainTbConfig.setNumber(cond ? 0 : 1);
1102 mainTbConfig.setBreakBefore(false);
1103 subTbConfig.setNumber(cond ? 1 : 0);
1104 subTbConfig.setBreakBefore(false);
1105 }
1106 else {
1107 bool cond = (subPos.x() < mainPos.x());
1108 mainTbConfig.setNumber(cond ? 0 : 1);
1109 mainTbConfig.setBreakBefore(!cond);
1110 subTbConfig.setNumber(cond ? 1 : 0);
1111 subTbConfig.setBreakBefore(cond);
1112 }
1113 break;
1114 }
1115 default:
1116 break;
1117 }
1118 }
1119 config_.lock()->setVisibleToolbar(ui->mainToolBar->isVisible());
1120 config_.lock()->setVisibleStatusBar(ui->statusBar->isVisible());
1121 config_.lock()->setFollowMode(bt_->isFollowPlay());
1122
1123 instForms_->closeAll();
1124
1125 FileHistoryHandler::saveFileHistory(fileHistory_);
1126 TrackVisibilityMemoryHandler::saveTrackVisibilityMemory(
1127 bt_->getSongStyle(bt_->getCurrentSongNumber()).type,
1128 ui->patternEditor->getVisibleTracks());
1129
1130 if (effListDiag_) effListDiag_->close();
1131 if (shortcutsDiag_) shortcutsDiag_->close();
1132 bmManForm_->close();
1133 if (commentDiag_) commentDiag_->close();
1134
1135 event->accept();
1136 }
1137
freezeViews()1138 void MainWindow::freezeViews()
1139 {
1140 ui->orderList->freeze();
1141 ui->patternEditor->freeze();
1142 }
1143
unfreezeViews()1144 void MainWindow::unfreezeViews()
1145 {
1146 ui->orderList->unfreeze();
1147 ui->patternEditor->unfreeze();
1148 }
1149
setShortcuts()1150 void MainWindow::setShortcuts()
1151 {
1152 auto shortcuts = config_.lock()->getShortcuts();
1153 octUpSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::OctaveUp)));
1154 octDownSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::OctaveDown)));
1155 focusPtnSc_.setKey(strToKeySeq(shortcuts.at(Configuration::FocusOnPattern)));
1156 focusOdrSc_.setKey(strToKeySeq(shortcuts.at(Configuration::FocusOnOrder)));
1157 focusInstSc_.setKey(strToKeySeq(shortcuts.at(Configuration::FocusOnInstrument)));
1158 playAndStopSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayAndStop)));
1159 ui->actionPlay->setShortcut(strToKeySeq(shortcuts.at(Configuration::Play)));
1160 ui->actionPlay_From_Start->setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayFromStart)));
1161 ui->actionPlay_Pattern->setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayPattern)));
1162 ui->actionPlay_From_Cursor->setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayFromCursor)));
1163 ui->actionPlay_From_Marker->setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayFromMarker)));
1164 playStepSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayStep)));
1165 ui->actionStop->setShortcut(strToKeySeq(shortcuts.at(Configuration::Stop)));
1166 ui->actionEdit_Mode->setShortcut(strToKeySeq(shortcuts.at(Configuration::ToggleEditJam)));
1167 ui->actionSet_Ro_w_Marker->setShortcut(strToKeySeq(shortcuts.at(Configuration::SetMarker)));
1168 ui->actionMix->setShortcut(strToKeySeq(shortcuts.at(Configuration::PasteMix)));
1169 ui->actionOverwrite->setShortcut(strToKeySeq(shortcuts.at(Configuration::PasteOverwrite)));
1170 ui->action_Insert->setShortcut(strToKeySeq(shortcuts.at(Configuration::PasteInsert)));
1171 ui->actionAll->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectAll)));
1172 ui->actionNone->setShortcut(strToKeySeq(shortcuts.at(Configuration::Deselect)));
1173 ui->actionRow->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectRow)));
1174 ui->actionColumn->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectColumn)));
1175 ui->actionPattern->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectColumn)));
1176 ui->actionOrder->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectOrder)));
1177 ui->action_Go_To->setShortcut(strToKeySeq(shortcuts.at(Configuration::GoToStep)));
1178 ui->actionToggle_Track->setShortcut(strToKeySeq(shortcuts.at(Configuration::ToggleTrack)));
1179 ui->actionSolo_Track->setShortcut(strToKeySeq(shortcuts.at(Configuration::SoloTrack)));
1180 ui->actionInterpolate->setShortcut(strToKeySeq(shortcuts.at(Configuration::Interpolate)));
1181 goPrevOdrSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::GoToPrevOrder)));
1182 goNextOdrSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::GoToNextOrder)));
1183 ui->action_Toggle_Bookmark->setShortcut(strToKeySeq(shortcuts.at(Configuration::ToggleBookmark)));
1184 ui->action_Previous_Bookmark->setShortcut(strToKeySeq(shortcuts.at(Configuration::PrevBookmark)));
1185 ui->action_Next_Bookmark->setShortcut(strToKeySeq(shortcuts.at(Configuration::NextBookmark)));
1186 ui->actionDecrease_Note->setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreaseNote)));
1187 ui->actionIncrease_Note->setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreaseNote)));
1188 ui->actionDecrease_Octave->setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreaseOctave)));
1189 ui->actionIncrease_Octave->setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreaseOctave)));
1190 prevInstSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::PrevInstrument)));
1191 nextInstSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::NextInstrument)));
1192 ui->action_Instrument_Mask->setShortcut(strToKeySeq(shortcuts.at(Configuration::MaskInstrument)));
1193 ui->action_Volume_Mask->setShortcut(strToKeySeq(shortcuts.at(Configuration::MaskVolume)));
1194 ui->actionEdit->setShortcut(strToKeySeq(shortcuts.at(Configuration::EditInstrument)));
1195 ui->actionFollow_Mode->setShortcut(strToKeySeq(shortcuts.at(Configuration::FollowMode)));
1196 ui->actionDuplicate_Order->setShortcut(strToKeySeq(shortcuts.at(Configuration::DuplicateOrder)));
1197 ui->actionClone_Patterns->setShortcut(strToKeySeq(shortcuts.at(Configuration::ClonePatterns)));
1198 ui->actionClone_Order->setShortcut(strToKeySeq(shortcuts.at(Configuration::CloneOrder)));
1199 ui->actionReplace_Instrument->setShortcut(strToKeySeq(shortcuts.at(Configuration::ReplaceInstrument)));
1200 ui->actionExpand->setShortcut(strToKeySeq(shortcuts.at(Configuration::ExpandPattern)));
1201 ui->actionShrink->setShortcut(strToKeySeq(shortcuts.at(Configuration::ShrinkPattern)));
1202 ui->actionFine_Decrease_Values->setShortcut(strToKeySeq(shortcuts.at(Configuration::FineDecreaseValues)));
1203 ui->actionFine_Increase_Values->setShortcut(strToKeySeq(shortcuts.at(Configuration::FineIncreaseValues)));
1204 ui->actionCoarse_D_ecrease_Values->setShortcut(strToKeySeq(shortcuts.at(Configuration::CoarseDecreaseValues)));
1205 ui->actionCoarse_I_ncrease_Values->setShortcut(strToKeySeq(shortcuts.at(Configuration::CoarseIncreaseValuse)));
1206 incPtnSizeSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreasePatternSize)));
1207 decPtnSizeSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreasePatternSize)));
1208 incEditStepSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreaseEditStep)));
1209 decEditStepSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreaseEditStep)));
1210 ui->action_Effect_List->setShortcut(strToKeySeq(shortcuts.at(Configuration::DisplayEffectList)));
1211 prevSongSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::PreviousSong)));
1212 nextSongSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::NextSong)));
1213 jamVolUpSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::JamVolumeUp)));
1214 jamVolDownSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::JamVolumeDown)));
1215
1216 ui->orderList->onShortcutUpdated();
1217 ui->patternEditor->onShortcutUpdated();
1218 }
1219
setTrackVisibility(const std::vector<int> & visTracks)1220 void MainWindow::setTrackVisibility(const std::vector<int>& visTracks)
1221 {
1222 ui->orderList->setVisibleTracks(visTracks);
1223 setOrderListGroupMaximumWidth();
1224 ui->patternEditor->setVisibleTracks(visTracks);
1225 if (config_.lock()->getMuteHiddenTracks()) {
1226 int all = static_cast<int>(bt_->getSongStyle(bt_->getCurrentSongNumber()).trackAttribs.size());
1227 for (int i = 0; i < all; ++i) {
1228 if (std::none_of(visTracks.begin(), visTracks.end(), [i](const int& n) { return i == n; }))
1229 bt_->setTrackMuteState(i, true);
1230 }
1231 }
1232 }
1233
updateInstrumentListColors()1234 void MainWindow::updateInstrumentListColors()
1235 {
1236 ui->instrumentList->setStyleSheet(
1237 QString("QListWidget { color: %1; background: %2; }")
1238 .arg(palette_->ilistTextColor.name(QColor::HexArgb),
1239 palette_->ilistBackColor.name(QColor::HexArgb))
1240 + QString("QListWidget::item:hover { color: %1; background: %2; }")
1241 .arg(palette_->ilistTextColor.name(QColor::HexArgb),
1242 palette_->ilistHovBackColor.name(QColor::HexArgb))
1243 + QString("QListWidget::item:selected { color: %1; background: %2; }")
1244 .arg(palette_->ilistTextColor.name(QColor::HexArgb),
1245 palette_->ilistSelBackColor.name(QColor::HexArgb))
1246 + QString("QListWidget::item:selected:hover { color: %1; background: %2; }")
1247 .arg(palette_->ilistTextColor.name(QColor::HexArgb),
1248 palette_->ilistHovSelBackColor.name(QColor::HexArgb)));
1249 }
1250
setOrderListGroupMaximumWidth()1251 void MainWindow::setOrderListGroupMaximumWidth()
1252 {
1253 ui->orderListGroupBox->setMaximumWidth(
1254 ui->orderListGroupBox->contentsMargins().left()
1255 + ui->orderListGroupBox->layout()->contentsMargins().left()
1256 + ui->orderList->maximumWidth()
1257 + ui->orderListGroupBox->layout()->contentsMargins().right()
1258 + ui->orderListGroupBox->contentsMargins().right());
1259 }
1260
1261 /********** MIDI **********/
midiThreadReceivedEvent(double delay,const uint8_t * msg,size_t len,void * userData)1262 void MainWindow::midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData)
1263 {
1264 MainWindow *self = reinterpret_cast<MainWindow *>(userData);
1265
1266 Q_UNUSED(delay)
1267
1268 // Note-On/Note-Off
1269 if (len == 3 && (msg[0] & 0xe0) == 0x80) {
1270 uint8_t status = msg[0];
1271 uint8_t key = msg[1];
1272 uint8_t velocity = msg[2];
1273 QMetaMethod method = self->metaObject()->method(self->midiKeyEventMethod_);
1274 method.invoke(self, Qt::QueuedConnection,
1275 Q_ARG(uchar, status), Q_ARG(uchar, key), Q_ARG(uchar, velocity));
1276 }
1277 // Program change
1278 else if (len == 2 && (msg[0] & 0xf0) == 0xc0) {
1279 uint8_t status = msg[0];
1280 uint8_t program = msg[1];
1281 QMetaMethod method = self->metaObject()->method(self->midiProgramEventMethod_);
1282 method.invoke(self, Qt::QueuedConnection,
1283 Q_ARG(uchar, status), Q_ARG(uchar, program));
1284 }
1285 }
1286
midiKeyEvent(uchar status,uchar key,uchar velocity)1287 void MainWindow::midiKeyEvent(uchar status, uchar key, uchar velocity)
1288 {
1289 bool release = ((status & 0xf0) == 0x80) || velocity == 0;
1290 int k = static_cast<int>(key) - 12;
1291
1292 octave_->setValue(k / 12);
1293
1294 if (importBankDiag_) {
1295 if (bankJamMidiCtrl_.load()) return;
1296 importBankDiag_->onJamKeyOffByMidi(k);
1297 if (!release) importBankDiag_->onJamKeyOnByMidi(k);
1298 return;
1299 }
1300
1301 int n = instForms_->checkActivatedFormNumber();
1302 if (n == -1) {
1303 bt_->jamKeyOff(k); // possibility to recover on stuck note
1304 if (!release) bt_->jamKeyOn(k, !config_.lock()->getFixJammingVolume());
1305 }
1306 else {
1307 SoundSource src = instForms_->getFormInstrumentSoundSource(n);
1308 bt_->jamKeyOffForced(k, src); // possibility to recover on stuck note
1309 if (!release) bt_->jamKeyOnForced(k, src, !config_.lock()->getFixJammingVolume());
1310 }
1311 }
1312
midiProgramEvent(uchar status,uchar program)1313 void MainWindow::midiProgramEvent(uchar status, uchar program)
1314 {
1315 Q_UNUSED(status)
1316 int row = findRowFromInstrumentList(program);
1317 ui->instrumentList->setCurrentRow(row);
1318 }
1319
1320 /********** Instrument list **********/
addInstrument()1321 void MainWindow::addInstrument()
1322 {
1323 SoundSource src = bt_->getCurrentTrackAttribute().source;
1324 switch (src) {
1325 case SoundSource::FM:
1326 case SoundSource::SSG:
1327 case SoundSource::ADPCM:
1328 {
1329 std::unordered_map<SoundSource, InstrumentType> map = {
1330 { SoundSource::FM, InstrumentType::FM },
1331 { SoundSource::SSG, InstrumentType::SSG },
1332 { SoundSource::ADPCM, InstrumentType::ADPCM },
1333 };
1334 auto& list = ui->instrumentList;
1335
1336 int num = bt_->findFirstFreeInstrumentNumber();
1337 if (num == -1) return; // Maximum count check
1338
1339 QString name = tr("Instrument %1").arg(num);
1340 bt_->addInstrument(num, map.at(src), name.toUtf8().toStdString());
1341
1342 comStack_->push(new AddInstrumentQtCommand(
1343 list, num, name, map.at(src), instForms_, this,
1344 config_.lock()->getWriteOnlyUsedSamples()));
1345 ui->instrumentList->setCurrentRow(num);
1346 break;
1347 }
1348 case SoundSource::RHYTHM:
1349 break;
1350 }
1351 }
1352
addDrumkit()1353 void MainWindow::addDrumkit()
1354 {
1355 auto& list = ui->instrumentList;
1356
1357 int num = bt_->findFirstFreeInstrumentNumber();
1358 if (num == -1) return; // Maximum count check
1359
1360 QString name = tr("Instrument %1").arg(num);
1361 bt_->addInstrument(num, InstrumentType::Drumkit, name.toUtf8().toStdString());
1362
1363 comStack_->push(new AddInstrumentQtCommand(
1364 list, num, name, InstrumentType::Drumkit, instForms_, this,
1365 config_.lock()->getWriteOnlyUsedSamples()));
1366 ui->instrumentList->setCurrentRow(num);
1367 }
1368
removeInstrument(int row)1369 void MainWindow::removeInstrument(int row)
1370 {
1371 if (row < 0) return;
1372
1373 auto& list = ui->instrumentList;
1374 int num = list->item(row)->data(Qt::UserRole).toInt();
1375
1376 auto inst = bt_->getInstrument(num);
1377
1378 bool updateRequest = false;
1379 if (config_.lock()->getWriteOnlyUsedSamples()){
1380 if (inst->getSoundSource() == SoundSource::ADPCM) {
1381 size_t size = bt_->getSampleADPCMUsers(dynamic_cast<InstrumentADPCM*>(
1382 inst.get())->getSampleNumber()).size();
1383 if (size == 1) updateRequest = true;
1384 }
1385 }
1386
1387 bt_->removeInstrument(num);
1388 comStack_->push(new RemoveInstrumentQtCommand(list, num, row, utf8ToQString(inst->getName()),
1389 inst->getType(), instForms_, this, updateRequest));
1390 }
1391
openInstrumentEditor()1392 void MainWindow::openInstrumentEditor()
1393 {
1394 auto item = ui->instrumentList->currentItem();
1395 int num = item->data(Qt::UserRole).toInt();
1396
1397 if (!instForms_->getForm(num)) { // Create form
1398 std::shared_ptr<QWidget> form;
1399 auto inst = bt_->getInstrument(num);
1400
1401 switch (inst->getType()) {
1402 case InstrumentType::FM:
1403 {
1404 auto fmForm = std::make_shared<InstrumentEditorFMForm>(num);
1405 form = fmForm;
1406 fmForm->setCore(bt_);
1407 fmForm->setConfiguration(config_.lock());
1408 fmForm->setColorPalette(palette_);
1409 fmForm->resize(config_.lock()->getInstrumentFMWindowWidth(),
1410 config_.lock()->getInstrumentFMWindowHeight());
1411
1412 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::envelopeNumberChanged,
1413 instForms_.get(), &InstrumentFormManager::onInstrumentFMEnvelopeNumberChanged);
1414 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::envelopeParameterChanged,
1415 instForms_.get(), &InstrumentFormManager::onInstrumentFMEnvelopeParameterChanged);
1416 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::lfoNumberChanged,
1417 instForms_.get(), &InstrumentFormManager::onInstrumentFMLFONumberChanged);
1418 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::lfoParameterChanged,
1419 instForms_.get(), &InstrumentFormManager::onInstrumentFMLFOParameterChanged);
1420 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::operatorSequenceNumberChanged,
1421 instForms_.get(), &InstrumentFormManager::onInstrumentFMOperatorSequenceNumberChanged);
1422 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::operatorSequenceParameterChanged,
1423 instForms_.get(), &InstrumentFormManager::onInstrumentFMOperatorSequenceParameterChanged);
1424 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::arpeggioNumberChanged,
1425 instForms_.get(), &InstrumentFormManager::onInstrumentFMArpeggioNumberChanged);
1426 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::arpeggioParameterChanged,
1427 instForms_.get(), &InstrumentFormManager::onInstrumentFMArpeggioParameterChanged);
1428 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::pitchNumberChanged,
1429 instForms_.get(), &InstrumentFormManager::onInstrumentFMPitchNumberChanged);
1430 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::pitchParameterChanged,
1431 instForms_.get(), &InstrumentFormManager::onInstrumentFMPitchParameterChanged);
1432 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::jamKeyOnEvent,
1433 this, [&](JamKey key) {
1434 bt_->jamKeyOnForced(key, SoundSource::FM, !config_.lock()->getFixJammingVolume());
1435 }, Qt::DirectConnection);
1436 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::jamKeyOffEvent,
1437 this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::FM); },
1438 Qt::DirectConnection);
1439 QObject::connect(fmForm.get(), &InstrumentEditorFMForm::modified,
1440 this, &MainWindow::setModifiedTrue);
1441
1442 fmForm->installEventFilter(this);
1443
1444 instForms_->onInstrumentFMEnvelopeNumberChanged();
1445 instForms_->onInstrumentFMLFONumberChanged();
1446 instForms_->onInstrumentFMOperatorSequenceNumberChanged();
1447 instForms_->onInstrumentFMArpeggioNumberChanged();
1448 instForms_->onInstrumentFMPitchNumberChanged();
1449 break;
1450 }
1451 case InstrumentType::SSG:
1452 {
1453 auto ssgForm = std::make_shared<InstrumentEditorSSGForm>(num);
1454 form = ssgForm;
1455 ssgForm->setCore(bt_);
1456 ssgForm->setConfiguration(config_.lock());
1457 ssgForm->setColorPalette(palette_);
1458 ssgForm->resize(config_.lock()->getInstrumentSSGWindowWidth(),
1459 config_.lock()->getInstrumentSSGWindowHeight());
1460
1461 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::waveformNumberChanged,
1462 instForms_.get(), &InstrumentFormManager::onInstrumentSSGWaveformNumberChanged);
1463 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::waveformParameterChanged,
1464 instForms_.get(), &InstrumentFormManager::onInstrumentSSGWaveformParameterChanged);
1465 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::toneNoiseNumberChanged,
1466 instForms_.get(), &InstrumentFormManager::onInstrumentSSGToneNoiseNumberChanged);
1467 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::toneNoiseParameterChanged,
1468 instForms_.get(), &InstrumentFormManager::onInstrumentSSGToneNoiseParameterChanged);
1469 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::envelopeNumberChanged,
1470 instForms_.get(), &InstrumentFormManager::onInstrumentSSGEnvelopeNumberChanged);
1471 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::envelopeParameterChanged,
1472 instForms_.get(), &InstrumentFormManager::onInstrumentSSGEnvelopeParameterChanged);
1473 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::arpeggioNumberChanged,
1474 instForms_.get(), &InstrumentFormManager::onInstrumentSSGArpeggioNumberChanged);
1475 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::arpeggioParameterChanged,
1476 instForms_.get(), &InstrumentFormManager::onInstrumentSSGArpeggioParameterChanged);
1477 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::pitchNumberChanged,
1478 instForms_.get(), &InstrumentFormManager::onInstrumentSSGPitchNumberChanged);
1479 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::pitchParameterChanged,
1480 instForms_.get(), &InstrumentFormManager::onInstrumentSSGPitchParameterChanged);
1481 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::jamKeyOnEvent,
1482 this, [&](JamKey key) {
1483 bt_->jamKeyOnForced(key, SoundSource::SSG, !config_.lock()->getFixJammingVolume());
1484 }, Qt::DirectConnection);
1485 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::jamKeyOffEvent,
1486 this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::SSG); }
1487 , Qt::DirectConnection);
1488 QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::modified,
1489 this, &MainWindow::setModifiedTrue);
1490
1491 ssgForm->installEventFilter(this);
1492
1493 instForms_->onInstrumentSSGWaveformNumberChanged();
1494 instForms_->onInstrumentSSGToneNoiseNumberChanged();
1495 instForms_->onInstrumentSSGEnvelopeNumberChanged();
1496 instForms_->onInstrumentSSGArpeggioNumberChanged();
1497 instForms_->onInstrumentSSGPitchNumberChanged();
1498 break;
1499 }
1500 case InstrumentType::ADPCM:
1501 {
1502 auto adpcmForm = std::make_shared<InstrumentEditorADPCMForm>(num);
1503 form = adpcmForm;
1504 adpcmForm->setCore(bt_);
1505 adpcmForm->setConfiguration(config_.lock());
1506 adpcmForm->setColorPalette(palette_);
1507 adpcmForm->resize(config_.lock()->getInstrumentADPCMWindowWidth(),
1508 config_.lock()->getInstrumentADPCMWindowHeight());
1509
1510 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::sampleNumberChanged,
1511 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleNumberChanged);
1512 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::sampleParameterChanged,
1513 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleParameterChanged);
1514 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::sampleAssignRequested,
1515 this, &MainWindow::assignADPCMSamples);
1516 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::sampleMemoryChanged,
1517 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleMemoryUpdated);
1518 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::envelopeNumberChanged,
1519 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMEnvelopeNumberChanged);
1520 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::envelopeParameterChanged,
1521 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMEnvelopeParameterChanged);
1522 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::arpeggioNumberChanged,
1523 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMArpeggioNumberChanged);
1524 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::arpeggioParameterChanged,
1525 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMArpeggioParameterChanged);
1526 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::pitchNumberChanged,
1527 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMPitchNumberChanged);
1528 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::pitchParameterChanged,
1529 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMPitchParameterChanged);
1530 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::jamKeyOnEvent,
1531 this, [&](JamKey key) {
1532 bt_->jamKeyOnForced(key, SoundSource::ADPCM, !config_.lock()->getFixJammingVolume());
1533 }, Qt::DirectConnection);
1534 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::jamKeyOffEvent,
1535 this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::ADPCM); },
1536 Qt::DirectConnection);
1537 QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::modified,
1538 this, &MainWindow::setModifiedTrue);
1539
1540 adpcmForm->installEventFilter(this);
1541
1542 instForms_->onInstrumentADPCMSampleNumberChanged();
1543 instForms_->onInstrumentADPCMEnvelopeNumberChanged();
1544 instForms_->onInstrumentADPCMArpeggioNumberChanged();
1545 instForms_->onInstrumentADPCMPitchNumberChanged();
1546 break;
1547 }
1548 case InstrumentType::Drumkit:
1549 {
1550 auto kitForm = std::make_shared<InstrumentEditorDrumkitForm>(num);
1551 form = kitForm;
1552 kitForm->setCore(bt_);
1553 kitForm->setConfiguration(config_.lock());
1554 kitForm->setColorPalette(palette_);
1555 kitForm->resize(config_.lock()->getInstrumentDrumkitWindowWidth(),
1556 config_.lock()->getInstrumentDrumkitWindowHeight());
1557
1558 QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::sampleNumberChanged,
1559 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleNumberChanged);
1560 QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::sampleParameterChanged,
1561 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleParameterChanged);
1562 QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::sampleAssignRequested,
1563 this, &MainWindow::assignADPCMSamples);
1564 QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::sampleMemoryChanged,
1565 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleMemoryUpdated);
1566 QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::jamKeyOnEvent,
1567 this, [&](JamKey key) {
1568 bt_->jamKeyOnForced(key, SoundSource::ADPCM, !config_.lock()->getFixJammingVolume());
1569 }, Qt::DirectConnection);
1570 QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::jamKeyOffEvent,
1571 this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::ADPCM); },
1572 Qt::DirectConnection);
1573 QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::modified,
1574 this, &MainWindow::setModifiedTrue);
1575
1576 kitForm->installEventFilter(this);
1577
1578 instForms_->onInstrumentADPCMSampleNumberChanged();
1579 break;
1580 }
1581 default:
1582 throw std::invalid_argument("Invalid instrument type");
1583 }
1584
1585 form->addActions({ &octUpSc_, &octDownSc_, &jamVolUpSc_, &jamVolDownSc_ });
1586
1587 instForms_->add(num, std::move(form), inst->getSoundSource(), inst->getType());
1588 }
1589
1590 instForms_->showForm(num);
1591 }
1592
findRowFromInstrumentList(int instNum)1593 int MainWindow::findRowFromInstrumentList(int instNum)
1594 {
1595 auto& list = ui->instrumentList;
1596 int row = 0;
1597 for (; row < list->count(); ++row) {
1598 auto item = list->item(row);
1599 if (item->data(Qt::UserRole).toInt() == instNum) break;
1600 }
1601 return row;
1602 }
1603
renameInstrument()1604 void MainWindow::renameInstrument()
1605 {
1606 auto list = ui->instrumentList;
1607 auto item = list->currentItem();
1608 // Finish current edit
1609 if (item == renamingInstItem_) {
1610 finishRenamingInstrument();
1611 return;
1612 }
1613 else if (renamingInstItem_) {
1614 finishRenamingInstrument();
1615 }
1616 renamingInstItem_ = item;
1617
1618 int num = item->data(Qt::UserRole).toInt();
1619 renamingInstEdit_ = new QLineEdit(utf8ToQString(bt_->getInstrument(num)->getName()));
1620 QObject::connect(renamingInstEdit_, &QLineEdit::editingFinished, this, &MainWindow::finishRenamingInstrument);
1621 renamingInstEdit_->installEventFilter(this);
1622 ui->instrumentList->setItemWidget(item, renamingInstEdit_);
1623 renamingInstEdit_->selectAll();
1624 renamingInstEdit_->setFocus();
1625 }
1626
finishRenamingInstrument()1627 void MainWindow::finishRenamingInstrument()
1628 {
1629 if (!renamingInstItem_ || !renamingInstEdit_) return;
1630 bool hasFocus = renamingInstEdit_->hasFocus();
1631 auto list = ui->instrumentList;
1632 int num = renamingInstItem_->data(Qt::UserRole).toInt();
1633 int row = findRowFromInstrumentList(num);
1634 auto oldName = utf8ToQString(bt_->getInstrument(num)->getName());
1635 QString newName = renamingInstEdit_->text();
1636 list->removeItemWidget(renamingInstItem_);
1637 if (newName != oldName) {
1638 bt_->setInstrumentName(num, newName.toUtf8().toStdString());
1639 comStack_->push(new ChangeInstrumentNameQtCommand(list, num, row, instForms_, oldName, newName));
1640 }
1641 renamingInstItem_ = nullptr;
1642 renamingInstEdit_ = nullptr;
1643 if (hasFocus) ui->instrumentList->setFocus();
1644 }
1645
cloneInstrument()1646 void MainWindow::cloneInstrument()
1647 {
1648 int num = bt_->findFirstFreeInstrumentNumber();
1649 if (num == -1) return;
1650
1651 int refNum = ui->instrumentList->currentItem()->data(Qt::UserRole).toInt();
1652 // KEEP CODE ORDER //
1653 bt_->cloneInstrument(num, refNum);
1654 auto inst = bt_->getInstrument(num);
1655 comStack_->push(new CloneInstrumentQtCommand(ui->instrumentList, num, inst->getType(),
1656 utf8ToQString(inst->getName()), instForms_));
1657 //----------//
1658 }
1659
deepCloneInstrument()1660 void MainWindow::deepCloneInstrument()
1661 {
1662 int num = bt_->findFirstFreeInstrumentNumber();
1663 if (num == -1) return;
1664
1665 int refNum = ui->instrumentList->currentItem()->data(Qt::UserRole).toInt();
1666 // KEEP CODE ORDER //
1667 bt_->deepCloneInstrument(num, refNum);
1668 auto inst = bt_->getInstrument(num);
1669 comStack_->push(new DeepCloneInstrumentQtCommand(
1670 ui->instrumentList, num, inst->getType(), utf8ToQString(inst->getName()),
1671 instForms_, this, config_.lock()->getWriteOnlyUsedSamples()));
1672 //----------//
1673 }
1674
loadInstrument()1675 void MainWindow::loadInstrument()
1676 {
1677 QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
1678 QStringList filters {
1679 tr("BambooTracker instrument (*.bti)"),
1680 tr("DefleMask preset (*.dmp)"),
1681 tr("TFM Music Maker instrument (*.tfi)"),
1682 tr("VGM Music Maker instrument (*.vgi)"),
1683 tr("WOPN instrument (*.opni)"),
1684 tr("Gens KMod dump (*.y12)"),
1685 tr("MVSTracker instrument (*.ins)")
1686 };
1687 QString defaultFilter = filters.at(config_.lock()->getInstrumentOpenFormat());
1688
1689 QString file = QFileDialog::getOpenFileName(this, tr("Open instrument"), (dir.isEmpty() ? "./" : dir),
1690 filters.join(";;"), &defaultFilter);
1691 if (file.isNull()) return;
1692
1693 int index = getSelectedFileFilter(file, filters);
1694 if (index != -1) config_.lock()->setInstrumentOpenFormat(index);
1695
1696
1697 funcLoadInstrument(file);
1698 }
1699
funcLoadInstrument(QString file)1700 void MainWindow::funcLoadInstrument(QString file)
1701 {
1702 int n = bt_->findFirstFreeInstrumentNumber();
1703 if (n == -1) {
1704 FileIOErrorMessageBox(file, true, FileIO::FileType::Inst,
1705 tr("The number of instruments has reached the upper limit."), this).exec();
1706 return;
1707 }
1708
1709 try {
1710 QFile fp(file);
1711 if (!fp.open(QIODevice::ReadOnly)) {
1712 FileIOErrorMessageBox::openError(file, true, FileIO::FileType::Inst, this);
1713 return;
1714 }
1715 QByteArray array = fp.readAll();
1716 fp.close();
1717
1718 BinaryContainer contaner;
1719 contaner.appendVector(std::vector<char>(array.begin(), array.end()));
1720 bt_->loadInstrument(contaner, file.toStdString(), n);
1721
1722 auto inst = bt_->getInstrument(n);
1723 comStack_->push(new AddInstrumentQtCommand(
1724 ui->instrumentList, n, utf8ToQString(inst->getName()), inst->getType(),
1725 instForms_, this, config_.lock()->getWriteOnlyUsedSamples()));
1726 ui->instrumentList->setCurrentRow(n);
1727 config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
1728 }
1729 catch (FileIOError& e) {
1730 FileIOErrorMessageBox(file, true, e, this).exec();
1731 }
1732 catch (std::exception& e) {
1733 FileIOErrorMessageBox(file, true, FileIO::FileType::Inst, QString(e.what()), this).exec();
1734 }
1735 }
1736
saveInstrument()1737 void MainWindow::saveInstrument()
1738 {
1739 int n = ui->instrumentList->currentItem()->data(Qt::UserRole).toInt();
1740 QString name = utf8ToQString(bt_->getInstrument(n)->getName());
1741
1742 QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
1743 QString file = QFileDialog::getSaveFileName(
1744 this, tr("Save instrument"),
1745 QString("%1/%2.bti").arg(dir.isEmpty() ? "." : dir, name),
1746 tr("BambooTracker instrument (*.bti)"));
1747 if (file.isNull()) return;
1748 if (!file.endsWith(".bti")) file += ".bti"; // For linux
1749
1750 try {
1751 BinaryContainer container;
1752 bt_->saveInstrument(container, n);
1753
1754 QFile fp(file);
1755 if (!fp.open(QIODevice::WriteOnly)) {
1756 FileIOErrorMessageBox::openError(file, false, FileIO::FileType::Inst, this);
1757 return;
1758 }
1759 fp.write(container.getPointer(), container.size());
1760 fp.close();
1761
1762 config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
1763 }
1764 catch (FileIOError& e) {
1765 FileIOErrorMessageBox(file, false, e, this).exec();
1766 }
1767 catch (std::exception& e) {
1768 FileIOErrorMessageBox(file, false, FileIO::FileType::Inst, QString(e.what()), this).exec();
1769 }
1770 }
1771
importInstrumentsFromBank()1772 void MainWindow::importInstrumentsFromBank()
1773 {
1774 stopPlaySong();
1775
1776 QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
1777 QStringList filters {
1778 tr("BambooTracker bank (*.btb)"),
1779 tr("WOPN bank (*.wopn)"),
1780 tr("PMD FF (*.ff)"),
1781 tr("PMD PPC (*.ppc)"),
1782 tr("FMP PVI (*.pvi)"),
1783 tr("MUCOM88 voice (*.dat)")
1784 };
1785 QString defaultFilter = filters.at(config_.lock()->getBankOpenFormat());
1786
1787 QString file = QFileDialog::getOpenFileName(this, tr("Open bank"), (dir.isEmpty() ? "./" : dir),
1788 filters.join(";;"), &defaultFilter);
1789 if (file.isNull()) {
1790 return;
1791 }
1792 else {
1793 int index = getSelectedFileFilter(file, filters);
1794 if (index != -1) config_.lock()->setBankOpenFormat(index);
1795 }
1796
1797 funcImportInstrumentsFromBank(file);
1798 }
1799
funcImportInstrumentsFromBank(QString file)1800 void MainWindow::funcImportInstrumentsFromBank(QString file)
1801 {
1802 stopPlaySong();
1803
1804 std::unique_ptr<AbstractBank> bank;
1805 std::shared_ptr<InstrumentsManager> bankMan = std::make_shared<InstrumentsManager>(true);
1806 try {
1807 QFile fp(file);
1808 if (!fp.open(QIODevice::ReadOnly)) {
1809 FileIOErrorMessageBox::openError(file, true, FileIO::FileType::Bank, this);
1810 return;
1811 }
1812 QByteArray array = fp.readAll();
1813 fp.close();
1814
1815 BinaryContainer container;
1816 container.appendVector(std::vector<char>(array.begin(), array.end()));
1817
1818 bank.reset(BankIO::loadBank(container, file.toStdString()));
1819 config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
1820 }
1821 catch (FileIOError& e) {
1822 FileIOErrorMessageBox(file, true, e, this).exec();
1823 return;
1824 }
1825 catch (std::exception& e) {
1826 FileIOErrorMessageBox(file, true, FileIO::FileType::Bank, QString(e.what()), this).exec();
1827 return;
1828 }
1829
1830 // Change text codec
1831 if (auto ff = dynamic_cast<FfBank*>(bank.get())) {
1832 QTextCodec* codec = QTextCodec::codecForName("Shift-JIS");
1833 for (size_t i = 0; i < ff->getNumInstruments(); ++i) {
1834 std::string sjis = ff->getInstrumentName(i);
1835 std::string utf8 = codec->toUnicode(sjis.c_str(), sjis.length()).toStdString();
1836 ff->setInstrumentName(i, utf8);
1837 }
1838 }
1839 else if (auto mu88 = dynamic_cast<Mucom88Bank*>(bank.get())) {
1840 QTextCodec* codec = QTextCodec::codecForName("Shift-JIS");
1841 for (size_t i = 0; i < mu88->getNumInstruments(); ++i) {
1842 std::string sjis = mu88->getInstrumentName(i);
1843 std::string utf8 = codec->toUnicode(sjis.c_str(), sjis.length()).toStdString();
1844 mu88->setInstrumentName(i, utf8);
1845 }
1846 }
1847
1848 size_t jamId = 128; // Dummy
1849 std::shared_ptr<AbstractInstrument> jamInst;
1850 importBankDiag_ = std::make_unique<InstrumentSelectionDialog>(*bank, tr("Select instruments to load:"), config_, this);
1851 auto updateInst = [&] (size_t id) {
1852 if (id != jamId) {
1853 bankJamMidiCtrl_.store(true);
1854 jamId = id;
1855 bankMan->clearAll();
1856 jamInst.reset(bank->loadInstrument(id, bankMan, 0));
1857 jamInst->setNumber(128); // Special number
1858 std::vector<std::vector<size_t>> addrs = bt_->assignADPCMBeforeForcedJamKeyOn(jamInst);
1859 for (size_t i = 0; i < addrs.size(); ++i) {
1860 bankMan->setSampleADPCMStartAddress(i, addrs[i][0]);
1861 bankMan->setSampleADPCMStopAddress(i, addrs[i][1]);
1862 }
1863 bankJamMidiCtrl_.store(false);
1864 }
1865 };
1866 QObject::connect(importBankDiag_.get(), &InstrumentSelectionDialog::jamKeyOnEvent,
1867 this, [&](size_t id, JamKey key) {
1868 updateInst(id);
1869 bt_->jamKeyOnForced(key, jamInst->getSoundSource(),
1870 !config_.lock()->getFixJammingVolume(), jamInst);
1871 },
1872 Qt::DirectConnection);
1873 QObject::connect(importBankDiag_.get(), &InstrumentSelectionDialog::jamKeyOnMidiEvent,
1874 this, [&](size_t id, int key) {
1875 updateInst(id);
1876 bt_->jamKeyOnForced(key, jamInst->getSoundSource(),
1877 !config_.lock()->getFixJammingVolume(), jamInst);
1878 },
1879 Qt::DirectConnection);
1880 QObject::connect(importBankDiag_.get(), &InstrumentSelectionDialog::jamKeyOffEvent,
1881 this, [&](JamKey key) { bt_->jamKeyOffForced(key, jamInst->getSoundSource()); },
1882 Qt::DirectConnection);
1883 QObject::connect(importBankDiag_.get(), &InstrumentSelectionDialog::jamKeyOffMidiEvent,
1884 this, [&](int key) { if (jamInst) bt_->jamKeyOffForced(key, jamInst->getSoundSource()); },
1885 Qt::DirectConnection);
1886 importBankDiag_->addActions({ &octUpSc_, &octDownSc_ });
1887
1888 if (importBankDiag_->exec() != QDialog::Accepted) {
1889 assignADPCMSamples(); // Restore
1890 importBankDiag_.reset();
1891 return;
1892 }
1893
1894 QVector<size_t> selection = importBankDiag_->currentInstrumentSelection();
1895 importBankDiag_.reset();
1896 if (selection.empty()) return;
1897
1898 try {
1899 bool sampleRestoreRequested = false;
1900 int lastNum = ui->instrumentList->currentRow();
1901 for (const size_t& index : selection) {
1902 int n = bt_->findFirstFreeInstrumentNumber();
1903 if (n == -1){
1904 FileIOErrorMessageBox(file, true, FileIO::FileType::Inst,
1905 tr("The number of instruments has reached the upper limit."), this).exec();
1906 ui->instrumentList->setCurrentRow(lastNum);
1907 return;
1908 }
1909
1910 bt_->importInstrument(*bank, index, n);
1911
1912 auto inst = bt_->getInstrument(n);
1913 comStack_->push(new AddInstrumentQtCommand(
1914 ui->instrumentList, n, utf8ToQString(inst->getName()),
1915 inst->getType(), instForms_, this, config_.lock()->getWriteOnlyUsedSamples(), true));
1916 lastNum = n;
1917
1918 sampleRestoreRequested |= (inst->getSoundSource() == SoundSource::ADPCM);
1919 }
1920 ui->instrumentList->setCurrentRow(lastNum);
1921
1922 if (sampleRestoreRequested) assignADPCMSamples(); // Store only once
1923 }
1924 catch (FileIOError& e) {
1925 FileIOErrorMessageBox(file, true, e, this).exec();
1926 }
1927 catch (std::exception& e) {
1928 FileIOErrorMessageBox(file, true, FileIO::FileType::Bank, QString(e.what()), this).exec();
1929 }
1930 }
1931
exportInstrumentsToBank()1932 void MainWindow::exportInstrumentsToBank()
1933 {
1934 std::vector<int> ids = bt_->getInstrumentIndices();
1935 std::shared_ptr<BtBank> bank(std::make_shared<BtBank>(ids, bt_->getInstrumentNames()));
1936
1937 InstrumentSelectionDialog dlg(*bank, tr("Select instruments to save:"), config_, this);
1938
1939 if (dlg.exec() != QDialog::Accepted)
1940 return;
1941
1942 QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
1943 QString file = QFileDialog::getSaveFileName(this, tr("Save bank"), (dir.isEmpty() ? "./" : dir),
1944 tr("BambooTracker bank (*.btb)"));
1945 if (file.isNull()) return;
1946
1947 QVector<size_t> selection = dlg.currentInstrumentSelection();
1948 if (selection.empty()) return;
1949 std::vector<int> sel;
1950 std::transform(selection.begin(), selection.end(), std::back_inserter(sel),
1951 [&ids](size_t i) { return ids.at(i); });
1952 std::sort(sel.begin(), sel.end());
1953
1954 try {
1955 BinaryContainer container;
1956 bt_->exportInstruments(container, sel);
1957
1958 QFile fp(file);
1959 if (!fp.open(QIODevice::WriteOnly)) {
1960 FileIOErrorMessageBox::openError(file, false, FileIO::FileType::Bank, this);
1961 return;
1962 }
1963 fp.write(container.getPointer(), container.size());
1964 fp.close();
1965
1966 config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
1967 }
1968 catch (FileIOError& e) {
1969 FileIOErrorMessageBox(file, false, e, this).exec();
1970 }
1971 catch (std::exception& e) {
1972 FileIOErrorMessageBox(file, false, FileIO::FileType::Bank, QString(e.what()), this).exec();
1973 }
1974 }
1975
swapInstruments(int row1,int row2)1976 void MainWindow::swapInstruments(int row1, int row2)
1977 {
1978 if (row1 == row2) return;
1979
1980 // KEEP CODE ORDER //
1981 int num1 = ui->instrumentList->item(row1)->data(Qt::UserRole).toInt();
1982 int num2 = ui->instrumentList->item(row2)->data(Qt::UserRole).toInt();
1983 QString name1 = utf8ToQString(bt_->getInstrument(num1)->getName());
1984 QString name2 = utf8ToQString(bt_->getInstrument(num2)->getName());
1985
1986 bt_->swapInstruments(num1, num2, config_.lock()->getReflectInstrumentNumberChange());
1987 comStack_->push(new SwapInstrumentsQtCommand(
1988 ui->instrumentList, row1, row2, name1, name2, instForms_, ui->patternEditor));
1989 //----------//
1990 }
1991
1992 /********** Undo-Redo **********/
undo()1993 void MainWindow::undo()
1994 {
1995 bt_->undo();
1996 comStack_->undo();
1997 }
1998
redo()1999 void MainWindow::redo()
2000 {
2001 bt_->redo();
2002 comStack_->redo();
2003 }
2004
2005 /********** Load data **********/
loadModule()2006 void MainWindow::loadModule()
2007 {
2008 instForms_->clearAll();
2009 ui->instrumentList->clear();
2010 on_instrumentList_itemSelectionChanged();
2011
2012 ui->modTitleLineEdit->setText(utf8ToQString(bt_->getModuleTitle()));
2013 ui->modTitleLineEdit->setCursorPosition(0);
2014 ui->authorLineEdit->setText(utf8ToQString(bt_->getModuleAuthor()));
2015 ui->authorLineEdit->setCursorPosition(0);
2016 ui->copyrightLineEdit->setText(utf8ToQString(bt_->getModuleCopyright()));
2017 ui->copyrightLineEdit->setCursorPosition(0);
2018 {
2019 QSignalBlocker blocker(ui->songComboBox); // Prevent duplicated call "loadSong"
2020 ui->songComboBox->clear();
2021 for (size_t i = 0; i < bt_->getSongCount(); ++i) {
2022 QString title = utf8ToQString(bt_->getSongTitle(static_cast<int>(i)));
2023 if (title.isEmpty()) title = tr("Untitled");
2024 ui->songComboBox->addItem(QString("#%1 %2").arg(i).arg(title));
2025 }
2026 }
2027 highlight1_->setValue(static_cast<int>(bt_->getModuleStepHighlight1Distance()));
2028 highlight2_->setValue(static_cast<int>(bt_->getModuleStepHighlight2Distance()));
2029
2030 for (auto& idx : bt_->getInstrumentIndices()) {
2031 auto inst = bt_->getInstrument(idx);
2032 comStack_->push(new AddInstrumentQtCommand(
2033 ui->instrumentList, idx, utf8ToQString(inst->getName()), inst->getType(),
2034 instForms_, this, config_.lock()->getWriteOnlyUsedSamples(), true));
2035 }
2036
2037 isSavedModBefore_ = false;
2038
2039 loadSong();
2040
2041 // Set tick frequency
2042 stream_->setInterruption(bt_->getModuleTickFrequency());
2043 if (timer_) timer_->setInterval(1000000 / bt_->getModuleTickFrequency());
2044 statusIntr_->setText(QString::number(bt_->getModuleTickFrequency()) + QString("Hz"));
2045
2046 // Set mixer
2047 QString text;
2048 switch (bt_->getModuleMixerType()) {
2049 case MixerType::UNSPECIFIED:
2050 bt_->setMasterVolumeFM(config_.lock()->getMixerVolumeFM());
2051 bt_->setMasterVolumeSSG(config_.lock()->getMixerVolumeSSG());
2052 text = tr("-");
2053 break;
2054 case MixerType::CUSTOM:
2055 bt_->setMasterVolumeFM(bt_->getModuleCustomMixerFMLevel());
2056 bt_->setMasterVolumeSSG(bt_->getModuleCustomMixerSSGLevel());
2057 text = tr("Custom");
2058 break;
2059 case MixerType::PC_9821_PC_9801_86:
2060 bt_->setMasterVolumeFM(0);
2061 bt_->setMasterVolumeSSG(-5.5);
2062 text = tr("PC-9821 with PC-9801-86");
2063 break;
2064 case MixerType::PC_9821_SPEAK_BOARD:
2065 bt_->setMasterVolumeFM(0);
2066 bt_->setMasterVolumeSSG(-3.0);
2067 text = tr("PC-9821 with Speak Board");
2068 break;
2069 case MixerType::PC_8801_VA2:
2070 bt_->setMasterVolumeFM(0);
2071 bt_->setMasterVolumeSSG(1.5);
2072 text = tr("PC-88VA2");
2073 break;
2074 case MixerType::PC_8801_MKII_SR:
2075 bt_->setMasterVolumeFM(0);
2076 bt_->setMasterVolumeSSG(2.5);
2077 text = tr("NEC PC-8801mkIISR");
2078 break;
2079 }
2080 statusMixer_->setText(text);
2081
2082 // Set comment
2083 if (commentDiag_) commentDiag_->setComment(utf8ToQString(bt_->getModuleComment()));
2084
2085 // Clear records
2086 QApplication::clipboard()->clear();
2087 comStack_->clear();
2088 bt_->clearCommandHistory();
2089 }
2090
openModule(QString file)2091 void MainWindow::openModule(QString file)
2092 {
2093 try {
2094 freezeViews();
2095 if (timer_) timer_->stop();
2096 else stream_->stop();
2097
2098 BinaryContainer container;
2099 QFile fp(file);
2100 if (!fp.open(QIODevice::ReadOnly)) {
2101 FileIOErrorMessageBox::openError(file, true, FileIO::FileType::Mod, this);
2102 return;
2103 }
2104 QByteArray array = fp.readAll();
2105 fp.close();
2106
2107 container.appendVector(std::vector<char>(array.begin(), array.end()));
2108 bt_->loadModule(container);
2109 bt_->setModulePath(file.toStdString());
2110
2111 loadModule();
2112
2113 config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
2114 changeFileHistory(file);
2115 }
2116 catch (std::exception& e) {
2117 if (auto ef = dynamic_cast<FileIOError*>(&e)) {
2118 FileIOErrorMessageBox(file, true, *ef, this).exec();
2119 }
2120 else {
2121 FileIOErrorMessageBox(file, true, FileIO::FileType::Mod, QString(e.what()), this).exec();
2122 }
2123 // Init module
2124 freezeViews();
2125 bt_->makeNewModule();
2126 loadModule();
2127 }
2128
2129 isModifiedForNotCommand_ = false;
2130 setWindowModified(false);
2131 if (timer_) timer_->start();
2132 else stream_->start();
2133 setInitialSelectedInstrument();
2134 assignADPCMSamples();
2135 }
2136
loadSong()2137 void MainWindow::loadSong()
2138 {
2139 // Init position
2140 int songCnt = static_cast<int>(bt_->getSongCount());
2141 if (ui->songComboBox->currentIndex() >= songCnt)
2142 bt_->setCurrentSongNumber(songCnt - 1);
2143 else
2144 bt_->setCurrentSongNumber(bt_->getCurrentSongNumber());
2145 bt_->setCurrentOrderNumber(0);
2146 bt_->setCurrentTrack(0);
2147 bt_->setCurrentStepNumber(0);
2148
2149 // Init ui
2150 ui->orderList->onSongLoaded();
2151 setOrderListGroupMaximumWidth();
2152 ui->patternEditor->onSongLoaded();
2153 unfreezeViews();
2154
2155 int curSong = bt_->getCurrentSongNumber();
2156 ui->songComboBox->setCurrentIndex(curSong);
2157 ui->tempoSpinBox->setValue(bt_->getSongTempo(curSong));
2158 ui->speedSpinBox->setValue(bt_->getSongSpeed(curSong));
2159 ui->patternSizeSpinBox->setValue(static_cast<int>(bt_->getDefaultPatternSize(curSong)));
2160 ui->grooveSpinBox->setValue(bt_->getSongGroove(curSong));
2161 ui->grooveSpinBox->setMaximum(static_cast<int>(bt_->getGrooveCount()) - 1);
2162 if (bt_->isUsedTempoInSong(curSong)) {
2163 ui->tempoSpinBox->setEnabled(true);
2164 ui->speedSpinBox->setEnabled(true);
2165 ui->grooveCheckBox->setChecked(false);
2166 ui->grooveSpinBox->setEnabled(false);
2167 }
2168 else {
2169 ui->tempoSpinBox->setEnabled(false);
2170 ui->speedSpinBox->setEnabled(false);
2171 ui->grooveCheckBox->setChecked(true);
2172 ui->grooveSpinBox->setEnabled(true);
2173 }
2174 onCurrentTrackChanged();
2175
2176 setWindowTitle();
2177 switch (bt_->getSongStyle(bt_->getCurrentSongNumber()).type) {
2178 case SongType::Standard: statusStyle_->setText(tr("Standard")); break;
2179 case SongType::FM3chExpanded: statusStyle_->setText(tr("FM3ch expanded")); break;
2180 }
2181 statusPlayPos_->setText(config_.lock()->getShowRowNumberInHex() ? "00/00" : "000/000");
2182
2183 bmManForm_->onCurrentSongNumberChanged();
2184 }
2185
assignADPCMSamples()2186 void MainWindow::assignADPCMSamples()
2187 {
2188 bt_->stopPlaySong();
2189 lockWidgets(false);
2190 if (timer_) timer_->stop();
2191 else stream_->stop();
2192 bt_->assignSampleADPCMRawSamples(); // Mutex register
2193 instForms_->onInstrumentADPCMSampleMemoryUpdated();
2194 if (timer_) timer_->start();
2195 else stream_->start();
2196 }
2197
2198 /********** Play song **********/
startPlaySong()2199 void MainWindow::startPlaySong()
2200 {
2201 bt_->startPlaySong();
2202 lockWidgets(true);
2203 firstViewUpdateRequest_ = true;
2204 }
2205
startPlayFromStart()2206 void MainWindow::startPlayFromStart()
2207 {
2208 bt_->startPlayFromStart();
2209 lockWidgets(true);
2210 firstViewUpdateRequest_ = true;
2211 }
2212
startPlayPattern()2213 void MainWindow::startPlayPattern()
2214 {
2215 bt_->startPlayPattern();
2216 lockWidgets(true);
2217 firstViewUpdateRequest_ = true;
2218 }
2219
startPlayFromCurrentStep()2220 void MainWindow::startPlayFromCurrentStep()
2221 {
2222 bt_->startPlayFromCurrentStep();
2223 lockWidgets(true);
2224 firstViewUpdateRequest_ = true;
2225 }
2226
startPlayFromMarker()2227 void MainWindow::startPlayFromMarker()
2228 {
2229 if (bt_->startPlayFromMarker()) {
2230 lockWidgets(true);
2231 firstViewUpdateRequest_ = true;
2232 }
2233 }
2234
playStep()2235 void MainWindow::playStep()
2236 {
2237 if (!bt_->isPlaySong()) {
2238 bt_->playStep();
2239 firstViewUpdateRequest_ = true;
2240 ui->patternEditor->onPlayStepPressed();
2241 }
2242 }
2243
stopPlaySong()2244 void MainWindow::stopPlaySong()
2245 {
2246 bt_->stopPlaySong();
2247 lockWidgets(false);
2248 ui->patternEditor->onStoppedPlaySong();
2249 ui->orderList->onStoppedPlaySong();
2250 }
2251
lockWidgets(bool isLock)2252 void MainWindow::lockWidgets(bool isLock)
2253 {
2254 hasLockedWigets_ = isLock;
2255 ui->songComboBox->setEnabled(!isLock);
2256 }
2257
2258 /********** Octave change **********/
changeOctave(bool upFlag)2259 void MainWindow::changeOctave(bool upFlag)
2260 {
2261 if (upFlag) octave_->stepUp();
2262 else octave_->stepDown();
2263
2264 statusOctave_->setText(tr("Octave: %1").arg(bt_->getCurrentOctave()));
2265 }
2266
2267 /********** Configuration change **********/
changeConfiguration()2268 void MainWindow::changeConfiguration()
2269 {
2270 // Real chip interface
2271 bool streamState = false;
2272 RealChipInterface intf = config_.lock()->getRealChipInterface();
2273 if (intf == RealChipInterface::NONE) {
2274 timer_.reset();
2275 bt_->useSCCI(nullptr);
2276 bt_->useC86CTL(nullptr);
2277 QString streamErr;
2278 streamState = stream_->initialize(
2279 config_.lock()->getSampleRate(),
2280 config_.lock()->getBufferLength(),
2281 bt_->getModuleTickFrequency(),
2282 utf8ToQString(config_.lock()->getSoundAPI()),
2283 utf8ToQString(config_.lock()->getSoundDevice()),
2284 &streamErr);
2285 if (!streamState) showStreamFailedDialog(streamErr);
2286 stream_->start();
2287 }
2288 else {
2289 stream_->stop();
2290 if (timer_) {
2291 timer_->stop();
2292 }
2293 else {
2294 timer_ = std::make_unique<Timer>();
2295 timer_->setInterval(1000000 / bt_->getModuleTickFrequency());
2296 tickEventMethod_ = metaObject()->indexOfSlot("onNewTickSignaledRealChip()");
2297 Q_ASSERT(tickEventMethod_ != -1);
2298 timer_->setFunction([&]{
2299 QMetaMethod method = this->metaObject()->method(this->tickEventMethod_);
2300 method.invoke(this, Qt::QueuedConnection);
2301 });
2302 }
2303
2304 setRealChipInterface(intf);
2305
2306 timer_->start();
2307 }
2308
2309 setMidiConfiguration();
2310 updateFonts();
2311 ui->orderList->setHorizontalScrollMode(config_.lock()->getMoveCursorByHorizontalScroll());
2312 ui->patternEditor->setHorizontalScrollMode(config_.lock()->getMoveCursorByHorizontalScroll());
2313 instForms_->updateByConfiguration();
2314
2315 bt_->changeConfiguration(config_);
2316
2317 if (streamState) {
2318 uint32_t sr = stream_->getStreamRate();
2319 if (config_.lock()->getSampleRate() != sr) {
2320 showStreamRateWarningDialog(sr);
2321 bt_->setStreamRate(sr);
2322 }
2323 }
2324
2325 setShortcuts();
2326 updateInstrumentListColors();
2327 bmManForm_->onConfigurationChanged(config_.lock()->getShowRowNumberInHex());
2328
2329 visualTimer_->stop();
2330 visualTimer_->start(static_cast<int>(std::round(1000. / config_.lock()->getWaveViewFrameRate())));
2331
2332 update();
2333 }
2334
setRealChipInterface(RealChipInterface intf)2335 void MainWindow::setRealChipInterface(RealChipInterface intf)
2336 {
2337 if (intf == bt_->getRealChipinterface()) return;
2338
2339 if (isWindowModified()
2340 && QMessageBox::warning(this, tr("Warning"),
2341 tr("The module has been changed. Do you want to save it?"),
2342 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
2343 on_actionSave_As_triggered();
2344 }
2345
2346 switch (intf) {
2347 case RealChipInterface::SCCI:
2348 bt_->useC86CTL(nullptr);
2349 scciDll_->load();
2350 if (scciDll_->isLoaded()) {
2351 auto getManager = reinterpret_cast<scci::SCCIFUNC>(
2352 scciDll_->resolve("getSoundInterfaceManager"));
2353 bt_->useSCCI(getManager ? getManager() : nullptr);
2354 }
2355 else {
2356 bt_->useSCCI(nullptr);
2357 }
2358 break;
2359 case RealChipInterface::C86CTL:
2360 bt_->useSCCI(nullptr);
2361 c86ctlDll_->load();
2362 if (c86ctlDll_->isLoaded()) {
2363 bt_->useC86CTL(new C86ctlBase(c86ctlDll_->resolve("CreateInstance")));
2364 }
2365 else {
2366 bt_->useC86CTL(nullptr);
2367 }
2368 break;
2369 default:
2370 break;
2371 }
2372
2373 bt_->assignSampleADPCMRawSamples(); // Mutex register
2374 instForms_->onInstrumentADPCMSampleMemoryUpdated();
2375 }
2376
setMidiConfiguration()2377 void MainWindow::setMidiConfiguration()
2378 {
2379 MidiInterface &midiIntf = MidiInterface::instance();
2380 std::string midiApi = config_.lock()->getMidiAPI();
2381 std::string midiInPortName = config_.lock()->getMidiInputPort();
2382
2383 std::string errDetail;
2384 if (!midiIntf.switchApi(midiApi, &errDetail))
2385 showMidiFailedDialog(QString::fromStdString(errDetail));
2386
2387 bool res = true;
2388 if (!midiInPortName.empty())
2389 res = midiIntf.openInputPortByName(midiInPortName, &errDetail);
2390 else if (midiIntf.supportsVirtualPort())
2391 res = midiIntf.openInputPort(~0u, &errDetail);
2392 if (!res) showMidiFailedDialog(QString::fromStdString(errDetail));
2393 }
2394
updateFonts()2395 void MainWindow::updateFonts()
2396 {
2397 ui->patternEditor->setFonts(
2398 utf8ToQString(config_.lock()->getPatternEditorHeaderFont()),
2399 config_.lock()->getPatternEditorHeaderFontSize(),
2400 utf8ToQString(config_.lock()->getPatternEditorRowsFont()),
2401 config_.lock()->getPatternEditorRowsFontSize());
2402 ui->orderList->setFonts(
2403 utf8ToQString(config_.lock()->getOrderListHeaderFont()),
2404 config_.lock()->getOrderListHeaderFontSize(),
2405 utf8ToQString(config_.lock()->getOrderListRowsFont()),
2406 config_.lock()->getOrderListRowsFontSize());
2407 }
2408
2409 /********** History change **********/
changeFileHistory(QString file)2410 void MainWindow::changeFileHistory(QString file)
2411 {
2412 fileHistory_->addFile(file);
2413 for (int i = ui->menu_Recent_Files->actions().count() - 1; 1 < i; --i)
2414 ui->menu_Recent_Files->removeAction(ui->menu_Recent_Files->actions().at(i));
2415 for (size_t i = 0; i < fileHistory_->size(); ++i) {
2416 // Leave Before Qt5.7.0 style due to windows xp
2417 QAction* action = ui->menu_Recent_Files->addAction(QString("&%1 %2").arg(i + 1).arg(fileHistory_->at(i)));
2418 action->setData(fileHistory_->at(i));
2419 }
2420 }
2421
2422 /********** Backup **********/
backupModule(QString srcFile)2423 bool MainWindow::backupModule(QString srcFile)
2424 {
2425 if (!isSavedModBefore_ && config_.lock()->getBackupModules()) {
2426 bool err = false;
2427 QString backup = srcFile + ".bak";
2428 if (QFile::exists(backup)) err = !QFile::remove(backup);
2429 if (err || !QFile::copy(srcFile, backup)) {
2430 QMessageBox::critical(this, tr("Error"), tr("Failed to backup module."));
2431 return false;
2432 }
2433 }
2434 return true;
2435 }
2436
2437 /******************************/
setWindowTitle()2438 void MainWindow::setWindowTitle()
2439 {
2440 int n = bt_->getCurrentSongNumber();
2441 QString filePath = QString::fromStdString(bt_->getModulePath());
2442 QString fileName = filePath.isEmpty() ? tr("Untitled") : QFileInfo(filePath).fileName();
2443 QString songTitle = utf8ToQString(bt_->getSongTitle(n));
2444 if (songTitle.isEmpty()) songTitle = tr("Untitled");
2445 QMainWindow::setWindowTitle(QString("%1[*] [#%2 %3] - BambooTracker")
2446 .arg(fileName, QString::number(n), songTitle));
2447 }
2448
setModifiedTrue()2449 void MainWindow::setModifiedTrue()
2450 {
2451 isModifiedForNotCommand_ = true;
2452 setWindowModified(true);
2453 }
2454
setInitialSelectedInstrument()2455 void MainWindow::setInitialSelectedInstrument()
2456 {
2457 if (bt_->getInstrumentIndices().empty()) {
2458 bt_->setCurrentInstrument(-1);
2459 statusInst_->setText(tr("No instrument"));
2460 }
2461 else {
2462 ui->instrumentList->setCurrentRow(0);
2463 }
2464 }
2465
getModuleFileBaseName() const2466 QString MainWindow::getModuleFileBaseName() const
2467 {
2468 auto filePathStd = bt_->getModulePath();
2469 QString filePath = QString::fromStdString(filePathStd);
2470 return (filePath.isEmpty() ? tr("Untitled") : QFileInfo(filePath).completeBaseName());
2471 }
2472
getSelectedFileFilter(QString & file,QStringList & filters) const2473 int MainWindow::getSelectedFileFilter(QString& file, QStringList& filters) const
2474 {
2475 QRegularExpression re(R"(\(\*\.(.+)\))");
2476 QString ex = QFileInfo(file).suffix();
2477 for (int i = 0; i < filters.size(); ++i)
2478 if (ex == re.match(filters[i]).captured(1)) return i;
2479 return -1;
2480 }
2481
2482 /******************************/
2483 /********** Instrument list events **********/
on_instrumentList_customContextMenuRequested(const QPoint & pos)2484 void MainWindow::on_instrumentList_customContextMenuRequested(const QPoint &pos)
2485 {
2486 auto& list = ui->instrumentList;
2487 QPoint globalPos = list->mapToGlobal(pos);
2488 QMenu menu;
2489
2490 // Leave Before Qt5.7.0 style due to windows xp
2491 menu.addActions({ ui->actionNew_Instrument, ui->actionNew_Drumki_t, ui->actionRemove_Instrument });
2492 menu.addSeparator();
2493 menu.addAction(ui->actionRename_Instrument);
2494 menu.addSeparator();
2495 menu.addActions({ ui->actionClone_Instrument, ui->actionDeep_Clone_Instrument });
2496 menu.addSeparator();
2497 menu.addActions({ ui->actionLoad_From_File, ui->actionSave_To_File });
2498 menu.addSeparator();
2499 menu.addActions({ ui->actionImport_From_Bank_File, ui->actionExport_To_Bank_File });
2500 menu.addSeparator();
2501 menu.addAction(ui->actionEdit);
2502
2503 menu.exec(globalPos);
2504 }
2505
on_instrumentList_itemDoubleClicked(QListWidgetItem * item)2506 void MainWindow::on_instrumentList_itemDoubleClicked(QListWidgetItem *item)
2507 {
2508 Q_UNUSED(item)
2509 openInstrumentEditor();
2510 }
2511
on_instrumentList_itemSelectionChanged()2512 void MainWindow::on_instrumentList_itemSelectionChanged()
2513 {
2514 int num = (ui->instrumentList->currentRow() == -1)
2515 ? -1
2516 : ui->instrumentList->currentItem()->data(Qt::UserRole).toInt();
2517 bt_->setCurrentInstrument(num);
2518
2519 if (num == -1) statusInst_->setText(tr("No instrument"));
2520 else statusInst_->setText(
2521 tr("Instrument: %1").arg(QString("%1").arg(num, 2, 16, QChar('0')).toUpper()));
2522
2523 bool canAdd = (bt_->findFirstFreeInstrumentNumber() != -1);
2524 ui->actionLoad_From_File->setEnabled(canAdd);
2525 ui->actionImport_From_Bank_File->setEnabled(canAdd);
2526
2527 bool isSelected = (num != -1);
2528 ui->actionRemove_Instrument->setEnabled(isSelected);
2529 ui->actionClone_Instrument->setEnabled(isSelected);
2530 ui->actionDeep_Clone_Instrument->setEnabled(isSelected);
2531 ui->actionSave_To_File->setEnabled(isSelected);
2532 ui->actionExport_To_Bank_File->setEnabled(isSelected);
2533 ui->actionRename_Instrument->setEnabled(isSelected);
2534 ui->actionEdit->setEnabled(isSelected);
2535 }
2536
on_grooveCheckBox_stateChanged(int arg1)2537 void MainWindow::on_grooveCheckBox_stateChanged(int arg1)
2538 {
2539 if (arg1 == Qt::Checked) {
2540 ui->tempoSpinBox->setEnabled(false);
2541 ui->speedSpinBox->setEnabled(false);
2542 ui->grooveSpinBox->setEnabled(true);
2543 bt_->toggleTempoOrGrooveInSong(bt_->getCurrentSongNumber(), false);
2544 }
2545 else {
2546 ui->tempoSpinBox->setEnabled(true);
2547 ui->speedSpinBox->setEnabled(true);
2548 ui->grooveSpinBox->setEnabled(false);
2549 bt_->toggleTempoOrGrooveInSong(bt_->getCurrentSongNumber(), true);
2550 }
2551
2552 setModifiedTrue();
2553 }
2554
on_actionExit_triggered()2555 void MainWindow::on_actionExit_triggered()
2556 {
2557 close();
2558 }
2559
on_actionUndo_triggered()2560 void MainWindow::on_actionUndo_triggered()
2561 {
2562 undo();
2563 }
2564
on_actionRedo_triggered()2565 void MainWindow::on_actionRedo_triggered()
2566 {
2567 redo();
2568 }
2569
on_actionCut_triggered()2570 void MainWindow::on_actionCut_triggered()
2571 {
2572 if (isEditedPattern_) ui->patternEditor->cutSelectedCells();
2573 }
2574
on_actionCopy_triggered()2575 void MainWindow::on_actionCopy_triggered()
2576 {
2577 if (isEditedPattern_) ui->patternEditor->copySelectedCells();
2578 else if (isEditedOrder_) ui->orderList->copySelectedCells();
2579 }
2580
on_actionPaste_triggered()2581 void MainWindow::on_actionPaste_triggered()
2582 {
2583 if (isEditedPattern_) ui->patternEditor->onPastePressed();
2584 else if (isEditedOrder_) ui->orderList->onPastePressed();
2585 }
2586
on_actionDelete_triggered()2587 void MainWindow::on_actionDelete_triggered()
2588 {
2589 if (isEditedPattern_) ui->patternEditor->onDeletePressed();
2590 else if (isEditedOrder_) ui->orderList->deleteOrder();
2591 else if (isEditedInstList_) on_actionRemove_Instrument_triggered();
2592 }
2593
updateMenuByPattern()2594 void MainWindow::updateMenuByPattern()
2595 {
2596 isEditedPattern_ = true;
2597 isEditedOrder_ = false;
2598 isEditedInstList_ = false;
2599
2600 if (bt_->isJamMode()) {
2601 // Edit
2602 ui->actionPaste->setEnabled(false);
2603 ui->actionMix->setEnabled(false);
2604 ui->actionOverwrite->setEnabled(false);
2605 ui->action_Insert->setEnabled(false);
2606 ui->actionDelete->setEnabled(false);
2607 // Pattern
2608 ui->actionInterpolate->setEnabled(false);
2609 ui->actionReverse->setEnabled(false);
2610 ui->actionReplace_Instrument->setEnabled(false);
2611 ui->actionExpand->setEnabled(false);
2612 ui->actionShrink->setEnabled(false);
2613 ui->actionDecrease_Note->setEnabled(false);
2614 ui->actionIncrease_Note->setEnabled(false);
2615 ui->actionDecrease_Octave->setEnabled(false);
2616 ui->actionIncrease_Octave->setEnabled(false);
2617 ui->actionFine_Decrease_Values->setEnabled(false);
2618 ui->actionFine_Increase_Values->setEnabled(false);
2619 ui->actionCoarse_D_ecrease_Values->setEnabled(false);
2620 ui->actionCoarse_I_ncrease_Values->setEnabled(false);
2621 }
2622 else {
2623 // Edit
2624 bool enabled = QApplication::clipboard()->text().startsWith("PATTERN_");
2625 ui->actionPaste->setEnabled(enabled);
2626 ui->actionMix->setEnabled(enabled);
2627 ui->actionOverwrite->setEnabled(enabled);
2628 ui->action_Insert->setEnabled(enabled);
2629 ui->actionDelete->setEnabled(true);
2630 // Pattern
2631 ui->actionInterpolate->setEnabled(isSelectedPattern_);
2632 ui->actionReverse->setEnabled(isSelectedPattern_);
2633 ui->actionReplace_Instrument->setEnabled(
2634 isSelectedPattern_ && ui->instrumentList->currentRow() != -1);
2635 ui->actionExpand->setEnabled(isSelectedPattern_);
2636 ui->actionShrink->setEnabled(isSelectedPattern_);
2637 ui->actionDecrease_Note->setEnabled(true);
2638 ui->actionIncrease_Note->setEnabled(true);
2639 ui->actionDecrease_Octave->setEnabled(true);
2640 ui->actionIncrease_Octave->setEnabled(true);
2641 ui->actionFine_Decrease_Values->setEnabled(true);
2642 ui->actionFine_Increase_Values->setEnabled(true);
2643 ui->actionCoarse_D_ecrease_Values->setEnabled(true);
2644 ui->actionCoarse_I_ncrease_Values->setEnabled(true);
2645 }
2646
2647 updateMenuByPatternSelection(isSelectedPattern_);
2648 }
2649
updateMenuByOrder()2650 void MainWindow::updateMenuByOrder()
2651 {
2652 isEditedPattern_ = false;
2653 isEditedOrder_ = true;
2654 isEditedInstList_ = false;
2655
2656 // Edit
2657 bool enabled = QApplication::clipboard()->text().startsWith("ORDER_");
2658 ui->actionCut->setEnabled(false);
2659 ui->actionPaste->setEnabled(enabled);
2660 ui->actionMix->setEnabled(false);
2661 ui->actionOverwrite->setEnabled(false);
2662 ui->action_Insert->setEnabled(false);
2663 ui->actionDelete->setEnabled(true);
2664 // Song
2665 bool canAdd = bt_->canAddNewOrder(bt_->getCurrentSongNumber());
2666 ui->actionInsert_Order->setEnabled(canAdd);
2667 //ui->actionRemove_Order->setEnabled(true);
2668 ui->actionDuplicate_Order->setEnabled(canAdd);
2669 //ui->actionMove_Order_Up->setEnabled(true);
2670 //ui->actionMove_Order_Down->setEnabled(true);
2671 ui->actionClone_Patterns->setEnabled(canAdd);
2672 ui->actionClone_Order->setEnabled(canAdd);
2673 // Pattern
2674 ui->actionInterpolate->setEnabled(false);
2675 ui->actionReverse->setEnabled(false);
2676 ui->actionReplace_Instrument->setEnabled(false);
2677 ui->actionExpand->setEnabled(false);
2678 ui->actionShrink->setEnabled(false);
2679 ui->actionDecrease_Note->setEnabled(false);
2680 ui->actionIncrease_Note->setEnabled(false);
2681 ui->actionDecrease_Octave->setEnabled(false);
2682 ui->actionIncrease_Octave->setEnabled(false);
2683 ui->actionFine_Decrease_Values->setEnabled(false);
2684 ui->actionFine_Increase_Values->setEnabled(false);
2685 ui->actionCoarse_D_ecrease_Values->setEnabled(false);
2686 ui->actionCoarse_I_ncrease_Values->setEnabled(false);
2687
2688 updateMenuByOrderSelection(isSelectedOrder_);
2689 }
2690
onCurrentTrackChanged()2691 void MainWindow::onCurrentTrackChanged()
2692 {
2693 SoundSource src = bt_->getCurrentTrackAttribute().source;
2694 bool space = (bt_->findFirstFreeInstrumentNumber() != -1);
2695 ui->actionNew_Instrument->setEnabled((src != SoundSource::RHYTHM) && space);
2696 ui->actionNew_Drumki_t->setEnabled((src == SoundSource::ADPCM) && space);
2697 }
2698
updateMenuByInstrumentList()2699 void MainWindow::updateMenuByInstrumentList()
2700 {
2701 isEditedPattern_ = false;
2702 isEditedOrder_ = false;
2703 isEditedInstList_ = true;
2704
2705 // Edit
2706 ui->actionPaste->setEnabled(false);
2707 ui->actionMix->setEnabled(false);
2708 ui->actionOverwrite->setEnabled(false);
2709 ui->action_Insert->setEnabled(false);
2710 ui->actionDelete->setEnabled(true);
2711
2712 // Pattern
2713 ui->actionInterpolate->setEnabled(false);
2714 ui->actionReverse->setEnabled(false);
2715 ui->actionReplace_Instrument->setEnabled(false);
2716 ui->actionExpand->setEnabled(false);
2717 ui->actionShrink->setEnabled(false);
2718 ui->actionDecrease_Note->setEnabled(false);
2719 ui->actionIncrease_Note->setEnabled(false);
2720 ui->actionDecrease_Octave->setEnabled(false);
2721 ui->actionIncrease_Octave->setEnabled(false);
2722 ui->actionFine_Decrease_Values->setEnabled(false);
2723 ui->actionFine_Increase_Values->setEnabled(false);
2724 ui->actionCoarse_D_ecrease_Values->setEnabled(false);
2725 ui->actionCoarse_I_ncrease_Values->setEnabled(false);
2726 }
2727
updateMenuByPatternSelection(bool isSelected)2728 void MainWindow::updateMenuByPatternSelection(bool isSelected)
2729 {
2730 isSelectedPattern_ = isSelected;
2731
2732 if (bt_->isJamMode()) {
2733 // Edit
2734 ui->actionCopy->setEnabled(false);
2735 ui->actionCut->setEnabled(false);
2736 // Pattern
2737 ui->actionInterpolate->setEnabled(false);
2738 ui->actionReverse->setEnabled(false);
2739 ui->actionReplace_Instrument->setEnabled(false);
2740 ui->actionExpand->setEnabled(false);
2741 ui->actionShrink->setEnabled(false);
2742 }
2743 else {
2744 // Edit
2745 ui->actionCopy->setEnabled(isSelected);
2746 ui->actionCut->setEnabled(isEditedPattern_ ? isSelected : false);
2747 // Pattern
2748 bool enabled = (isEditedPattern_ && isEditedPattern_) ? isSelected : false;
2749 ui->actionInterpolate->setEnabled(enabled);
2750 ui->actionReverse->setEnabled(enabled);
2751 ui->actionReplace_Instrument->setEnabled(
2752 enabled && ui->instrumentList->currentRow() != -1);
2753 ui->actionExpand->setEnabled(enabled);
2754 ui->actionShrink->setEnabled(enabled);
2755 }
2756 }
2757
updateMenuByOrderSelection(bool isSelected)2758 void MainWindow::updateMenuByOrderSelection(bool isSelected)
2759 {
2760 isSelectedOrder_ = isSelected;
2761
2762 // Edit
2763 ui->actionCopy->setEnabled(isSelected);
2764 }
2765
on_actionAll_triggered()2766 void MainWindow::on_actionAll_triggered()
2767 {
2768 if (isEditedPattern_) ui->patternEditor->onSelectPressed(1);
2769 else if (isEditedOrder_) ui->orderList->onSelectPressed(1);
2770 }
2771
on_actionNone_triggered()2772 void MainWindow::on_actionNone_triggered()
2773 {
2774 if (isEditedPattern_) ui->patternEditor->onSelectPressed(0);
2775 else if (isEditedOrder_) ui->orderList->onSelectPressed(0);
2776 }
2777
on_actionDecrease_Note_triggered()2778 void MainWindow::on_actionDecrease_Note_triggered()
2779 {
2780 if (isEditedPattern_) ui->patternEditor->onTransposePressed(false, false);
2781 }
2782
on_actionIncrease_Note_triggered()2783 void MainWindow::on_actionIncrease_Note_triggered()
2784 {
2785 if (isEditedPattern_) ui->patternEditor->onTransposePressed(false, true);
2786 }
2787
on_actionDecrease_Octave_triggered()2788 void MainWindow::on_actionDecrease_Octave_triggered()
2789 {
2790 if (isEditedPattern_) ui->patternEditor->onTransposePressed(true, false);
2791 }
2792
on_actionIncrease_Octave_triggered()2793 void MainWindow::on_actionIncrease_Octave_triggered()
2794 {
2795 if (isEditedPattern_) ui->patternEditor->onTransposePressed(true, true);
2796 }
2797
on_actionInsert_Order_triggered()2798 void MainWindow::on_actionInsert_Order_triggered()
2799 {
2800 ui->orderList->insertOrderBelow();
2801 }
2802
on_actionRemove_Order_triggered()2803 void MainWindow::on_actionRemove_Order_triggered()
2804 {
2805 ui->orderList->deleteOrder();
2806 }
2807
on_actionModule_Properties_triggered()2808 void MainWindow::on_actionModule_Properties_triggered()
2809 {
2810 ModulePropertiesDialog dialog(bt_, config_.lock()->getMixerVolumeFM(), config_.lock()->getMixerVolumeSSG());
2811 if (dialog.exec() == QDialog::Accepted
2812 && showUndoResetWarningDialog(tr("Do you want to change song properties?"))) {
2813 int instRow = ui->instrumentList->currentRow();
2814 bt_->stopPlaySong();
2815 lockWidgets(false);
2816 dialog.onAccepted();
2817 freezeViews();
2818 if (!timer_) stream_->stop();
2819 loadModule();
2820 setModifiedTrue();
2821 setWindowTitle();
2822 ui->instrumentList->setCurrentRow(instRow);
2823 if (!timer_) stream_->start();
2824 assignADPCMSamples();
2825 }
2826 }
2827
on_actionNew_Instrument_triggered()2828 void MainWindow::on_actionNew_Instrument_triggered()
2829 {
2830 addInstrument();
2831 }
2832
on_actionRemove_Instrument_triggered()2833 void MainWindow::on_actionRemove_Instrument_triggered()
2834 {
2835 removeInstrument(ui->instrumentList->currentRow());
2836 }
2837
on_actionClone_Instrument_triggered()2838 void MainWindow::on_actionClone_Instrument_triggered()
2839 {
2840 cloneInstrument();
2841 }
2842
on_actionDeep_Clone_Instrument_triggered()2843 void MainWindow::on_actionDeep_Clone_Instrument_triggered()
2844 {
2845 deepCloneInstrument();
2846 }
2847
on_actionEdit_triggered()2848 void MainWindow::on_actionEdit_triggered()
2849 {
2850 openInstrumentEditor();
2851 }
2852
on_actionPlay_triggered()2853 void MainWindow::on_actionPlay_triggered()
2854 {
2855 startPlaySong();
2856 }
2857
on_actionPlay_Pattern_triggered()2858 void MainWindow::on_actionPlay_Pattern_triggered()
2859 {
2860 startPlayPattern();
2861 }
2862
on_actionPlay_From_Start_triggered()2863 void MainWindow::on_actionPlay_From_Start_triggered()
2864 {
2865 startPlayFromStart();
2866 }
2867
on_actionPlay_From_Cursor_triggered()2868 void MainWindow::on_actionPlay_From_Cursor_triggered()
2869 {
2870 startPlayFromCurrentStep();
2871 }
2872
on_actionStop_triggered()2873 void MainWindow::on_actionStop_triggered()
2874 {
2875 stopPlaySong();
2876 }
2877
on_actionEdit_Mode_triggered()2878 void MainWindow::on_actionEdit_Mode_triggered()
2879 {
2880 bt_->toggleJamMode();
2881 ui->orderList->changeEditable();
2882 ui->patternEditor->changeEditable();
2883
2884 if (isEditedOrder_) updateMenuByOrder();
2885 else if (isEditedPattern_) updateMenuByPattern();
2886
2887 bool editable = !bt_->isJamMode();
2888 statusDetail_->setText(editable ? tr("Change to edit mode") : tr("Change to jam mode"));
2889 ui->action_Toggle_Bookmark->setEnabled(editable);
2890 }
2891
on_actionToggle_Track_triggered()2892 void MainWindow::on_actionToggle_Track_triggered()
2893 {
2894 ui->patternEditor->onToggleTrackPressed();
2895 }
2896
on_actionSolo_Track_triggered()2897 void MainWindow::on_actionSolo_Track_triggered()
2898 {
2899 ui->patternEditor->onSoloTrackPressed();
2900 }
2901
on_actionKill_Sound_triggered()2902 void MainWindow::on_actionKill_Sound_triggered()
2903 {
2904 bt_->killSound();
2905 }
2906
on_actionAbout_triggered()2907 void MainWindow::on_actionAbout_triggered()
2908 {
2909 QMessageBox box(QMessageBox::NoIcon,
2910 tr("About"),
2911 QString("<h2>BambooTracker v%1</h2>").arg(
2912 QString::fromStdString(Version::ofApplicationInString()))
2913 + tr("<b>YM2608 (OPNA) Music Tracker<br>"
2914 "Copyright (C) 2018-2020 Rerrah</b><br>"
2915 "<hr>"
2916 "Libraries:<br>"
2917 "- C86CTL by (C) honet (BSD 3-Clause)<br>"
2918 "- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>"
2919 "- MAME (MAME License)<br>"
2920 "- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>"
2921 "and (C) Jean Pierre Cimalando (LGPL v2.1)<br>"
2922 "- RtAudio by (C) Gary P. Scavone (RtAudio License)<br>"
2923 "- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>"
2924 "- SCCI by (C) gasshi (SCCI License)<br>"
2925 "- Silk icons by (C) Mark James (CC BY 2.5 or 3.0)<br>"
2926 "- Qt (GPL v2+ or LGPL v3)<br>"
2927 "- VGMPlay by (C) Valley Bell (GPL v2)<br>"
2928 "<br>"
2929 "Also see changelog which lists contributors."),
2930 QMessageBox::Ok,
2931 this);
2932 box.setIconPixmap(QIcon(":/icon/app_icon").pixmap(QSize(44, 44)));
2933 box.exec();
2934 }
2935
on_actionFollow_Mode_triggered()2936 void MainWindow::on_actionFollow_Mode_triggered()
2937 {
2938 bt_->setFollowPlay(ui->actionFollow_Mode->isChecked());
2939 config_.lock()->setFollowMode(ui->actionFollow_Mode->isChecked());
2940
2941 ui->orderList->onFollowModeChanged();
2942 ui->patternEditor->onFollowModeChanged();
2943 }
2944
on_actionGroove_Settings_triggered()2945 void MainWindow::on_actionGroove_Settings_triggered()
2946 {
2947 std::vector<std::vector<int>> seqs(bt_->getGrooveCount());
2948 std::generate(seqs.begin(), seqs.end(), [&, i = 0]() mutable { return bt_->getGroove(i++); });
2949
2950 GrooveSettingsDialog diag;
2951 diag.setGrooveSquences(seqs);
2952 if (diag.exec() == QDialog::Accepted) {
2953 bt_->stopPlaySong();
2954 lockWidgets(false);
2955 bt_->setGrooves(diag.getGrooveSequences());
2956 ui->grooveSpinBox->setMaximum(static_cast<int>(bt_->getGrooveCount()) - 1);
2957 setModifiedTrue();
2958 }
2959 }
2960
on_actionConfiguration_triggered()2961 void MainWindow::on_actionConfiguration_triggered()
2962 {
2963 ConfigurationDialog diag(config_.lock(), palette_, stream_);
2964 QObject::connect(&diag, &ConfigurationDialog::applyPressed, this, &MainWindow::changeConfiguration);
2965
2966 if (diag.exec() == QDialog::Accepted) {
2967 bt_->stopPlaySong();
2968 changeConfiguration();
2969 ConfigurationHandler::saveConfiguration(config_.lock());
2970 ColorPaletteHandler::savePalette(palette_.get());
2971 lockWidgets(false);
2972 }
2973 }
2974
on_actionExpand_triggered()2975 void MainWindow::on_actionExpand_triggered()
2976 {
2977 ui->patternEditor->onExpandPressed();
2978 }
2979
on_actionShrink_triggered()2980 void MainWindow::on_actionShrink_triggered()
2981 {
2982 ui->patternEditor->onShrinkPressed();
2983 }
2984
on_actionDuplicate_Order_triggered()2985 void MainWindow::on_actionDuplicate_Order_triggered()
2986 {
2987 ui->orderList->onDuplicatePressed();
2988 }
2989
on_actionMove_Order_Up_triggered()2990 void MainWindow::on_actionMove_Order_Up_triggered()
2991 {
2992 ui->orderList->onMoveOrderPressed(true);
2993 }
2994
on_actionMove_Order_Down_triggered()2995 void MainWindow::on_actionMove_Order_Down_triggered()
2996 {
2997 ui->orderList->onMoveOrderPressed(false);
2998 }
2999
on_actionClone_Patterns_triggered()3000 void MainWindow::on_actionClone_Patterns_triggered()
3001 {
3002 ui->orderList->onClonePatternsPressed();
3003 }
3004
on_actionClone_Order_triggered()3005 void MainWindow::on_actionClone_Order_triggered()
3006 {
3007 ui->orderList->onCloneOrderPressed();
3008 }
3009
on_actionNew_triggered()3010 void MainWindow::on_actionNew_triggered()
3011 {
3012 if (isWindowModified()) {
3013 QString modTitle = utf8ToQString(bt_->getModuleTitle());
3014 if (modTitle.isEmpty()) modTitle = tr("Untitled");
3015 QMessageBox dialog(QMessageBox::Warning,
3016 "BambooTracker",
3017 tr("Save changes to %1?").arg(modTitle),
3018 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
3019 switch (dialog.exec()) {
3020 case QMessageBox::Yes:
3021 if (!on_actionSave_triggered()) return;
3022 break;
3023 case QMessageBox::No:
3024 break;
3025 case QMessageBox::Cancel:
3026 return;
3027 default:
3028 break;
3029 }
3030 }
3031
3032 bt_->stopPlaySong();
3033 lockWidgets(false);
3034 freezeViews();
3035 if (!timer_) stream_->stop();
3036 bt_->makeNewModule();
3037 loadModule();
3038 setInitialSelectedInstrument();
3039 isModifiedForNotCommand_ = false;
3040 setWindowModified(false);
3041 if (!timer_) stream_->start();
3042 assignADPCMSamples();
3043 }
3044
on_actionComments_triggered()3045 void MainWindow::on_actionComments_triggered()
3046 {
3047 if (commentDiag_) {
3048 if (commentDiag_->isVisible()) commentDiag_->activateWindow();
3049 else commentDiag_->show();
3050 }
3051 else {
3052 commentDiag_ = std::make_unique<CommentEditDialog>(utf8ToQString(bt_->getModuleComment()));
3053 commentDiag_->show();
3054 QObject::connect(commentDiag_.get(), &CommentEditDialog::commentChanged,
3055 this, [&](const QString text) {
3056 bt_->setModuleComment(text.toUtf8().toStdString());
3057 setModifiedTrue();
3058 });
3059 }
3060 }
3061
on_actionSave_triggered()3062 bool MainWindow::on_actionSave_triggered()
3063 {
3064 auto path = QString::fromStdString(bt_->getModulePath());
3065 if (!path.isEmpty() && QFileInfo::exists(path) && QFileInfo(path).isFile()) {
3066 if (!backupModule(path)) return false;
3067
3068 try {
3069 BinaryContainer container;
3070 bt_->saveModule(container);
3071
3072 QFile fp(path);
3073 if (!fp.open(QIODevice::WriteOnly)) {
3074 FileIOErrorMessageBox::openError(path, false, FileIO::FileType::Mod, this);
3075 return false;
3076 }
3077 fp.write(container.getPointer(), container.size());
3078 fp.close();
3079
3080 isModifiedForNotCommand_ = false;
3081 isSavedModBefore_ = true;
3082 setWindowModified(false);
3083 setWindowTitle();
3084 return true;
3085 }
3086 catch (FileIOError& e) {
3087 FileIOErrorMessageBox(path, false, e, this).exec();
3088 return false;
3089 }
3090 catch (std::exception& e) {
3091 FileIOErrorMessageBox(path, false, FileIO::FileType::Mod, QString(e.what()), this).exec();
3092 return false;
3093 }
3094 }
3095 else {
3096 return on_actionSave_As_triggered();
3097 }
3098 }
3099
on_actionSave_As_triggered()3100 bool MainWindow::on_actionSave_As_triggered()
3101 {
3102 QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3103 QString file = QFileDialog::getSaveFileName(
3104 this, tr("Save module"),
3105 QString("%1/%2.btm").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()),
3106 tr("BambooTracker module (*.btm)"));
3107 if (file.isNull()) return false;
3108 if (!file.endsWith(".btm")) file += ".btm"; // For linux
3109
3110 if (QFile::exists(file)) { // Backup if the module already exists
3111 if (!backupModule(file)) return false;
3112 }
3113
3114 bt_->setModulePath(file.toStdString());
3115 try {
3116 BinaryContainer container;
3117 bt_->saveModule(container);
3118
3119 QFile fp(file);
3120 if (!fp.open(QIODevice::WriteOnly)) {
3121 FileIOErrorMessageBox::openError(file, false, FileIO::FileType::Mod, this);
3122 return false;
3123 }
3124 fp.write(container.getPointer(), container.size());
3125 fp.close();
3126
3127 isModifiedForNotCommand_ = false;
3128 isSavedModBefore_ = true;
3129 setWindowModified(false);
3130 setWindowTitle();
3131 config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
3132 changeFileHistory(file);
3133 return true;
3134 }
3135 catch (FileIOError& e) {
3136 FileIOErrorMessageBox(file, false, e, this).exec();
3137 return false;
3138 }
3139 catch (std::exception& e) {
3140 FileIOErrorMessageBox(file, false, FileIO::FileType::Mod, QString(e.what()), this).exec();
3141 return false;
3142 }
3143 }
3144
on_actionOpen_triggered()3145 void MainWindow::on_actionOpen_triggered()
3146 {
3147 if (isWindowModified()) {
3148 QString modTitle = utf8ToQString(bt_->getModuleTitle());
3149 if (modTitle.isEmpty()) modTitle = tr("Untitled");
3150 QMessageBox dialog(QMessageBox::Warning,
3151 "BambooTracker",
3152 tr("Save changes to %1?").arg(modTitle),
3153 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
3154 switch (dialog.exec()) {
3155 case QMessageBox::Yes:
3156 if (!on_actionSave_triggered()) return;
3157 break;
3158 case QMessageBox::No:
3159 break;
3160 case QMessageBox::Cancel:
3161 return;
3162 default:
3163 break;
3164 }
3165 }
3166
3167 QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3168 QString file = QFileDialog::getOpenFileName(this, tr("Open module"), (dir.isEmpty() ? "./" : dir),
3169 tr("BambooTracker module (*.btm)"));
3170 if (file.isNull()) return;
3171
3172 bt_->stopPlaySong();
3173 lockWidgets(false);
3174
3175 openModule(file);
3176 }
3177
on_actionLoad_From_File_triggered()3178 void MainWindow::on_actionLoad_From_File_triggered()
3179 {
3180 loadInstrument();
3181 }
3182
on_actionSave_To_File_triggered()3183 void MainWindow::on_actionSave_To_File_triggered()
3184 {
3185 saveInstrument();
3186 }
3187
on_actionImport_From_Bank_File_triggered()3188 void MainWindow::on_actionImport_From_Bank_File_triggered()
3189 {
3190 importInstrumentsFromBank();
3191 }
3192
on_actionInterpolate_triggered()3193 void MainWindow::on_actionInterpolate_triggered()
3194 {
3195 ui->patternEditor->onInterpolatePressed();
3196 }
3197
on_actionReverse_triggered()3198 void MainWindow::on_actionReverse_triggered()
3199 {
3200 ui->patternEditor->onReversePressed();
3201 }
3202
on_actionReplace_Instrument_triggered()3203 void MainWindow::on_actionReplace_Instrument_triggered()
3204 {
3205 ui->patternEditor->onReplaceInstrumentPressed();
3206 }
3207
on_actionRow_triggered()3208 void MainWindow::on_actionRow_triggered()
3209 {
3210 if (isEditedPattern_) ui->patternEditor->onSelectPressed(2);
3211 else if (isEditedOrder_) ui->orderList->onSelectPressed(2);
3212 }
3213
on_actionColumn_triggered()3214 void MainWindow::on_actionColumn_triggered()
3215 {
3216 if (isEditedPattern_) ui->patternEditor->onSelectPressed(3);
3217 else if (isEditedOrder_) ui->orderList->onSelectPressed(3);
3218 }
3219
on_actionPattern_triggered()3220 void MainWindow::on_actionPattern_triggered()
3221 {
3222 if (isEditedPattern_) ui->patternEditor->onSelectPressed(4);
3223 else if (isEditedOrder_) ui->orderList->onSelectPressed(4);
3224 }
3225
on_actionOrder_triggered()3226 void MainWindow::on_actionOrder_triggered()
3227 {
3228 if (isEditedPattern_) ui->patternEditor->onSelectPressed(5);
3229 else if (isEditedOrder_) ui->orderList->onSelectPressed(5);
3230 }
3231
on_actionRemove_Unused_Instruments_triggered()3232 void MainWindow::on_actionRemove_Unused_Instruments_triggered()
3233 {
3234 if (showUndoResetWarningDialog(tr("Do you want to remove all unused instruments?"))) {
3235 bt_->stopPlaySong();
3236 lockWidgets(false);
3237
3238 auto list = ui->instrumentList;
3239 for (auto& n : bt_->getUnusedInstrumentIndices()) {
3240 for (int i = 0; i < list->count(); ++i) {
3241 if (list->item(i)->data(Qt::UserRole).toInt() == n) {
3242 removeInstrument(i);
3243 }
3244 }
3245 }
3246 bt_->clearUnusedInstrumentProperties();
3247 bt_->clearCommandHistory();
3248 comStack_->clear();
3249 setModifiedTrue();
3250 }
3251 }
3252
on_actionRemove_Unused_Patterns_triggered()3253 void MainWindow::on_actionRemove_Unused_Patterns_triggered()
3254 {
3255 if (showUndoResetWarningDialog(tr("Do you want to remove all unused patterns?"))) {
3256 bt_->stopPlaySong();
3257 lockWidgets(false);
3258
3259 bt_->clearUnusedPatterns();
3260 bt_->clearCommandHistory();
3261 comStack_->clear();
3262 setModifiedTrue();
3263 }
3264 }
3265
on_actionWAV_triggered()3266 void MainWindow::on_actionWAV_triggered()
3267 {
3268 WaveExportSettingsDialog diag;
3269 if (diag.exec() != QDialog::Accepted) return;
3270
3271 QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3272 QString path = QFileDialog::getSaveFileName(
3273 this, tr("Export to WAV"),
3274 QString("%1/%2.wav").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()),
3275 tr("WAV signed 16-bit PCM (*.wav)"));
3276 if (path.isNull()) return;
3277 if (!path.endsWith(".wav")) path += ".wav"; // For linux
3278
3279 int max = static_cast<int>(bt_->getAllStepCount(
3280 bt_->getCurrentSongNumber(), static_cast<size_t>(diag.getLoopCount()))) + 3;
3281 QProgressDialog progress(tr("Export to WAV"), tr("Cancel"), 0, max);
3282 progress.setValue(0);
3283 progress.setWindowFlags(progress.windowFlags()
3284 & ~Qt::WindowContextHelpButtonHint
3285 & ~Qt::WindowCloseButtonHint);
3286 progress.show();
3287
3288 bt_->stopPlaySong();
3289 lockWidgets(false);
3290 stream_->stop();
3291
3292 try {
3293 WavContainer container(0, static_cast<uint32_t>(diag.getSampleRate()));
3294 auto bar = [&progress]() -> bool {
3295 QApplication::processEvents();
3296 progress.setValue(progress.value() + 1);
3297 return progress.wasCanceled();
3298 };
3299
3300 bool res = bt_->exportToWav(container, diag.getLoopCount(), bar);
3301 if (res) {
3302 QFile fp(path);
3303 if (!fp.open(QIODevice::WriteOnly)) {
3304 FileIOErrorMessageBox::openError(path, false, FileIO::FileType::WAV, this);
3305 }
3306 else {
3307 BinaryContainer bc = container.createWavBinary();
3308 fp.write(bc.getPointer(), bc.size());
3309 fp.close();
3310 bar();
3311
3312 config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString());
3313 }
3314 }
3315 }
3316 catch (FileIOError& e) {
3317 FileIOErrorMessageBox(path, false, e, this).exec();
3318 }
3319 catch (std::exception& e) {
3320 FileIOErrorMessageBox(path, false, FileIO::FileType::WAV, QString(e.what()), this).exec();
3321 }
3322
3323 stream_->start();
3324 }
3325
on_actionVGM_triggered()3326 void MainWindow::on_actionVGM_triggered()
3327 {
3328 VgmExportSettingsDialog diag;
3329 if (diag.exec() != QDialog::Accepted) return;
3330 GD3Tag tag = diag.getGD3Tag();
3331
3332 QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3333 QString path = QFileDialog::getSaveFileName(
3334 this, tr("Export to VGM"),
3335 QString("%1/%2.vgm").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()),
3336 tr("VGM file (*.vgm)"));
3337 if (path.isNull()) return;
3338 if (!path.endsWith(".vgm")) path += ".vgm"; // For linux
3339
3340 int max = static_cast<int>(bt_->getAllStepCount(bt_->getCurrentSongNumber(), 1)) + 3;
3341 QProgressDialog progress(tr("Export to VGM"), tr("Cancel"), 0, max);
3342 progress.setValue(0);
3343 progress.setWindowFlags(progress.windowFlags()
3344 & ~Qt::WindowContextHelpButtonHint
3345 & ~Qt::WindowCloseButtonHint);
3346 progress.show();
3347
3348 bt_->stopPlaySong();
3349 lockWidgets(false);
3350 stream_->stop();
3351
3352 try {
3353 BinaryContainer container;
3354 auto bar = [&progress]() -> bool {
3355 QApplication::processEvents();
3356 progress.setValue(progress.value() + 1);
3357 return progress.wasCanceled();
3358 };
3359
3360 bool res = bt_->exportToVgm(container, diag.getExportTarget(), diag.enabledGD3(), tag, bar);
3361 if (res) {
3362 QFile fp(path);
3363 if (!fp.open(QIODevice::WriteOnly)) {
3364 FileIOErrorMessageBox::openError(path, false, FileIO::FileType::VGM, this);
3365 }
3366 else {
3367 fp.write(container.getPointer(), container.size());
3368 fp.close();
3369 bar();
3370
3371 config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString());
3372 }
3373 }
3374 }
3375 catch (FileIOError& e) {
3376 FileIOErrorMessageBox(path, false, e, this).exec();
3377 }
3378 catch (std::exception& e) {
3379 FileIOErrorMessageBox(path, false, FileIO::FileType::VGM, QString(e.what()), this).exec();
3380 }
3381
3382 stream_->start();
3383 }
3384
on_actionS98_triggered()3385 void MainWindow::on_actionS98_triggered()
3386 {
3387 S98ExportSettingsDialog diag;
3388 if (diag.exec() != QDialog::Accepted) return;
3389 S98Tag tag = diag.getS98Tag();
3390
3391 QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3392 QString path = QFileDialog::getSaveFileName(
3393 this, tr("Export to S98"),
3394 QString("%1/%2.s98").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()),
3395 tr("S98 file (*.s98)"));
3396 if (path.isNull()) return;
3397 if (!path.endsWith(".s98")) path += ".s98"; // For linux
3398
3399 int max = static_cast<int>(bt_->getAllStepCount(bt_->getCurrentSongNumber(), 1)) + 3;
3400 QProgressDialog progress(tr("Export to S98"), tr("Cancel"), 0, max);
3401 progress.setValue(0);
3402 progress.setWindowFlags(progress.windowFlags()
3403 & ~Qt::WindowContextHelpButtonHint
3404 & ~Qt::WindowCloseButtonHint);
3405 progress.show();
3406
3407 bt_->stopPlaySong();
3408 lockWidgets(false);
3409 stream_->stop();
3410
3411 try {
3412 BinaryContainer container;
3413 auto bar = [&progress]() -> bool {
3414 QApplication::processEvents();
3415 progress.setValue(progress.value() + 1);
3416 return progress.wasCanceled();
3417 };
3418
3419 bool res = bt_->exportToS98(container, diag.getExportTarget(), diag.enabledTag(),
3420 tag, diag.getResolution(), bar);
3421 if (res) {
3422 QFile fp(path);
3423 if (!fp.open(QIODevice::WriteOnly)) {
3424 FileIOErrorMessageBox::openError(path, false, FileIO::FileType::S98, this);
3425 }
3426 else {
3427 fp.write(container.getPointer(), container.size());
3428 fp.close();
3429 bar();
3430
3431 config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString());
3432 }
3433 }
3434 }
3435 catch (FileIOError& e) {
3436 FileIOErrorMessageBox(path, false, e, this).exec();
3437 }
3438 catch (std::exception& e) {
3439 FileIOErrorMessageBox(path, false, FileIO::FileType::S98, QString(e.what()), this).exec();
3440 }
3441
3442 stream_->start();
3443 }
3444
on_actionMix_triggered()3445 void MainWindow::on_actionMix_triggered()
3446 {
3447 if (isEditedPattern_) ui->patternEditor->onPasteMixPressed();
3448 }
3449
on_actionOverwrite_triggered()3450 void MainWindow::on_actionOverwrite_triggered()
3451 {
3452 if (isEditedPattern_) ui->patternEditor->onPasteOverwritePressed();
3453 }
3454
onNewTickSignaledRealChip()3455 void MainWindow::onNewTickSignaledRealChip()
3456 {
3457 onNewTickSignaled(bt_->streamCountUp());
3458 }
3459
onNewTickSignaled(int state)3460 void MainWindow::onNewTickSignaled(int state)
3461 {
3462 if (!state) { // New step
3463 int order = bt_->getPlayingOrderNumber();
3464 if (order > -1) { // Playing
3465 if (isVisible() && !isMinimized()) {
3466 ui->orderList->updatePositionByOrderUpdate(firstViewUpdateRequest_);
3467 ui->patternEditor->updatePositionByStepUpdate(firstViewUpdateRequest_);
3468 firstViewUpdateRequest_ = false;
3469 }
3470
3471 int width, base;
3472 if (config_.lock()->getShowRowNumberInHex()) {
3473 width = 2;
3474 base = 16;
3475 }
3476 else {
3477 width = 3;
3478 base = 10;
3479 }
3480 statusPlayPos_->setText(
3481 QString("%1/%2")
3482 .arg(order, width, base, QChar('0'))
3483 .arg(bt_->getPlayingStepNumber(), width, base, QChar('0')).toUpper());
3484 }
3485 }
3486 else if (state == -1) {
3487 if (hasLockedWigets_) lockWidgets(false);
3488 }
3489
3490 // Update BPM status
3491 if (bt_->getStreamGrooveEnabled()) {
3492 statusBpm_->setText(tr("Groove"));
3493 }
3494 else {
3495 // BPM = tempo * 6 / speed * 4 / 1st highlight
3496 double bpm = 24.0 * bt_->getStreamTempo() / bt_->getStreamSpeed() / highlight1_->value();
3497 statusBpm_->setText(QString::number(bpm, 'f', 2) + QString(" BPM"));
3498 }
3499 }
3500
on_actionClear_triggered()3501 void MainWindow::on_actionClear_triggered()
3502 {
3503 fileHistory_->clearHistory();
3504 for (int i = ui->menu_Recent_Files->actions().count() - 1; 1 < i; --i)
3505 ui->menu_Recent_Files->removeAction(ui->menu_Recent_Files->actions().at(i));
3506 }
3507
on_keyRepeatCheckBox_stateChanged(int arg1)3508 void MainWindow::on_keyRepeatCheckBox_stateChanged(int arg1)
3509 {
3510 config_.lock()->setKeyRepetition(arg1 == Qt::Checked);
3511 }
3512
updateVisuals()3513 void MainWindow::updateVisuals()
3514 {
3515 int16_t wave[2 * OPNAController::OUTPUT_HISTORY_SIZE];
3516 bt_->getOutputHistory(wave);
3517
3518 ui->waveVisual->setStereoSamples(wave, OPNAController::OUTPUT_HISTORY_SIZE);
3519 }
3520
on_action_Effect_List_triggered()3521 void MainWindow::on_action_Effect_List_triggered()
3522 {
3523 if (effListDiag_) {
3524 if (effListDiag_->isVisible()) effListDiag_->activateWindow();
3525 else effListDiag_->show();
3526 }
3527 else {
3528 effListDiag_ = std::make_unique<EffectListDialog>();
3529 effListDiag_->show();
3530 }
3531 }
3532
on_actionShortcuts_triggered()3533 void MainWindow::on_actionShortcuts_triggered()
3534 {
3535 if (shortcutsDiag_) {
3536 if (shortcutsDiag_->isVisible()) shortcutsDiag_->activateWindow();
3537 else shortcutsDiag_->show();
3538 }
3539 else {
3540 shortcutsDiag_ = std::make_unique<KeyboardShortcutListDialog>();
3541 shortcutsDiag_->show();
3542 }
3543 }
3544
on_actionExport_To_Bank_File_triggered()3545 void MainWindow::on_actionExport_To_Bank_File_triggered()
3546 {
3547 exportInstrumentsToBank();
3548 }
3549
on_actionRemove_Duplicate_Instruments_triggered()3550 void MainWindow::on_actionRemove_Duplicate_Instruments_triggered()
3551 {
3552 if (showUndoResetWarningDialog(tr("Do you want to remove all duplicate instruments?"))) {
3553 bt_->stopPlaySong();
3554 lockWidgets(false);
3555
3556 std::vector<std::vector<int>> duplicates = bt_->checkDuplicateInstruments();
3557 auto list = ui->instrumentList;
3558 for (auto& group : duplicates) {
3559 for (size_t i = 1; i < group.size(); ++i) {
3560 for (int j = 0; j < list->count(); ++j) {
3561 if (list->item(j)->data(Qt::UserRole).toInt() == group[i])
3562 removeInstrument(j);
3563 }
3564 }
3565 }
3566 bt_->replaceDuplicateInstrumentsInPatterns(duplicates);
3567 bt_->clearUnusedInstrumentProperties();
3568 bt_->clearCommandHistory();
3569 comStack_->clear();
3570 ui->patternEditor->onDuplicateInstrumentsRemoved();
3571 setModifiedTrue();
3572 }
3573 }
3574
on_actionRename_Instrument_triggered()3575 void MainWindow::on_actionRename_Instrument_triggered()
3576 {
3577 renameInstrument();
3578 }
3579
on_action_Bookmark_Manager_triggered()3580 void MainWindow::on_action_Bookmark_Manager_triggered()
3581 {
3582 if (bmManForm_->isVisible()) bmManForm_->activateWindow();
3583 else bmManForm_->show();
3584 }
3585
on_actionFine_Decrease_Values_triggered()3586 void MainWindow::on_actionFine_Decrease_Values_triggered()
3587 {
3588 if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(false, false);
3589 }
3590
on_actionFine_Increase_Values_triggered()3591 void MainWindow::on_actionFine_Increase_Values_triggered()
3592 {
3593 if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(false, true);
3594 }
3595
on_actionCoarse_D_ecrease_Values_triggered()3596 void MainWindow::on_actionCoarse_D_ecrease_Values_triggered()
3597 {
3598 if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(true, false);
3599 }
3600
on_actionCoarse_I_ncrease_Values_triggered()3601 void MainWindow::on_actionCoarse_I_ncrease_Values_triggered()
3602 {
3603 if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(true, true);
3604 }
3605
on_action_Toggle_Bookmark_triggered()3606 void MainWindow::on_action_Toggle_Bookmark_triggered()
3607 {
3608 bmManForm_->onBookmarkToggleRequested(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
3609 }
3610
on_action_Next_Bookmark_triggered()3611 void MainWindow::on_action_Next_Bookmark_triggered()
3612 {
3613 bmManForm_->onBookmarkJumpRequested(true, bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
3614 }
3615
on_action_Previous_Bookmark_triggered()3616 void MainWindow::on_action_Previous_Bookmark_triggered()
3617 {
3618 bmManForm_->onBookmarkJumpRequested(false, bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
3619 }
3620
on_action_Instrument_Mask_triggered()3621 void MainWindow::on_action_Instrument_Mask_triggered()
3622 {
3623 config_.lock()->setInstrumentMask(ui->action_Instrument_Mask->isChecked());
3624 }
3625
on_action_Volume_Mask_triggered()3626 void MainWindow::on_action_Volume_Mask_triggered()
3627 {
3628 config_.lock()->setVolumeMask(ui->action_Volume_Mask->isChecked());
3629 }
3630
on_actionSet_Ro_w_Marker_triggered()3631 void MainWindow::on_actionSet_Ro_w_Marker_triggered()
3632 {
3633 bt_->setMarker(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
3634 ui->patternEditor->changeMarker();
3635 }
3636
on_actionPlay_From_Marker_triggered()3637 void MainWindow::on_actionPlay_From_Marker_triggered()
3638 {
3639 startPlayFromMarker();
3640 }
3641
on_action_Go_To_triggered()3642 void MainWindow::on_action_Go_To_triggered()
3643 {
3644 GoToDialog diag(bt_);
3645 if (diag.exec() == QDialog::Accepted) {
3646 if (!bt_->isPlaySong() || !bt_->isFollowPlay()) {
3647 bt_->setCurrentOrderNumber(diag.getOrder());
3648 bt_->setCurrentStepNumber(diag.getStep());
3649 bt_->setCurrentTrack(diag.getTrack());
3650 ui->orderList->updatePositionByPositionJump(true);
3651 ui->patternEditor->updatepositionByPositionJump(true);
3652 }
3653 }
3654 }
3655
on_actionRemove_Unused_ADPCM_Samples_triggered()3656 void MainWindow::on_actionRemove_Unused_ADPCM_Samples_triggered()
3657 {
3658 if (showUndoResetWarningDialog(tr("Do you want to remove all unused ADPCM samples?"))) {
3659 bt_->stopPlaySong();
3660 lockWidgets(false);
3661
3662 bt_->clearUnusedADPCMSamples();
3663 assignADPCMSamples();
3664 bt_->clearCommandHistory();
3665 comStack_->clear();
3666 setModifiedTrue();
3667 }
3668 }
3669
on_action_Status_Bar_triggered()3670 void MainWindow::on_action_Status_Bar_triggered()
3671 {
3672 ui->statusBar->setVisible(ui->action_Status_Bar->isChecked());
3673 }
3674
on_action_Toolbar_triggered()3675 void MainWindow::on_action_Toolbar_triggered()
3676 {
3677 bool visible = ui->action_Toolbar->isChecked();
3678 ui->mainToolBar->setVisible(visible);
3679 ui->subToolBar->setVisible(visible);
3680 }
3681
on_actionNew_Drumki_t_triggered()3682 void MainWindow::on_actionNew_Drumki_t_triggered()
3683 {
3684 addDrumkit();
3685 }
3686
on_action_Wave_View_triggered(bool checked)3687 void MainWindow::on_action_Wave_View_triggered(bool checked)
3688 {
3689 config_.lock()->setVisibleWaveView(checked);
3690 ui->waveVisual->setVisible(checked);
3691 if (checked)
3692 visualTimer_->start(static_cast<int>(std::round(1000. / config_.lock()->getWaveViewFrameRate())));
3693 else
3694 visualTimer_->stop();
3695 }
3696
on_action_Transpose_Song_triggered()3697 void MainWindow::on_action_Transpose_Song_triggered()
3698 {
3699 TransposeSongDialog diag;
3700 if (diag.exec() == QDialog::Accepted) {
3701 if (showUndoResetWarningDialog(tr("Do you want to transpose a song?"))) {
3702 bt_->stopPlaySong();
3703 lockWidgets(false);
3704
3705 bt_->transposeSong(bt_->getCurrentSongNumber(),
3706 diag.getTransposeSeminotes(), diag.getExcludeInstruments());
3707 ui->patternEditor->onPatternDataGlobalChanged();
3708
3709 bt_->clearCommandHistory();
3710 comStack_->clear();
3711 setModifiedTrue();
3712 }
3713 }
3714 }
3715
on_action_Swap_Tracks_triggered()3716 void MainWindow::on_action_Swap_Tracks_triggered()
3717 {
3718 SwapTracksDialog diag(bt_->getSongStyle(bt_->getCurrentSongNumber()));
3719 if (diag.exec() == QDialog::Accepted) {
3720 int track1 = diag.getTrack1();
3721 int track2 = diag.getTrack2();
3722 if ((track1 != track2) && showUndoResetWarningDialog(tr("Do you want to swap tracks?"))) {
3723 bt_->stopPlaySong();
3724 lockWidgets(false);
3725
3726 bt_->swapTracks(bt_->getCurrentSongNumber(), track1, track2);
3727 ui->orderList->onOrderDataGlobalChanged();
3728 ui->patternEditor->onPatternDataGlobalChanged();
3729
3730 bt_->clearCommandHistory();
3731 comStack_->clear();
3732 setModifiedTrue();
3733 }
3734 }
3735 }
3736
on_action_Insert_triggered()3737 void MainWindow::on_action_Insert_triggered()
3738 {
3739 if (isEditedPattern_) ui->patternEditor->onPasteInsertPressed();
3740 }
3741
on_action_Hide_Tracks_triggered()3742 void MainWindow::on_action_Hide_Tracks_triggered()
3743 {
3744 HideTracksDialog diag(bt_->getSongStyle(bt_->getCurrentSongNumber()),
3745 ui->patternEditor->getVisibleTracks());
3746 if (diag.exec() == QDialog::Accepted) {
3747 setTrackVisibility(diag.getVisibleTracks());
3748 }
3749 }
3750
on_action_Estimate_Song_Length_triggered()3751 void MainWindow::on_action_Estimate_Song_Length_triggered()
3752 {
3753 double time = bt_->calculateSongLength(bt_->getCurrentSongNumber());
3754 int seconds = static_cast<int>(std::round(time));
3755 QMessageBox box;
3756 box.setIcon(QMessageBox::Information);
3757 box.setText(tr("Approximate song length: %1m%2s")
3758 .arg(seconds / 60).arg(seconds % 60, 2, 10, QChar('0')));
3759 box.exec();
3760 }
3761