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