From 9f5f4e684357c542e95613e2f7dd9e47e66b6276 Mon Sep 17 00:00:00 2001 From: kichikuou Date: Sat, 16 May 2020 12:20:45 +0900 Subject: [PATCH] Add Windows native implementaton of MAKO The MIDI synthesizer bundled with SDL_Mixer doesn't sound as good as Windows' built-in software synthesizer. This adds Windows native implementation of MAKO as win/mako.cpp. It plays MIDI by writing out SMF to a temporary file and play it with MCI. CD emulation (--playlist) is also implemented using MCI. The PCM implementation is copyed from TOTO's original Win32 impl. This removes SDL_mixer dependency in Windows. Fixes #1. --- src/CMakeLists.txt | 19 ++- src/sys/mako.cpp | 12 +- src/sys/mako.h | 11 +- src/sys/nact_input.cpp | 19 ++- src/win/mako.cpp | 311 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 349 insertions(+), 23 deletions(-) create mode 100644 src/win/mako.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ffa0458..091457b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,23 +47,22 @@ else() # NOT (ANDROID OR EMSCRIPTEN) include(FindPkgConfig) pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2) pkg_check_modules(SDL2TTF REQUIRED IMPORTED_TARGET SDL2_ttf) - pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer) + target_link_libraries(system3 PRIVATE PkgConfig::SDL2 PkgConfig::SDL2TTF) set(DEFAULT_FONT_PATH ${CMAKE_INSTALL_PREFIX}/share/system3/fonts/) - target_sources(system3 PRIVATE sys/mako.cpp) - if (CMAKE_SYSTEM_NAME STREQUAL "Windows") - target_sources(system3 PRIVATE win/nact_win.cpp res3/Script1.rc) - target_link_libraries(system3 PRIVATE winmm) - else() - target_sources(system3 PRIVATE linux/nact_linux.cpp) - endif() - - target_link_libraries(system3 PRIVATE PkgConfig::SDL2 PkgConfig::SDL2TTF PkgConfig::SDL2MIXER) install(TARGETS system3 RUNTIME DESTINATION bin) install(FILES ../fonts/MTLc3m.ttf ../fonts/MTLc3m.ttf.license DESTINATION share/system3/fonts) + if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + target_sources(system3 PRIVATE win/nact_win.cpp win/mako.cpp res3/Script1.rc) + target_link_libraries(system3 PRIVATE winmm) + else() + pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer) + target_sources(system3 PRIVATE linux/nact_linux.cpp sys/mako.cpp) + target_link_libraries(system3 PRIVATE PkgConfig::SDL2MIXER) + endif() endif() target_sources(system3 PRIVATE diff --git a/src/sys/mako.cpp b/src/sys/mako.cpp index a265146..4566db7 100644 --- a/src/sys/mako.cpp +++ b/src/sys/mako.cpp @@ -16,12 +16,18 @@ #define MIX_INIT_MID MIX_INIT_FLUIDSYNTH #endif +namespace { + +std::vector smf; +Mix_Music *mix_music; +Mix_Chunk *mix_chunk; + +} // namespace + MAKO::MAKO(NACT* parent, const char* playlist) : current_music(0), next_loop(0), - nact(parent), - mix_music(NULL), - mix_chunk(NULL) + nact(parent) { int mix_init_flags = MIX_INIT_MID; diff --git a/src/sys/mako.h b/src/sys/mako.h index 1ad167e..b53f027 100644 --- a/src/sys/mako.h +++ b/src/sys/mako.h @@ -14,10 +14,6 @@ #include "../common.h" #include "nact.h" -#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) -#include -#endif - class MAKO { public: @@ -33,6 +29,10 @@ public: void stop_pcm(); bool check_pcm(); +#ifdef _WIN32 + void on_mci_notify(SDL_SysWMmsg* msg); +#endif + // AMUS.DAT, AMSE.DAT char amus[16]; char amse[16]; @@ -48,9 +48,6 @@ private: bool load_playlist(const char* path); std::vector playlist; - std::vector smf; - Mix_Music *mix_music; - Mix_Chunk *mix_chunk; #endif }; diff --git a/src/sys/nact_input.cpp b/src/sys/nact_input.cpp index 511e775..8da5854 100644 --- a/src/sys/nact_input.cpp +++ b/src/sys/nact_input.cpp @@ -4,13 +4,19 @@ [ NACT - input ] */ +#ifdef _WIN32 +#include +#undef ERROR +#endif +#include #include "nact.h" #include "ags.h" +#include "mako.h" #include "texthook.h" static int mousex, mousey, fingers; -static bool pump_events(AGS* ags) +static bool pump_events(AGS* ags, MAKO* mako) { SDL_Event e; while (SDL_PollEvent(&e)) { @@ -34,6 +40,13 @@ static bool pump_events(AGS* ags) case SDL_APP_DIDENTERFOREGROUND: ags->flush_screen(false); break; + +#ifdef _WIN32 + case SDL_SYSWMEVENT: + if (e.syswm.msg->msg.win.msg == MM_MCINOTIFY) + mako->on_mci_notify(e.syswm.msg); + break; +#endif } } return false; @@ -45,7 +58,7 @@ uint8 NACT::get_key() texthook_keywait(); - if (pump_events(ags)) + if (pump_events(ags, mako)) terminate = true; // キーボード&マウス @@ -90,7 +103,7 @@ uint8 NACT::get_key() void NACT::get_cursor(int* x, int* y) { - if (pump_events(ags)) + if (pump_events(ags, mako)) terminate = true; *x = mousex; *y = mousey; diff --git a/src/win/mako.cpp b/src/win/mako.cpp new file mode 100644 index 0000000..4e22039 --- /dev/null +++ b/src/win/mako.cpp @@ -0,0 +1,311 @@ +/* + ALICE SOFT SYSTEM 3 for Win32 + + [ MAKO ] +*/ + +#include +#undef ERROR +#include +#include +#include "mako.h" +#include "mako_midi.h" +#include "dri.h" + +extern SDL_Window* g_window; + +namespace { + +const char* mci_geterror(DWORD err) +{ + static char buf[128]; + mciGetErrorString(err, buf, sizeof(buf)); + return buf; +} + +class Music { +public: + static HWND hwnd_notify; + + Music(const char* fname, int loop) : devid(0) { + MCI_OPEN_PARMS open; + DWORD flags = MCI_OPEN_ELEMENT | MCI_WAIT; + open.lpstrElementName = fname; + MCIERROR err = mciSendCommand(0, MCI_OPEN, flags, (DWORD_PTR)&open); + if (err) { + WARNING("MCI_OPEN failed: %s: %s", fname, mci_geterror(err)); + return; + } + devid = open.wDeviceID; + loops = loop; + } + + Music(MAKOMidi& midi, int loop) : devid(0) { + std::vector smf = midi.generate_smf(loop); + char path[MAX_PATH + 1]; + if (!GetTempPath(sizeof(path), path)) { + WARNING("GetTempPath failed: 0x%x", GetLastError()); + return; + } + char fname[MAX_PATH + 1]; + if (!GetTempFileName(path, "mid", 0, fname)) { + WARNING("GetTempFileName failed: 0x%x", GetLastError()); + return; + } + FILE* fp = fopen(fname, "wb"); + if (!fp) { + WARNING("Cannot open %s: %s", fname, strerror(errno)); + return; + } + tmpfile = fname; // Delete this file in destructor + if (fwrite(smf.data(), smf.size(), 1, fp) != 1) { + WARNING("Cannot write to %s: %s", fname, strerror(errno)); + fclose(fp); + return; + } + fclose(fp); + + DWORD flags = MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_WAIT; + MCI_OPEN_PARMS open; + open.lpstrElementName = fname; + open.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_SEQUENCER; + MCIERROR err = mciSendCommand(0, MCI_OPEN, flags, (DWORD_PTR)&open); + if (err) { + WARNING("MCI_OPEN failed: %s: %s", fname, mci_geterror(err)); + return; + } + devid = open.wDeviceID; + loops = loop ? 1 : 0; + } + + ~Music() { + close(); + if (!tmpfile.empty()) + DeleteFile(tmpfile.c_str()); + } + + void close() { + if (devid) { + mciSendCommand(devid, MCI_CLOSE, 0, 0); + devid = 0; + } + } + + bool play() { + if (!devid) + return false; + MCI_PLAY_PARMS play; + play.dwCallback = (DWORD_PTR)hwnd_notify; + play.dwFrom = 0; + DWORD flags = MCI_FROM | (hwnd_notify ? MCI_NOTIFY : 0); + MCIERROR err = mciSendCommand(devid, MCI_PLAY, flags, (DWORD_PTR)&play); + if (err) { + WARNING("MCI_PLAY failed: %s", mci_geterror(err)); + close(); + return false; + } + return true; + } + + bool is_playing() { + if (!devid) + return false; + MCI_STATUS_PARMS stat; + stat.dwItem = MCI_STATUS_MODE; + MCIERROR err = mciSendCommand(devid, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&stat); + if (err) { + NOTICE("MCI_STATUS failed: %s", mci_geterror(err)); + return false; + } + return stat.dwReturn == MCI_MODE_PLAY; + } + + bool on_mci_notify(SDL_SysWMmsg* msg) { + if (msg->msg.win.lParam != devid) + return true; + switch (msg->msg.win.wParam) { + case MCI_NOTIFY_FAILURE: + close(); + return false; + case MCI_NOTIFY_SUCCESSFUL: + if (loops && --loops == 0) { + close(); + return false; + } + return play(); + } + return true; + } + +private: + MCIDEVICEID devid; + int loops; // number of times to play, 0 for infinite loop + std::string tmpfile; +}; + +Music *music; +HWND Music::hwnd_notify; +uint8* wav_buffer; + +} // namespace + +MAKO::MAKO(NACT* parent, const char* playlist) : + current_music(0), + next_loop(0), + nact(parent) +{ + if (playlist) + load_playlist(playlist); + + strcpy_s(amus, 16, "AMUS.DAT"); + strcpy_s(amse, 16, "AMSE.DAT"); // 実際には使わない + + for(int i = 1; i <= 99; i++) { + cd_track[i] = 0; + } + + SDL_SysWMinfo info; + if (!SDL_GetWindowWMInfo(g_window, &info)) { + WARNING("SDL_GetWindowWMInfo failed: %s", SDL_GetError()); + } else { + Music::hwnd_notify = info.info.win.window; + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + } +} + +MAKO::~MAKO() +{ + stop_music(); + stop_pcm(); +} + +bool MAKO::load_playlist(const char* path) +{ + FILE* fp = fopen(path, "rt"); + if (!fp) { + WARNING("Cannot open %s", path); + return false; + } + char buf[256]; + while (fgets(buf, sizeof(buf) - 1, fp)) { + char *p = &buf[strlen(buf) - 1]; + if (*p == '\n') + *p = '\0'; + playlist.push_back(buf[0] ? strdup(buf) : NULL); + } + fclose(fp); + return true; +} + +void MAKO::play_music(int page) +{ + if (current_music == page) + return; + + stop_music(); + + int track = page < 100 ? cd_track[page] : 0; + if (track) { + if (track >= playlist.size() || !playlist[track]) + return; + music = new Music(playlist[track], next_loop); + } else { + MAKOMidi midi(nact, amus); + if (!midi.load_mml(page)) + return; + midi.load_mda(page); + music = new Music(midi, next_loop); + } + if (music->play()) + current_music = page; +} + +void MAKO::stop_music() +{ + if (music) { + delete music; + music = nullptr; + } + current_music = 0; +} + +bool MAKO::check_music() +{ + return music && music->is_playing(); +} + +void MAKO::get_mark(int* mark, int* loop) +{ + // TODO: fix + *mark = 0; + *loop = 0; +} + +void MAKO::play_pcm(int page, bool loop) +{ + static char header[44] = { + 'R' , 'I' , 'F' , 'F' , 0x00, 0x00, 0x00, 0x00, 'W' , 'A' , 'V' , 'E' , 'f' , 'm' , 't' , ' ' , + 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x40, 0x1f, 0x00, 0x00, 0x40, 0x1f, 0x00, 0x00, + 0x01, 0x00, 0x08, 0x00, 'd' , 'a' , 't' , 'a' , 0x00, 0x00, 0x00, 0x00 + }; + + stop_pcm(); + + uint8* buffer = NULL; + int size; + DRI* dri = new DRI(); + + if ((buffer = dri->load("AWAV.DAT", page, &size)) != NULL) { + // WAV形式 (Only You) + wav_buffer = buffer; + } else if ((buffer = dri->load("AMSE.DAT", page, &size)) != NULL) { + // AMSE形式 (乙女戦記) + int total = (size - 12) * 2 + 0x24; + int samples = (size - 12) * 2; + + wav_buffer = (uint8*)malloc(total + 8); + memcpy(wav_buffer, header, 44); + wav_buffer[ 4] = (total >> 0) & 0xff; + wav_buffer[ 5] = (total >> 8) & 0xff; + wav_buffer[ 6] = (total >> 16) & 0xff; + wav_buffer[ 7] = (total >> 24) & 0xff; + wav_buffer[40] = (samples >> 0) & 0xff; + wav_buffer[41] = (samples >> 8) & 0xff; + wav_buffer[42] = (samples >> 16) & 0xff; + wav_buffer[43] = (samples >> 24) & 0xff; + for(int i = 12, p = 44; i < size; i++) { + wav_buffer[p++] = buffer[i] & 0xf0; + wav_buffer[p++] = (buffer[i] & 0x0f) << 4; + } + free(buffer); + } + PlaySound((LPCTSTR)wav_buffer, NULL, SND_ASYNC | SND_MEMORY | (loop ? SND_LOOP : 0)); +} + +void MAKO::stop_pcm() +{ + PlaySound(NULL, NULL, SND_PURGE); + if (wav_buffer) { + free(wav_buffer); + wav_buffer = nullptr; + } +} + +bool MAKO::check_pcm() +{ + // 再生中でtrue + static const char null_wav[45] = { + 'R' , 'I' , 'F' , 'F' , 0x25, 0x00, 0x00, 0x00, 'W' , 'A' , 'V' , 'E' , 'f' , 'm' , 't' , ' ' , + 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x40, 0x1f, 0x00, 0x00, 0x40, 0x1f, 0x00, 0x00, + 0x01, 0x00, 0x08, 0x00, 'd' , 'a' , 't' , 'a' , 0x01, 0x00, 0x00, 0x00, 0x00 + }; + return !PlaySound(null_wav, NULL, SND_ASYNC | SND_MEMORY | SND_NOSTOP); +} + +void MAKO::on_mci_notify(SDL_SysWMmsg* msg) +{ + if (!music) + return; + if (!music->on_mci_notify(msg)) + current_music = 0; +}