diff --git a/README.md b/README.md index ef8c7b2..a7aa0b6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Unit simulator for MLTD using the BASS audio library. HCA decryption code uses a # How to build 1. Download dependencies from http://www.un4seen.com/ (bass, bassmix, and bass_fx). 2. Place bass.h/lib, bassmix.h/lib, and bass_fx.h/lib in bass/. - 3. Open QuintetPlayer.pro in Qt Studio and build. + 3. Open QuintetPlayer.pro in Qt Creator and build. This program is technically cross-platform, but has only been tested on Windows, so YMMV. diff --git a/src/HCAStreamChannel.cpp b/src/HCAStreamChannel.cpp index 971b71d..e2fc64a 100644 --- a/src/HCAStreamChannel.cpp +++ b/src/HCAStreamChannel.cpp @@ -86,6 +86,7 @@ void HCAStreamChannel::unload() bool HCAStreamChannel::load(const std::string& filename) { + unload(); auto pair = dec->decode(filename.c_str(), 0, cipher_key_1, cipher_key_2, volume); ptr = pair.first; size = pair.second; @@ -94,6 +95,7 @@ bool HCAStreamChannel::load(const std::string& filename) bool HCAStreamChannel::load(const std::string& filename, DWORD samplenum) { + unload(); auto pair = dec->decode(filename.c_str(), samplenum); ptr = pair.first; size = pair.second; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 97807b4..8b2393d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -375,31 +375,32 @@ void MainWindow::setIdol(int index) idol_pixmap[index] = QPixmap(filename); idol_image[index]->setPixmap(idol_pixmap[index]); - HCAStreamChannel&& hcastream = HCAStreamChannel(&dec, 0.9f); - HCAStreamChannel&& hcastream2 = HCAStreamChannel(&dec, 0.9f); QWORD pos = BASS_ChannelGetPosition(bgm->get_decode_channel(), BASS_POS_BYTE); - hcastream.load("res/" + current_song + "/" + current_idols[index] + ".hca", pos/4); - hcastream2.load("res/" + current_song + "/oneshot/" + current_idols[index] + ".hca", 0); + idols[index]->load("res/" + current_song + "/" + current_idols[index] + ".hca", pos/4); + idols_oneshot[index]->load("res/" + current_song + "/oneshot/" + current_idols[index] + ".hca", 0); if(index >= unit_size) { - hcastream.destroy_channels(); - hcastream2.destroy_channels(); + idols[index]->destroy_channels(); + idols_oneshot[index]->destroy_channels(); } else { + bool was_playing = true; + if(BASS_ChannelIsActive(play_stream) == BASS_ACTIVE_PAUSED) + { + was_playing = false; + } + BASS_ChannelPause(play_stream); pos = BASS_ChannelGetPosition(bgm->get_decode_channel(), BASS_POS_BYTE); - BASS_ChannelSetPosition(hcastream.get_decode_channel(), pos / 2, BASS_POS_BYTE); + BASS_ChannelSetPosition(idols[index]->get_decode_channel(), pos / 2, BASS_POS_BYTE); + BASS_Mixer_StreamAddChannel(idol_mix_stream, idols[index]->get_decode_channel(), 0); + fuzzyAdjust(); + if(was_playing) + { + BASS_ChannelPlay(play_stream, FALSE); + } } - // Cleanup wave and channel data - BASS_Mixer_ChannelRemove(idols[index]->get_decode_channel()); - BASS_Mixer_ChannelRemove(idols_oneshot[index]->get_decode_channel()); - - *idols[index] = std::move(hcastream); - *idols_oneshot[index] = std::move(hcastream2); - - fuzzyAdjust(); - BASS_Mixer_StreamAddChannel(idol_mix_stream, idols[index]->get_decode_channel(), 0); } void MainWindow::setUnit(bool checked) @@ -482,42 +483,35 @@ std::string MainWindow::filterCommand(const std::string &command) { std::string filtered; filtered.reserve(13); + std::set singing_set; for(unsigned int i = 0; i < command.length(); ++i) { int idolnum = command[i] - 48; - if((idolnum == 'x' - 48) || (idolnum >= 0 && idolnum < NUM_IDOLS && idol_to_type[current_idols[idolnum]] & ALL)) + if(idolnum == 'x' - 48) { filtered += command[i]; } + else + { + while(idolnum >= 0 && idolnum < unit_size) + { + if(singing_set.find(idolnum) == singing_set.end() && idol_to_type[current_idols[idolnum]] & ALL) + { + singing_set.insert(idolnum); + filtered += (char)(idolnum + 48); + break; + } + idolnum = fallback.at(idolnum); + } + } } return filtered; } -void MainWindow::applyOneshotCommand(const std::string &command) -{ - static double volTable[] = { 0.75, 0.61, 0.51, 0.469, 0.413, 0.373, 0.345, 0.326, 0.308, 0.29, 0.271, 0.259, 0.246 }; - // Game volume table = { 1, 0.89, 0.71, 0.67, 0.59 }; - static double panTable[NUM_IDOLS][NUM_IDOLS] = {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {-0.25, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {-0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - { -0.4, -0.2, 0.2, 0.4, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - { -0.5, -0.25, 0, 0.25, 0.5, 0, 0, 0, 0, 0, 0, 0, 0}, - { -0.5, -0.3, -0.1, 0.1, 0.3, 0.5, 0, 0, 0, 0, 0, 0, 0}, - { -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0, 0, 0, 0, 0, 0}, - { -0.6, -0.5, -0.3, -0.15, 0.15, 0.3, 0.5, 0.6, 0, 0, 0, 0, 0}, - { -0.7, -0.55, -0.4, -0.2, 0, 0.2, 0.4, 0.55, 0.7, 0, 0, 0, 0}, - { -0.8, -0.6, -0.5, -0.3, -0.15, 0.15, 0.3, 0.5, 0.6, 0.8, 0, 0, 0}, - { -0.9, -0.7, -0.55, -0.4, -0.2, 0, 0.2, 0.4, 0.55, 0.7, 0.9, 0, 0}, - { -1, -0.8, -0.7, -0.6, -0.4, -0.2, 0.2, 0.4, 0.6, 0.7, 0.8, 1, 0}, - { -1, -0.9, -0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0.8, 0.9, 1}}; +void MainWindow::applyOneshotCommand(QWORD pos, const std::string &command) +{ QWORD bgmpos = BASS_ChannelGetPosition(bgm->get_decode_channel(), BASS_POS_BYTE) / 4; - std::istringstream iss(command); - std::string com; - QWORD ospos = 0, mappos = 0; - iss >> ospos; - iss.get(); - mappos = (bgmpos - ospos) * 2; - std::getline(iss, com); + QWORD mappos = (bgmpos - pos) * 2; for(int i = 0; i < unit_size; ++i) { @@ -525,13 +519,19 @@ void MainWindow::applyOneshotCommand(const std::string &command) BASS_ChannelSetPosition(idols_oneshot[i]->get_decode_channel(), 0, BASS_POS_BYTE); } - std::string filtered = filterCommand(com); + std::string filtered = filterCommand(command); int numSinging = filtered.length(); size_t n = std::count(filtered.begin(), filtered.end(), 'x'); for(int i = 0; i < numSinging; ++i) { if(filtered[i] != 'x') { + bool was_playing = true; + if(BASS_ChannelIsActive(play_stream) == BASS_ACTIVE_PAUSED) + { + was_playing = false; + } + BASS_ChannelPause(play_stream); QWORD len = BASS_ChannelGetLength(idols_oneshot[filtered[i] - 48]->get_decode_channel(), BASS_POS_BYTE); if(mappos >= 0 && mappos < len) { @@ -540,6 +540,10 @@ void MainWindow::applyOneshotCommand(const std::string &command) BASS_ChannelSetPosition(idols_oneshot[filtered[i] - 48]->get_decode_channel(), mappos, BASS_POS_BYTE); BASS_Mixer_StreamAddChannel(idol_oneshot_stream, idols_oneshot[filtered[i] - 48]->get_decode_channel(), 0); } + if(was_playing) + { + BASS_ChannelPlay(play_stream, FALSE); + } } } } @@ -554,21 +558,7 @@ void MainWindow::applyCommand(const std::string &command) } return; } - static double volTable[] = { 0.75, 0.61, 0.51, 0.469, 0.413, 0.373, 0.345, 0.326, 0.308, 0.29, 0.271, 0.259, 0.246 }; - // Game volume table = { 1, 0.89, 0.71, 0.67, 0.59 }; - static double panTable[NUM_IDOLS][NUM_IDOLS] = {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {-0.25, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {-0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - { -0.4, -0.2, 0.2, 0.4, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - { -0.5, -0.25, 0, 0.25, 0.5, 0, 0, 0, 0, 0, 0, 0, 0}, - { -0.5, -0.3, -0.1, 0.1, 0.3, 0.5, 0, 0, 0, 0, 0, 0, 0}, - { -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0, 0, 0, 0, 0, 0}, - { -0.6, -0.5, -0.3, -0.15, 0.15, 0.3, 0.5, 0.6, 0, 0, 0, 0, 0}, - { -0.7, -0.55, -0.4, -0.2, 0, 0.2, 0.4, 0.55, 0.7, 0, 0, 0, 0}, - { -0.8, -0.6, -0.5, -0.3, -0.15, 0.15, 0.3, 0.5, 0.6, 0.8, 0, 0, 0}, - { -0.9, -0.7, -0.55, -0.4, -0.2, 0, 0.2, 0.4, 0.55, 0.7, 0.9, 0, 0}, - { -1, -0.8, -0.7, -0.6, -0.4, -0.2, 0.2, 0.4, 0.6, 0.7, 0.8, 1, 0}, - { -1, -0.9, -0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0.8, 0.9, 1}}; + switch(command[0]) { case 'P': @@ -580,9 +570,6 @@ void MainWindow::applyCommand(const std::string &command) case 'A': applyCommand(findIdolsOfType(ANGEL)); break; - case 'O': - applyOneshotCommand(command.substr(1, command.length() - 1)); - break; default: for(int i = 0; i < unit_size; ++i) { @@ -611,7 +598,7 @@ void CALLBACK MainWindow::dispatchEvent(HSYNC, DWORD channel, DWORD, void *user) void CALLBACK MainWindow::dispatchOneshotEvent(HSYNC, DWORD channel, DWORD, void *user) { QWORD pos = BASS_ChannelGetPosition(channel, BASS_POS_BYTE) / 4; - ((MainWindow*)user)->applyCommand(((MainWindow*)user)->oneshot_event_list[pos]); + ((MainWindow*)user)->applyOneshotCommand(pos, ((MainWindow*)user)->oneshot_event_list[pos]); } void MainWindow::addSyncEvents() @@ -650,7 +637,7 @@ void MainWindow::fuzzyAdjust() { --it; } - applyCommand(it->second); + applyOneshotCommand(it->first, it->second); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 66e8e83..af5fcec 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -38,7 +38,7 @@ class MainWindow : public QMainWindow void addSyncEvents(); void fuzzyAdjust(); void applyCommand(const std::string &command); - void applyOneshotCommand(const std::string &command); + void applyOneshotCommand(QWORD pos, const std::string &command); void saveConfig(const std::string &filename); bool loadConfig(std::unordered_map &config, const std::string &filename); void loadConfigFile(const std::string &filename); diff --git a/src/utils.h b/src/utils.h index d1c37f5..758cb42 100644 --- a/src/utils.h +++ b/src/utils.h @@ -4,7 +4,6 @@ #include #define NUM_IDOLS 13 -#define MACHIUKE 4575494 #define ALL 0x15 #define PRINCESS 0x10 #define FAIRY 0x4 @@ -14,3 +13,22 @@ void export_to_wav(HSTREAM mix_stream, const std::string& filename, const std::m void parse_control_file(std::map& event_list, const std::string & control_file); void parse_names(std::unordered_map& filename_to_readable, const std::string& infile, QComboBox* sel[], int size); void parse_types(std::unordered_map& filename_to_type, const std::string& infile); + +static const double volTable[] = { 0.75, 0.61, 0.51, 0.469, 0.413, 0.373, 0.345, 0.326, 0.308, 0.29, 0.271, 0.259, 0.246 }; +// Game volume table = { 1, 0.89, 0.71, 0.67, 0.59 }; +static const double panTable[NUM_IDOLS][NUM_IDOLS] = {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {-0.25, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {-0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -0.4, -0.2, 0.2, 0.4, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { -0.5, -0.25, 0, 0.25, 0.5, 0, 0, 0, 0, 0, 0, 0, 0}, + { -0.5, -0.3, -0.1, 0.1, 0.3, 0.5, 0, 0, 0, 0, 0, 0, 0}, + { -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0, 0, 0, 0, 0, 0}, + { -0.6, -0.5, -0.3, -0.15, 0.15, 0.3, 0.5, 0.6, 0, 0, 0, 0, 0}, + { -0.7, -0.55, -0.4, -0.2, 0, 0.2, 0.4, 0.55, 0.7, 0, 0, 0, 0}, + { -0.8, -0.6, -0.5, -0.3, -0.15, 0.15, 0.3, 0.5, 0.6, 0.8, 0, 0, 0}, + { -0.9, -0.7, -0.55, -0.4, -0.2, 0, 0.2, 0.4, 0.55, 0.7, 0.9, 0, 0}, + { -1, -0.8, -0.7, -0.6, -0.4, -0.2, 0.2, 0.4, 0.6, 0.7, 0.8, 1, 0}, + { -1, -0.9, -0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0.8, 0.9, 1}}; +static const std::unordered_map fallback{{11, 3}, { 9, 1}, { 7, 3}, { 5, 1}, + { 3, 1}, { 1, 0}, { 0,-1}, { 2, 0}, { 4, 2}, + { 6, 2}, { 8, 4}, {10, 2}, {12, 4}};