1 #include "mainwindow.h"
2 #include "ui_mainwindow.h"
3 #include "PsfLoader.h"
4 #include "SH_OpenAL.h"
5 #include "PsfTags.h"
6 #include "AppConfig.h"
7
8 #include <QFileDialog>
9 #include <QMessageBox>
10 #include <QDateTime>
11 #include <chrono>
12
13 #define PREFERENCE_UI_LASTFOLDER "ui.lastfolder"
14
MainWindow(QWidget * parent)15 MainWindow::MainWindow(QWidget* parent)
16 : QMainWindow(parent)
17 , ui(new Ui::MainWindow)
18 , model(this)
19 {
20 ui->setupUi(this);
21
22 m_virtualMachine = new CPsfVm();
23 m_virtualMachine->SetSpuHandler(&CSH_OpenAL::HandlerFactory);
24 m_OnNewFrameConnection = m_virtualMachine->OnNewFrame.Connect([&]() { OnNewFrame(); });
25
26 model.setHeaderData(0, Qt::Orientation::Horizontal, QVariant("Game"), Qt::DisplayRole);
27 model.setHeaderData(1, Qt::Orientation::Horizontal, QVariant("Title"), Qt::DisplayRole);
28 model.setHeaderData(2, Qt::Orientation::Horizontal, QVariant("Length"), Qt::DisplayRole);
29 ui->tableView->setModel(&model);
30 ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
31 ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
32
33 CAppConfig::GetInstance().RegisterPreferenceString(PREFERENCE_UI_LASTFOLDER, QDir::homePath().toStdString().c_str());
34 m_path = CAppConfig::GetInstance().GetPreferenceString(PREFERENCE_UI_LASTFOLDER);
35
36 // used as workaround to avoid direct ui access from a thread
37 connect(this, SIGNAL(ChangeRow(int)), ui->tableView, SLOT(selectRow(int)));
38
39 m_running = true;
40 m_thread = std::thread(&MainWindow::UiUpdateLoop, this);
41 }
42
UiUpdateLoop()43 void MainWindow::UiUpdateLoop()
44 {
45 while(m_running)
46 {
47 auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(16);
48 if(m_frames > m_trackLength)
49 {
50 m_frames = 0;
51 on_nextButton_clicked();
52 }
53 else if(m_frames >= m_fadePosition)
54 {
55 float currentRatio = static_cast<float>(m_trackLength - m_fadePosition) / static_cast<float>(m_trackLength - m_frames);
56 float currentVolume = (1 / currentRatio) * m_volumeAdjust;
57 m_virtualMachine->SetVolumeAdjust(currentVolume);
58 }
59 else if(m_trackLength > 0 && m_frames > 0 && m_fadePosition > 0)
60 {
61 float currentRatio = 1 / (static_cast<float>(m_trackLength - m_fadePosition) / static_cast<float>(m_frames));
62 if(currentRatio < 1.0f)
63 {
64 float currentVolume = currentRatio * m_volumeAdjust;
65 m_virtualMachine->SetVolumeAdjust(currentVolume);
66 }
67 }
68 std::this_thread::sleep_until(end);
69 }
70 }
71
~MainWindow()72 MainWindow::~MainWindow()
73 {
74 m_running = false;
75 if(m_thread.joinable()) m_thread.join();
76 if(m_virtualMachine != nullptr)
77 {
78 m_virtualMachine->Pause();
79 delete m_virtualMachine;
80 m_virtualMachine = nullptr;
81 }
82 delete ui;
83 }
84
on_actionOpen_triggered()85 void MainWindow::on_actionOpen_triggered()
86 {
87 QFileDialog dialog(this);
88 dialog.setFileMode(QFileDialog::ExistingFiles);
89 dialog.setDirectory(QString(m_path.c_str()));
90 dialog.setNameFilter(tr("PSF files (*.psf *.psf2 *.minipsf *.minipsf2)"));
91 if(dialog.exec())
92 {
93 if(m_virtualMachine != nullptr)
94 {
95 m_virtualMachine->Pause();
96 m_virtualMachine->Reset();
97 int index = model.rowCount();
98 foreach(QString file, dialog.selectedFiles())
99 {
100 try
101 {
102 CPsfBase::TagMap tags;
103 std::string fileName = file.toStdString();
104 CPsfLoader::LoadPsf(*m_virtualMachine, fileName, "", &tags);
105 model.addPlaylistItem(fileName, tags);
106 }
107 catch(const std::exception& e)
108 {
109 QMessageBox messageBox;
110 messageBox.critical(0, "Error", e.what());
111 messageBox.show();
112 }
113 }
114
115 m_path = QFileInfo(model.at(index)->path.c_str()).absolutePath().toStdString();
116 CAppConfig::GetInstance().SetPreferenceString(PREFERENCE_UI_LASTFOLDER, m_path.c_str());
117 PlayTrackIndex(index);
118 }
119 }
120 }
121
UpdateTrackDetails(CPsfBase::TagMap & tags)122 void MainWindow::UpdateTrackDetails(CPsfBase::TagMap& tags)
123 {
124 auto tag = CPsfTags(tags);
125 ui->current_game->setText(QString::fromWCharArray(tag.GetTagValue("game").c_str()));
126 ui->current_total_length->setText(QString::fromWCharArray(tag.GetTagValue("length").c_str()));
127 ui->current_title->setText(QString::fromWCharArray(tag.GetTagValue("title").c_str()));
128 ui->current_artist->setText(QString::fromWCharArray(tag.GetTagValue("artist").c_str()));
129 ui->current_cp->setText(QString::fromWCharArray(tag.GetTagValue("copyright").c_str()));
130 ui->current_year->setText(QString::fromWCharArray(tag.GetTagValue("year").c_str()));
131
132 m_volumeAdjust = 1.0f;
133
134 try
135 {
136 m_volumeAdjust = stof(tag.GetTagValue("volume"));
137 }
138 catch(...)
139 {
140 }
141 double dlength = CPsfTags::ConvertTimeString(tag.GetTagValue("length").c_str());
142 double dfade = CPsfTags::ConvertTimeString(tag.GetTagValue("fade").c_str());
143 m_trackLength = static_cast<uint64>(dlength * 60.0);
144 m_fadePosition = m_trackLength - static_cast<uint64>(dfade * 60.0);
145 m_frames = 0;
146
147 m_virtualMachine->SetVolumeAdjust((dfade > 0) ? 0 : m_volumeAdjust);
148
149 ChangeRow(m_currentindex);
150 }
151
OnNewFrame()152 void MainWindow::OnNewFrame()
153 {
154 m_frames++;
155 ui->current_time->setText(QDateTime::fromTime_t(m_frames / 60).toUTC().toString("mm:ss"));
156 }
157
on_tableView_customContextMenuRequested(const QPoint & pos)158 void MainWindow::on_tableView_customContextMenuRequested(const QPoint& pos)
159 {
160 auto item = ui->tableView->indexAt(pos);
161 if(item.isValid())
162 {
163 QMenu* menu = new QMenu(this);
164 auto playAct = new QAction("Play", this);
165 auto delAct = new QAction("Remove", this);
166
167 menu->addAction(playAct);
168 menu->addAction(delAct);
169 menu->popup(ui->tableView->viewport()->mapToGlobal(pos));
170
171 auto index = item.row();
172 connect(playAct, &QAction::triggered, std::bind(&MainWindow::PlayTrackIndex, this, index));
173 connect(delAct, &QAction::triggered, std::bind(&MainWindow::DeleteTrackIndex, this, index));
174 }
175 }
176
PlayTrackIndex(int index)177 void MainWindow::PlayTrackIndex(int index)
178 {
179 m_virtualMachine->Pause();
180 m_virtualMachine->Reset();
181 m_currentindex = index;
182 CPsfBase::TagMap tags;
183 CPsfLoader::LoadPsf(*m_virtualMachine, model.at(m_currentindex)->path, "", &tags);
184 UpdateTrackDetails(tags);
185 m_virtualMachine->Resume();
186 }
187
on_tableView_doubleClicked(const QModelIndex & index)188 void MainWindow::on_tableView_doubleClicked(const QModelIndex& index)
189 {
190 PlayTrackIndex(index.row());
191 }
192
DeleteTrackIndex(int index)193 void MainWindow::DeleteTrackIndex(int index)
194 {
195 auto playlistsize = model.removePlaylistItem(index);
196 if(index == m_currentindex)
197 {
198 if(playlistsize >= m_currentindex)
199 {
200 PlayTrackIndex(m_currentindex);
201 }
202 else if(m_currentindex > 0 && playlistsize == m_currentindex - 1)
203 {
204 PlayTrackIndex(--m_currentindex);
205 }
206 else
207 {
208 if(m_virtualMachine->GetStatus() == CVirtualMachine::STATUS::RUNNING)
209 {
210 m_virtualMachine->Pause();
211 m_virtualMachine->Reset();
212 }
213 m_currentindex--;
214 }
215 }
216 else if(index < m_currentindex)
217 {
218 m_currentindex--;
219 }
220 }
221
on_playpauseButton_clicked()222 void MainWindow::on_playpauseButton_clicked()
223 {
224 if(m_currentindex > -1)
225 {
226 if(m_virtualMachine != nullptr)
227 {
228 if(m_virtualMachine->GetStatus() == CVirtualMachine::STATUS::RUNNING)
229 {
230 m_virtualMachine->Pause();
231 }
232 else
233 {
234 m_virtualMachine->Resume();
235 }
236 }
237 }
238 else
239 {
240 on_actionOpen_triggered();
241 }
242 }
243
on_nextButton_clicked()244 void MainWindow::on_nextButton_clicked()
245 {
246 if(model.rowCount() < 1) return;
247
248 int currentindex = m_currentindex;
249 if(m_currentindex < model.rowCount() - 1)
250 {
251 m_currentindex += 1;
252 }
253 else
254 {
255 m_currentindex = 0;
256 }
257
258 if(currentindex == m_currentindex) return;
259
260 PlayTrackIndex(m_currentindex);
261 }
262
on_prevButton_clicked()263 void MainWindow::on_prevButton_clicked()
264 {
265 if(model.rowCount() < 1) return;
266
267 if(m_currentindex > 0)
268 {
269 m_currentindex -= 1;
270 }
271 else
272 {
273 m_currentindex = model.rowCount() - 1;
274 }
275
276 PlayTrackIndex(m_currentindex);
277 }
278
on_actionPlayPause_triggered()279 void MainWindow::on_actionPlayPause_triggered()
280 {
281 on_playpauseButton_clicked();
282 }
283
on_actionPrev_triggered()284 void MainWindow::on_actionPrev_triggered()
285 {
286 on_prevButton_clicked();
287 }
288
on_actionNext_triggered()289 void MainWindow::on_actionNext_triggered()
290 {
291 on_nextButton_clicked();
292 }
293