AmigaConverterQT/mainwindow.cpp

470 lines
11 KiB
C++

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <iostream>
#include <QMessageBox>
#include <QFileDialog>
#include <QVector>
#include <QMetaType>
#include <QTemporaryFile>
#include <QProcess>
#include <QMediaPlayer>
#include <QAudioOutput>
#include <algorithm>
using namespace std;
// TODO
// add a bunch of actual error handling
// saving
// types
struct Sample {
QString path;
QString name;
bool normalise = true;
bool ready = false;
bool lowcutOn = false;
bool highcutOn = false;
int lowcutFrequency = 50;
int highcutFrequency = 18000;
QString rate = "C-2";
};
Q_DECLARE_METATYPE(Sample);
static QMap<QString,int> protrackerTable{
{"C-1",4144},
{"C#1",4390},
{"D-1",4655},
{"D#1",4926},
{"E-1",5231},
{"F-1",5542},
{"F#1",5872},
{"G-1",6223},
{"G#1",6593},
{"A-1",6982},
{"A#1",7389},
{"B-1",7830},
{"C-2",8287},
{"C#2",8779},
{"D-2",9309},
{"D#2",9852},
{"E-2",10463},
{"F-2",11084},
{"F#2",11745},
{"G-2",12445},
{"G#2",13185},
{"A-2",13964},
{"A#2",14779},
{"B-2",15694},
{"C-3",16574},
{"C#3",17559},
{"D-3",18668},
{"D#3",19705},
{"E-3",20864},
{"F-3",22168},
{"F#3",23489},
{"G-3",24803},
{"G#3",26273},
{"A-3",27928},
};
// variables
int sampleSelection;
QString OutputPath;
QList<Sample> samples;
// helpers
int logslider(int position) {
// position will be between 0 and 1000
int minp = 0;
int maxp = 100;
// The result should be between 50 and 18000
int minv = log(50);
int maxv = log(18000);
// calculate adjustment factor
int scale = (maxv-minv) / (maxp-minp);
return exp(minv + scale*(position-minp));
}
void quitDialog(void) {
QMessageBox msgBox;
msgBox.setText("Are you sure you want to quit?");
msgBox.setStandardButtons(QMessageBox::No|QMessageBox::Yes);
msgBox.setDefaultButton(QMessageBox::No);
int reply = msgBox.exec();
if(reply == QMessageBox::Yes)
QCoreApplication::quit();
}
// does a sample conversion with sox. returns the path to the temporary file containing the converted sample
QString convert(Sample s, bool preview) {
// get a temporary file name
QTemporaryFile f;
QString path;
if(f.open()) {
path = f.fileName();
}
f.close();
if(preview)
path.append(".wav");
else
path.append(".8svx");
int rate = protrackerTable[s.rate];
// construct sox command line
QStringList args = QStringList{s.path,QString("-b"),QString("8"),QString{"-c"},QString("1"), path}; // 8bit mono
args << QString("remix") << QString("-");
if(s.normalise) args << QString("norm") << QString("-6");
if(s.highcutOn) args << QString("lowpass") << QString("-1") << QString::number(s.highcutFrequency);
if(s.lowcutOn) args << QString("highpass") << QString("-1") << QString::number(s.lowcutFrequency);
args << QString("rate") << QString::number(rate); // TODO adjust this based on user selection
args += QString("dither -S").split(' ');
qDebug() << args;
QProcess sox;
sox.setProgram("sox");
sox.setArguments(args);
sox.start();
sox.waitForFinished();
qDebug() << sox.readAllStandardOutput();
qDebug() << sox.readAllStandardError();
return path;
}
// mainwindow proper
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// initialise frequency combo box
// TODO make this in order and actually matter
// invert the table
QList<QString> k = protrackerTable.keys();
QList<QPair<int,QString>> inv;
// Populate the inverted list
for (auto k : protrackerTable.keys()) {
inv.append(QPair<int,QString>(protrackerTable[k], k));
}
// sort it
std::sort(std::begin(inv), std::end(inv));
// add things to combobox
for (QPair<int,QString> p : inv) {
this->ui->FrequencyCombo->addItem(p.second,QVariant(p.first));
}
this->ui->centralwidget->setEnabled(false);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::closeEvent(QCloseEvent *event) {
event->ignore();
quitDialog();
}
void MainWindow::on_actionQuit_triggered()
{
quitDialog();
}
// updates the UI components relating to the current selected sample:
void MainWindow::updateSampleUI() {
Ui::MainWindow* u = this->ui;
QVariant v = u->sampleCombo->currentData();
Sample s = v.value<Sample>();
u->NormaliseCheckbox->setChecked(s.normalise);
u->lowCutCheckbox->setChecked(s.lowcutOn);
u->highCutCheckbox->setChecked(s.lowcutOn);
u->lowCutSlider->setValue(s.lowcutFrequency);
u->highCutSlider->setValue(abs(s.highcutFrequency-20000+20)); // reverse it
// update sample rate box
int t = u->FrequencyCombo->findText(s.rate);
u->FrequencyCombo->setCurrentIndex(t);
// fix enable/disable for sliders
this->updateSliders();
}
void MainWindow::updateSliders() {
this->ui->lowCutSlider->setEnabled(this->ui->lowCutCheckbox->isChecked());
this->ui->highCutSlider->setEnabled(this->ui->highCutCheckbox->isChecked());
}
bool clearDialog() {
QMessageBox msgBox;
msgBox.setText("Lose current samples?");
msgBox.setStandardButtons(QMessageBox::No|QMessageBox::Yes);
msgBox.setDefaultButton(QMessageBox::No);
int reply = msgBox.exec();
if(reply == QMessageBox::No)
return false;
return true;
}
void MainWindow::on_actionLoad_Samples_triggered()
{
if(this->ui->sampleCombo->count() != 0) {
if(!clearDialog())
return;
}
this->ui->sampleCombo->clear();
QStringList fileNames = QFileDialog::getOpenFileNames(this,
tr("Open Samples"), ".", tr("Wave Files (*.wav)"));
if(fileNames.count() != 0) {
for(QString path : fileNames) {
Sample tmp;
tmp.path = path;
tmp.name = path.split(QChar('/')).last();
QVariant var;
var.setValue(tmp);
this->ui->sampleCombo->addItem(tmp.name.append(" | Not Ready"),var);
}
this->ui->centralwidget->setEnabled(true);
this->updateSampleUI();
}
}
void MainWindow::on_browseButton_clicked()
{
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"),
"/home",
QFileDialog::ShowDirsOnly
| QFileDialog::DontResolveSymlinks);
this->ui->DirectoryName->setText(dir);
OutputPath = dir;
}
void MainWindow::on_NormaliseCheckbox_clicked()
{
QComboBox* s = this->ui->sampleCombo;
if(s->count() > 0) {
Sample d = s->currentData().value<Sample>();
d.normalise = !d.normalise;
QVariant v;
v.setValue(d);
s->setItemData(s->currentIndex(),v);
}
}
void MainWindow::on_sampleCombo_currentIndexChanged(int index)
{
this->updateSampleUI();
}
void MainWindow::on_lowCutCheckbox_clicked()
{
QComboBox* s = this->ui->sampleCombo;
if(s->count() > 0) {
Sample d = s->currentData().value<Sample>();
d.lowcutOn = !d.lowcutOn;
QVariant v;
v.setValue(d);
s->setItemData(s->currentIndex(),v);
}
this->updateSliders();
}
void MainWindow::on_highCutCheckbox_clicked()
{
QComboBox* s = this->ui->sampleCombo;
if(s->count() > 0) {
Sample d = s->currentData().value<Sample>();
d.highcutOn = !d.highcutOn;
QVariant v;
v.setValue(d);
s->setItemData(s->currentIndex(),v);
}
this->updateSliders();
}
void MainWindow::on_lowCutSlider_sliderMoved(int position)
{
QComboBox* s = this->ui->sampleCombo;
if(s->count() > 0) {
Sample d = s->currentData().value<Sample>();
d.lowcutFrequency = position;
QVariant v;
v.setValue(d);
s->setItemData(s->currentIndex(),v);
}
this->updateSliders();
}
void MainWindow::on_highCutSlider_sliderMoved(int position)
{
QComboBox* s = this->ui->sampleCombo;
if(s->count() > 0) {
Sample d = s->currentData().value<Sample>();
d.highcutFrequency = abs(position-20000+20);
QVariant v;
v.setValue(d);
s->setItemData(s->currentIndex(),v);
}
this->updateSliders();
}
void MainWindow::on_actionClear_All_triggered()
{
if(this->ui->sampleCombo->count() != 0) {
if(!clearDialog())
return;
}
this->ui->sampleCombo->clear();
this->ui->centralwidget->setEnabled(false);
}
void MainWindow::on_readyButton_clicked()
{
QComboBox* s = this->ui->sampleCombo;
if(s->count() > 0) {
Sample d = s->currentData().value<Sample>();
d.ready = !d.ready;
QVariant v;
v.setValue(d);
s->setItemData(s->currentIndex(),v);
QString newLabel;
if(d.ready) {
newLabel = d.name.append(" | Ready");
} else {
newLabel = d.name.append(" | Not Ready");
}
s->setItemText(s->currentIndex(),newLabel);
}
this->updateSliders();
}
// TODO playback here, the sample's getting made fine! sox just being funky
void MainWindow::on_previewButton_clicked()
{
if(this->ui->sampleCombo->count() != 0) {
QVariant d = this->ui->sampleCombo->currentData();
QString path = convert(d.value<Sample>(), true);
// use sox for playback lol
QMediaPlayer* player = new QMediaPlayer;
QAudioOutput* audioOutput = new QAudioOutput;
QFile file = QFile(path);
QLocale l = QLocale();
QString size = l.formattedDataSize(file.size(),0);
this->ui->SizeLabel->setText(size);
player->setAudioOutput(audioOutput);
player->setSource(QUrl::fromLocalFile(path));
audioOutput->setVolume(50);
player->play();
}
}
void MainWindow::on_FrequencyCombo_activated(int index)
{
QComboBox* s = this->ui->sampleCombo;
if(s->count() > 0) {
Sample d = s->currentData().value<Sample>();
d.rate = this->ui->FrequencyCombo->currentText();
QVariant v;
v.setValue(d);
s->setItemData(s->currentIndex(),v);
}
this->updateSliders();
}
void MainWindow::on_ExportButton_clicked()
{
if(OutputPath.isEmpty()) {
QMessageBox box;
box.setText("no output directory selected!");
box.exec();
return;
}
bool ready = true;
QList<Sample> samples;
QComboBox* b = this->ui->sampleCombo;
for(int i = 0;i < b->count(); i++) {
Sample s = b->itemData(i).value<Sample>();
samples += s;
if(!s.ready) {
ready = false;
}
}
if(!ready) {
QMessageBox msgBox;
msgBox.setText("Not all samples are marked as ready. Do you want to proceed?");
msgBox.setStandardButtons(QMessageBox::No|QMessageBox::Yes);
msgBox.setDefaultButton(QMessageBox::No);
int reply = msgBox.exec();
if(reply == QMessageBox::Yes)
ready = true;
}
if(!ready) return;
for(Sample s : samples) {
QString p = convert(s,false);
QString dest = s.name;
dest.replace("wav","8svx");
dest = OutputPath + QString("/") + dest;
qDebug() << dest;
QFile::copy(p,dest);
}
}