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 * current_music_volume) / 127] + 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->reg_volume) { voice->reg_volume = car_volume | (opl_voice->carrier.scale & 0xc0); 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. if ((opl_voice->feedback & 0x01) != 0 && opl_voice->modulator.level != 0x3f) { mod_volume = 0x3f - opl_voice->modulator.level; if (mod_volume >= car_volume) { mod_volume = car_volume; } OPL_WriteRegister((OPL_REGS_LEVEL + voice->op1) | voice->array, mod_volume | (opl_voice->modulator.scale & 0xc0)); } } }
static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) { genmidi_voice_t *opl_voice; unsigned int full_volume; unsigned int op_volume; unsigned int reg_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. full_volume = (volume_mapping_table[voice->note_volume] * volume_mapping_table[voice->channel->volume] * volume_mapping_table[current_music_volume]) / (127 * 127); // The volume of each instrument can be controlled via GENMIDI: op_volume = 0x3f - opl_voice->carrier.level; // The volume value to use in the register: reg_volume = (op_volume * full_volume) / 128; reg_volume = (0x3f - reg_volume) | opl_voice->carrier.scale; // Update the volume register(s) if necessary. if (reg_volume != voice->reg_volume) { voice->reg_volume = reg_volume; OPL_WriteRegister(OPL_REGS_LEVEL + voice->op2, reg_volume); // If we are using non-modulated feedback mode, we must set the // volume for both voices. // Note that the same register volume value is written for // both voices, always calculated from the carrier's level // value. if ((opl_voice->feedback & 0x01) != 0) { OPL_WriteRegister(OPL_REGS_LEVEL + voice->op1, reg_volume); } } }
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); }
static void LoadOperatorData(int operator, genmidi_op_t *data, boolean max_level) { 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 & 0xc0) | (data->level & 0x3f); if (max_level) { level |= 0x3f; } 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); }
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); 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) | voice->array, data->feedback | voice->reg_pan); // Hack to force a volume update. voice->reg_volume = 999; // Calculate voice priority. voice->priority = 0x0f - (data->carrier.attack >> 4) + 0x0f - (data->carrier.sustain & 0x0f); }
void OPL_InitRegisters(void) { 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); // Keyboard split point on (?) OPL_WriteRegister(OPL_REG_FM_MODE, 0x40); }
int 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); return (result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0; }