1 /*
2  * Copyright (C) 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 "adpcm_sample_editor.hpp"
27 #include "ui_adpcm_sample_editor.h"
28 #include <cmath>
29 #include <limits>
30 #include <vector>
31 #include <algorithm>
32 #include <QMimeData>
33 #include <QFile>
34 #include <QIODevice>
35 #include <QFileInfo>
36 #include <QFileDialog>
37 #include <QStringList>
38 #include <QPainter>
39 #include <QRect>
40 #include <QRectF>
41 #include <QToolBar>
42 #include <QMenu>
43 #include <QToolButton>
44 #include <QWheelEvent>
45 #include <QHoverEvent>
46 #include "chips/codec/ymb_codec.hpp"
47 #include "gui/event_guard.hpp"
48 #include "gui/instrument_editor/sample_length_dialog.hpp"
49 #include "gui/instrument_editor/grid_settings_dialog.hpp"
50 #include "gui/file_io_error_message_box.hpp"
51 
ADPCMSampleEditor(QWidget * parent)52 ADPCMSampleEditor::ADPCMSampleEditor(QWidget *parent) :
53 	QWidget(parent),
54 	ui(new Ui::ADPCMSampleEditor),
55 	isIgnoreEvent_(false),
56 	zoom_(0),
57 	viewedSampLen_(2),
58 	gridIntr_(1),
59 	cursorSamp_(0, 0),
60 	prevPressedSamp_(-1, -1),
61 	addrStart_(0),
62 	addrStop_(0),
63 	sample_(2),
64 	drawMode_(DrawMode::Disabled)
65 {
66 	ui->setupUi(this);
67 
68 	auto rkfunc = [&](int dummy) {
69 		Q_UNUSED(dummy)
70 		if (!isIgnoreEvent_) {
71 			int rk = ui->rootKeySpinBox->value() * 12 + ui->rootKeyComboBox->currentIndex();
72 			bt_.lock()->setSampleADPCMRootKeyNumber(ui->sampleNumSpinBox->value(), rk);
73 			emit sampleParameterChanged(ui->sampleNumSpinBox->value());
74 			emit modified();
75 		}
76 	};
77 	// Leave Before Qt5.7.0 style due to windows xp
78 	QObject::connect(ui->rootKeyComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, rkfunc);
79 	QObject::connect(ui->rootKeySpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, rkfunc);
80 
81 	ui->memoryWidget->installEventFilter(this);
82 	ui->sampleViewWidget->setAttribute(Qt::WA_Hover);
83 	ui->sampleViewWidget->installEventFilter(this);
84 
85 	auto tb = new QToolBar();
86 	tb->setIconSize(QSize(16, 16));
87 	tb->setMinimumHeight(28);
88 	tb->setMaximumHeight(28);
89 	tb->addActions({ ui->action_Clear, ui->action_Import, ui->action_Resize });
90 	tb->addSeparator();
91 	tb->addActions({ ui->actionZoom_In, ui->actionZoom_Out });
92 	auto gridMenu = new QMenu();
93 	gridMenu->addActions({ ui->action_Grid_View, ui->actionG_rid_Settings });
94 	auto gridButton = new QToolButton();
95 	gridButton->setPopupMode(QToolButton::MenuButtonPopup);
96 	gridButton->setMenu(gridMenu);
97 	gridButton->setDefaultAction(ui->action_Grid_View);
98 	tb->addWidget(gridButton);
99 	tb->addSeparator();
100 	auto editMenu = new QMenu();
101 	editMenu->addActions({ ui->action_Draw_Sample, ui->actionDirec_t_Draw });
102 	auto editButton = new QToolButton();
103 	editButton->setPopupMode(QToolButton::MenuButtonPopup);
104 	editButton->setMenu(editMenu);
105 	editButton->setDefaultAction(ui->action_Draw_Sample);
106 	QObject::connect(editButton, &QToolButton::triggered, editButton, &QToolButton::setDefaultAction);
107 	tb->addWidget(editButton);
108 	tb->addAction(ui->actionRe_verse);
109 	ui->verticalLayout_2->insertWidget(0, tb);
110 }
111 
~ADPCMSampleEditor()112 ADPCMSampleEditor::~ADPCMSampleEditor()
113 {
114 	delete ui;
115 }
116 
setCore(std::weak_ptr<BambooTracker> core)117 void ADPCMSampleEditor::setCore(std::weak_ptr<BambooTracker> core)
118 {
119 	bt_ = core;
120 }
121 
setConfiguration(std::weak_ptr<Configuration> config)122 void ADPCMSampleEditor::setConfiguration(std::weak_ptr<Configuration> config)
123 {
124 	config_ = config;
125 }
126 
setColorPalette(std::shared_ptr<ColorPalette> palette)127 void ADPCMSampleEditor::setColorPalette(std::shared_ptr<ColorPalette> palette)
128 {
129 	palette_ = palette;
130 }
131 
getSampleNumber() const132 int ADPCMSampleEditor::getSampleNumber() const
133 {
134 	return ui->sampleNumSpinBox->value();
135 }
136 
dragEnterEvent(QDragEnterEvent * event)137 void ADPCMSampleEditor::dragEnterEvent(QDragEnterEvent* event)
138 {
139 	const QMimeData* mime = event->mimeData();
140 	if (mime->hasUrls() && mime->urls().length() == 1
141 			&& QFileInfo(mime->urls().first().toLocalFile()).suffix().toLower() == "wav") {
142 		event->acceptProposedAction();
143 	}
144 }
145 
eventFilter(QObject * obj,QEvent * ev)146 bool ADPCMSampleEditor::eventFilter(QObject* obj, QEvent* ev)
147 {
148 	if (obj == ui->memoryWidget) {
149 		switch (ev->type()) {
150 		case QEvent::Paint:
151 		{
152 			QPainter painter(ui->memoryWidget);
153 			painter.drawPixmap(ui->memoryWidget->rect(), memPixmap_);
154 			break;
155 		}
156 		case QEvent::Resize:
157 			memPixmap_ = QPixmap(ui->memoryWidget->size());
158 			updateSampleMemoryBar();
159 			break;
160 		default:
161 			break;
162 		}
163 		return false;
164 	}
165 
166 	if (obj == ui->sampleViewWidget) {
167 		switch (ev->type()) {
168 		case QEvent::Paint:
169 		{
170 			QPainter painter(ui->sampleViewWidget);
171 			painter.drawPixmap(ui->sampleViewWidget->rect(), sampViewPixmap_);
172 			break;
173 		}
174 		case QEvent::Resize:
175 			sampViewPixmap_ = QPixmap(ui->sampleViewWidget->size());
176 			updateSampleView();
177 			break;
178 		case QEvent::Wheel:
179 		{
180 			auto we = dynamic_cast<QWheelEvent*>(ev);
181 			int cnt = we->angleDelta().y() / 120;
182 			bool ctrl = we->modifiers().testFlag(Qt::ControlModifier);
183 			if (cnt > 0) {
184 				for (int i = 0; i < cnt; ++i) {
185 					if (ctrl) on_actionZoom_In_triggered();
186 					else ui->horizontalScrollBar->setValue(ui->horizontalScrollBar->value() - 1);
187 				}
188 			}
189 			else {
190 				for (int i = 0; cnt < i; --i) {
191 					if (ctrl) on_actionZoom_Out_triggered();
192 					else ui->horizontalScrollBar->setValue(ui->horizontalScrollBar->value() + 1);
193 				}
194 			}
195 			break;
196 		}
197 		case QEvent::HoverMove:
198 		{
199 			auto pos = dynamic_cast<QHoverEvent*>(ev)->pos();
200 			detectCursorSamplePosition(pos.x(), pos.y());
201 
202 			if (prevPressedSamp_.x() != -1) {	// Change sample
203 				const int px = prevPressedSamp_.x();
204 				const int py = prevPressedSamp_.y();
205 				const int cx = cursorSamp_.x();
206 				const int cy = cursorSamp_.y();
207 				if (px < cx) {
208 					const int dx = cx - px;
209 					const int dy = cy - py;
210 					for (int x = px + 1; x <= cx; ++x)
211 						sample_.at(x) = (x - px) * dy / dx + py;
212 				}
213 				else if (px == cx) {
214 					sample_.at(cx) = cy;
215 				}
216 				else {
217 					const int dx = px - cx;
218 					const int dy = py - cy;
219 					for (int x = cx; x < px; ++x)
220 						sample_.at(x) = (x - cx) * dy / dx + cy;
221 				}
222 				prevPressedSamp_ = cursorSamp_;
223 
224 				if (drawMode_ == DrawMode::Direct) {
225 					sendEditedSample();
226 				}
227 				else {
228 					updateSampleView();
229 					ui->sampleViewWidget->update();
230 				}
231 			}
232 			break;
233 		}
234 		case QEvent::MouseButtonPress:
235 		{
236 			if (drawMode_ == DrawMode::Disabled) break;
237 			sample_.at(cursorSamp_.x()) = cursorSamp_.y();
238 			prevPressedSamp_ = cursorSamp_;
239 
240 			if (drawMode_ == DrawMode::Direct) {
241 				sendEditedSample();
242 			}
243 			else {
244 				updateSampleView();
245 				ui->sampleViewWidget->update();
246 			}
247 			break;
248 		}
249 		case QEvent::MouseButtonRelease:
250 		{
251 			prevPressedSamp_.setX(-1);	// Clear
252 			break;
253 		}
254 		default:
255 			break;
256 		}
257 		return false;
258 	}
259 
260 	return false;
261 }
262 
dropEvent(QDropEvent * event)263 void ADPCMSampleEditor::dropEvent(QDropEvent* event)
264 {
265 	importSampleFrom(reinterpret_cast<QDropEvent*>(event)->mimeData()->urls().first().toLocalFile());
266 }
267 
setInstrumentSampleParameters(int sampNum,bool repeatable,int rKeyNum,int rDeltaN,size_t start,size_t stop,std::vector<uint8_t> sample)268 void ADPCMSampleEditor::setInstrumentSampleParameters(int sampNum, bool repeatable, int rKeyNum,
269 													  int rDeltaN, size_t start, size_t stop,
270 													  std::vector<uint8_t> sample)
271 {
272 	Ui::EventGuard ev(isIgnoreEvent_);
273 
274 	ui->sampleNumSpinBox->setValue(sampNum);
275 
276 	updateUsersView();
277 
278 	ui->repeatCheckBox->setChecked(repeatable);
279 	ui->rootKeyComboBox->setCurrentIndex(rKeyNum % 12);
280 	ui->rootKeySpinBox->setValue(rKeyNum / 12);
281 	ui->rootRateSpinBox->setValue(rDeltaN);
282 
283 	addrStart_ = start;
284 	addrStop_ = stop;
285 	updateSampleMemoryBar();
286 	ui->memoryWidget->update();
287 
288 	if (!ui->action_Draw_Sample->isChecked()) {
289 		sample_.resize(sample.size() * 2);
290 		codec::ymb_decode(sample.data(), sample_.data(), static_cast<long>(sample_.size()));
291 
292 		// Slider settings
293 		for (int z = 0, len = sample_.size(); ; ++z) {
294 			int tmp = (len + 1) >> 1;
295 			if (tmp < 2) {
296 				zoom_ = z;
297 				viewedSampLen_ = len;
298 				ui->horizontalScrollBar->setMaximum(sample_.size() - len);
299 				break;
300 			}
301 			else if (z == zoom_) {
302 				viewedSampLen_ = len;
303 				ui->horizontalScrollBar->setMaximum(sample_.size() - len);
304 				break;
305 			}
306 			else {
307 				len = tmp;
308 			}
309 		}
310 		updateSampleView();
311 		ui->sampleViewWidget->update();
312 	}
313 
314 	ui->detailLabel->setText(updateDetailView());
315 }
316 
importSampleFrom(const QString file)317 void ADPCMSampleEditor::importSampleFrom(const QString file)
318 {
319 	std::unique_ptr<WavContainer> wav;
320 	try {
321 		QFile fp(file);
322 		if (!fp.open(QIODevice::ReadOnly)) {
323 			FileIOErrorMessageBox::openError(file, true, FileIO::FileType::WAV, this);
324 			return;
325 		}
326 		QByteArray array = fp.readAll();
327 		fp.close();
328 
329 		wav = std::make_unique<WavContainer>(BinaryContainer(std::vector<char>(array.begin(), array.end())));
330 	}
331 	catch (FileIOError& e) {
332 		FileIOErrorMessageBox(file, true, e, this).exec();
333 		return;
334 	}
335 	catch (std::exception& e) {
336 		FileIOErrorMessageBox(file, true, FileIO::FileType::WAV, QString(e.what()), this).exec();
337 		return;
338 	}
339 
340 	if (wav->getSampleRate() < 2000 || 55466 < wav->getSampleRate()) {
341 		FileIOErrorMessageBox(file, true, FileIO::FileType::WAV,
342 							  tr("Supported sample rate is 2kHz-55.5kHz, but the rate of selected sample is %1.")
343 							  .arg(wav->getSampleRate()), this).exec();
344 		return;
345 	}
346 
347 	if (wav->getChannelCount() != 1) {
348 		FileIOErrorMessageBox(file, true, FileIO::FileType::WAV,
349 							  tr("The selected sample is not mono channel."), this).exec();
350 		return;
351 	}
352 
353 	BinaryContainer bc = wav->getSample();
354 	size_t rawSize = bc.size() / 2;
355 	std::vector<int16_t> raw(rawSize);
356 	for (size_t i = 0; i < rawSize; ++i) {
357 		raw[i] = bc.readInt16(i * 2);
358 	}
359 	std::vector<uint8_t> adpcm((raw.size() + 1) / 2);
360 	codec::ymb_encode(raw.data(), adpcm.data(), static_cast<long>(raw.size()));
361 
362 	const int ROOT_KEY = 60;	//C5
363 
364 	bt_.lock()->storeSampleADPCMRawSample(ui->sampleNumSpinBox->value(), adpcm);
365 	ui->rootKeyComboBox->setCurrentIndex(ROOT_KEY % 12);
366 	ui->rootKeySpinBox->setValue(ROOT_KEY / 12);
367 	ui->rootRateSpinBox->setValue(calcADPCMDeltaN(wav->getSampleRate()));
368 
369 	emit modified();
370 	emit sampleAssignRequested();
371 	emit sampleParameterChanged(ui->sampleNumSpinBox->value());
372 }
373 
updateSampleMemoryBar()374 void ADPCMSampleEditor::updateSampleMemoryBar()
375 {
376 	QRect bar = memPixmap_.rect();
377 	if (!bar.isValid()) return;
378 
379 	QPainter painter(&memPixmap_);
380 	painter.fillRect(bar, palette_->instADPCMMemBackColor);
381 
382 	double maxSize = bt_.lock()->getADPCMStoredSize() >> 5;
383 	double limit = bt_.lock()->getADPCMLimit() >> 5;	// By 32 bytes
384 	QRectF allSamp(0, 0, std::max(1., bar.width() * (maxSize / limit)), rect().height());
385 	painter.fillRect(allSamp, palette_->instADPCMMemAllColor);
386 
387 	if (addrStart_ || addrStart_ != addrStop_) {
388 		QRectF curSamp(bar.width() * (addrStart_ / limit),
389 					   0, std::max(1., bar.width() * ((addrStop_ - addrStart_) / limit)), rect().height());
390 		painter.fillRect(curSamp, palette_->instADPCMMemCurColor);
391 	}
392 }
393 
updateSampleView()394 void ADPCMSampleEditor::updateSampleView()
395 {
396 	QRect rect = sampViewPixmap_.rect();
397 	if (!rect.isValid()) return;
398 
399 	QPainter painter(&sampViewPixmap_);
400 	painter.fillRect(rect, palette_->instADPCMSampViewBackColor);
401 
402 	painter.setPen(palette_->instADPCMSampViewCenterColor);
403 	const int maxX = rect.width();
404 	const int centerY = rect.height() >> 1;
405 	painter.drawLine(0, centerY, maxX, centerY);
406 
407 	QColor foreColor;
408 	switch (drawMode_) {
409 	case DrawMode::Disabled:	foreColor = palette_->instADPCMSampViewForeColor;		break;
410 	case DrawMode::Normal:		foreColor = palette_->instADPCMSampViewDrawColor;		break;
411 	case DrawMode::Direct:		foreColor = palette_->instADPCMSampViewDirectDrawColor;	break;
412 	}
413 	painter.setPen(foreColor);
414 	const int16_t maxY = std::numeric_limits<int16_t>::max();
415 	const size_t first = ui->horizontalScrollBar->value();
416 	const bool showGrid = ui->action_Grid_View->isChecked();
417 	if (maxX < viewedSampLen_) {
418 		int prevY = centerY;
419 		size_t g = first;
420 		for (int x = 0; x < maxX; ++x) {
421 			size_t i = (viewedSampLen_ - 1) * x / (maxX - 1);
422 			size_t p = i + first;
423 			int16_t sample = sample_.at(p);
424 			int y = centerY - (centerY * sample / maxY);
425 			if (showGrid && g <= p) {
426 				painter.setPen(palette_->instADPCMSampViewGridColor);
427 				painter.drawLine(x, 0, x, rect.height());
428 				g = (g / gridIntr_ + 1) * gridIntr_;
429 				painter.setPen(foreColor);
430 			}
431 			if (x) painter.drawLine(x - 1, prevY, x, y);
432 			prevY = y;
433 		}
434 	}
435 	else {
436 		QPoint prev, p;
437 		for (int i = 0; i < viewedSampLen_; ++i) {
438 			p.setX((maxX - 1) * i / (viewedSampLen_ - 1));
439 			int16_t sample = sample_[i + first];
440 			p.setY(centerY - (centerY * sample / maxY));
441 			if (showGrid && !(i % gridIntr_)) {
442 				painter.setPen(palette_->instADPCMSampViewGridColor);
443 				painter.drawLine(p.x(), 0, p.x(), rect.height());
444 				painter.setPen(foreColor);
445 			}
446 			if (p.x()) painter.drawLine(prev, p);
447 			prev = p;
448 		}
449 	}
450 }
451 
updateUsersView()452 void ADPCMSampleEditor::updateUsersView()
453 {
454 	std::vector<int> users = bt_.lock()->getSampleADPCMUsers(ui->sampleNumSpinBox->value());
455 	QStringList l;
456 	std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) {
457 		return QString("%1").arg(n, 2, 16, QChar('0')).toUpper();
458 	});
459 	ui->usersLineEdit->setText(l.join(","));
460 }
461 
detectCursorSamplePosition(int cx,int cy)462 void ADPCMSampleEditor::detectCursorSamplePosition(int cx, int cy)
463 {
464 	const QRect& rect = ui->sampleViewWidget->rect();
465 
466 	// Detect x
467 	const int w = rect.width();
468 	if (viewedSampLen_ < w) {
469 		const double segW = rect.width() / (viewedSampLen_ - 1.);
470 		double th = segW / 2.;
471 		for (int i = 0; i < viewedSampLen_; ++i, th += segW) {
472 			if (cx < th) {
473 				cursorSamp_.setX(ui->horizontalScrollBar->value() + i);
474 				break;
475 			}
476 		}
477 	}
478 	else {
479 		cursorSamp_.setX(
480 					ui->horizontalScrollBar->value() + (viewedSampLen_ - 1) * clamp(cx, 0, w - 1) / (w - 1));
481 	}
482 
483 	// Detect y
484 	const double centerY = rect.height() >> 1;
485 	int y = std::numeric_limits<int16_t>::max() * (centerY - cy) / centerY;
486 	cursorSamp_.setY(clamp(y, static_cast<int>(std::numeric_limits<int16_t>::min()),
487 						   static_cast<int>(std::numeric_limits<int16_t>::max())));
488 
489 	// Update position view
490 	ui->detailLabel->setText(updateDetailView());
491 }
492 
sendEditedSample()493 void ADPCMSampleEditor::sendEditedSample()
494 {
495 	std::vector<uint8_t> adpcm(sample_.size() >> 1);
496 	codec::ymb_encode(sample_.data(), adpcm.data(), static_cast<long>(sample_.size()));
497 	bt_.lock()->storeSampleADPCMRawSample(ui->sampleNumSpinBox->value(), std::move(adpcm));
498 	emit modified();
499 	emit sampleAssignRequested();
500 	emit sampleParameterChanged(ui->sampleNumSpinBox->value());
501 }
502 
onSampleNumberChanged()503 void ADPCMSampleEditor::onSampleNumberChanged()
504 {
505 	updateUsersView();
506 }
507 
onSampleMemoryUpdated(size_t start,size_t stop)508 void ADPCMSampleEditor::onSampleMemoryUpdated(size_t start, size_t stop)
509 {
510 	addrStart_ = start;
511 	addrStop_ = stop;
512 	updateSampleMemoryBar();
513 	ui->memoryWidget->update();
514 }
515 
on_sampleNumSpinBox_valueChanged(int arg1)516 void ADPCMSampleEditor::on_sampleNumSpinBox_valueChanged(int arg1)
517 {
518 	if (!isIgnoreEvent_) emit sampleNumberChanged(arg1);	// Direct connection
519 
520 	onSampleNumberChanged();
521 
522 	updateSampleMemoryBar();
523 	ui->memoryWidget->update();
524 
525 	updateSampleView();
526 	ui->sampleViewWidget->update();
527 }
528 
on_repeatCheckBox_toggled(bool checked)529 void ADPCMSampleEditor::on_repeatCheckBox_toggled(bool checked)
530 {
531 	if (!isIgnoreEvent_) {
532 		bt_.lock()->setSampleADPCMRepeatEnabled(ui->sampleNumSpinBox->value(), checked);
533 		emit sampleParameterChanged(ui->sampleNumSpinBox->value());
534 		emit modified();
535 	}
536 }
537 
on_rootRateSpinBox_valueChanged(int arg1)538 void ADPCMSampleEditor::on_rootRateSpinBox_valueChanged(int arg1)
539 {
540 	ui->rootRateSpinBox->setSuffix(
541 				QString(" (0x") + QString("%1 | ").arg(arg1, 3, 16, QChar('0')).toUpper()
542 				+ QString("%1Hz)").arg(QString::number(arg1 * 55500. * std::pow(2., -16.), 'f', 3)));
543 
544 	if (!isIgnoreEvent_) {
545 		bt_.lock()->setSampleADPCMRootDeltaN(ui->sampleNumSpinBox->value(), arg1);
546 		emit sampleParameterChanged(ui->sampleNumSpinBox->value());
547 		emit modified();
548 	}
549 }
550 
on_action_Resize_triggered()551 void ADPCMSampleEditor::on_action_Resize_triggered()
552 {
553 	SampleLengthDialog diag(sample_.size());
554 	if (diag.exec() == QDialog::Accepted) {
555 		sample_.resize(diag.getLength());
556 		sendEditedSample();
557 
558 		updateSampleView();
559 		ui->sampleViewWidget->update();
560 	}
561 }
562 
on_actionRe_verse_triggered()563 void ADPCMSampleEditor::on_actionRe_verse_triggered()
564 {
565 	std::reverse(sample_.begin(), sample_.end());
566 	sendEditedSample();
567 
568 	updateSampleView();
569 	ui->sampleViewWidget->update();
570 }
571 
on_actionZoom_In_triggered()572 void ADPCMSampleEditor::on_actionZoom_In_triggered()
573 {
574 	int len = (viewedSampLen_ + 1) >> 1;
575 	if (len > 1) {
576 		++zoom_;
577 		viewedSampLen_ = len;
578 		ui->horizontalScrollBar->setMaximum(sample_.size() - len);
579 		updateSampleView();
580 		ui->sampleViewWidget->update();
581 
582 		ui->detailLabel->setText(updateDetailView());
583 	}
584 }
585 
on_actionZoom_Out_triggered()586 void ADPCMSampleEditor::on_actionZoom_Out_triggered()
587 {
588 	if (zoom_) {
589 		--zoom_;
590 		viewedSampLen_ = sample_.size() >> zoom_;
591 		ui->horizontalScrollBar->setMaximum(sample_.size() - viewedSampLen_);
592 		updateSampleView();
593 		ui->sampleViewWidget->update();
594 
595 		ui->detailLabel->setText(updateDetailView());
596 	}
597 }
598 
on_horizontalScrollBar_valueChanged(int value)599 void ADPCMSampleEditor::on_horizontalScrollBar_valueChanged(int value)
600 {
601 	Q_UNUSED(value)
602 	updateSampleView();
603 	ui->sampleViewWidget->update();
604 }
605 
on_action_Import_triggered()606 void ADPCMSampleEditor::on_action_Import_triggered()
607 {
608 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
609 	QString file = QFileDialog::getOpenFileName(this, tr("Import sample"),
610 												(dir.isEmpty() ? "./" : dir), tr("WAV signed 16-bit PCM (*.wav)"));
611 	if (file.isNull()) return;
612 
613 	importSampleFrom(file);
614 }
615 
on_action_Clear_triggered()616 void ADPCMSampleEditor::on_action_Clear_triggered()
617 {
618 	bt_.lock()->clearSampleADPCMRawSample(ui->sampleNumSpinBox->value());
619 
620 	updateSampleMemoryBar();
621 	ui->memoryWidget->update();
622 
623 	emit modified();
624 
625 	if (config_.lock()->getWriteOnlyUsedSamples()) {
626 		emit sampleAssignRequested();
627 	}
628 	else {
629 		emit sampleMemoryChanged();
630 	}
631 
632 	emit sampleParameterChanged(ui->sampleNumSpinBox->value());
633 }
634 
on_action_Grid_View_triggered()635 void ADPCMSampleEditor::on_action_Grid_View_triggered()
636 {
637 	updateSampleView();
638 	ui->sampleViewWidget->update();
639 }
640 
on_actionG_rid_Settings_triggered()641 void ADPCMSampleEditor::on_actionG_rid_Settings_triggered()
642 {
643 	GridSettingsDialog diag(gridIntr_);
644 	if (diag.exec() == QDialog::Accepted) {
645 		gridIntr_ = diag.getInterval();
646 		updateSampleView();
647 		ui->sampleViewWidget->update();
648 	}
649 }
650 
on_action_Draw_Sample_triggered(bool checked)651 void ADPCMSampleEditor::on_action_Draw_Sample_triggered(bool checked)
652 {
653 	if (checked) {
654 		ui->actionDirec_t_Draw->setChecked(false);
655 		drawMode_ = DrawMode::Normal;
656 	}
657 	else {
658 		if (drawMode_ == DrawMode::Normal) drawMode_ = DrawMode::Disabled;
659 		sendEditedSample();	// Convert sample
660 	}
661 	updateSampleView();
662 }
663 
on_actionDirec_t_Draw_triggered(bool checked)664 void ADPCMSampleEditor::on_actionDirec_t_Draw_triggered(bool checked)
665 {
666 	if (checked) {
667 		ui->action_Draw_Sample->setChecked(false);
668 		if (drawMode_ == DrawMode::Normal) sendEditedSample();	// Convert sample
669 		drawMode_ = DrawMode::Direct;
670 	}
671 	else {
672 		if (drawMode_ == DrawMode::Direct) drawMode_ = DrawMode::Disabled;
673 	}
674 	updateSampleView();
675 }
676