diff --git a/games/sdl2-doom/Makefile b/games/sdl3-doom/Makefile similarity index 53% rename from games/sdl2-doom/Makefile rename to games/sdl3-doom/Makefile index cfed1794..13b46039 100644 --- a/games/sdl2-doom/Makefile +++ b/games/sdl3-doom/Makefile @@ -1,13 +1,13 @@ include $(TOPDIR)/rules.mk -PKG_NAME:=sdl2-doom -PKG_SOURCE_DATE:=2024-05-31 +PKG_NAME:=sdl3-doom +PKG_SOURCE_DATE:=2025-01-26 PKG_RELEASE:=1 PKG_SOURCE_PROTO:=git -PKG_SOURCE_URL:=https://github.com/gen04177/sdl2-doom -PKG_SOURCE_VERSION:=fbbfb86b34af2cf267a9287cd91a5d1575fcfe58 -PKG_MIRROR_HASH:=b0f6b325c29c1bfb427e58190c9d560be7f99491656c219f10e4d72bc33c4ed8 +PKG_SOURCE_URL:=https://github.com/raydelto/sdl3_doom +PKG_SOURCE_VERSION:=b271b6d72900d18ee3ef34c1608fe7e20a0c0ad3 +PKG_MIRROR_HASH:=0d65720a1d68e63721cca360ebc2614fc2ad2e624df1e60a293a24ce67279373 PKG_MAINTAINER:=Daniel Golle PKG_LICENSE:=GPL-2.0-or-later @@ -18,27 +18,27 @@ PKG_BUILD_FLAGS:=gc-sections lto include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/cmake.mk -define Package/sdl2-doom +define Package/sdl3-doom SECTION:=games CATEGORY:=Games - TITLE:=SDL2 Doom - URL:=https://github.com/AlexOberhofer/sdl2-doom - DEPENDS:=+libstdcpp +libsdl2 +libsdl2-mixer + TITLE:=SDL3 Doom + URL:=https://github.com/raydelto/sdl3_doom + DEPENDS:=+libstdcpp +libsdl3 +libsdl3-mixer endef -define Package/sdl2-doom/description - This is a source port of the ID Software source release of DOOM. +define Package/sdl3-doom/description + This is a source port of the ID Software source release of DOOM to SDL3. Using low-resolution software rendering designed in 1993 by idSoftware for (from today's perspective) very slow CPUs it runs with good performance even on low-end devices. endef -define Package/sdl2-doom/install +define Package/sdl3-doom/install $(INSTALL_DIR) $(1)/usr/bin - $(INSTALL_BIN) $(PKG_BUILD_DIR)/sdl2-doom $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/sdl3-doom $(1)/usr/bin endef define Build/Install endef -$(eval $(call BuildPackage,sdl2-doom)) +$(eval $(call BuildPackage,sdl3-doom)) diff --git a/games/sdl3-doom/patches/0001-i_sdlmusic-port-music-backend-to-the-SDL3_mixer-3.2-.patch b/games/sdl3-doom/patches/0001-i_sdlmusic-port-music-backend-to-the-SDL3_mixer-3.2-.patch new file mode 100644 index 00000000..9dd0e2d2 --- /dev/null +++ b/games/sdl3-doom/patches/0001-i_sdlmusic-port-music-backend-to-the-SDL3_mixer-3.2-.patch @@ -0,0 +1,287 @@ +From 0898b8bc17127829ea44325985e70c49c800ff21 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 9 Jun 2026 16:25:51 +0100 +Subject: [PATCH] i_sdlmusic: port music backend to the SDL3_mixer 3.2 MIX_* + API + +The Mix_* SDL_mixer API used by this port was removed in SDL_mixer 3.2.0. +Port the music backend to the new track-based MIX_* API: a single +MIX_Mixer device and MIX_Track, MIX_LoadAudio for songs, MIX_PlayTrack +with the MIX_PROP_PLAY_LOOPS_NUMBER property for looping, MIX_SetTrackGain +for volume and MIX_GetTrackPlaybackPosition for the substitute-music loop +points (replacing the removed post-mix effect callback). + +Signed-off-by: Daniel Golle +--- + src/i_sdlmusic.c | 107 ++++++++++++++++++++++++----------------------- + 1 file changed, 55 insertions(+), 52 deletions(-) + +--- a/src/i_sdlmusic.c ++++ b/src/i_sdlmusic.c +@@ -119,12 +119,13 @@ static char *temp_timidity_cfg = NULL; + static boolean playing_substitute = false; + static file_metadata_t file_metadata; + +-// Position (in samples) that we have reached in the current track. +-// This is updated by the TrackPositionCallback function. +-static unsigned int current_track_pos; ++// SDL3_mixer mixer device and the single track used for music playback. ++static MIX_Mixer *music_mixer = NULL; ++static MIX_Track *music_track = NULL; ++static int music_freq = 44100; + + // Currently playing music track. +-static Mix_Music *current_track_music = NULL; ++static MIX_Audio *current_track_music = NULL; + + // If true, the currently playing track is being played on loop. + static boolean current_track_loop; +@@ -852,12 +853,16 @@ static void I_SDL_ShutdownMusic(void) + { + if (music_initialized) + { +- Mix_HaltMusic(); ++ MIX_StopTrack(music_track, 0); + music_initialized = false; + + if (sdl_was_initialized) + { +- Mix_CloseAudio(); ++ MIX_DestroyTrack(music_track); ++ music_track = NULL; ++ MIX_DestroyMixer(music_mixer); ++ music_mixer = NULL; ++ MIX_Quit(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + sdl_was_initialized = false; + } +@@ -866,17 +871,7 @@ static void I_SDL_ShutdownMusic(void) + + static boolean SDLIsInitialized(void) + { +- int freq, channels; +- SDL_AudioFormat format; +- +- return Mix_QuerySpec(&freq, &format, &channels) != 0; +-} +- +-// Callback function that is invoked to track current track position. +-void TrackPositionCallback(int chan, void *stream, int len, void *udata) +-{ +- // Position is doubled up twice: for 16-bit samples and for stereo. +- current_track_pos += len / 4; ++ return music_mixer != NULL; + } + + // Initialize music subsystem +@@ -928,20 +923,29 @@ static boolean I_SDL_InitMusic(void) + } + else + { +- const SDL_AudioSpec spec = {SDL_AUDIO_S16, 2, 1024}; ++ const SDL_AudioSpec spec = {SDL_AUDIO_S16, 2, 44100}; + if (!SDL_Init(SDL_INIT_AUDIO)) + { + fprintf(stderr, "Unable to set up sound.\n"); + } +- else if (!Mix_OpenAudio(0, &spec)) ++ else if (!MIX_Init()) + { +- fprintf(stderr, "Error initializing SDL_mixer: %s\n", ++ fprintf(stderr, "Error initializing SDL3_mixer: %s\n", + SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + } ++ else if (!(music_mixer = MIX_CreateMixerDevice( ++ SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec))) ++ { ++ fprintf(stderr, "Error initializing SDL3_mixer: %s\n", ++ SDL_GetError()); ++ MIX_Quit(); ++ SDL_QuitSubSystem(SDL_INIT_AUDIO); ++ } + else + { +- Mix_PauseAudio(0); ++ music_track = MIX_CreateTrack(music_mixer); ++ music_freq = spec.freq; + + sdl_was_initialized = true; + music_initialized = true; +@@ -961,9 +965,6 @@ static boolean I_SDL_InitMusic(void) + fprintf(stderr, "SDL3_Mixer does not support external music playback.\n"); + } + +- // Register an effect function to track the music position. +- Mix_RegisterEffect(MIX_CHANNEL_POST, TrackPositionCallback, NULL, NULL); +- + // If we're in GENMIDI mode, try to load sound packs. + if (snd_musicdevice == SNDDEVICE_GENMIDI) + { +@@ -980,18 +981,18 @@ static boolean I_SDL_InitMusic(void) + + static void UpdateMusicVolume(void) + { +- int vol; ++ float gain; + + if (musicpaused) + { +- vol = 0; ++ gain = 0.0f; + } + else + { +- vol = (current_music_volume * MIX_MAX_VOLUME) / 127; ++ gain = (float) current_music_volume / 127.0f; + } + +- Mix_VolumeMusic(vol); ++ MIX_SetTrackGain(music_track, gain); + } + + // Set music volume (0 - 127) +@@ -1020,7 +1021,7 @@ static void I_SDL_PlaySong(void *handle, + return; + } + +- current_track_music = (Mix_Music *) handle; ++ current_track_music = (MIX_Audio *) handle; + current_track_loop = looping; + + if (looping) +@@ -1029,18 +1030,23 @@ static void I_SDL_PlaySong(void *handle, + } + else + { +- loops = 1; ++ loops = 0; + } + + // Don't loop when playing substitute music, as we do it + // ourselves instead. + if (playing_substitute && file_metadata.valid) + { +- loops = 1; +- current_track_pos = 0; // start of track ++ loops = 0; + } + +- Mix_PlayMusic(current_track_music, loops); ++ { ++ SDL_PropertiesID opts = SDL_CreateProperties(); ++ SDL_SetNumberProperty(opts, MIX_PROP_PLAY_LOOPS_NUMBER, loops); ++ MIX_SetTrackAudio(music_track, current_track_music); ++ MIX_PlayTrack(music_track, opts); ++ SDL_DestroyProperties(opts); ++ } + } + + static void I_SDL_PauseSong(void) +@@ -1074,14 +1080,14 @@ static void I_SDL_StopSong(void) + return; + } + +- Mix_HaltMusic(); ++ MIX_StopTrack(music_track, 0); + playing_substitute = false; + current_track_music = NULL; + } + + static void I_SDL_UnRegisterSong(void *handle) + { +- Mix_Music *music = (Mix_Music *) handle; ++ MIX_Audio *music = (MIX_Audio *) handle; + + if (!music_initialized) + { +@@ -1093,7 +1099,7 @@ static void I_SDL_UnRegisterSong(void *h + return; + } + +- Mix_FreeMusic(music); ++ MIX_DestroyAudio(music); + } + + // Determine whether memory block is a .mid file +@@ -1132,7 +1138,7 @@ static boolean ConvertMus(byte *musdata, + static void *I_SDL_RegisterSong(void *data, int len) + { + char *filename; +- Mix_Music *music; ++ MIX_Audio *music; + + if (!music_initialized) + { +@@ -1146,7 +1152,7 @@ static void *I_SDL_RegisterSong(void *da + + if (filename != NULL) + { +- music = Mix_LoadMUS(filename); ++ music = MIX_LoadAudio(music_mixer, filename, false); + + if (music == NULL) + { +@@ -1185,7 +1191,7 @@ static void *I_SDL_RegisterSong(void *da + // by now, but Mix_SetMusicCMD() only works with Mix_LoadMUS(), so + // we have to generate a temporary file. + +- music = Mix_LoadMUS(filename); ++ music = MIX_LoadAudio(music_mixer, filename, false); + + if (music == NULL) + { +@@ -1217,19 +1223,13 @@ static boolean I_SDL_MusicIsPlaying(void + return false; + } + +- return Mix_PlayingMusic(); ++ return MIX_TrackPlaying(music_track); + } + + // Get position in substitute music track, in seconds since start of track. + static double GetMusicPosition(void) + { +- unsigned int music_pos; +- int freq; +- +- Mix_QuerySpec(&freq, NULL, NULL); +- music_pos = current_track_pos; +- +- return (double) music_pos / freq; ++ return (double) MIX_GetTrackPlaybackPosition(music_track) / music_freq; + } + + static void RestartCurrentTrack(void) +@@ -1244,15 +1244,18 @@ static void RestartCurrentTrack(void) + // If the track finished we need to restart it. + if (current_track_music != NULL) + { +- Mix_PlayMusic(current_track_music, 1); ++ SDL_PropertiesID opts = SDL_CreateProperties(); ++ SDL_SetNumberProperty(opts, MIX_PROP_PLAY_LOOPS_NUMBER, 0); ++ MIX_SetTrackAudio(music_track, current_track_music); ++ MIX_PlayTrack(music_track, opts); ++ SDL_DestroyProperties(opts); + } + +- Mix_SetMusicPosition(start); +- current_track_pos = file_metadata.start_time; ++ MIX_SetTrackPlaybackPosition(music_track, (Sint64) (start * music_freq)); + } + else + { +- Mix_HaltMusic(); ++ MIX_StopTrack(music_track, 0); + current_track_music = NULL; + playing_substitute = false; + } +@@ -1274,7 +1277,7 @@ static void I_SDL_PollMusic(void) + } + + // Have we reached the actual end of track (not loop end)? +- if (!Mix_PlayingMusic() && current_track_loop) ++ if (!MIX_TrackPlaying(music_track) && current_track_loop) + { + RestartCurrentTrack(); + } diff --git a/games/sdl3-doom/patches/0002-cmake-link-against-the-exported-SDL3-and-SDL3_mixer-.patch b/games/sdl3-doom/patches/0002-cmake-link-against-the-exported-SDL3-and-SDL3_mixer-.patch new file mode 100644 index 00000000..01f64dd5 --- /dev/null +++ b/games/sdl3-doom/patches/0002-cmake-link-against-the-exported-SDL3-and-SDL3_mixer-.patch @@ -0,0 +1,27 @@ +From 9accc179f21c54a0ca552617dff243d9fdc3e1bf Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 9 Jun 2026 16:31:56 +0100 +Subject: [PATCH] cmake: link against the exported SDL3 and SDL3_mixer targets + +The link line referenced ${SDL3_LIBRARIES} and ${SDL3_mixer_LIBRARIES}, +variables that the SDL3 / SDL3_mixer 3.2 CMake config packages do not +define. As a result the executable failed to link with a long list of +undefined references to the SDL and MIX_* symbols. + +The config packages instead export the imported targets SDL3::SDL3 and +SDL3_mixer::SDL3_mixer (which also carry the correct include directories +and transitive dependencies). Link against those. + +Signed-off-by: Daniel Golle +--- + CMakeLists.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -21,4 +21,4 @@ add_executable(sdl3-doom src/i_main.c sr + src/r_sky.c src/r_things.c src/sha1.c src/sounds.c src/statdump.c src/st_lib.c src/st_stuff.c src/s_sound.c src/tables.c + src/v_video.c src/wi_stuff.c src/w_checksum.c src/w_file.c src/w_main.c src/w_wad.c src/z_zone.c src/w_file_stdc.c ) + +-target_link_libraries(sdl3-doom PRIVATE ${SDL3_LIBRARIES} ${SDL3_mixer_LIBRARIES}) ++target_link_libraries(sdl3-doom PRIVATE SDL3::SDL3 SDL3_mixer::SDL3_mixer) diff --git a/games/sdl3-doom/patches/0003-i_sdlsound-port-SFX-backend-to-the-SDL3_mixer-3.2-MI.patch b/games/sdl3-doom/patches/0003-i_sdlsound-port-SFX-backend-to-the-SDL3_mixer-3.2-MI.patch new file mode 100644 index 00000000..56a7c6ae --- /dev/null +++ b/games/sdl3-doom/patches/0003-i_sdlsound-port-SFX-backend-to-the-SDL3_mixer-3.2-MI.patch @@ -0,0 +1,389 @@ +From 729b82f23169b13b990a0799d6f7b2b1148fb431 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 9 Jun 2026 16:32:14 +0100 +Subject: [PATCH] i_sdlsound: port SFX backend to the SDL3_mixer 3.2 MIX_* API + +The Mix_* SDL_mixer API used by this port was removed in SDL_mixer 3.2.0. +Port the sound-effects backend to the new track-based MIX_* API, +mirroring the music backend port. + +The fixed pool of 16 mixer channels becomes a pool of 16 MIX_Track +objects rendered by a single MIX_Mixer device. The per-sound Mix_Chunk +embedded in the sound cache is replaced by a raw PCM buffer plus a +MIX_Audio handle wrapping it: + + - struct allocated_sound_s: drop Mix_Chunk chunk; add byte *abuf, + Uint32 alen and MIX_Audio *audio. + - AllocateSound() now returns allocated_sound_t* and only sets up the + raw buffer (abuf/alen, audio = NULL); the converted-PCM writers + (ExpandSoundData_SRC / ExpandSoundData_SDL) write into snd->abuf and + then call the new FinishSound() which wraps the buffer with + MIX_LoadRawAudio(sound_mixer, abuf, alen, &spec). FreeAllocatedSound() + releases it with MIX_DestroyAudio(). + - I_SDL_InitSound: Mix_OpenAudio() -> MIX_Init() + + MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec); the + mixer_freq/format/channels formerly read back via Mix_QuerySpec() are + taken from the spec; Mix_AllocateChannels() becomes a loop of + MIX_CreateTrack(); Mix_PauseAudio() and the SDL_mixer <= 1.2.8 + Mix_SetPanning workaround are dropped. + - I_SDL_ShutdownSound: destroy the tracks, MIX_DestroyMixer() and + MIX_Quit() (replacing Mix_CloseAudio()). + - I_SDL_StartSound: Mix_PlayChannelTimed() -> MIX_SetTrackAudio() + + MIX_PlayTrack(track, 0). + - I_SDL_StopSound: Mix_HaltChannel() -> MIX_StopTrack(track, 0). + - I_SDL_SoundIsPlaying: Mix_Playing() -> MIX_TrackPlaying(). + - I_SDL_UpdateSoundParams: Mix_SetPanning(left, right) (0..255) -> + MIX_SetTrackStereo() with a MIX_StereoGains of left/255 and right/255. + +MIX_MAX_VOLUME is gone; gains are floats in 0.0..1.0. GetSliceSize() is +removed as the new mixer device spec takes a sample rate, not a slice +size, and the function had no other users. + +Signed-off-by: Daniel Golle +--- + src/i_sdlsound.c | 173 +++++++++++++++++++++++------------------------ + 1 file changed, 83 insertions(+), 90 deletions(-) + +--- a/src/i_sdlsound.c ++++ b/src/i_sdlsound.c +@@ -51,15 +51,18 @@ typedef struct allocated_sound_s allocat + struct allocated_sound_s + { + sfxinfo_t *sfxinfo; +- Mix_Chunk chunk; ++ byte *abuf; ++ Uint32 alen; ++ MIX_Audio *audio; + int use_count; + allocated_sound_t *prev, *next; + }; + +-static boolean setpanning_workaround = false; +- + static boolean sound_initialized = false; + ++static MIX_Mixer *sound_mixer = NULL; ++static MIX_Track *sound_tracks[NUM_CHANNELS]; ++ + static sfxinfo_t *channels_playing[NUM_CHANNELS]; + + static int mixer_freq; +@@ -143,7 +146,12 @@ static void FreeAllocatedSound(allocated + + // Keep track of the amount of allocated sound data: + +- allocated_sounds_size -= snd->chunk.alen; ++ allocated_sounds_size -= snd->alen; ++ ++ if (snd->audio != NULL) ++ { ++ MIX_DestroyAudio(snd->audio); ++ } + + free(snd); + } +@@ -201,7 +209,7 @@ static void ReserveCacheSpace(size_t len + + // Allocate a block for a new sound effect. + +-static Mix_Chunk *AllocateSound(sfxinfo_t *sfxinfo, size_t len) ++static allocated_sound_t *AllocateSound(sfxinfo_t *sfxinfo, size_t len) + { + allocated_sound_t *snd; + +@@ -226,12 +234,11 @@ static Mix_Chunk *AllocateSound(sfxinfo_ + + } while (snd == NULL); + +- // Skip past the chunk structure for the audio buffer ++ // The audio buffer immediately follows the structure header. + +- snd->chunk.abuf = (byte *) (snd + 1); +- snd->chunk.alen = len; +- snd->chunk.allocated = 1; +- snd->chunk.volume = MIX_MAX_VOLUME; ++ snd->abuf = (byte *) (snd + 1); ++ snd->alen = len; ++ snd->audio = NULL; + + snd->sfxinfo = sfxinfo; + snd->use_count = 0; +@@ -246,7 +253,25 @@ static Mix_Chunk *AllocateSound(sfxinfo_ + + AllocatedSoundLink(snd); + +- return &snd->chunk; ++ return snd; ++} ++ ++// Wrap the converted PCM buffer of an allocated sound in a MIX_Audio ++// object so that it can be assigned to a track and played back. ++ ++static boolean FinishSound(allocated_sound_t *snd) ++{ ++ const SDL_AudioSpec spec = {mixer_format, mixer_channels, mixer_freq}; ++ ++ snd->audio = MIX_LoadRawAudio(sound_mixer, snd->abuf, snd->alen, &spec); ++ ++ if (snd->audio == NULL) ++ { ++ fprintf(stderr, "FinishSound: %s\n", SDL_GetError()); ++ return false; ++ } ++ ++ return true; + } + + // Lock a sound, to indicate that it may not be freed. +@@ -344,7 +369,7 @@ static boolean ExpandSoundData_SRC(sfxin + uint32_t alen; + int retn; + int16_t *expanded; +- Mix_Chunk *chunk; ++ allocated_sound_t *snd; + + src_data.input_frames = length; + src_data.data_in = malloc(length * sizeof(float)); +@@ -375,14 +400,14 @@ static boolean ExpandSoundData_SRC(sfxin + + alen = src_data.output_frames_gen * 4; + +- chunk = AllocateSound(sfxinfo, src_data.output_frames_gen * 4); ++ snd = AllocateSound(sfxinfo, src_data.output_frames_gen * 4); + +- if (chunk == NULL) ++ if (snd == NULL) + { + return false; + } + +- expanded = (int16_t *) chunk->abuf; ++ expanded = (int16_t *) snd->abuf; + + // Convert the result back into 16-bit integers. + +@@ -436,12 +461,12 @@ static boolean ExpandSoundData_SRC(sfxin + + if (clipped > 0) + { +- fprintf(stderr, "Sound '%s': clipped %u samples (%0.2f %%)\n", ++ fprintf(stderr, "Sound '%s': clipped %u samples (%0.2f %%)\n", + sfxinfo->name, clipped, +- 400.0 * clipped / chunk->alen); ++ 400.0 * clipped / snd->alen); + } + +- return true; ++ return FinishSound(snd); + } + + #endif +@@ -533,10 +558,10 @@ static boolean ExpandSoundData_SDL(sfxin + int samplerate, + int length) + { +- Mix_Chunk *chunk; ++ allocated_sound_t *snd; + uint32_t expanded_length; +- +- // Calculate the length of the expanded version of the sample. ++ ++ // Calculate the length of the expanded version of the sample. + + expanded_length = (uint32_t) ((((uint64_t) length) * mixer_freq) / samplerate); + +@@ -546,15 +571,15 @@ static boolean ExpandSoundData_SDL(sfxin + + // Allocate a chunk in which to expand the sound + +- chunk = AllocateSound(sfxinfo, expanded_length); ++ snd = AllocateSound(sfxinfo, expanded_length); + +- if (chunk == NULL) ++ if (snd == NULL) + { + return false; + } + + // If we can, use the standard / optimized SDL conversion routines. +- Sint16 *expanded = (Sint16 *) chunk->abuf; ++ Sint16 *expanded = (Sint16 *) snd->abuf; + int expand_ratio; + int i; + +@@ -614,7 +639,7 @@ static boolean ExpandSoundData_SDL(sfxin + } + #endif /* #ifdef LOW_PASS_FILTER */ + +- return true; ++ return FinishSound(snd); + } + + // Load and convert a sound effect +@@ -814,17 +839,9 @@ static void I_SDL_UpdateSoundParams(int + if (right < 0) right = 0; + else if (right > 255) right = 255; + +- // SDL_mixer version 1.2.8 and earlier has a bug in the Mix_SetPanning +- // function. A workaround is to call Mix_UnregisterAllEffects for +- // the channel before calling it. This is undesirable as it may lead +- // to the channel volumes resetting briefly. +- +- if (setpanning_workaround) +- { +- Mix_UnregisterAllEffects(handle); +- } ++ MIX_StereoGains gains = {left / 255.0f, right / 255.0f}; + +- Mix_SetPanning(handle, left, right); ++ MIX_SetTrackStereo(sound_tracks[handle], &gains); + } + + // +@@ -865,7 +882,8 @@ static int I_SDL_StartSound(sfxinfo_t *s + + // play sound + +- Mix_PlayChannelTimed(channel, &snd->chunk, 0, -1); ++ MIX_SetTrackAudio(sound_tracks[channel], snd->audio); ++ MIX_PlayTrack(sound_tracks[channel], 0); + + channels_playing[channel] = sfxinfo; + +@@ -883,7 +901,7 @@ static void I_SDL_StopSound(int handle) + return; + } + +- Mix_HaltChannel(handle); ++ MIX_StopTrack(sound_tracks[handle], 0); + + // Sound data is no longer needed; release the + // sound data being used for this channel +@@ -899,7 +917,7 @@ static boolean I_SDL_SoundIsPlaying(int + return false; + } + +- return Mix_Playing(handle); ++ return MIX_TrackPlaying(sound_tracks[handle]); + } + + // +@@ -926,42 +944,25 @@ static void I_SDL_UpdateSound(void) + + static void I_SDL_ShutdownSound(void) + { ++ int i; ++ + if (!sound_initialized) + { + return; + } + +- Mix_CloseAudio(); +- SDL_QuitSubSystem(SDL_INIT_AUDIO); +- +- sound_initialized = false; +-} +- +-// Calculate slice size, based on snd_maxslicetime_ms. +-// The result must be a power of two. +- +-static int GetSliceSize(void) +-{ +- int limit; +- int n; +- +- limit = (snd_samplerate * snd_maxslicetime_ms) / 1000; +- +- // Try all powers of two, not exceeding the limit. +- +- for (n=0;; ++n) ++ for (i = 0; i < NUM_CHANNELS; ++i) + { +- // 2^n <= limit < 2^n+1 ? +- +- if ((1 << (n + 1)) > limit) +- { +- return (1 << n); +- } ++ MIX_DestroyTrack(sound_tracks[i]); ++ sound_tracks[i] = NULL; + } + +- // Should never happen? ++ MIX_DestroyMixer(sound_mixer); ++ sound_mixer = NULL; ++ MIX_Quit(); ++ SDL_QuitSubSystem(SDL_INIT_AUDIO); + +- return 1024; ++ sound_initialized = false; + } + + static boolean I_SDL_InitSound(boolean _use_sfx_prefix) +@@ -982,17 +983,28 @@ static boolean I_SDL_InitSound(boolean _ + fprintf(stderr, "Unable to set up sound.\n"); + return false; + } +- const SDL_AudioSpec spec = {SDL_AUDIO_S16, 2, GetSliceSize()}; ++ const SDL_AudioSpec spec = {SDL_AUDIO_S16, 2, snd_samplerate}; ++ ++ if (!MIX_Init()) ++ { ++ fprintf(stderr, "Error initialising SDL3_mixer: %s\n", SDL_GetError()); ++ return false; ++ } ++ ++ sound_mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec); + +- if (!Mix_OpenAudio(0, &spec)) ++ if (sound_mixer == NULL) + { +- fprintf(stderr, "Error initialising SDL_mixer: %s\n", SDL_GetError()); ++ fprintf(stderr, "Error initialising SDL3_mixer: %s\n", SDL_GetError()); ++ MIX_Quit(); + return false; + } + + ExpandSoundData = ExpandSoundData_SDL; + +- Mix_QuerySpec(&mixer_freq, &mixer_format, &mixer_channels); ++ mixer_freq = spec.freq; ++ mixer_format = spec.format; ++ mixer_channels = spec.channels; + + #ifdef HAVE_LIBSAMPLERATE + if (use_libsamplerate != 0) +@@ -1014,30 +1026,11 @@ static boolean I_SDL_InitSound(boolean _ + } + #endif + +- // SDL_mixer version 1.2.8 and earlier has a bug in the Mix_SetPanning +- // function that can cause the game to lock up. If we're using an old +- // version, we need to apply a workaround. But the workaround has its +- // own drawbacks ... +- ++ for (i = 0; i < NUM_CHANNELS; ++i) + { +- int v = SDL_VERSIONNUM(SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION); +- +- if (v <= SDL_VERSIONNUM(1, 2, 8)) +- { +- setpanning_workaround = true; +- fprintf(stderr, "\n" +- "ATTENTION: You are using an old version of SDL_mixer!\n" +- " This version has a bug that may cause " +- "your sound to stutter.\n" +- " Please upgrade to a newer version!\n" +- "\n"); +- } ++ sound_tracks[i] = MIX_CreateTrack(sound_mixer); + } + +- Mix_AllocateChannels(NUM_CHANNELS); +- +- Mix_PauseAudio(0); +- + sound_initialized = true; + + return true; diff --git a/games/sdl3-doom/patches/0004-i_sound-share-a-single-SDL3_mixer-device-between-mus.patch b/games/sdl3-doom/patches/0004-i_sound-share-a-single-SDL3_mixer-device-between-mus.patch new file mode 100644 index 00000000..bf00a212 --- /dev/null +++ b/games/sdl3-doom/patches/0004-i_sound-share-a-single-SDL3_mixer-device-between-mus.patch @@ -0,0 +1,332 @@ +From 586e123b3d64025739c6eef8e3f16c1091166150 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 9 Jun 2026 17:06:08 +0100 +Subject: [PATCH] i_sound: share a single SDL3_mixer device between music and + SFX + +The SDL2_mixer build opened one audio device with Mix_OpenAudio() that +was shared by music and every sound effect channel. The port to the +SDL3_mixer 3.2 MIX_* API instead created two independent mixers, one in +i_sdlmusic.c (music_mixer) and one in i_sdlsound.c (sound_mixer), each +via its own MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK). +That opens two separate output streams on the default device and leaves +both backends independently calling SDL_Init(SDL_INIT_AUDIO), MIX_Init() +and, on shutdown, MIX_Quit() plus SDL_QuitSubSystem(); whichever backend +shuts down first tears the mixer library out from under the other. + +Introduce a small reference-counted helper, i_sdlmixer, that owns one +MIX_Mixer on the default playback device. Both backends acquire it on +init and release it on shutdown, so the device, MIX_Init() and the audio +subsystem are set up once and torn down once. Each backend keeps its own +MIX_Track(s) on the shared mixer, which matches the canonical SDL3_mixer +model of one mixer feeding many tracks. + +Signed-off-by: Daniel Golle +--- + CMakeLists.txt | 4 +- + include/i_sdlmixer.h | 29 +++++++++++++++ + src/i_sdlmixer.c | 87 ++++++++++++++++++++++++++++++++++++++++++++ + src/i_sdlmusic.c | 65 +++++++-------------------------- + src/i_sdlsound.c | 28 +++----------- + 5 files changed, 138 insertions(+), 75 deletions(-) + create mode 100644 include/i_sdlmixer.h + create mode 100644 src/i_sdlmixer.c + +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -12,8 +12,8 @@ include_directories(${SDL3_MIXER_INCLUDE + add_definitions(-Wall -D_REENTRANT -D_THREAD_SAFE) + add_executable(sdl3-doom src/i_main.c src/dummy.c src/am_map.c src/doomdef.c src/doomstat.c src/dstrings.c src/d_event.c + src/d_items.c src/d_iwad.c src/d_loop.c src/d_main.c src/d_mode.c src/d_net.c src/f_finale.c src/f_wipe.c src/g_game.c src/hu_lib.c +- src/hu_stuff.c src/info.c src/i_cdmus.c src/i_endoom.c src/i_joystick.c src/i_scale.c src/i_sound.c src/i_sdlmusic.c +- src/i_sdlsound.c src/i_system.c src/i_timer.c src/i_input.c src/i_video.c src/mus2mid.c src/memio.c src/m_argv.c ++ src/hu_stuff.c src/info.c src/i_cdmus.c src/i_endoom.c src/i_joystick.c src/i_scale.c src/i_sound.c src/i_sdlmixer.c src/i_sdlmusic.c ++ src/i_sdlsound.c src/i_system.c src/i_timer.c src/i_input.c src/i_video.c src/mus2mid.c src/memio.c src/m_argv.c + src/m_bbox.c src/m_cheat.c src/m_config.c src/m_controls.c src/m_fixed.c src/m_menu.c src/m_misc.c src/m_random.c + src/p_ceilng.c src/p_doors.c src/p_enemy.c src/p_floor.c src/p_inter.c src/p_lights.c src/p_map.c src/p_maputl.c + src/p_mobj.c src/p_plats.c src/p_pspr.c src/p_saveg.c src/p_setup.c src/p_sight.c src/p_spec.c src/p_switch.c +--- /dev/null ++++ b/include/i_sdlmixer.h +@@ -0,0 +1,29 @@ ++// ++// Copyright(C) 1993-1996 Id Software, Inc. ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// Shared SDL3_mixer device, used by both the music and sound ++// effect backends. ++// ++ ++#ifndef __I_SDLMIXER__ ++#define __I_SDLMIXER__ ++ ++#include "SDL3_mixer/SDL_mixer.h" ++#include "doomtype.h" ++ ++MIX_Mixer *I_SDLMixer_Acquire(void); ++void I_SDLMixer_Release(void); ++ ++#endif +--- /dev/null ++++ b/src/i_sdlmixer.c +@@ -0,0 +1,87 @@ ++// ++// Copyright(C) 1993-1996 Id Software, Inc. ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// Shared SDL3_mixer device, used by both the music and sound ++// effect backends. ++// ++ ++#include ++ ++#include "SDL3/SDL.h" ++#include "SDL3_mixer/SDL_mixer.h" ++ ++#include "i_sdlmixer.h" ++#include "i_sound.h" ++ ++static MIX_Mixer *shared_mixer = NULL; ++static int mixer_refcount = 0; ++ ++MIX_Mixer *I_SDLMixer_Acquire(void) ++{ ++ if (shared_mixer != NULL) ++ { ++ ++mixer_refcount; ++ return shared_mixer; ++ } ++ ++ if (!SDL_Init(SDL_INIT_AUDIO)) ++ { ++ fprintf(stderr, "Unable to set up sound.\n"); ++ return NULL; ++ } ++ ++ if (!MIX_Init()) ++ { ++ fprintf(stderr, "Error initialising SDL3_mixer: %s\n", SDL_GetError()); ++ SDL_QuitSubSystem(SDL_INIT_AUDIO); ++ return NULL; ++ } ++ ++ const SDL_AudioSpec spec = {SDL_AUDIO_S16, 2, snd_samplerate}; ++ ++ shared_mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec); ++ ++ if (shared_mixer == NULL) ++ { ++ fprintf(stderr, "Error initialising SDL3_mixer: %s\n", SDL_GetError()); ++ MIX_Quit(); ++ SDL_QuitSubSystem(SDL_INIT_AUDIO); ++ return NULL; ++ } ++ ++ mixer_refcount = 1; ++ ++ return shared_mixer; ++} ++ ++void I_SDLMixer_Release(void) ++{ ++ if (mixer_refcount <= 0) ++ { ++ return; ++ } ++ ++ --mixer_refcount; ++ ++ if (mixer_refcount > 0) ++ { ++ return; ++ } ++ ++ MIX_DestroyMixer(shared_mixer); ++ shared_mixer = NULL; ++ MIX_Quit(); ++ SDL_QuitSubSystem(SDL_INIT_AUDIO); ++} +--- a/src/i_sdlmusic.c ++++ b/src/i_sdlmusic.c +@@ -31,6 +31,7 @@ + + #include "deh_str.h" + #include "gusconf.h" ++#include "i_sdlmixer.h" + #include "i_sound.h" + #include "i_system.h" + #include "i_swap.h" +@@ -102,11 +103,6 @@ static const char *subst_config_filename + + static boolean music_initialized = false; + +-// If this is true, this module initialized SDL sound and has the +-// responsibility to shut it down +- +-static boolean sdl_was_initialized = false; +- + static boolean musicpaused = false; + static int current_music_volume; + +@@ -856,24 +852,13 @@ static void I_SDL_ShutdownMusic(void) + MIX_StopTrack(music_track, 0); + music_initialized = false; + +- if (sdl_was_initialized) +- { +- MIX_DestroyTrack(music_track); +- music_track = NULL; +- MIX_DestroyMixer(music_mixer); +- music_mixer = NULL; +- MIX_Quit(); +- SDL_QuitSubSystem(SDL_INIT_AUDIO); +- sdl_was_initialized = false; +- } ++ MIX_DestroyTrack(music_track); ++ music_track = NULL; ++ music_mixer = NULL; ++ I_SDLMixer_Release(); + } + } + +-static boolean SDLIsInitialized(void) +-{ +- return music_mixer != NULL; +-} +- + // Initialize music subsystem + static boolean I_SDL_InitMusic(void) + { +@@ -914,42 +899,20 @@ static boolean I_SDL_InitMusic(void) + DumpSubstituteConfig(myargv[i + 1]); + } + +- // If SDL_mixer is not initialized, we have to initialize it +- // and have the responsibility to shut it down later on. ++ music_mixer = I_SDLMixer_Acquire(); + +- if (SDLIsInitialized()) +- { +- music_initialized = true; +- } +- else ++ if (music_mixer != NULL) + { +- const SDL_AudioSpec spec = {SDL_AUDIO_S16, 2, 44100}; +- if (!SDL_Init(SDL_INIT_AUDIO)) +- { +- fprintf(stderr, "Unable to set up sound.\n"); +- } +- else if (!MIX_Init()) +- { +- fprintf(stderr, "Error initializing SDL3_mixer: %s\n", +- SDL_GetError()); +- SDL_QuitSubSystem(SDL_INIT_AUDIO); +- } +- else if (!(music_mixer = MIX_CreateMixerDevice( +- SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec))) +- { +- fprintf(stderr, "Error initializing SDL3_mixer: %s\n", +- SDL_GetError()); +- MIX_Quit(); +- SDL_QuitSubSystem(SDL_INIT_AUDIO); +- } +- else ++ SDL_AudioSpec spec; ++ ++ music_track = MIX_CreateTrack(music_mixer); ++ ++ if (MIX_GetMixerFormat(music_mixer, &spec)) + { +- music_track = MIX_CreateTrack(music_mixer); + music_freq = spec.freq; +- +- sdl_was_initialized = true; +- music_initialized = true; + } ++ ++ music_initialized = true; + } + + // Once initialization is complete, the temporary Timidity config +--- a/src/i_sdlsound.c ++++ b/src/i_sdlsound.c +@@ -32,6 +32,7 @@ + #endif + + #include "deh_str.h" ++#include "i_sdlmixer.h" + #include "i_sound.h" + #include "i_system.h" + #include "i_swap.h" +@@ -957,10 +958,8 @@ static void I_SDL_ShutdownSound(void) + sound_tracks[i] = NULL; + } + +- MIX_DestroyMixer(sound_mixer); + sound_mixer = NULL; +- MIX_Quit(); +- SDL_QuitSubSystem(SDL_INIT_AUDIO); ++ I_SDLMixer_Release(); + + sound_initialized = false; + } +@@ -978,33 +977,18 @@ static boolean I_SDL_InitSound(boolean _ + channels_playing[i] = NULL; + } + +- if (!SDL_Init(SDL_INIT_AUDIO)) +- { +- fprintf(stderr, "Unable to set up sound.\n"); +- return false; +- } +- const SDL_AudioSpec spec = {SDL_AUDIO_S16, 2, snd_samplerate}; +- +- if (!MIX_Init()) +- { +- fprintf(stderr, "Error initialising SDL3_mixer: %s\n", SDL_GetError()); +- return false; +- } +- +- sound_mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec); ++ sound_mixer = I_SDLMixer_Acquire(); + + if (sound_mixer == NULL) + { +- fprintf(stderr, "Error initialising SDL3_mixer: %s\n", SDL_GetError()); +- MIX_Quit(); + return false; + } + + ExpandSoundData = ExpandSoundData_SDL; + +- mixer_freq = spec.freq; +- mixer_format = spec.format; +- mixer_channels = spec.channels; ++ mixer_freq = snd_samplerate; ++ mixer_format = SDL_AUDIO_S16; ++ mixer_channels = 2; + + #ifdef HAVE_LIBSAMPLERATE + if (use_libsamplerate != 0) diff --git a/games/sdl3-doom/patches/0005-st_stuff-fix-arms-widget-crash-from-boolean-width-mi.patch b/games/sdl3-doom/patches/0005-st_stuff-fix-arms-widget-crash-from-boolean-width-mi.patch new file mode 100644 index 00000000..c08ae898 --- /dev/null +++ b/games/sdl3-doom/patches/0005-st_stuff-fix-arms-widget-crash-from-boolean-width-mi.patch @@ -0,0 +1,68 @@ +From 9f4f3835a072293c185842972e713c2972fa3ec0 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 9 Jun 2026 17:06:18 +0100 +Subject: [PATCH] st_stuff: fix arms widget crash from boolean width mismatch + +boolean is typedef'd to the C99 bool, which is one byte wide, but the +status bar arms widgets were initialised with + + (int *) &plyr->weaponowned[i+1] + +and STlib_updateMultIcon() dereferences that int* to index mi->p[]. On a +one-byte bool this reads four adjacent ownership bytes as a single int, +yielding values such as 257 that index far out of the two-element arms +patch array. The result is a NULL patch passed to V_DrawPatch(), which +dereferences it and crashes; this triggers within seconds of the +attract-mode demo once the status bar is drawn. + +Mirror the existing keyboxes idiom: keep an int shadow array, +st_armsowned[], updated each tick from plyr->weaponowned[], and point the +arms widgets at it. The widget then reads a correctly sized int holding 0 +or 1, as it expects. + +Signed-off-by: Daniel Golle +--- + src/st_stuff.c | 15 +++++++++++++-- + 1 file changed, 13 insertions(+), 2 deletions(-) + +--- a/src/st_stuff.c ++++ b/src/st_stuff.c +@@ -380,7 +380,10 @@ static int st_facecount = 0; + static int st_faceindex = 0; + + // holds key-type for each key box on bar +-static int keyboxes[3]; ++static int keyboxes[3]; ++ ++// holds weapon ownership (0 or 1) for each arms widget on bar ++static int st_armsowned[6]; + + // a random number per tick + static int st_randomnumber; +@@ -894,6 +897,12 @@ void ST_updateWidgets(void) + keyboxes[i] = i+3; + } + ++ // update weapon ownership widgets ++ for (i=0;i<6;i++) ++ { ++ st_armsowned[i] = plyr->weaponowned[i+1] ? 1 : 0; ++ } ++ + // refresh everything if this is him coming back to life + ST_updateFaceWidget(); + +@@ -1261,10 +1270,12 @@ void ST_createWidgets(void) + // weapons owned + for(i=0;i<6;i++) + { ++ st_armsowned[i] = plyr->weaponowned[i+1] ? 1 : 0; ++ + STlib_initMultIcon(&w_arms[i], + ST_ARMSX+(i%3)*ST_ARMSXSPACE, + ST_ARMSY+(i/3)*ST_ARMSYSPACE, +- arms[i], (int *) &plyr->weaponowned[i+1], ++ arms[i], &st_armsowned[i], + &st_armson); + } + diff --git a/games/sdl3-doom/patches/0006-build-enable-FEATURE_SOUND-so-the-SDL-sound-effect-m.patch b/games/sdl3-doom/patches/0006-build-enable-FEATURE_SOUND-so-the-SDL-sound-effect-m.patch new file mode 100644 index 00000000..c825a2b0 --- /dev/null +++ b/games/sdl3-doom/patches/0006-build-enable-FEATURE_SOUND-so-the-SDL-sound-effect-m.patch @@ -0,0 +1,43 @@ +From d76f4e70fae478c139112f24d28a01e7f11769a4 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 9 Jun 2026 17:32:27 +0100 +Subject: [PATCH] build: enable FEATURE_SOUND so the SDL sound-effect module is + built + +The sound-effect backend (i_sdlsound.c, registered as sound_sdl_module) +is compiled in only when FEATURE_SOUND is defined. The legacy makefiles +pass -DFEATURE_SOUND, but the CMake build never did, so sound_modules[] +was empty, I_SDL_InitSound() was never called and the game had no sound +effects (only the music backend opened a device, which is hardcoded). + +Define FEATURE_SOUND in CMakeLists.txt to match the makefiles, and fix +the now-compiled SDL_mixer include in i_sound.c to the SDL3_mixer path. + +Signed-off-by: Daniel Golle +--- + CMakeLists.txt | 2 +- + src/i_sound.c | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -9,7 +9,7 @@ include_directories("/usr/local/include/ + include_directories(${SDL3_INCLUDE_DIR}) + include_directories(${SDL3_MIXER_INCLUDE_DIR}) + +-add_definitions(-Wall -D_REENTRANT -D_THREAD_SAFE) ++add_definitions(-Wall -D_REENTRANT -D_THREAD_SAFE -DFEATURE_SOUND) + add_executable(sdl3-doom src/i_main.c src/dummy.c src/am_map.c src/doomdef.c src/doomstat.c src/dstrings.c src/d_event.c + src/d_items.c src/d_iwad.c src/d_loop.c src/d_main.c src/d_mode.c src/d_net.c src/f_finale.c src/f_wipe.c src/g_game.c src/hu_lib.c + src/hu_stuff.c src/info.c src/i_cdmus.c src/i_endoom.c src/i_joystick.c src/i_scale.c src/i_sound.c src/i_sdlmixer.c src/i_sdlmusic.c +--- a/src/i_sound.c ++++ b/src/i_sound.c +@@ -19,7 +19,7 @@ + #include + + #ifdef FEATURE_SOUND +-#include "SDL3/SDL_mixer.h" ++#include "SDL3_mixer/SDL_mixer.h" + #endif + + #include "config.h" diff --git a/games/sdl3-doom/patches/0007-i_oplmusic-restore-from-Chocolate-DOOM.patch b/games/sdl3-doom/patches/0007-i_oplmusic-restore-from-Chocolate-DOOM.patch new file mode 100644 index 00000000..7f853922 --- /dev/null +++ b/games/sdl3-doom/patches/0007-i_oplmusic-restore-from-Chocolate-DOOM.patch @@ -0,0 +1,7639 @@ +From 74f8842efcf00c800a018a0e5591eae6ac046152 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 9 Jun 2026 18:20:53 +0100 +Subject: [PATCH] i_oplmusic: restore from Chocolate DOOM + +Enable authentic music like it was on SB16 with OPL3 emulation. + +This brings back the previously removed OPL3 music emulator +(Nuked-OPL3-fast) which renders the original synth patterns included +in the WAD files. Wired up to the SDL3 audio path. Tested, and it +sounds great. + +Signed-off-by: Daniel Golle +--- + CMakeLists.txt | 5 +- + include/doomtype.h | 4 + + include/i_sound.h | 10 + + include/midifile.h | 232 +++++ + src/i_oplmusic.c | 1898 ++++++++++++++++++++++++++++++++++++++++ + src/i_sound.c | 9 +- + src/midifile.c | 846 ++++++++++++++++++ + src/opl/opl.c | 520 +++++++++++ + src/opl/opl.h | 153 ++++ + src/opl/opl3.c | 1596 +++++++++++++++++++++++++++++++++ + src/opl/opl3.h | 199 +++++ + src/opl/opl_internal.h | 71 ++ + src/opl/opl_queue.c | 285 ++++++ + src/opl/opl_queue.h | 39 + + src/opl/opl_sdl.c | 565 ++++++++++++ + src/opl/wf_rom.h | 1062 ++++++++++++++++++++++ + 16 files changed, 7489 insertions(+), 5 deletions(-) + create mode 100644 include/midifile.h + create mode 100644 src/i_oplmusic.c + create mode 100644 src/midifile.c + create mode 100644 src/opl/opl.c + create mode 100644 src/opl/opl.h + create mode 100644 src/opl/opl3.c + create mode 100644 src/opl/opl3.h + create mode 100644 src/opl/opl_internal.h + create mode 100644 src/opl/opl_queue.c + create mode 100644 src/opl/opl_queue.h + create mode 100644 src/opl/opl_sdl.c + create mode 100644 src/opl/wf_rom.h + +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -5,6 +5,7 @@ find_package(SDL3 REQUIRED) + find_package(SDL3_mixer REQUIRED) + + include_directories(./include) ++include_directories(./src/opl) + include_directories("/usr/local/include/") + include_directories(${SDL3_INCLUDE_DIR}) + include_directories(${SDL3_MIXER_INCLUDE_DIR}) +@@ -13,7 +14,9 @@ add_definitions(-Wall -D_REENTRANT -D_TH + add_executable(sdl3-doom src/i_main.c src/dummy.c src/am_map.c src/doomdef.c src/doomstat.c src/dstrings.c src/d_event.c + src/d_items.c src/d_iwad.c src/d_loop.c src/d_main.c src/d_mode.c src/d_net.c src/f_finale.c src/f_wipe.c src/g_game.c src/hu_lib.c + src/hu_stuff.c src/info.c src/i_cdmus.c src/i_endoom.c src/i_joystick.c src/i_scale.c src/i_sound.c src/i_sdlmixer.c src/i_sdlmusic.c +- src/i_sdlsound.c src/i_system.c src/i_timer.c src/i_input.c src/i_video.c src/mus2mid.c src/memio.c src/m_argv.c ++ src/i_sdlsound.c src/i_oplmusic.c src/midifile.c ++ src/opl/opl.c src/opl/opl3.c src/opl/opl_queue.c src/opl/opl_sdl.c ++ src/i_system.c src/i_timer.c src/i_input.c src/i_video.c src/mus2mid.c src/memio.c src/m_argv.c + src/m_bbox.c src/m_cheat.c src/m_config.c src/m_controls.c src/m_fixed.c src/m_menu.c src/m_misc.c src/m_random.c + src/p_ceilng.c src/p_doors.c src/p_enemy.c src/p_floor.c src/p_inter.c src/p_lights.c src/p_map.c src/p_maputl.c + src/p_mobj.c src/p_plats.c src/p_pspr.c src/p_saveg.c src/p_setup.c src/p_sight.c src/p_spec.c src/p_switch.c +--- a/include/doomtype.h ++++ b/include/doomtype.h +@@ -48,10 +48,14 @@ + + #ifdef __GNUC__ + #define PACKEDATTR __attribute__((packed)) ++#define PACKEDPREFIX + #else + #define PACKEDATTR ++#define PACKEDPREFIX + #endif + ++#define PACKED_STRUCT(...) PACKEDPREFIX struct __VA_ARGS__ PACKEDATTR ++ + // C99 integer types; with gcc we just use this. Other compilers + // should add conditional statements that define the C99 types. + +--- a/include/i_sound.h ++++ b/include/i_sound.h +@@ -234,5 +234,15 @@ extern char *snd_musiccmd; + + void I_BindSoundVariables(void); + ++// DMX version to emulate for OPL emulation: ++typedef enum { ++ opl_doom1_1_666, // Doom 1 v1.666 ++ opl_doom2_1_666, // Doom 2 v1.666, Hexen, Heretic ++ opl_doom_1_9 // Doom v1.9, Strife ++} opl_driver_ver_t; ++ ++void I_SetOPLDriverVer(opl_driver_ver_t ver); ++void I_OPL_DevMessages(char *, size_t); ++ + #endif + +--- /dev/null ++++ b/include/midifile.h +@@ -0,0 +1,232 @@ ++// ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// MIDI file parsing. ++// ++ ++#ifndef MIDIFILE_H ++#define MIDIFILE_H ++ ++typedef struct midi_file_s midi_file_t; ++typedef struct midi_track_iter_s midi_track_iter_t; ++ ++#define MIDI_CHANNELS_PER_TRACK 16 ++ ++#define MIDI_RPN_MSB 0x00 ++#define MIDI_RPN_PITCH_BEND_SENS_LSB 0x00 ++#define MIDI_RPN_FINE_TUNING_LSB 0x01 ++#define MIDI_RPN_COARSE_TUNING_LSB 0x02 ++#define MIDI_RPN_NULL 0x7F ++ ++typedef enum ++{ ++ MIDI_EVENT_NOTE_OFF = 0x80, ++ MIDI_EVENT_NOTE_ON = 0x90, ++ MIDI_EVENT_AFTERTOUCH = 0xA0, ++ MIDI_EVENT_CONTROLLER = 0xB0, ++ MIDI_EVENT_PROGRAM_CHANGE = 0xC0, ++ MIDI_EVENT_CHAN_AFTERTOUCH = 0xD0, ++ MIDI_EVENT_PITCH_BEND = 0xE0, ++ ++ MIDI_EVENT_SYSEX = 0xF0, ++ MIDI_EVENT_SYSEX_SPLIT = 0xF7, ++ MIDI_EVENT_META = 0xFF, ++} midi_event_type_t; ++ ++typedef enum ++{ ++ MIDI_CONTROLLER_BANK_SELECT_MSB = 0x00, ++ MIDI_CONTROLLER_MODULATION = 0x01, ++ MIDI_CONTROLLER_BREATH_CONTROL = 0x02, ++ MIDI_CONTROLLER_FOOT_CONTROL = 0x04, ++ MIDI_CONTROLLER_PORTAMENTO = 0x05, ++ MIDI_CONTROLLER_DATA_ENTRY_MSB = 0x06, ++ MIDI_CONTROLLER_VOLUME_MSB = 0x07, ++ MIDI_CONTROLLER_PAN = 0x0A, ++ MIDI_CONTROLLER_EXPRESSION = 0x0B, ++ ++ MIDI_CONTROLLER_BANK_SELECT_LSB = 0x20, ++ MIDI_CONTROLLER_DATA_ENTRY_LSB = 0x26, ++ MIDI_CONTROLLER_VOLUME_LSB = 0X27, ++ ++ MIDI_CONTROLLER_HOLD1_PEDAL = 0x40, ++ MIDI_CONTROLLER_SOFT_PEDAL = 0x43, ++ ++ MIDI_CONTROLLER_REVERB = 0x5B, ++ MIDI_CONTROLLER_CHORUS = 0x5D, ++ ++ MIDI_CONTROLLER_NRPN_LSB = 0x62, ++ MIDI_CONTROLLER_NRPN_MSB = 0x63, ++ MIDI_CONTROLLER_RPN_LSB = 0x64, ++ MIDI_CONTROLLER_RPN_MSB = 0x65, ++ ++ MIDI_CONTROLLER_ALL_SOUND_OFF = 0x78, ++ MIDI_CONTROLLER_RESET_ALL_CTRLS = 0x79, ++ MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B, ++ ++ MIDI_CONTROLLER_POLY_MODE_OFF = 0x7E, ++ MIDI_CONTROLLER_POLY_MODE_ON = 0x7F, ++} midi_controller_t; ++ ++typedef enum ++{ ++ MIDI_META_SEQUENCE_NUMBER = 0x00, ++ ++ MIDI_META_TEXT = 0x01, ++ MIDI_META_COPYRIGHT = 0x02, ++ MIDI_META_TRACK_NAME = 0x03, ++ MIDI_META_INSTR_NAME = 0x04, ++ MIDI_META_LYRICS = 0x05, ++ MIDI_META_MARKER = 0x06, ++ MIDI_META_CUE_POINT = 0x07, ++ ++ MIDI_META_CHANNEL_PREFIX = 0x20, ++ MIDI_META_END_OF_TRACK = 0x2F, ++ ++ MIDI_META_SET_TEMPO = 0x51, ++ MIDI_META_SMPTE_OFFSET = 0x54, ++ MIDI_META_TIME_SIGNATURE = 0x58, ++ MIDI_META_KEY_SIGNATURE = 0x59, ++ MIDI_META_SEQUENCER_SPECIFIC = 0x7F, ++} midi_meta_event_type_t; ++ ++#define EMIDI_LOOP_FLAG 0x7F ++ ++typedef enum ++{ ++ EMIDI_DEVICE_GENERAL_MIDI = 0x00, ++ EMIDI_DEVICE_SOUND_CANVAS = 0x01, ++ EMIDI_DEVICE_AWE32 = 0x02, ++ EMIDI_DEVICE_WAVE_BLASTER = 0x03, ++ EMIDI_DEVICE_SOUND_BLASTER = 0x04, ++ EMIDI_DEVICE_PRO_AUDIO = 0x05, ++ EMIDI_DEVICE_SOUND_MAN_16 = 0x06, ++ EMIDI_DEVICE_ADLIB = 0x07, ++ EMIDI_DEVICE_SOUNDSCAPE = 0x08, ++ EMIDI_DEVICE_ULTRASOUND = 0x09, ++ EMIDI_DEVICE_ALL = 0x7F, ++} emidi_device_t; ++ ++typedef enum ++{ ++ EMIDI_CONTROLLER_TRACK_DESIGNATION = 0x6E, ++ EMIDI_CONTROLLER_TRACK_EXCLUSION = 0x6F, ++ EMIDI_CONTROLLER_PROGRAM_CHANGE = 0x70, ++ EMIDI_CONTROLLER_VOLUME = 0x71, ++ EMIDI_CONTROLLER_LOOP_BEGIN = 0x74, ++ EMIDI_CONTROLLER_LOOP_END = 0x75, ++ EMIDI_CONTROLLER_GLOBAL_LOOP_BEGIN = 0x76, ++ EMIDI_CONTROLLER_GLOBAL_LOOP_END = 0x77, ++} emidi_controller_t; ++ ++typedef struct ++{ ++ // Meta event type: ++ ++ unsigned int type; ++ ++ // Length: ++ ++ unsigned int length; ++ ++ // Meta event data: ++ ++ byte *data; ++} midi_meta_event_data_t; ++ ++typedef struct ++{ ++ // Length: ++ ++ unsigned int length; ++ ++ // Event data: ++ ++ byte *data; ++} midi_sysex_event_data_t; ++ ++typedef struct ++{ ++ // The channel number to which this applies: ++ ++ unsigned int channel; ++ ++ // Extra parameters: ++ ++ unsigned int param1; ++ unsigned int param2; ++} midi_channel_event_data_t; ++ ++typedef struct ++{ ++ // Time between the previous event and this event. ++ unsigned int delta_time; ++ ++ // Type of event: ++ midi_event_type_t event_type; ++ ++ union ++ { ++ midi_channel_event_data_t channel; ++ midi_meta_event_data_t meta; ++ midi_sysex_event_data_t sysex; ++ } data; ++} midi_event_t; ++ ++// Load a MIDI file. ++ ++midi_file_t *MIDI_LoadFile(char *filename); ++ ++// Free a MIDI file. ++ ++void MIDI_FreeFile(midi_file_t *file); ++ ++// Get the time division value from the MIDI header. ++ ++unsigned int MIDI_GetFileTimeDivision(midi_file_t *file); ++ ++// Get the number of tracks in a MIDI file. ++ ++unsigned int MIDI_NumTracks(midi_file_t *file); ++ ++// Start iterating over the events in a track. ++ ++midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track_num); ++ ++// Free an iterator. ++ ++void MIDI_FreeIterator(midi_track_iter_t *iter); ++ ++// Get the time until the next MIDI event in a track. ++ ++unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter); ++ ++// Get a pointer to the next MIDI event. ++ ++int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event); ++ ++// Reset an iterator to the beginning of a track. ++ ++void MIDI_RestartIterator(midi_track_iter_t *iter); ++ ++// Set loop point to current position. ++ ++void MIDI_SetLoopPoint(midi_track_iter_t *iter); ++ ++// Set position to saved loop point. ++ ++void MIDI_RestartAtLoopPoint(midi_track_iter_t *iter); ++ ++#endif /* #ifndef MIDIFILE_H */ ++ +--- /dev/null ++++ b/src/i_oplmusic.c +@@ -0,0 +1,1898 @@ ++// ++// Copyright(C) 1993-1996 Id Software, Inc. ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// System interface for music. ++// ++ ++ ++#include ++#include ++#include ++ ++#include "memio.h" ++#include "mus2mid.h" ++ ++#include "deh_main.h" ++#include "i_sound.h" ++#include "i_swap.h" ++#include "m_misc.h" ++#include "w_wad.h" ++#include "z_zone.h" ++ ++#include "opl.h" ++#include "midifile.h" ++ ++// #define OPL_MIDI_DEBUG ++ ++#define MAXMIDLENGTH (96 * 1024) ++#define GENMIDI_NUM_INSTRS 128 ++#define GENMIDI_NUM_PERCUSSION 47 ++ ++#define GENMIDI_HEADER "#OPL_II#" ++#define GENMIDI_FLAG_FIXED 0x0001 /* fixed pitch */ ++#define GENMIDI_FLAG_2VOICE 0x0004 /* double voice (OPL3) */ ++ ++#define PERCUSSION_LOG_LEN 16 ++ ++typedef PACKED_STRUCT ( ++{ ++ byte tremolo; ++ byte attack; ++ byte sustain; ++ byte waveform; ++ byte scale; ++ byte level; ++}) genmidi_op_t; ++ ++typedef PACKED_STRUCT ( ++{ ++ genmidi_op_t modulator; ++ byte feedback; ++ genmidi_op_t carrier; ++ byte unused; ++ short base_note_offset; ++}) genmidi_voice_t; ++ ++typedef PACKED_STRUCT ( ++{ ++ unsigned short flags; ++ byte fine_tuning; ++ byte fixed_note; ++ ++ genmidi_voice_t voices[2]; ++}) genmidi_instr_t; ++ ++// Data associated with a channel of a track that is currently playing. ++ ++typedef struct ++{ ++ // The instrument currently used for this track. ++ ++ genmidi_instr_t *instrument; ++ ++ // Volume level ++ ++ int volume; ++ int volume_base; ++ ++ // Pan ++ ++ int pan; ++ ++ // Pitch bend value: ++ ++ int bend; ++ ++} opl_channel_data_t; ++ ++// Data associated with a track that is currently playing. ++ ++typedef struct ++{ ++ // Track iterator used to read new events. ++ ++ midi_track_iter_t *iter; ++} opl_track_data_t; ++ ++typedef struct opl_voice_s opl_voice_t; ++ ++struct opl_voice_s ++{ ++ // Index of this voice: ++ int index; ++ ++ // The operators used by this voice: ++ int op1, op2; ++ ++ // Array used by voice: ++ int array; ++ ++ // Currently-loaded instrument data ++ genmidi_instr_t *current_instr; ++ ++ // The voice number in the instrument to use. ++ // This is normally set to zero; if this is a double voice ++ // instrument, it may be one. ++ unsigned int current_instr_voice; ++ ++ // The channel currently using this voice. ++ opl_channel_data_t *channel; ++ ++ // The midi key that this voice is playing. ++ unsigned int key; ++ ++ // The note being played. This is normally the same as ++ // the key, but if the instrument is a fixed pitch ++ // instrument, it is different. ++ unsigned int note; ++ ++ // The frequency value being used. ++ unsigned int freq; ++ ++ // The volume of the note being played on this channel. ++ unsigned int note_volume; ++ ++ // The current volume (register value) that has been set for this channel. ++ unsigned int car_volume; ++ unsigned int mod_volume; ++ ++ // Pan. ++ unsigned int reg_pan; ++ ++ // Priority. ++ unsigned int priority; ++}; ++ ++// Operators used by the different voices. ++ ++static const int voice_operators[2][OPL_NUM_VOICES] = { ++ { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12 }, ++ { 0x03, 0x04, 0x05, 0x0b, 0x0c, 0x0d, 0x13, 0x14, 0x15 } ++}; ++ ++// Frequency values to use for each note. ++ ++static const unsigned short frequency_curve[] = { ++ ++ 0x133, 0x133, 0x134, 0x134, 0x135, 0x136, 0x136, 0x137, // -1 ++ 0x137, 0x138, 0x138, 0x139, 0x139, 0x13a, 0x13b, 0x13b, ++ 0x13c, 0x13c, 0x13d, 0x13d, 0x13e, 0x13f, 0x13f, 0x140, ++ 0x140, 0x141, 0x142, 0x142, 0x143, 0x143, 0x144, 0x144, ++ ++ 0x145, 0x146, 0x146, 0x147, 0x147, 0x148, 0x149, 0x149, // -2 ++ 0x14a, 0x14a, 0x14b, 0x14c, 0x14c, 0x14d, 0x14d, 0x14e, ++ 0x14f, 0x14f, 0x150, 0x150, 0x151, 0x152, 0x152, 0x153, ++ 0x153, 0x154, 0x155, 0x155, 0x156, 0x157, 0x157, 0x158, ++ ++ // These are used for the first seven MIDI note values: ++ ++ 0x158, 0x159, 0x15a, 0x15a, 0x15b, 0x15b, 0x15c, 0x15d, // 0 ++ 0x15d, 0x15e, 0x15f, 0x15f, 0x160, 0x161, 0x161, 0x162, ++ 0x162, 0x163, 0x164, 0x164, 0x165, 0x166, 0x166, 0x167, ++ 0x168, 0x168, 0x169, 0x16a, 0x16a, 0x16b, 0x16c, 0x16c, ++ ++ 0x16d, 0x16e, 0x16e, 0x16f, 0x170, 0x170, 0x171, 0x172, // 1 ++ 0x172, 0x173, 0x174, 0x174, 0x175, 0x176, 0x176, 0x177, ++ 0x178, 0x178, 0x179, 0x17a, 0x17a, 0x17b, 0x17c, 0x17c, ++ 0x17d, 0x17e, 0x17e, 0x17f, 0x180, 0x181, 0x181, 0x182, ++ ++ 0x183, 0x183, 0x184, 0x185, 0x185, 0x186, 0x187, 0x188, // 2 ++ 0x188, 0x189, 0x18a, 0x18a, 0x18b, 0x18c, 0x18d, 0x18d, ++ 0x18e, 0x18f, 0x18f, 0x190, 0x191, 0x192, 0x192, 0x193, ++ 0x194, 0x194, 0x195, 0x196, 0x197, 0x197, 0x198, 0x199, ++ ++ 0x19a, 0x19a, 0x19b, 0x19c, 0x19d, 0x19d, 0x19e, 0x19f, // 3 ++ 0x1a0, 0x1a0, 0x1a1, 0x1a2, 0x1a3, 0x1a3, 0x1a4, 0x1a5, ++ 0x1a6, 0x1a6, 0x1a7, 0x1a8, 0x1a9, 0x1a9, 0x1aa, 0x1ab, ++ 0x1ac, 0x1ad, 0x1ad, 0x1ae, 0x1af, 0x1b0, 0x1b0, 0x1b1, ++ ++ 0x1b2, 0x1b3, 0x1b4, 0x1b4, 0x1b5, 0x1b6, 0x1b7, 0x1b8, // 4 ++ 0x1b8, 0x1b9, 0x1ba, 0x1bb, 0x1bc, 0x1bc, 0x1bd, 0x1be, ++ 0x1bf, 0x1c0, 0x1c0, 0x1c1, 0x1c2, 0x1c3, 0x1c4, 0x1c4, ++ 0x1c5, 0x1c6, 0x1c7, 0x1c8, 0x1c9, 0x1c9, 0x1ca, 0x1cb, ++ ++ 0x1cc, 0x1cd, 0x1ce, 0x1ce, 0x1cf, 0x1d0, 0x1d1, 0x1d2, // 5 ++ 0x1d3, 0x1d3, 0x1d4, 0x1d5, 0x1d6, 0x1d7, 0x1d8, 0x1d8, ++ 0x1d9, 0x1da, 0x1db, 0x1dc, 0x1dd, 0x1de, 0x1de, 0x1df, ++ 0x1e0, 0x1e1, 0x1e2, 0x1e3, 0x1e4, 0x1e5, 0x1e5, 0x1e6, ++ ++ 0x1e7, 0x1e8, 0x1e9, 0x1ea, 0x1eb, 0x1ec, 0x1ed, 0x1ed, // 6 ++ 0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x1f2, 0x1f3, 0x1f4, 0x1f5, ++ 0x1f6, 0x1f6, 0x1f7, 0x1f8, 0x1f9, 0x1fa, 0x1fb, 0x1fc, ++ 0x1fd, 0x1fe, 0x1ff, 0x200, 0x201, 0x201, 0x202, 0x203, ++ ++ // First note of looped range used for all octaves: ++ ++ 0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20a, 0x20b, // 7 ++ 0x20c, 0x20d, 0x20e, 0x20f, 0x210, 0x210, 0x211, 0x212, ++ 0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21a, ++ 0x21b, 0x21c, 0x21d, 0x21e, 0x21f, 0x220, 0x221, 0x222, ++ ++ 0x223, 0x224, 0x225, 0x226, 0x227, 0x228, 0x229, 0x22a, // 8 ++ 0x22b, 0x22c, 0x22d, 0x22e, 0x22f, 0x230, 0x231, 0x232, ++ 0x233, 0x234, 0x235, 0x236, 0x237, 0x238, 0x239, 0x23a, ++ 0x23b, 0x23c, 0x23d, 0x23e, 0x23f, 0x240, 0x241, 0x242, ++ ++ 0x244, 0x245, 0x246, 0x247, 0x248, 0x249, 0x24a, 0x24b, // 9 ++ 0x24c, 0x24d, 0x24e, 0x24f, 0x250, 0x251, 0x252, 0x253, ++ 0x254, 0x256, 0x257, 0x258, 0x259, 0x25a, 0x25b, 0x25c, ++ 0x25d, 0x25e, 0x25f, 0x260, 0x262, 0x263, 0x264, 0x265, ++ ++ 0x266, 0x267, 0x268, 0x269, 0x26a, 0x26c, 0x26d, 0x26e, // 10 ++ 0x26f, 0x270, 0x271, 0x272, 0x273, 0x275, 0x276, 0x277, ++ 0x278, 0x279, 0x27a, 0x27b, 0x27d, 0x27e, 0x27f, 0x280, ++ 0x281, 0x282, 0x284, 0x285, 0x286, 0x287, 0x288, 0x289, ++ ++ 0x28b, 0x28c, 0x28d, 0x28e, 0x28f, 0x290, 0x292, 0x293, // 11 ++ 0x294, 0x295, 0x296, 0x298, 0x299, 0x29a, 0x29b, 0x29c, ++ 0x29e, 0x29f, 0x2a0, 0x2a1, 0x2a2, 0x2a4, 0x2a5, 0x2a6, ++ 0x2a7, 0x2a9, 0x2aa, 0x2ab, 0x2ac, 0x2ae, 0x2af, 0x2b0, ++ ++ 0x2b1, 0x2b2, 0x2b4, 0x2b5, 0x2b6, 0x2b7, 0x2b9, 0x2ba, // 12 ++ 0x2bb, 0x2bd, 0x2be, 0x2bf, 0x2c0, 0x2c2, 0x2c3, 0x2c4, ++ 0x2c5, 0x2c7, 0x2c8, 0x2c9, 0x2cb, 0x2cc, 0x2cd, 0x2ce, ++ 0x2d0, 0x2d1, 0x2d2, 0x2d4, 0x2d5, 0x2d6, 0x2d8, 0x2d9, ++ ++ 0x2da, 0x2dc, 0x2dd, 0x2de, 0x2e0, 0x2e1, 0x2e2, 0x2e4, // 13 ++ 0x2e5, 0x2e6, 0x2e8, 0x2e9, 0x2ea, 0x2ec, 0x2ed, 0x2ee, ++ 0x2f0, 0x2f1, 0x2f2, 0x2f4, 0x2f5, 0x2f6, 0x2f8, 0x2f9, ++ 0x2fb, 0x2fc, 0x2fd, 0x2ff, 0x300, 0x302, 0x303, 0x304, ++ ++ 0x306, 0x307, 0x309, 0x30a, 0x30b, 0x30d, 0x30e, 0x310, // 14 ++ 0x311, 0x312, 0x314, 0x315, 0x317, 0x318, 0x31a, 0x31b, ++ 0x31c, 0x31e, 0x31f, 0x321, 0x322, 0x324, 0x325, 0x327, ++ 0x328, 0x329, 0x32b, 0x32c, 0x32e, 0x32f, 0x331, 0x332, ++ ++ 0x334, 0x335, 0x337, 0x338, 0x33a, 0x33b, 0x33d, 0x33e, // 15 ++ 0x340, 0x341, 0x343, 0x344, 0x346, 0x347, 0x349, 0x34a, ++ 0x34c, 0x34d, 0x34f, 0x350, 0x352, 0x353, 0x355, 0x357, ++ 0x358, 0x35a, 0x35b, 0x35d, 0x35e, 0x360, 0x361, 0x363, ++ ++ 0x365, 0x366, 0x368, 0x369, 0x36b, 0x36c, 0x36e, 0x370, // 16 ++ 0x371, 0x373, 0x374, 0x376, 0x378, 0x379, 0x37b, 0x37c, ++ 0x37e, 0x380, 0x381, 0x383, 0x384, 0x386, 0x388, 0x389, ++ 0x38b, 0x38d, 0x38e, 0x390, 0x392, 0x393, 0x395, 0x397, ++ ++ 0x398, 0x39a, 0x39c, 0x39d, 0x39f, 0x3a1, 0x3a2, 0x3a4, // 17 ++ 0x3a6, 0x3a7, 0x3a9, 0x3ab, 0x3ac, 0x3ae, 0x3b0, 0x3b1, ++ 0x3b3, 0x3b5, 0x3b7, 0x3b8, 0x3ba, 0x3bc, 0x3bd, 0x3bf, ++ 0x3c1, 0x3c3, 0x3c4, 0x3c6, 0x3c8, 0x3ca, 0x3cb, 0x3cd, ++ ++ // The last note has an incomplete range, and loops round back to ++ // the start. Note that the last value is actually a buffer overrun ++ // and does not fit with the other values. ++ ++ 0x3cf, 0x3d1, 0x3d2, 0x3d4, 0x3d6, 0x3d8, 0x3da, 0x3db, // 18 ++ 0x3dd, 0x3df, 0x3e1, 0x3e3, 0x3e4, 0x3e6, 0x3e8, 0x3ea, ++ 0x3ec, 0x3ed, 0x3ef, 0x3f1, 0x3f3, 0x3f5, 0x3f6, 0x3f8, ++ 0x3fa, 0x3fc, 0x3fe, 0x36c, ++}; ++ ++// Mapping from MIDI volume level to OPL level value. ++ ++static const unsigned int volume_mapping_table[] = { ++ 0, 1, 3, 5, 6, 8, 10, 11, ++ 13, 14, 16, 17, 19, 20, 22, 23, ++ 25, 26, 27, 29, 30, 32, 33, 34, ++ 36, 37, 39, 41, 43, 45, 47, 49, ++ 50, 52, 54, 55, 57, 59, 60, 61, ++ 63, 64, 66, 67, 68, 69, 71, 72, ++ 73, 74, 75, 76, 77, 79, 80, 81, ++ 82, 83, 84, 84, 85, 86, 87, 88, ++ 89, 90, 91, 92, 92, 93, 94, 95, ++ 96, 96, 97, 98, 99, 99, 100, 101, ++ 101, 102, 103, 103, 104, 105, 105, 106, ++ 107, 107, 108, 109, 109, 110, 110, 111, ++ 112, 112, 113, 113, 114, 114, 115, 115, ++ 116, 117, 117, 118, 118, 119, 119, 120, ++ 120, 121, 121, 122, 122, 123, 123, 123, ++ 124, 124, 125, 125, 126, 126, 127, 127 ++}; ++ ++static opl_driver_ver_t opl_drv_ver = opl_doom_1_9; ++static boolean music_initialized = false; ++ ++//static boolean musicpaused = false; ++static int start_music_volume; ++static int current_music_volume; ++ ++// GENMIDI lump instrument data: ++ ++static genmidi_instr_t *main_instrs; ++static genmidi_instr_t *percussion_instrs; ++static char (*main_instr_names)[32]; ++static char (*percussion_names)[32]; ++ ++// Voices: ++ ++static opl_voice_t voices[OPL_NUM_VOICES * 2]; ++static opl_voice_t *voice_free_list[OPL_NUM_VOICES * 2]; ++static opl_voice_t *voice_alloced_list[OPL_NUM_VOICES * 2]; ++static int voice_free_num; ++static int voice_alloced_num; ++static int opl_opl3mode; ++static int num_opl_voices; ++ ++// Data for each channel. ++ ++static opl_channel_data_t channels[MIDI_CHANNELS_PER_TRACK]; ++ ++// Track data for playing tracks: ++ ++static opl_track_data_t *tracks; ++static unsigned int num_tracks = 0; ++static unsigned int running_tracks = 0; ++static boolean song_looping; ++ ++// Tempo control variables ++ ++static unsigned int ticks_per_beat; ++static unsigned int us_per_beat; ++ ++// Mini-log of recently played percussion instruments: ++ ++static uint8_t last_perc[PERCUSSION_LOG_LEN]; ++static unsigned int last_perc_count; ++ ++// Configuration file variable, containing the port number for the ++// adlib chip. ++ ++char *snd_dmxoption = ""; ++int opl_io_port = 0x388; ++ ++// If true, OPL sound channels are reversed to their correct arrangement ++// (as intended by the MIDI standard) rather than the backwards one ++// used by DMX due to a bug. ++ ++static boolean opl_stereo_correct = false; ++ ++// Load instrument table from GENMIDI lump: ++ ++static boolean LoadInstrumentTable(void) ++{ ++ byte *lump; ++ ++ lump = W_CacheLumpName(DEH_String("genmidi"), PU_STATIC); ++ ++ // DMX does not check header ++ ++ main_instrs = (genmidi_instr_t *) (lump + strlen(GENMIDI_HEADER)); ++ percussion_instrs = main_instrs + GENMIDI_NUM_INSTRS; ++ main_instr_names = ++ (char (*)[32]) (percussion_instrs + GENMIDI_NUM_PERCUSSION); ++ percussion_names = main_instr_names + GENMIDI_NUM_INSTRS; ++ ++ return true; ++} ++ ++// Get the next available voice from the freelist. ++ ++static opl_voice_t *GetFreeVoice(void) ++{ ++ opl_voice_t *result; ++ int i; ++ ++ // None available? ++ ++ if (voice_free_num == 0) ++ { ++ return NULL; ++ } ++ ++ // Remove from free list ++ ++ result = voice_free_list[0]; ++ ++ voice_free_num--; ++ ++ for (i = 0; i < voice_free_num; i++) ++ { ++ voice_free_list[i] = voice_free_list[i + 1]; ++ } ++ ++ // Add to allocated list ++ ++ voice_alloced_list[voice_alloced_num++] = result; ++ ++ return result; ++} ++ ++// Release a voice back to the freelist. ++ ++static void VoiceKeyOff(opl_voice_t *voice); ++ ++static void ReleaseVoice(int index) ++{ ++ opl_voice_t *voice; ++ boolean double_voice; ++ int i; ++ ++ // Doom 2 1.666 OPL crash emulation. ++ if (index >= voice_alloced_num) ++ { ++ ++ voice_alloced_num = 0; ++ voice_free_num = 0; ++ return; ++ } ++ ++ voice = voice_alloced_list[index]; ++ ++ VoiceKeyOff(voice); ++ ++ voice->channel = NULL; ++ voice->note = 0; ++ ++ double_voice = voice->current_instr_voice != 0; ++ ++ // Remove from alloced list. ++ ++ voice_alloced_num--; ++ ++ for (i = index; i < voice_alloced_num; i++) ++ { ++ voice_alloced_list[i] = voice_alloced_list[i + 1]; ++ } ++ ++ // Search to the end of the freelist (This is how Doom behaves!) ++ ++ voice_free_list[voice_free_num++] = voice; ++ ++ if (double_voice && opl_drv_ver < opl_doom_1_9) ++ { ++ ReleaseVoice(index); ++ } ++} ++ ++// Load data to the specified operator ++ ++static void LoadOperatorData(int operator, genmidi_op_t *data, ++ boolean max_level, unsigned int *volume) ++{ ++ int level; ++ ++ // The scale and level fields must be combined for the level register. ++ // For the carrier wave we always set the maximum level. ++ ++ level = data->scale; ++ ++ if (max_level) ++ { ++ level |= 0x3f; ++ } ++ else ++ { ++ level |= data->level; ++ } ++ ++ *volume = level; ++ ++ OPL_WriteRegister(OPL_REGS_LEVEL + operator, level); ++ OPL_WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo); ++ OPL_WriteRegister(OPL_REGS_ATTACK + operator, data->attack); ++ OPL_WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain); ++ OPL_WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform); ++} ++ ++// Set the instrument for a particular voice. ++ ++static void SetVoiceInstrument(opl_voice_t *voice, ++ genmidi_instr_t *instr, ++ unsigned int instr_voice) ++{ ++ genmidi_voice_t *data; ++ unsigned int modulating; ++ ++ // Instrument already set for this channel? ++ ++ if (voice->current_instr == instr ++ && voice->current_instr_voice == instr_voice) ++ { ++ return; ++ } ++ ++ voice->current_instr = instr; ++ voice->current_instr_voice = instr_voice; ++ ++ data = &instr->voices[instr_voice]; ++ ++ // Are we usind modulated feedback mode? ++ ++ modulating = (data->feedback & 0x01) == 0; ++ ++ // Doom loads the second operator first, then the first. ++ // The carrier is set to minimum volume until the voice volume ++ // is set in SetVoiceVolume (below). If we are not using ++ // modulating mode, we must set both to minimum volume. ++ ++ LoadOperatorData(voice->op2 | voice->array, &data->carrier, true, ++ &voice->car_volume); ++ LoadOperatorData(voice->op1 | voice->array, &data->modulator, !modulating, ++ &voice->mod_volume); ++ ++ // Set feedback register that control the connection between the ++ // two operators. Turn on bits in the upper nybble; I think this ++ // is for OPL3, where it turns on channel A/B. ++ ++ OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array, ++ data->feedback | voice->reg_pan); ++ ++ // Calculate voice priority. ++ ++ voice->priority = 0x0f - (data->carrier.attack >> 4) ++ + 0x0f - (data->carrier.sustain & 0x0f); ++} ++ ++static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) ++{ ++ genmidi_voice_t *opl_voice; ++ unsigned int midi_volume; ++ unsigned int full_volume; ++ unsigned int car_volume; ++ unsigned int mod_volume; ++ ++ voice->note_volume = volume; ++ ++ opl_voice = &voice->current_instr->voices[voice->current_instr_voice]; ++ ++ // Multiply note volume and channel volume to get the actual volume. ++ ++ midi_volume = 2 * (volume_mapping_table[voice->channel->volume] + 1); ++ ++ full_volume = (volume_mapping_table[voice->note_volume] * midi_volume) ++ >> 9; ++ ++ // The volume value to use in the register: ++ car_volume = 0x3f - full_volume; ++ ++ // Update the volume register(s) if necessary. ++ ++ if (car_volume != (voice->car_volume & 0x3f)) ++ { ++ voice->car_volume = car_volume | (voice->car_volume & 0xc0); ++ ++ OPL_WriteRegister((OPL_REGS_LEVEL + voice->op2) | voice->array, ++ voice->car_volume); ++ ++ // If we are using non-modulated feedback mode, we must set the ++ // volume for both voices. ++ ++ if ((opl_voice->feedback & 0x01) != 0 ++ && opl_voice->modulator.level != 0x3f) ++ { ++ mod_volume = opl_voice->modulator.level; ++ if (mod_volume < car_volume) ++ { ++ mod_volume = car_volume; ++ } ++ ++ mod_volume |= voice->mod_volume & 0xc0; ++ ++ if(mod_volume != voice->mod_volume) ++ { ++ voice->mod_volume = mod_volume; ++ OPL_WriteRegister((OPL_REGS_LEVEL + voice->op1) | voice->array, ++ mod_volume | ++ (opl_voice->modulator.scale & 0xc0)); ++ } ++ } ++ } ++} ++ ++static void SetVoicePan(opl_voice_t *voice, unsigned int pan) ++{ ++ genmidi_voice_t *opl_voice; ++ ++ voice->reg_pan = pan; ++ opl_voice = &voice->current_instr->voices[voice->current_instr_voice];; ++ ++ OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array, ++ opl_voice->feedback | pan); ++} ++ ++// Initialize the voice table and freelist ++ ++static void InitVoices(void) ++{ ++ int i; ++ ++ // Start with an empty free list. ++ ++ voice_free_num = num_opl_voices; ++ voice_alloced_num = 0; ++ ++ // Initialize each voice. ++ ++ for (i = 0; i < num_opl_voices; ++i) ++ { ++ voices[i].index = i % OPL_NUM_VOICES; ++ voices[i].op1 = voice_operators[0][i % OPL_NUM_VOICES]; ++ voices[i].op2 = voice_operators[1][i % OPL_NUM_VOICES]; ++ voices[i].array = (i / OPL_NUM_VOICES) << 8; ++ voices[i].current_instr = NULL; ++ ++ // Add this voice to the freelist. ++ ++ voice_free_list[i] = &voices[i]; ++ } ++} ++ ++static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume, ++ boolean clip_start); ++ ++// Set music volume (0 - 127) ++ ++static void I_OPL_SetMusicVolume(int volume) ++{ ++ unsigned int i; ++ ++ if (current_music_volume == volume) ++ { ++ return; ++ } ++ ++ // Internal state variable. ++ ++ current_music_volume = volume; ++ ++ // Update the volume of all voices. ++ ++ for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i) ++ { ++ if (i == 15) ++ { ++ SetChannelVolume(&channels[i], volume, false); ++ } ++ else ++ { ++ SetChannelVolume(&channels[i], channels[i].volume_base, false); ++ } ++ } ++} ++ ++static void VoiceKeyOff(opl_voice_t *voice) ++{ ++ OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array, ++ voice->freq >> 8); ++} ++ ++static opl_channel_data_t *TrackChannelForEvent(opl_track_data_t *track, ++ midi_event_t *event) ++{ ++ unsigned int channel_num = event->data.channel.channel; ++ ++ // MIDI uses track #9 for percussion, but for MUS it's track #15 ++ // instead. Because DMX works on MUS data internally, we need to ++ // swap back to the MUS version of the channel number. ++ if (channel_num == 9) ++ { ++ channel_num = 15; ++ } ++ else if (channel_num == 15) ++ { ++ channel_num = 9; ++ } ++ ++ return &channels[channel_num]; ++} ++ ++// Get the frequency that we should be using for a voice. ++ ++static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event) ++{ ++ opl_channel_data_t *channel; ++ int i; ++ unsigned int key; ++ ++/* ++ printf("note off: channel %i, %i, %i\n", ++ event->data.channel.channel, ++ event->data.channel.param1, ++ event->data.channel.param2); ++*/ ++ ++ channel = TrackChannelForEvent(track, event); ++ key = event->data.channel.param1; ++ ++ // Turn off voices being used to play this key. ++ // If it is a double voice instrument there will be two. ++ ++ for (i = 0; i < voice_alloced_num; i++) ++ { ++ if (voice_alloced_list[i]->channel == channel ++ && voice_alloced_list[i]->key == key) ++ { ++ // Finished with this voice now. ++ ++ ReleaseVoice(i); ++ ++ i--; ++ } ++ } ++} ++ ++// When all voices are in use, we must discard an existing voice to ++// play a new note. Find and free an existing voice. The channel ++// passed to the function is the channel for the new note to be ++// played. ++ ++static void ReplaceExistingVoice(void) ++{ ++ int i; ++ int result; ++ ++ // Check the allocated voices, if we find an instrument that is ++ // of a lower priority to the new instrument, discard it. ++ // If a voice is being used to play the second voice of an instrument, ++ // use that, as second voices are non-essential. ++ // Lower numbered MIDI channels implicitly have a higher priority ++ // than higher-numbered channels, eg. MIDI channel 1 is never ++ // discarded for MIDI channel 2. ++ ++ result = 0; ++ ++ for (i = 0; i < voice_alloced_num; i++) ++ { ++ if (voice_alloced_list[i]->current_instr_voice != 0 ++ || voice_alloced_list[i]->channel ++ >= voice_alloced_list[result]->channel) ++ { ++ result = i; ++ } ++ } ++ ++ ReleaseVoice(result); ++} ++ ++// Alternate versions of ReplaceExistingVoice() used when emulating old ++// versions of the DMX library used in Doom 1.666, Heretic and Hexen. ++ ++static void ReplaceExistingVoiceDoom1(void) ++{ ++ int i; ++ int result; ++ ++ result = 0; ++ ++ for (i = 0; i < voice_alloced_num; i++) ++ { ++ if (voice_alloced_list[i]->channel ++ > voice_alloced_list[result]->channel) ++ { ++ result = i; ++ } ++ } ++ ++ ReleaseVoice(result); ++} ++ ++static void ReplaceExistingVoiceDoom2(opl_channel_data_t *channel) ++{ ++ int i; ++ int result; ++ int priority; ++ ++ result = 0; ++ ++ priority = 0x8000; ++ ++ for (i = 0; i < voice_alloced_num - 3; i++) ++ { ++ if (voice_alloced_list[i]->priority < priority ++ && voice_alloced_list[i]->channel >= channel) ++ { ++ priority = voice_alloced_list[i]->priority; ++ result = i; ++ } ++ } ++ ++ ReleaseVoice(result); ++} ++ ++ ++static unsigned int FrequencyForVoice(opl_voice_t *voice) ++{ ++ genmidi_voice_t *gm_voice; ++ signed int freq_index; ++ unsigned int octave; ++ unsigned int sub_index; ++ signed int note; ++ ++ note = voice->note; ++ ++ // Apply note offset. ++ // Don't apply offset if the instrument is a fixed note instrument. ++ ++ gm_voice = &voice->current_instr->voices[voice->current_instr_voice]; ++ ++ if ((SHORT(voice->current_instr->flags) & GENMIDI_FLAG_FIXED) == 0) ++ { ++ note += (signed short) SHORT(gm_voice->base_note_offset); ++ } ++ ++ // Avoid possible overflow due to base note offset: ++ ++ while (note < 0) ++ { ++ note += 12; ++ } ++ ++ while (note > 95) ++ { ++ note -= 12; ++ } ++ ++ freq_index = 64 + 32 * note + voice->channel->bend; ++ ++ // If this is the second voice of a double voice instrument, the ++ // frequency index can be adjusted by the fine tuning field. ++ ++ if (voice->current_instr_voice != 0) ++ { ++ freq_index += (voice->current_instr->fine_tuning / 2) - 64; ++ } ++ ++ if (freq_index < 0) ++ { ++ freq_index = 0; ++ } ++ ++ // The first 7 notes use the start of the table, while ++ // consecutive notes loop around the latter part. ++ ++ if (freq_index < 284) ++ { ++ return frequency_curve[freq_index]; ++ } ++ ++ sub_index = (freq_index - 284) % (12 * 32); ++ octave = (freq_index - 284) / (12 * 32); ++ ++ // Once the seventh octave is reached, things break down. ++ // We can only go up to octave 7 as a maximum anyway (the OPL ++ // register only has three bits for octave number), but for the ++ // notes in octave 7, the first five bits have octave=7, the ++ // following notes have octave=6. This 7/6 pattern repeats in ++ // following octaves (which are technically impossible to ++ // represent anyway). ++ ++ if (octave >= 7) ++ { ++ octave = 7; ++ } ++ ++ // Calculate the resulting register value to use for the frequency. ++ ++ return frequency_curve[sub_index + 284] | (octave << 10); ++} ++ ++// Update the frequency that a voice is programmed to use. ++ ++static void UpdateVoiceFrequency(opl_voice_t *voice) ++{ ++ unsigned int freq; ++ ++ // Calculate the frequency to use for this voice and update it ++ // if neccessary. ++ ++ freq = FrequencyForVoice(voice); ++ ++ if (voice->freq != freq) ++ { ++ OPL_WriteRegister((OPL_REGS_FREQ_1 + voice->index) | voice->array, ++ freq & 0xff); ++ OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array, ++ (freq >> 8) | 0x20); ++ ++ voice->freq = freq; ++ } ++} ++ ++// Program a single voice for an instrument. For a double voice ++// instrument (GENMIDI_FLAG_2VOICE), this is called twice for each ++// key on event. ++ ++static void VoiceKeyOn(opl_channel_data_t *channel, ++ genmidi_instr_t *instrument, ++ unsigned int instrument_voice, ++ unsigned int note, ++ unsigned int key, ++ unsigned int volume) ++{ ++ opl_voice_t *voice; ++ ++ if (!opl_opl3mode && opl_drv_ver == opl_doom1_1_666) ++ { ++ instrument_voice = 0; ++ } ++ ++ // Find a voice to use for this new note. ++ ++ voice = GetFreeVoice(); ++ ++ if (voice == NULL) ++ { ++ return; ++ } ++ ++ voice->channel = channel; ++ voice->key = key; ++ ++ // Work out the note to use. This is normally the same as ++ // the key, unless it is a fixed pitch instrument. ++ ++ if ((SHORT(instrument->flags) & GENMIDI_FLAG_FIXED) != 0) ++ { ++ voice->note = instrument->fixed_note; ++ } ++ else ++ { ++ voice->note = note; ++ } ++ ++ voice->reg_pan = channel->pan; ++ ++ // Program the voice with the instrument data: ++ ++ SetVoiceInstrument(voice, instrument, instrument_voice); ++ ++ // Set the volume level. ++ ++ SetVoiceVolume(voice, volume); ++ ++ // Write the frequency value to turn the note on. ++ ++ voice->freq = 0; ++ UpdateVoiceFrequency(voice); ++} ++ ++static void KeyOnEvent(opl_track_data_t *track, midi_event_t *event) ++{ ++ genmidi_instr_t *instrument; ++ opl_channel_data_t *channel; ++ unsigned int note, key, volume, voicenum; ++ boolean double_voice; ++ ++/* ++ printf("note on: channel %i, %i, %i\n", ++ event->data.channel.channel, ++ event->data.channel.param1, ++ event->data.channel.param2); ++*/ ++ ++ note = event->data.channel.param1; ++ key = event->data.channel.param1; ++ volume = event->data.channel.param2; ++ ++ // A volume of zero means key off. Some MIDI tracks, eg. the ones ++ // in AV.wad, use a second key on with a volume of zero to mean ++ // key off. ++ if (volume <= 0) ++ { ++ KeyOffEvent(track, event); ++ return; ++ } ++ ++ // The channel. ++ channel = TrackChannelForEvent(track, event); ++ ++ // Percussion channel is treated differently. ++ if (event->data.channel.channel == 9) ++ { ++ if (key < 35 || key > 81) ++ { ++ return; ++ } ++ ++ instrument = &percussion_instrs[key - 35]; ++ ++ last_perc[last_perc_count] = key; ++ last_perc_count = (last_perc_count + 1) % PERCUSSION_LOG_LEN; ++ note = 60; ++ } ++ else ++ { ++ instrument = channel->instrument; ++ } ++ ++ double_voice = (SHORT(instrument->flags) & GENMIDI_FLAG_2VOICE) != 0; ++ ++ switch (opl_drv_ver) ++ { ++ case opl_doom1_1_666: ++ voicenum = double_voice + 1; ++ if (!opl_opl3mode) ++ { ++ voicenum = 1; ++ } ++ while (voice_alloced_num > num_opl_voices - voicenum) ++ { ++ ReplaceExistingVoiceDoom1(); ++ } ++ ++ // Find and program a voice for this instrument. If this ++ // is a double voice instrument, we must do this twice. ++ ++ if (double_voice) ++ { ++ VoiceKeyOn(channel, instrument, 1, note, key, volume); ++ } ++ ++ VoiceKeyOn(channel, instrument, 0, note, key, volume); ++ break; ++ case opl_doom2_1_666: ++ if (voice_alloced_num == num_opl_voices) ++ { ++ ReplaceExistingVoiceDoom2(channel); ++ } ++ if (voice_alloced_num == num_opl_voices - 1 && double_voice) ++ { ++ ReplaceExistingVoiceDoom2(channel); ++ } ++ ++ // Find and program a voice for this instrument. If this ++ // is a double voice instrument, we must do this twice. ++ ++ if (double_voice) ++ { ++ VoiceKeyOn(channel, instrument, 1, note, key, volume); ++ } ++ ++ VoiceKeyOn(channel, instrument, 0, note, key, volume); ++ break; ++ default: ++ case opl_doom_1_9: ++ if (voice_free_num == 0) ++ { ++ ReplaceExistingVoice(); ++ } ++ ++ // Find and program a voice for this instrument. If this ++ // is a double voice instrument, we must do this twice. ++ ++ VoiceKeyOn(channel, instrument, 0, note, key, volume); ++ ++ if (double_voice) ++ { ++ VoiceKeyOn(channel, instrument, 1, note, key, volume); ++ } ++ break; ++ } ++} ++ ++static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event) ++{ ++ opl_channel_data_t *channel; ++ int instrument; ++ ++ // Set the instrument used on this channel. ++ ++ channel = TrackChannelForEvent(track, event); ++ instrument = event->data.channel.param1; ++ channel->instrument = &main_instrs[instrument]; ++ ++ // TODO: Look through existing voices that are turned on on this ++ // channel, and change the instrument. ++} ++ ++static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume, ++ boolean clip_start) ++{ ++ unsigned int i; ++ ++ channel->volume_base = volume; ++ ++ if (volume > current_music_volume) ++ { ++ volume = current_music_volume; ++ } ++ ++ if (clip_start && volume > start_music_volume) ++ { ++ volume = start_music_volume; ++ } ++ ++ channel->volume = volume; ++ ++ // Update all voices that this channel is using. ++ ++ for (i = 0; i < num_opl_voices; ++i) ++ { ++ if (voices[i].channel == channel) ++ { ++ SetVoiceVolume(&voices[i], voices[i].note_volume); ++ } ++ } ++} ++ ++static void SetChannelPan(opl_channel_data_t *channel, unsigned int pan) ++{ ++ unsigned int reg_pan; ++ unsigned int i; ++ ++ // The DMX library has the stereo channels backwards, maybe because ++ // Paul Radek had a Soundblaster card with the channels reversed, or ++ // perhaps it was just a bug in the OPL3 support that was never ++ // finished. By default we preserve this bug, but we also provide a ++ // secret DMXOPTION to fix it. ++ if (opl_stereo_correct) ++ { ++ pan = 144 - pan; ++ } ++ ++ if (opl_opl3mode) ++ { ++ if (pan >= 96) ++ { ++ reg_pan = 0x10; ++ } ++ else if (pan <= 48) ++ { ++ reg_pan = 0x20; ++ } ++ else ++ { ++ reg_pan = 0x30; ++ } ++ if (channel->pan != reg_pan) ++ { ++ channel->pan = reg_pan; ++ for (i = 0; i < num_opl_voices; i++) ++ { ++ if (voices[i].channel == channel) ++ { ++ SetVoicePan(&voices[i], reg_pan); ++ } ++ } ++ } ++ } ++} ++ ++// Handler for the MIDI_CONTROLLER_ALL_NOTES_OFF channel event. ++static void AllNotesOff(opl_channel_data_t *channel, unsigned int param) ++{ ++ int i; ++ ++ for (i = 0; i < voice_alloced_num; i++) ++ { ++ if (voice_alloced_list[i]->channel == channel) ++ { ++ // Finished with this voice now. ++ ++ ReleaseVoice(i); ++ ++ i--; ++ } ++ } ++} ++ ++static void ControllerEvent(opl_track_data_t *track, midi_event_t *event) ++{ ++ opl_channel_data_t *channel; ++ unsigned int controller; ++ unsigned int param; ++ ++/* ++ printf("change controller: channel %i, %i, %i\n", ++ event->data.channel.channel, ++ event->data.channel.param1, ++ event->data.channel.param2); ++*/ ++ ++ channel = TrackChannelForEvent(track, event); ++ controller = event->data.channel.param1; ++ param = event->data.channel.param2; ++ ++ switch (controller) ++ { ++ case MIDI_CONTROLLER_VOLUME_MSB: ++ SetChannelVolume(channel, param, true); ++ break; ++ ++ case MIDI_CONTROLLER_PAN: ++ SetChannelPan(channel, param); ++ break; ++ ++ case MIDI_CONTROLLER_ALL_NOTES_OFF: ++ AllNotesOff(channel, param); ++ break; ++ ++ default: ++#ifdef OPL_MIDI_DEBUG ++ fprintf(stderr, "Unknown MIDI controller type: %u\n", controller); ++#endif ++ break; ++ } ++} ++ ++// Process a pitch bend event. ++ ++static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event) ++{ ++ opl_channel_data_t *channel; ++ int i; ++ opl_voice_t *voice_updated_list[OPL_NUM_VOICES * 2]; ++ unsigned int voice_updated_num = 0; ++ opl_voice_t *voice_not_updated_list[OPL_NUM_VOICES * 2]; ++ unsigned int voice_not_updated_num = 0; ++ ++ // Update the channel bend value. Only the MSB of the pitch bend ++ // value is considered: this is what Doom does. ++ ++ channel = TrackChannelForEvent(track, event); ++ channel->bend = event->data.channel.param2 - 64; ++ ++ // Update all voices for this channel. ++ ++ for (i = 0; i < voice_alloced_num; ++i) ++ { ++ if (voice_alloced_list[i]->channel == channel) ++ { ++ UpdateVoiceFrequency(voice_alloced_list[i]); ++ voice_updated_list[voice_updated_num++] = voice_alloced_list[i]; ++ } ++ else ++ { ++ voice_not_updated_list[voice_not_updated_num++] = ++ voice_alloced_list[i]; ++ } ++ } ++ ++ for (i = 0; i < voice_not_updated_num; i++) ++ { ++ voice_alloced_list[i] = voice_not_updated_list[i]; ++ } ++ ++ for (i = 0; i < voice_updated_num; i++) ++ { ++ voice_alloced_list[i + voice_not_updated_num] = voice_updated_list[i]; ++ } ++} ++ ++static void MetaSetTempo(unsigned int tempo) ++{ ++ OPL_AdjustCallbacks((float) us_per_beat / tempo); ++ us_per_beat = tempo; ++} ++ ++// Process a meta event. ++ ++static void MetaEvent(opl_track_data_t *track, midi_event_t *event) ++{ ++ byte *data = event->data.meta.data; ++ unsigned int data_len = event->data.meta.length; ++ ++ switch (event->data.meta.type) ++ { ++ // Things we can just ignore. ++ ++ case MIDI_META_SEQUENCE_NUMBER: ++ case MIDI_META_TEXT: ++ case MIDI_META_COPYRIGHT: ++ case MIDI_META_TRACK_NAME: ++ case MIDI_META_INSTR_NAME: ++ case MIDI_META_LYRICS: ++ case MIDI_META_MARKER: ++ case MIDI_META_CUE_POINT: ++ case MIDI_META_SEQUENCER_SPECIFIC: ++ break; ++ ++ case MIDI_META_SET_TEMPO: ++ if (data_len == 3) ++ { ++ MetaSetTempo((data[0] << 16) | (data[1] << 8) | data[2]); ++ } ++ break; ++ ++ // End of track - actually handled when we run out of events ++ // in the track, see below. ++ ++ case MIDI_META_END_OF_TRACK: ++ break; ++ ++ default: ++#ifdef OPL_MIDI_DEBUG ++ fprintf(stderr, "Unknown MIDI meta event type: %u\n", ++ event->data.meta.type); ++#endif ++ break; ++ } ++} ++ ++// Process a MIDI event from a track. ++ ++static void ProcessEvent(opl_track_data_t *track, midi_event_t *event) ++{ ++ switch (event->event_type) ++ { ++ case MIDI_EVENT_NOTE_OFF: ++ KeyOffEvent(track, event); ++ break; ++ ++ case MIDI_EVENT_NOTE_ON: ++ KeyOnEvent(track, event); ++ break; ++ ++ case MIDI_EVENT_CONTROLLER: ++ ControllerEvent(track, event); ++ break; ++ ++ case MIDI_EVENT_PROGRAM_CHANGE: ++ ProgramChangeEvent(track, event); ++ break; ++ ++ case MIDI_EVENT_PITCH_BEND: ++ PitchBendEvent(track, event); ++ break; ++ ++ case MIDI_EVENT_META: ++ MetaEvent(track, event); ++ break; ++ ++ // SysEx events can be ignored. ++ ++ case MIDI_EVENT_SYSEX: ++ case MIDI_EVENT_SYSEX_SPLIT: ++ break; ++ ++ default: ++#ifdef OPL_MIDI_DEBUG ++ fprintf(stderr, "Unknown MIDI event type %i\n", event->event_type); ++#endif ++ break; ++ } ++} ++ ++static void ScheduleTrack(opl_track_data_t *track); ++static void InitChannel(opl_channel_data_t *channel); ++ ++// Restart a song from the beginning. ++ ++static void RestartSong(void *unused) ++{ ++ unsigned int i; ++ ++ running_tracks = num_tracks; ++ ++ start_music_volume = current_music_volume; ++ ++ for (i = 0; i < num_tracks; ++i) ++ { ++ MIDI_RestartIterator(tracks[i].iter); ++ ScheduleTrack(&tracks[i]); ++ } ++ ++ for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i) ++ { ++ InitChannel(&channels[i]); ++ } ++} ++ ++// Callback function invoked when another event needs to be read from ++// a track. ++ ++static void TrackTimerCallback(void *arg) ++{ ++ opl_track_data_t *track = arg; ++ midi_event_t *event; ++ ++ // Get the next event and process it. ++ ++ if (!MIDI_GetNextEvent(track->iter, &event)) ++ { ++ return; ++ } ++ ++ ProcessEvent(track, event); ++ ++ // End of track? ++ ++ if (event->event_type == MIDI_EVENT_META ++ && event->data.meta.type == MIDI_META_END_OF_TRACK) ++ { ++ --running_tracks; ++ ++ // When all tracks have finished, restart the song. ++ // Don't restart the song immediately, but wait for 5ms ++ // before triggering a restart. Otherwise it is possible ++ // to construct an empty MIDI file that causes the game ++ // to lock up in an infinite loop. (5ms should be short ++ // enough not to be noticeable by the listener). ++ ++ if (running_tracks <= 0 && song_looping) ++ { ++ OPL_SetCallback(5000, RestartSong, NULL); ++ } ++ ++ return; ++ } ++ ++ // Reschedule the callback for the next event in the track. ++ ++ ScheduleTrack(track); ++} ++ ++static void ScheduleTrack(opl_track_data_t *track) ++{ ++ unsigned int nticks; ++ uint64_t us; ++ ++ // Get the number of microseconds until the next event. ++ ++ nticks = MIDI_GetDeltaTime(track->iter); ++ us = ((uint64_t) nticks * us_per_beat) / ticks_per_beat; ++ ++ // Set a timer to be invoked when the next event is ++ // ready to play. ++ ++ OPL_SetCallback(us, TrackTimerCallback, track); ++} ++ ++// Initialize a channel. ++ ++static void InitChannel(opl_channel_data_t *channel) ++{ ++ // TODO: Work out sensible defaults? ++ ++ channel->instrument = &main_instrs[0]; ++ channel->volume = current_music_volume; ++ channel->volume_base = 100; ++ if (channel->volume > channel->volume_base) ++ { ++ channel->volume = channel->volume_base; ++ } ++ channel->pan = 0x30; ++ channel->bend = 0; ++} ++ ++// Start a MIDI track playing: ++ ++static void StartTrack(midi_file_t *file, unsigned int track_num) ++{ ++ opl_track_data_t *track; ++ ++ track = &tracks[track_num]; ++ track->iter = MIDI_IterateTrack(file, track_num); ++ ++ // Schedule the first event. ++ ++ ScheduleTrack(track); ++} ++ ++// Start playing a mid ++ ++static void I_OPL_PlaySong(void *handle, boolean looping) ++{ ++ midi_file_t *file; ++ unsigned int i; ++ ++ if (!music_initialized || handle == NULL) ++ { ++ return; ++ } ++ ++ file = handle; ++ ++ // Allocate track data. ++ ++ tracks = malloc(MIDI_NumTracks(file) * sizeof(opl_track_data_t)); ++ ++ num_tracks = MIDI_NumTracks(file); ++ running_tracks = num_tracks; ++ song_looping = looping; ++ ++ ticks_per_beat = MIDI_GetFileTimeDivision(file); ++ ++ // Default is 120 bpm. ++ // TODO: this is wrong ++ ++ us_per_beat = 500 * 1000; ++ ++ start_music_volume = current_music_volume; ++ ++ for (i = 0; i < num_tracks; ++i) ++ { ++ StartTrack(file, i); ++ } ++ ++ for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i) ++ { ++ InitChannel(&channels[i]); ++ } ++ ++ // If the music was previously paused, it needs to be unpaused; playing ++ // a new song implies that we turn off pause. This matches vanilla ++ // behavior of the DMX library, and some of the higher-level code in ++ // s_sound.c relies on this. ++ OPL_SetPaused(0); ++} ++ ++static void I_OPL_PauseSong(void) ++{ ++ unsigned int i; ++ ++ if (!music_initialized) ++ { ++ return; ++ } ++ ++ // Pause OPL callbacks. ++ ++ OPL_SetPaused(1); ++ ++ // Turn off all main instrument voices (not percussion). ++ // This is what Vanilla does. ++ ++ for (i = 0; i < num_opl_voices; ++i) ++ { ++ if (voices[i].channel != NULL ++ && voices[i].current_instr < percussion_instrs) ++ { ++ VoiceKeyOff(&voices[i]); ++ } ++ } ++} ++ ++static void I_OPL_ResumeSong(void) ++{ ++ if (!music_initialized) ++ { ++ return; ++ } ++ ++ OPL_SetPaused(0); ++} ++ ++static void I_OPL_StopSong(void) ++{ ++ unsigned int i; ++ ++ if (!music_initialized) ++ { ++ return; ++ } ++ ++ OPL_Lock(); ++ ++ // Stop all playback. ++ ++ OPL_ClearCallbacks(); ++ ++ // Free all voices. ++ ++ for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i) ++ { ++ AllNotesOff(&channels[i], 0); ++ } ++ ++ // Free all track data. ++ ++ for (i = 0; i < num_tracks; ++i) ++ { ++ MIDI_FreeIterator(tracks[i].iter); ++ } ++ ++ free(tracks); ++ ++ tracks = NULL; ++ num_tracks = 0; ++ ++ OPL_Unlock(); ++} ++ ++static void I_OPL_UnRegisterSong(void *handle) ++{ ++ if (!music_initialized) ++ { ++ return; ++ } ++ ++ if (handle != NULL) ++ { ++ MIDI_FreeFile(handle); ++ } ++} ++ ++// Determine whether memory block is a .mid file ++ ++static boolean IsMid(byte *mem, int len) ++{ ++ return len > 4 && !memcmp(mem, "MThd", 4); ++} ++ ++static boolean ConvertMus(byte *musdata, int len, char *filename) ++{ ++ MEMFILE *instream; ++ MEMFILE *outstream; ++ void *outbuf; ++ size_t outbuf_len; ++ int result; ++ ++ instream = mem_fopen_read(musdata, len); ++ outstream = mem_fopen_write(); ++ ++ result = mus2mid(instream, outstream); ++ ++ if (result == 0) ++ { ++ mem_get_buf(outstream, &outbuf, &outbuf_len); ++ ++ M_WriteFile(filename, outbuf, outbuf_len); ++ } ++ ++ mem_fclose(instream); ++ mem_fclose(outstream); ++ ++ return result; ++} ++ ++static void *I_OPL_RegisterSong(void *data, int len) ++{ ++ midi_file_t *result; ++ char *filename; ++ ++ if (!music_initialized) ++ { ++ return NULL; ++ } ++ ++ // MUS files begin with "MUS" ++ // Reject anything which doesnt have this signature ++ ++ filename = M_TempFile("doom.mid"); ++ ++ if (IsMid(data, len) && len < MAXMIDLENGTH) ++ { ++ M_WriteFile(filename, data, len); ++ } ++ else ++ { ++ // Assume a MUS file and try to convert ++ ++ ConvertMus(data, len, filename); ++ } ++ ++ result = MIDI_LoadFile(filename); ++ ++ if (result == NULL) ++ { ++ fprintf(stderr, "I_OPL_RegisterSong: Failed to load MID.\n"); ++ } ++ ++ // remove file now ++ ++ remove(filename); ++ free(filename); ++ ++ return result; ++} ++ ++// Is the song playing? ++ ++static boolean I_OPL_MusicIsPlaying(void) ++{ ++ if (!music_initialized) ++ { ++ return false; ++ } ++ ++ return num_tracks > 0; ++} ++ ++// Shutdown music ++ ++static void I_OPL_ShutdownMusic(void) ++{ ++ if (music_initialized) ++ { ++ // Stop currently-playing track, if there is one: ++ ++ I_OPL_StopSong(); ++ ++ OPL_Shutdown(); ++ ++ // Release GENMIDI lump ++ ++ W_ReleaseLumpName(DEH_String("genmidi")); ++ ++ music_initialized = false; ++ } ++} ++ ++// Initialize music subsystem ++ ++static boolean I_OPL_InitMusic(void) ++{ ++ const char *dmxoption; ++ opl_init_result_t chip_type; ++ ++ OPL_SetSampleRate(snd_samplerate); ++ ++ chip_type = OPL_Init(opl_io_port); ++ if (chip_type == OPL_INIT_NONE) ++ { ++ printf("Dude. The Adlib isn't responding.\n"); ++ return false; ++ } ++ ++ // The DMXOPTION variable must be set to enable OPL3 support. ++ // As an extension, we also allow it to be set from the config file. ++ dmxoption = getenv("DMXOPTION"); ++ if (dmxoption == NULL) ++ { ++ dmxoption = snd_dmxoption != NULL ? snd_dmxoption : ""; ++ } ++ ++ if (chip_type == OPL_INIT_OPL3 && strstr(dmxoption, "-opl3") != NULL) ++ { ++ opl_opl3mode = 1; ++ num_opl_voices = OPL_NUM_VOICES * 2; ++ } ++ else ++ { ++ opl_opl3mode = 0; ++ num_opl_voices = OPL_NUM_VOICES; ++ } ++ ++ // Secret, undocumented DMXOPTION that reverses the stereo channels ++ // into their correct orientation. ++ opl_stereo_correct = strstr(dmxoption, "-reverse") != NULL; ++ ++ // Initialize all registers. ++ ++ OPL_InitRegisters(opl_opl3mode); ++ ++ // Load instruments from GENMIDI lump: ++ ++ if (!LoadInstrumentTable()) ++ { ++ OPL_Shutdown(); ++ return false; ++ } ++ ++ InitVoices(); ++ ++ tracks = NULL; ++ num_tracks = 0; ++ music_initialized = true; ++ ++ return true; ++} ++ ++static snddevice_t music_opl_devices[] = ++{ ++ SNDDEVICE_ADLIB, ++ SNDDEVICE_SB, ++}; ++ ++music_module_t music_opl_module = ++{ ++ music_opl_devices, ++ arrlen(music_opl_devices), ++ I_OPL_InitMusic, ++ I_OPL_ShutdownMusic, ++ I_OPL_SetMusicVolume, ++ I_OPL_PauseSong, ++ I_OPL_ResumeSong, ++ I_OPL_RegisterSong, ++ I_OPL_UnRegisterSong, ++ I_OPL_PlaySong, ++ I_OPL_StopSong, ++ I_OPL_MusicIsPlaying, ++ NULL, // Poll ++}; ++ ++void I_SetOPLDriverVer(opl_driver_ver_t ver) ++{ ++ opl_drv_ver = ver; ++} ++ ++//---------------------------------------------------------------------- ++// ++// Development / debug message generation, to help developing GENMIDI ++// lumps. ++// ++//---------------------------------------------------------------------- ++ ++static int NumActiveChannels(void) ++{ ++ int i; ++ ++ for (i = MIDI_CHANNELS_PER_TRACK - 1; i >= 0; --i) ++ { ++ if (channels[i].instrument != &main_instrs[0]) ++ { ++ return i + 1; ++ } ++ } ++ ++ return 0; ++} ++ ++static int ChannelInUse(opl_channel_data_t *channel) ++{ ++ int i; ++ ++ for (i = 0; i < voice_alloced_num; i++) ++ { ++ if (voice_alloced_list[i]->channel == channel) ++ { ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++void I_OPL_DevMessages(char *result, size_t result_len) ++{ ++ char tmp[80]; ++ int instr_num; ++ int lines; ++ int i; ++ ++ if (num_tracks == 0) ++ { ++ M_snprintf(result, result_len, "No OPL track!"); ++ return; ++ } ++ ++ M_snprintf(result, result_len, "Tracks:\n"); ++ lines = 1; ++ ++ for (i = 0; i < NumActiveChannels(); ++i) ++ { ++ if (channels[i].instrument == NULL) ++ { ++ continue; ++ } ++ ++ instr_num = channels[i].instrument - main_instrs; ++ ++ M_snprintf(tmp, sizeof(tmp), ++ "chan %i: %c i#%i (%s)\n", ++ i, ++ ChannelInUse(&channels[i]) ? '\'' : ' ', ++ instr_num + 1, ++ main_instr_names[instr_num]); ++ M_StringConcat(result, tmp, result_len); ++ ++ ++lines; ++ } ++ ++ M_snprintf(tmp, sizeof(tmp), "\nLast percussion:\n"); ++ M_StringConcat(result, tmp, result_len); ++ lines += 2; ++ ++ i = (last_perc_count + PERCUSSION_LOG_LEN - 1) % PERCUSSION_LOG_LEN; ++ ++ do { ++ if (last_perc[i] == 0) ++ { ++ break; ++ } ++ ++ M_snprintf(tmp, sizeof(tmp), ++ "%cp#%i (%s)\n", ++ i == 0 ? '\'' : ' ', ++ last_perc[i], ++ percussion_names[last_perc[i] - 35]); ++ M_StringConcat(result, tmp, result_len); ++ ++lines; ++ ++ i = (i + PERCUSSION_LOG_LEN - 1) % PERCUSSION_LOG_LEN; ++ } while (lines < 25 && i != last_perc_count); ++} ++ +--- a/src/i_sound.c ++++ b/src/i_sound.c +@@ -100,6 +100,7 @@ static sound_module_t *sound_modules[] = + static music_module_t *music_modules[] = + { + #ifdef FEATURE_SOUND ++ &music_opl_module, + &music_sdl_module, + #endif + NULL, +@@ -158,14 +159,14 @@ static void InitMusicModule(void) + { + int i; + +- music_module = &music_sdl_module; return; ++ music_module = NULL; + +- /*for (i=0; music_modules[i] != NULL; ++i) ++ for (i=0; music_modules[i] != NULL; ++i) + { + // Is the music device in the list of devices supported + // by this module? + +- if (SndDeviceInList(snd_musicdevice, ++ if (SndDeviceInList(snd_musicdevice, + music_modules[i]->sound_devices, + music_modules[i]->num_sound_devices)) + { +@@ -177,7 +178,7 @@ static void InitMusicModule(void) + return; + } + } +- }*/ ++ } + } + + // +--- /dev/null ++++ b/src/midifile.c +@@ -0,0 +1,846 @@ ++// ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// Reading of MIDI files. ++// ++ ++#include ++#include ++#include ++#include ++ ++#include "doomtype.h" ++#include "i_swap.h" ++#include "i_system.h" ++#include "m_misc.h" ++#include "midifile.h" ++ ++// SDL renamed the byte-swapping macros between SDL2 and SDL3, and the ++// helpers below are not present in this codebase's older base. Provide ++// thin shims so the imported chocolate-doom sources build unmodified. ++#define SDL_SwapBE16(x) SDL_Swap16BE(x) ++#define SDL_SwapBE32(x) SDL_Swap32BE(x) ++#define I_Realloc(ptr, size) realloc((ptr), (size)) ++#define M_fopen(name, mode) fopen((name), (mode)) ++ ++#define HEADER_CHUNK_ID "MThd" ++#define TRACK_CHUNK_ID "MTrk" ++#define MAX_BUFFER_SIZE 0x10000 ++ ++// haleyjd 09/09/10: packing required ++#ifdef _MSC_VER ++#pragma pack(push, 1) ++#endif ++ ++typedef PACKED_STRUCT ( ++{ ++ byte chunk_id[4]; ++ unsigned int chunk_size; ++}) chunk_header_t; ++ ++typedef PACKED_STRUCT ( ++{ ++ chunk_header_t chunk_header; ++ unsigned short format_type; ++ unsigned short num_tracks; ++ unsigned short time_division; ++}) midi_header_t; ++ ++// haleyjd 09/09/10: packing off. ++#ifdef _MSC_VER ++#pragma pack(pop) ++#endif ++ ++typedef struct ++{ ++ // Length in bytes: ++ ++ unsigned int data_len; ++ ++ // Events in this track: ++ ++ midi_event_t *events; ++ int num_events; ++} midi_track_t; ++ ++struct midi_track_iter_s ++{ ++ midi_track_t *track; ++ unsigned int position; ++ unsigned int loop_point; ++}; ++ ++struct midi_file_s ++{ ++ midi_header_t header; ++ ++ // All tracks in this file: ++ midi_track_t *tracks; ++ unsigned int num_tracks; ++ ++ // Data buffer used to store data read for SysEx or meta events: ++ byte *buffer; ++ unsigned int buffer_size; ++}; ++ ++// Check the header of a chunk: ++ ++static boolean CheckChunkHeader(chunk_header_t *chunk, ++ const char *expected_id) ++{ ++ boolean result; ++ ++ result = (memcmp((char *) chunk->chunk_id, expected_id, 4) == 0); ++ ++ if (!result) ++ { ++ fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header, " ++ "got '%c%c%c%c'\n", ++ expected_id, ++ chunk->chunk_id[0], chunk->chunk_id[1], ++ chunk->chunk_id[2], chunk->chunk_id[3]); ++ } ++ ++ return result; ++} ++ ++// Read a single byte. Returns false on error. ++ ++static boolean ReadByte(byte *result, FILE *stream) ++{ ++ int c; ++ ++ c = fgetc(stream); ++ ++ if (c == EOF) ++ { ++ fprintf(stderr, "ReadByte: Unexpected end of file\n"); ++ return false; ++ } ++ else ++ { ++ *result = (byte) c; ++ ++ return true; ++ } ++} ++ ++// Read a variable-length value. ++ ++static boolean ReadVariableLength(unsigned int *result, FILE *stream) ++{ ++ int i; ++ byte b = 0; ++ ++ *result = 0; ++ ++ for (i=0; i<4; ++i) ++ { ++ if (!ReadByte(&b, stream)) ++ { ++ fprintf(stderr, "ReadVariableLength: Error while reading " ++ "variable-length value\n"); ++ return false; ++ } ++ ++ // Insert the bottom seven bits from this byte. ++ ++ *result <<= 7; ++ *result |= b & 0x7f; ++ ++ // If the top bit is not set, this is the end. ++ ++ if ((b & 0x80) == 0) ++ { ++ return true; ++ } ++ } ++ ++ fprintf(stderr, "ReadVariableLength: Variable-length value too " ++ "long: maximum of four bytes\n"); ++ return false; ++} ++ ++// Read a byte sequence into the data buffer. ++ ++static void *ReadByteSequence(unsigned int num_bytes, FILE *stream) ++{ ++ unsigned int i; ++ byte *result; ++ ++ // Allocate a buffer. Allocate one extra byte, as malloc(0) is ++ // non-portable. ++ ++ result = malloc(num_bytes + 1); ++ ++ if (result == NULL) ++ { ++ fprintf(stderr, "ReadByteSequence: Failed to allocate buffer\n"); ++ return NULL; ++ } ++ ++ // Read the data: ++ ++ for (i=0; ievent_type = event_type & 0xf0; ++ event->data.channel.channel = event_type & 0x0f; ++ ++ // Read parameters: ++ ++ if (!ReadByte(&b, stream)) ++ { ++ fprintf(stderr, "ReadChannelEvent: Error while reading channel " ++ "event parameters\n"); ++ return false; ++ } ++ ++ event->data.channel.param1 = b; ++ ++ // Second parameter: ++ ++ if (two_param) ++ { ++ if (!ReadByte(&b, stream)) ++ { ++ fprintf(stderr, "ReadChannelEvent: Error while reading channel " ++ "event parameters\n"); ++ return false; ++ } ++ ++ event->data.channel.param2 = b; ++ } ++ ++ return true; ++} ++ ++// Read sysex event: ++ ++static boolean ReadSysExEvent(midi_event_t *event, int event_type, ++ FILE *stream) ++{ ++ event->event_type = event_type; ++ ++ if (!ReadVariableLength(&event->data.sysex.length, stream)) ++ { ++ fprintf(stderr, "ReadSysExEvent: Failed to read length of " ++ "SysEx block\n"); ++ return false; ++ } ++ ++ // Read the byte sequence: ++ ++ event->data.sysex.data = ReadByteSequence(event->data.sysex.length, stream); ++ ++ if (event->data.sysex.data == NULL) ++ { ++ fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); ++ return false; ++ } ++ ++ return true; ++} ++ ++// Read meta event: ++ ++static boolean ReadMetaEvent(midi_event_t *event, FILE *stream) ++{ ++ byte b = 0; ++ ++ event->event_type = MIDI_EVENT_META; ++ ++ // Read meta event type: ++ ++ if (!ReadByte(&b, stream)) ++ { ++ fprintf(stderr, "ReadMetaEvent: Failed to read meta event type\n"); ++ return false; ++ } ++ ++ event->data.meta.type = b; ++ ++ // Read length of meta event data: ++ ++ if (!ReadVariableLength(&event->data.meta.length, stream)) ++ { ++ fprintf(stderr, "ReadSysExEvent: Failed to read length of " ++ "SysEx block\n"); ++ return false; ++ } ++ ++ // Read the byte sequence: ++ ++ event->data.meta.data = ReadByteSequence(event->data.meta.length, stream); ++ ++ if (event->data.meta.data == NULL) ++ { ++ fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); ++ return false; ++ } ++ ++ return true; ++} ++ ++static boolean ReadEvent(midi_event_t *event, unsigned int *last_event_type, ++ FILE *stream) ++{ ++ byte event_type = 0; ++ ++ if (!ReadVariableLength(&event->delta_time, stream)) ++ { ++ fprintf(stderr, "ReadEvent: Failed to read event timestamp\n"); ++ return false; ++ } ++ ++ if (!ReadByte(&event_type, stream)) ++ { ++ fprintf(stderr, "ReadEvent: Failed to read event type\n"); ++ return false; ++ } ++ ++ // All event types have their top bit set. Therefore, if ++ // the top bit is not set, it is because we are using the "same ++ // as previous event type" shortcut to save a byte. Skip back ++ // a byte so that we read this byte again. ++ ++ if ((event_type & 0x80) == 0) ++ { ++ event_type = *last_event_type; ++ ++ if (fseek(stream, -1, SEEK_CUR) < 0) ++ { ++ fprintf(stderr, "ReadEvent: Unable to seek in stream\n"); ++ return false; ++ } ++ } ++ else ++ { ++ *last_event_type = event_type; ++ } ++ ++ // Check event type: ++ ++ switch (event_type & 0xf0) ++ { ++ // Two parameter channel events: ++ ++ case MIDI_EVENT_NOTE_OFF: ++ case MIDI_EVENT_NOTE_ON: ++ case MIDI_EVENT_AFTERTOUCH: ++ case MIDI_EVENT_CONTROLLER: ++ case MIDI_EVENT_PITCH_BEND: ++ return ReadChannelEvent(event, event_type, true, stream); ++ ++ // Single parameter channel events: ++ ++ case MIDI_EVENT_PROGRAM_CHANGE: ++ case MIDI_EVENT_CHAN_AFTERTOUCH: ++ return ReadChannelEvent(event, event_type, false, stream); ++ ++ default: ++ break; ++ } ++ ++ // Specific value? ++ ++ switch (event_type) ++ { ++ case MIDI_EVENT_SYSEX: ++ case MIDI_EVENT_SYSEX_SPLIT: ++ return ReadSysExEvent(event, event_type, stream); ++ ++ case MIDI_EVENT_META: ++ return ReadMetaEvent(event, stream); ++ ++ default: ++ break; ++ } ++ ++ fprintf(stderr, "ReadEvent: Unknown MIDI event type: 0x%x\n", event_type); ++ return false; ++} ++ ++// Free an event: ++ ++static void FreeEvent(midi_event_t *event) ++{ ++ // Some event types have dynamically allocated buffers assigned ++ // to them that must be freed. ++ ++ switch (event->event_type) ++ { ++ case MIDI_EVENT_SYSEX: ++ case MIDI_EVENT_SYSEX_SPLIT: ++ free(event->data.sysex.data); ++ break; ++ ++ case MIDI_EVENT_META: ++ free(event->data.meta.data); ++ break; ++ ++ default: ++ // Nothing to do. ++ break; ++ } ++} ++ ++// Read and check the track chunk header ++ ++static boolean ReadTrackHeader(midi_track_t *track, FILE *stream) ++{ ++ size_t records_read; ++ chunk_header_t chunk_header; ++ ++ records_read = fread(&chunk_header, sizeof(chunk_header_t), 1, stream); ++ ++ if (records_read < 1) ++ { ++ return false; ++ } ++ ++ if (!CheckChunkHeader(&chunk_header, TRACK_CHUNK_ID)) ++ { ++ return false; ++ } ++ ++ track->data_len = SDL_SwapBE32(chunk_header.chunk_size); ++ ++ return true; ++} ++ ++static boolean ReadTrack(midi_track_t *track, FILE *stream) ++{ ++ midi_event_t *new_events; ++ midi_event_t *event; ++ unsigned int last_event_type; ++ ++ track->num_events = 0; ++ track->events = NULL; ++ ++ // Read the header: ++ ++ if (!ReadTrackHeader(track, stream)) ++ { ++ return false; ++ } ++ ++ // Then the events: ++ ++ last_event_type = 0; ++ ++ for (;;) ++ { ++ // Resize the track slightly larger to hold another event: ++ ++ new_events = I_Realloc(track->events, ++ sizeof(midi_event_t) * (track->num_events + 1)); ++ track->events = new_events; ++ ++ // Read the next event: ++ ++ event = &track->events[track->num_events]; ++ if (!ReadEvent(event, &last_event_type, stream)) ++ { ++ return false; ++ } ++ ++ ++track->num_events; ++ ++ // End of track? ++ ++ if (event->event_type == MIDI_EVENT_META ++ && event->data.meta.type == MIDI_META_END_OF_TRACK) ++ { ++ break; ++ } ++ } ++ ++ return true; ++} ++ ++// Free a track: ++ ++static void FreeTrack(midi_track_t *track) ++{ ++ unsigned int i; ++ ++ for (i=0; inum_events; ++i) ++ { ++ FreeEvent(&track->events[i]); ++ } ++ ++ free(track->events); ++} ++ ++static boolean ReadAllTracks(midi_file_t *file, FILE *stream) ++{ ++ unsigned int i; ++ ++ // Allocate list of tracks and read each track: ++ ++ file->tracks = malloc(sizeof(midi_track_t) * file->num_tracks); ++ ++ if (file->tracks == NULL) ++ { ++ return false; ++ } ++ ++ memset(file->tracks, 0, sizeof(midi_track_t) * file->num_tracks); ++ ++ // Read each track: ++ ++ for (i=0; inum_tracks; ++i) ++ { ++ if (!ReadTrack(&file->tracks[i], stream)) ++ { ++ return false; ++ } ++ } ++ ++ return true; ++} ++ ++// Read and check the header chunk. ++ ++static boolean ReadFileHeader(midi_file_t *file, FILE *stream) ++{ ++ size_t records_read; ++ unsigned int format_type; ++ ++ records_read = fread(&file->header, sizeof(midi_header_t), 1, stream); ++ ++ if (records_read < 1) ++ { ++ return false; ++ } ++ ++ if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID) ++ || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6) ++ { ++ fprintf(stderr, "ReadFileHeader: Invalid MIDI chunk header! " ++ "chunk_size=%i\n", ++ SDL_SwapBE32(file->header.chunk_header.chunk_size)); ++ return false; ++ } ++ ++ format_type = SDL_SwapBE16(file->header.format_type); ++ file->num_tracks = SDL_SwapBE16(file->header.num_tracks); ++ ++ if ((format_type != 0 && format_type != 1) ++ || file->num_tracks < 1) ++ { ++ fprintf(stderr, "ReadFileHeader: Only type 0/1 " ++ "MIDI files supported!\n"); ++ return false; ++ } ++ ++ return true; ++} ++ ++void MIDI_FreeFile(midi_file_t *file) ++{ ++ int i; ++ ++ if (file->tracks != NULL) ++ { ++ for (i=0; inum_tracks; ++i) ++ { ++ FreeTrack(&file->tracks[i]); ++ } ++ ++ free(file->tracks); ++ } ++ ++ free(file); ++} ++ ++midi_file_t *MIDI_LoadFile(char *filename) ++{ ++ midi_file_t *file; ++ FILE *stream; ++ ++ file = malloc(sizeof(midi_file_t)); ++ ++ if (file == NULL) ++ { ++ return NULL; ++ } ++ ++ file->tracks = NULL; ++ file->num_tracks = 0; ++ file->buffer = NULL; ++ file->buffer_size = 0; ++ ++ // Open file ++ ++ stream = M_fopen(filename, "rb"); ++ ++ if (stream == NULL) ++ { ++ fprintf(stderr, "MIDI_LoadFile: Failed to open '%s'\n", filename); ++ MIDI_FreeFile(file); ++ return NULL; ++ } ++ ++ // Read MIDI file header ++ ++ if (!ReadFileHeader(file, stream)) ++ { ++ fclose(stream); ++ MIDI_FreeFile(file); ++ return NULL; ++ } ++ ++ // Read all tracks: ++ ++ if (!ReadAllTracks(file, stream)) ++ { ++ fclose(stream); ++ MIDI_FreeFile(file); ++ return NULL; ++ } ++ ++ fclose(stream); ++ ++ return file; ++} ++ ++// Get the number of tracks in a MIDI file. ++ ++unsigned int MIDI_NumTracks(midi_file_t *file) ++{ ++ return file->num_tracks; ++} ++ ++// Start iterating over the events in a track. ++ ++midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track) ++{ ++ midi_track_iter_t *iter; ++ ++ assert(track < file->num_tracks); ++ ++ iter = malloc(sizeof(*iter)); ++ iter->track = &file->tracks[track]; ++ iter->position = 0; ++ iter->loop_point = 0; ++ ++ return iter; ++} ++ ++void MIDI_FreeIterator(midi_track_iter_t *iter) ++{ ++ free(iter); ++} ++ ++// Get the time until the next MIDI event in a track. ++ ++unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter) ++{ ++ if (iter->position < iter->track->num_events) ++ { ++ midi_event_t *next_event; ++ ++ next_event = &iter->track->events[iter->position]; ++ ++ return next_event->delta_time; ++ } ++ else ++ { ++ return 0; ++ } ++} ++ ++// Get a pointer to the next MIDI event. ++ ++int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event) ++{ ++ if (iter->position < iter->track->num_events) ++ { ++ *event = &iter->track->events[iter->position]; ++ ++iter->position; ++ ++ return 1; ++ } ++ else ++ { ++ return 0; ++ } ++} ++ ++unsigned int MIDI_GetFileTimeDivision(midi_file_t *file) ++{ ++ short result = SDL_SwapBE16(file->header.time_division); ++ ++ // Negative time division indicates SMPTE time and must be handled ++ // differently. ++ if (result < 0) ++ { ++ return (signed int)(-(result/256)) ++ * (signed int)(result & 0xFF); ++ } ++ else ++ { ++ return result; ++ } ++} ++ ++void MIDI_RestartIterator(midi_track_iter_t *iter) ++{ ++ iter->position = 0; ++ iter->loop_point = 0; ++} ++ ++void MIDI_SetLoopPoint(midi_track_iter_t *iter) ++{ ++ iter->loop_point = iter->position; ++} ++ ++void MIDI_RestartAtLoopPoint(midi_track_iter_t *iter) ++{ ++ iter->position = iter->loop_point; ++} ++ ++#ifdef TEST ++ ++static char *MIDI_EventTypeToString(midi_event_type_t event_type) ++{ ++ switch (event_type) ++ { ++ case MIDI_EVENT_NOTE_OFF: ++ return "MIDI_EVENT_NOTE_OFF"; ++ case MIDI_EVENT_NOTE_ON: ++ return "MIDI_EVENT_NOTE_ON"; ++ case MIDI_EVENT_AFTERTOUCH: ++ return "MIDI_EVENT_AFTERTOUCH"; ++ case MIDI_EVENT_CONTROLLER: ++ return "MIDI_EVENT_CONTROLLER"; ++ case MIDI_EVENT_PROGRAM_CHANGE: ++ return "MIDI_EVENT_PROGRAM_CHANGE"; ++ case MIDI_EVENT_CHAN_AFTERTOUCH: ++ return "MIDI_EVENT_CHAN_AFTERTOUCH"; ++ case MIDI_EVENT_PITCH_BEND: ++ return "MIDI_EVENT_PITCH_BEND"; ++ case MIDI_EVENT_SYSEX: ++ return "MIDI_EVENT_SYSEX"; ++ case MIDI_EVENT_SYSEX_SPLIT: ++ return "MIDI_EVENT_SYSEX_SPLIT"; ++ case MIDI_EVENT_META: ++ return "MIDI_EVENT_META"; ++ ++ default: ++ return "(unknown)"; ++ } ++} ++ ++void PrintTrack(midi_track_t *track) ++{ ++ midi_event_t *event; ++ unsigned int i; ++ ++ for (i=0; inum_events; ++i) ++ { ++ event = &track->events[i]; ++ ++ if (event->delta_time > 0) ++ { ++ printf("Delay: %u ticks\n", event->delta_time); ++ } ++ ++ printf("Event type: %s (%i)\n", ++ MIDI_EventTypeToString(event->event_type), ++ event->event_type); ++ ++ switch(event->event_type) ++ { ++ case MIDI_EVENT_NOTE_OFF: ++ case MIDI_EVENT_NOTE_ON: ++ case MIDI_EVENT_AFTERTOUCH: ++ case MIDI_EVENT_CONTROLLER: ++ case MIDI_EVENT_PROGRAM_CHANGE: ++ case MIDI_EVENT_CHAN_AFTERTOUCH: ++ case MIDI_EVENT_PITCH_BEND: ++ printf("\tChannel: %u\n", event->data.channel.channel); ++ printf("\tParameter 1: %u\n", event->data.channel.param1); ++ printf("\tParameter 2: %u\n", event->data.channel.param2); ++ break; ++ ++ case MIDI_EVENT_SYSEX: ++ case MIDI_EVENT_SYSEX_SPLIT: ++ printf("\tLength: %u\n", event->data.sysex.length); ++ break; ++ ++ case MIDI_EVENT_META: ++ printf("\tMeta type: %u\n", event->data.meta.type); ++ printf("\tLength: %u\n", event->data.meta.length); ++ break; ++ } ++ } ++} ++ ++int main(int argc, char *argv[]) ++{ ++ midi_file_t *file; ++ unsigned int i; ++ ++ if (argc < 2) ++ { ++ printf("Usage: %s \n", argv[0]); ++ exit(1); ++ } ++ ++ file = MIDI_LoadFile(argv[1]); ++ ++ if (file == NULL) ++ { ++ fprintf(stderr, "Failed to open %s\n", argv[1]); ++ exit(1); ++ } ++ ++ for (i=0; inum_tracks; ++i) ++ { ++ printf("\n== Track %u ==\n\n", i); ++ ++ PrintTrack(&file->tracks[i]); ++ } ++ ++ return 0; ++} ++ ++#endif ++ +--- /dev/null ++++ b/src/opl/opl.c +@@ -0,0 +1,520 @@ ++// ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// OPL interface. ++// ++ ++#include "config.h" ++ ++#include ++#include ++#include ++ ++#include "SDL3/SDL.h" ++ ++#include "opl.h" ++#include "opl_internal.h" ++ ++//#define OPL_DEBUG_TRACE ++ ++ ++static opl_driver_t *drivers[] = ++{ ++#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM) ++ &opl_linux_driver, ++#endif ++#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64) ++ &opl_openbsd_driver, ++#endif ++#ifdef _WIN32 ++ &opl_win32_driver, ++#endif ++ &opl_sdl_driver, ++ NULL ++}; ++ ++static opl_driver_t *driver = NULL; ++static int init_stage_reg_writes = 1; ++ ++unsigned int opl_sample_rate = 22050; ++ ++// ++// Init/shutdown code. ++// ++ ++// Initialize the specified driver and detect an OPL chip. Returns ++// true if an OPL is detected. ++ ++static opl_init_result_t InitDriver(opl_driver_t *_driver, ++ unsigned int port_base) ++{ ++ opl_init_result_t result1, result2; ++ ++ // Initialize the driver. ++ ++ if (!_driver->init_func(port_base)) ++ { ++ return OPL_INIT_NONE; ++ } ++ ++ // The driver was initialized okay, so we now have somewhere ++ // to write to. It doesn't mean there's an OPL chip there, ++ // though. Perform the detection sequence to make sure. ++ // (it's done twice, like how Doom does it). ++ ++ driver = _driver; ++ init_stage_reg_writes = 1; ++ ++ result1 = OPL_Detect(); ++ result2 = OPL_Detect(); ++ if (result1 == OPL_INIT_NONE || result2 == OPL_INIT_NONE) ++ { ++ printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name); ++ _driver->shutdown_func(); ++ driver = NULL; ++ return OPL_INIT_NONE; ++ } ++ ++ init_stage_reg_writes = 0; ++ ++ printf("OPL_Init: Using driver '%s'.\n", driver->name); ++ ++ return result2; ++} ++ ++// Find a driver automatically by trying each in the list. ++ ++static opl_init_result_t AutoSelectDriver(unsigned int port_base) ++{ ++ int i; ++ opl_init_result_t result; ++ ++ for (i = 0; drivers[i] != NULL; ++i) ++ { ++ result = InitDriver(drivers[i], port_base); ++ if (result != OPL_INIT_NONE) ++ { ++ return result; ++ } ++ } ++ ++ printf("OPL_Init: Failed to find a working driver.\n"); ++ ++ return OPL_INIT_NONE; ++} ++ ++// Initialize the OPL library. Return value indicates type of OPL chip ++// detected, if any. ++ ++opl_init_result_t OPL_Init(unsigned int port_base) ++{ ++ char *driver_name; ++ int i; ++ int result; ++ ++ driver_name = getenv("OPL_DRIVER"); ++ ++ if (driver_name != NULL) ++ { ++ // Search the list until we find the driver with this name. ++ ++ for (i=0; drivers[i] != NULL; ++i) ++ { ++ if (!strcmp(driver_name, drivers[i]->name)) ++ { ++ result = InitDriver(drivers[i], port_base); ++ if (result) ++ { ++ return result; ++ } ++ else ++ { ++ printf("OPL_Init: Failed to initialize " ++ "driver: '%s'.\n", driver_name); ++ return OPL_INIT_NONE; ++ } ++ } ++ } ++ ++ printf("OPL_Init: unknown driver: '%s'.\n", driver_name); ++ ++ return OPL_INIT_NONE; ++ } ++ else ++ { ++ return AutoSelectDriver(port_base); ++ } ++} ++ ++// Shut down the OPL library. ++ ++void OPL_Shutdown(void) ++{ ++ if (driver != NULL) ++ { ++ driver->shutdown_func(); ++ driver = NULL; ++ } ++} ++ ++// Set the sample rate used for software OPL emulation. ++ ++void OPL_SetSampleRate(unsigned int rate) ++{ ++ opl_sample_rate = rate; ++} ++ ++void OPL_WritePort(opl_port_t port, unsigned int value) ++{ ++ if (driver != NULL) ++ { ++#ifdef OPL_DEBUG_TRACE ++ printf("OPL_write: %i, %x\n", port, value); ++ fflush(stdout); ++#endif ++ driver->write_port_func(port, value); ++ } ++} ++ ++unsigned int OPL_ReadPort(opl_port_t port) ++{ ++ if (driver != NULL) ++ { ++ unsigned int result; ++ ++#ifdef OPL_DEBUG_TRACE ++ printf("OPL_read: %i...\n", port); ++ fflush(stdout); ++#endif ++ ++ result = driver->read_port_func(port); ++ ++#ifdef OPL_DEBUG_TRACE ++ printf("OPL_read: %i -> %x\n", port, result); ++ fflush(stdout); ++#endif ++ ++ return result; ++ } ++ else ++ { ++ return 0; ++ } ++} ++ ++// ++// Higher-level functions, based on the lower-level functions above ++// (register write, etc). ++// ++ ++unsigned int OPL_ReadStatus(void) ++{ ++ return OPL_ReadPort(OPL_REGISTER_PORT); ++} ++ ++// Write an OPL register value ++ ++void OPL_WriteRegister(int reg, int value) ++{ ++ int i; ++ ++ if (reg & 0x100) ++ { ++ OPL_WritePort(OPL_REGISTER_PORT_OPL3, reg); ++ } ++ else ++ { ++ OPL_WritePort(OPL_REGISTER_PORT, reg); ++ } ++ ++ // For timing, read the register port six times after writing the ++ // register number to cause the appropriate delay ++ ++ for (i=0; i<6; ++i) ++ { ++ // An oddity of the Doom OPL code: at startup initialization, ++ // the spacing here is performed by reading from the register ++ // port; after initialization, the data port is read, instead. ++ ++ if (init_stage_reg_writes) ++ { ++ OPL_ReadPort(OPL_REGISTER_PORT); ++ } ++ else ++ { ++ OPL_ReadPort(OPL_DATA_PORT); ++ } ++ } ++ ++ OPL_WritePort(OPL_DATA_PORT, value); ++ ++ // Read the register port 24 times after writing the value to ++ // cause the appropriate delay ++ ++ for (i=0; i<24; ++i) ++ { ++ OPL_ReadStatus(); ++ } ++} ++ ++// Detect the presence of an OPL chip ++ ++opl_init_result_t OPL_Detect(void) ++{ ++ int result1, result2; ++ int i; ++ ++ // Reset both timers: ++ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); ++ ++ // Enable interrupts: ++ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); ++ ++ // Read status ++ result1 = OPL_ReadStatus(); ++ ++ // Set timer: ++ OPL_WriteRegister(OPL_REG_TIMER1, 0xff); ++ ++ // Start timer 1: ++ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x21); ++ ++ // Wait for 80 microseconds ++ // This is how Doom does it: ++ ++ for (i=0; i<200; ++i) ++ { ++ OPL_ReadStatus(); ++ } ++ ++ OPL_Delay(1 * OPL_MS); ++ ++ // Read status ++ result2 = OPL_ReadStatus(); ++ ++ // Reset both timers: ++ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); ++ ++ // Enable interrupts: ++ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); ++ ++ if ((result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0) ++ { ++ result1 = OPL_ReadPort(OPL_REGISTER_PORT); ++ result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3); ++ if (result1 == 0x00) ++ { ++ return OPL_INIT_OPL3; ++ } ++ else ++ { ++ return OPL_INIT_OPL2; ++ } ++ } ++ else ++ { ++ return OPL_INIT_NONE; ++ } ++} ++ ++// Initialize registers on startup ++ ++void OPL_InitRegisters(int opl3) ++{ ++ int r; ++ ++ // Initialize level registers ++ ++ for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) ++ { ++ OPL_WriteRegister(r, 0x3f); ++ } ++ ++ // Initialize other registers ++ // These two loops write to registers that actually don't exist, ++ // but this is what Doom does ... ++ // Similarly, the <= is also intenational. ++ ++ for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) ++ { ++ OPL_WriteRegister(r, 0x00); ++ } ++ ++ // More registers ... ++ ++ for (r=1; r < OPL_REGS_LEVEL; ++r) ++ { ++ OPL_WriteRegister(r, 0x00); ++ } ++ ++ // Re-initialize the low registers: ++ ++ // Reset both timers and enable interrupts: ++ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); ++ OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); ++ ++ // "Allow FM chips to control the waveform of each operator": ++ OPL_WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20); ++ ++ if (opl3) ++ { ++ OPL_WriteRegister(OPL_REG_NEW, 0x01); ++ ++ // Initialize level registers ++ ++ for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) ++ { ++ OPL_WriteRegister(r | 0x100, 0x3f); ++ } ++ ++ // Initialize other registers ++ // These two loops write to registers that actually don't exist, ++ // but this is what Doom does ... ++ // Similarly, the <= is also intenational. ++ ++ for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) ++ { ++ OPL_WriteRegister(r | 0x100, 0x00); ++ } ++ ++ // More registers ... ++ ++ for (r=1; r < OPL_REGS_LEVEL; ++r) ++ { ++ OPL_WriteRegister(r | 0x100, 0x00); ++ } ++ } ++ ++ // Keyboard split point on (?) ++ OPL_WriteRegister(OPL_REG_FM_MODE, 0x40); ++ ++ if (opl3) ++ { ++ OPL_WriteRegister(OPL_REG_NEW, 0x01); ++ } ++} ++ ++// ++// Timer functions. ++// ++ ++void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data) ++{ ++ if (driver != NULL) ++ { ++ driver->set_callback_func(us, callback, data); ++ } ++} ++ ++void OPL_ClearCallbacks(void) ++{ ++ if (driver != NULL) ++ { ++ driver->clear_callbacks_func(); ++ } ++} ++ ++void OPL_Lock(void) ++{ ++ if (driver != NULL) ++ { ++ driver->lock_func(); ++ } ++} ++ ++void OPL_Unlock(void) ++{ ++ if (driver != NULL) ++ { ++ driver->unlock_func(); ++ } ++} ++ ++typedef struct ++{ ++ int finished; ++ ++ SDL_Mutex *mutex; ++ SDL_Condition *cond; ++} delay_data_t; ++ ++static void DelayCallback(void *_delay_data) ++{ ++ delay_data_t *delay_data = _delay_data; ++ ++ SDL_LockMutex(delay_data->mutex); ++ delay_data->finished = 1; ++ ++ SDL_SignalCondition(delay_data->cond); ++ ++ SDL_UnlockMutex(delay_data->mutex); ++} ++ ++// Delay for specified number of microseconds after OPL subsystem is initialized ++ ++void OPL_Delay(uint64_t us) ++{ ++ delay_data_t delay_data; ++ ++ if (driver == NULL) ++ { ++ return; ++ } ++ ++ // Create a callback that will signal this thread after the ++ // specified time. ++ ++ // Note that this is not just a simple time-based delay, it will ensure ++ // that the OPL system is initialized and the queue has begun processing ++ // before releasing the mutex lock ++ ++ delay_data.finished = 0; ++ delay_data.mutex = SDL_CreateMutex(); ++ delay_data.cond = SDL_CreateCondition(); ++ ++ OPL_SetCallback(us, DelayCallback, &delay_data); ++ ++ // Wait until the callback is invoked. ++ ++ SDL_LockMutex(delay_data.mutex); ++ ++ while (!delay_data.finished) ++ { ++ SDL_WaitCondition(delay_data.cond, delay_data.mutex); ++ } ++ ++ SDL_UnlockMutex(delay_data.mutex); ++ ++ // Clean up. ++ ++ SDL_DestroyMutex(delay_data.mutex); ++ SDL_DestroyCondition(delay_data.cond); ++} ++ ++void OPL_SetPaused(int paused) ++{ ++ if (driver != NULL) ++ { ++ driver->set_paused_func(paused); ++ } ++} ++ ++void OPL_AdjustCallbacks(float value) ++{ ++ if (driver != NULL) ++ { ++ driver->adjust_callbacks_func(value); ++ } ++} ++ +--- /dev/null ++++ b/src/opl/opl.h +@@ -0,0 +1,153 @@ ++// ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// OPL interface. ++// ++ ++ ++#ifndef OPL_OPL_H ++#define OPL_OPL_H ++ ++#include ++ ++typedef void (*opl_callback_t)(void *data); ++ ++// Result from OPL_Init(), indicating what type of OPL chip was detected, ++// if any. ++typedef enum ++{ ++ OPL_INIT_NONE, ++ OPL_INIT_OPL2, ++ OPL_INIT_OPL3, ++} opl_init_result_t; ++ ++typedef enum ++{ ++ OPL_REGISTER_PORT = 0, ++ OPL_DATA_PORT = 1, ++ OPL_REGISTER_PORT_OPL3 = 2 ++} opl_port_t; ++ ++#define OPL_NUM_OPERATORS 21 ++#define OPL_NUM_VOICES 9 ++ ++#define OPL_REG_WAVEFORM_ENABLE 0x01 ++#define OPL_REG_TIMER1 0x02 ++#define OPL_REG_TIMER2 0x03 ++#define OPL_REG_TIMER_CTRL 0x04 ++#define OPL_REG_FM_MODE 0x08 ++#define OPL_REG_NEW 0x105 ++ ++// Operator registers (21 of each): ++ ++#define OPL_REGS_TREMOLO 0x20 ++#define OPL_REGS_LEVEL 0x40 ++#define OPL_REGS_ATTACK 0x60 ++#define OPL_REGS_SUSTAIN 0x80 ++#define OPL_REGS_WAVEFORM 0xE0 ++ ++// Voice registers (9 of each): ++ ++#define OPL_REGS_FREQ_1 0xA0 ++#define OPL_REGS_FREQ_2 0xB0 ++#define OPL_REGS_FEEDBACK 0xC0 ++ ++// Times ++ ++#define OPL_SECOND ((uint64_t) 1000 * 1000) ++#define OPL_MS ((uint64_t) 1000) ++#define OPL_US ((uint64_t) 1) ++ ++// ++// Low-level functions. ++// ++ ++// Initialize the OPL subsystem. ++ ++opl_init_result_t OPL_Init(unsigned int port_base); ++ ++// Shut down the OPL subsystem. ++ ++void OPL_Shutdown(void); ++ ++// Set the sample rate used for software emulation. ++ ++void OPL_SetSampleRate(unsigned int rate); ++ ++// Write to one of the OPL I/O ports: ++ ++void OPL_WritePort(opl_port_t port, unsigned int value); ++ ++// Read from one of the OPL I/O ports: ++ ++unsigned int OPL_ReadPort(opl_port_t port); ++ ++// ++// Higher-level functions. ++// ++ ++// Read the cuurrent status byte of the OPL chip. ++ ++unsigned int OPL_ReadStatus(void); ++ ++// Write to an OPL register. ++ ++void OPL_WriteRegister(int reg, int value); ++ ++// Perform a detection sequence to determine that an ++// OPL chip is present. ++ ++opl_init_result_t OPL_Detect(void); ++ ++// Initialize all registers, performed on startup. ++ ++void OPL_InitRegisters(int opl3); ++ ++// ++// Timer callback functions. ++// ++ ++// Set a timer callback. After the specified number of microseconds ++// have elapsed, the callback will be invoked. ++ ++void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data); ++ ++// Adjust callback times by the specified factor. For example, a value of ++// 0.5 will halve all remaining times. ++ ++void OPL_AdjustCallbacks(float factor); ++ ++// Clear all OPL callbacks that have been set. ++ ++void OPL_ClearCallbacks(void); ++ ++// Begin critical section, during which, OPL callbacks will not be ++// invoked. ++ ++void OPL_Lock(void); ++ ++// End critical section. ++ ++void OPL_Unlock(void); ++ ++// Block until the specified number of microseconds have elapsed. ++ ++void OPL_Delay(uint64_t us); ++ ++// Pause the OPL callbacks. ++ ++void OPL_SetPaused(int paused); ++ ++#endif ++ +--- /dev/null ++++ b/src/opl/opl3.c +@@ -0,0 +1,1596 @@ ++ ++/* Nuked OPL3 ++ * ++ * Copyright (C) 2013-2020 Nuke.YKT ++ * Copyright (C) 2026 Tony Gies (Nuked-OPL3-fast modifications) ++ * ++ * This file is part of Nuked OPL3. ++ * ++ * Nuked OPL3 is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as ++ * published by the Free Software Foundation, either version 2.1 ++ * of the License, or (at your option) any later version. ++ * ++ * Nuked OPL3 is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with Nuked OPL3. If not, see . ++ * ++ * Nuked OPL3 emulator. ++ * Thanks: ++ * MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): ++ * Feedback and Rhythm part calculation information. ++ * forums.submarine.org.uk(carbon14, opl3): ++ * Tremolo and phase generator calculation information. ++ * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): ++ * OPL2 ROMs. ++ * siliconpr0n.org(John McMaster, digshadow): ++ * YMF262 and VRC VII decaps and die shots. ++ * ++ * Upstream version: 1.8 (commit cfedb09) ++ * Fork version: 1.8-fast.1 ++ * Fork home: https://github.com/tgies/Nuked-OPL3-fast ++ * ++ * Nuked-OPL3-fast is a bit-exact performance-optimized fork of Nuked-OPL3. ++ * Audio output is identical to upstream for the same register stream. ++ * ++ * Modifications vs. upstream: ++ * ++ * - Replaced 8 runtime waveform math functions with a unified 8x1024 ++ * logsin lookup table (logsin_wf, in wf_rom.h). ++ * - Pre-shifted the exprom table at compile time to eliminate a runtime ++ * shift in OPL3_SlotGenerate. ++ * - Hoisted per-sample envelope rate resolution out of OPL3_EnvelopeCalc ++ * into OPL3_EnvelopeUpdateRate (eg_rate_hi[4], eg_rate_lo[4]). ++ * - Added write-time caches on opl3_slot: eg_tl_ksl (TL + KSL sum), ++ * eg_ks (envelope key-scale shift), pg_inc (non-vibrato phase ++ * increment). ++ * - Added fast paths in OPL3_ProcessSlot for fully-attenuated key-off ++ * non-rhythm slots, permanently-dead uninitialized slots, and ++ * sustain-with-rate-zero slots. ++ * - Added OPL3_SlotGenerateSilent: when eg_out >= 0x180 the exprom ++ * result is provably zero, so output reduces to the waveform sign bit. ++ * Used by the key-off fast path. ++ * - Unrolled both 18-channel mix loops in OPL3_Generate4Ch using a ++ * per-channel out_cnt, skipping dummy reads for muted/2-op voices. ++ * - Fused the rhythm-mode special cases in OPL3_PhaseGenerate into a ++ * single switch indexed by slot_num for jump-table dispatch. ++ * - Reordered opl3_slot to put hot per-sample fields in the first cache ++ * line; struct size reduced from 96 to 88 bytes. ++ * - Minor: __builtin_ctz for the envelope timer on GCC/Clang with a ++ * portable fallback; replaced the tremolo-position modulo with an ++ * explicit wrap. ++ */ ++ ++#include ++#include ++#include ++#include "opl3.h" ++#include "wf_rom.h" ++ ++#if OPL_ENABLE_STEREOEXT && !defined OPL_SIN ++#ifndef _USE_MATH_DEFINES ++#define _USE_MATH_DEFINES 1 ++#endif ++#include ++/* input: [0, 256), output: [0, 65536] */ ++#define OPL_SIN(x) ((int32_t)(sin((x) * M_PI / 512.0) * 65536.0)) ++#endif ++ ++/* Quirk: Some FM channels are output one sample later on the left side than the right. */ ++#ifndef OPL_QUIRK_CHANNELSAMPLEDELAY ++#define OPL_QUIRK_CHANNELSAMPLEDELAY (!OPL_ENABLE_STEREOEXT) ++#endif ++ ++#define RSM_FRAC 10 ++ ++/* Channel types */ ++ ++enum { ++ ch_2op = 0, ++ ch_4op = 1, ++ ch_4op2 = 2, ++ ch_drum = 3 ++}; ++ ++/* Envelope key types */ ++ ++enum { ++ egk_norm = 0x01, ++ egk_drum = 0x02 ++}; ++ ++/* ++ exp table ++*/ ++ ++static const uint16_t exprom[256] = { ++ 0xff4, 0xfea, 0xfde, 0xfd4, 0xfc8, 0xfbe, 0xfb4, 0xfa8, ++ 0xf9e, 0xf92, 0xf88, 0xf7e, 0xf72, 0xf68, 0xf5c, 0xf52, ++ 0xf48, 0xf3e, 0xf32, 0xf28, 0xf1e, 0xf14, 0xf08, 0xefe, ++ 0xef4, 0xeea, 0xee0, 0xed4, 0xeca, 0xec0, 0xeb6, 0xeac, ++ 0xea2, 0xe98, 0xe8e, 0xe84, 0xe7a, 0xe70, 0xe66, 0xe5c, ++ 0xe52, 0xe48, 0xe3e, 0xe34, 0xe2a, 0xe20, 0xe16, 0xe0c, ++ 0xe04, 0xdfa, 0xdf0, 0xde6, 0xddc, 0xdd2, 0xdca, 0xdc0, ++ 0xdb6, 0xdac, 0xda4, 0xd9a, 0xd90, 0xd88, 0xd7e, 0xd74, ++ 0xd6a, 0xd62, 0xd58, 0xd50, 0xd46, 0xd3c, 0xd34, 0xd2a, ++ 0xd22, 0xd18, 0xd10, 0xd06, 0xcfe, 0xcf4, 0xcec, 0xce2, ++ 0xcda, 0xcd0, 0xcc8, 0xcbe, 0xcb6, 0xcae, 0xca4, 0xc9c, ++ 0xc92, 0xc8a, 0xc82, 0xc78, 0xc70, 0xc68, 0xc60, 0xc56, ++ 0xc4e, 0xc46, 0xc3c, 0xc34, 0xc2c, 0xc24, 0xc1c, 0xc12, ++ 0xc0a, 0xc02, 0xbfa, 0xbf2, 0xbea, 0xbe0, 0xbd8, 0xbd0, ++ 0xbc8, 0xbc0, 0xbb8, 0xbb0, 0xba8, 0xba0, 0xb98, 0xb90, ++ 0xb88, 0xb80, 0xb78, 0xb70, 0xb68, 0xb60, 0xb58, 0xb50, ++ 0xb48, 0xb40, 0xb38, 0xb32, 0xb2a, 0xb22, 0xb1a, 0xb12, ++ 0xb0a, 0xb02, 0xafc, 0xaf4, 0xaec, 0xae4, 0xade, 0xad6, ++ 0xace, 0xac6, 0xac0, 0xab8, 0xab0, 0xaa8, 0xaa2, 0xa9a, ++ 0xa92, 0xa8c, 0xa84, 0xa7c, 0xa76, 0xa6e, 0xa68, 0xa60, ++ 0xa58, 0xa52, 0xa4a, 0xa44, 0xa3c, 0xa36, 0xa2e, 0xa28, ++ 0xa20, 0xa18, 0xa12, 0xa0c, 0xa04, 0x9fe, 0x9f6, 0x9f0, ++ 0x9e8, 0x9e2, 0x9da, 0x9d4, 0x9ce, 0x9c6, 0x9c0, 0x9b8, ++ 0x9b2, 0x9ac, 0x9a4, 0x99e, 0x998, 0x990, 0x98a, 0x984, ++ 0x97c, 0x976, 0x970, 0x96a, 0x962, 0x95c, 0x956, 0x950, ++ 0x948, 0x942, 0x93c, 0x936, 0x930, 0x928, 0x922, 0x91c, ++ 0x916, 0x910, 0x90a, 0x904, 0x8fc, 0x8f6, 0x8f0, 0x8ea, ++ 0x8e4, 0x8de, 0x8d8, 0x8d2, 0x8cc, 0x8c6, 0x8c0, 0x8ba, ++ 0x8b4, 0x8ae, 0x8a8, 0x8a2, 0x89c, 0x896, 0x890, 0x88a, ++ 0x884, 0x87e, 0x878, 0x872, 0x86c, 0x866, 0x860, 0x85a, ++ 0x854, 0x850, 0x84a, 0x844, 0x83e, 0x838, 0x832, 0x82c, ++ 0x828, 0x822, 0x81c, 0x816, 0x810, 0x80c, 0x806, 0x800, ++}; ++ ++/* ++ freq mult table multiplied by 2 ++ ++ 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 ++*/ ++ ++static const uint8_t mt[16] = { ++ 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 ++}; ++ ++/* ++ ksl table ++*/ ++ ++static const uint8_t kslrom[16] = { ++ 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 ++}; ++ ++static const uint8_t kslshift[4] = { ++ 8, 1, 2, 0 ++}; ++ ++/* ++ envelope generator constants ++*/ ++ ++static const uint8_t eg_incstep[4][4] = { ++ { 0, 0, 0, 0 }, ++ { 1, 0, 0, 0 }, ++ { 1, 0, 1, 0 }, ++ { 1, 1, 1, 0 } ++}; ++ ++/* ++ address decoding ++*/ ++ ++static const int8_t ad_slot[0x20] = { ++ 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1, ++ 12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ++}; ++ ++static const uint8_t ch_slot[18] = { ++ 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 ++}; ++ ++#if OPL_ENABLE_STEREOEXT ++/* ++ stereo extension panning table ++*/ ++ ++static int32_t panpot_lut[256]; ++static uint8_t panpot_lut_build = 0; ++#endif ++ ++/* ++ Envelope generator ++*/ ++ ++enum envelope_gen_num ++{ ++ envelope_gen_num_attack = 0, ++ envelope_gen_num_decay = 1, ++ envelope_gen_num_sustain = 2, ++ envelope_gen_num_release = 3 ++}; ++ ++static void OPL3_EnvelopeUpdateKSL(opl3_slot *slot) ++{ ++ int16_t ksl = (kslrom[slot->channel->f_num >> 6u] << 2) ++ - ((0x08 - slot->channel->block) << 5); ++ if (ksl < 0) ++ { ++ ksl = 0; ++ } ++ slot->eg_ksl = (uint8_t)ksl; ++ /* Refresh the cached (reg_tl << 2) + (eg_ksl >> kslshift[reg_ksl]) ++ * sum used by OPL3_EnvelopeCalc. Both reg_tl/reg_ksl-driven changes ++ * (via SlotWrite40, which calls this function) and eg_ksl-driven ++ * changes (f_num/block updates via Channel{A0,B0}) flow through ++ * here, so this covers all dirty cases. */ ++ slot->eg_tl_ksl = (uint16_t)((slot->reg_tl << 2) ++ + (slot->eg_ksl >> kslshift[slot->reg_ksl])); ++} ++ ++static void OPL3_EnvelopeUpdateRate(opl3_slot *slot) ++{ ++ uint8_t ii; ++ ++ slot->eg_ks = slot->channel->ksv >> ((slot->reg_ksr ^ 1) << 1); ++ for (ii = 0; ii < 4; ii++) ++ { ++ uint8_t rate = slot->eg_ks + (slot->eg_rates[ii] << 2); ++ uint8_t rate_hi = rate >> 2; ++ if (rate_hi & 0x10) ++ { ++ rate_hi = 0x0f; ++ } ++ slot->eg_rate_hi[ii] = rate_hi; ++ slot->eg_rate_lo[ii] = rate & 0x03; ++ } ++} ++ ++static void OPL3_EnvelopeCalc(opl3_slot *slot) ++{ ++ uint8_t nonzero; ++ uint8_t rate_hi; ++ uint8_t rate_lo; ++ uint8_t reg_rate = 0; ++ uint8_t eg_shift, shift; ++ uint16_t eg_rout; ++ int16_t eg_inc; ++ uint8_t eg_off; ++ uint8_t reset = 0; ++ ++ slot->eg_out = slot->eg_rout + slot->eg_tl_ksl + *slot->trem; ++ if (slot->key && slot->eg_gen == envelope_gen_num_release) ++ { ++ reset = 1; ++ reg_rate = slot->eg_rates[0]; ++ } ++ else ++ { ++ reg_rate = slot->eg_rates[slot->eg_gen]; ++ } ++ slot->pg_reset = reset; ++ nonzero = (reg_rate != 0); ++ if (reset) ++ { ++ rate_hi = slot->eg_rate_hi[0]; ++ rate_lo = slot->eg_rate_lo[0]; ++ } ++ else ++ { ++ rate_hi = slot->eg_rate_hi[slot->eg_gen]; ++ rate_lo = slot->eg_rate_lo[slot->eg_gen]; ++ } ++ eg_shift = rate_hi + slot->chip->eg_add; ++ shift = 0; ++ if (nonzero) ++ { ++ if (rate_hi < 12) ++ { ++ if (slot->chip->eg_state) ++ { ++ switch (eg_shift) ++ { ++ case 12: ++ shift = 1; ++ break; ++ case 13: ++ shift = (rate_lo >> 1) & 0x01; ++ break; ++ case 14: ++ shift = rate_lo & 0x01; ++ break; ++ default: ++ break; ++ } ++ } ++ } ++ else ++ { ++ shift = (rate_hi & 0x03) + eg_incstep[rate_lo][slot->chip->eg_timer_lo]; ++ if (shift & 0x04) ++ { ++ shift = 0x03; ++ } ++ if (!shift) ++ { ++ shift = slot->chip->eg_state; ++ } ++ } ++ } ++ eg_rout = slot->eg_rout; ++ eg_inc = 0; ++ eg_off = 0; ++ /* Instant attack */ ++ if (reset && rate_hi == 0x0f) ++ { ++ eg_rout = 0x00; ++ } ++ /* Envelope off */ ++ if ((slot->eg_rout & 0x1f8) == 0x1f8) ++ { ++ eg_off = 1; ++ } ++ if (slot->eg_gen != envelope_gen_num_attack && !reset && eg_off) ++ { ++ eg_rout = 0x1ff; ++ } ++ switch (slot->eg_gen) ++ { ++ case envelope_gen_num_attack: ++ if (!slot->eg_rout) ++ { ++ slot->eg_gen = envelope_gen_num_decay; ++ } ++ else if (slot->key && shift > 0 && rate_hi != 0x0f) ++ { ++ eg_inc = ~slot->eg_rout >> (4 - shift); ++ } ++ break; ++ case envelope_gen_num_decay: ++ if ((slot->eg_rout >> 4) == slot->reg_sl) ++ { ++ slot->eg_gen = envelope_gen_num_sustain; ++ } ++ else if (!eg_off && !reset && shift > 0) ++ { ++ eg_inc = 1 << (shift - 1); ++ } ++ break; ++ case envelope_gen_num_sustain: ++ case envelope_gen_num_release: ++ if (!eg_off && !reset && shift > 0) ++ { ++ eg_inc = 1 << (shift - 1); ++ } ++ break; ++ } ++ slot->eg_rout = (eg_rout + eg_inc) & 0x1ff; ++ /* Key off */ ++ if (reset) ++ { ++ slot->eg_gen = envelope_gen_num_attack; ++ } ++ if (!slot->key) ++ { ++ slot->eg_gen = envelope_gen_num_release; ++ } ++} ++ ++static void OPL3_EnvelopeKeyOn(opl3_slot *slot, uint8_t type) ++{ ++ slot->key |= type; ++} ++ ++static void OPL3_EnvelopeKeyOff(opl3_slot *slot, uint8_t type) ++{ ++ slot->key &= ~type; ++} ++ ++/* ++ Phase Generator ++*/ ++ ++static void OPL3_PhaseUpdateInc(opl3_slot *slot) ++{ ++ uint32_t basefreq = ((uint32_t)slot->channel->f_num << slot->channel->block) >> 1; ++ slot->pg_inc = (basefreq * mt[slot->reg_mult]) >> 1; ++} ++ ++static void OPL3_PhaseGenerate(opl3_slot *slot) ++{ ++ opl3_chip *chip; ++ uint16_t f_num; ++ uint32_t basefreq; ++ uint32_t phaseinc; ++ uint8_t rm_xor, n_bit; ++ uint32_t noise; ++ uint16_t phase; ++ ++ chip = slot->chip; ++ if (slot->reg_vib) ++ { ++ int8_t range; ++ uint8_t vibpos; ++ ++ f_num = slot->channel->f_num; ++ range = (f_num >> 7) & 7; ++ vibpos = slot->chip->vibpos; ++ ++ if (!(vibpos & 3)) ++ { ++ range = 0; ++ } ++ else if (vibpos & 1) ++ { ++ range >>= 1; ++ } ++ range >>= slot->chip->vibshift; ++ ++ if (vibpos & 4) ++ { ++ range = -range; ++ } ++ f_num += range; ++ basefreq = (f_num << slot->channel->block) >> 1; ++ phaseinc = (basefreq * mt[slot->reg_mult]) >> 1; ++ } ++ else ++ { ++ phaseinc = slot->pg_inc; ++ } ++ phase = (uint16_t)(slot->pg_phase >> 9); ++ if (slot->pg_reset) ++ { ++ slot->pg_phase = 0; ++ } ++ slot->pg_phase += phaseinc; ++ /* Rhythm mode: dispatch on slot_num via a single switch so non-rhythm ++ * slots (33 of 36) hit the default case and skip everything. The ++ * fused switch also lets gcc emit a jump table instead of branches. */ ++ noise = chip->noise; ++ slot->pg_phase_out = phase; ++ switch (slot->slot_num) ++ { ++ case 13: /* hh */ ++ chip->rm_hh_bit2 = (phase >> 2) & 1; ++ chip->rm_hh_bit3 = (phase >> 3) & 1; ++ chip->rm_hh_bit7 = (phase >> 7) & 1; ++ chip->rm_hh_bit8 = (phase >> 8) & 1; ++ if (chip->rhy & 0x20) ++ { ++ rm_xor = (chip->rm_hh_bit2 ^ chip->rm_hh_bit7) ++ | (chip->rm_hh_bit3 ^ chip->rm_tc_bit5) ++ | (chip->rm_tc_bit3 ^ chip->rm_tc_bit5); ++ slot->pg_phase_out = rm_xor << 9; ++ if (rm_xor ^ (noise & 1)) ++ { ++ slot->pg_phase_out |= 0xd0; ++ } ++ else ++ { ++ slot->pg_phase_out |= 0x34; ++ } ++ } ++ break; ++ case 16: /* sd */ ++ if (chip->rhy & 0x20) ++ { ++ slot->pg_phase_out = (chip->rm_hh_bit8 << 9) ++ | ((chip->rm_hh_bit8 ^ (noise & 1)) << 8); ++ } ++ break; ++ case 17: /* tc */ ++ if (chip->rhy & 0x20) ++ { ++ chip->rm_tc_bit3 = (phase >> 3) & 1; ++ chip->rm_tc_bit5 = (phase >> 5) & 1; ++ rm_xor = (chip->rm_hh_bit2 ^ chip->rm_hh_bit7) ++ | (chip->rm_hh_bit3 ^ chip->rm_tc_bit5) ++ | (chip->rm_tc_bit3 ^ chip->rm_tc_bit5); ++ slot->pg_phase_out = (rm_xor << 9) | 0x80; ++ } ++ break; ++ default: ++ break; ++ } ++ n_bit = ((noise >> 14) ^ noise) & 0x01; ++ chip->noise = (noise >> 1) | (n_bit << 22); ++} ++ ++/* ++ Slot ++*/ ++ ++static void OPL3_SlotWrite20(opl3_slot *slot, uint8_t data) ++{ ++ if ((data >> 7) & 0x01) ++ { ++ slot->trem = &slot->chip->tremolo; ++ } ++ else ++ { ++ slot->trem = (uint8_t*)&slot->chip->zeromod; ++ } ++ slot->reg_vib = (data >> 6) & 0x01; ++ slot->reg_type = (data >> 5) & 0x01; ++ slot->eg_rates[2] = slot->reg_type ? 0 : slot->reg_rr; ++ slot->reg_ksr = (data >> 4) & 0x01; ++ slot->reg_mult = data & 0x0f; ++ OPL3_EnvelopeUpdateRate(slot); ++ OPL3_PhaseUpdateInc(slot); ++} ++ ++static void OPL3_SlotWrite40(opl3_slot *slot, uint8_t data) ++{ ++ slot->reg_ksl = (data >> 6) & 0x03; ++ slot->reg_tl = data & 0x3f; ++ OPL3_EnvelopeUpdateKSL(slot); ++} ++ ++static void OPL3_SlotWrite60(opl3_slot *slot, uint8_t data) ++{ ++ slot->reg_ar = (data >> 4) & 0x0f; ++ slot->reg_dr = data & 0x0f; ++ slot->eg_rates[0] = slot->reg_ar; ++ slot->eg_rates[1] = slot->reg_dr; ++ OPL3_EnvelopeUpdateRate(slot); ++} ++ ++static void OPL3_SlotWrite80(opl3_slot *slot, uint8_t data) ++{ ++ slot->reg_sl = (data >> 4) & 0x0f; ++ if (slot->reg_sl == 0x0f) ++ { ++ slot->reg_sl = 0x1f; ++ } ++ slot->reg_rr = data & 0x0f; ++ slot->eg_rates[2] = slot->reg_type ? 0 : slot->reg_rr; ++ slot->eg_rates[3] = slot->reg_rr; ++ OPL3_EnvelopeUpdateRate(slot); ++} ++ ++static void OPL3_SlotWriteE0(opl3_slot *slot, uint8_t data) ++{ ++ slot->reg_wf = data & 0x07; ++ if (slot->chip->newm == 0x00) ++ { ++ slot->reg_wf &= 0x03; ++ } ++} ++ ++static inline void OPL3_SlotGenerate(opl3_slot *slot) ++{ ++ uint16_t phase = slot->pg_phase_out + *slot->mod; ++ uint16_t envelope = slot->eg_out; ++ uint16_t wf_data = logsin_wf[slot->reg_wf][phase & 0x3ff]; ++ uint16_t neg = (uint16_t)(((int16_t)wf_data) >> 15); ++ uint32_t level = (wf_data & 0x7fff) + (envelope << 3); ++ if (level > 0x1fff) ++ { ++ level = 0x1fff; ++ } ++ slot->out = ((exprom[level & 0xffu] >> (level >> 8)) ^ neg); ++} ++ ++/* Silent-regime variant: when the caller has proven eg_out >= 0x180, the ++ * exprom lookup always reads through to zero (max exprom value 0xff4 >> 12 ++ * = 0), so the final out reduces to just the sign bit of wf_data. Skips a ++ * load, an add, a clamp, a shift, and a xor. */ ++static inline void OPL3_SlotGenerateSilent(opl3_slot *slot) ++{ ++ uint16_t phase = slot->pg_phase_out + *slot->mod; ++ uint16_t wf_data = logsin_wf[slot->reg_wf][phase & 0x3ff]; ++ slot->out = (int16_t)wf_data >> 15; ++} ++ ++static inline void OPL3_SlotCalcFB(opl3_slot *slot) ++{ ++ if (slot->channel->fb != 0x00) ++ { ++ slot->fbmod = (slot->prout + slot->out) >> (0x09 - slot->channel->fb); ++ } ++ else ++ { ++ slot->fbmod = 0; ++ } ++ slot->prout = slot->out; ++} ++ ++/* ++ Channel ++*/ ++ ++static void OPL3_ChannelSetupAlg(opl3_channel *channel); ++ ++static void OPL3_ChannelUpdateRhythm(opl3_chip *chip, uint8_t data) ++{ ++ opl3_channel *channel6; ++ opl3_channel *channel7; ++ opl3_channel *channel8; ++ uint8_t chnum; ++ ++ chip->rhy = data & 0x3f; ++ if (chip->rhy & 0x20) ++ { ++ channel6 = &chip->channel[6]; ++ channel7 = &chip->channel[7]; ++ channel8 = &chip->channel[8]; ++ channel6->out[0] = &channel6->slotz[1]->out; ++ channel6->out[1] = &channel6->slotz[1]->out; ++ channel6->out[2] = &chip->zeromod; ++ channel6->out[3] = &chip->zeromod; ++ channel6->out_cnt = 2; ++ channel7->out[0] = &channel7->slotz[0]->out; ++ channel7->out[1] = &channel7->slotz[0]->out; ++ channel7->out[2] = &channel7->slotz[1]->out; ++ channel7->out[3] = &channel7->slotz[1]->out; ++ channel7->out_cnt = 4; ++ channel8->out[0] = &channel8->slotz[0]->out; ++ channel8->out[1] = &channel8->slotz[0]->out; ++ channel8->out[2] = &channel8->slotz[1]->out; ++ channel8->out[3] = &channel8->slotz[1]->out; ++ channel8->out_cnt = 4; ++ for (chnum = 6; chnum < 9; chnum++) ++ { ++ chip->channel[chnum].chtype = ch_drum; ++ } ++ OPL3_ChannelSetupAlg(channel6); ++ OPL3_ChannelSetupAlg(channel7); ++ OPL3_ChannelSetupAlg(channel8); ++ /* hh */ ++ if (chip->rhy & 0x01) ++ { ++ OPL3_EnvelopeKeyOn(channel7->slotz[0], egk_drum); ++ } ++ else ++ { ++ OPL3_EnvelopeKeyOff(channel7->slotz[0], egk_drum); ++ } ++ /* tc */ ++ if (chip->rhy & 0x02) ++ { ++ OPL3_EnvelopeKeyOn(channel8->slotz[1], egk_drum); ++ } ++ else ++ { ++ OPL3_EnvelopeKeyOff(channel8->slotz[1], egk_drum); ++ } ++ /* tom */ ++ if (chip->rhy & 0x04) ++ { ++ OPL3_EnvelopeKeyOn(channel8->slotz[0], egk_drum); ++ } ++ else ++ { ++ OPL3_EnvelopeKeyOff(channel8->slotz[0], egk_drum); ++ } ++ /* sd */ ++ if (chip->rhy & 0x08) ++ { ++ OPL3_EnvelopeKeyOn(channel7->slotz[1], egk_drum); ++ } ++ else ++ { ++ OPL3_EnvelopeKeyOff(channel7->slotz[1], egk_drum); ++ } ++ /* bd */ ++ if (chip->rhy & 0x10) ++ { ++ OPL3_EnvelopeKeyOn(channel6->slotz[0], egk_drum); ++ OPL3_EnvelopeKeyOn(channel6->slotz[1], egk_drum); ++ } ++ else ++ { ++ OPL3_EnvelopeKeyOff(channel6->slotz[0], egk_drum); ++ OPL3_EnvelopeKeyOff(channel6->slotz[1], egk_drum); ++ } ++ } ++ else ++ { ++ for (chnum = 6; chnum < 9; chnum++) ++ { ++ chip->channel[chnum].chtype = ch_2op; ++ OPL3_ChannelSetupAlg(&chip->channel[chnum]); ++ OPL3_EnvelopeKeyOff(chip->channel[chnum].slotz[0], egk_drum); ++ OPL3_EnvelopeKeyOff(chip->channel[chnum].slotz[1], egk_drum); ++ } ++ } ++} ++ ++static void OPL3_ChannelWriteA0(opl3_channel *channel, uint8_t data) ++{ ++ if (channel->chip->newm && channel->chtype == ch_4op2) ++ { ++ return; ++ } ++ channel->f_num = (channel->f_num & 0x300) | data; ++ channel->ksv = (channel->block << 1) ++ | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); ++ OPL3_EnvelopeUpdateKSL(channel->slotz[0]); ++ OPL3_EnvelopeUpdateKSL(channel->slotz[1]); ++ OPL3_EnvelopeUpdateRate(channel->slotz[0]); ++ OPL3_EnvelopeUpdateRate(channel->slotz[1]); ++ OPL3_PhaseUpdateInc(channel->slotz[0]); ++ OPL3_PhaseUpdateInc(channel->slotz[1]); ++ if (channel->chip->newm && channel->chtype == ch_4op) ++ { ++ channel->pair->f_num = channel->f_num; ++ channel->pair->ksv = channel->ksv; ++ OPL3_EnvelopeUpdateKSL(channel->pair->slotz[0]); ++ OPL3_EnvelopeUpdateKSL(channel->pair->slotz[1]); ++ OPL3_EnvelopeUpdateRate(channel->pair->slotz[0]); ++ OPL3_EnvelopeUpdateRate(channel->pair->slotz[1]); ++ OPL3_PhaseUpdateInc(channel->pair->slotz[0]); ++ OPL3_PhaseUpdateInc(channel->pair->slotz[1]); ++ } ++} ++ ++static void OPL3_ChannelWriteB0(opl3_channel *channel, uint8_t data) ++{ ++ if (channel->chip->newm && channel->chtype == ch_4op2) ++ { ++ return; ++ } ++ channel->f_num = (channel->f_num & 0xff) | ((data & 0x03) << 8); ++ channel->block = (data >> 2) & 0x07; ++ channel->ksv = (channel->block << 1) ++ | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); ++ OPL3_EnvelopeUpdateKSL(channel->slotz[0]); ++ OPL3_EnvelopeUpdateKSL(channel->slotz[1]); ++ OPL3_EnvelopeUpdateRate(channel->slotz[0]); ++ OPL3_EnvelopeUpdateRate(channel->slotz[1]); ++ OPL3_PhaseUpdateInc(channel->slotz[0]); ++ OPL3_PhaseUpdateInc(channel->slotz[1]); ++ if (channel->chip->newm && channel->chtype == ch_4op) ++ { ++ channel->pair->f_num = channel->f_num; ++ channel->pair->block = channel->block; ++ channel->pair->ksv = channel->ksv; ++ OPL3_EnvelopeUpdateKSL(channel->pair->slotz[0]); ++ OPL3_EnvelopeUpdateKSL(channel->pair->slotz[1]); ++ OPL3_EnvelopeUpdateRate(channel->pair->slotz[0]); ++ OPL3_EnvelopeUpdateRate(channel->pair->slotz[1]); ++ OPL3_PhaseUpdateInc(channel->pair->slotz[0]); ++ OPL3_PhaseUpdateInc(channel->pair->slotz[1]); ++ } ++} ++ ++static void OPL3_ChannelSetupAlg(opl3_channel *channel) ++{ ++ if (channel->chtype == ch_drum) ++ { ++ if (channel->ch_num == 7 || channel->ch_num == 8) ++ { ++ channel->slotz[0]->mod = &channel->chip->zeromod; ++ channel->slotz[1]->mod = &channel->chip->zeromod; ++ return; ++ } ++ switch (channel->alg & 0x01) ++ { ++ case 0x00: ++ channel->slotz[0]->mod = &channel->slotz[0]->fbmod; ++ channel->slotz[1]->mod = &channel->slotz[0]->out; ++ break; ++ case 0x01: ++ channel->slotz[0]->mod = &channel->slotz[0]->fbmod; ++ channel->slotz[1]->mod = &channel->chip->zeromod; ++ break; ++ } ++ return; ++ } ++ if (channel->alg & 0x08) ++ { ++ return; ++ } ++ if (channel->alg & 0x04) ++ { ++ channel->pair->out[0] = &channel->chip->zeromod; ++ channel->pair->out[1] = &channel->chip->zeromod; ++ channel->pair->out[2] = &channel->chip->zeromod; ++ channel->pair->out[3] = &channel->chip->zeromod; ++ channel->pair->out_cnt = 0; ++ switch (channel->alg & 0x03) ++ { ++ case 0x00: ++ channel->pair->slotz[0]->mod = &channel->pair->slotz[0]->fbmod; ++ channel->pair->slotz[1]->mod = &channel->pair->slotz[0]->out; ++ channel->slotz[0]->mod = &channel->pair->slotz[1]->out; ++ channel->slotz[1]->mod = &channel->slotz[0]->out; ++ channel->out[0] = &channel->slotz[1]->out; ++ channel->out[1] = &channel->chip->zeromod; ++ channel->out[2] = &channel->chip->zeromod; ++ channel->out[3] = &channel->chip->zeromod; ++ channel->out_cnt = 1; ++ break; ++ case 0x01: ++ channel->pair->slotz[0]->mod = &channel->pair->slotz[0]->fbmod; ++ channel->pair->slotz[1]->mod = &channel->pair->slotz[0]->out; ++ channel->slotz[0]->mod = &channel->chip->zeromod; ++ channel->slotz[1]->mod = &channel->slotz[0]->out; ++ channel->out[0] = &channel->pair->slotz[1]->out; ++ channel->out[1] = &channel->slotz[1]->out; ++ channel->out[2] = &channel->chip->zeromod; ++ channel->out[3] = &channel->chip->zeromod; ++ channel->out_cnt = 2; ++ break; ++ case 0x02: ++ channel->pair->slotz[0]->mod = &channel->pair->slotz[0]->fbmod; ++ channel->pair->slotz[1]->mod = &channel->chip->zeromod; ++ channel->slotz[0]->mod = &channel->pair->slotz[1]->out; ++ channel->slotz[1]->mod = &channel->slotz[0]->out; ++ channel->out[0] = &channel->pair->slotz[0]->out; ++ channel->out[1] = &channel->slotz[1]->out; ++ channel->out[2] = &channel->chip->zeromod; ++ channel->out[3] = &channel->chip->zeromod; ++ channel->out_cnt = 2; ++ break; ++ case 0x03: ++ channel->pair->slotz[0]->mod = &channel->pair->slotz[0]->fbmod; ++ channel->pair->slotz[1]->mod = &channel->chip->zeromod; ++ channel->slotz[0]->mod = &channel->pair->slotz[1]->out; ++ channel->slotz[1]->mod = &channel->chip->zeromod; ++ channel->out[0] = &channel->pair->slotz[0]->out; ++ channel->out[1] = &channel->slotz[0]->out; ++ channel->out[2] = &channel->slotz[1]->out; ++ channel->out[3] = &channel->chip->zeromod; ++ channel->out_cnt = 3; ++ break; ++ } ++ } ++ else ++ { ++ switch (channel->alg & 0x01) ++ { ++ case 0x00: ++ channel->slotz[0]->mod = &channel->slotz[0]->fbmod; ++ channel->slotz[1]->mod = &channel->slotz[0]->out; ++ channel->out[0] = &channel->slotz[1]->out; ++ channel->out[1] = &channel->chip->zeromod; ++ channel->out[2] = &channel->chip->zeromod; ++ channel->out[3] = &channel->chip->zeromod; ++ channel->out_cnt = 1; ++ break; ++ case 0x01: ++ channel->slotz[0]->mod = &channel->slotz[0]->fbmod; ++ channel->slotz[1]->mod = &channel->chip->zeromod; ++ channel->out[0] = &channel->slotz[0]->out; ++ channel->out[1] = &channel->slotz[1]->out; ++ channel->out[2] = &channel->chip->zeromod; ++ channel->out[3] = &channel->chip->zeromod; ++ channel->out_cnt = 2; ++ break; ++ } ++ } ++} ++ ++static void OPL3_ChannelUpdateAlg(opl3_channel *channel) ++{ ++ channel->alg = channel->con; ++ if (channel->chip->newm) ++ { ++ if (channel->chtype == ch_4op) ++ { ++ channel->pair->alg = 0x04 | (channel->con << 1) | (channel->pair->con); ++ channel->alg = 0x08; ++ OPL3_ChannelSetupAlg(channel->pair); ++ } ++ else if (channel->chtype == ch_4op2) ++ { ++ channel->alg = 0x04 | (channel->pair->con << 1) | (channel->con); ++ channel->pair->alg = 0x08; ++ OPL3_ChannelSetupAlg(channel); ++ } ++ else ++ { ++ OPL3_ChannelSetupAlg(channel); ++ } ++ } ++ else ++ { ++ OPL3_ChannelSetupAlg(channel); ++ } ++} ++ ++static void OPL3_ChannelWriteC0(opl3_channel *channel, uint8_t data) ++{ ++ channel->fb = (data & 0x0e) >> 1; ++ channel->con = data & 0x01; ++ OPL3_ChannelUpdateAlg(channel); ++ if (channel->chip->newm) ++ { ++ channel->cha = ((data >> 4) & 0x01) ? ~0 : 0; ++ channel->chb = ((data >> 5) & 0x01) ? ~0 : 0; ++ channel->chc = ((data >> 6) & 0x01) ? ~0 : 0; ++ channel->chd = ((data >> 7) & 0x01) ? ~0 : 0; ++ } ++ else ++ { ++ channel->cha = channel->chb = (uint16_t)~0; ++ // TODO: Verify on real chip if DAC2 output is disabled in compat mode ++ channel->chc = channel->chd = 0; ++ } ++#if OPL_ENABLE_STEREOEXT ++ if (!channel->chip->stereoext) ++ { ++ channel->leftpan = channel->cha << 16; ++ channel->rightpan = channel->chb << 16; ++ } ++#endif ++} ++ ++#if OPL_ENABLE_STEREOEXT ++static void OPL3_ChannelWriteD0(opl3_channel* channel, uint8_t data) ++{ ++ if (channel->chip->stereoext) ++ { ++ channel->leftpan = panpot_lut[data ^ 0xffu]; ++ channel->rightpan = panpot_lut[data]; ++ } ++} ++#endif ++ ++static void OPL3_ChannelKeyOn(opl3_channel *channel) ++{ ++ if (channel->chip->newm) ++ { ++ if (channel->chtype == ch_4op) ++ { ++ OPL3_EnvelopeKeyOn(channel->slotz[0], egk_norm); ++ OPL3_EnvelopeKeyOn(channel->slotz[1], egk_norm); ++ OPL3_EnvelopeKeyOn(channel->pair->slotz[0], egk_norm); ++ OPL3_EnvelopeKeyOn(channel->pair->slotz[1], egk_norm); ++ } ++ else if (channel->chtype == ch_2op || channel->chtype == ch_drum) ++ { ++ OPL3_EnvelopeKeyOn(channel->slotz[0], egk_norm); ++ OPL3_EnvelopeKeyOn(channel->slotz[1], egk_norm); ++ } ++ } ++ else ++ { ++ OPL3_EnvelopeKeyOn(channel->slotz[0], egk_norm); ++ OPL3_EnvelopeKeyOn(channel->slotz[1], egk_norm); ++ } ++} ++ ++static void OPL3_ChannelKeyOff(opl3_channel *channel) ++{ ++ if (channel->chip->newm) ++ { ++ if (channel->chtype == ch_4op) ++ { ++ OPL3_EnvelopeKeyOff(channel->slotz[0], egk_norm); ++ OPL3_EnvelopeKeyOff(channel->slotz[1], egk_norm); ++ OPL3_EnvelopeKeyOff(channel->pair->slotz[0], egk_norm); ++ OPL3_EnvelopeKeyOff(channel->pair->slotz[1], egk_norm); ++ } ++ else if (channel->chtype == ch_2op || channel->chtype == ch_drum) ++ { ++ OPL3_EnvelopeKeyOff(channel->slotz[0], egk_norm); ++ OPL3_EnvelopeKeyOff(channel->slotz[1], egk_norm); ++ } ++ } ++ else ++ { ++ OPL3_EnvelopeKeyOff(channel->slotz[0], egk_norm); ++ OPL3_EnvelopeKeyOff(channel->slotz[1], egk_norm); ++ } ++} ++ ++static void OPL3_ChannelSet4Op(opl3_chip *chip, uint8_t data) ++{ ++ uint8_t bit; ++ uint8_t chnum; ++ for (bit = 0; bit < 6; bit++) ++ { ++ chnum = bit; ++ if (bit >= 3) ++ { ++ chnum += 9 - 3; ++ } ++ if ((data >> bit) & 0x01) ++ { ++ chip->channel[chnum].chtype = ch_4op; ++ chip->channel[chnum + 3u].chtype = ch_4op2; ++ OPL3_ChannelUpdateAlg(&chip->channel[chnum]); ++ } ++ else ++ { ++ chip->channel[chnum].chtype = ch_2op; ++ chip->channel[chnum + 3u].chtype = ch_2op; ++ OPL3_ChannelUpdateAlg(&chip->channel[chnum]); ++ OPL3_ChannelUpdateAlg(&chip->channel[chnum + 3u]); ++ } ++ } ++} ++ ++static int16_t OPL3_ClipSample(int32_t sample) ++{ ++ if (sample > 32767) ++ { ++ sample = 32767; ++ } ++ else if (sample < -32768) ++ { ++ sample = -32768; ++ } ++ return (int16_t)sample; ++} ++ ++static void OPL3_ProcessSlot(opl3_slot *slot) ++{ ++ /* Fast path for fully-attenuated key-off non-rhythm slots. The envelope ++ * rate machine cannot change eg_rout here, but the full path still updates ++ * feedback history, eg_out/eg_gen/pg_reset, phase output, noise, and out. */ ++ if (!slot->key && slot->eg_rout == 0x1ff ++ && slot->slot_num != 13 && slot->slot_num != 16 && slot->slot_num != 17) ++ { ++ opl3_chip *chip = slot->chip; ++ uint32_t phaseinc; ++ uint16_t phase; ++ uint32_t noise = chip->noise; ++ uint8_t n_bit = ((noise >> 14) ^ noise) & 0x01; ++ ++ if (slot->channel->fb == 0 && slot->pg_inc == 0 && slot->out == 0 ++ && *slot->mod == 0 && slot->eg_tl_ksl == 0 && *slot->trem == 0 ++ && slot->pg_phase == 0 && slot->reg_vib == 0 && slot->reg_wf == 0) ++ { ++ slot->fbmod = 0; ++ slot->prout = 0; ++ slot->eg_out = 0x1ff; ++ slot->pg_reset = 0; ++ slot->eg_gen = envelope_gen_num_release; ++ slot->pg_phase_out = 0; ++ chip->noise = (noise >> 1) | (n_bit << 22); ++ return; ++ } ++ ++ OPL3_SlotCalcFB(slot); ++ ++ slot->eg_out = slot->eg_rout + slot->eg_tl_ksl + *slot->trem; ++ slot->pg_reset = 0; ++ slot->eg_gen = envelope_gen_num_release; ++ ++ if (slot->reg_vib) ++ { ++ uint16_t f_num = slot->channel->f_num; ++ int8_t range = (f_num >> 7) & 7; ++ uint8_t vibpos = chip->vibpos; ++ ++ if (!(vibpos & 3)) ++ { ++ range = 0; ++ } ++ else if (vibpos & 1) ++ { ++ range >>= 1; ++ } ++ range >>= chip->vibshift; ++ ++ if (vibpos & 4) ++ { ++ range = -range; ++ } ++ f_num += range; ++ phaseinc = (((uint32_t)f_num << slot->channel->block) >> 1) ++ * mt[slot->reg_mult] >> 1; ++ } ++ else ++ { ++ phaseinc = slot->pg_inc; ++ } ++ ++ phase = (uint16_t)(slot->pg_phase >> 9); ++ slot->pg_phase += phaseinc; ++ slot->pg_phase_out = phase; ++ chip->noise = (noise >> 1) | (n_bit << 22); ++ ++ /* eg_out = eg_rout + eg_tl_ksl + *trem >= 0x1ff here, so the ++ * silent-regime shortcut is always valid. */ ++ OPL3_SlotGenerateSilent(slot); ++ return; ++ } ++ if (slot->eg_gen == envelope_gen_num_sustain && slot->key ++ && slot->eg_rates[envelope_gen_num_sustain] == 0) ++ { ++ OPL3_SlotCalcFB(slot); ++ slot->eg_out = slot->eg_rout + slot->eg_tl_ksl + *slot->trem; ++ slot->pg_reset = 0; ++ if ((slot->eg_rout & 0x1f8) == 0x1f8) ++ { ++ slot->eg_rout = 0x1ff; ++ } ++ ++ if (!slot->reg_vib ++ && slot->slot_num != 13 && slot->slot_num != 16 && slot->slot_num != 17) ++ { ++ opl3_chip *chip = slot->chip; ++ uint32_t noise = chip->noise; ++ uint8_t n_bit = ((noise >> 14) ^ noise) & 0x01; ++ uint16_t phase = (uint16_t)(slot->pg_phase >> 9); ++ ++ slot->pg_phase += slot->pg_inc; ++ slot->pg_phase_out = phase; ++ chip->noise = (noise >> 1) | (n_bit << 22); ++ } ++ else ++ { ++ OPL3_PhaseGenerate(slot); ++ } ++ ++ OPL3_SlotGenerate(slot); ++ return; ++ } ++ OPL3_SlotCalcFB(slot); ++ OPL3_EnvelopeCalc(slot); ++ OPL3_PhaseGenerate(slot); ++ OPL3_SlotGenerate(slot); ++} ++ ++inline void OPL3_Generate4Ch(opl3_chip *chip, int16_t *buf4) ++{ ++ opl3_channel *channel; ++ opl3_writebuf *writebuf; ++ int16_t **out; ++ int32_t mix[2]; ++ uint8_t ii; ++ int16_t accm; ++ uint8_t shift = 0; ++ uint8_t update_tremolo; ++ ++ buf4[1] = OPL3_ClipSample(chip->mixbuff[1]); ++ buf4[3] = OPL3_ClipSample(chip->mixbuff[3]); ++ ++#if OPL_QUIRK_CHANNELSAMPLEDELAY ++ for (ii = 0; ii < 15; ii++) ++#else ++ for (ii = 0; ii < 36; ii++) ++#endif ++ { ++ OPL3_ProcessSlot(&chip->slot[ii]); ++ } ++ ++ mix[0] = mix[1] = 0; ++ for (ii = 0; ii < 18; ii++) ++ { ++ channel = &chip->channel[ii]; ++ if (!channel->out_cnt) continue; ++#if OPL_ENABLE_STEREOEXT ++ if (!(channel->leftpan | channel->chc)) continue; ++#else ++ if (!(channel->cha | channel->chc)) continue; ++#endif ++ out = channel->out; ++ accm = *out[0]; ++ if (channel->out_cnt > 1) ++ { ++ accm += *out[1]; ++ if (channel->out_cnt > 2) ++ { ++ accm += *out[2]; ++ if (channel->out_cnt > 3) accm += *out[3]; ++ } ++ } ++#if OPL_ENABLE_STEREOEXT ++ mix[0] += (int16_t)((accm * channel->leftpan) >> 16); ++#else ++ mix[0] += (int16_t)(accm & channel->cha); ++#endif ++ mix[1] += (int16_t)(accm & channel->chc); ++ } ++ chip->mixbuff[0] = mix[0]; ++ chip->mixbuff[2] = mix[1]; ++ ++#if OPL_QUIRK_CHANNELSAMPLEDELAY ++ for (ii = 15; ii < 18; ii++) ++ { ++ OPL3_ProcessSlot(&chip->slot[ii]); ++ } ++#endif ++ ++ buf4[0] = OPL3_ClipSample(chip->mixbuff[0]); ++ buf4[2] = OPL3_ClipSample(chip->mixbuff[2]); ++ ++#if OPL_QUIRK_CHANNELSAMPLEDELAY ++ for (ii = 18; ii < 33; ii++) ++ { ++ OPL3_ProcessSlot(&chip->slot[ii]); ++ } ++#endif ++ ++ mix[0] = mix[1] = 0; ++ for (ii = 0; ii < 18; ii++) ++ { ++ channel = &chip->channel[ii]; ++ if (!channel->out_cnt) continue; ++ out = channel->out; ++ accm = *out[0]; ++ if (channel->out_cnt > 1) ++ { ++ accm += *out[1]; ++ if (channel->out_cnt > 2) ++ { ++ accm += *out[2]; ++ if (channel->out_cnt > 3) accm += *out[3]; ++ } ++ } ++#if OPL_ENABLE_STEREOEXT ++ mix[0] += (int16_t)((accm * channel->rightpan) >> 16); ++#else ++ mix[0] += (int16_t)(accm & channel->chb); ++#endif ++ mix[1] += (int16_t)(accm & channel->chd); ++ } ++ chip->mixbuff[1] = mix[0]; ++ chip->mixbuff[3] = mix[1]; ++ ++#if OPL_QUIRK_CHANNELSAMPLEDELAY ++ for (ii = 33; ii < 36; ii++) ++ { ++ OPL3_ProcessSlot(&chip->slot[ii]); ++ } ++#endif ++ ++ update_tremolo = chip->tremolo_dirty; ++ if ((chip->timer & 0x3f) == 0x3f) ++ { ++ chip->tremolopos++; ++ if (chip->tremolopos == 210) ++ { ++ chip->tremolopos = 0; ++ } ++ update_tremolo = 1; ++ } ++ if (update_tremolo) ++ { ++ if (chip->tremolopos < 105) ++ { ++ chip->tremolo = chip->tremolopos >> chip->tremoloshift; ++ } ++ else ++ { ++ chip->tremolo = (210 - chip->tremolopos) >> chip->tremoloshift; ++ } ++ chip->tremolo_dirty = 0; ++ } ++ ++ if ((chip->timer & 0x3ff) == 0x3ff) ++ { ++ chip->vibpos = (chip->vibpos + 1) & 7; ++ } ++ ++ chip->timer++; ++ ++ if (chip->eg_state) ++ { ++ uint32_t eg_timer_low = (uint32_t)chip->eg_timer & 0x1fffu; ++ if (!eg_timer_low) ++ { ++ chip->eg_add = 0; ++ } ++ else ++ { ++#if defined(__GNUC__) || defined(__clang__) ++ shift = (uint8_t)__builtin_ctz(eg_timer_low); ++#else ++ while (((eg_timer_low >> shift) & 1) == 0) ++ { ++ shift++; ++ } ++#endif ++ chip->eg_add = shift + 1; ++ } ++ chip->eg_timer_lo = (uint8_t)(chip->eg_timer & 0x3u); ++ } ++ ++ if (chip->eg_timerrem || chip->eg_state) ++ { ++ if (chip->eg_timer == UINT64_C(0xfffffffff)) ++ { ++ chip->eg_timer = 0; ++ chip->eg_timerrem = 1; ++ } ++ else ++ { ++ chip->eg_timer++; ++ chip->eg_timerrem = 0; ++ } ++ } ++ ++ chip->eg_state ^= 1; ++ ++ while ((writebuf = &chip->writebuf[chip->writebuf_cur]), writebuf->time <= chip->writebuf_samplecnt) ++ { ++ if (!(writebuf->reg & 0x200)) ++ { ++ break; ++ } ++ writebuf->reg &= 0x1ff; ++ OPL3_WriteReg(chip, writebuf->reg, writebuf->data); ++ chip->writebuf_cur = (chip->writebuf_cur + 1) % OPL_WRITEBUF_SIZE; ++ } ++ chip->writebuf_samplecnt++; ++} ++ ++void OPL3_Generate(opl3_chip *chip, int16_t *buf) ++{ ++ int16_t samples[4]; ++ OPL3_Generate4Ch(chip, samples); ++ buf[0] = samples[0]; ++ buf[1] = samples[1]; ++} ++ ++void OPL3_Generate4ChResampled(opl3_chip *chip, int16_t *buf4) ++{ ++ while (chip->samplecnt >= chip->rateratio) ++ { ++ chip->oldsamples[0] = chip->samples[0]; ++ chip->oldsamples[1] = chip->samples[1]; ++ chip->oldsamples[2] = chip->samples[2]; ++ chip->oldsamples[3] = chip->samples[3]; ++ OPL3_Generate4Ch(chip, chip->samples); ++ chip->samplecnt -= chip->rateratio; ++ } ++ buf4[0] = (int16_t)((chip->oldsamples[0] * (chip->rateratio - chip->samplecnt) ++ + chip->samples[0] * chip->samplecnt) / chip->rateratio); ++ buf4[1] = (int16_t)((chip->oldsamples[1] * (chip->rateratio - chip->samplecnt) ++ + chip->samples[1] * chip->samplecnt) / chip->rateratio); ++ buf4[2] = (int16_t)((chip->oldsamples[2] * (chip->rateratio - chip->samplecnt) ++ + chip->samples[2] * chip->samplecnt) / chip->rateratio); ++ buf4[3] = (int16_t)((chip->oldsamples[3] * (chip->rateratio - chip->samplecnt) ++ + chip->samples[3] * chip->samplecnt) / chip->rateratio); ++ chip->samplecnt += 1 << RSM_FRAC; ++} ++ ++void OPL3_GenerateResampled(opl3_chip *chip, int16_t *buf) ++{ ++ int16_t samples[4]; ++ OPL3_Generate4ChResampled(chip, samples); ++ buf[0] = samples[0]; ++ buf[1] = samples[1]; ++} ++ ++void OPL3_Reset(opl3_chip *chip, uint32_t samplerate) ++{ ++ opl3_slot *slot; ++ opl3_channel *channel; ++ uint8_t slotnum; ++ uint8_t channum; ++ uint8_t local_ch_slot; ++ ++ memset(chip, 0, sizeof(opl3_chip)); ++ for (slotnum = 0; slotnum < 36; slotnum++) ++ { ++ slot = &chip->slot[slotnum]; ++ slot->chip = chip; ++ slot->mod = &chip->zeromod; ++ slot->eg_rout = 0x1ff; ++ slot->eg_out = 0x1ff; ++ slot->eg_gen = envelope_gen_num_release; ++ slot->trem = (uint8_t*)&chip->zeromod; ++ slot->eg_rates[0] = slot->eg_rates[1] = slot->eg_rates[2] = slot->eg_rates[3] = 0; ++ slot->slot_num = slotnum; ++ } ++ for (channum = 0; channum < 18; channum++) ++ { ++ channel = &chip->channel[channum]; ++ local_ch_slot = ch_slot[channum]; ++ channel->slotz[0] = &chip->slot[local_ch_slot]; ++ channel->slotz[1] = &chip->slot[local_ch_slot + 3u]; ++ chip->slot[local_ch_slot].channel = channel; ++ chip->slot[local_ch_slot + 3u].channel = channel; ++ if ((channum % 9) < 3) ++ { ++ channel->pair = &chip->channel[channum + 3u]; ++ } ++ else if ((channum % 9) < 6) ++ { ++ channel->pair = &chip->channel[channum - 3u]; ++ } ++ channel->chip = chip; ++ channel->out[0] = &chip->zeromod; ++ channel->out[1] = &chip->zeromod; ++ channel->out[2] = &chip->zeromod; ++ channel->out[3] = &chip->zeromod; ++ channel->out_cnt = 0; ++ channel->chtype = ch_2op; ++ channel->cha = 0xffff; ++ channel->chb = 0xffff; ++#if OPL_ENABLE_STEREOEXT ++ channel->leftpan = 0x10000; ++ channel->rightpan = 0x10000; ++#endif ++ channel->ch_num = channum; ++ OPL3_ChannelSetupAlg(channel); ++ } ++ chip->noise = 1; ++ chip->rateratio = (samplerate << RSM_FRAC) / 49716; ++ chip->tremoloshift = 4; ++ chip->vibshift = 1; ++ ++#if OPL_ENABLE_STEREOEXT ++ if (!panpot_lut_build) ++ { ++ int32_t i; ++ for (i = 0; i < 256; i++) ++ { ++ panpot_lut[i] = OPL_SIN(i); ++ } ++ panpot_lut_build = 1; ++ } ++#endif ++} ++ ++void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v) ++{ ++ uint8_t high = (reg >> 8) & 0x01; ++ uint8_t regm = reg & 0xff; ++ switch (regm & 0xf0) ++ { ++ case 0x00: ++ if (high) ++ { ++ switch (regm & 0x0f) ++ { ++ case 0x04: ++ OPL3_ChannelSet4Op(chip, v); ++ break; ++ case 0x05: ++ chip->newm = v & 0x01; ++#if OPL_ENABLE_STEREOEXT ++ chip->stereoext = (v >> 1) & 0x01; ++#endif ++ break; ++ } ++ } ++ else ++ { ++ switch (regm & 0x0f) ++ { ++ case 0x08: ++ chip->nts = (v >> 6) & 0x01; ++ break; ++ } ++ } ++ break; ++ case 0x20: ++ case 0x30: ++ if (ad_slot[regm & 0x1fu] >= 0) ++ { ++ OPL3_SlotWrite20(&chip->slot[18u * high + ad_slot[regm & 0x1fu]], v); ++ } ++ break; ++ case 0x40: ++ case 0x50: ++ if (ad_slot[regm & 0x1fu] >= 0) ++ { ++ OPL3_SlotWrite40(&chip->slot[18u * high + ad_slot[regm & 0x1fu]], v); ++ } ++ break; ++ case 0x60: ++ case 0x70: ++ if (ad_slot[regm & 0x1fu] >= 0) ++ { ++ OPL3_SlotWrite60(&chip->slot[18u * high + ad_slot[regm & 0x1fu]], v); ++ } ++ break; ++ case 0x80: ++ case 0x90: ++ if (ad_slot[regm & 0x1fu] >= 0) ++ { ++ OPL3_SlotWrite80(&chip->slot[18u * high + ad_slot[regm & 0x1fu]], v); ++ } ++ break; ++ case 0xe0: ++ case 0xf0: ++ if (ad_slot[regm & 0x1fu] >= 0) ++ { ++ OPL3_SlotWriteE0(&chip->slot[18u * high + ad_slot[regm & 0x1fu]], v); ++ } ++ break; ++ case 0xa0: ++ if ((regm & 0x0f) < 9) ++ { ++ OPL3_ChannelWriteA0(&chip->channel[9u * high + (regm & 0x0fu)], v); ++ } ++ break; ++ case 0xb0: ++ if (regm == 0xbd && !high) ++ { ++ uint8_t tremoloshift = (((v >> 7) ^ 1) << 1) + 2; ++ if (chip->tremoloshift != tremoloshift) ++ { ++ chip->tremolo_dirty = 1; ++ } ++ chip->tremoloshift = tremoloshift; ++ chip->vibshift = ((v >> 6) & 0x01) ^ 1; ++ OPL3_ChannelUpdateRhythm(chip, v); ++ } ++ else if ((regm & 0x0f) < 9) ++ { ++ OPL3_ChannelWriteB0(&chip->channel[9u * high + (regm & 0x0fu)], v); ++ if (v & 0x20) ++ { ++ OPL3_ChannelKeyOn(&chip->channel[9u * high + (regm & 0x0fu)]); ++ } ++ else ++ { ++ OPL3_ChannelKeyOff(&chip->channel[9u * high + (regm & 0x0fu)]); ++ } ++ } ++ break; ++ case 0xc0: ++ if ((regm & 0x0f) < 9) ++ { ++ OPL3_ChannelWriteC0(&chip->channel[9u * high + (regm & 0x0fu)], v); ++ } ++ break; ++#if OPL_ENABLE_STEREOEXT ++ case 0xd0: ++ if ((regm & 0x0f) < 9) ++ { ++ OPL3_ChannelWriteD0(&chip->channel[9u * high + (regm & 0x0fu)], v); ++ } ++ break; ++#endif ++ } ++} ++ ++void OPL3_WriteRegBuffered(opl3_chip *chip, uint16_t reg, uint8_t v) ++{ ++ uint64_t time1, time2; ++ opl3_writebuf *writebuf; ++ uint32_t writebuf_last; ++ ++ writebuf_last = chip->writebuf_last; ++ writebuf = &chip->writebuf[writebuf_last]; ++ ++ if (writebuf->reg & 0x200) ++ { ++ OPL3_WriteReg(chip, writebuf->reg & 0x1ff, writebuf->data); ++ ++ chip->writebuf_cur = (writebuf_last + 1) % OPL_WRITEBUF_SIZE; ++ chip->writebuf_samplecnt = writebuf->time; ++ } ++ ++ writebuf->reg = reg | 0x200; ++ writebuf->data = v; ++ time1 = chip->writebuf_lasttime + OPL_WRITEBUF_DELAY; ++ time2 = chip->writebuf_samplecnt; ++ ++ if (time1 < time2) ++ { ++ time1 = time2; ++ } ++ ++ writebuf->time = time1; ++ chip->writebuf_lasttime = time1; ++ chip->writebuf_last = (writebuf_last + 1) % OPL_WRITEBUF_SIZE; ++} ++ ++void OPL3_Generate4ChStream(opl3_chip *chip, int16_t *sndptr1, int16_t *sndptr2, uint32_t numsamples) ++{ ++ uint_fast32_t i; ++ int16_t samples[4]; ++ ++ for(i = 0; i < numsamples; i++) ++ { ++ OPL3_Generate4ChResampled(chip, samples); ++ sndptr1[0] = samples[0]; ++ sndptr1[1] = samples[1]; ++ sndptr2[0] = samples[2]; ++ sndptr2[1] = samples[3]; ++ sndptr1 += 2; ++ sndptr2 += 2; ++ } ++} ++ ++void OPL3_GenerateStream(opl3_chip *chip, int16_t *sndptr, uint32_t numsamples) ++{ ++ uint_fast32_t i; ++ ++ for(i = 0; i < numsamples; i++) ++ { ++ OPL3_GenerateResampled(chip, sndptr); ++ sndptr += 2; ++ } ++} +--- /dev/null ++++ b/src/opl/opl3.h +@@ -0,0 +1,199 @@ ++ ++/* Nuked OPL3 ++ * ++ * Copyright (C) 2013-2020 Nuke.YKT ++ * Copyright (C) 2026 Tony Gies (Nuked-OPL3-fast modifications) ++ * ++ * This file is part of Nuked OPL3. ++ * ++ * Nuked OPL3 is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as ++ * published by the Free Software Foundation, either version 2.1 ++ * of the License, or (at your option) any later version. ++ * ++ * Nuked OPL3 is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with Nuked OPL3. If not, see . ++ * ++ * Nuked OPL3 emulator. ++ * Thanks: ++ * MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): ++ * Feedback and Rhythm part calculation information. ++ * forums.submarine.org.uk(carbon14, opl3): ++ * Tremolo and phase generator calculation information. ++ * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): ++ * OPL2 ROMs. ++ * siliconpr0n.org(John McMaster, digshadow): ++ * YMF262 and VRC VII decaps and die shots. ++ * ++ * Upstream version: 1.8 (commit cfedb09) ++ * Fork version: 1.8-fast.1 ++ * Fork home: https://github.com/tgies/Nuked-OPL3-fast ++ * ++ * Nuked-OPL3-fast is a bit-exact performance-optimized fork of Nuked-OPL3. ++ * Audio output is identical to upstream for the same register stream. ++ * ++ * Modifications vs. upstream visible in this header: ++ * ++ * - Added cached fields to opl3_slot: eg_tl_ksl, eg_ks, pg_inc, ++ * eg_rate_hi[4], eg_rate_lo[4], slot_num. ++ * - Added out_cnt to opl3_channel for mix-loop active-slot tracking. ++ * - Reordered opl3_slot to put hot per-sample fields first; struct size ++ * shrank from 96 to 88 bytes. ++ * - Removed unused legacy fields (eg_inc, eg_rate) from opl3_slot. ++ */ ++ ++#ifndef OPL_OPL3_H ++#define OPL_OPL3_H ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#include ++ ++#ifndef OPL_ENABLE_STEREOEXT ++#define OPL_ENABLE_STEREOEXT 0 ++#endif ++ ++#define OPL_WRITEBUF_SIZE 1024 ++#define OPL_WRITEBUF_DELAY 2 ++ ++typedef struct _opl3_slot opl3_slot; ++typedef struct _opl3_channel opl3_channel; ++typedef struct _opl3_chip opl3_chip; ++ ++struct _opl3_slot { ++ opl3_channel *channel; ++ opl3_chip *chip; ++ int16_t *mod; ++ uint8_t *trem; ++ uint32_t pg_reset; ++ uint32_t pg_phase; ++ uint32_t pg_inc; ++ int16_t out; ++ int16_t fbmod; ++ int16_t prout; ++ uint16_t eg_rout; ++ uint16_t eg_out; ++ /* Cached (reg_tl << 2) + (eg_ksl >> kslshift[reg_ksl]); maintained by ++ * OPL3_EnvelopeUpdateKSL whenever any of those inputs change. Hoists ++ * a load + lookup + shift out of the per-sample envelope hot path. */ ++ uint16_t eg_tl_ksl; ++ uint16_t pg_phase_out; ++ uint8_t key; ++ uint8_t eg_gen; ++ uint8_t reg_vib; ++ uint8_t reg_mult; ++ uint8_t reg_wf; ++ uint8_t slot_num; ++ uint8_t eg_ksl; ++ uint8_t eg_ks; ++ uint8_t reg_type; ++ uint8_t reg_ksr; ++ uint8_t reg_ksl; ++ uint8_t reg_tl; ++ uint8_t reg_ar; ++ uint8_t reg_dr; ++ uint8_t reg_sl; ++ uint8_t reg_rr; ++ uint8_t eg_rates[4]; ++ uint8_t eg_rate_hi[4]; ++ uint8_t eg_rate_lo[4]; ++}; ++ ++struct _opl3_channel { ++ opl3_slot *slotz[2];/*Don't use "slots" keyword to avoid conflict with Qt applications*/ ++ opl3_channel *pair; ++ opl3_chip *chip; ++ int16_t *out[4]; ++ uint8_t out_cnt; ++ ++#if OPL_ENABLE_STEREOEXT ++ int32_t leftpan; ++ int32_t rightpan; ++#endif ++ ++ uint8_t chtype; ++ uint16_t f_num; ++ uint8_t block; ++ uint8_t fb; ++ uint8_t con; ++ uint8_t alg; ++ uint8_t ksv; ++ uint16_t cha, chb; ++ uint16_t chc, chd; ++ uint8_t ch_num; ++}; ++ ++typedef struct _opl3_writebuf { ++ uint64_t time; ++ uint16_t reg; ++ uint8_t data; ++} opl3_writebuf; ++ ++struct _opl3_chip { ++ opl3_channel channel[18]; ++ opl3_slot slot[36]; ++ uint16_t timer; ++ uint64_t eg_timer; ++ uint8_t eg_timerrem; ++ uint8_t eg_state; ++ uint8_t eg_add; ++ uint8_t eg_timer_lo; ++ uint8_t newm; ++ uint8_t nts; ++ uint8_t rhy; ++ uint8_t vibpos; ++ uint8_t vibshift; ++ uint8_t tremolo; ++ uint8_t tremolopos; ++ uint8_t tremoloshift; ++ uint8_t tremolo_dirty; ++ uint32_t noise; ++ int16_t zeromod; ++ int32_t mixbuff[4]; ++ uint8_t rm_hh_bit2; ++ uint8_t rm_hh_bit3; ++ uint8_t rm_hh_bit7; ++ uint8_t rm_hh_bit8; ++ uint8_t rm_tc_bit3; ++ uint8_t rm_tc_bit5; ++ ++#if OPL_ENABLE_STEREOEXT ++ uint8_t stereoext; ++#endif ++ ++ /* OPL3L */ ++ int32_t rateratio; ++ int32_t samplecnt; ++ int16_t oldsamples[4]; ++ int16_t samples[4]; ++ ++ uint64_t writebuf_samplecnt; ++ uint32_t writebuf_cur; ++ uint32_t writebuf_last; ++ uint64_t writebuf_lasttime; ++ opl3_writebuf writebuf[OPL_WRITEBUF_SIZE]; ++}; ++ ++void OPL3_Generate(opl3_chip *chip, int16_t *buf); ++void OPL3_GenerateResampled(opl3_chip *chip, int16_t *buf); ++void OPL3_Reset(opl3_chip *chip, uint32_t samplerate); ++void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v); ++void OPL3_WriteRegBuffered(opl3_chip *chip, uint16_t reg, uint8_t v); ++void OPL3_GenerateStream(opl3_chip *chip, int16_t *sndptr, uint32_t numsamples); ++ ++void OPL3_Generate4Ch(opl3_chip *chip, int16_t *buf4); ++void OPL3_Generate4ChResampled(opl3_chip *chip, int16_t *buf4); ++void OPL3_Generate4ChStream(opl3_chip *chip, int16_t *sndptr1, int16_t *sndptr2, uint32_t numsamples); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +--- /dev/null ++++ b/src/opl/opl_internal.h +@@ -0,0 +1,71 @@ ++// ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// OPL internal interface. ++// ++ ++ ++#ifndef OPL_INTERNAL_H ++#define OPL_INTERNAL_H ++ ++#include "opl.h" ++ ++typedef int (*opl_init_func)(unsigned int port_base); ++typedef void (*opl_shutdown_func)(void); ++typedef unsigned int (*opl_read_port_func)(opl_port_t port); ++typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value); ++typedef void (*opl_set_callback_func)(uint64_t us, ++ opl_callback_t callback, ++ void *data); ++typedef void (*opl_clear_callbacks_func)(void); ++typedef void (*opl_lock_func)(void); ++typedef void (*opl_unlock_func)(void); ++typedef void (*opl_set_paused_func)(int paused); ++typedef void (*opl_adjust_callbacks_func)(float value); ++ ++typedef struct ++{ ++ const char *name; ++ ++ opl_init_func init_func; ++ opl_shutdown_func shutdown_func; ++ opl_read_port_func read_port_func; ++ opl_write_port_func write_port_func; ++ opl_set_callback_func set_callback_func; ++ opl_clear_callbacks_func clear_callbacks_func; ++ opl_lock_func lock_func; ++ opl_unlock_func unlock_func; ++ opl_set_paused_func set_paused_func; ++ opl_adjust_callbacks_func adjust_callbacks_func; ++} opl_driver_t; ++ ++// Sample rate to use when doing software emulation. ++ ++extern unsigned int opl_sample_rate; ++ ++ ++#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM) ++extern opl_driver_t opl_linux_driver; ++#endif ++#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64) ++extern opl_driver_t opl_openbsd_driver; ++#endif ++#ifdef _WIN32 ++extern opl_driver_t opl_win32_driver; ++#endif ++extern opl_driver_t opl_sdl_driver; ++ ++ ++#endif /* #ifndef OPL_INTERNAL_H */ ++ +--- /dev/null ++++ b/src/opl/opl_queue.c +@@ -0,0 +1,285 @@ ++// ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// Queue of waiting callbacks, stored in a binary min heap, so that we ++// can always get the first callback. ++// ++ ++#include ++#include ++#include ++ ++#include "opl_queue.h" ++ ++#define MAX_OPL_QUEUE 64 ++ ++typedef struct ++{ ++ opl_callback_t callback; ++ void *data; ++ uint64_t time; ++} opl_queue_entry_t; ++ ++struct opl_callback_queue_s ++{ ++ opl_queue_entry_t entries[MAX_OPL_QUEUE]; ++ unsigned int num_entries; ++}; ++ ++opl_callback_queue_t *OPL_Queue_Create(void) ++{ ++ opl_callback_queue_t *queue; ++ ++ queue = malloc(sizeof(opl_callback_queue_t)); ++ queue->num_entries = 0; ++ ++ return queue; ++} ++ ++void OPL_Queue_Destroy(opl_callback_queue_t *queue) ++{ ++ free(queue); ++} ++ ++int OPL_Queue_IsEmpty(opl_callback_queue_t *queue) ++{ ++ return queue->num_entries == 0; ++} ++ ++void OPL_Queue_Clear(opl_callback_queue_t *queue) ++{ ++ queue->num_entries = 0; ++} ++ ++void OPL_Queue_Push(opl_callback_queue_t *queue, ++ opl_callback_t callback, void *data, ++ uint64_t time) ++{ ++ int entry_id; ++ int parent_id; ++ ++ if (queue->num_entries >= MAX_OPL_QUEUE) ++ { ++ fprintf(stderr, "OPL_Queue_Push: Exceeded maximum callbacks\n"); ++ return; ++ } ++ ++ // Add to last queue entry. ++ ++ entry_id = queue->num_entries; ++ ++queue->num_entries; ++ ++ // Shift existing entries down in the heap. ++ ++ while (entry_id > 0) ++ { ++ parent_id = (entry_id - 1) / 2; ++ ++ // Is the heap condition satisfied? ++ ++ if (time >= queue->entries[parent_id].time) ++ { ++ break; ++ } ++ ++ // Move the existing entry down in the heap. ++ ++ memcpy(&queue->entries[entry_id], ++ &queue->entries[parent_id], ++ sizeof(opl_queue_entry_t)); ++ ++ // Advance to the parent. ++ ++ entry_id = parent_id; ++ } ++ ++ // Insert new callback data. ++ ++ queue->entries[entry_id].callback = callback; ++ queue->entries[entry_id].data = data; ++ queue->entries[entry_id].time = time; ++} ++ ++int OPL_Queue_Pop(opl_callback_queue_t *queue, ++ opl_callback_t *callback, void **data) ++{ ++ opl_queue_entry_t *entry; ++ int child1, child2; ++ int i, next_i; ++ ++ // Empty? ++ ++ if (queue->num_entries <= 0) ++ { ++ return 0; ++ } ++ ++ // Store the result: ++ ++ *callback = queue->entries[0].callback; ++ *data = queue->entries[0].data; ++ ++ // Decrease the heap size, and keep pointer to the last entry in ++ // the heap, which must now be percolated down from the top. ++ ++ --queue->num_entries; ++ entry = &queue->entries[queue->num_entries]; ++ ++ // Percolate down. ++ ++ i = 0; ++ ++ for (;;) ++ { ++ child1 = i * 2 + 1; ++ child2 = i * 2 + 2; ++ ++ if (child1 < queue->num_entries ++ && queue->entries[child1].time < entry->time) ++ { ++ // Left child is less than entry. ++ // Use the minimum of left and right children. ++ ++ if (child2 < queue->num_entries ++ && queue->entries[child2].time < queue->entries[child1].time) ++ { ++ next_i = child2; ++ } ++ else ++ { ++ next_i = child1; ++ } ++ } ++ else if (child2 < queue->num_entries ++ && queue->entries[child2].time < entry->time) ++ { ++ // Right child is less than entry. Go down the right side. ++ ++ next_i = child2; ++ } ++ else ++ { ++ // Finished percolating. ++ break; ++ } ++ ++ // Percolate the next value up and advance. ++ ++ memcpy(&queue->entries[i], ++ &queue->entries[next_i], ++ sizeof(opl_queue_entry_t)); ++ i = next_i; ++ } ++ ++ // Store the old last-entry at its new position. ++ ++ memcpy(&queue->entries[i], entry, sizeof(opl_queue_entry_t)); ++ ++ return 1; ++} ++ ++uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue) ++{ ++ if (queue->num_entries > 0) ++ { ++ return queue->entries[0].time; ++ } ++ else ++ { ++ return 0; ++ } ++} ++ ++void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue, ++ uint64_t time, float factor) ++{ ++ int64_t offset; ++ int i; ++ ++ for (i = 0; i < queue->num_entries; ++i) ++ { ++ offset = queue->entries[i].time - time; ++ queue->entries[i].time = time + (uint64_t) (offset / factor); ++ } ++} ++ ++#ifdef TEST ++ ++#include ++ ++static void PrintQueueNode(opl_callback_queue_t *queue, int node, int depth) ++{ ++ int i; ++ ++ if (node >= queue->num_entries) ++ { ++ return; ++ } ++ ++ for (i=0; ientries[node].time); ++ ++ PrintQueueNode(queue, node * 2 + 1, depth + 1); ++ PrintQueueNode(queue, node * 2 + 2, depth + 1); ++} ++ ++static void PrintQueue(opl_callback_queue_t *queue) ++{ ++ PrintQueueNode(queue, 0, 0); ++} ++ ++int main() ++{ ++ opl_callback_queue_t *queue; ++ int iteration; ++ ++ queue = OPL_Queue_Create(); ++ ++ for (iteration=0; iteration<5000; ++iteration) ++ { ++ opl_callback_t callback; ++ void *data; ++ unsigned int time; ++ unsigned int newtime; ++ int i; ++ ++ for (i=0; i= time); ++ time = newtime; ++ } ++ ++ assert(OPL_Queue_IsEmpty(queue)); ++ assert(!OPL_Queue_Pop(queue, &callback, &data)); ++ } ++} ++ ++#endif ++ +--- /dev/null ++++ b/src/opl/opl_queue.h +@@ -0,0 +1,39 @@ ++// ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// OPL callback queue. ++// ++ ++#ifndef OPL_QUEUE_H ++#define OPL_QUEUE_H ++ ++#include "opl.h" ++ ++typedef struct opl_callback_queue_s opl_callback_queue_t; ++ ++opl_callback_queue_t *OPL_Queue_Create(void); ++int OPL_Queue_IsEmpty(opl_callback_queue_t *queue); ++void OPL_Queue_Clear(opl_callback_queue_t *queue); ++void OPL_Queue_Destroy(opl_callback_queue_t *queue); ++void OPL_Queue_Push(opl_callback_queue_t *queue, ++ opl_callback_t callback, void *data, ++ uint64_t time); ++int OPL_Queue_Pop(opl_callback_queue_t *queue, ++ opl_callback_t *callback, void **data); ++uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue); ++void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue, ++ uint64_t time, float factor); ++ ++#endif /* #ifndef OPL_QUEUE_H */ ++ +--- /dev/null ++++ b/src/opl/opl_sdl.c +@@ -0,0 +1,565 @@ ++// ++// Copyright(C) 2005-2014 Simon Howard ++// ++// This program is free software; you can redistribute it and/or ++// modify it under the terms of the GNU General Public License ++// as published by the Free Software Foundation; either version 2 ++// of the License, or (at your option) any later version. ++// ++// This program is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// DESCRIPTION: ++// OPL SDL interface. ++// ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "SDL3/SDL.h" ++#include "SDL3_mixer/SDL_mixer.h" ++ ++#include "opl3.h" ++ ++#include "opl.h" ++#include "opl_internal.h" ++ ++#include "opl_queue.h" ++ ++#include "i_sdlmixer.h" ++ ++typedef struct ++{ ++ unsigned int rate; // Number of times the timer is advanced per sec. ++ unsigned int enabled; // Non-zero if timer is enabled. ++ unsigned int value; // Last value that was set. ++ uint64_t expire_time; // Calculated time that timer will expire. ++} opl_timer_t; ++ ++// When the callback mutex is locked using OPL_Lock, callback functions ++// are not invoked. ++ ++static SDL_Mutex *callback_mutex = NULL; ++ ++// Queue of callbacks waiting to be invoked. ++ ++static opl_callback_queue_t *callback_queue; ++ ++// Mutex used to control access to the callback queue. ++ ++static SDL_Mutex *callback_queue_mutex = NULL; ++ ++// Mutex serialising access to the OPL emulator chip. Register writes ++// arrive from the control thread (during initialisation) and from OPL ++// callbacks invoked on the mixer thread, while PCM generation runs on ++// the mixer thread; the emulator state must not be read and written at ++// the same time. ++ ++static SDL_Mutex *chip_mutex = NULL; ++ ++// Current time, in us since startup: ++ ++static uint64_t current_time; ++ ++// If non-zero, playback is currently paused. ++ ++static int opl_sdl_paused; ++ ++// Time offset (in us) due to the fact that callbacks ++// were previously paused. ++ ++static uint64_t pause_offset; ++ ++// OPL software emulator structure. ++ ++static opl3_chip opl_chip; ++static int opl_opl3mode; ++ ++// Register number that was written. ++ ++static int register_num = 0; ++ ++// Timers; DBOPL does not do timer stuff itself. ++ ++static opl_timer_t timer1 = { 12500, 0, 0, 0 }; ++static opl_timer_t timer2 = { 3125, 0, 0, 0 }; ++ ++// SDL3_mixer state. The OPL emulator generates 16-bit signed stereo PCM at ++// opl_sample_rate; the samples are pushed into an SDL_AudioStream which is ++// assigned to a track on the shared mixer. SDL3_mixer then resamples and ++// mixes the music alongside the sound effects on a single device. ++ ++static MIX_Mixer *opl_mixer = NULL; ++static MIX_Track *opl_track = NULL; ++static SDL_AudioStream *opl_stream = NULL; ++static int mixing_freq; ++ ++// SDL3_mixer pulls from a track's audio stream greedily and can request ++// far more than the device actually consumes per real second (it buffers ++// ahead). The OPL music clock must advance at the real playback rate, or ++// the sequencer races ahead and the music plays too fast. To pace it, the ++// number of sample frames the device has actually output is counted by a ++// post-mix callback, and the stream callback only generates and advances ++// the emulator up to that point (plus a small look-ahead buffer). ++ ++static uint64_t frames_consumed; ++static uint64_t frames_generated; ++ ++#define OPL_LOOKAHEAD_FRAMES 2048 ++ ++// Advance time by the specified number of samples, invoking any ++// callback functions as appropriate. ++ ++static void AdvanceTime(unsigned int nsamples) ++{ ++ opl_callback_t callback; ++ void *callback_data; ++ uint64_t us; ++ ++ SDL_LockMutex(callback_queue_mutex); ++ ++ // Advance time. ++ ++ us = ((uint64_t) nsamples * OPL_SECOND) / mixing_freq; ++ current_time += us; ++ ++ if (opl_sdl_paused) ++ { ++ pause_offset += us; ++ } ++ ++ // Are there callbacks to invoke now? Keep invoking them ++ // until there are no more left. ++ ++ while (!OPL_Queue_IsEmpty(callback_queue) ++ && current_time >= OPL_Queue_Peek(callback_queue) + pause_offset) ++ { ++ // Pop the callback from the queue to invoke it. ++ ++ if (!OPL_Queue_Pop(callback_queue, &callback, &callback_data)) ++ { ++ break; ++ } ++ ++ // The mutex stuff here is a bit complicated. We must ++ // hold callback_mutex when we invoke the callback (so that ++ // the control thread can use OPL_Lock() to prevent callbacks ++ // from being invoked), but we must not be holding ++ // callback_queue_mutex, as the callback must be able to ++ // call OPL_SetCallback to schedule new callbacks. ++ ++ SDL_UnlockMutex(callback_queue_mutex); ++ ++ SDL_LockMutex(callback_mutex); ++ callback(callback_data); ++ SDL_UnlockMutex(callback_mutex); ++ ++ SDL_LockMutex(callback_queue_mutex); ++ } ++ ++ SDL_UnlockMutex(callback_queue_mutex); ++} ++ ++// Generate the specified number of samples of OPL output into the buffer. ++ ++static void FillBuffer(int16_t *buffer, unsigned int nsamples) ++{ ++ SDL_LockMutex(chip_mutex); ++ OPL3_GenerateStream(&opl_chip, buffer, nsamples); ++ SDL_UnlockMutex(chip_mutex); ++} ++ ++// Audio stream callback, invoked by the mixer when it needs more music. ++// additional_amount is the number of bytes (in the stream's input format, ++// 16-bit stereo) that the mixer is asking for. ++ ++static void OPL_Stream_Callback(void *udata, SDL_AudioStream *stream, ++ int additional_amount, int total_amount) ++{ ++ // 4 bytes per stereo sample (16 bits * 2 channels). ++ unsigned int buffer_samples = additional_amount / 4; ++ int16_t local[512 * 2]; ++ ++ // Only run the emulator up to the point the device has actually ++ // played, plus a small look-ahead. Any requested frames beyond that ++ // are filled with silence; the mixer is buffering ahead and will come ++ // back for the real audio once the device has consumed more. ++ ++ uint64_t budget = frames_consumed + OPL_LOOKAHEAD_FRAMES; ++ ++ while (buffer_samples > 0) ++ { ++ uint64_t next_callback_time; ++ uint64_t nsamples; ++ ++ if (frames_generated >= budget) ++ { ++ // No budget left: pad the rest of the request with silence. ++ unsigned int n = buffer_samples; ++ ++ if (n > 512) ++ { ++ n = 512; ++ } ++ ++ memset(local, 0, n * 4); ++ SDL_PutAudioStreamData(stream, local, (int) (n * 4)); ++ buffer_samples -= n; ++ continue; ++ } ++ ++ SDL_LockMutex(callback_queue_mutex); ++ ++ // Work out the time until the next callback waiting in ++ // the callback queue must be invoked. We can then fill the ++ // buffer with this many samples. ++ ++ if (opl_sdl_paused || OPL_Queue_IsEmpty(callback_queue)) ++ { ++ nsamples = buffer_samples; ++ } ++ else ++ { ++ next_callback_time = OPL_Queue_Peek(callback_queue) + pause_offset; ++ ++ nsamples = (next_callback_time - current_time) * mixing_freq; ++ nsamples = (nsamples + OPL_SECOND - 1) / OPL_SECOND; ++ ++ if (nsamples > buffer_samples) ++ { ++ nsamples = buffer_samples; ++ } ++ } ++ ++ SDL_UnlockMutex(callback_queue_mutex); ++ ++ // Never generate more than the local buffer can hold or more ++ // than the realtime budget allows. ++ ++ if (nsamples > 512) ++ { ++ nsamples = 512; ++ } ++ ++ if (frames_generated + nsamples > budget) ++ { ++ nsamples = budget - frames_generated; ++ } ++ ++ if (nsamples > 0) ++ { ++ FillBuffer(local, nsamples); ++ SDL_PutAudioStreamData(stream, local, ++ (int) (nsamples * 4)); ++ ++ buffer_samples -= nsamples; ++ frames_generated += nsamples; ++ } ++ ++ // Invoke callbacks for this point in time. ++ ++ AdvanceTime(nsamples); ++ } ++} ++ ++// Post-mix callback: counts the sample frames actually sent to the audio ++// device, which is used to pace the OPL emulator at the real playback rate. ++ ++static void OPL_PostMix_Callback(void *udata, MIX_Mixer *mixer, ++ const SDL_AudioSpec *spec, float *pcm, ++ int samples) ++{ ++ frames_consumed += (unsigned int) samples / spec->channels; ++} ++ ++static void OPL_SDL_Shutdown(void) ++{ ++ if (opl_mixer != NULL) ++ { ++ MIX_SetPostMixCallback(opl_mixer, NULL, NULL); ++ } ++ ++ if (opl_track != NULL) ++ { ++ MIX_StopTrack(opl_track, 0); ++ MIX_SetTrackAudioStream(opl_track, NULL); ++ MIX_DestroyTrack(opl_track); ++ opl_track = NULL; ++ } ++ ++ if (opl_stream != NULL) ++ { ++ SDL_DestroyAudioStream(opl_stream); ++ opl_stream = NULL; ++ } ++ ++ if (opl_mixer != NULL) ++ { ++ opl_mixer = NULL; ++ I_SDLMixer_Release(); ++ } ++ ++ if (callback_queue != NULL) ++ { ++ OPL_Queue_Destroy(callback_queue); ++ callback_queue = NULL; ++ } ++ ++ if (callback_mutex != NULL) ++ { ++ SDL_DestroyMutex(callback_mutex); ++ callback_mutex = NULL; ++ } ++ ++ if (callback_queue_mutex != NULL) ++ { ++ SDL_DestroyMutex(callback_queue_mutex); ++ callback_queue_mutex = NULL; ++ } ++ ++ if (chip_mutex != NULL) ++ { ++ SDL_DestroyMutex(chip_mutex); ++ chip_mutex = NULL; ++ } ++} ++ ++static int OPL_SDL_Init(unsigned int port_base) ++{ ++ SDL_AudioSpec spec; ++ ++ // Acquire the shared SDL3_mixer device, so that music and sound ++ // effects play through a single device. ++ ++ opl_mixer = I_SDLMixer_Acquire(); ++ ++ if (opl_mixer == NULL) ++ { ++ return 0; ++ } ++ ++ opl_sdl_paused = 0; ++ pause_offset = 0; ++ frames_consumed = 0; ++ frames_generated = 0; ++ ++ // Count the frames the device actually plays, to pace the emulator. ++ ++ MIX_SetPostMixCallback(opl_mixer, OPL_PostMix_Callback, NULL); ++ ++ // Queue structure of callbacks to invoke. ++ ++ callback_queue = OPL_Queue_Create(); ++ current_time = 0; ++ ++ // The OPL emulator runs at its configured sample rate; SDL3_mixer ++ // resamples to the device rate as needed. ++ ++ mixing_freq = opl_sample_rate; ++ ++ // Create the emulator structure and the mutexes before the mixer can ++ // ever invoke the stream callback, so the chip is fully reset and the ++ // locks are valid by the time PCM generation starts. ++ ++ OPL3_Reset(&opl_chip, mixing_freq); ++ opl_opl3mode = 0; ++ ++ callback_mutex = SDL_CreateMutex(); ++ callback_queue_mutex = SDL_CreateMutex(); ++ chip_mutex = SDL_CreateMutex(); ++ ++ spec.format = SDL_AUDIO_S16; ++ spec.channels = 2; ++ spec.freq = mixing_freq; ++ ++ opl_stream = SDL_CreateAudioStream(&spec, NULL); ++ ++ if (opl_stream == NULL) ++ { ++ fprintf(stderr, "OPL_SDL_Init: failed to create audio stream: %s\n", ++ SDL_GetError()); ++ OPL_SDL_Shutdown(); ++ return 0; ++ } ++ ++ SDL_SetAudioStreamGetCallback(opl_stream, OPL_Stream_Callback, NULL); ++ ++ // Assign the stream to a track on the shared mixer and start it ++ // playing. The callback always supplies data so the stream never ++ // runs dry. ++ ++ opl_track = MIX_CreateTrack(opl_mixer); ++ ++ if (opl_track == NULL || !MIX_SetTrackAudioStream(opl_track, opl_stream)) ++ { ++ fprintf(stderr, "OPL_SDL_Init: failed to set up mixer track: %s\n", ++ SDL_GetError()); ++ OPL_SDL_Shutdown(); ++ return 0; ++ } ++ ++ MIX_PlayTrack(opl_track, 0); ++ ++ return 1; ++} ++ ++static unsigned int OPL_SDL_PortRead(opl_port_t port) ++{ ++ unsigned int result = 0; ++ ++ if (port == OPL_REGISTER_PORT_OPL3) ++ { ++ return 0xff; ++ } ++ ++ if (timer1.enabled && current_time > timer1.expire_time) ++ { ++ result |= 0x80; // Either have expired ++ result |= 0x40; // Timer 1 has expired ++ } ++ ++ if (timer2.enabled && current_time > timer2.expire_time) ++ { ++ result |= 0x80; // Either have expired ++ result |= 0x20; // Timer 2 has expired ++ } ++ ++ return result; ++} ++ ++static void OPLTimer_CalculateEndTime(opl_timer_t *timer) ++{ ++ int tics; ++ ++ // If the timer is enabled, calculate the time when the timer ++ // will expire. ++ ++ if (timer->enabled) ++ { ++ tics = 0x100 - timer->value; ++ timer->expire_time = current_time ++ + ((uint64_t) tics * OPL_SECOND) / timer->rate; ++ } ++} ++ ++static void WriteRegister(unsigned int reg_num, unsigned int value) ++{ ++ switch (reg_num) ++ { ++ case OPL_REG_TIMER1: ++ timer1.value = value; ++ OPLTimer_CalculateEndTime(&timer1); ++ break; ++ ++ case OPL_REG_TIMER2: ++ timer2.value = value; ++ OPLTimer_CalculateEndTime(&timer2); ++ break; ++ ++ case OPL_REG_TIMER_CTRL: ++ if (value & 0x80) ++ { ++ timer1.enabled = 0; ++ timer2.enabled = 0; ++ } ++ else ++ { ++ if ((value & 0x40) == 0) ++ { ++ timer1.enabled = (value & 0x01) != 0; ++ OPLTimer_CalculateEndTime(&timer1); ++ } ++ ++ if ((value & 0x20) == 0) ++ { ++ timer2.enabled = (value & 0x02) != 0; ++ OPLTimer_CalculateEndTime(&timer2); ++ } ++ } ++ ++ break; ++ ++ case OPL_REG_NEW: ++ opl_opl3mode = value & 0x01; ++ ++ default: ++ SDL_LockMutex(chip_mutex); ++ OPL3_WriteRegBuffered(&opl_chip, reg_num, value); ++ SDL_UnlockMutex(chip_mutex); ++ break; ++ } ++} ++ ++static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value) ++{ ++ if (port == OPL_REGISTER_PORT) ++ { ++ register_num = value; ++ } ++ else if (port == OPL_REGISTER_PORT_OPL3) ++ { ++ register_num = value | 0x100; ++ } ++ else if (port == OPL_DATA_PORT) ++ { ++ WriteRegister(register_num, value); ++ } ++} ++ ++static void OPL_SDL_SetCallback(uint64_t us, opl_callback_t callback, ++ void *data) ++{ ++ SDL_LockMutex(callback_queue_mutex); ++ OPL_Queue_Push(callback_queue, callback, data, ++ current_time - pause_offset + us); ++ SDL_UnlockMutex(callback_queue_mutex); ++} ++ ++static void OPL_SDL_ClearCallbacks(void) ++{ ++ SDL_LockMutex(callback_queue_mutex); ++ OPL_Queue_Clear(callback_queue); ++ SDL_UnlockMutex(callback_queue_mutex); ++} ++ ++static void OPL_SDL_Lock(void) ++{ ++ SDL_LockMutex(callback_mutex); ++} ++ ++static void OPL_SDL_Unlock(void) ++{ ++ SDL_UnlockMutex(callback_mutex); ++} ++ ++static void OPL_SDL_SetPaused(int paused) ++{ ++ opl_sdl_paused = paused; ++} ++ ++static void OPL_SDL_AdjustCallbacks(float factor) ++{ ++ SDL_LockMutex(callback_queue_mutex); ++ OPL_Queue_AdjustCallbacks(callback_queue, current_time, factor); ++ SDL_UnlockMutex(callback_queue_mutex); ++} ++ ++opl_driver_t opl_sdl_driver = ++{ ++ "SDL", ++ OPL_SDL_Init, ++ OPL_SDL_Shutdown, ++ OPL_SDL_PortRead, ++ OPL_SDL_PortWrite, ++ OPL_SDL_SetCallback, ++ OPL_SDL_ClearCallbacks, ++ OPL_SDL_Lock, ++ OPL_SDL_Unlock, ++ OPL_SDL_SetPaused, ++ OPL_SDL_AdjustCallbacks, ++}; +--- /dev/null ++++ b/src/opl/wf_rom.h +@@ -0,0 +1,1062 @@ ++/* Nuked-OPL3-fast: logsin waveform lookup table. ++ * ++ * Copyright (C) 2013-2020 Nuke.YKT (source logsin data and waveform helpers) ++ * Copyright (C) 2026 Tony Gies (8x1024 lookup table layout) ++ * ++ * This file is part of Nuked-OPL3-fast and is licensed under the GNU Lesser ++ * General Public License version 2.1 or any later version. See LICENSE or ++ * the header of opl3.c for full terms. ++ * ++ * Generated by gen_logsin.py. Do not edit by hand; regenerate via: ++ * python3 gen_logsin.py > wf_rom.h ++ */ ++ ++#ifndef OPL_WF_ROM_H ++#define OPL_WF_ROM_H ++ ++#include ++ ++static const uint16_t logsin_wf[8][1024] = { ++ { ++ 0x0859, 0x06c3, 0x0607, 0x058b, 0x052e, 0x04e4, 0x04a6, 0x0471, ++ 0x0443, 0x041a, 0x03f5, 0x03d3, 0x03b5, 0x0398, 0x037e, 0x0365, ++ 0x034e, 0x0339, 0x0324, 0x0311, 0x02ff, 0x02ed, 0x02dc, 0x02cd, ++ 0x02bd, 0x02af, 0x02a0, 0x0293, 0x0286, 0x0279, 0x026d, 0x0261, ++ 0x0256, 0x024b, 0x0240, 0x0236, 0x022c, 0x0222, 0x0218, 0x020f, ++ 0x0206, 0x01fd, 0x01f5, 0x01ec, 0x01e4, 0x01dc, 0x01d4, 0x01cd, ++ 0x01c5, 0x01be, 0x01b7, 0x01b0, 0x01a9, 0x01a2, 0x019b, 0x0195, ++ 0x018f, 0x0188, 0x0182, 0x017c, 0x0177, 0x0171, 0x016b, 0x0166, ++ 0x0160, 0x015b, 0x0155, 0x0150, 0x014b, 0x0146, 0x0141, 0x013c, ++ 0x0137, 0x0133, 0x012e, 0x0129, 0x0125, 0x0121, 0x011c, 0x0118, ++ 0x0114, 0x010f, 0x010b, 0x0107, 0x0103, 0x00ff, 0x00fb, 0x00f8, ++ 0x00f4, 0x00f0, 0x00ec, 0x00e9, 0x00e5, 0x00e2, 0x00de, 0x00db, ++ 0x00d7, 0x00d4, 0x00d1, 0x00cd, 0x00ca, 0x00c7, 0x00c4, 0x00c1, ++ 0x00be, 0x00bb, 0x00b8, 0x00b5, 0x00b2, 0x00af, 0x00ac, 0x00a9, ++ 0x00a7, 0x00a4, 0x00a1, 0x009f, 0x009c, 0x0099, 0x0097, 0x0094, ++ 0x0092, 0x008f, 0x008d, 0x008a, 0x0088, 0x0086, 0x0083, 0x0081, ++ 0x007f, 0x007d, 0x007a, 0x0078, 0x0076, 0x0074, 0x0072, 0x0070, ++ 0x006e, 0x006c, 0x006a, 0x0068, 0x0066, 0x0064, 0x0062, 0x0060, ++ 0x005e, 0x005c, 0x005b, 0x0059, 0x0057, 0x0055, 0x0053, 0x0052, ++ 0x0050, 0x004e, 0x004d, 0x004b, 0x004a, 0x0048, 0x0046, 0x0045, ++ 0x0043, 0x0042, 0x0040, 0x003f, 0x003e, 0x003c, 0x003b, 0x0039, ++ 0x0038, 0x0037, 0x0035, 0x0034, 0x0033, 0x0031, 0x0030, 0x002f, ++ 0x002e, 0x002d, 0x002b, 0x002a, 0x0029, 0x0028, 0x0027, 0x0026, ++ 0x0025, 0x0024, 0x0023, 0x0022, 0x0021, 0x0020, 0x001f, 0x001e, ++ 0x001d, 0x001c, 0x001b, 0x001a, 0x0019, 0x0018, 0x0017, 0x0017, ++ 0x0016, 0x0015, 0x0014, 0x0014, 0x0013, 0x0012, 0x0011, 0x0011, ++ 0x0010, 0x000f, 0x000f, 0x000e, 0x000d, 0x000d, 0x000c, 0x000c, ++ 0x000b, 0x000a, 0x000a, 0x0009, 0x0009, 0x0008, 0x0008, 0x0007, ++ 0x0007, 0x0007, 0x0006, 0x0006, 0x0005, 0x0005, 0x0005, 0x0004, ++ 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x0002, 0x0002, 0x0002, ++ 0x0002, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0002, ++ 0x0002, 0x0002, 0x0002, 0x0003, 0x0003, 0x0003, 0x0004, 0x0004, ++ 0x0004, 0x0005, 0x0005, 0x0005, 0x0006, 0x0006, 0x0007, 0x0007, ++ 0x0007, 0x0008, 0x0008, 0x0009, 0x0009, 0x000a, 0x000a, 0x000b, ++ 0x000c, 0x000c, 0x000d, 0x000d, 0x000e, 0x000f, 0x000f, 0x0010, ++ 0x0011, 0x0011, 0x0012, 0x0013, 0x0014, 0x0014, 0x0015, 0x0016, ++ 0x0017, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, ++ 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, ++ 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002d, 0x002e, ++ 0x002f, 0x0030, 0x0031, 0x0033, 0x0034, 0x0035, 0x0037, 0x0038, ++ 0x0039, 0x003b, 0x003c, 0x003e, 0x003f, 0x0040, 0x0042, 0x0043, ++ 0x0045, 0x0046, 0x0048, 0x004a, 0x004b, 0x004d, 0x004e, 0x0050, ++ 0x0052, 0x0053, 0x0055, 0x0057, 0x0059, 0x005b, 0x005c, 0x005e, ++ 0x0060, 0x0062, 0x0064, 0x0066, 0x0068, 0x006a, 0x006c, 0x006e, ++ 0x0070, 0x0072, 0x0074, 0x0076, 0x0078, 0x007a, 0x007d, 0x007f, ++ 0x0081, 0x0083, 0x0086, 0x0088, 0x008a, 0x008d, 0x008f, 0x0092, ++ 0x0094, 0x0097, 0x0099, 0x009c, 0x009f, 0x00a1, 0x00a4, 0x00a7, ++ 0x00a9, 0x00ac, 0x00af, 0x00b2, 0x00b5, 0x00b8, 0x00bb, 0x00be, ++ 0x00c1, 0x00c4, 0x00c7, 0x00ca, 0x00cd, 0x00d1, 0x00d4, 0x00d7, ++ 0x00db, 0x00de, 0x00e2, 0x00e5, 0x00e9, 0x00ec, 0x00f0, 0x00f4, ++ 0x00f8, 0x00fb, 0x00ff, 0x0103, 0x0107, 0x010b, 0x010f, 0x0114, ++ 0x0118, 0x011c, 0x0121, 0x0125, 0x0129, 0x012e, 0x0133, 0x0137, ++ 0x013c, 0x0141, 0x0146, 0x014b, 0x0150, 0x0155, 0x015b, 0x0160, ++ 0x0166, 0x016b, 0x0171, 0x0177, 0x017c, 0x0182, 0x0188, 0x018f, ++ 0x0195, 0x019b, 0x01a2, 0x01a9, 0x01b0, 0x01b7, 0x01be, 0x01c5, ++ 0x01cd, 0x01d4, 0x01dc, 0x01e4, 0x01ec, 0x01f5, 0x01fd, 0x0206, ++ 0x020f, 0x0218, 0x0222, 0x022c, 0x0236, 0x0240, 0x024b, 0x0256, ++ 0x0261, 0x026d, 0x0279, 0x0286, 0x0293, 0x02a0, 0x02af, 0x02bd, ++ 0x02cd, 0x02dc, 0x02ed, 0x02ff, 0x0311, 0x0324, 0x0339, 0x034e, ++ 0x0365, 0x037e, 0x0398, 0x03b5, 0x03d3, 0x03f5, 0x041a, 0x0443, ++ 0x0471, 0x04a6, 0x04e4, 0x052e, 0x058b, 0x0607, 0x06c3, 0x0859, ++ 0x8859, 0x86c3, 0x8607, 0x858b, 0x852e, 0x84e4, 0x84a6, 0x8471, ++ 0x8443, 0x841a, 0x83f5, 0x83d3, 0x83b5, 0x8398, 0x837e, 0x8365, ++ 0x834e, 0x8339, 0x8324, 0x8311, 0x82ff, 0x82ed, 0x82dc, 0x82cd, ++ 0x82bd, 0x82af, 0x82a0, 0x8293, 0x8286, 0x8279, 0x826d, 0x8261, ++ 0x8256, 0x824b, 0x8240, 0x8236, 0x822c, 0x8222, 0x8218, 0x820f, ++ 0x8206, 0x81fd, 0x81f5, 0x81ec, 0x81e4, 0x81dc, 0x81d4, 0x81cd, ++ 0x81c5, 0x81be, 0x81b7, 0x81b0, 0x81a9, 0x81a2, 0x819b, 0x8195, ++ 0x818f, 0x8188, 0x8182, 0x817c, 0x8177, 0x8171, 0x816b, 0x8166, ++ 0x8160, 0x815b, 0x8155, 0x8150, 0x814b, 0x8146, 0x8141, 0x813c, ++ 0x8137, 0x8133, 0x812e, 0x8129, 0x8125, 0x8121, 0x811c, 0x8118, ++ 0x8114, 0x810f, 0x810b, 0x8107, 0x8103, 0x80ff, 0x80fb, 0x80f8, ++ 0x80f4, 0x80f0, 0x80ec, 0x80e9, 0x80e5, 0x80e2, 0x80de, 0x80db, ++ 0x80d7, 0x80d4, 0x80d1, 0x80cd, 0x80ca, 0x80c7, 0x80c4, 0x80c1, ++ 0x80be, 0x80bb, 0x80b8, 0x80b5, 0x80b2, 0x80af, 0x80ac, 0x80a9, ++ 0x80a7, 0x80a4, 0x80a1, 0x809f, 0x809c, 0x8099, 0x8097, 0x8094, ++ 0x8092, 0x808f, 0x808d, 0x808a, 0x8088, 0x8086, 0x8083, 0x8081, ++ 0x807f, 0x807d, 0x807a, 0x8078, 0x8076, 0x8074, 0x8072, 0x8070, ++ 0x806e, 0x806c, 0x806a, 0x8068, 0x8066, 0x8064, 0x8062, 0x8060, ++ 0x805e, 0x805c, 0x805b, 0x8059, 0x8057, 0x8055, 0x8053, 0x8052, ++ 0x8050, 0x804e, 0x804d, 0x804b, 0x804a, 0x8048, 0x8046, 0x8045, ++ 0x8043, 0x8042, 0x8040, 0x803f, 0x803e, 0x803c, 0x803b, 0x8039, ++ 0x8038, 0x8037, 0x8035, 0x8034, 0x8033, 0x8031, 0x8030, 0x802f, ++ 0x802e, 0x802d, 0x802b, 0x802a, 0x8029, 0x8028, 0x8027, 0x8026, ++ 0x8025, 0x8024, 0x8023, 0x8022, 0x8021, 0x8020, 0x801f, 0x801e, ++ 0x801d, 0x801c, 0x801b, 0x801a, 0x8019, 0x8018, 0x8017, 0x8017, ++ 0x8016, 0x8015, 0x8014, 0x8014, 0x8013, 0x8012, 0x8011, 0x8011, ++ 0x8010, 0x800f, 0x800f, 0x800e, 0x800d, 0x800d, 0x800c, 0x800c, ++ 0x800b, 0x800a, 0x800a, 0x8009, 0x8009, 0x8008, 0x8008, 0x8007, ++ 0x8007, 0x8007, 0x8006, 0x8006, 0x8005, 0x8005, 0x8005, 0x8004, ++ 0x8004, 0x8004, 0x8003, 0x8003, 0x8003, 0x8002, 0x8002, 0x8002, ++ 0x8002, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8002, ++ 0x8002, 0x8002, 0x8002, 0x8003, 0x8003, 0x8003, 0x8004, 0x8004, ++ 0x8004, 0x8005, 0x8005, 0x8005, 0x8006, 0x8006, 0x8007, 0x8007, ++ 0x8007, 0x8008, 0x8008, 0x8009, 0x8009, 0x800a, 0x800a, 0x800b, ++ 0x800c, 0x800c, 0x800d, 0x800d, 0x800e, 0x800f, 0x800f, 0x8010, ++ 0x8011, 0x8011, 0x8012, 0x8013, 0x8014, 0x8014, 0x8015, 0x8016, ++ 0x8017, 0x8017, 0x8018, 0x8019, 0x801a, 0x801b, 0x801c, 0x801d, ++ 0x801e, 0x801f, 0x8020, 0x8021, 0x8022, 0x8023, 0x8024, 0x8025, ++ 0x8026, 0x8027, 0x8028, 0x8029, 0x802a, 0x802b, 0x802d, 0x802e, ++ 0x802f, 0x8030, 0x8031, 0x8033, 0x8034, 0x8035, 0x8037, 0x8038, ++ 0x8039, 0x803b, 0x803c, 0x803e, 0x803f, 0x8040, 0x8042, 0x8043, ++ 0x8045, 0x8046, 0x8048, 0x804a, 0x804b, 0x804d, 0x804e, 0x8050, ++ 0x8052, 0x8053, 0x8055, 0x8057, 0x8059, 0x805b, 0x805c, 0x805e, ++ 0x8060, 0x8062, 0x8064, 0x8066, 0x8068, 0x806a, 0x806c, 0x806e, ++ 0x8070, 0x8072, 0x8074, 0x8076, 0x8078, 0x807a, 0x807d, 0x807f, ++ 0x8081, 0x8083, 0x8086, 0x8088, 0x808a, 0x808d, 0x808f, 0x8092, ++ 0x8094, 0x8097, 0x8099, 0x809c, 0x809f, 0x80a1, 0x80a4, 0x80a7, ++ 0x80a9, 0x80ac, 0x80af, 0x80b2, 0x80b5, 0x80b8, 0x80bb, 0x80be, ++ 0x80c1, 0x80c4, 0x80c7, 0x80ca, 0x80cd, 0x80d1, 0x80d4, 0x80d7, ++ 0x80db, 0x80de, 0x80e2, 0x80e5, 0x80e9, 0x80ec, 0x80f0, 0x80f4, ++ 0x80f8, 0x80fb, 0x80ff, 0x8103, 0x8107, 0x810b, 0x810f, 0x8114, ++ 0x8118, 0x811c, 0x8121, 0x8125, 0x8129, 0x812e, 0x8133, 0x8137, ++ 0x813c, 0x8141, 0x8146, 0x814b, 0x8150, 0x8155, 0x815b, 0x8160, ++ 0x8166, 0x816b, 0x8171, 0x8177, 0x817c, 0x8182, 0x8188, 0x818f, ++ 0x8195, 0x819b, 0x81a2, 0x81a9, 0x81b0, 0x81b7, 0x81be, 0x81c5, ++ 0x81cd, 0x81d4, 0x81dc, 0x81e4, 0x81ec, 0x81f5, 0x81fd, 0x8206, ++ 0x820f, 0x8218, 0x8222, 0x822c, 0x8236, 0x8240, 0x824b, 0x8256, ++ 0x8261, 0x826d, 0x8279, 0x8286, 0x8293, 0x82a0, 0x82af, 0x82bd, ++ 0x82cd, 0x82dc, 0x82ed, 0x82ff, 0x8311, 0x8324, 0x8339, 0x834e, ++ 0x8365, 0x837e, 0x8398, 0x83b5, 0x83d3, 0x83f5, 0x841a, 0x8443, ++ 0x8471, 0x84a6, 0x84e4, 0x852e, 0x858b, 0x8607, 0x86c3, 0x8859 ++ }, ++ { ++ 0x0859, 0x06c3, 0x0607, 0x058b, 0x052e, 0x04e4, 0x04a6, 0x0471, ++ 0x0443, 0x041a, 0x03f5, 0x03d3, 0x03b5, 0x0398, 0x037e, 0x0365, ++ 0x034e, 0x0339, 0x0324, 0x0311, 0x02ff, 0x02ed, 0x02dc, 0x02cd, ++ 0x02bd, 0x02af, 0x02a0, 0x0293, 0x0286, 0x0279, 0x026d, 0x0261, ++ 0x0256, 0x024b, 0x0240, 0x0236, 0x022c, 0x0222, 0x0218, 0x020f, ++ 0x0206, 0x01fd, 0x01f5, 0x01ec, 0x01e4, 0x01dc, 0x01d4, 0x01cd, ++ 0x01c5, 0x01be, 0x01b7, 0x01b0, 0x01a9, 0x01a2, 0x019b, 0x0195, ++ 0x018f, 0x0188, 0x0182, 0x017c, 0x0177, 0x0171, 0x016b, 0x0166, ++ 0x0160, 0x015b, 0x0155, 0x0150, 0x014b, 0x0146, 0x0141, 0x013c, ++ 0x0137, 0x0133, 0x012e, 0x0129, 0x0125, 0x0121, 0x011c, 0x0118, ++ 0x0114, 0x010f, 0x010b, 0x0107, 0x0103, 0x00ff, 0x00fb, 0x00f8, ++ 0x00f4, 0x00f0, 0x00ec, 0x00e9, 0x00e5, 0x00e2, 0x00de, 0x00db, ++ 0x00d7, 0x00d4, 0x00d1, 0x00cd, 0x00ca, 0x00c7, 0x00c4, 0x00c1, ++ 0x00be, 0x00bb, 0x00b8, 0x00b5, 0x00b2, 0x00af, 0x00ac, 0x00a9, ++ 0x00a7, 0x00a4, 0x00a1, 0x009f, 0x009c, 0x0099, 0x0097, 0x0094, ++ 0x0092, 0x008f, 0x008d, 0x008a, 0x0088, 0x0086, 0x0083, 0x0081, ++ 0x007f, 0x007d, 0x007a, 0x0078, 0x0076, 0x0074, 0x0072, 0x0070, ++ 0x006e, 0x006c, 0x006a, 0x0068, 0x0066, 0x0064, 0x0062, 0x0060, ++ 0x005e, 0x005c, 0x005b, 0x0059, 0x0057, 0x0055, 0x0053, 0x0052, ++ 0x0050, 0x004e, 0x004d, 0x004b, 0x004a, 0x0048, 0x0046, 0x0045, ++ 0x0043, 0x0042, 0x0040, 0x003f, 0x003e, 0x003c, 0x003b, 0x0039, ++ 0x0038, 0x0037, 0x0035, 0x0034, 0x0033, 0x0031, 0x0030, 0x002f, ++ 0x002e, 0x002d, 0x002b, 0x002a, 0x0029, 0x0028, 0x0027, 0x0026, ++ 0x0025, 0x0024, 0x0023, 0x0022, 0x0021, 0x0020, 0x001f, 0x001e, ++ 0x001d, 0x001c, 0x001b, 0x001a, 0x0019, 0x0018, 0x0017, 0x0017, ++ 0x0016, 0x0015, 0x0014, 0x0014, 0x0013, 0x0012, 0x0011, 0x0011, ++ 0x0010, 0x000f, 0x000f, 0x000e, 0x000d, 0x000d, 0x000c, 0x000c, ++ 0x000b, 0x000a, 0x000a, 0x0009, 0x0009, 0x0008, 0x0008, 0x0007, ++ 0x0007, 0x0007, 0x0006, 0x0006, 0x0005, 0x0005, 0x0005, 0x0004, ++ 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x0002, 0x0002, 0x0002, ++ 0x0002, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0002, ++ 0x0002, 0x0002, 0x0002, 0x0003, 0x0003, 0x0003, 0x0004, 0x0004, ++ 0x0004, 0x0005, 0x0005, 0x0005, 0x0006, 0x0006, 0x0007, 0x0007, ++ 0x0007, 0x0008, 0x0008, 0x0009, 0x0009, 0x000a, 0x000a, 0x000b, ++ 0x000c, 0x000c, 0x000d, 0x000d, 0x000e, 0x000f, 0x000f, 0x0010, ++ 0x0011, 0x0011, 0x0012, 0x0013, 0x0014, 0x0014, 0x0015, 0x0016, ++ 0x0017, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, ++ 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, ++ 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002d, 0x002e, ++ 0x002f, 0x0030, 0x0031, 0x0033, 0x0034, 0x0035, 0x0037, 0x0038, ++ 0x0039, 0x003b, 0x003c, 0x003e, 0x003f, 0x0040, 0x0042, 0x0043, ++ 0x0045, 0x0046, 0x0048, 0x004a, 0x004b, 0x004d, 0x004e, 0x0050, ++ 0x0052, 0x0053, 0x0055, 0x0057, 0x0059, 0x005b, 0x005c, 0x005e, ++ 0x0060, 0x0062, 0x0064, 0x0066, 0x0068, 0x006a, 0x006c, 0x006e, ++ 0x0070, 0x0072, 0x0074, 0x0076, 0x0078, 0x007a, 0x007d, 0x007f, ++ 0x0081, 0x0083, 0x0086, 0x0088, 0x008a, 0x008d, 0x008f, 0x0092, ++ 0x0094, 0x0097, 0x0099, 0x009c, 0x009f, 0x00a1, 0x00a4, 0x00a7, ++ 0x00a9, 0x00ac, 0x00af, 0x00b2, 0x00b5, 0x00b8, 0x00bb, 0x00be, ++ 0x00c1, 0x00c4, 0x00c7, 0x00ca, 0x00cd, 0x00d1, 0x00d4, 0x00d7, ++ 0x00db, 0x00de, 0x00e2, 0x00e5, 0x00e9, 0x00ec, 0x00f0, 0x00f4, ++ 0x00f8, 0x00fb, 0x00ff, 0x0103, 0x0107, 0x010b, 0x010f, 0x0114, ++ 0x0118, 0x011c, 0x0121, 0x0125, 0x0129, 0x012e, 0x0133, 0x0137, ++ 0x013c, 0x0141, 0x0146, 0x014b, 0x0150, 0x0155, 0x015b, 0x0160, ++ 0x0166, 0x016b, 0x0171, 0x0177, 0x017c, 0x0182, 0x0188, 0x018f, ++ 0x0195, 0x019b, 0x01a2, 0x01a9, 0x01b0, 0x01b7, 0x01be, 0x01c5, ++ 0x01cd, 0x01d4, 0x01dc, 0x01e4, 0x01ec, 0x01f5, 0x01fd, 0x0206, ++ 0x020f, 0x0218, 0x0222, 0x022c, 0x0236, 0x0240, 0x024b, 0x0256, ++ 0x0261, 0x026d, 0x0279, 0x0286, 0x0293, 0x02a0, 0x02af, 0x02bd, ++ 0x02cd, 0x02dc, 0x02ed, 0x02ff, 0x0311, 0x0324, 0x0339, 0x034e, ++ 0x0365, 0x037e, 0x0398, 0x03b5, 0x03d3, 0x03f5, 0x041a, 0x0443, ++ 0x0471, 0x04a6, 0x04e4, 0x052e, 0x058b, 0x0607, 0x06c3, 0x0859, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000 ++ }, ++ { ++ 0x0859, 0x06c3, 0x0607, 0x058b, 0x052e, 0x04e4, 0x04a6, 0x0471, ++ 0x0443, 0x041a, 0x03f5, 0x03d3, 0x03b5, 0x0398, 0x037e, 0x0365, ++ 0x034e, 0x0339, 0x0324, 0x0311, 0x02ff, 0x02ed, 0x02dc, 0x02cd, ++ 0x02bd, 0x02af, 0x02a0, 0x0293, 0x0286, 0x0279, 0x026d, 0x0261, ++ 0x0256, 0x024b, 0x0240, 0x0236, 0x022c, 0x0222, 0x0218, 0x020f, ++ 0x0206, 0x01fd, 0x01f5, 0x01ec, 0x01e4, 0x01dc, 0x01d4, 0x01cd, ++ 0x01c5, 0x01be, 0x01b7, 0x01b0, 0x01a9, 0x01a2, 0x019b, 0x0195, ++ 0x018f, 0x0188, 0x0182, 0x017c, 0x0177, 0x0171, 0x016b, 0x0166, ++ 0x0160, 0x015b, 0x0155, 0x0150, 0x014b, 0x0146, 0x0141, 0x013c, ++ 0x0137, 0x0133, 0x012e, 0x0129, 0x0125, 0x0121, 0x011c, 0x0118, ++ 0x0114, 0x010f, 0x010b, 0x0107, 0x0103, 0x00ff, 0x00fb, 0x00f8, ++ 0x00f4, 0x00f0, 0x00ec, 0x00e9, 0x00e5, 0x00e2, 0x00de, 0x00db, ++ 0x00d7, 0x00d4, 0x00d1, 0x00cd, 0x00ca, 0x00c7, 0x00c4, 0x00c1, ++ 0x00be, 0x00bb, 0x00b8, 0x00b5, 0x00b2, 0x00af, 0x00ac, 0x00a9, ++ 0x00a7, 0x00a4, 0x00a1, 0x009f, 0x009c, 0x0099, 0x0097, 0x0094, ++ 0x0092, 0x008f, 0x008d, 0x008a, 0x0088, 0x0086, 0x0083, 0x0081, ++ 0x007f, 0x007d, 0x007a, 0x0078, 0x0076, 0x0074, 0x0072, 0x0070, ++ 0x006e, 0x006c, 0x006a, 0x0068, 0x0066, 0x0064, 0x0062, 0x0060, ++ 0x005e, 0x005c, 0x005b, 0x0059, 0x0057, 0x0055, 0x0053, 0x0052, ++ 0x0050, 0x004e, 0x004d, 0x004b, 0x004a, 0x0048, 0x0046, 0x0045, ++ 0x0043, 0x0042, 0x0040, 0x003f, 0x003e, 0x003c, 0x003b, 0x0039, ++ 0x0038, 0x0037, 0x0035, 0x0034, 0x0033, 0x0031, 0x0030, 0x002f, ++ 0x002e, 0x002d, 0x002b, 0x002a, 0x0029, 0x0028, 0x0027, 0x0026, ++ 0x0025, 0x0024, 0x0023, 0x0022, 0x0021, 0x0020, 0x001f, 0x001e, ++ 0x001d, 0x001c, 0x001b, 0x001a, 0x0019, 0x0018, 0x0017, 0x0017, ++ 0x0016, 0x0015, 0x0014, 0x0014, 0x0013, 0x0012, 0x0011, 0x0011, ++ 0x0010, 0x000f, 0x000f, 0x000e, 0x000d, 0x000d, 0x000c, 0x000c, ++ 0x000b, 0x000a, 0x000a, 0x0009, 0x0009, 0x0008, 0x0008, 0x0007, ++ 0x0007, 0x0007, 0x0006, 0x0006, 0x0005, 0x0005, 0x0005, 0x0004, ++ 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x0002, 0x0002, 0x0002, ++ 0x0002, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0002, ++ 0x0002, 0x0002, 0x0002, 0x0003, 0x0003, 0x0003, 0x0004, 0x0004, ++ 0x0004, 0x0005, 0x0005, 0x0005, 0x0006, 0x0006, 0x0007, 0x0007, ++ 0x0007, 0x0008, 0x0008, 0x0009, 0x0009, 0x000a, 0x000a, 0x000b, ++ 0x000c, 0x000c, 0x000d, 0x000d, 0x000e, 0x000f, 0x000f, 0x0010, ++ 0x0011, 0x0011, 0x0012, 0x0013, 0x0014, 0x0014, 0x0015, 0x0016, ++ 0x0017, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, ++ 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, ++ 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002d, 0x002e, ++ 0x002f, 0x0030, 0x0031, 0x0033, 0x0034, 0x0035, 0x0037, 0x0038, ++ 0x0039, 0x003b, 0x003c, 0x003e, 0x003f, 0x0040, 0x0042, 0x0043, ++ 0x0045, 0x0046, 0x0048, 0x004a, 0x004b, 0x004d, 0x004e, 0x0050, ++ 0x0052, 0x0053, 0x0055, 0x0057, 0x0059, 0x005b, 0x005c, 0x005e, ++ 0x0060, 0x0062, 0x0064, 0x0066, 0x0068, 0x006a, 0x006c, 0x006e, ++ 0x0070, 0x0072, 0x0074, 0x0076, 0x0078, 0x007a, 0x007d, 0x007f, ++ 0x0081, 0x0083, 0x0086, 0x0088, 0x008a, 0x008d, 0x008f, 0x0092, ++ 0x0094, 0x0097, 0x0099, 0x009c, 0x009f, 0x00a1, 0x00a4, 0x00a7, ++ 0x00a9, 0x00ac, 0x00af, 0x00b2, 0x00b5, 0x00b8, 0x00bb, 0x00be, ++ 0x00c1, 0x00c4, 0x00c7, 0x00ca, 0x00cd, 0x00d1, 0x00d4, 0x00d7, ++ 0x00db, 0x00de, 0x00e2, 0x00e5, 0x00e9, 0x00ec, 0x00f0, 0x00f4, ++ 0x00f8, 0x00fb, 0x00ff, 0x0103, 0x0107, 0x010b, 0x010f, 0x0114, ++ 0x0118, 0x011c, 0x0121, 0x0125, 0x0129, 0x012e, 0x0133, 0x0137, ++ 0x013c, 0x0141, 0x0146, 0x014b, 0x0150, 0x0155, 0x015b, 0x0160, ++ 0x0166, 0x016b, 0x0171, 0x0177, 0x017c, 0x0182, 0x0188, 0x018f, ++ 0x0195, 0x019b, 0x01a2, 0x01a9, 0x01b0, 0x01b7, 0x01be, 0x01c5, ++ 0x01cd, 0x01d4, 0x01dc, 0x01e4, 0x01ec, 0x01f5, 0x01fd, 0x0206, ++ 0x020f, 0x0218, 0x0222, 0x022c, 0x0236, 0x0240, 0x024b, 0x0256, ++ 0x0261, 0x026d, 0x0279, 0x0286, 0x0293, 0x02a0, 0x02af, 0x02bd, ++ 0x02cd, 0x02dc, 0x02ed, 0x02ff, 0x0311, 0x0324, 0x0339, 0x034e, ++ 0x0365, 0x037e, 0x0398, 0x03b5, 0x03d3, 0x03f5, 0x041a, 0x0443, ++ 0x0471, 0x04a6, 0x04e4, 0x052e, 0x058b, 0x0607, 0x06c3, 0x0859, ++ 0x0859, 0x06c3, 0x0607, 0x058b, 0x052e, 0x04e4, 0x04a6, 0x0471, ++ 0x0443, 0x041a, 0x03f5, 0x03d3, 0x03b5, 0x0398, 0x037e, 0x0365, ++ 0x034e, 0x0339, 0x0324, 0x0311, 0x02ff, 0x02ed, 0x02dc, 0x02cd, ++ 0x02bd, 0x02af, 0x02a0, 0x0293, 0x0286, 0x0279, 0x026d, 0x0261, ++ 0x0256, 0x024b, 0x0240, 0x0236, 0x022c, 0x0222, 0x0218, 0x020f, ++ 0x0206, 0x01fd, 0x01f5, 0x01ec, 0x01e4, 0x01dc, 0x01d4, 0x01cd, ++ 0x01c5, 0x01be, 0x01b7, 0x01b0, 0x01a9, 0x01a2, 0x019b, 0x0195, ++ 0x018f, 0x0188, 0x0182, 0x017c, 0x0177, 0x0171, 0x016b, 0x0166, ++ 0x0160, 0x015b, 0x0155, 0x0150, 0x014b, 0x0146, 0x0141, 0x013c, ++ 0x0137, 0x0133, 0x012e, 0x0129, 0x0125, 0x0121, 0x011c, 0x0118, ++ 0x0114, 0x010f, 0x010b, 0x0107, 0x0103, 0x00ff, 0x00fb, 0x00f8, ++ 0x00f4, 0x00f0, 0x00ec, 0x00e9, 0x00e5, 0x00e2, 0x00de, 0x00db, ++ 0x00d7, 0x00d4, 0x00d1, 0x00cd, 0x00ca, 0x00c7, 0x00c4, 0x00c1, ++ 0x00be, 0x00bb, 0x00b8, 0x00b5, 0x00b2, 0x00af, 0x00ac, 0x00a9, ++ 0x00a7, 0x00a4, 0x00a1, 0x009f, 0x009c, 0x0099, 0x0097, 0x0094, ++ 0x0092, 0x008f, 0x008d, 0x008a, 0x0088, 0x0086, 0x0083, 0x0081, ++ 0x007f, 0x007d, 0x007a, 0x0078, 0x0076, 0x0074, 0x0072, 0x0070, ++ 0x006e, 0x006c, 0x006a, 0x0068, 0x0066, 0x0064, 0x0062, 0x0060, ++ 0x005e, 0x005c, 0x005b, 0x0059, 0x0057, 0x0055, 0x0053, 0x0052, ++ 0x0050, 0x004e, 0x004d, 0x004b, 0x004a, 0x0048, 0x0046, 0x0045, ++ 0x0043, 0x0042, 0x0040, 0x003f, 0x003e, 0x003c, 0x003b, 0x0039, ++ 0x0038, 0x0037, 0x0035, 0x0034, 0x0033, 0x0031, 0x0030, 0x002f, ++ 0x002e, 0x002d, 0x002b, 0x002a, 0x0029, 0x0028, 0x0027, 0x0026, ++ 0x0025, 0x0024, 0x0023, 0x0022, 0x0021, 0x0020, 0x001f, 0x001e, ++ 0x001d, 0x001c, 0x001b, 0x001a, 0x0019, 0x0018, 0x0017, 0x0017, ++ 0x0016, 0x0015, 0x0014, 0x0014, 0x0013, 0x0012, 0x0011, 0x0011, ++ 0x0010, 0x000f, 0x000f, 0x000e, 0x000d, 0x000d, 0x000c, 0x000c, ++ 0x000b, 0x000a, 0x000a, 0x0009, 0x0009, 0x0008, 0x0008, 0x0007, ++ 0x0007, 0x0007, 0x0006, 0x0006, 0x0005, 0x0005, 0x0005, 0x0004, ++ 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x0002, 0x0002, 0x0002, ++ 0x0002, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0002, ++ 0x0002, 0x0002, 0x0002, 0x0003, 0x0003, 0x0003, 0x0004, 0x0004, ++ 0x0004, 0x0005, 0x0005, 0x0005, 0x0006, 0x0006, 0x0007, 0x0007, ++ 0x0007, 0x0008, 0x0008, 0x0009, 0x0009, 0x000a, 0x000a, 0x000b, ++ 0x000c, 0x000c, 0x000d, 0x000d, 0x000e, 0x000f, 0x000f, 0x0010, ++ 0x0011, 0x0011, 0x0012, 0x0013, 0x0014, 0x0014, 0x0015, 0x0016, ++ 0x0017, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, ++ 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, ++ 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002d, 0x002e, ++ 0x002f, 0x0030, 0x0031, 0x0033, 0x0034, 0x0035, 0x0037, 0x0038, ++ 0x0039, 0x003b, 0x003c, 0x003e, 0x003f, 0x0040, 0x0042, 0x0043, ++ 0x0045, 0x0046, 0x0048, 0x004a, 0x004b, 0x004d, 0x004e, 0x0050, ++ 0x0052, 0x0053, 0x0055, 0x0057, 0x0059, 0x005b, 0x005c, 0x005e, ++ 0x0060, 0x0062, 0x0064, 0x0066, 0x0068, 0x006a, 0x006c, 0x006e, ++ 0x0070, 0x0072, 0x0074, 0x0076, 0x0078, 0x007a, 0x007d, 0x007f, ++ 0x0081, 0x0083, 0x0086, 0x0088, 0x008a, 0x008d, 0x008f, 0x0092, ++ 0x0094, 0x0097, 0x0099, 0x009c, 0x009f, 0x00a1, 0x00a4, 0x00a7, ++ 0x00a9, 0x00ac, 0x00af, 0x00b2, 0x00b5, 0x00b8, 0x00bb, 0x00be, ++ 0x00c1, 0x00c4, 0x00c7, 0x00ca, 0x00cd, 0x00d1, 0x00d4, 0x00d7, ++ 0x00db, 0x00de, 0x00e2, 0x00e5, 0x00e9, 0x00ec, 0x00f0, 0x00f4, ++ 0x00f8, 0x00fb, 0x00ff, 0x0103, 0x0107, 0x010b, 0x010f, 0x0114, ++ 0x0118, 0x011c, 0x0121, 0x0125, 0x0129, 0x012e, 0x0133, 0x0137, ++ 0x013c, 0x0141, 0x0146, 0x014b, 0x0150, 0x0155, 0x015b, 0x0160, ++ 0x0166, 0x016b, 0x0171, 0x0177, 0x017c, 0x0182, 0x0188, 0x018f, ++ 0x0195, 0x019b, 0x01a2, 0x01a9, 0x01b0, 0x01b7, 0x01be, 0x01c5, ++ 0x01cd, 0x01d4, 0x01dc, 0x01e4, 0x01ec, 0x01f5, 0x01fd, 0x0206, ++ 0x020f, 0x0218, 0x0222, 0x022c, 0x0236, 0x0240, 0x024b, 0x0256, ++ 0x0261, 0x026d, 0x0279, 0x0286, 0x0293, 0x02a0, 0x02af, 0x02bd, ++ 0x02cd, 0x02dc, 0x02ed, 0x02ff, 0x0311, 0x0324, 0x0339, 0x034e, ++ 0x0365, 0x037e, 0x0398, 0x03b5, 0x03d3, 0x03f5, 0x041a, 0x0443, ++ 0x0471, 0x04a6, 0x04e4, 0x052e, 0x058b, 0x0607, 0x06c3, 0x0859 ++ }, ++ { ++ 0x0859, 0x06c3, 0x0607, 0x058b, 0x052e, 0x04e4, 0x04a6, 0x0471, ++ 0x0443, 0x041a, 0x03f5, 0x03d3, 0x03b5, 0x0398, 0x037e, 0x0365, ++ 0x034e, 0x0339, 0x0324, 0x0311, 0x02ff, 0x02ed, 0x02dc, 0x02cd, ++ 0x02bd, 0x02af, 0x02a0, 0x0293, 0x0286, 0x0279, 0x026d, 0x0261, ++ 0x0256, 0x024b, 0x0240, 0x0236, 0x022c, 0x0222, 0x0218, 0x020f, ++ 0x0206, 0x01fd, 0x01f5, 0x01ec, 0x01e4, 0x01dc, 0x01d4, 0x01cd, ++ 0x01c5, 0x01be, 0x01b7, 0x01b0, 0x01a9, 0x01a2, 0x019b, 0x0195, ++ 0x018f, 0x0188, 0x0182, 0x017c, 0x0177, 0x0171, 0x016b, 0x0166, ++ 0x0160, 0x015b, 0x0155, 0x0150, 0x014b, 0x0146, 0x0141, 0x013c, ++ 0x0137, 0x0133, 0x012e, 0x0129, 0x0125, 0x0121, 0x011c, 0x0118, ++ 0x0114, 0x010f, 0x010b, 0x0107, 0x0103, 0x00ff, 0x00fb, 0x00f8, ++ 0x00f4, 0x00f0, 0x00ec, 0x00e9, 0x00e5, 0x00e2, 0x00de, 0x00db, ++ 0x00d7, 0x00d4, 0x00d1, 0x00cd, 0x00ca, 0x00c7, 0x00c4, 0x00c1, ++ 0x00be, 0x00bb, 0x00b8, 0x00b5, 0x00b2, 0x00af, 0x00ac, 0x00a9, ++ 0x00a7, 0x00a4, 0x00a1, 0x009f, 0x009c, 0x0099, 0x0097, 0x0094, ++ 0x0092, 0x008f, 0x008d, 0x008a, 0x0088, 0x0086, 0x0083, 0x0081, ++ 0x007f, 0x007d, 0x007a, 0x0078, 0x0076, 0x0074, 0x0072, 0x0070, ++ 0x006e, 0x006c, 0x006a, 0x0068, 0x0066, 0x0064, 0x0062, 0x0060, ++ 0x005e, 0x005c, 0x005b, 0x0059, 0x0057, 0x0055, 0x0053, 0x0052, ++ 0x0050, 0x004e, 0x004d, 0x004b, 0x004a, 0x0048, 0x0046, 0x0045, ++ 0x0043, 0x0042, 0x0040, 0x003f, 0x003e, 0x003c, 0x003b, 0x0039, ++ 0x0038, 0x0037, 0x0035, 0x0034, 0x0033, 0x0031, 0x0030, 0x002f, ++ 0x002e, 0x002d, 0x002b, 0x002a, 0x0029, 0x0028, 0x0027, 0x0026, ++ 0x0025, 0x0024, 0x0023, 0x0022, 0x0021, 0x0020, 0x001f, 0x001e, ++ 0x001d, 0x001c, 0x001b, 0x001a, 0x0019, 0x0018, 0x0017, 0x0017, ++ 0x0016, 0x0015, 0x0014, 0x0014, 0x0013, 0x0012, 0x0011, 0x0011, ++ 0x0010, 0x000f, 0x000f, 0x000e, 0x000d, 0x000d, 0x000c, 0x000c, ++ 0x000b, 0x000a, 0x000a, 0x0009, 0x0009, 0x0008, 0x0008, 0x0007, ++ 0x0007, 0x0007, 0x0006, 0x0006, 0x0005, 0x0005, 0x0005, 0x0004, ++ 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x0002, 0x0002, 0x0002, ++ 0x0002, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x0859, 0x06c3, 0x0607, 0x058b, 0x052e, 0x04e4, 0x04a6, 0x0471, ++ 0x0443, 0x041a, 0x03f5, 0x03d3, 0x03b5, 0x0398, 0x037e, 0x0365, ++ 0x034e, 0x0339, 0x0324, 0x0311, 0x02ff, 0x02ed, 0x02dc, 0x02cd, ++ 0x02bd, 0x02af, 0x02a0, 0x0293, 0x0286, 0x0279, 0x026d, 0x0261, ++ 0x0256, 0x024b, 0x0240, 0x0236, 0x022c, 0x0222, 0x0218, 0x020f, ++ 0x0206, 0x01fd, 0x01f5, 0x01ec, 0x01e4, 0x01dc, 0x01d4, 0x01cd, ++ 0x01c5, 0x01be, 0x01b7, 0x01b0, 0x01a9, 0x01a2, 0x019b, 0x0195, ++ 0x018f, 0x0188, 0x0182, 0x017c, 0x0177, 0x0171, 0x016b, 0x0166, ++ 0x0160, 0x015b, 0x0155, 0x0150, 0x014b, 0x0146, 0x0141, 0x013c, ++ 0x0137, 0x0133, 0x012e, 0x0129, 0x0125, 0x0121, 0x011c, 0x0118, ++ 0x0114, 0x010f, 0x010b, 0x0107, 0x0103, 0x00ff, 0x00fb, 0x00f8, ++ 0x00f4, 0x00f0, 0x00ec, 0x00e9, 0x00e5, 0x00e2, 0x00de, 0x00db, ++ 0x00d7, 0x00d4, 0x00d1, 0x00cd, 0x00ca, 0x00c7, 0x00c4, 0x00c1, ++ 0x00be, 0x00bb, 0x00b8, 0x00b5, 0x00b2, 0x00af, 0x00ac, 0x00a9, ++ 0x00a7, 0x00a4, 0x00a1, 0x009f, 0x009c, 0x0099, 0x0097, 0x0094, ++ 0x0092, 0x008f, 0x008d, 0x008a, 0x0088, 0x0086, 0x0083, 0x0081, ++ 0x007f, 0x007d, 0x007a, 0x0078, 0x0076, 0x0074, 0x0072, 0x0070, ++ 0x006e, 0x006c, 0x006a, 0x0068, 0x0066, 0x0064, 0x0062, 0x0060, ++ 0x005e, 0x005c, 0x005b, 0x0059, 0x0057, 0x0055, 0x0053, 0x0052, ++ 0x0050, 0x004e, 0x004d, 0x004b, 0x004a, 0x0048, 0x0046, 0x0045, ++ 0x0043, 0x0042, 0x0040, 0x003f, 0x003e, 0x003c, 0x003b, 0x0039, ++ 0x0038, 0x0037, 0x0035, 0x0034, 0x0033, 0x0031, 0x0030, 0x002f, ++ 0x002e, 0x002d, 0x002b, 0x002a, 0x0029, 0x0028, 0x0027, 0x0026, ++ 0x0025, 0x0024, 0x0023, 0x0022, 0x0021, 0x0020, 0x001f, 0x001e, ++ 0x001d, 0x001c, 0x001b, 0x001a, 0x0019, 0x0018, 0x0017, 0x0017, ++ 0x0016, 0x0015, 0x0014, 0x0014, 0x0013, 0x0012, 0x0011, 0x0011, ++ 0x0010, 0x000f, 0x000f, 0x000e, 0x000d, 0x000d, 0x000c, 0x000c, ++ 0x000b, 0x000a, 0x000a, 0x0009, 0x0009, 0x0008, 0x0008, 0x0007, ++ 0x0007, 0x0007, 0x0006, 0x0006, 0x0005, 0x0005, 0x0005, 0x0004, ++ 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x0002, 0x0002, 0x0002, ++ 0x0002, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000 ++ }, ++ { ++ 0x0859, 0x0607, 0x052e, 0x04a6, 0x0443, 0x03f5, 0x03b5, 0x037e, ++ 0x034e, 0x0324, 0x02ff, 0x02dc, 0x02bd, 0x02a0, 0x0286, 0x026d, ++ 0x0256, 0x0240, 0x022c, 0x0218, 0x0206, 0x01f5, 0x01e4, 0x01d4, ++ 0x01c5, 0x01b7, 0x01a9, 0x019b, 0x018f, 0x0182, 0x0177, 0x016b, ++ 0x0160, 0x0155, 0x014b, 0x0141, 0x0137, 0x012e, 0x0125, 0x011c, ++ 0x0114, 0x010b, 0x0103, 0x00fb, 0x00f4, 0x00ec, 0x00e5, 0x00de, ++ 0x00d7, 0x00d1, 0x00ca, 0x00c4, 0x00be, 0x00b8, 0x00b2, 0x00ac, ++ 0x00a7, 0x00a1, 0x009c, 0x0097, 0x0092, 0x008d, 0x0088, 0x0083, ++ 0x007f, 0x007a, 0x0076, 0x0072, 0x006e, 0x006a, 0x0066, 0x0062, ++ 0x005e, 0x005b, 0x0057, 0x0053, 0x0050, 0x004d, 0x004a, 0x0046, ++ 0x0043, 0x0040, 0x003e, 0x003b, 0x0038, 0x0035, 0x0033, 0x0030, ++ 0x002e, 0x002b, 0x0029, 0x0027, 0x0025, 0x0023, 0x0021, 0x001f, ++ 0x001d, 0x001b, 0x0019, 0x0017, 0x0016, 0x0014, 0x0013, 0x0011, ++ 0x0010, 0x000f, 0x000d, 0x000c, 0x000b, 0x000a, 0x0009, 0x0008, ++ 0x0007, 0x0006, 0x0005, 0x0005, 0x0004, 0x0003, 0x0003, 0x0002, ++ 0x0002, 0x0001, 0x0001, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0002, ++ 0x0002, 0x0003, 0x0003, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, ++ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000f, 0x0010, ++ 0x0011, 0x0013, 0x0014, 0x0016, 0x0017, 0x0019, 0x001b, 0x001d, ++ 0x001f, 0x0021, 0x0023, 0x0025, 0x0027, 0x0029, 0x002b, 0x002e, ++ 0x0030, 0x0033, 0x0035, 0x0038, 0x003b, 0x003e, 0x0040, 0x0043, ++ 0x0046, 0x004a, 0x004d, 0x0050, 0x0053, 0x0057, 0x005b, 0x005e, ++ 0x0062, 0x0066, 0x006a, 0x006e, 0x0072, 0x0076, 0x007a, 0x007f, ++ 0x0083, 0x0088, 0x008d, 0x0092, 0x0097, 0x009c, 0x00a1, 0x00a7, ++ 0x00ac, 0x00b2, 0x00b8, 0x00be, 0x00c4, 0x00ca, 0x00d1, 0x00d7, ++ 0x00de, 0x00e5, 0x00ec, 0x00f4, 0x00fb, 0x0103, 0x010b, 0x0114, ++ 0x011c, 0x0125, 0x012e, 0x0137, 0x0141, 0x014b, 0x0155, 0x0160, ++ 0x016b, 0x0177, 0x0182, 0x018f, 0x019b, 0x01a9, 0x01b7, 0x01c5, ++ 0x01d4, 0x01e4, 0x01f5, 0x0206, 0x0218, 0x022c, 0x0240, 0x0256, ++ 0x026d, 0x0286, 0x02a0, 0x02bd, 0x02dc, 0x02ff, 0x0324, 0x034e, ++ 0x037e, 0x03b5, 0x03f5, 0x0443, 0x04a6, 0x052e, 0x0607, 0x0859, ++ 0x8859, 0x8607, 0x852e, 0x84a6, 0x8443, 0x83f5, 0x83b5, 0x837e, ++ 0x834e, 0x8324, 0x82ff, 0x82dc, 0x82bd, 0x82a0, 0x8286, 0x826d, ++ 0x8256, 0x8240, 0x822c, 0x8218, 0x8206, 0x81f5, 0x81e4, 0x81d4, ++ 0x81c5, 0x81b7, 0x81a9, 0x819b, 0x818f, 0x8182, 0x8177, 0x816b, ++ 0x8160, 0x8155, 0x814b, 0x8141, 0x8137, 0x812e, 0x8125, 0x811c, ++ 0x8114, 0x810b, 0x8103, 0x80fb, 0x80f4, 0x80ec, 0x80e5, 0x80de, ++ 0x80d7, 0x80d1, 0x80ca, 0x80c4, 0x80be, 0x80b8, 0x80b2, 0x80ac, ++ 0x80a7, 0x80a1, 0x809c, 0x8097, 0x8092, 0x808d, 0x8088, 0x8083, ++ 0x807f, 0x807a, 0x8076, 0x8072, 0x806e, 0x806a, 0x8066, 0x8062, ++ 0x805e, 0x805b, 0x8057, 0x8053, 0x8050, 0x804d, 0x804a, 0x8046, ++ 0x8043, 0x8040, 0x803e, 0x803b, 0x8038, 0x8035, 0x8033, 0x8030, ++ 0x802e, 0x802b, 0x8029, 0x8027, 0x8025, 0x8023, 0x8021, 0x801f, ++ 0x801d, 0x801b, 0x8019, 0x8017, 0x8016, 0x8014, 0x8013, 0x8011, ++ 0x8010, 0x800f, 0x800d, 0x800c, 0x800b, 0x800a, 0x8009, 0x8008, ++ 0x8007, 0x8006, 0x8005, 0x8005, 0x8004, 0x8003, 0x8003, 0x8002, ++ 0x8002, 0x8001, 0x8001, 0x8001, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8001, 0x8001, 0x8001, 0x8002, ++ 0x8002, 0x8003, 0x8003, 0x8004, 0x8005, 0x8005, 0x8006, 0x8007, ++ 0x8008, 0x8009, 0x800a, 0x800b, 0x800c, 0x800d, 0x800f, 0x8010, ++ 0x8011, 0x8013, 0x8014, 0x8016, 0x8017, 0x8019, 0x801b, 0x801d, ++ 0x801f, 0x8021, 0x8023, 0x8025, 0x8027, 0x8029, 0x802b, 0x802e, ++ 0x8030, 0x8033, 0x8035, 0x8038, 0x803b, 0x803e, 0x8040, 0x8043, ++ 0x8046, 0x804a, 0x804d, 0x8050, 0x8053, 0x8057, 0x805b, 0x805e, ++ 0x8062, 0x8066, 0x806a, 0x806e, 0x8072, 0x8076, 0x807a, 0x807f, ++ 0x8083, 0x8088, 0x808d, 0x8092, 0x8097, 0x809c, 0x80a1, 0x80a7, ++ 0x80ac, 0x80b2, 0x80b8, 0x80be, 0x80c4, 0x80ca, 0x80d1, 0x80d7, ++ 0x80de, 0x80e5, 0x80ec, 0x80f4, 0x80fb, 0x8103, 0x810b, 0x8114, ++ 0x811c, 0x8125, 0x812e, 0x8137, 0x8141, 0x814b, 0x8155, 0x8160, ++ 0x816b, 0x8177, 0x8182, 0x818f, 0x819b, 0x81a9, 0x81b7, 0x81c5, ++ 0x81d4, 0x81e4, 0x81f5, 0x8206, 0x8218, 0x822c, 0x8240, 0x8256, ++ 0x826d, 0x8286, 0x82a0, 0x82bd, 0x82dc, 0x82ff, 0x8324, 0x834e, ++ 0x837e, 0x83b5, 0x83f5, 0x8443, 0x84a6, 0x852e, 0x8607, 0x8859, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000 ++ }, ++ { ++ 0x0859, 0x0607, 0x052e, 0x04a6, 0x0443, 0x03f5, 0x03b5, 0x037e, ++ 0x034e, 0x0324, 0x02ff, 0x02dc, 0x02bd, 0x02a0, 0x0286, 0x026d, ++ 0x0256, 0x0240, 0x022c, 0x0218, 0x0206, 0x01f5, 0x01e4, 0x01d4, ++ 0x01c5, 0x01b7, 0x01a9, 0x019b, 0x018f, 0x0182, 0x0177, 0x016b, ++ 0x0160, 0x0155, 0x014b, 0x0141, 0x0137, 0x012e, 0x0125, 0x011c, ++ 0x0114, 0x010b, 0x0103, 0x00fb, 0x00f4, 0x00ec, 0x00e5, 0x00de, ++ 0x00d7, 0x00d1, 0x00ca, 0x00c4, 0x00be, 0x00b8, 0x00b2, 0x00ac, ++ 0x00a7, 0x00a1, 0x009c, 0x0097, 0x0092, 0x008d, 0x0088, 0x0083, ++ 0x007f, 0x007a, 0x0076, 0x0072, 0x006e, 0x006a, 0x0066, 0x0062, ++ 0x005e, 0x005b, 0x0057, 0x0053, 0x0050, 0x004d, 0x004a, 0x0046, ++ 0x0043, 0x0040, 0x003e, 0x003b, 0x0038, 0x0035, 0x0033, 0x0030, ++ 0x002e, 0x002b, 0x0029, 0x0027, 0x0025, 0x0023, 0x0021, 0x001f, ++ 0x001d, 0x001b, 0x0019, 0x0017, 0x0016, 0x0014, 0x0013, 0x0011, ++ 0x0010, 0x000f, 0x000d, 0x000c, 0x000b, 0x000a, 0x0009, 0x0008, ++ 0x0007, 0x0006, 0x0005, 0x0005, 0x0004, 0x0003, 0x0003, 0x0002, ++ 0x0002, 0x0001, 0x0001, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0002, ++ 0x0002, 0x0003, 0x0003, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, ++ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000f, 0x0010, ++ 0x0011, 0x0013, 0x0014, 0x0016, 0x0017, 0x0019, 0x001b, 0x001d, ++ 0x001f, 0x0021, 0x0023, 0x0025, 0x0027, 0x0029, 0x002b, 0x002e, ++ 0x0030, 0x0033, 0x0035, 0x0038, 0x003b, 0x003e, 0x0040, 0x0043, ++ 0x0046, 0x004a, 0x004d, 0x0050, 0x0053, 0x0057, 0x005b, 0x005e, ++ 0x0062, 0x0066, 0x006a, 0x006e, 0x0072, 0x0076, 0x007a, 0x007f, ++ 0x0083, 0x0088, 0x008d, 0x0092, 0x0097, 0x009c, 0x00a1, 0x00a7, ++ 0x00ac, 0x00b2, 0x00b8, 0x00be, 0x00c4, 0x00ca, 0x00d1, 0x00d7, ++ 0x00de, 0x00e5, 0x00ec, 0x00f4, 0x00fb, 0x0103, 0x010b, 0x0114, ++ 0x011c, 0x0125, 0x012e, 0x0137, 0x0141, 0x014b, 0x0155, 0x0160, ++ 0x016b, 0x0177, 0x0182, 0x018f, 0x019b, 0x01a9, 0x01b7, 0x01c5, ++ 0x01d4, 0x01e4, 0x01f5, 0x0206, 0x0218, 0x022c, 0x0240, 0x0256, ++ 0x026d, 0x0286, 0x02a0, 0x02bd, 0x02dc, 0x02ff, 0x0324, 0x034e, ++ 0x037e, 0x03b5, 0x03f5, 0x0443, 0x04a6, 0x052e, 0x0607, 0x0859, ++ 0x0859, 0x0607, 0x052e, 0x04a6, 0x0443, 0x03f5, 0x03b5, 0x037e, ++ 0x034e, 0x0324, 0x02ff, 0x02dc, 0x02bd, 0x02a0, 0x0286, 0x026d, ++ 0x0256, 0x0240, 0x022c, 0x0218, 0x0206, 0x01f5, 0x01e4, 0x01d4, ++ 0x01c5, 0x01b7, 0x01a9, 0x019b, 0x018f, 0x0182, 0x0177, 0x016b, ++ 0x0160, 0x0155, 0x014b, 0x0141, 0x0137, 0x012e, 0x0125, 0x011c, ++ 0x0114, 0x010b, 0x0103, 0x00fb, 0x00f4, 0x00ec, 0x00e5, 0x00de, ++ 0x00d7, 0x00d1, 0x00ca, 0x00c4, 0x00be, 0x00b8, 0x00b2, 0x00ac, ++ 0x00a7, 0x00a1, 0x009c, 0x0097, 0x0092, 0x008d, 0x0088, 0x0083, ++ 0x007f, 0x007a, 0x0076, 0x0072, 0x006e, 0x006a, 0x0066, 0x0062, ++ 0x005e, 0x005b, 0x0057, 0x0053, 0x0050, 0x004d, 0x004a, 0x0046, ++ 0x0043, 0x0040, 0x003e, 0x003b, 0x0038, 0x0035, 0x0033, 0x0030, ++ 0x002e, 0x002b, 0x0029, 0x0027, 0x0025, 0x0023, 0x0021, 0x001f, ++ 0x001d, 0x001b, 0x0019, 0x0017, 0x0016, 0x0014, 0x0013, 0x0011, ++ 0x0010, 0x000f, 0x000d, 0x000c, 0x000b, 0x000a, 0x0009, 0x0008, ++ 0x0007, 0x0006, 0x0005, 0x0005, 0x0004, 0x0003, 0x0003, 0x0002, ++ 0x0002, 0x0001, 0x0001, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0002, ++ 0x0002, 0x0003, 0x0003, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, ++ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000f, 0x0010, ++ 0x0011, 0x0013, 0x0014, 0x0016, 0x0017, 0x0019, 0x001b, 0x001d, ++ 0x001f, 0x0021, 0x0023, 0x0025, 0x0027, 0x0029, 0x002b, 0x002e, ++ 0x0030, 0x0033, 0x0035, 0x0038, 0x003b, 0x003e, 0x0040, 0x0043, ++ 0x0046, 0x004a, 0x004d, 0x0050, 0x0053, 0x0057, 0x005b, 0x005e, ++ 0x0062, 0x0066, 0x006a, 0x006e, 0x0072, 0x0076, 0x007a, 0x007f, ++ 0x0083, 0x0088, 0x008d, 0x0092, 0x0097, 0x009c, 0x00a1, 0x00a7, ++ 0x00ac, 0x00b2, 0x00b8, 0x00be, 0x00c4, 0x00ca, 0x00d1, 0x00d7, ++ 0x00de, 0x00e5, 0x00ec, 0x00f4, 0x00fb, 0x0103, 0x010b, 0x0114, ++ 0x011c, 0x0125, 0x012e, 0x0137, 0x0141, 0x014b, 0x0155, 0x0160, ++ 0x016b, 0x0177, 0x0182, 0x018f, 0x019b, 0x01a9, 0x01b7, 0x01c5, ++ 0x01d4, 0x01e4, 0x01f5, 0x0206, 0x0218, 0x022c, 0x0240, 0x0256, ++ 0x026d, 0x0286, 0x02a0, 0x02bd, 0x02dc, 0x02ff, 0x0324, 0x034e, ++ 0x037e, 0x03b5, 0x03f5, 0x0443, 0x04a6, 0x052e, 0x0607, 0x0859, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, ++ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000 ++ }, ++ { ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, ++ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000 ++ }, ++ { ++ 0x0000, 0x0008, 0x0010, 0x0018, 0x0020, 0x0028, 0x0030, 0x0038, ++ 0x0040, 0x0048, 0x0050, 0x0058, 0x0060, 0x0068, 0x0070, 0x0078, ++ 0x0080, 0x0088, 0x0090, 0x0098, 0x00a0, 0x00a8, 0x00b0, 0x00b8, ++ 0x00c0, 0x00c8, 0x00d0, 0x00d8, 0x00e0, 0x00e8, 0x00f0, 0x00f8, ++ 0x0100, 0x0108, 0x0110, 0x0118, 0x0120, 0x0128, 0x0130, 0x0138, ++ 0x0140, 0x0148, 0x0150, 0x0158, 0x0160, 0x0168, 0x0170, 0x0178, ++ 0x0180, 0x0188, 0x0190, 0x0198, 0x01a0, 0x01a8, 0x01b0, 0x01b8, ++ 0x01c0, 0x01c8, 0x01d0, 0x01d8, 0x01e0, 0x01e8, 0x01f0, 0x01f8, ++ 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230, 0x0238, ++ 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, ++ 0x0280, 0x0288, 0x0290, 0x0298, 0x02a0, 0x02a8, 0x02b0, 0x02b8, ++ 0x02c0, 0x02c8, 0x02d0, 0x02d8, 0x02e0, 0x02e8, 0x02f0, 0x02f8, ++ 0x0300, 0x0308, 0x0310, 0x0318, 0x0320, 0x0328, 0x0330, 0x0338, ++ 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370, 0x0378, ++ 0x0380, 0x0388, 0x0390, 0x0398, 0x03a0, 0x03a8, 0x03b0, 0x03b8, ++ 0x03c0, 0x03c8, 0x03d0, 0x03d8, 0x03e0, 0x03e8, 0x03f0, 0x03f8, ++ 0x0400, 0x0408, 0x0410, 0x0418, 0x0420, 0x0428, 0x0430, 0x0438, ++ 0x0440, 0x0448, 0x0450, 0x0458, 0x0460, 0x0468, 0x0470, 0x0478, ++ 0x0480, 0x0488, 0x0490, 0x0498, 0x04a0, 0x04a8, 0x04b0, 0x04b8, ++ 0x04c0, 0x04c8, 0x04d0, 0x04d8, 0x04e0, 0x04e8, 0x04f0, 0x04f8, ++ 0x0500, 0x0508, 0x0510, 0x0518, 0x0520, 0x0528, 0x0530, 0x0538, ++ 0x0540, 0x0548, 0x0550, 0x0558, 0x0560, 0x0568, 0x0570, 0x0578, ++ 0x0580, 0x0588, 0x0590, 0x0598, 0x05a0, 0x05a8, 0x05b0, 0x05b8, ++ 0x05c0, 0x05c8, 0x05d0, 0x05d8, 0x05e0, 0x05e8, 0x05f0, 0x05f8, ++ 0x0600, 0x0608, 0x0610, 0x0618, 0x0620, 0x0628, 0x0630, 0x0638, ++ 0x0640, 0x0648, 0x0650, 0x0658, 0x0660, 0x0668, 0x0670, 0x0678, ++ 0x0680, 0x0688, 0x0690, 0x0698, 0x06a0, 0x06a8, 0x06b0, 0x06b8, ++ 0x06c0, 0x06c8, 0x06d0, 0x06d8, 0x06e0, 0x06e8, 0x06f0, 0x06f8, ++ 0x0700, 0x0708, 0x0710, 0x0718, 0x0720, 0x0728, 0x0730, 0x0738, ++ 0x0740, 0x0748, 0x0750, 0x0758, 0x0760, 0x0768, 0x0770, 0x0778, ++ 0x0780, 0x0788, 0x0790, 0x0798, 0x07a0, 0x07a8, 0x07b0, 0x07b8, ++ 0x07c0, 0x07c8, 0x07d0, 0x07d8, 0x07e0, 0x07e8, 0x07f0, 0x07f8, ++ 0x0800, 0x0808, 0x0810, 0x0818, 0x0820, 0x0828, 0x0830, 0x0838, ++ 0x0840, 0x0848, 0x0850, 0x0858, 0x0860, 0x0868, 0x0870, 0x0878, ++ 0x0880, 0x0888, 0x0890, 0x0898, 0x08a0, 0x08a8, 0x08b0, 0x08b8, ++ 0x08c0, 0x08c8, 0x08d0, 0x08d8, 0x08e0, 0x08e8, 0x08f0, 0x08f8, ++ 0x0900, 0x0908, 0x0910, 0x0918, 0x0920, 0x0928, 0x0930, 0x0938, ++ 0x0940, 0x0948, 0x0950, 0x0958, 0x0960, 0x0968, 0x0970, 0x0978, ++ 0x0980, 0x0988, 0x0990, 0x0998, 0x09a0, 0x09a8, 0x09b0, 0x09b8, ++ 0x09c0, 0x09c8, 0x09d0, 0x09d8, 0x09e0, 0x09e8, 0x09f0, 0x09f8, ++ 0x0a00, 0x0a08, 0x0a10, 0x0a18, 0x0a20, 0x0a28, 0x0a30, 0x0a38, ++ 0x0a40, 0x0a48, 0x0a50, 0x0a58, 0x0a60, 0x0a68, 0x0a70, 0x0a78, ++ 0x0a80, 0x0a88, 0x0a90, 0x0a98, 0x0aa0, 0x0aa8, 0x0ab0, 0x0ab8, ++ 0x0ac0, 0x0ac8, 0x0ad0, 0x0ad8, 0x0ae0, 0x0ae8, 0x0af0, 0x0af8, ++ 0x0b00, 0x0b08, 0x0b10, 0x0b18, 0x0b20, 0x0b28, 0x0b30, 0x0b38, ++ 0x0b40, 0x0b48, 0x0b50, 0x0b58, 0x0b60, 0x0b68, 0x0b70, 0x0b78, ++ 0x0b80, 0x0b88, 0x0b90, 0x0b98, 0x0ba0, 0x0ba8, 0x0bb0, 0x0bb8, ++ 0x0bc0, 0x0bc8, 0x0bd0, 0x0bd8, 0x0be0, 0x0be8, 0x0bf0, 0x0bf8, ++ 0x0c00, 0x0c08, 0x0c10, 0x0c18, 0x0c20, 0x0c28, 0x0c30, 0x0c38, ++ 0x0c40, 0x0c48, 0x0c50, 0x0c58, 0x0c60, 0x0c68, 0x0c70, 0x0c78, ++ 0x0c80, 0x0c88, 0x0c90, 0x0c98, 0x0ca0, 0x0ca8, 0x0cb0, 0x0cb8, ++ 0x0cc0, 0x0cc8, 0x0cd0, 0x0cd8, 0x0ce0, 0x0ce8, 0x0cf0, 0x0cf8, ++ 0x0d00, 0x0d08, 0x0d10, 0x0d18, 0x0d20, 0x0d28, 0x0d30, 0x0d38, ++ 0x0d40, 0x0d48, 0x0d50, 0x0d58, 0x0d60, 0x0d68, 0x0d70, 0x0d78, ++ 0x0d80, 0x0d88, 0x0d90, 0x0d98, 0x0da0, 0x0da8, 0x0db0, 0x0db8, ++ 0x0dc0, 0x0dc8, 0x0dd0, 0x0dd8, 0x0de0, 0x0de8, 0x0df0, 0x0df8, ++ 0x0e00, 0x0e08, 0x0e10, 0x0e18, 0x0e20, 0x0e28, 0x0e30, 0x0e38, ++ 0x0e40, 0x0e48, 0x0e50, 0x0e58, 0x0e60, 0x0e68, 0x0e70, 0x0e78, ++ 0x0e80, 0x0e88, 0x0e90, 0x0e98, 0x0ea0, 0x0ea8, 0x0eb0, 0x0eb8, ++ 0x0ec0, 0x0ec8, 0x0ed0, 0x0ed8, 0x0ee0, 0x0ee8, 0x0ef0, 0x0ef8, ++ 0x0f00, 0x0f08, 0x0f10, 0x0f18, 0x0f20, 0x0f28, 0x0f30, 0x0f38, ++ 0x0f40, 0x0f48, 0x0f50, 0x0f58, 0x0f60, 0x0f68, 0x0f70, 0x0f78, ++ 0x0f80, 0x0f88, 0x0f90, 0x0f98, 0x0fa0, 0x0fa8, 0x0fb0, 0x0fb8, ++ 0x0fc0, 0x0fc8, 0x0fd0, 0x0fd8, 0x0fe0, 0x0fe8, 0x0ff0, 0x0ff8, ++ 0x8ff8, 0x8ff0, 0x8fe8, 0x8fe0, 0x8fd8, 0x8fd0, 0x8fc8, 0x8fc0, ++ 0x8fb8, 0x8fb0, 0x8fa8, 0x8fa0, 0x8f98, 0x8f90, 0x8f88, 0x8f80, ++ 0x8f78, 0x8f70, 0x8f68, 0x8f60, 0x8f58, 0x8f50, 0x8f48, 0x8f40, ++ 0x8f38, 0x8f30, 0x8f28, 0x8f20, 0x8f18, 0x8f10, 0x8f08, 0x8f00, ++ 0x8ef8, 0x8ef0, 0x8ee8, 0x8ee0, 0x8ed8, 0x8ed0, 0x8ec8, 0x8ec0, ++ 0x8eb8, 0x8eb0, 0x8ea8, 0x8ea0, 0x8e98, 0x8e90, 0x8e88, 0x8e80, ++ 0x8e78, 0x8e70, 0x8e68, 0x8e60, 0x8e58, 0x8e50, 0x8e48, 0x8e40, ++ 0x8e38, 0x8e30, 0x8e28, 0x8e20, 0x8e18, 0x8e10, 0x8e08, 0x8e00, ++ 0x8df8, 0x8df0, 0x8de8, 0x8de0, 0x8dd8, 0x8dd0, 0x8dc8, 0x8dc0, ++ 0x8db8, 0x8db0, 0x8da8, 0x8da0, 0x8d98, 0x8d90, 0x8d88, 0x8d80, ++ 0x8d78, 0x8d70, 0x8d68, 0x8d60, 0x8d58, 0x8d50, 0x8d48, 0x8d40, ++ 0x8d38, 0x8d30, 0x8d28, 0x8d20, 0x8d18, 0x8d10, 0x8d08, 0x8d00, ++ 0x8cf8, 0x8cf0, 0x8ce8, 0x8ce0, 0x8cd8, 0x8cd0, 0x8cc8, 0x8cc0, ++ 0x8cb8, 0x8cb0, 0x8ca8, 0x8ca0, 0x8c98, 0x8c90, 0x8c88, 0x8c80, ++ 0x8c78, 0x8c70, 0x8c68, 0x8c60, 0x8c58, 0x8c50, 0x8c48, 0x8c40, ++ 0x8c38, 0x8c30, 0x8c28, 0x8c20, 0x8c18, 0x8c10, 0x8c08, 0x8c00, ++ 0x8bf8, 0x8bf0, 0x8be8, 0x8be0, 0x8bd8, 0x8bd0, 0x8bc8, 0x8bc0, ++ 0x8bb8, 0x8bb0, 0x8ba8, 0x8ba0, 0x8b98, 0x8b90, 0x8b88, 0x8b80, ++ 0x8b78, 0x8b70, 0x8b68, 0x8b60, 0x8b58, 0x8b50, 0x8b48, 0x8b40, ++ 0x8b38, 0x8b30, 0x8b28, 0x8b20, 0x8b18, 0x8b10, 0x8b08, 0x8b00, ++ 0x8af8, 0x8af0, 0x8ae8, 0x8ae0, 0x8ad8, 0x8ad0, 0x8ac8, 0x8ac0, ++ 0x8ab8, 0x8ab0, 0x8aa8, 0x8aa0, 0x8a98, 0x8a90, 0x8a88, 0x8a80, ++ 0x8a78, 0x8a70, 0x8a68, 0x8a60, 0x8a58, 0x8a50, 0x8a48, 0x8a40, ++ 0x8a38, 0x8a30, 0x8a28, 0x8a20, 0x8a18, 0x8a10, 0x8a08, 0x8a00, ++ 0x89f8, 0x89f0, 0x89e8, 0x89e0, 0x89d8, 0x89d0, 0x89c8, 0x89c0, ++ 0x89b8, 0x89b0, 0x89a8, 0x89a0, 0x8998, 0x8990, 0x8988, 0x8980, ++ 0x8978, 0x8970, 0x8968, 0x8960, 0x8958, 0x8950, 0x8948, 0x8940, ++ 0x8938, 0x8930, 0x8928, 0x8920, 0x8918, 0x8910, 0x8908, 0x8900, ++ 0x88f8, 0x88f0, 0x88e8, 0x88e0, 0x88d8, 0x88d0, 0x88c8, 0x88c0, ++ 0x88b8, 0x88b0, 0x88a8, 0x88a0, 0x8898, 0x8890, 0x8888, 0x8880, ++ 0x8878, 0x8870, 0x8868, 0x8860, 0x8858, 0x8850, 0x8848, 0x8840, ++ 0x8838, 0x8830, 0x8828, 0x8820, 0x8818, 0x8810, 0x8808, 0x8800, ++ 0x87f8, 0x87f0, 0x87e8, 0x87e0, 0x87d8, 0x87d0, 0x87c8, 0x87c0, ++ 0x87b8, 0x87b0, 0x87a8, 0x87a0, 0x8798, 0x8790, 0x8788, 0x8780, ++ 0x8778, 0x8770, 0x8768, 0x8760, 0x8758, 0x8750, 0x8748, 0x8740, ++ 0x8738, 0x8730, 0x8728, 0x8720, 0x8718, 0x8710, 0x8708, 0x8700, ++ 0x86f8, 0x86f0, 0x86e8, 0x86e0, 0x86d8, 0x86d0, 0x86c8, 0x86c0, ++ 0x86b8, 0x86b0, 0x86a8, 0x86a0, 0x8698, 0x8690, 0x8688, 0x8680, ++ 0x8678, 0x8670, 0x8668, 0x8660, 0x8658, 0x8650, 0x8648, 0x8640, ++ 0x8638, 0x8630, 0x8628, 0x8620, 0x8618, 0x8610, 0x8608, 0x8600, ++ 0x85f8, 0x85f0, 0x85e8, 0x85e0, 0x85d8, 0x85d0, 0x85c8, 0x85c0, ++ 0x85b8, 0x85b0, 0x85a8, 0x85a0, 0x8598, 0x8590, 0x8588, 0x8580, ++ 0x8578, 0x8570, 0x8568, 0x8560, 0x8558, 0x8550, 0x8548, 0x8540, ++ 0x8538, 0x8530, 0x8528, 0x8520, 0x8518, 0x8510, 0x8508, 0x8500, ++ 0x84f8, 0x84f0, 0x84e8, 0x84e0, 0x84d8, 0x84d0, 0x84c8, 0x84c0, ++ 0x84b8, 0x84b0, 0x84a8, 0x84a0, 0x8498, 0x8490, 0x8488, 0x8480, ++ 0x8478, 0x8470, 0x8468, 0x8460, 0x8458, 0x8450, 0x8448, 0x8440, ++ 0x8438, 0x8430, 0x8428, 0x8420, 0x8418, 0x8410, 0x8408, 0x8400, ++ 0x83f8, 0x83f0, 0x83e8, 0x83e0, 0x83d8, 0x83d0, 0x83c8, 0x83c0, ++ 0x83b8, 0x83b0, 0x83a8, 0x83a0, 0x8398, 0x8390, 0x8388, 0x8380, ++ 0x8378, 0x8370, 0x8368, 0x8360, 0x8358, 0x8350, 0x8348, 0x8340, ++ 0x8338, 0x8330, 0x8328, 0x8320, 0x8318, 0x8310, 0x8308, 0x8300, ++ 0x82f8, 0x82f0, 0x82e8, 0x82e0, 0x82d8, 0x82d0, 0x82c8, 0x82c0, ++ 0x82b8, 0x82b0, 0x82a8, 0x82a0, 0x8298, 0x8290, 0x8288, 0x8280, ++ 0x8278, 0x8270, 0x8268, 0x8260, 0x8258, 0x8250, 0x8248, 0x8240, ++ 0x8238, 0x8230, 0x8228, 0x8220, 0x8218, 0x8210, 0x8208, 0x8200, ++ 0x81f8, 0x81f0, 0x81e8, 0x81e0, 0x81d8, 0x81d0, 0x81c8, 0x81c0, ++ 0x81b8, 0x81b0, 0x81a8, 0x81a0, 0x8198, 0x8190, 0x8188, 0x8180, ++ 0x8178, 0x8170, 0x8168, 0x8160, 0x8158, 0x8150, 0x8148, 0x8140, ++ 0x8138, 0x8130, 0x8128, 0x8120, 0x8118, 0x8110, 0x8108, 0x8100, ++ 0x80f8, 0x80f0, 0x80e8, 0x80e0, 0x80d8, 0x80d0, 0x80c8, 0x80c0, ++ 0x80b8, 0x80b0, 0x80a8, 0x80a0, 0x8098, 0x8090, 0x8088, 0x8080, ++ 0x8078, 0x8070, 0x8068, 0x8060, 0x8058, 0x8050, 0x8048, 0x8040, ++ 0x8038, 0x8030, 0x8028, 0x8020, 0x8018, 0x8010, 0x8008, 0x8000 ++ } ++}; ++ ++#endif /* OPL_WF_ROM_H */ diff --git a/games/sdl3-doom/patches/0008-build-change-name-to-SDL3-Doom.patch b/games/sdl3-doom/patches/0008-build-change-name-to-SDL3-Doom.patch new file mode 100644 index 00000000..ec2b4b6b --- /dev/null +++ b/games/sdl3-doom/patches/0008-build-change-name-to-SDL3-Doom.patch @@ -0,0 +1,40 @@ +From b649f9423125f87f63c2cdf611327547526a35b9 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 9 Jun 2026 18:30:46 +0100 +Subject: [PATCH] build: change name to SDL3-Doom + +Replace stale reference to the SDL2-Doom name. + +Signed-off-by: Daniel Golle +--- + include/config.h | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/include/config.h ++++ b/include/config.h +@@ -70,13 +70,13 @@ + #undef PACKAGE_BUGREPORT + + /* Define to the full name of this package. */ +-#define PACKAGE_NAME "SDL2-Doom" ++#define PACKAGE_NAME "SDL3-Doom" + + /* Define to the full name and version of this package. */ +-#define PACKAGE_STRING "SDL2-Doom" ++#define PACKAGE_STRING "SDL3-Doom" + + /* Define to the one symbol short name of this package. */ +-#define PACKAGE_TARNAME "sdl2-doom.tar" ++#define PACKAGE_TARNAME "sdl3-doom.tar" + + /* Define to the home page for this package. */ + #define PACKAGE_URL "" +@@ -85,7 +85,7 @@ + #define PACKAGE_VERSION 0.1 + + /* Change this when you create your awesome forked version */ +-#define PROGRAM_PREFIX "sdl2-doom" ++#define PROGRAM_PREFIX "sdl3-doom" + + /* Define to 1 if you have the ANSI C header files. */ + #define STDC_HEADERS 1 diff --git a/games/sdl3-doom/patches/0009-cmake-drop-the-unused-SDL2_mixer-find-module.patch b/games/sdl3-doom/patches/0009-cmake-drop-the-unused-SDL2_mixer-find-module.patch new file mode 100644 index 00000000..1f53d9e3 --- /dev/null +++ b/games/sdl3-doom/patches/0009-cmake-drop-the-unused-SDL2_mixer-find-module.patch @@ -0,0 +1,240 @@ +From 1c8bcca4ffe4b58bce9643a785d4590bc32a40a8 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 9 Jun 2026 18:35:56 +0100 +Subject: [PATCH] cmake: drop the unused SDL2_mixer find module + +cmake/sdl2/FindSDL2_mixer.cmake is left over from the SDL2 era. The +build uses find_package(SDL3_mixer) and the SDL3_mixer::SDL3_mixer +imported target, and nothing references this module (no CMAKE_MODULE_PATH +points at cmake/sdl2), so remove it. + +Signed-off-by: Daniel Golle +--- + cmake/sdl2/FindSDL2_mixer.cmake | 220 -------------------------------- + 1 file changed, 220 deletions(-) + delete mode 100644 cmake/sdl2/FindSDL2_mixer.cmake + +--- a/cmake/sdl2/FindSDL2_mixer.cmake ++++ /dev/null +@@ -1,220 +0,0 @@ +-# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +-# file Copyright.txt or https://cmake.org/licensing for details. +- +-# Copyright 2019 Amine Ben Hassouna +-# Copyright 2000-2019 Kitware, Inc. and Contributors +-# All rights reserved. +- +-# Redistribution and use in source and binary forms, with or without +-# modification, are permitted provided that the following conditions +-# are met: +- +-# * Redistributions of source code must retain the above copyright +-# notice, this list of conditions and the following disclaimer. +- +-# * Redistributions in binary form must reproduce the above copyright +-# notice, this list of conditions and the following disclaimer in the +-# documentation and/or other materials provided with the distribution. +- +-# * Neither the name of Kitware, Inc. nor the names of Contributors +-# may be used to endorse or promote products derived from this +-# software without specific prior written permission. +- +-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +-# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +- +-#[=======================================================================[.rst: +-FindSDL2_mixer +--------------- +- +-Locate SDL2_mixer library +- +-This module defines the following 'IMPORTED' target: +- +-:: +- +- SDL2::Mixer +- The SDL2_mixer library, if found. +- Have SDL2::Core as a link dependency. +- +- +- +-This module will set the following variables in your project: +- +-:: +- +- SDL2_MIXER_LIBRARIES, the name of the library to link against +- SDL2_MIXER_INCLUDE_DIRS, where to find the headers +- SDL2_MIXER_FOUND, if false, do not try to link against +- SDL2_MIXER_VERSION_STRING - human-readable string containing the +- version of SDL2_mixer +- +-This module responds to the following cache variables: +- +-:: +- +- SDL2_MIXER_PATH +- Set a custom SDL2_mixer Library path (default: empty) +- +- SDL2_MIXER_NO_DEFAULT_PATH +- Disable search SDL2_mixer Library in default path. +- If SDL2_MIXER_PATH (default: ON) +- Else (default: OFF) +- +- SDL2_MIXER_INCLUDE_DIR +- SDL2_mixer headers path. +- +- SDL2_MIXER_LIBRARY +- SDL2_mixer Library (.dll, .so, .a, etc) path. +- +- +-Additional Note: If you see an empty SDL2_MIXER_LIBRARY in your project +-configuration, it means CMake did not find your SDL2_mixer library +-(SDL2_mixer.dll, libsdl2_mixer.so, etc). Set SDL2_MIXER_LIBRARY to point +-to your SDL2_mixer library, and configure again. This value is used to +-generate the final SDL2_MIXER_LIBRARIES variable and the SDL2::Mixer target, +-but when this value is unset, SDL2_MIXER_LIBRARIES and SDL2::Mixer does not +-get created. +- +- +-$SDL2MIXERDIR is an environment variable that would correspond to the +-./configure --prefix=$SDL2MIXERDIR used in building SDL2_mixer. +- +-$SDL2DIR is an environment variable that would correspond to the +-./configure --prefix=$SDL2DIR used in building SDL2. +- +- +- +-Created by Amine Ben Hassouna: +- Adapt FindSDL_mixer.cmake to SDL2_mixer (FindSDL2_mixer.cmake). +- Add cache variables for more flexibility: +- SDL2_MIXER_PATH, SDL2_MIXER_NO_DEFAULT_PATH (for details, see doc above). +- Add SDL2 as a required dependency. +- Modernize the FindSDL2_mixer.cmake module by creating a specific target: +- SDL2::Mixer (for details, see doc above). +- +-Original FindSDL_mixer.cmake module: +- Created by Eric Wing. This was influenced by the FindSDL.cmake +- module, but with modifications to recognize OS X frameworks and +- additional Unix paths (FreeBSD, etc). +-#]=======================================================================] +- +-# SDL2 Library required +-find_package(SDL2 QUIET) +-if(NOT SDL2_FOUND) +- set(SDL2_MIXER_SDL2_NOT_FOUND "Could NOT find SDL2 (SDL2 is required by SDL2_mixer).") +- if(SDL2_mixer_FIND_REQUIRED) +- message(FATAL_ERROR ${SDL2_MIXER_SDL2_NOT_FOUND}) +- else() +- if(NOT SDL2_mixer_FIND_QUIETLY) +- message(STATUS ${SDL2_MIXER_SDL2_NOT_FOUND}) +- endif() +- return() +- endif() +- unset(SDL2_MIXER_SDL2_NOT_FOUND) +-endif() +- +- +-# Define options for searching SDL2_mixer Library in a custom path +- +-set(SDL2_MIXER_PATH "" CACHE STRING "Custom SDL2_mixer Library path") +- +-set(_SDL2_MIXER_NO_DEFAULT_PATH OFF) +-if(SDL2_MIXER_PATH) +- set(_SDL2_MIXER_NO_DEFAULT_PATH ON) +-endif() +- +-set(SDL2_MIXER_NO_DEFAULT_PATH ${_SDL2_MIXER_NO_DEFAULT_PATH} +- CACHE BOOL "Disable search SDL2_mixer Library in default path") +-unset(_SDL2_MIXER_NO_DEFAULT_PATH) +- +-set(SDL2_MIXER_NO_DEFAULT_PATH_CMD) +-if(SDL2_MIXER_NO_DEFAULT_PATH) +- set(SDL2_MIXER_NO_DEFAULT_PATH_CMD NO_DEFAULT_PATH) +-endif() +- +-# Search for the SDL2_mixer include directory +-find_path(SDL2_MIXER_INCLUDE_DIR SDL_mixer.h +- HINTS +- ENV SDL2MIXERDIR +- ENV SDL2DIR +- ${SDL2_MIXER_NO_DEFAULT_PATH_CMD} +- PATH_SUFFIXES SDL2 +- # path suffixes to search inside ENV{SDL2DIR} +- # and ENV{SDL2MIXERDIR} +- include/SDL2 include +- PATHS ${SDL2_MIXER_PATH} +- DOC "Where the SDL2_mixer headers can be found" +-) +- +-if(CMAKE_SIZEOF_VOID_P EQUAL 8) +- set(VC_LIB_PATH_SUFFIX lib/x64) +-else() +- set(VC_LIB_PATH_SUFFIX lib/x86) +-endif() +- +-# Search for the SDL2_mixer library +-find_library(SDL2_MIXER_LIBRARY +- NAMES SDL2_mixer +- HINTS +- ENV SDL2MIXERDIR +- ENV SDL2DIR +- ${SDL2_MIXER_NO_DEFAULT_PATH_CMD} +- PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} +- PATHS ${SDL2_MIXER_PATH} +- DOC "Where the SDL2_mixer Library can be found" +-) +- +-# Read SDL2_mixer version +-if(SDL2_MIXER_INCLUDE_DIR AND EXISTS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h") +- file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+[0-9]+$") +- file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+[0-9]+$") +- file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+[0-9]+$") +- string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MAJOR "${SDL2_MIXER_VERSION_MAJOR_LINE}") +- string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MINOR "${SDL2_MIXER_VERSION_MINOR_LINE}") +- string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_PATCH "${SDL2_MIXER_VERSION_PATCH_LINE}") +- set(SDL2_MIXER_VERSION_STRING ${SDL2_MIXER_VERSION_MAJOR}.${SDL2_MIXER_VERSION_MINOR}.${SDL2_MIXER_VERSION_PATCH}) +- unset(SDL2_MIXER_VERSION_MAJOR_LINE) +- unset(SDL2_MIXER_VERSION_MINOR_LINE) +- unset(SDL2_MIXER_VERSION_PATCH_LINE) +- unset(SDL2_MIXER_VERSION_MAJOR) +- unset(SDL2_MIXER_VERSION_MINOR) +- unset(SDL2_MIXER_VERSION_PATCH) +-endif() +- +-set(SDL2_MIXER_LIBRARIES ${SDL2_MIXER_LIBRARY}) +-set(SDL2_MIXER_INCLUDE_DIRS ${SDL2_MIXER_INCLUDE_DIR}) +- +-include(FindPackageHandleStandardArgs) +- +-FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_mixer +- REQUIRED_VARS SDL2_MIXER_LIBRARIES SDL2_MIXER_INCLUDE_DIRS +- VERSION_VAR SDL2_MIXER_VERSION_STRING) +- +- +-mark_as_advanced(SDL2_MIXER_PATH +- SDL2_MIXER_NO_DEFAULT_PATH +- SDL2_MIXER_LIBRARY +- SDL2_MIXER_INCLUDE_DIR) +- +- +-if(SDL2_MIXER_FOUND) +- +- # SDL2::Mixer target +- if(SDL2_MIXER_LIBRARY AND NOT TARGET SDL2::Mixer) +- add_library(SDL2::Mixer UNKNOWN IMPORTED) +- set_target_properties(SDL2::Mixer PROPERTIES +- IMPORTED_LOCATION "${SDL2_MIXER_LIBRARY}" +- INTERFACE_INCLUDE_DIRECTORIES "${SDL2_MIXER_INCLUDE_DIR}" +- INTERFACE_LINK_LIBRARIES SDL2::Core) +- endif() +-endif() +\ No newline at end of file diff --git a/libs/sdl2-compat/Makefile b/libs/sdl2-compat/Makefile new file mode 100644 index 00000000..c90259cb --- /dev/null +++ b/libs/sdl2-compat/Makefile @@ -0,0 +1,59 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=sdl2-compat +PKG_VERSION:=2.32.70 +PKG_RELEASE:=1 + +PKG_SOURCE:=sdl2-compat-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://github.com/libsdl-org/sdl2-compat/releases/download/release-$(PKG_VERSION)/ +PKG_HASH:=998fa62557eb46ffe7e5c3e2c123bc332f7df9d9f593b3ceed88ed1158428a44 + +PKG_BUILD_DIR:=$(BUILD_DIR)/sdl2-compat-$(PKG_VERSION) + +PKG_MAINTAINER:=Daniel Golle +PKG_LICENSE:=Zlib +PKG_LICENSE_FILES:=LICENSE.txt + +CMAKE_INSTALL:=1 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +CMAKE_BINARY_SUBDIR:=openwrt-build + +CMAKE_OPTIONS += \ + -DSDL2COMPAT_INSTALL=ON \ + -DSDL2COMPAT_INSTALL_TESTS=OFF \ + -DSDL2COMPAT_STATIC=OFF \ + -DSDL2COMPAT_TESTS=OFF \ + -DSDL2COMPAT_X11=OFF + +define Package/libsdl2-compat + SECTION:=libs + CATEGORY:=Libraries + TITLE:=SDL2 compatibility layer built on SDL3 + URL:=https://www.libsdl.org + DEPENDS:=+libsdl3 + PROVIDES:=libsdl2 +endef + +define Package/libsdl2-compat/description + sdl2-compat is an implementation of the SDL2 API on top of SDL3, + letting SDL2 applications and libraries run against a single SDL3 build. + It ships the libSDL2 shared library and SDL2 development files. +endef + +define Build/InstallDev + $(call Build/InstallDev/cmake,$(1)) + $(SED) 's|^libdir=.*|libdir=\x24{exec_prefix}/lib|' \ + $(1)/usr/lib/pkgconfig/sdl2-compat.pc + $(SED) 's|^includedir=.*|includedir=\x24{prefix}/include|' \ + $(1)/usr/lib/pkgconfig/sdl2-compat.pc +endef + +define Package/libsdl2-compat/install + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libSDL2*.so* $(1)/usr/lib +endef + +$(eval $(call BuildPackage,libsdl2-compat)) diff --git a/libs/sdl2-image/Makefile b/libs/sdl2-image/Makefile index 394581dc..ab82afae 100644 --- a/libs/sdl2-image/Makefile +++ b/libs/sdl2-image/Makefile @@ -27,11 +27,13 @@ CMAKE_OPTIONS += \ -DSDL2IMAGE_INSTALL=ON \ -DSDL2IMAGE_VENDORED=OFF \ -DSDL2IMAGE_DEPS_SHARED=OFF \ - -DSDL2IMAGE_BACKEND_STB=ON \ + -DSDL2IMAGE_BACKEND_STB=OFF \ + -DSDL2IMAGE_PNG=ON \ + -DSDL2IMAGE_JPG=ON \ + -DSDL2IMAGE_TIF=ON \ + -DSDL2IMAGE_WEBP=ON \ -DSDL2IMAGE_AVIF=OFF \ - -DSDL2IMAGE_JXL=OFF \ - -DSDL2IMAGE_TIF=OFF \ - -DSDL2IMAGE_WEBP=OFF + -DSDL2IMAGE_JXL=OFF define Build/InstallDev $(call Build/InstallDev/cmake,$(1)) @@ -45,12 +47,12 @@ define Package/libsdl2-image CATEGORY:=Libraries TITLE:=SDL2 Image URL:=https://www.libsdl.org/ - DEPENDS:=+libsdl2 + DEPENDS:=+libsdl2 +libpng +libjpeg +libtiff +libwebp endef define Package/libsdl2-image/description - SDL_image is an image file loading library for SDL2. PNG and JPEG are - handled by the bundled stb_image backend, alongside built-in support for + SDL_image is an image file loading library for SDL2. PNG, JPEG, TIFF and + WebP are handled by libpng, libjpeg, libtiff and libwebp, alongside built-in support for BMP, GIF, LBM, PCX, PNM, QOI, SVG, TGA, XCF, XPM and XV. endef diff --git a/libs/sdl2-mixer/Makefile b/libs/sdl2-mixer/Makefile index b809454b..c2e9fc09 100644 --- a/libs/sdl2-mixer/Makefile +++ b/libs/sdl2-mixer/Makefile @@ -1,12 +1,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=sdl2-mixer -PKG_VERSION:=2.8.1 +PKG_VERSION:=2.8.2 PKG_RELEASE:=1 PKG_SOURCE:=SDL2_mixer-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/libsdl-org/SDL_mixer/tar.gz/release-$(PKG_VERSION)? -PKG_HASH:=63804b4b2ba503865c0853f102231aeff489b1dfc6dea4750a69e2a8ef54b2bb +PKG_HASH:=bcd36dc5df23f2a8a89ea518a323dcdde0e296bdcd9b3427d933c21165ca42ce PKG_BUILD_DIR:=$(BUILD_DIR)/SDL_mixer-release-$(PKG_VERSION) diff --git a/libs/sdl2/Makefile b/libs/sdl2/Makefile deleted file mode 100644 index 6f3e699e..00000000 --- a/libs/sdl2/Makefile +++ /dev/null @@ -1,157 +0,0 @@ -include $(TOPDIR)/rules.mk - -PKG_NAME:=sdl2 -PKG_VERSION:=2.32.10 -PKG_RELEASE:=1 - -PKG_SOURCE:=SDL2-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://www.libsdl.org/release/ -PKG_HASH:=5f5993c530f084535c65a6879e9b26ad441169b3e25d789d83287040a9ca5165 - -PKG_BUILD_DIR:=$(BUILD_DIR)/SDL2-$(PKG_VERSION) - -PKG_MAINTAINER:=Daniel Golle -PKG_LICENSE:=BSD-3c -PKG_LICENSE_FILES:=COPYING LICENSE - -CMAKE_INSTALL:=1 -PKG_CONFIG_DEPENDS:=CONFIG_PACKAGE_libsdl2-tests - -include $(INCLUDE_DIR)/package.mk -include $(INCLUDE_DIR)/cmake.mk - -CMAKE_BINARY_SUBDIR:=openwrt-build - -CMAKE_OPTIONS += \ - -DSDL_ALSA=ON \ - -DSDL_ALSA_SHARED=ON \ - -DSDL_ARTS=OFF \ - -DSDL_ARTS_SHARED=OFF \ - -DSDL_ASAN=OFF \ - -DSDL_BACKGROUNDING_SIGNAL=OFF \ - -DSDL_CCACHE=OFF \ - -DSDL_CLOCK_GETTIME=ON \ - -DSDL_COCOA=OFF \ - -DSDL_DBUS=OFF \ - -DSDL_DIRECTFB=OFF \ - -DSDL_DIRECTFB_SHARED=OFF \ - -DSDL_DIRECTX=OFF \ - -DSDL_DISKAUDIO=OFF \ - -DSDL_DUMMYAUDIO=ON \ - -DSDL_DUMMYVIDEO=ON \ - -DSDL_ESD=OFF \ - -DSDL_ESD_SHARED=OFF \ - -DSDL_FOREGROUNDING_SIGNAL=OFF \ - -DSDL_FUSIONSOUND=OFF \ - -DSDL_FUSIONSOUND_SHARED=OFF \ - -DSDL_GCC_ATOMICS=ON \ - -DSDL_HIDAPI=ON \ - -DSDL_HIDAPI_JOYSTICK=ON \ - -DSDL_HIDAPI_LIBUSB=ON \ - -DSDL_IBUS=OFF \ - -DSDL_JACK=OFF \ - -DSDL_JACK_SHARED=OFF \ - -DSDL_KMSDRM=ON \ - -DSDL_KMSDRM_SHARED=ON \ - -DSDL_LASX=OFF \ - -DSDL_LIBC=ON \ - -DSDL_LIBICONV=OFF \ - -DSDL_LIBSAMPLERATE=ON \ - -DSDL_LIBSAMPLERATE_SHARED=ON \ - -DSDL_LIBUDEV=ON \ - -DSDL_LSX=OFF \ - -DSDL_METAL=OFF \ - -DSDL_MMX=OFF \ - -DSDL_NAS=OFF \ - -DSDL_NAS_SHARED=OFF \ - -DSDL_OFFSCREEN=ON \ - -DSDL_OPENGL=ON \ - -DSDL_OPENGLES=ON \ - -DSDL_OSS=OFF \ - -DSDL_PIPEWIRE=OFF \ - -DSDL_PIPEWIRE_SHARED=OFF \ - -DSDL_PTHREADS=ON \ - -DSDL_PTHREADS_SEM=ON \ - -DSDL_PULSEAUDIO=ON \ - -DSDL_PULSEAUDIO_SHARED=ON \ - -DSDL_RENDER_D3D=OFF \ - -DSDL_RENDER_METAL=OFF \ - -DSDL_RPATH=OFF \ - -DSDL_RPI=OFF \ - -DSDL_SNDIO=OFF \ - -DSDL_SNDIO_SHARED=OFF \ - -DSDL_SYSTEM_ICONV=OFF \ - -DSDL_TESTS=$(if $(CONFIG_PACKAGE_libsdl2-tests),ON,OFF) \ - -DSDL_INSTALL_TESTS=$(if $(CONFIG_PACKAGE_libsdl2-tests),ON,OFF) \ - -DSDL_VENDOR_INFO=OFF \ - -DSDL_VIRTUAL_JOYSTICK=OFF \ - -DSDL_VIVANTE=OFF \ - -DSDL_VULKAN=ON \ - -DSDL_WASAPI=OFF \ - -DSDL_WAYLAND=ON \ - -DSDL_WAYLAND_LIBDECOR=OFF \ - -DSDL_WAYLAND_LIBDECOR_SHARED=OFF \ - -DSDL_WAYLAND_QT_TOUCH=ON \ - -DSDL_WAYLAND_SHARED=ON \ - -DSDL_X11=OFF \ - -DSDL_X11_SHARED=OFF \ - -DSDL_X11_XCURSOR=OFF \ - -DSDL_X11_XDBE=OFF \ - -DSDL_X11_XFIXES=OFF \ - -DSDL_X11_XINPUT=OFF \ - -DSDL_X11_XRANDR=OFF \ - -DSDL_X11_XSCRNSAVER=OFF \ - -DSDL_X11_XSHAPE=OFF \ - -DSDL_XINPUT=OFF - -define Package/libsdl2 - SECTION:=libs - CATEGORY:=Libraries - TITLE:=Simple DirectMedia Layer - URL:=https://www.libsdl.org - DEPENDS:=+libstdcpp +alsa-lib +libudev-zero +pulseaudio +libmesa +libsamplerate +libwayland +wayland-protocols -endef - -define Package/libsdl2/description - Simple DirectMedia Layer is a cross-platform development library designed to - provide low level access to audio, keyboard, mouse, joystick, and graphics - hardware via OpenGL and Direct3D. -endef - -define Build/InstallDev - $(call Build/InstallDev/cmake,$(1)) -ifdef CONFIG_DEBUG - $(LN) libSDL2d.so $(1)/usr/lib/libSDL2.so -endif -endef - -define Package/libsdl2/install - $(INSTALL_DIR) $(1)/usr/lib - $(CP) $(PKG_INSTALL_DIR)/usr/lib/libSDL2*.so* $(1)/usr/lib -ifdef CONFIG_DEBUG - $(LN) libSDL2d.so $(1)/usr/lib/libSDL2.so -endif -endef - -define Package/libsdl2-tests - SECTION:=utils - CATEGORY:=Utilities - SUBMENU:=Video - TITLE:=SDL installed tests - URL:=https://www.libsdl.org - DEPENDS:=+libsdl2 -endef - -define Package/libsdl2-tests/description - Various test programs to validate SDL is working correctly, and some benchmarks. -endef - -define Package/libsdl2-tests/install - $(INSTALL_DIR) $(1)/usr/libexec/installed-tests - $(CP) $(PKG_INSTALL_DIR)/usr/libexec/installed-tests/* $(1)/usr/libexec/installed-tests - $(INSTALL_DIR) $(1)/usr/share/installed-tests - $(CP) $(PKG_INSTALL_DIR)/usr/share/installed-tests/* $(1)/usr/share/installed-tests -endef - -$(eval $(call BuildPackage,libsdl2)) -$(eval $(call BuildPackage,libsdl2-tests)) diff --git a/libs/sdl2/patches/100-fix-segfault-in-Wayland_DestroyWindow.patch b/libs/sdl2/patches/100-fix-segfault-in-Wayland_DestroyWindow.patch deleted file mode 100644 index dcda4883..00000000 --- a/libs/sdl2/patches/100-fix-segfault-in-Wayland_DestroyWindow.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/src/video/wayland/SDL_waylandwindow.c -+++ b/src/video/wayland/SDL_waylandwindow.c -@@ -2292,7 +2292,7 @@ void Wayland_DestroyWindow(_THIS, SDL_Wi - SDL_VideoData *data = _this->driverdata; - SDL_WindowData *wind = window->driverdata; - -- if (data) { -+ if (wind) { - #ifdef SDL_VIDEO_OPENGL_EGL - if (wind->egl_surface) { - SDL_EGL_DestroySurface(_this, wind->egl_surface); -@@ -2346,6 +2346,8 @@ void Wayland_DestroyWindow(_THIS, SDL_Wi - wl_surface_destroy(wind->surface); - - SDL_free(wind); -+ } -+ if (data) { - WAYLAND_wl_display_flush(data->display); - } - window->driverdata = NULL; diff --git a/libs/sdl2/patches/110-tests-no-libunwind.patch b/libs/sdl2/patches/110-tests-no-libunwind.patch deleted file mode 100644 index 0d39562f..00000000 --- a/libs/sdl2/patches/110-tests-no-libunwind.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -1642,7 +1642,6 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS - endif() - - endif() -- CheckLibUnwind() - - if(HAVE_DBUS_DBUS_H) - list(APPEND SOURCE_FILES "${SDL2_SOURCE_DIR}/src/core/linux/SDL_dbus.c") diff --git a/libs/sdl2/patches/120-tests-install-resources-to-datadir.patch b/libs/sdl2/patches/120-tests-install-resources-to-datadir.patch deleted file mode 100644 index b8bedaeb..00000000 --- a/libs/sdl2/patches/120-tests-install-resources-to-datadir.patch +++ /dev/null @@ -1,24 +0,0 @@ -From: Daniel Golle -Subject: tests: install resource files to datadir, not libexecdir - -The .bmp / .hex / .txt / .dat / .wav files shipped alongside the test -binaries are not executables; they are read-only data. Installing them -under ${libexecdir}/installed-tests/SDL2/ is both inconsistent with the -GNOME installed-tests convention (data lives under ${datadir}, only -binaries under ${libexecdir}) and trips OpenWrt's package QA which -expects every regular file in /usr/libexec/ to carry the executable -bit. Move them to ${datadir}/installed-tests/SDL2/ alongside the -matching ${exe}.test descriptors, and pin the permissions to 0644 so -the umask of the build host cannot influence the installed mode. - ---- a/test/CMakeLists.txt -+++ b/test/CMakeLists.txt -@@ -531,6 +531,7 @@ if(SDL_INSTALL_TESTS) - endif() - install( - FILES ${RESOURCE_FILES} -- DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/installed-tests/SDL2 -+ PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ -+ DESTINATION ${CMAKE_INSTALL_DATADIR}/installed-tests/SDL2 - ) - endif() diff --git a/libs/sdl3-image/Makefile b/libs/sdl3-image/Makefile new file mode 100644 index 00000000..06d4f5df --- /dev/null +++ b/libs/sdl3-image/Makefile @@ -0,0 +1,52 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=sdl3-image +PKG_VERSION:=3.4.4 +PKG_RELEASE:=1 + +PKG_SOURCE:=SDL3_image-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/libsdl-org/SDL_image/tar.gz/release-$(PKG_VERSION)? +PKG_HASH:=b0c11bbde540e26d1cedf31174349fe6ab67e57658efe22e16e75172859c817d + +PKG_BUILD_DIR:=$(BUILD_DIR)/SDL_image-release-$(PKG_VERSION) + +PKG_MAINTAINER:=Daniel Golle +PKG_LICENSE:=Zlib +PKG_LICENSE_FILES:=LICENSE.txt + +CMAKE_INSTALL:=1 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +CMAKE_BINARY_SUBDIR:=openwrt-build + +CMAKE_OPTIONS += \ + -DSDLIMAGE_SAMPLES=OFF \ + -DSDLIMAGE_TESTS=OFF \ + -DSDLIMAGE_INSTALL=ON \ + -DSDLIMAGE_VENDORED=OFF \ + -DSDLIMAGE_DEPS_SHARED=OFF \ + -DSDLIMAGE_BACKEND_STB=OFF \ + -DSDLIMAGE_PNG=ON \ + -DSDLIMAGE_PNG_LIBPNG=ON \ + -DSDLIMAGE_JPG=ON \ + -DSDLIMAGE_TIF=ON \ + -DSDLIMAGE_WEBP=ON \ + -DSDLIMAGE_AVIF=OFF \ + -DSDLIMAGE_JXL=OFF + +define Package/libsdl3-image + SECTION:=libs + CATEGORY:=Libraries + TITLE:=SDL3 Image + URL:=https://www.libsdl.org/ + DEPENDS:=+libsdl3 +libpng +libjpeg +libtiff +libwebp +endef + +define Package/libsdl3-image/install + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libSDL3_image*.so* $(1)/usr/lib/ +endef + +$(eval $(call BuildPackage,libsdl3-image)) diff --git a/libs/sdl3-mixer/Makefile b/libs/sdl3-mixer/Makefile new file mode 100644 index 00000000..4ba56db3 --- /dev/null +++ b/libs/sdl3-mixer/Makefile @@ -0,0 +1,50 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=sdl3-mixer +PKG_VERSION:=3.2.4 +PKG_RELEASE:=1 + +PKG_SOURCE:=SDL3_mixer-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/libsdl-org/SDL_mixer/tar.gz/release-$(PKG_VERSION)? +PKG_HASH:=f2ea848ccdf2f394cd4973ee0f6c482e04511044695cccfd46bab6dcd7f780aa + +PKG_BUILD_DIR:=$(BUILD_DIR)/SDL_mixer-release-$(PKG_VERSION) + +PKG_MAINTAINER:=Daniel Golle +PKG_LICENSE:=BSD-3c +PKG_LICENSE_FILES:=LICENSE.txt + +CMAKE_INSTALL:=1 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +CMAKE_BINARY_SUBDIR:=openwrt-build + +CMAKE_OPTIONS += \ + -DSDLMIXER_SAMPLES=OFF \ + -DSDLMIXER_VENDORED=OFF \ + -DSDLMIXER_MIDI_FLUIDSYNTH_SHARED=ON + +define Build/InstallDev + $(call Build/InstallDev/cmake,$(1)) +endef + +define Package/libsdl3-mixer + SECTION:=libs + CATEGORY:=Libraries + TITLE:=SDL3 Mixer + URL:=https://www.libsdl.org/ + DEPENDS:=+libsdl3 +libfluidsynth +libopusfile +libwavpack +libxmp +endef + +define Package/libsdl3-mixer/description + SDL3 Audio Mixer library +endef + +define Package/libsdl3-mixer/install + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libSDL3_mixer*.so* $(1)/usr/lib/ +endef + +$(eval $(call BuildPackage,libsdl3-mixer)) diff --git a/libs/sdl3/Makefile b/libs/sdl3/Makefile new file mode 100644 index 00000000..04e8fd92 --- /dev/null +++ b/libs/sdl3/Makefile @@ -0,0 +1,115 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=sdl3 +PKG_VERSION:=3.4.10 +PKG_RELEASE:=1 + +PKG_SOURCE:=SDL3-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://github.com/libsdl-org/SDL/releases/download/release-$(PKG_VERSION)/ +PKG_HASH:=12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785 + +PKG_BUILD_DIR:=$(BUILD_DIR)/SDL3-$(PKG_VERSION) + +PKG_MAINTAINER:=Daniel Golle +PKG_LICENSE:=Zlib +PKG_LICENSE_FILES:=LICENSE.txt + +CMAKE_INSTALL:=1 +PKG_CONFIG_DEPENDS:=CONFIG_PACKAGE_libsdl3-tests +PKG_BUILD_DEPENDS:=wayland/host libwayland wayland-protocols libxkbcommon + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +CMAKE_BINARY_SUBDIR:=openwrt-build + +CMAKE_OPTIONS += \ + -DSDL_SHARED=ON \ + -DSDL_STATIC=OFF \ + -DSDL_TEST_LIBRARY=$(if $(CONFIG_PACKAGE_libsdl3-tests),ON,OFF) \ + -DSDL_TESTS=$(if $(CONFIG_PACKAGE_libsdl3-tests),ON,OFF) \ + -DSDL_INSTALL_TESTS=$(if $(CONFIG_PACKAGE_libsdl3-tests),ON,OFF) \ + -DSDL_ALSA=ON \ + -DSDL_ALSA_SHARED=ON \ + -DSDL_CLOCK_GETTIME=ON \ + -DSDL_DBUS=OFF \ + -DSDL_DISKAUDIO=OFF \ + -DSDL_DUMMYAUDIO=ON \ + -DSDL_DUMMYVIDEO=ON \ + -DSDL_GCC_ATOMICS=ON \ + -DSDL_HIDAPI=ON \ + -DSDL_HIDAPI_JOYSTICK=ON \ + -DSDL_HIDAPI_LIBUSB=ON \ + -DSDL_HIDAPI_LIBUSB_SHARED=ON \ + -DSDL_IBUS=OFF \ + -DSDL_JACK=OFF \ + -DSDL_KMSDRM=ON \ + -DSDL_KMSDRM_SHARED=ON \ + -DSDL_LIBC=ON \ + -DSDL_LIBICONV=OFF \ + -DSDL_LIBUDEV=ON \ + -DSDL_OFFSCREEN=ON \ + -DSDL_OPENGL=ON \ + -DSDL_OPENGLES=ON \ + -DSDL_OSS=OFF \ + -DSDL_PIPEWIRE=OFF \ + -DSDL_PTHREADS=ON \ + -DSDL_PTHREADS_SEM=ON \ + -DSDL_PULSEAUDIO=ON \ + -DSDL_PULSEAUDIO_SHARED=ON \ + -DSDL_RPATH=OFF \ + -DSDL_RPI=OFF \ + -DSDL_SNDIO=OFF \ + -DSDL_SYSTEM_ICONV=OFF \ + -DSDL_VIVANTE=OFF \ + -DSDL_VULKAN=ON \ + -DSDL_WAYLAND=ON \ + -DSDL_WAYLAND_LIBDECOR=OFF \ + -DSDL_WAYLAND_SHARED=ON \ + -DSDL_X11=OFF + +define Package/libsdl3 + SECTION:=libs + CATEGORY:=Libraries + TITLE:=Simple DirectMedia Layer 3 + URL:=https://www.libsdl.org + DEPENDS:=+libstdcpp +alsa-lib +libusb-1.0 +libudev-zero +pulseaudio +libmesa +endef + +define Package/libsdl3/description + Simple DirectMedia Layer is a cross-platform development library designed to + provide low level access to audio, keyboard, mouse, joystick, and graphics + hardware via OpenGL and Direct3D. +endef + +define Build/InstallDev + $(call Build/InstallDev/cmake,$(1)) +endef + +define Package/libsdl3/install + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libSDL3*.so* $(1)/usr/lib +endef + +define Package/libsdl3-tests + SECTION:=utils + CATEGORY:=Utilities + SUBMENU:=Video + TITLE:=SDL3 installed tests + URL:=https://www.libsdl.org + DEPENDS:=+libsdl3 +endef + +define Package/libsdl3-tests/description + Various test programs to validate SDL3 is working correctly, and some benchmarks. +endef + +define Package/libsdl3-tests/install + $(INSTALL_DIR) $(1)/usr/libexec/installed-tests + $(CP) $(PKG_INSTALL_DIR)/usr/libexec/installed-tests/* $(1)/usr/libexec/installed-tests + $(INSTALL_DIR) $(1)/usr/share/installed-tests + $(CP) $(PKG_INSTALL_DIR)/usr/share/installed-tests/* $(1)/usr/share/installed-tests +endef + +$(eval $(call BuildPackage,libsdl3)) +$(eval $(call BuildPackage,libsdl3-tests))