mirror of
https://github.com/kichikuou/system3-sdl2.git
synced 2026-09-30 02:48:10 +03:00
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.
This commit is contained in:
+9
-10
@@ -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
|
||||
|
||||
+9
-3
@@ -16,12 +16,18 @@
|
||||
#define MIX_INIT_MID MIX_INIT_FLUIDSYNTH
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<uint8> 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;
|
||||
|
||||
|
||||
+4
-7
@@ -14,10 +14,6 @@
|
||||
#include "../common.h"
|
||||
#include "nact.h"
|
||||
|
||||
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__)
|
||||
#include <SDL_mixer.h>
|
||||
#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<const char*> playlist;
|
||||
std::vector<uint8> smf;
|
||||
Mix_Music *mix_music;
|
||||
Mix_Chunk *mix_chunk;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
+16
-3
@@ -4,13 +4,19 @@
|
||||
[ NACT - input ]
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#undef ERROR
|
||||
#endif
|
||||
#include <SDL_syswm.h>
|
||||
#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;
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
ALICE SOFT SYSTEM 3 for Win32
|
||||
|
||||
[ MAKO ]
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#undef ERROR
|
||||
#include <string>
|
||||
#include <SDL_syswm.h>
|
||||
#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<uint8> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user