#include <cstdio>
#include <cmath>
#include <unicode/ustring.h>
#include <unicode/unistr.h>
#include <unicode/ucnv.h>
#include <QUrl>
#include <QFileInfo>
#include <QMimeData>
#include <QFont>
#include <QDirIterator>
#include <QMessageBox>
#include <QCheckBox>
#include "qmpmidioutfluid.hpp"
#include "qmpmidiplay.hpp"
#include "qmpmainwindow.hpp"
#include "ui_qmpmainwindow.h"
#define setButtonHeight(x,h) {x->setMaximumHeight(h*(logicalDpiY()/96.));x->setMinimumHeight(h*(logicalDpiY()/96.));}
#define setButtonWidth(x,h) {x->setMaximumWidth(h*(logicalDpiY()/96.));x->setMinimumWidth(h*(logicalDpiY()/96.));}
#ifdef _WIN32
#include <windows.h>
char *wcsto8bit(const wchar_t *s)
{
int size = WideCharToMultiByte(CP_OEMCP, WC_NO_BEST_FIT_CHARS, s, -1, 0, 0, 0, 0);
char *c = (char *)calloc(size, sizeof(char));
WideCharToMultiByte(CP_OEMCP, WC_NO_BEST_FIT_CHARS, s, -1, c, size, 0, 0);
return c;
}
#endif
#define UPDATE_INTERVAL 66
qmpMainWindow *qmpMainWindow::ref = nullptr;
qmpMainWindow::qmpMainWindow(QCommandLineParser *_clp, QWidget *parent):
QMainWindow(parent),
ui(new Ui::qmpMainWindow),
clp(_clp)
{
ui->setupUi(this);
ui->lbCurPoly->setText("00000");
ui->lbMaxPoly->setText("00000");
ui->lbFileName->setText("");
ref = this;
ui->verticalLayout->setAlignment(ui->pushButton, Qt::AlignRight);
setButtonHeight(ui->pbNext, 36);
setButtonHeight(ui->pbPlayPause, 36);
setButtonHeight(ui->pbAdd, 36);
setButtonHeight(ui->pbPrev, 36);
setButtonHeight(ui->pbSettings, 36);
setButtonHeight(ui->pbStop, 36);
playing = false;
stopped = true;
dragging = false;
fin = false;
settings.reset(new qmpSettings());
settingsw = new qmpSettingsWindow(settings.get(), this);
player = nullptr;
timer = nullptr;
fluidrenderer = nullptr;
}
qmpMainWindow::~qmpMainWindow()
{
QList<QAction *>a = ui->lbFileName->actions();
for (unsigned i = 0; i < a.size(); ++i)
{
ui->lbFileName->removeAction(a[i]);
delete a[i];
}
pmgr->deinitPlugins();
auto rtdev = qmpRtMidiManager::getDevices();
for (auto &i : rtdev)
player->unregisterMidiOutDevice(i.second);
delete pmgr;
if (timer)
delete timer;
delete helpw;
helpw = nullptr;
delete efxw;
efxw = nullptr;
delete chnlw;
chnlw = nullptr;
delete plistw;
plistw = nullptr;
delete infow;
infow = nullptr;
delete settingsw;
settingsw = nullptr;
delete panicf;
panicf = nullptr;
delete renderf;
renderf = nullptr;
delete reloadsynf;
reloadsynf = nullptr;
if (player)
delete player;
internalfluid->deviceDeinit();
delete internalfluid;
delete ui;
}
void qmpMainWindow::init()
{
show();
ui->centralWidget->setEnabled(false);
pmgr = new qmpPluginManager();
registerMidiOptions();
std::future<void> f = std::async(std::launch::async, [this]
{
player = new CMidiPlayer();
internalfluid = new qmpMidiOutFluid();
player->registerMidiOutDevice(internalfluid, "Internal FluidSynth");
reloadsynf = new qmpReloadSynthFunc(this);
internalfluid->registerOptions(pmgr->pluginAPI);
playerSetup(internalfluid);
internalfluid->deviceInit();
loadSoundFont(internalfluid);
for (int i = 0; i < 16; ++i)
player->setChannelOutput(i, 0);
auto rtdev = qmpRtMidiManager::getDevices();
for (auto &i : rtdev)
player->registerMidiOutDevice(i.first, i.second);
});
while (f.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout)
QApplication::processEvents();
ui->centralWidget->setEnabled(true);
settingsw->registerSoundFontOption();
registerBehaviorOptions();
settingsw->registerCustomizeWidgetOptions();
plistw = new qmpPlistWindow(this);
chnlw = new qmpChannelsWindow(this);
efxw = new qmpEfxWindow(this);
infow = new qmpInfoWindow(this);
helpw = new qmpHelpWindow(this);
timer = new QTimer(this);
renderf = new qmpRenderFunc(this);
panicf = new qmpPanicFunc(this);
if (argfiles.size())
{
plistw->emptyList();
for (auto &i : argfiles)
plistw->insertItem(i);
}
if (settings->getOptionBool("Behavior/DialogStatus"))
{
QRect g = settings->getOptionRaw("DialogStatus/MainW", QRect()).toRect();
if (!g.isNull())
setGeometry(g);
}
registerFunctionality(renderf, "Render", tr("Render to wave").toStdString(), getThemedIconc(":/img/render.svg"), 0, false);
registerFunctionality(panicf, "Panic", tr("Panic").toStdString(), getThemedIconc(":/img/panic.svg"), 0, false);
registerFunctionality(reloadsynf, "ReloadSynth", tr("Restart fluidsynth").toStdString(), getThemedIconc(":/img/repeat-base.svg"), 0, false);
const QStringList &qpp = clp->values("plugin");
std::vector<std::string> pp;
for (auto &s : qpp)
pp.push_back(s.toStdString());
pmgr->scanPlugins(pp);
settingsw->registerPluginOption(pmgr);
settingsw->updatePluginList(pmgr);
pmgr->initPlugins();
settingsw->registerExtraMidiOptions();
QVariant *dinif_v = static_cast<QVariant *>(settings->getOptionCustom("Midi/DeviceInitializationFiles"));
QList<QVariant> devinif_list = dinif_v->toList();
delete dinif_v;
QMap<QString, QString> devinif_map;
for (auto &i : devinif_list)
{
QPair<QString, QString> p = i.value<QPair<QString, QString>>();
devinif_map[p.first] = p.second;
}
auto rtdev = qmpRtMidiManager::getDevices();
for (auto &i : rtdev)
{
if (devinif_map.contains(QString(i.second.c_str())))
i.first->setInitializerFile(devinif_map[QString(i.second.c_str())].toStdString().c_str());
}
chnlw->selectDefaultDevice();
ui->vsMasterVol->setValue(settings->getOptionRaw("FluidSynth/Gain", 50).toInt());
connect(timer, &QTimer::timeout, this, &qmpMainWindow::updateWidgets);
connect(timer, &QTimer::timeout, infow, &qmpInfoWindow::updateInfo);
ui->pbNext->setIcon(QIcon(getThemedIcon(":/img/next.svg")));
ui->pbPrev->setIcon(QIcon(getThemedIcon(":/img/prev.svg")));
ui->pbPlayPause->setIcon(QIcon(getThemedIcon(":/img/play.svg")));
ui->pbStop->setIcon(QIcon(getThemedIcon(":/img/stop.svg")));
ui->pbSettings->setIcon(QIcon(getThemedIcon(":/img/settings.svg")));
ui->pbAdd->setIcon(QIcon(getThemedIcon(":/img/open.svg")));
if (argfiles.size())
on_pbPlayPause_clicked();
setupWidget();
settingsw->postInit();
}
int qmpMainWindow::parseArgs()
{
bool loadfolder = clp->isSet("load-all-files");
const QStringList &args = clp->positionalArguments();
for (int i = 0; i < args.size(); ++i)
{
if (QFileInfo(args.at(i)).exists())
{
if (loadfolder || settings->getOptionBool("Behavior/LoadFolder"))
{
QDirIterator di(QFileInfo(args.at(i)).absolutePath());
while (di.hasNext())
{
QString c = di.next();
argfiles.push_back(c);
}
}
else
argfiles.push_back(args.at(i));
}
}
return 0;
}
bool qmpMainWindow::startedWithFiles()
{
return !argfiles.empty();
}
void qmpMainWindow::closeEvent(QCloseEvent *event)
{
if (settings->getOptionBool("Behavior/DialogStatus"))
{
settings->setOptionRaw("DialogStatus/MainW", geometry());
}
on_pbStop_clicked();
fin = true;
for (auto i = mfunc.begin(); i != mfunc.end(); ++i)
{
i->second.i()->close();
i->second.setAssignedControl((QReflectiveAction *)nullptr),
i->second.setAssignedControl((QReflectivePushButton *)nullptr);
}
efxw->close();
chnlw->close();
plistw->close();
infow->close();
settingsw->close();
event->accept();
}
void qmpMainWindow::dropEvent(QDropEvent *event)
{
QList<QUrl> l = event->mimeData()->urls();
QStringList sl;
for (int i = 0; i < l.size(); ++i)
sl.push_back(l.at(i).toLocalFile());
plistw->insertItems(sl);
switchTrack(plistw->getLastItem());
}
void qmpMainWindow::dragEnterEvent(QDragEnterEvent *event)
{
//if(event->mimeData()->hasFormat("application/x-midi"))
event->acceptProposedAction();
}
void qmpMainWindow::updateWidgets()
{
setFuncEnabled("Render", stopped);
setFuncEnabled("ReloadSynth", stopped);
if (player->isFinished() && playerTh)
{
if (!plistw->getRepeat())
{
timer->stop();
stopped = true;
playing = false;
invokeCallback("main.stop", nullptr);
setFuncEnabled("Render", stopped);
setFuncEnabled("ReloadSynth", stopped);
player->playerDeinit();
auto f = std::async([this] {playerTh->join();});
while (f.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout)
{
QApplication::processEvents();
ui->lbCurPoly->setText(QString("%1").arg(internalfluid->getPolyphone(), 5, 10, QChar('0')));
ui->lbMaxPoly->setText(QString("%1").arg(internalfluid->getMaxPolyphone(), 5, 10, QChar('0')));
}
delete playerTh;
playerTh = nullptr;
player->playerPanic();
player->playerReset();
chnlw->on_pbUnmute_clicked();
chnlw->on_pbUnsolo_clicked();
ui->pbPlayPause->setIcon(QIcon(getThemedIcon(":/img/play.svg")));
ui->hsTimer->setValue(0);
ui->lbCurPoly->setText("00000");
ui->lbMaxPoly->setText("00000");
ui->lbCurTime->setText("00:00");
}
else
switchTrack(plistw->getNextItem(), false);
}
if (renderTh)
{
if (fluidrenderer->isFinished())
{
renderTh->join();
timer->stop();
ui->centralWidget->setEnabled(true);
delete renderTh;
renderTh = nullptr;
fluidrenderer->renderDeinit();
delete fluidrenderer;
fluidrenderer = nullptr;
}
}
while (!player->isFinished() && player->getTCeptr() > player->getStamp(ui->hsTimer->value())
&& ui->hsTimer->value() < 100 && !dragging)
ui->hsTimer->setValue(ui->hsTimer->value() + 1);
if (playing)
{
std::chrono::duration<double> elapsed =
std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - st);
char ts[100];
sprintf(ts, "%02d:%02d", (int)(elapsed.count() + offset) / 60, (int)(elapsed.count() + offset) % 60);
ui->lbCurTime->setText(ts);
ui->lbCurPoly->setText(QString("%1").arg(internalfluid->getPolyphone(), 5, 10, QChar('0')));
ui->lbMaxPoly->setText(QString("%1").arg(internalfluid->getMaxPolyphone(), 5, 10, QChar('0')));
}
}
QString qmpMainWindow::getFileName()
{
return ui->lbFileName->text();
}
QUrl qmpMainWindow::getFilePath()
{
return filepath;
}
void qmpMainWindow::switchTrack(QString s, bool interrupt)
{
stopped = true;
playing = false;
setFuncEnabled("Render", stopped);
setFuncEnabled("ReloadSynth", stopped);
ui->pbPlayPause->setIcon(QIcon(getThemedIcon(":/img/pause.svg")));
if (interrupt)
{
player->playerDeinit();
player->playerPanic();
}
invokeCallback("main.stop", nullptr);
stopped = false;
playing = true;
if (playerTh)
{
auto f = std::async([this] {playerTh->join();});
while (f.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout)
{
QApplication::processEvents();
ui->lbCurPoly->setText(QString("%1").arg(internalfluid->getPolyphone(), 5, 10, QChar('0')));
ui->lbMaxPoly->setText(QString("%1").arg(internalfluid->getMaxPolyphone(), 5, 10, QChar('0')));
}
delete playerTh;
playerTh = nullptr;
}
timer->stop();
player->playerPanic();
player->playerReset();
ui->hsTimer->setValue(0);
chnlw->on_pbUnmute_clicked();
chnlw->on_pbUnsolo_clicked();
QString fns = s;
filepath = QUrl::fromLocalFile(fns);
setWindowTitle(QUrl::fromLocalFile(fns).fileName().left(QUrl::fromLocalFile(fns).fileName().lastIndexOf('.')) + " - QMidiPlayer");
ui->lbFileName->setText(filepath.fileName().left(filepath.fileName().lastIndexOf('.')));
if (plistw->getCurrentItem() != fns)
plistw->setCurrentItem(fns);
onfnChanged();
if (!loadFile(fns))
return;
char ts[100];
sprintf(ts, "%02d:%02d", (int)player->getFtime() / 60, (int)player->getFtime() % 60);
ui->lbFinTime->setText(ts);
player->playerInit();
PlaybackStatus ps = getPlaybackStatus();
invokeCallback("main.start", &ps);
internalfluid->setGain(ui->vsMasterVol->value() / 250.);
efxw->sendEfxChange();
playerTh = new std::thread([this]
{
player->playerThread();
if (settings->getOptionBool("Midi/WaitVoice") && player->isFinished())
while (internalfluid->getOutputLevel() > -100 && internalfluid->getPolyphone() > 0)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
});
#ifdef _WIN32
SetThreadPriority((void *)playerTh->native_handle(), THREAD_PRIORITY_TIME_CRITICAL);
#endif
st = std::chrono::steady_clock::now();
offset = 0;
timer->start(UPDATE_INTERVAL);
}
std::string qmpMainWindow::getTitle()
{
return decodeStdString(player->getTitle());
}
std::wstring qmpMainWindow::getWTitle()
{
return decodeStdWString(player->getTitle());
}
void qmpMainWindow::playerSetup(IFluidSettings *fs)
{
fs->setOptStr("audio.driver", settings->getOptionEnumIntOptName("FluidSynth/AudioDriver").c_str());
fs->setOptInt("audio.period-size", settings->getOptionInt("FluidSynth/BufSize"));
fs->setOptInt("audio.periods", settings->getOptionInt("FluidSynth/BufCnt"));
fs->setOptStr("audio.sample-format", settings->getOptionEnumIntOptName("FluidSynth/SampleFormat").c_str());
fs->setOptNum("synth.sample-rate", settings->getOptionInt("FluidSynth/SampleRate"));
fs->setOptInt("synth.polyphony", settings->getOptionInt("FluidSynth/Polyphony"));
fs->setOptInt("synth.cpu-cores", settings->getOptionInt("FluidSynth/Threads"));
std::string bsmode;
if (settings->getOptionBool("FluidSynth/AutoBS") && player->getFileStandard())
switch (player->getFileStandard())
{
case 1:
bsmode = "gm";
break;
case 2:
bsmode = "mma";
break;
case 3:
bsmode = "gs";
break;
case 4:
bsmode = "xg";
break;
}
else
{
bsmode = settings->getOptionEnumIntOptName("FluidSynth/BankSelect");
std::transform(bsmode.begin(), bsmode.end(), bsmode.begin(), [](char i)
{
return tolower(i);
});
}
fs->setOptStr("synth.midi-bank-select", bsmode.c_str());
fs->setOptInt("synth.device-id", settings->getOptionInt("FluidSynth/DeviceID"));
player->sendSysX(settings->getOptionBool("Midi/SendSysEx"));
}
void qmpMainWindow::loadSoundFont(IFluidSettings *fs)
{
QVariant *data = static_cast<QVariant *>(settings->getOptionCustom("FluidSynth/SoundFonts"));
QList<QVariant> sflist = data->toList();
for (auto i = sflist.rbegin(); i != sflist.rend(); ++i)
{
if (i->toString().startsWith('#'))
continue;
QString sf = i->toString();
#ifdef _WIN32
char *c = wcsto8bit(sf.toStdWString().c_str());
fs->loadSFont(c);
free(c);
#else
fs->loadSFont(sf.toStdString().c_str());
#endif
}
delete data;
}
int qmpMainWindow::loadFile(QString fns)
{
#ifdef _WIN32
char *c = wcsto8bit(fns.toStdWString().c_str());
#else
std::string s = fns.toStdString();
const char *c = s.c_str();
#endif
int ret = 1;
invokeCallback("main.reset", nullptr);
if (!player->playerLoadFile(c))
{
QMessageBox::critical(this, tr("Error"), tr("%1 is not a valid midi file.").arg(fns));
ret = 0;
}
#ifdef _WIN32
free(c);
#endif
return ret;
}
void qmpMainWindow::registerMidiOptions()
{
settings->registerOptionBool("MIDI", "Disable MIDI Mapping", "Midi/DisableMapping", false);
settings->registerOptionBool("MIDI", "Send system exclusive messages", "Midi/SendSysEx", true);
settings->registerOptionBool("MIDI", "Wait for remaining voice before stopping", "Midi/WaitVoice", true);
settings->registerOptionInt("MIDI", "Fluidsynth Device ID", "FluidSynth/DeviceID", 0x00, 0x7E, 0x10);
UErrorCode e = U_ZERO_ERROR;
auto ucnvc = ucnv_countAvailable();
std::vector<std::string> converters;
for (auto i = 0; i < ucnvc; ++i)
{
auto n = ucnv_getAvailableName(i);
auto sn = ucnv_getStandardName(n, "MIME", &e);
if (sn) converters.push_back(std::string(sn));
}
settings->registerOptionEnumInt("MIDI", "Text encoding", "Midi/TextEncoding", converters, 0);
}
void qmpMainWindow::registerBehaviorOptions()
{
settings->registerOptionBool("Behavior", "Restore last playlist on startup", "Behavior/RestorePlaylist", false);
settings->registerOptionBool("Behavior", "Add files in the same folder to playlist", "Behavior/LoadFolder", false);
settings->registerOptionBool("Behavior", "Save dialog status", "Behavior/DialogStatus", false);
settings->registerOptionBool("Behavior", "Show labels beside icon in toolbar buttons", "Behavior/ShowButtonLabel", false);
settings->registerOptionEnumInt("Behavior", "Icon Theme", "Behavior/IconTheme", {"Auto", "Dark", "Light"}, 0);
}
QString qmpMainWindow::decodeString(const char *str)
{
std::string enc("utf-8");
if (ref && ref->settings)
enc = ref->settings->getOptionEnumIntOptName("Midi/TextEncoding");
icu::UnicodeString us(str, enc.c_str());
std::string r;
return QString::fromUtf8(us.toUTF8String(r).c_str());
}
std::string qmpMainWindow::decodeStdString(const char *str)
{
std::string enc("utf-8");
if (ref && ref->settings)
enc = ref->settings->getOptionEnumIntOptName("Midi/TextEncoding");
icu::UnicodeString us(str, enc.c_str());
std::string r;
us.toUTF8String(r);
return r;
}
std::wstring qmpMainWindow::decodeStdWString(const char *str)
{
std::string enc("utf-8");
if (ref && ref->settings)
enc = ref->settings->getOptionEnumIntOptName("Midi/TextEncoding");
icu::UnicodeString us(str, enc.c_str());
std::wstring r;
int32_t sz = 0;
UErrorCode e = U_ZERO_ERROR;
u_strToWCS(nullptr, 0, &sz, us.getBuffer(), us.length(), &e);
r.resize(sz + 1);
e = U_ZERO_ERROR;
u_strToWCS(r.data(), r.size(), nullptr, us.getBuffer(), us.length(), &e);
return r;
}
void qmpMainWindow::on_pbPlayPause_clicked()
{
if (stopped)
{
QString fns = plistw->getFirstItem();
if (!fns.length())
{
if (!plistw->on_pbAdd_clicked())
{
playing = false;
return;
}
fns = plistw->getFirstItem();
if (!fns.length())
return (void)(playing = false);
}
filepath = QUrl::fromLocalFile(fns);
setWindowTitle(QUrl::fromLocalFile(fns).fileName().left(QUrl::fromLocalFile(fns).fileName().lastIndexOf('.')) + " - QMidiPlayer");
ui->lbFileName->setText(filepath.fileName().left(filepath.fileName().lastIndexOf('.')));
onfnChanged();
if (!loadFile(fns))
return;
char ts[100];
sprintf(ts, "%02d:%02d", (int)player->getFtime() / 60, (int)player->getFtime() % 60);
ui->lbFinTime->setText(ts);
player->playerInit();
playing = true;
PlaybackStatus ps = getPlaybackStatus();
invokeCallback("main.start", &ps);
internalfluid->setGain(ui->vsMasterVol->value() / 250.);
efxw->sendEfxChange();
playerTh = new std::thread([this]
{
player->playerThread();
if (settings->getOptionBool("Midi/WaitVoice") && player->isFinished())
while (internalfluid->getOutputLevel() > -100 && internalfluid->getPolyphone() > 0)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
});
#ifdef _WIN32
SetThreadPriority((void *)playerTh->native_handle(), THREAD_PRIORITY_TIME_CRITICAL);
#endif
st = std::chrono::steady_clock::now();
offset = 0;
timer->start(UPDATE_INTERVAL);
stopped = false;
ui->pbPlayPause->setIcon(QIcon(getThemedIcon(":/img/pause.svg")));
}
else
setPaused(playing);
}
void qmpMainWindow::on_hsTimer_sliderPressed()
{
dragging = true;
}
void qmpMainWindow::on_hsTimer_sliderReleased()
{
dragging = false;
if (playing)
{
if (ui->hsTimer->value() == 100)
{
on_pbNext_clicked();
return;
}
player->playerPanic();
player->setTCeptr(player->getStamp(ui->hsTimer->value()), ui->hsTimer->value());
offset = ui->hsTimer->value() / 100.*player->getFtime();
st = std::chrono::steady_clock::now();
}
else
{
if (stopped)
{
ui->hsTimer->setValue(0);
return;
}
player->setTCeptr(player->getStamp(ui->hsTimer->value()), ui->hsTimer->value());
offset = ui->hsTimer->value() / 100.*player->getFtime();
char ts[100];
sprintf(ts, "%02d:%02d", (int)(offset) / 60, (int)(offset) % 60);
ui->lbCurTime->setText(ts);
}
PlaybackStatus ps = getPlaybackStatus();
invokeCallback("main.seek", &ps);
}
uint32_t qmpMainWindow::getPlaybackPercentage()
{
return ui->hsTimer->value();
}
void qmpMainWindow::playerSeek(uint32_t percentage)
{
if (percentage > 100)
percentage = 100;
if (percentage < 0)
percentage = 0;
if (playing)
{
if (percentage == 100)
{
on_pbNext_clicked();
return;
}
player->playerPanic();
ui->hsTimer->setValue(percentage);
player->setTCeptr(player->getStamp(percentage), percentage);
offset = percentage / 100. * player->getFtime();
st = std::chrono::steady_clock::now();
}
else
{
if (stopped)
{
ui->hsTimer->setValue(0);
return;
}
player->setTCeptr(player->getStamp(percentage), percentage);
offset = percentage / 100. * player->getFtime();
ui->hsTimer->setValue(percentage);
char ts[100];
sprintf(ts, "%02d:%02d", (int)(offset) / 60, (int)(offset) % 60);
ui->lbCurTime->setText(ts);
}
PlaybackStatus ps = getPlaybackStatus();
invokeCallback("main.seek", &ps);
}
PlaybackStatus qmpMainWindow::getPlaybackStatus()
{
std::chrono::duration<double> elapsed =
std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - st);
return {
.paused=!playing,
.stopped=stopped,
.curtime_ms=stopped ? 0 : uint64_t((elapsed.count() + offset) * 1000),
.maxtime_ms=stopped ? 0 : uint64_t(player->getFtime() * 1000),
.curtick=stopped ? 0 : player->getTick(),
.maxtick=stopped ? 0 : player->getMaxTick()
};
}
void qmpMainWindow::on_vsMasterVol_valueChanged()
{
if (!stopped)
internalfluid->setGain(ui->vsMasterVol->value() / 250.);
settings->setOptionRaw("FluidSynth/Gain", ui->vsMasterVol->value());
}
void qmpMainWindow::on_pbStop_clicked()
{
stop();
}
void qmpMainWindow::dialogClosed()
{
if (!settingsw->isVisible())
ui->pbSettings->setChecked(false);
}
void qmpMainWindow::on_pbPrev_clicked()
{
prevTrack();
}
void qmpMainWindow::on_pbNext_clicked()
{
nextTrack();
}
void qmpMainWindow::selectionChanged()
{
switchTrack(plistw->getCurrentItem());
}
void qmpMainWindow::on_lbFileName_customContextMenuRequested(const QPoint &pos)
{
QMenu menu(ui->lbFileName);
menu.addActions(ui->lbFileName->actions());
menu.exec(this->pos() + ui->lbFileName->pos() + pos);
}
void qmpMainWindow::onfnChanged()
{
if (!ui->lbFileName->text().length())
return;
QFont f = ui->lbFileName->font();
f.setPointSize(18);
QFontMetrics fm(f);
QSize size = fm.size(0, ui->lbFileName->text());
double fw = ui->lbFileName->width() / (double)size.width();
double fh = ui->lbFileName->height() / (double)size.height();
double ps = floor(f.pointSizeF() * (fw < fh ? fw : fh));
if (ps < 6)
ps = 6;
f.setPointSizeF(ps > 18 ? 18 : ps);
ui->lbFileName->setFont(f);
}
int qmpMainWindow::registerUIHook(std::string e, ICallBack *callback, void *userdat)
{
std::map<int, std::pair<qmpCallBack, void *>> &m = muicb[e];
int id = 0;
if (m.size())
id = m.rbegin()->first + 1;
m[id] = std::make_pair(qmpCallBack(callback), userdat);
return id;
}
int qmpMainWindow::registerUIHook(std::string e, callback_t callback, void *userdat)
{
std::map<int, std::pair<qmpCallBack, void *>> &m = muicb[e];
int id = 0;
if (m.size())
id = m.rbegin()->first + 1;
m[id] = std::make_pair(qmpCallBack(callback), userdat);
return id;
}
void qmpMainWindow::unregisterUIHook(std::string e, int hook)
{
std::map<int, std::pair<qmpCallBack, void *>> &m = muicb[e];
m.erase(hook);
}
void qmpMainWindow::registerFunctionality(qmpFuncBaseIntf *i, std::string name, std::string desc, const char *icon, int iconlen, bool checkable)
{
if (mfunc.find(name) != mfunc.end())
return;
mfunc[name] = qmpFuncPrivate(i, desc, icon, iconlen, checkable);
}
void qmpMainWindow::unregisterFunctionality(std::string name)
{
mfunc.erase(name);
for (auto i = enabled_actions.begin(); i != enabled_actions.end(); ++i)
if (*i == name)
{
enabled_actions.erase(i);
break;
}
for (auto i = enabled_buttons.begin(); i != enabled_buttons.end(); ++i)
if (*i == name)
{
enabled_buttons.erase(i);
break;
}
setupWidget();
}
void qmpMainWindow::setFuncState(std::string name, bool state)
{
mfunc[name].setChecked(state);
}
void qmpMainWindow::setFuncEnabled(std::string name, bool enable)
{
mfunc[name].setEnabled(enable);
}
bool qmpMainWindow::isDarkTheme()
{
if (!settings->getOptionEnumInt("Behavior/IconTheme"))
{
return ui->centralWidget->palette().color(QPalette::Window).lightness() < 128;
}
else return 2 - settings->getOptionEnumInt("Behavior/IconTheme");
}
void qmpMainWindow::startRender()
{
#ifdef _WIN32
char *ofstr = wcsto8bit((plistw->getCurrentItem() + QString(".wav")).toStdWString().c_str());
char *ifstr = wcsto8bit(plistw->getCurrentItem().toStdWString().c_str());
fluidrenderer = new qmpFileRendererFluid(ifstr, ofstr);
playerSetup(fluidrenderer);
fluidrenderer->renderInit();
free(ofstr);
free(ifstr);
#else
fluidrenderer = new qmpFileRendererFluid(
plistw->getCurrentItem().toStdString().c_str(),
(plistw->getCurrentItem() + QString(".wav")).toStdString().c_str()
);
playerSetup(fluidrenderer);
fluidrenderer->renderInit();
#endif
loadSoundFont(fluidrenderer);
ui->centralWidget->setEnabled(false);
fluidrenderer->setGain(ui->vsMasterVol->value() / 250.);
efxw->sendEfxChange(fluidrenderer);
timer->start(UPDATE_INTERVAL);
renderTh = new std::thread(&qmpFileRendererFluid::renderWorker, fluidrenderer);
}
void qmpMainWindow::reloadSynth()
{
ui->centralWidget->setEnabled(false);
std::future<void> f = std::async(std::launch::async,
[this]
{
internalfluid->deviceDeinit(true);
playerSetup(internalfluid);
internalfluid->deviceInit();
loadSoundFont(internalfluid);
}
);
while (f.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout)
QApplication::processEvents();
ui->centralWidget->setEnabled(true);
}
std::map<std::string, qmpFuncPrivate> &qmpMainWindow::getFunc()
{
return mfunc;
}
void qmpMainWindow::setPaused(bool paused)
{
if (stopped)
return;
playing = !paused;
if (paused)
{
player->playerPanic();
offset = ui->hsTimer->value() / 100. * player->getFtime();
}
else
{
st = std::chrono::steady_clock::now();
player->setResumed();
}
player->setTCpaused(paused);
PlaybackStatus ps = getPlaybackStatus();
invokeCallback("main.pause", &ps);
ui->pbPlayPause->setIcon(QIcon(getThemedIcon(paused ? ":/img/play.svg" : ":/img/pause.svg")));
}
void qmpMainWindow::nextTrack()
{
switchTrack(plistw->getNextItem());
}
void qmpMainWindow::prevTrack()
{
switchTrack(plistw->getPrevItem());
}
void qmpMainWindow::stop()
{
if (!stopped)
{
timer->stop();
stopped = true;
playing = false;
invokeCallback("main.stop", nullptr);
player->playerDeinit();
setFuncEnabled("Render", stopped);
setFuncEnabled("ReloadSynth", stopped);
player->playerPanic();
player->playerReset();
if (playerTh)
{
playerTh->join();
delete playerTh;
playerTh = nullptr;
}
chnlw->on_pbUnmute_clicked();
chnlw->on_pbUnsolo_clicked();
ui->pbPlayPause->setIcon(QIcon(getThemedIcon(":/img/play.svg")));
ui->hsTimer->setValue(0);
ui->lbCurPoly->setText("00000");
ui->lbMaxPoly->setText("00000");
ui->lbCurTime->setText("00:00");
}
}
void qmpMainWindow::setupWidget()
{
for (auto i = mfunc.begin(); i != mfunc.end(); ++i)
{
i->second.setAssignedControl(static_cast<QReflectiveAction *>(nullptr));
i->second.setAssignedControl(static_cast<QReflectivePushButton *>(nullptr));
}
QVariant *v = static_cast<QVariant *>(settings->getOptionCustom("Behavior/Toolbar"));
enabled_buttons.clear();
for (auto i : v->toList())
enabled_buttons.push_back(i.toString().toStdString());
delete v;
v = static_cast<QVariant *>(settings->getOptionCustom("Behavior/Actions"));
enabled_actions.clear();
for (auto i : v->toList())
enabled_actions.push_back(i.toString().toStdString());
delete v;
QList<QWidget *>w = ui->buttonwidget->findChildren<QWidget *>("", Qt::FindDirectChildrenOnly);
qDeleteAll(w);
QList<QAction *>a = ui->lbFileName->actions();
for (int i = 0; i < a.size(); ++i)
{
ui->lbFileName->removeAction(a[i]);
delete a[i];
}
for (unsigned i = 0; i < enabled_buttons.size(); ++i)
{
if (mfunc.find(enabled_buttons[i]) == mfunc.end())
continue;
QReflectivePushButton *pb = new QReflectivePushButton(
mfunc[enabled_buttons[i]].icon(),
tr(mfunc[enabled_buttons[i]].desc().c_str()),
enabled_buttons[i]
);
setButtonHeight(pb, 32);
//!!TODO
if (settings->getOptionBool("Behavior/ShowButtonLabel"))
{
pb->setText(tr(mfunc[enabled_buttons[i]].desc().c_str()));
pb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
else
setButtonWidth(pb, 32);
pb->setIconSize(QSize(16, 16));
ui->buttonwidget->layout()->addWidget(pb);
mfunc[enabled_buttons[i]].setAssignedControl(pb);
connect(pb, &QReflectivePushButton::onClick, this, &qmpMainWindow::funcReflector);
}
for (unsigned i = 0; i < enabled_actions.size(); ++i)
{
if (mfunc.find(enabled_actions[i]) == mfunc.end())
continue;
QReflectiveAction *a = new QReflectiveAction(
mfunc[enabled_actions[i]].icon(),
tr(mfunc[enabled_actions[i]].desc().c_str()),
enabled_actions[i]
);
ui->lbFileName->addAction(a);
mfunc[enabled_actions[i]].setAssignedControl(a);
connect(a, &QReflectiveAction::onClick, this, &qmpMainWindow::funcReflector);
}
ui->buttonwidget->layout()->setAlignment(Qt::AlignLeft);
}
void qmpMainWindow::invokeCallback(std::string cat, void *callerdat)
{
std::map<int, std::pair<qmpCallBack, void *>> *mp;
mp = &muicb[cat];
for (auto &i : *mp)
i.second.first(callerdat, i.second.second);
}
void qmpMainWindow::on_pbSettings_clicked()
{
if (ui->pbSettings->isChecked())
settingsw->show();
else settingsw->close();
}
void qmpMainWindow::funcReflector(std::string reflt)
{
if (mfunc[reflt].isCheckable())
{
mfunc[reflt].setChecked(!mfunc[reflt].isChecked());
if (mfunc[reflt].isChecked())
mfunc[reflt].i()->show();
else
mfunc[reflt].i()->close();
}
else mfunc[reflt].i()->show();
}
void qmpMainWindow::on_pushButton_clicked()
{
helpw->show();
}
qmpFuncPrivate::qmpFuncPrivate(qmpFuncBaseIntf *i, std::string _desc, const char *icon, int iconlen, bool checkable):
_i(i), des(_desc), _checkable(checkable)
{
if (icon)
{
QImage img;
if (icon[0] == ':' && icon[1] == '/' || icon[0] == 'q' && icon[1] == 'r' && icon[2] == 'c')
img = QImage(QString(icon));
else
img.loadFromData((uchar *)icon, iconlen);
QPixmap pixm;
pixm.convertFromImage(img);
_icon = QIcon(pixm);
}
else _icon = QIcon();
checked = false;
asgna = nullptr;
asgnb = nullptr;
}
void qmpMainWindow::on_pbAdd_clicked()
{
if (plistw->on_pbAdd_clicked())
switchTrack(plistw->getLastItem());
}