470 lines
11 KiB
C++
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);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|