diff options
author | Simon Howard | 2015-05-30 13:04:56 -0400 |
---|---|---|
committer | Simon Howard | 2015-05-30 13:04:56 -0400 |
commit | 0e90c19ca717298f4f3b83dfd075cdf0c458de32 (patch) | |
tree | 09fae977fd34c03759d5c90e7daf2a087e6e7756 | |
parent | 5082f14944442344030d66f6fbdf86a75a1c1c70 (diff) | |
parent | 1e516e34911d3a95479c303fe26b59f20e618252 (diff) | |
download | chocolate-doom-0e90c19ca717298f4f3b83dfd075cdf0c458de32.tar.gz chocolate-doom-0e90c19ca717298f4f3b83dfd075cdf0c458de32.tar.bz2 chocolate-doom-0e90c19ca717298f4f3b83dfd075cdf0c458de32.zip |
Merge pull request #545 from khokh2001/opl3_mode
opl: Add OPL3 mode.
The DMX library had limited support for the features of the OPL3 chip,
enabled by setting the DMXOPTIONS variable. This reproduces the OPL3
support in Chocolate Doom's OPL playback and emulation layer.
Huge thanks to Alexey Khokholov for researching and developing this.
This fixes #470.
-rw-r--r-- | opl/opl.c | 82 | ||||
-rw-r--r-- | opl/opl.h | 6 | ||||
-rw-r--r-- | opl/opl_sdl.c | 43 | ||||
-rw-r--r-- | src/i_oplmusic.c | 131 | ||||
-rw-r--r-- | src/i_sound.c | 2 | ||||
-rw-r--r-- | src/m_config.c | 5 | ||||
-rw-r--r-- | src/setup/sound.c | 53 |
7 files changed, 258 insertions, 64 deletions
@@ -67,6 +67,8 @@ unsigned int opl_sample_rate = 22050; static int InitDriver(opl_driver_t *_driver, unsigned int port_base) { + int result1, result2; + // Initialize the driver. if (!_driver->init_func(port_base)) @@ -82,7 +84,9 @@ static int InitDriver(opl_driver_t *_driver, unsigned int port_base) driver = _driver; init_stage_reg_writes = 1; - if (!OPL_Detect() || !OPL_Detect()) + result1 = OPL_Detect(); + result2 = OPL_Detect(); + if (!result1 || !result2) { printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name); _driver->shutdown_func(); @@ -90,15 +94,11 @@ static int InitDriver(opl_driver_t *_driver, unsigned int port_base) return 0; } - // Initialize all registers. - - OPL_InitRegisters(); - init_stage_reg_writes = 0; printf("OPL_Init: Using driver '%s'.\n", driver->name); - return 1; + return result2; } // Find a driver automatically by trying each in the list. @@ -106,12 +106,14 @@ static int InitDriver(opl_driver_t *_driver, unsigned int port_base) static int AutoSelectDriver(unsigned int port_base) { int i; + int result; for (i=0; drivers[i] != NULL; ++i) { - if (InitDriver(drivers[i], port_base)) + result = InitDriver(drivers[i], port_base); + if (result) { - return 1; + return result; } } @@ -127,6 +129,7 @@ int OPL_Init(unsigned int port_base) { char *driver_name; int i; + int result; driver_name = getenv("OPL_DRIVER"); @@ -138,9 +141,10 @@ int OPL_Init(unsigned int port_base) { if (!strcmp(driver_name, drivers[i]->name)) { - if (InitDriver(drivers[i], port_base)) + result = InitDriver(drivers[i], port_base); + if (result) { - return 1; + return result; } else { @@ -233,7 +237,14 @@ void OPL_WriteRegister(int reg, int value) { int i; - OPL_WritePort(OPL_REGISTER_PORT, reg); + 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 @@ -306,13 +317,22 @@ int OPL_Detect(void) // Enable interrupts: OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); - return (result1 & 0xe0) == 0x00 - && (result2 & 0xe0) == 0xc0; + if ((result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0) + { + result1 = OPL_ReadPort(OPL_REGISTER_PORT); + result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3); + if (result1 == 0x00) + { + return 2; + } + return 1; + } + return 0; } // Initialize registers on startup -void OPL_InitRegisters(void) +void OPL_InitRegisters(int opl3) { int r; @@ -349,8 +369,42 @@ void OPL_InitRegisters(void) // "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); + } } // @@ -26,7 +26,8 @@ typedef void (*opl_callback_t)(void *data); typedef enum { OPL_REGISTER_PORT = 0, - OPL_DATA_PORT = 1 + OPL_DATA_PORT = 1, + OPL_REGISTER_PORT_OPL3 = 2 } opl_port_t; #define OPL_NUM_OPERATORS 21 @@ -37,6 +38,7 @@ typedef enum #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): @@ -101,7 +103,7 @@ int OPL_Detect(void); // Initialize all registers, performed on startup. -void OPL_InitRegisters(void); +void OPL_InitRegisters(int opl3); // // Timer callback functions. diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 8834ee07..1334ac07 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -71,6 +71,7 @@ static uint64_t pause_offset; // OPL software emulator structure. static Chip opl_chip; +static int opl_opl3mode; // Temporary mixing buffer used by the mixing callback. @@ -164,15 +165,30 @@ static void FillBuffer(int16_t *buffer, unsigned int nsamples) assert(nsamples < mixing_freq); - Chip__GenerateBlock2(&opl_chip, nsamples, mix_buffer); + if (opl_opl3mode) + { + Chip__GenerateBlock3(&opl_chip, nsamples, mix_buffer); - // Mix into the destination buffer, doubling up into stereo. + // Mix into the destination buffer, doubling up into stereo. - for (i=0; i<nsamples; ++i) - { - buffer[i * 2] = (int16_t) mix_buffer[i]; - buffer[i * 2 + 1] = (int16_t) mix_buffer[i]; + for (i=0; i<nsamples; ++i) + { + buffer[i * 2] = (int16_t) mix_buffer[i * 2]; + buffer[i * 2 + 1] = (int16_t) mix_buffer[i * 2 + 1]; + } } + else + { + Chip__GenerateBlock2(&opl_chip, nsamples, mix_buffer); + + // Mix into the destination buffer, doubling up into stereo. + + for (i=0; i<nsamples; ++i) + { + buffer[i * 2] = (int16_t) mix_buffer[i]; + buffer[i * 2 + 1] = (int16_t) mix_buffer[i]; + } + } } // Callback function to fill a new sound buffer: @@ -351,13 +367,14 @@ static int OPL_SDL_Init(unsigned int port_base) // Mix buffer: - mix_buffer = malloc(mixing_freq * sizeof(uint32_t)); + mix_buffer = malloc(mixing_freq * sizeof(uint32_t) * 2); // Create the emulator structure: DBOPL_InitTables(); Chip__Chip(&opl_chip); Chip__Setup(&opl_chip, mixing_freq); + opl_opl3mode = 0; callback_mutex = SDL_CreateMutex(); callback_queue_mutex = SDL_CreateMutex(); @@ -372,6 +389,11 @@ 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 @@ -439,6 +461,9 @@ static void WriteRegister(unsigned int reg_num, unsigned int value) break; + case OPL_REG_NEW: + opl_opl3mode = value & 0x01; + default: Chip__WriteReg(&opl_chip, reg_num, value); break; @@ -451,6 +476,10 @@ static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value) { register_num = value; } + else if (port == OPL_REGISTER_PORT_OPL3) + { + register_num = value | 0x100; + } else if (port == OPL_DATA_PORT) { WriteRegister(register_num, value); diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 7715465e..cbdbc329 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -86,6 +86,10 @@ typedef struct int volume; + // Pan + + int pan; + // Pitch bend value: int bend; @@ -115,6 +119,9 @@ struct opl_voice_s // The operators used by this voice: int op1, op2; + // Array used by voice: + int array; + // Currently-loaded instrument data genmidi_instr_t *current_instr; @@ -143,6 +150,9 @@ struct opl_voice_s // The current volume (register value) that has been set for this channel. unsigned int reg_volume; + // Pan. + unsigned int reg_pan; + // Priority. unsigned int priority; @@ -312,10 +322,12 @@ static char (*percussion_names)[32]; // Voices: -static opl_voice_t voices[OPL_NUM_VOICES]; +static opl_voice_t voices[OPL_NUM_VOICES * 2]; static opl_voice_t *voice_free_list; static opl_voice_t *voice_alloced_list; static int voice_alloced_num; +static int opl_opl3mode; +static int num_opl_voices; // Track data for playing tracks: @@ -338,6 +350,7 @@ static unsigned int last_perc_count; // adlib chip. int opl_io_port = 0x388; +int opl_type = 0; // Load instrument table from GENMIDI lump: @@ -519,15 +532,15 @@ static void SetVoiceInstrument(opl_voice_t *voice, // is set in SetVoiceVolume (below). If we are not using // modulating mode, we must set both to minimum volume. - LoadOperatorData(voice->op2, &data->carrier, true); - LoadOperatorData(voice->op1, &data->modulator, !modulating); + LoadOperatorData(voice->op2 | voice->array, &data->carrier, true); + LoadOperatorData(voice->op1 | voice->array, &data->modulator, !modulating); // 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, - data->feedback | 0x30); + OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array, + data->feedback | voice->reg_pan); // Hack to force a volume update. @@ -568,7 +581,8 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) { voice->reg_volume = car_volume | (opl_voice->carrier.scale & 0xc0); - OPL_WriteRegister(OPL_REGS_LEVEL + voice->op2, voice->reg_volume); + OPL_WriteRegister((OPL_REGS_LEVEL + voice->op2) | voice->array, + voice->reg_volume); // If we are using non-modulated feedback mode, we must set the // volume for both voices. @@ -581,13 +595,24 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) { mod_volume = car_volume; } - OPL_WriteRegister(OPL_REGS_LEVEL + voice->op1, + 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) @@ -600,11 +625,12 @@ static void InitVoices(void) // Initialize each voice. - for (i = 0; i < OPL_NUM_VOICES; ++i) + for (i = 0; i < num_opl_voices; ++i) { - voices[i].index = i; - voices[i].op1 = voice_operators[0][i]; - voices[i].op2 = voice_operators[1][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. @@ -625,7 +651,7 @@ static void I_OPL_SetMusicVolume(int volume) // Update the volume of all voices. - for (i = 0; i < OPL_NUM_VOICES; ++i) + for (i = 0; i < num_opl_voices; ++i) { if (voices[i].channel != NULL) { @@ -636,7 +662,8 @@ static void I_OPL_SetMusicVolume(int volume) static void VoiceKeyOff(opl_voice_t *voice) { - OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, voice->freq >> 8); + OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array, + voice->freq >> 8); } static opl_channel_data_t *TrackChannelForEvent(opl_track_data_t *track, @@ -869,8 +896,10 @@ static void UpdateVoiceFrequency(opl_voice_t *voice) if (voice->freq != freq) { - OPL_WriteRegister(OPL_REGS_FREQ_1 + voice->index, freq & 0xff); - OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, (freq >> 8) | 0x20); + 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; } @@ -913,6 +942,8 @@ static void VoiceKeyOn(opl_channel_data_t *channel, voice->note = note; } + voice->reg_pan = channel->pan; + // Program the voice with the instrument data: SetVoiceInstrument(voice, instrument, instrument_voice); @@ -980,11 +1011,11 @@ static void KeyOnEvent(opl_track_data_t *track, midi_event_t *event) if (opl_drv_ver == opl_v_old) { - if (voice_alloced_num == OPL_NUM_VOICES) + if (voice_alloced_num == num_opl_voices) { ReplaceExistingVoiceOld(channel); } - if (voice_alloced_num == OPL_NUM_VOICES - 1 && double_voice) + if (voice_alloced_num == num_opl_voices - 1 && double_voice) { ReplaceExistingVoiceOld(channel); } @@ -1041,7 +1072,7 @@ static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume) // Update all voices that this channel is using. - for (i = 0; i < OPL_NUM_VOICES; ++i) + for (i = 0; i < num_opl_voices; ++i) { if (voices[i].channel == channel) { @@ -1050,6 +1081,39 @@ static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume) } } +static void SetChannelPan(opl_channel_data_t *channel, unsigned int pan) +{ + unsigned int reg_pan; + unsigned int i; + + 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) { @@ -1108,6 +1172,10 @@ static void ControllerEvent(opl_track_data_t *track, midi_event_t *event) SetChannelVolume(channel, param); break; + case MIDI_CONTROLLER_PAN: + SetChannelPan(channel, param); + break; + case MIDI_CONTROLLER_ALL_NOTES_OFF: AllNotesOff(channel, param); break; @@ -1135,7 +1203,7 @@ static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event) // Update all voices for this channel. - for (i = 0; i < OPL_NUM_VOICES; ++i) + for (i = 0; i < num_opl_voices; ++i) { if (voices[i].channel == channel) { @@ -1323,6 +1391,7 @@ static void InitChannel(opl_track_data_t *track, opl_channel_data_t *channel) channel->instrument = &main_instrs[0]; channel->volume = 127; + channel->pan = 0x30; channel->bend = 0; } @@ -1397,7 +1466,7 @@ static void I_OPL_PauseSong(void) // Turn off all main instrument voices (not percussion). // This is what Vanilla does. - for (i = 0; i < OPL_NUM_VOICES; ++i) + for (i = 0; i < num_opl_voices; ++i) { if (voices[i].channel != NULL && voices[i].current_instr < percussion_instrs) @@ -1434,7 +1503,7 @@ static void I_OPL_StopSong(void) // Free all voices. - for (i = 0; i < OPL_NUM_VOICES; ++i) + for (i = 0; i < num_opl_voices; ++i) { if (voices[i].channel != NULL) { @@ -1581,14 +1650,32 @@ static void I_OPL_ShutdownMusic(void) static boolean I_OPL_InitMusic(void) { + int opl_chip_type; + OPL_SetSampleRate(snd_samplerate); - if (!OPL_Init(opl_io_port)) + opl_chip_type = OPL_Init(opl_io_port); + if (!opl_chip_type) { printf("Dude. The Adlib isn't responding.\n"); return false; } + if (opl_chip_type == 2 && opl_type) + { + opl_opl3mode = 1; + num_opl_voices = OPL_NUM_VOICES * 2; + } + else + { + opl_opl3mode = 0; + num_opl_voices = OPL_NUM_VOICES; + } + + // Initialize all registers. + + OPL_InitRegisters(opl_opl3mode); + // Load instruments from GENMIDI lump: if (!LoadInstrumentTable()) diff --git a/src/i_sound.c b/src/i_sound.c index 73442cbd..03a9facc 100644 --- a/src/i_sound.c +++ b/src/i_sound.c @@ -68,6 +68,7 @@ extern music_module_t music_opl_module; extern opl_driver_ver_t opl_drv_ver; extern int opl_io_port; +extern int opl_type; // For native music module: @@ -446,6 +447,7 @@ void I_BindSoundVariables(void) M_BindIntVariable("snd_samplerate", &snd_samplerate); M_BindIntVariable("snd_cachesize", &snd_cachesize); M_BindIntVariable("opl_io_port", &opl_io_port); + M_BindIntVariable("opl_type", &opl_type); M_BindStringVariable("timidity_cfg_path", &timidity_cfg_path); M_BindStringVariable("gus_patch_path", &gus_patch_path); diff --git a/src/m_config.c b/src/m_config.c index ad539a7f..3ec0b633 100644 --- a/src/m_config.c +++ b/src/m_config.c @@ -824,6 +824,11 @@ static default_t extra_defaults_list[] = CONFIG_VARIABLE_INT_HEX(opl_io_port), //! + // OPL chip type. + // + CONFIG_VARIABLE_INT(opl_type), + + //! // @game doom heretic strife // // If non-zero, the ENDOOM text screen is displayed when exiting the diff --git a/src/setup/sound.c b/src/setup/sound.c index 280a6bc2..092a5dd8 100644 --- a/src/setup/sound.c +++ b/src/setup/sound.c @@ -61,6 +61,12 @@ static char *musicmode_strings[] = "CD audio" }; +static char *opltype_strings[] = +{ + "OPL2", + "OPL3" +}; + static char *cfg_extension[] = { "cfg", NULL }; // Config file variables: @@ -84,6 +90,7 @@ static float libsamplerate_scale = 0.65; static char *timidity_cfg_path = NULL; static char *gus_patch_path = NULL; static int gus_ram_kb = 1024; +static int opl_type = 0; // DOS specific variables: these are unused but should be maintained // so that the config file can be shared between chocolate @@ -139,29 +146,36 @@ static void UpdateExtraTable(TXT_UNCAST_ARG(widget), { TXT_CAST_ARG(txt_table_t, extra_table); - // Rebuild the GUS table. Start by emptying it, then only add the - // GUS control widget if we are in GUS music mode. - - TXT_ClearTable(extra_table); - - if (snd_musicmode == MUSICMODE_GUS) + switch (snd_musicmode) { + case MUSICMODE_OPL: + TXT_InitTable(extra_table, 2); + TXT_SetColumnWidths(extra_table, 19, 4); TXT_AddWidgets(extra_table, - TXT_NewLabel("GUS patch path:"), - TXT_NewFileSelector(&gus_patch_path, 30, - "Select path to GUS patches", - TXT_DIRECTORY), - NULL); - } + TXT_NewLabel("OPL type"), + TXT_NewDropdownList(&opl_type, opltype_strings, 2), + NULL); + break; - if (snd_musicmode == MUSICMODE_NATIVE) - { + case MUSICMODE_GUS: + TXT_InitTable(extra_table, 1); TXT_AddWidgets(extra_table, - TXT_NewLabel("Timidity configuration file:"), - TXT_NewFileSelector(&timidity_cfg_path, 30, - "Select Timidity config file", - cfg_extension), - NULL); + TXT_NewLabel("GUS patch path:"), + TXT_NewFileSelector(&gus_patch_path, 30, + "Select path to GUS patches", + TXT_DIRECTORY), + NULL); + break; + + case MUSICMODE_NATIVE: + TXT_InitTable(extra_table, 1); + TXT_AddWidgets(extra_table, + TXT_NewLabel("Timidity configuration file:"), + TXT_NewFileSelector(&timidity_cfg_path, 30, + "Select Timidity config file", + cfg_extension), + NULL); + break; } } @@ -324,6 +338,7 @@ void BindSoundVariables(void) M_BindIntVariable("snd_cachesize", &snd_cachesize); M_BindIntVariable("opl_io_port", &opl_io_port); + M_BindIntVariable("opl_type", &opl_type); if (gamemission == strife) { |