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 */