mirror of
https://github.com/ZDoom/Raze.git
synced 2024-11-15 00:42:08 +00:00
- removed the OPL synth because parts of it are GPLv3 and even under the best circumstances not compatible with the license mix here.
This commit is contained in:
parent
4571ba74c2
commit
e43d2c10c0
27 changed files with 4 additions and 9426 deletions
|
@ -381,7 +381,6 @@ set( LZMA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/lzma/C" )
|
|||
set( GDTOA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/gdtoa" )
|
||||
set( ZMUSIC_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zmusic" )
|
||||
set( TIMIDITYPP_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/timidityplus" )
|
||||
set( OPLSYNTH_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/oplsynth" )
|
||||
set( GME_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/game-music-emu" )
|
||||
|
||||
if( NOT CMAKE_CROSSCOMPILING )
|
||||
|
@ -403,7 +402,6 @@ endif()
|
|||
add_subdirectory( libraries/lzma )
|
||||
add_subdirectory( libraries/dumb )
|
||||
add_subdirectory( libraries/game-music-emu)
|
||||
add_subdirectory( libraries/oplsynth )
|
||||
add_subdirectory( libraries/timidityplus )
|
||||
add_subdirectory( libraries/zmusic )
|
||||
add_subdirectory( tools )
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
cmake_minimum_required( VERSION 2.8.7 )
|
||||
|
||||
use_fast_math()
|
||||
require_stricmp()
|
||||
require_strnicmp()
|
||||
|
||||
if( ZD_CMAKE_COMPILER_IS_GNUC_COMPATIBLE )
|
||||
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter -fomit-frame-pointer" )
|
||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" )
|
||||
endif()
|
||||
|
||||
include_directories( oplsynth )
|
||||
|
||||
file( GLOB HEADER_FILES
|
||||
oplsynth/*.h
|
||||
)
|
||||
add_library( oplsynth STATIC
|
||||
fmopl.cpp
|
||||
musicblock.cpp
|
||||
nukedopl3.cpp
|
||||
opl_mus_player.cpp
|
||||
OPL3.cpp
|
||||
oplio.cpp
|
||||
dosbox/opl.cpp
|
||||
)
|
||||
target_link_libraries( oplsynth )
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,230 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2002-2011 The DOSBox Team
|
||||
* OPL2/OPL3 emulation library
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Originally based on ADLIBEMU.C, an AdLib/OPL2 emulation library by Ken Silverman
|
||||
* Copyright (C) 1998-2001 Ken Silverman
|
||||
* Ken Silverman's official web site: "http://www.advsys.net/ken"
|
||||
*/
|
||||
|
||||
|
||||
#define fltype double
|
||||
|
||||
/*
|
||||
define Bits, Bitu, Bit32s, Bit32u, Bit16s, Bit16u, Bit8s, Bit8u here
|
||||
*/
|
||||
/*
|
||||
#include <stdint.h>
|
||||
typedef uintptr_t Bitu;
|
||||
typedef intptr_t Bits;
|
||||
typedef uint32_t Bit32u;
|
||||
typedef int32_t Bit32s;
|
||||
typedef uint16_t Bit16u;
|
||||
typedef int16_t Bit16s;
|
||||
typedef uint8_t Bit8u;
|
||||
typedef int8_t Bit8s;
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
define attribution that inlines/forces inlining of a function (optional)
|
||||
*/
|
||||
#define OPL_INLINE inline
|
||||
|
||||
|
||||
#undef NUM_CHANNELS
|
||||
#if defined(OPLTYPE_IS_OPL3)
|
||||
#define NUM_CHANNELS 18
|
||||
#else
|
||||
#define NUM_CHANNELS 9
|
||||
#endif
|
||||
|
||||
#define MAXOPERATORS (NUM_CHANNELS*2)
|
||||
|
||||
|
||||
#define FL05 ((fltype)0.5)
|
||||
#define FL2 ((fltype)2.0)
|
||||
#define PI ((fltype)3.1415926535897932384626433832795)
|
||||
|
||||
|
||||
#define FIXEDPT 0x10000 // fixed-point calculations using 16+16
|
||||
#define FIXEDPT_LFO 0x1000000 // fixed-point calculations using 8+24
|
||||
|
||||
#define WAVEPREC 1024 // waveform precision (10 bits)
|
||||
|
||||
#define INTFREQU ((fltype)(14318180.0 / 288.0)) // clocking of the chip
|
||||
|
||||
|
||||
#define OF_TYPE_ATT 0
|
||||
#define OF_TYPE_DEC 1
|
||||
#define OF_TYPE_REL 2
|
||||
#define OF_TYPE_SUS 3
|
||||
#define OF_TYPE_SUS_NOKEEP 4
|
||||
#define OF_TYPE_OFF 5
|
||||
|
||||
#define ARC_CONTROL 0x00
|
||||
#define ARC_TVS_KSR_MUL 0x20
|
||||
#define ARC_KSL_OUTLEV 0x40
|
||||
#define ARC_ATTR_DECR 0x60
|
||||
#define ARC_SUSL_RELR 0x80
|
||||
#define ARC_FREQ_NUM 0xa0
|
||||
#define ARC_KON_BNUM 0xb0
|
||||
#define ARC_PERC_MODE 0xbd
|
||||
#define ARC_FEEDBACK 0xc0
|
||||
#define ARC_WAVE_SEL 0xe0
|
||||
|
||||
#define ARC_SECONDSET 0x100 // second operator set for OPL3
|
||||
|
||||
|
||||
#define OP_ACT_OFF 0x00
|
||||
#define OP_ACT_NORMAL 0x01 // regular channel activated (bitmasked)
|
||||
#define OP_ACT_PERC 0x02 // percussion channel activated (bitmasked)
|
||||
|
||||
#define BLOCKBUF_SIZE 512
|
||||
|
||||
|
||||
// vibrato constants
|
||||
#define VIBTAB_SIZE 8
|
||||
#define VIBFAC 70/50000 // no braces, integer mul/div
|
||||
|
||||
// tremolo constants and table
|
||||
#define TREMTAB_SIZE 53
|
||||
#define TREM_FREQ ((fltype)(3.7)) // tremolo at 3.7hz
|
||||
|
||||
|
||||
/* operator struct definition
|
||||
For OPL2 all 9 channels consist of two operators each, carrier and modulator.
|
||||
Channel x has operators x as modulator and operators (9+x) as carrier.
|
||||
For OPL3 all 18 channels consist either of two operators (2op mode) or four
|
||||
operators (4op mode) which is determined through register4 of the second
|
||||
adlib register set.
|
||||
Only the channels 0,1,2 (first set) and 9,10,11 (second set) can act as
|
||||
4op channels. The two additional operators for a channel y come from the
|
||||
2op channel y+3 so the operatorss y, (9+y), y+3, (9+y)+3 make up a 4op
|
||||
channel.
|
||||
*/
|
||||
typedef struct operator_struct {
|
||||
Bit32s cval, lastcval; // current output/last output (used for feedback)
|
||||
Bit32u tcount, wfpos, tinc; // time (position in waveform) and time increment
|
||||
fltype amp, step_amp; // and amplification (envelope)
|
||||
fltype vol; // volume
|
||||
fltype sustain_level; // sustain level
|
||||
Bit32s mfbi; // feedback amount
|
||||
fltype a0, a1, a2, a3; // attack rate function coefficients
|
||||
fltype decaymul, releasemul; // decay/release rate functions
|
||||
Bit32u op_state; // current state of operator (attack/decay/sustain/release/off)
|
||||
Bit32u toff;
|
||||
Bit32s freq_high; // highest three bits of the frequency, used for vibrato calculations
|
||||
Bit16s* cur_wform; // start of selected waveform
|
||||
Bit32u cur_wmask; // mask for selected waveform
|
||||
Bit32u act_state; // activity state (regular, percussion)
|
||||
bool sus_keep; // keep sustain level when decay finished
|
||||
bool vibrato,tremolo; // vibrato/tremolo enable bits
|
||||
|
||||
// variables used to provide non-continuous envelopes
|
||||
Bit32u generator_pos; // for non-standard sample rates we need to determine how many samples have passed
|
||||
Bits cur_env_step; // current (standardized) sample position
|
||||
Bits env_step_a,env_step_d,env_step_r; // number of std samples of one step (for attack/decay/release mode)
|
||||
Bit8u step_skip_pos_a; // position of 8-cyclic step skipping (always 2^x to check against mask)
|
||||
Bits env_step_skip_a; // bitmask that determines if a step is skipped (respective bit is zero then)
|
||||
|
||||
#if defined(OPLTYPE_IS_OPL3)
|
||||
bool is_4op,is_4op_attached; // base of a 4op channel/part of a 4op channel
|
||||
float left_pan,right_pan; // opl3 stereo panning amount
|
||||
#endif
|
||||
} op_type;
|
||||
|
||||
// per-chip variables
|
||||
class DBOPL : public OPLEmul
|
||||
{
|
||||
private:
|
||||
op_type op[MAXOPERATORS];
|
||||
|
||||
Bits int_samplerate;
|
||||
|
||||
Bit8u status;
|
||||
Bit32u opl_index;
|
||||
#if defined(OPLTYPE_IS_OPL3)
|
||||
Bit8u adlibreg[512]; // adlib register set (including second set)
|
||||
Bit8u wave_sel[44]; // waveform selection
|
||||
#else
|
||||
Bit8u adlibreg[256]; // adlib register set
|
||||
Bit8u wave_sel[22]; // waveform selection
|
||||
#endif
|
||||
|
||||
fltype recipsamp; // inverse of sampling rate
|
||||
|
||||
// vibrato/tremolo tables
|
||||
Bit32s vib_table[VIBTAB_SIZE];
|
||||
Bit32s trem_table[TREMTAB_SIZE*2];
|
||||
|
||||
Bit32s vibval_const[BLOCKBUF_SIZE];
|
||||
Bit32s tremval_const[BLOCKBUF_SIZE];
|
||||
|
||||
// vibrato value tables (used per-operator)
|
||||
Bit32s vibval_var1[BLOCKBUF_SIZE];
|
||||
Bit32s vibval_var2[BLOCKBUF_SIZE];
|
||||
|
||||
// vibrato/trmolo value table pointers
|
||||
Bit32s *vibval1, *vibval2, *vibval3, *vibval4;
|
||||
Bit32s *tremval1, *tremval2, *tremval3, *tremval4;
|
||||
|
||||
// calculated frequency multiplication values (depend on sampling rate)
|
||||
fltype frqmul[16];
|
||||
|
||||
|
||||
// vibrato/tremolo increment/counter
|
||||
Bit32u vibtab_pos;
|
||||
Bit32u vibtab_add;
|
||||
Bit32u tremtab_pos;
|
||||
Bit32u tremtab_add;
|
||||
|
||||
// Enable full MIDI panning; disable OPL3 panning
|
||||
bool FullPan;
|
||||
|
||||
|
||||
// enable an operator
|
||||
void enable_operator(Bitu regbase, op_type* op_pt, Bit32u act_type);
|
||||
|
||||
void disable_operator(op_type* op_pt, Bit32u act_type);
|
||||
|
||||
// functions to change parameters of an operator
|
||||
void change_frequency(Bitu chanbase, Bitu regbase, op_type* op_pt);
|
||||
|
||||
void change_attackrate(Bitu regbase, op_type* op_pt);
|
||||
void change_decayrate(Bitu regbase, op_type* op_pt);
|
||||
void change_releaserate(Bitu regbase, op_type* op_pt);
|
||||
void change_sustainlevel(Bitu regbase, op_type* op_pt);
|
||||
void change_waveform(Bitu regbase, op_type* op_pt);
|
||||
void change_keepsustain(Bitu regbase, op_type* op_pt);
|
||||
void change_vibrato(Bitu regbase, op_type* op_pt);
|
||||
void change_feedback(Bitu chanbase, op_type* op_pt);
|
||||
|
||||
// general functions
|
||||
public:
|
||||
void Reset();
|
||||
void Update(float* sndptr, int numsamples);
|
||||
void WriteReg(int idx, int val);
|
||||
void SetPanning(int c, float left, float right);
|
||||
|
||||
DBOPL(bool stereo);
|
||||
};
|
||||
|
||||
static Bit32u generator_add; // should be a chip parameter
|
File diff suppressed because it is too large
Load diff
|
@ -1,498 +0,0 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// Copyright 2002-2016 Randy Heit
|
||||
// Copyright 2005-2014 Simon Howard
|
||||
// Copyright 2017 Christoph Oelckers
|
||||
//
|
||||
// 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 3 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// This is mostly a reimplementation of the interface provided by
|
||||
// MusLib based on Chocolate-Doom's OPL player, although the
|
||||
// interface has been cleaned up a bit to be more consistent and readable.
|
||||
//
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "musicblock.h"
|
||||
|
||||
musicBlock::musicBlock ()
|
||||
{
|
||||
memset (this, 0, sizeof(*this));
|
||||
for(auto &oplchannel : oplchannels) oplchannel.Panning = 64; // default to center panning.
|
||||
for(auto &voice : voices) voice.index = ~0u; // mark all free.
|
||||
}
|
||||
|
||||
musicBlock::~musicBlock ()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
int musicBlock::releaseVoice(uint32_t slot, uint32_t killed)
|
||||
{
|
||||
struct OPLVoice *ch = &voices[slot];
|
||||
io->WriteFrequency(slot, ch->note, ch->pitch, 0);
|
||||
ch->index = ~0u;
|
||||
ch->sustained = false;
|
||||
if (!killed) ch->timestamp = ++timeCounter;
|
||||
if (killed) io->MuteChannel(slot);
|
||||
return slot;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
int musicBlock::findFreeVoice()
|
||||
{
|
||||
// We want to prefer the least recently freed voice, as more recently
|
||||
// freed voices can still play a tone from their release state.
|
||||
// Sustained voices are replaced when there are no free voices.
|
||||
uint32_t min_value = ~0u;
|
||||
int result = -1;
|
||||
for (uint32_t i = 0; i < io->NumChannels; ++i)
|
||||
{
|
||||
uint32_t voice_value = voices[i].timestamp + (voices[i].sustained ? (1 << 31) : 0);
|
||||
if ((voices[i].index == ~0u || voices[i].sustained) && (voice_value < min_value))
|
||||
{
|
||||
min_value = voice_value;
|
||||
result = i;
|
||||
}
|
||||
}
|
||||
if (result >= 0)
|
||||
{
|
||||
releaseVoice(result, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
int musicBlock::replaceExistingVoice()
|
||||
{
|
||||
// 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.
|
||||
int result = 0;
|
||||
|
||||
for (uint32_t i = 0; i < io->NumChannels; ++i)
|
||||
{
|
||||
if (voices[i].current_instr_voice == &voices[i].current_instr->voices[1] ||
|
||||
voices[i].index >= voices[result].index)
|
||||
{
|
||||
result = i;
|
||||
}
|
||||
}
|
||||
|
||||
releaseVoice(result, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::voiceKeyOn(uint32_t slot, uint32_t channo, GenMidiInstrument *instrument, uint32_t instrument_voice, uint32_t key, uint32_t volume)
|
||||
{
|
||||
struct OPLVoice *voice = &voices[slot];
|
||||
auto &channel = oplchannels[channo];
|
||||
GenMidiVoice *gmvoice;
|
||||
|
||||
voice->index = channo;
|
||||
voice->key = key;
|
||||
|
||||
// Program the voice with the instrument data:
|
||||
voice->current_instr = instrument;
|
||||
gmvoice = voice->current_instr_voice = &instrument->voices[instrument_voice];
|
||||
io->WriteInstrument(slot,gmvoice, channel.Vibrato);
|
||||
io->WritePan(slot, gmvoice, channel.Panning);
|
||||
|
||||
// Set the volume level.
|
||||
voice->note_volume = volume;
|
||||
io->WriteVolume(slot, gmvoice, channel.Volume, channel.Expression, volume);
|
||||
|
||||
// Write the frequency value to turn the note on.
|
||||
|
||||
// Work out the note to use. This is normally the same as
|
||||
// the key, unless it is a fixed pitch instrument.
|
||||
int note;
|
||||
if (instrument->flags & GENMIDI_FLAG_FIXED) note = instrument->fixed_note;
|
||||
else if (channo == CHAN_PERCUSSION) note = 60;
|
||||
else note = key;
|
||||
|
||||
// If this is the second voice of a double voice instrument, the
|
||||
// frequency index can be adjusted by the fine tuning field.
|
||||
voice->fine_tuning = (instrument_voice != 0) ? (voice->current_instr->fine_tuning / 2) - 64 : 0;
|
||||
voice->pitch = voice->fine_tuning + channel.Pitch;
|
||||
|
||||
if (!(instrument->flags & GENMIDI_FLAG_FIXED) && channo != CHAN_PERCUSSION)
|
||||
{
|
||||
note += gmvoice->base_note_offset;
|
||||
}
|
||||
|
||||
// Avoid possible overflow due to base note offset:
|
||||
|
||||
while (note < 0)
|
||||
{
|
||||
note += 12;
|
||||
}
|
||||
|
||||
while (note > HIGHEST_NOTE)
|
||||
{
|
||||
note -= 12;
|
||||
}
|
||||
voice->note = note;
|
||||
io->WriteFrequency(slot, note, voice->pitch, 1);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
bool opl_singlevoice;
|
||||
|
||||
void musicBlock::noteOn(uint32_t channel, uint8_t key, int volume)
|
||||
{
|
||||
if (volume <= 0)
|
||||
{
|
||||
noteOff(channel, key);
|
||||
return;
|
||||
}
|
||||
GenMidiInstrument *instrument;
|
||||
|
||||
// Percussion channel is treated differently.
|
||||
if (channel == CHAN_PERCUSSION)
|
||||
{
|
||||
if (key < GENMIDI_FIST_PERCUSSION || key >= GENMIDI_FIST_PERCUSSION + GENMIDI_NUM_PERCUSSION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
instrument = &OPLinstruments[key + (GENMIDI_NUM_INSTRS - GENMIDI_FIST_PERCUSSION)];
|
||||
}
|
||||
else
|
||||
{
|
||||
auto inst = oplchannels[channel].Instrument;
|
||||
if (inst >= GENMIDI_NUM_TOTAL) return; // better safe than sorry.
|
||||
instrument = &OPLinstruments[inst];
|
||||
}
|
||||
|
||||
bool double_voice = ((instrument->flags) & GENMIDI_FLAG_2VOICE) && !opl_singlevoice;
|
||||
|
||||
int i = findFreeVoice();
|
||||
if (i < 0) i = replaceExistingVoice();
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
voiceKeyOn(i, channel, instrument, 0, key, volume);
|
||||
if (double_voice)
|
||||
{
|
||||
i = findFreeVoice();
|
||||
if (i >= 0)
|
||||
{
|
||||
voiceKeyOn(i, channel, instrument, 1, key, volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::noteOff(uint32_t id, uint8_t note)
|
||||
{
|
||||
uint32_t sustain = oplchannels[id].Sustain;
|
||||
|
||||
for(uint32_t i = 0; i < io->NumChannels; i++)
|
||||
{
|
||||
if (voices[i].index == id && voices[i].key == note)
|
||||
{
|
||||
if (sustain >= MIN_SUSTAIN)
|
||||
{
|
||||
voices[i].sustained = true;
|
||||
voices[i].timestamp = ++timeCounter;
|
||||
}
|
||||
else releaseVoice(i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::changePitch(uint32_t id, int val1, int val2)
|
||||
{
|
||||
// Convert pitch from 14-bit to 7-bit, then scale it, since the player
|
||||
// code only understands sensitivities of 2 semitones.
|
||||
int pitch = ((val1 | (val2 << 7)) - 8192) * oplchannels[id].PitchSensitivity / (200 * 128) + 64;
|
||||
oplchannels[id].Pitch = pitch;
|
||||
for(uint32_t i = 0; i < io->NumChannels; i++)
|
||||
{
|
||||
auto &ch = voices[i];
|
||||
if (ch.index == id)
|
||||
{
|
||||
ch.pitch = ch.fine_tuning + pitch;
|
||||
io->WriteFrequency(i, ch.note, ch.pitch, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::changeModulation(uint32_t id, int value)
|
||||
{
|
||||
bool vibrato = (value >= VIBRATO_THRESHOLD);
|
||||
oplchannels[id].Vibrato = vibrato;
|
||||
for (uint32_t i = 0; i < io->NumChannels; i++)
|
||||
{
|
||||
auto &ch = voices[i];
|
||||
if (ch.index == id)
|
||||
{
|
||||
io->WriteTremolo(i, ch.current_instr_voice, vibrato);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::changeSustain(uint32_t id, int value)
|
||||
{
|
||||
oplchannels[id].Sustain = value;
|
||||
if (value < MIN_SUSTAIN)
|
||||
{
|
||||
for (uint32_t i = 0; i < io->NumChannels; i++)
|
||||
{
|
||||
if (voices[i].index == id && voices[i].sustained)
|
||||
releaseVoice(i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// Change volume or expression.
|
||||
// Since both go to the same register, one function can handle both.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::changeVolume(uint32_t id, int value, bool expression)
|
||||
{
|
||||
auto &chan = oplchannels[id];
|
||||
if (!expression) chan.Volume = value;
|
||||
else chan.Expression = value;
|
||||
for (uint32_t i = 0; i < io->NumChannels; i++)
|
||||
{
|
||||
auto &ch = voices[i];
|
||||
if (ch.index == id)
|
||||
{
|
||||
io->WriteVolume(i, ch.current_instr_voice, chan.Volume, chan.Expression, ch.note_volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::changePanning(uint32_t id, int value)
|
||||
{
|
||||
oplchannels[id].Panning = value;
|
||||
for(uint32_t i = 0; i < io->NumChannels; i++)
|
||||
{
|
||||
auto &ch = voices[i];
|
||||
if (ch.index == id)
|
||||
{
|
||||
io->WritePan(i, ch.current_instr_voice, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::notesOff(uint32_t id, int value)
|
||||
{
|
||||
for (uint32_t i = 0; i < io->NumChannels; ++i)
|
||||
{
|
||||
if (voices[i].index == id)
|
||||
{
|
||||
if (oplchannels[id].Sustain >= MIN_SUSTAIN)
|
||||
{
|
||||
voices[i].sustained = true;
|
||||
voices[i].timestamp = ++timeCounter;
|
||||
}
|
||||
else releaseVoice(i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// release all notes for this channel
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::allNotesOff(uint32_t id, int value)
|
||||
{
|
||||
for (uint32_t i = 0; i < io->NumChannels; ++i)
|
||||
{
|
||||
if (voices[i].index == id)
|
||||
{
|
||||
releaseVoice(i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::changeExtended(uint32_t id, uint8_t controller, int value)
|
||||
{
|
||||
switch (controller)
|
||||
{
|
||||
case ctrlRPNHi:
|
||||
oplchannels[id].RPN = (oplchannels[id].RPN & 0x007F) | (value << 7);
|
||||
break;
|
||||
|
||||
case ctrlRPNLo:
|
||||
oplchannels[id].RPN = (oplchannels[id].RPN & 0x3F80) | value;
|
||||
break;
|
||||
|
||||
case ctrlNRPNLo:
|
||||
case ctrlNRPNHi:
|
||||
oplchannels[id].RPN = 0x3FFF;
|
||||
break;
|
||||
|
||||
case ctrlDataEntryHi:
|
||||
if (oplchannels[id].RPN == 0)
|
||||
{
|
||||
oplchannels[id].PitchSensitivity = value * 100 + (oplchannels[id].PitchSensitivity % 100);
|
||||
}
|
||||
break;
|
||||
|
||||
case ctrlDataEntryLo:
|
||||
if (oplchannels[id].RPN == 0)
|
||||
{
|
||||
oplchannels[id].PitchSensitivity = value + (oplchannels[id].PitchSensitivity / 100) * 100;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::resetControllers(uint32_t chan, int vol)
|
||||
{
|
||||
auto &channel = oplchannels[chan];
|
||||
|
||||
channel.Volume = vol;
|
||||
channel.Expression = 127;
|
||||
channel.Sustain = 0;
|
||||
channel.Pitch = 64;
|
||||
channel.RPN = 0x3fff;
|
||||
channel.PitchSensitivity = 200;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::programChange(uint32_t channel, int value)
|
||||
{
|
||||
oplchannels[channel].Instrument = value;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::resetAllControllers(int vol)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < NUM_CHANNELS; i++)
|
||||
{
|
||||
resetControllers(i, vol);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void musicBlock::stopAllVoices()
|
||||
{
|
||||
for (uint32_t i = 0; i < io->NumChannels; i++)
|
||||
{
|
||||
if (voices[i].index != ~0u) releaseVoice(i, 1);
|
||||
voices[i].timestamp = 0;
|
||||
}
|
||||
timeCounter = 0;
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,527 +0,0 @@
|
|||
/*
|
||||
** opl_mus_player.cpp
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1999-2016 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "opl_mus_player.h"
|
||||
#include "opl.h"
|
||||
#include "o_swap.h"
|
||||
|
||||
|
||||
#define IMF_RATE 700.0
|
||||
|
||||
OPLmusicBlock::OPLmusicBlock(int core, int numchips)
|
||||
{
|
||||
currentCore = core;
|
||||
scoredata = NULL;
|
||||
NextTickIn = 0;
|
||||
LastOffset = 0;
|
||||
NumChips = std::min(numchips, 2);
|
||||
Looping = false;
|
||||
FullPan = false;
|
||||
io = NULL;
|
||||
io = new OPLio;
|
||||
}
|
||||
|
||||
OPLmusicBlock::~OPLmusicBlock()
|
||||
{
|
||||
delete io;
|
||||
}
|
||||
|
||||
void OPLmusicBlock::ResetChips (int numchips)
|
||||
{
|
||||
io->Reset ();
|
||||
NumChips = io->Init(currentCore, std::min(numchips, 2), FullPan, false);
|
||||
}
|
||||
|
||||
void OPLmusicBlock::Restart()
|
||||
{
|
||||
stopAllVoices ();
|
||||
resetAllControllers (127);
|
||||
playingcount = 0;
|
||||
LastOffset = 0;
|
||||
}
|
||||
|
||||
OPLmusicFile::OPLmusicFile (const void *data, size_t length, int core, int numchips, const char *&errormessage)
|
||||
: OPLmusicBlock(core, numchips), ScoreLen ((int)length)
|
||||
{
|
||||
static char errorbuffer[80];
|
||||
errormessage = nullptr;
|
||||
if (io == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scoredata = new uint8_t[ScoreLen];
|
||||
memcpy(scoredata, data, length);
|
||||
|
||||
if (0 == (NumChips = io->Init(core, NumChips, false, false)))
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Check for RDosPlay raw OPL format
|
||||
if (!memcmp(scoredata, "RAWADATA", 8))
|
||||
{
|
||||
RawPlayer = RDosPlay;
|
||||
if (*(uint16_t *)(scoredata + 8) == 0)
|
||||
{ // A clock speed of 0 is bad
|
||||
*(uint16_t *)(scoredata + 8) = 0xFFFF;
|
||||
}
|
||||
SamplesPerTick = LittleShort(*(uint16_t *)(scoredata + 8)) / ADLIB_CLOCK_MUL;
|
||||
}
|
||||
// Check for DosBox OPL dump
|
||||
else if (!memcmp(scoredata, "DBRAWOPL", 8))
|
||||
{
|
||||
if (LittleShort(((uint16_t *)scoredata)[5]) == 1)
|
||||
{
|
||||
RawPlayer = DosBox1;
|
||||
SamplesPerTick = OPL_SAMPLE_RATE / 1000;
|
||||
ScoreLen = std::min<int>(ScoreLen - 24, LittleLong(((uint32_t *)scoredata)[4])) + 24;
|
||||
}
|
||||
else if (LittleLong(((uint32_t *)scoredata)[2]) == 2)
|
||||
{
|
||||
bool okay = true;
|
||||
if (scoredata[21] != 0)
|
||||
{
|
||||
snprintf(errorbuffer, 80, "Unsupported DOSBox Raw OPL format %d\n", scoredata[20]);
|
||||
errormessage = errorbuffer;
|
||||
okay = false;
|
||||
}
|
||||
if (scoredata[22] != 0)
|
||||
{
|
||||
snprintf(errorbuffer, 80, "Unsupported DOSBox Raw OPL compression %d\n", scoredata[21]);
|
||||
errormessage = errorbuffer;
|
||||
okay = false;
|
||||
}
|
||||
if (!okay)
|
||||
goto fail;
|
||||
RawPlayer = DosBox2;
|
||||
SamplesPerTick = OPL_SAMPLE_RATE / 1000;
|
||||
int headersize = 0x1A + scoredata[0x19];
|
||||
ScoreLen = std::min<int>(ScoreLen - headersize, LittleLong(((uint32_t *)scoredata)[3]) * 2) + headersize;
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(errorbuffer, 80, "Unsupported DOSBox Raw OPL version %d.%d\n", LittleShort(((uint16_t *)scoredata)[4]), LittleShort(((uint16_t *)scoredata)[5]));
|
||||
errormessage = errorbuffer;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
// Check for modified IMF format (includes a header)
|
||||
else if (!memcmp(scoredata, "ADLIB\1", 6))
|
||||
{
|
||||
int songlen;
|
||||
uint8_t *max = scoredata + ScoreLen;
|
||||
RawPlayer = IMF;
|
||||
SamplesPerTick = OPL_SAMPLE_RATE / IMF_RATE;
|
||||
|
||||
score = scoredata + 6;
|
||||
// Skip track and game name
|
||||
for (int i = 2; i != 0; --i)
|
||||
{
|
||||
while (score < max && *score++ != '\0') {}
|
||||
}
|
||||
if (score < max) score++; // Skip unknown byte
|
||||
if (score + 8 > max)
|
||||
{ // Not enough room left for song data
|
||||
goto fail;
|
||||
}
|
||||
songlen = LittleLong(*(uint32_t *)score);
|
||||
if (songlen != 0 && (songlen +=4) < ScoreLen - (score - scoredata))
|
||||
{
|
||||
ScoreLen = songlen + int(score - scoredata);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errormessage = "Unknown OPL format";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
Restart ();
|
||||
return;
|
||||
|
||||
fail:
|
||||
delete[] scoredata;
|
||||
scoredata = nullptr;
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
OPLmusicFile::~OPLmusicFile ()
|
||||
{
|
||||
if (scoredata != NULL)
|
||||
{
|
||||
io->Reset ();
|
||||
delete[] scoredata;
|
||||
scoredata = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool OPLmusicFile::IsValid () const
|
||||
{
|
||||
return scoredata != NULL;
|
||||
}
|
||||
|
||||
void OPLmusicFile::SetLooping (bool loop)
|
||||
{
|
||||
Looping = loop;
|
||||
}
|
||||
|
||||
void OPLmusicFile::Restart ()
|
||||
{
|
||||
OPLmusicBlock::Restart();
|
||||
WhichChip = 0;
|
||||
switch (RawPlayer)
|
||||
{
|
||||
case RDosPlay:
|
||||
score = scoredata + 10;
|
||||
SamplesPerTick = LittleShort(*(uint16_t *)(scoredata + 8)) / ADLIB_CLOCK_MUL;
|
||||
break;
|
||||
|
||||
case DosBox1:
|
||||
score = scoredata + 24;
|
||||
SamplesPerTick = OPL_SAMPLE_RATE / 1000;
|
||||
break;
|
||||
|
||||
case DosBox2:
|
||||
score = scoredata + 0x1A + scoredata[0x19];
|
||||
SamplesPerTick = OPL_SAMPLE_RATE / 1000;
|
||||
break;
|
||||
|
||||
case IMF:
|
||||
score = scoredata + 6;
|
||||
|
||||
// Skip track and game name
|
||||
for (int i = 2; i != 0; --i)
|
||||
{
|
||||
while (*score++ != '\0') {}
|
||||
}
|
||||
score++; // Skip unknown byte
|
||||
if (*(uint32_t *)score != 0)
|
||||
{
|
||||
score += 4; // Skip song length
|
||||
}
|
||||
break;
|
||||
}
|
||||
io->SetClockRate(SamplesPerTick);
|
||||
}
|
||||
|
||||
bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
|
||||
{
|
||||
float *samples1 = (float *)buff;
|
||||
int stereoshift = (int)(FullPan | io->IsOPL3);
|
||||
int numsamples = numbytes / (sizeof(float) << stereoshift);
|
||||
bool prevEnded = false;
|
||||
bool res = true;
|
||||
|
||||
memset(buff, 0, numbytes);
|
||||
|
||||
while (numsamples > 0)
|
||||
{
|
||||
int tick_in = int(NextTickIn);
|
||||
int samplesleft = std::min(numsamples, tick_in);
|
||||
size_t i;
|
||||
|
||||
if (samplesleft > 0)
|
||||
{
|
||||
for (i = 0; i < io->NumChips; ++i)
|
||||
{
|
||||
io->chips[i]->Update(samples1, samplesleft);
|
||||
}
|
||||
OffsetSamples(samples1, samplesleft << stereoshift);
|
||||
NextTickIn -= samplesleft;
|
||||
assert (NextTickIn >= 0);
|
||||
numsamples -= samplesleft;
|
||||
samples1 += samplesleft << stereoshift;
|
||||
}
|
||||
|
||||
if (NextTickIn < 1)
|
||||
{
|
||||
int next = PlayTick();
|
||||
assert(next >= 0);
|
||||
if (next == 0)
|
||||
{ // end of song
|
||||
if (!Looping || prevEnded)
|
||||
{
|
||||
if (numsamples > 0)
|
||||
{
|
||||
for (i = 0; i < io->NumChips; ++i)
|
||||
{
|
||||
io->chips[i]->Update(samples1, numsamples);
|
||||
}
|
||||
OffsetSamples(samples1, numsamples << stereoshift);
|
||||
}
|
||||
res = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Avoid infinite loops from songs that do nothing but end
|
||||
prevEnded = true;
|
||||
Restart ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prevEnded = false;
|
||||
io->WriteDelay(next);
|
||||
NextTickIn += SamplesPerTick * next;
|
||||
assert (NextTickIn >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void OPLmusicBlock::OffsetSamples(float *buff, int count)
|
||||
{
|
||||
// Three out of four of the OPL waveforms are non-negative. Depending on
|
||||
// timbre selection, this can cause the output waveform to tend toward
|
||||
// very large positive values. Heretic's music is particularly bad for
|
||||
// this. This function attempts to compensate by offseting the sample
|
||||
// data back to around the [-1.0, 1.0] range.
|
||||
|
||||
double max = -1e10, min = 1e10, offset, step;
|
||||
int i, ramp, largest_at = 0;
|
||||
|
||||
// Find max and min values for this segment of the waveform.
|
||||
for (i = 0; i < count; ++i)
|
||||
{
|
||||
if (buff[i] > max)
|
||||
{
|
||||
max = buff[i];
|
||||
largest_at = i;
|
||||
}
|
||||
if (buff[i] < min)
|
||||
{
|
||||
min = buff[i];
|
||||
largest_at = i;
|
||||
}
|
||||
}
|
||||
// Prefer to keep the offset at 0, even if it means a little clipping.
|
||||
if (LastOffset == 0 && min >= -1.1 && max <= 1.1)
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = (max + min) / 2;
|
||||
// If the new offset is close to 0, make it 0 to avoid making another
|
||||
// full loop through the sample data.
|
||||
if (fabs(offset) < 1/256.0)
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
// Ramp the offset change so there aren't any abrupt clicks in the output.
|
||||
// If the ramp is too short, it can sound scratchy. cblood2.mid is
|
||||
// particularly unforgiving of short ramps.
|
||||
if (count >= 512)
|
||||
{
|
||||
ramp = 512;
|
||||
step = (offset - LastOffset) / 512;
|
||||
}
|
||||
else
|
||||
{
|
||||
ramp = std::min(count, std::max(196, largest_at));
|
||||
step = (offset - LastOffset) / ramp;
|
||||
}
|
||||
offset = LastOffset;
|
||||
i = 0;
|
||||
if (step != 0)
|
||||
{
|
||||
for (; i < ramp; ++i)
|
||||
{
|
||||
buff[i] = float(buff[i] - offset);
|
||||
offset += step;
|
||||
}
|
||||
}
|
||||
if (offset != 0)
|
||||
{
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
buff[i] = float(buff[i] - offset);
|
||||
}
|
||||
}
|
||||
LastOffset = float(offset);
|
||||
}
|
||||
|
||||
int OPLmusicFile::PlayTick ()
|
||||
{
|
||||
uint8_t reg, data;
|
||||
uint16_t delay;
|
||||
|
||||
switch (RawPlayer)
|
||||
{
|
||||
case RDosPlay:
|
||||
while (score < scoredata + ScoreLen)
|
||||
{
|
||||
data = *score++;
|
||||
reg = *score++;
|
||||
switch (reg)
|
||||
{
|
||||
case 0: // Delay
|
||||
if (data != 0)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // Speed change or OPL3 switch
|
||||
if (data == 0)
|
||||
{
|
||||
SamplesPerTick = LittleShort(*(uint16_t *)(score)) / ADLIB_CLOCK_MUL;
|
||||
io->SetClockRate(SamplesPerTick);
|
||||
score += 2;
|
||||
}
|
||||
else if (data == 1)
|
||||
{
|
||||
WhichChip = 0;
|
||||
}
|
||||
else if (data == 2)
|
||||
{
|
||||
WhichChip = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF: // End of song
|
||||
if (data == 0xFF)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default: // It's something to stuff into the OPL chip
|
||||
io->WriteRegister(WhichChip, reg, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DosBox1:
|
||||
while (score < scoredata + ScoreLen)
|
||||
{
|
||||
reg = *score++;
|
||||
|
||||
if (reg == 4)
|
||||
{
|
||||
reg = *score++;
|
||||
data = *score++;
|
||||
}
|
||||
else if (reg == 0)
|
||||
{ // One-byte delay
|
||||
return *score++ + 1;
|
||||
}
|
||||
else if (reg == 1)
|
||||
{ // Two-byte delay
|
||||
int delay = score[0] + (score[1] << 8) + 1;
|
||||
score += 2;
|
||||
return delay;
|
||||
}
|
||||
else if (reg == 2)
|
||||
{ // Select OPL chip 0
|
||||
WhichChip = 0;
|
||||
continue;
|
||||
}
|
||||
else if (reg == 3)
|
||||
{ // Select OPL chip 1
|
||||
WhichChip = 1;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
data = *score++;
|
||||
}
|
||||
io->WriteRegister(WhichChip, reg, data);
|
||||
}
|
||||
break;
|
||||
|
||||
case DosBox2:
|
||||
{
|
||||
uint8_t *to_reg = scoredata + 0x1A;
|
||||
uint8_t to_reg_size = scoredata[0x19];
|
||||
uint8_t short_delay_code = scoredata[0x17];
|
||||
uint8_t long_delay_code = scoredata[0x18];
|
||||
|
||||
while (score < scoredata + ScoreLen)
|
||||
{
|
||||
uint8_t code = *score++;
|
||||
data = *score++;
|
||||
|
||||
// Which OPL chip to write to is encoded in the high bit of the code value.
|
||||
int which = !!(code & 0x80);
|
||||
code &= 0x7F;
|
||||
|
||||
if (code == short_delay_code)
|
||||
{
|
||||
return data + 1;
|
||||
}
|
||||
else if (code == long_delay_code)
|
||||
{
|
||||
return (data + 1) << 8;
|
||||
}
|
||||
else if (code < to_reg_size)
|
||||
{
|
||||
io->WriteRegister(which, to_reg[code], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IMF:
|
||||
delay = 0;
|
||||
while (delay == 0 && score + 4 - scoredata <= ScoreLen)
|
||||
{
|
||||
if (*(uint32_t *)score == 0xFFFFFFFF)
|
||||
{ // This is a special value that means to end the song.
|
||||
return 0;
|
||||
}
|
||||
reg = score[0];
|
||||
data = score[1];
|
||||
delay = LittleShort(((uint16_t *)score)[1]);
|
||||
score += 4;
|
||||
io->WriteRegister (0, reg, data);
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,485 +0,0 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// Copyright 2002-2016 Randy Heit
|
||||
// Copyright 2005-2014 Simon Howard
|
||||
// Copyright 2017 Christoph Oelckers
|
||||
//
|
||||
// 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 3 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// OPL IO interface. Partly built from the non-MusLib code in the old version
|
||||
// plus some additions from Chocolate Doom.
|
||||
//
|
||||
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
#include "genmidi.h"
|
||||
#include "oplio.h"
|
||||
#include "opl.h"
|
||||
|
||||
const double HALF_PI = (3.14159265358979323846 * 0.5);
|
||||
|
||||
OPLio::~OPLio()
|
||||
{
|
||||
}
|
||||
|
||||
void OPLio::SetClockRate(double samples_per_tick)
|
||||
{
|
||||
}
|
||||
|
||||
void OPLio::WriteDelay(int ticks)
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// Initialize OPL emulator
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
int OPLio::Init(int core, uint32_t numchips, bool stereo, bool initopl3)
|
||||
{
|
||||
assert(numchips >= 1 && numchips <= OPL_NUM_VOICES);
|
||||
uint32_t i;
|
||||
IsOPL3 = (core == 1 || core == 2 || core == 3);
|
||||
|
||||
memset(chips, 0, sizeof(chips));
|
||||
if (IsOPL3)
|
||||
{
|
||||
numchips = (numchips + 1) >> 1;
|
||||
}
|
||||
for (i = 0; i < numchips; ++i)
|
||||
{
|
||||
OPLEmul *chip = IsOPL3 ? (core == 1 ? DBOPLCreate(stereo) : (core == 2 ? JavaOPLCreate(stereo) : NukedOPL3Create(stereo))) : YM3812Create(stereo);
|
||||
if (chip == NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
chips[i] = chip;
|
||||
}
|
||||
NumChips = i;
|
||||
NumChannels = i * (IsOPL3 ? OPL3_NUM_VOICES : OPL_NUM_VOICES);
|
||||
WriteInitState(initopl3);
|
||||
return i;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void OPLio::WriteInitState(bool initopl3)
|
||||
{
|
||||
for (uint32_t k = 0; k < NumChips; ++k)
|
||||
{
|
||||
int chip = k << (int)IsOPL3;
|
||||
if (IsOPL3 && initopl3)
|
||||
{
|
||||
WriteRegister(chip, OPL_REG_OPL3_ENABLE, 1);
|
||||
WriteRegister(chip, OPL_REG_4OPMODE_ENABLE, 0);
|
||||
}
|
||||
WriteRegister(chip, OPL_REG_WAVEFORM_ENABLE, WAVEFORM_ENABLED);
|
||||
WriteRegister(chip, OPL_REG_PERCUSSION_MODE, 0); // should be the default, but cannot verify for some of the cores.
|
||||
}
|
||||
|
||||
// Reset all channels.
|
||||
for (uint32_t k = 0; k < NumChannels; k++)
|
||||
{
|
||||
MuteChannel(k);
|
||||
WriteValue(OPL_REGS_FREQ_2, k, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// Deinitialize emulator before shutdown
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void OPLio::Reset(void)
|
||||
{
|
||||
for (auto &c : chips)
|
||||
{
|
||||
if (c != nullptr)
|
||||
{
|
||||
delete c;
|
||||
c = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void OPLio::WriteRegister(int chipnum, uint32_t reg, uint8_t data)
|
||||
{
|
||||
if (IsOPL3)
|
||||
{
|
||||
reg |= (chipnum & 1) << 8;
|
||||
chipnum >>= 1;
|
||||
}
|
||||
if (chips[chipnum] != nullptr)
|
||||
{
|
||||
chips[chipnum]->WriteReg(reg, data);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void OPLio::WriteValue(uint32_t regbase, uint32_t channel, uint8_t value)
|
||||
{
|
||||
WriteRegister (channel / OPL_NUM_VOICES, regbase + (channel % OPL_NUM_VOICES), value);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
static const int voice_operators[OPL_NUM_VOICES] = { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12 };
|
||||
|
||||
void OPLio::WriteOperator(uint32_t regbase, uint32_t channel, int index, uint8_t data2)
|
||||
{
|
||||
WriteRegister(channel / OPL_NUM_VOICES, regbase + voice_operators[channel % OPL_NUM_VOICES] + 3*index, data2);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// Write frequency/octave/keyon data to a channel
|
||||
//
|
||||
// [RH] This is totally different from the original MUS library code
|
||||
// but matches exactly what DMX does. I haven't a clue why there are 284
|
||||
// special bytes at the beginning of the table for the first few notes.
|
||||
// That last byte in the table doesn't look right, either, but that's what
|
||||
// it really is.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
static const uint16_t frequencies[] = { // (this is the table from Chocolate Doom, which contains the same values as ZDoom's original one but is better formatted.
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
void OPLio::WriteFrequency(uint32_t channel, uint32_t note, uint32_t pitch, uint32_t keyon)
|
||||
{
|
||||
int octave = 0;
|
||||
int j = (note << 5) + pitch;
|
||||
|
||||
if (j < 0)
|
||||
{
|
||||
j = 0;
|
||||
}
|
||||
else if (j >= 284)
|
||||
{
|
||||
j -= 284;
|
||||
octave = j / (32*12);
|
||||
if (octave > 7)
|
||||
{
|
||||
octave = 7;
|
||||
}
|
||||
j = (j % (32*12)) + 284;
|
||||
}
|
||||
int i = frequencies[j] | (octave << 10);
|
||||
|
||||
WriteValue (OPL_REGS_FREQ_1, channel, (uint8_t)i);
|
||||
WriteValue (OPL_REGS_FREQ_2, channel, (uint8_t)(i>>8)|(keyon<<5));
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
static uint8_t volumetable[128] = {
|
||||
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};
|
||||
|
||||
void OPLio::WriteVolume(uint32_t channel, struct GenMidiVoice *voice, uint32_t vol1, uint32_t vol2, uint32_t vol3)
|
||||
{
|
||||
if (voice != nullptr)
|
||||
{
|
||||
uint32_t full_volume = volumetable[std::min<uint32_t>(127, (uint32_t)((uint64_t)vol1*vol2*vol3) / (127 * 127))];
|
||||
int reg_volume2 = ((0x3f - voice->carrier.level) * full_volume) / 128;
|
||||
reg_volume2 = (0x3f - reg_volume2) | voice->carrier.scale;
|
||||
WriteOperator(OPL_REGS_LEVEL, channel, 1, reg_volume2);
|
||||
|
||||
int reg_volume1;
|
||||
if (voice->feedback & 0x01)
|
||||
{
|
||||
// Chocolate Doom says:
|
||||
// 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.
|
||||
|
||||
// But Muslib does it differently than the comment above states. Which one is correct?
|
||||
|
||||
reg_volume1 = ((0x3f - voice->modulator.level) * full_volume) / 128;
|
||||
reg_volume1 = (0x3f - reg_volume1) | voice->modulator.scale;
|
||||
}
|
||||
else
|
||||
{
|
||||
reg_volume1 = voice->modulator.level | voice->modulator.scale;
|
||||
}
|
||||
WriteOperator(OPL_REGS_LEVEL, channel, 0,reg_volume1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void OPLio::WritePan(uint32_t channel, struct GenMidiVoice *voice, int pan)
|
||||
{
|
||||
if (voice != 0)
|
||||
{
|
||||
WriteValue(OPL_REGS_FEEDBACK, channel, voice->feedback | (pan >= 28 ? 0x20 : 0) | (pan <= 100 ? 0x10 : 0));
|
||||
|
||||
// Set real panning if we're using emulated chips.
|
||||
int chanper = IsOPL3 ? OPL3_NUM_VOICES : OPL_NUM_VOICES;
|
||||
int which = channel / chanper;
|
||||
if (chips[which] != NULL)
|
||||
{
|
||||
// This is the MIDI-recommended pan formula. 0 and 1 are
|
||||
// both hard left so that 64 can be perfectly center.
|
||||
double level = (pan <= 1) ? 0 : (pan - 1) / 126.0;
|
||||
chips[which]->SetPanning(channel % chanper,
|
||||
(float)cos(HALF_PI * level), (float)sin(HALF_PI * level));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void OPLio::WriteTremolo(uint32_t channel, struct GenMidiVoice *voice, bool vibrato)
|
||||
{
|
||||
int val1 = voice->modulator.tremolo, val2 = voice->carrier.tremolo;
|
||||
if (vibrato)
|
||||
{
|
||||
if (voice->feedback & 1) val1 |= 0x40;
|
||||
val2 |= 0x40;
|
||||
}
|
||||
WriteOperator(OPL_REGS_TREMOLO, channel, 1, val2);
|
||||
WriteOperator(OPL_REGS_TREMOLO, channel, 0, val1);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void OPLio::MuteChannel(uint32_t channel)
|
||||
{
|
||||
WriteOperator(OPL_REGS_LEVEL, channel, 1, NO_VOLUME);
|
||||
WriteOperator(OPL_REGS_ATTACK, channel, 1, MAX_ATTACK_DECAY);
|
||||
WriteOperator(OPL_REGS_SUSTAIN, channel, 1, NO_SUSTAIN_MAX_RELEASE);
|
||||
|
||||
WriteOperator(OPL_REGS_LEVEL, channel, 0, NO_VOLUME);
|
||||
WriteOperator(OPL_REGS_ATTACK, channel, 0, MAX_ATTACK_DECAY);
|
||||
WriteOperator(OPL_REGS_SUSTAIN, channel, 0, NO_SUSTAIN_MAX_RELEASE);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void OPLio::LoadOperatorData(uint32_t channel, int op_index, genmidi_op_t *data, bool max_level, bool vibrato)
|
||||
{
|
||||
// The scale and level fields must be combined for the level register.
|
||||
// For the carrier wave we always set the maximum level.
|
||||
|
||||
int level = data->scale;
|
||||
if (max_level) level |= 0x3f;
|
||||
else level |= data->level;
|
||||
|
||||
int tremolo = data->tremolo;
|
||||
if (vibrato) tremolo |= 0x40;
|
||||
|
||||
WriteOperator(OPL_REGS_LEVEL, channel, op_index, level);
|
||||
WriteOperator(OPL_REGS_TREMOLO, channel, op_index, tremolo);
|
||||
WriteOperator(OPL_REGS_ATTACK, channel, op_index, data->attack);
|
||||
WriteOperator(OPL_REGS_SUSTAIN, channel, op_index, data->sustain);
|
||||
WriteOperator(OPL_REGS_WAVEFORM, channel, op_index, data->waveform);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void OPLio::WriteInstrument(uint32_t channel, struct GenMidiVoice *voice, bool vibrato)
|
||||
{
|
||||
bool modulating = (voice->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 later. If we are not using modulating mode, we must set both to minimum volume.
|
||||
|
||||
LoadOperatorData(channel, 1, &voice->carrier, true, vibrato);
|
||||
LoadOperatorData(channel, 0, &voice->modulator, !modulating, vibrato && modulating);
|
||||
|
||||
// The feedback register is written by the calling code.
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__GNUC__)
|
||||
// With versions of GCC newer than 4.2, it appears it was determined that the
|
||||
// cost of an unaligned pointer on PPC was high enough to add padding to the
|
||||
// end of packed structs. For whatever reason __packed__ and pragma pack are
|
||||
// handled differently in this regard. Note that this only needs to be applied
|
||||
// to types which are used in arrays or sizeof is needed. This also prevents
|
||||
// code from taking references to the struct members.
|
||||
#define FORCE_PACKED __attribute__((__packed__))
|
||||
#else
|
||||
#define FORCE_PACKED
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct genmidi_op_t
|
||||
{
|
||||
uint8_t tremolo;
|
||||
uint8_t attack;
|
||||
uint8_t sustain;
|
||||
uint8_t waveform;
|
||||
uint8_t scale;
|
||||
uint8_t level;
|
||||
} FORCE_PACKED;
|
||||
|
||||
struct GenMidiVoice
|
||||
{
|
||||
genmidi_op_t modulator;
|
||||
uint8_t feedback;
|
||||
genmidi_op_t carrier;
|
||||
uint8_t unused;
|
||||
int16_t base_note_offset;
|
||||
} FORCE_PACKED;
|
||||
|
||||
|
||||
struct GenMidiInstrument
|
||||
{
|
||||
uint16_t flags;
|
||||
uint8_t fine_tuning;
|
||||
uint8_t fixed_note;
|
||||
GenMidiVoice voices[2];
|
||||
} FORCE_PACKED;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
enum
|
||||
{
|
||||
GENMIDI_FLAG_FIXED = 0x0001, /* fixed pitch */
|
||||
GENMIDI_FLAG_2VOICE = 0x0004 /* double voice (OPL3) */
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
GENMIDI_NUM_INSTRS = 128,
|
||||
GENMIDI_FIST_PERCUSSION = 35,
|
||||
GENMIDI_NUM_PERCUSSION = 47,
|
||||
GENMIDI_NUM_TOTAL = GENMIDI_NUM_INSTRS + GENMIDI_NUM_PERCUSSION
|
||||
};
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "genmidi.h"
|
||||
#include "oplio.h"
|
||||
|
||||
|
||||
struct OPLVoice
|
||||
{
|
||||
unsigned int index; // Index of this voice, or -1 if not in use.
|
||||
unsigned int key; // The midi key that this voice is playing.
|
||||
unsigned int note; // 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_volume; // The volume of the note being played on this channel.
|
||||
GenMidiInstrument *current_instr; // Currently-loaded instrument data
|
||||
GenMidiVoice *current_instr_voice;// The voice number in the instrument to use. This is normally set to the instrument's first voice; if this is a double voice instrument, it may be the second one
|
||||
bool sustained;
|
||||
int8_t fine_tuning;
|
||||
int pitch;
|
||||
uint32_t timestamp;
|
||||
};
|
||||
|
||||
struct musicBlock {
|
||||
musicBlock();
|
||||
~musicBlock();
|
||||
|
||||
OPLChannel oplchannels[NUM_CHANNELS];
|
||||
OPLio *io;
|
||||
uint32_t timeCounter;
|
||||
|
||||
struct GenMidiInstrument OPLinstruments[GENMIDI_NUM_TOTAL];
|
||||
|
||||
void changeModulation(uint32_t id, int value);
|
||||
void changeSustain(uint32_t id, int value);
|
||||
void changeVolume(uint32_t id, int value, bool expression);
|
||||
void changePanning(uint32_t id, int value);
|
||||
void notesOff(uint32_t id, int value);
|
||||
void allNotesOff(uint32_t id, int value);
|
||||
void changeExtended(uint32_t channel, uint8_t controller, int value);
|
||||
void resetControllers(uint32_t channel, int vol);
|
||||
void programChange(uint32_t channel, int value);
|
||||
void resetAllControllers(int vol);
|
||||
void changePitch(uint32_t channel, int val1, int val2);
|
||||
|
||||
void noteOn(uint32_t channel, uint8_t note, int volume);
|
||||
void noteOff(uint32_t channel, uint8_t note);
|
||||
void stopAllVoices();
|
||||
|
||||
protected:
|
||||
OPLVoice voices[NUM_VOICES];
|
||||
|
||||
int findFreeVoice();
|
||||
int replaceExistingVoice();
|
||||
void voiceKeyOn(uint32_t slot, uint32_t channo, GenMidiInstrument *instrument, uint32_t instrument_voice, uint32_t, uint32_t volume);
|
||||
int releaseVoice(uint32_t slot, uint32_t killed);
|
||||
|
||||
friend class Stat_opl;
|
||||
|
||||
};
|
||||
|
||||
enum ExtCtrl {
|
||||
ctrlRPNHi,
|
||||
ctrlRPNLo,
|
||||
ctrlNRPNHi,
|
||||
ctrlNRPNLo,
|
||||
ctrlDataEntryHi,
|
||||
ctrlDataEntryLo,
|
||||
};
|
|
@ -1,240 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2015 Nuke.YKT(Alexey Khokholov)
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
Nuked Yamaha YMF262(aka 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.
|
||||
*/
|
||||
|
||||
//version 1.6
|
||||
|
||||
#include "opl.h"
|
||||
#include "musicblock.h"
|
||||
|
||||
namespace NukedOPL3
|
||||
{
|
||||
|
||||
typedef uintptr_t Bitu;
|
||||
typedef intptr_t Bits;
|
||||
typedef uint32_t Bit32u;
|
||||
typedef int32_t Bit32s;
|
||||
typedef uint16_t Bit16u;
|
||||
typedef int16_t Bit16s;
|
||||
typedef uint8_t Bit8u;
|
||||
typedef int8_t Bit8s;
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// logsin table
|
||||
//
|
||||
|
||||
static const Bit16u logsinrom[256] = {
|
||||
0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365,
|
||||
0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261,
|
||||
0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd,
|
||||
0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166,
|
||||
0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118,
|
||||
0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db,
|
||||
0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9,
|
||||
0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081,
|
||||
0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060,
|
||||
0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045,
|
||||
0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f,
|
||||
0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e,
|
||||
0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011,
|
||||
0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007,
|
||||
0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002,
|
||||
0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000
|
||||
};
|
||||
|
||||
//
|
||||
// exp table
|
||||
//
|
||||
|
||||
static const Bit16u exprom[256] = {
|
||||
0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, 0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a,
|
||||
0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042, 0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a,
|
||||
0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072, 0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b,
|
||||
0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4, 0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be,
|
||||
0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9, 0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4,
|
||||
0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110, 0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c,
|
||||
0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149, 0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167,
|
||||
0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185, 0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4,
|
||||
0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4, 0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4,
|
||||
0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205, 0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227,
|
||||
0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249, 0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d,
|
||||
0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291, 0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5,
|
||||
0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db, 0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302,
|
||||
0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329, 0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351,
|
||||
0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a, 0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4,
|
||||
0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf, 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa
|
||||
};
|
||||
|
||||
//
|
||||
// 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 Bit8u mt[16] = { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 };
|
||||
|
||||
//
|
||||
// ksl table
|
||||
//
|
||||
|
||||
static const Bit8u kslrom[16] = { 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 };
|
||||
|
||||
static const Bit8u kslshift[4] = { 8, 1, 2, 0 };
|
||||
|
||||
//
|
||||
// LFO vibrato
|
||||
//
|
||||
|
||||
static const Bit8u vib_table[8] = { 3, 1, 0, 1, 3, 1, 0, 1 };
|
||||
static const Bit8s vibsgn_table[8] = { 1, 1, 1, 1, -1, -1, -1, -1 };
|
||||
|
||||
//
|
||||
// envelope generator constants
|
||||
//
|
||||
|
||||
static const Bit8u eg_incstep[3][4][8] = {
|
||||
{ { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 } },
|
||||
{ { 0, 1, 0, 1, 0, 1, 0, 1 }, { 0, 1, 0, 1, 1, 1, 0, 1 }, { 0, 1, 1, 1, 0, 1, 1, 1 }, { 0, 1, 1, 1, 1, 1, 1, 1 } },
|
||||
{ { 1, 1, 1, 1, 1, 1, 1, 1 }, { 2, 2, 1, 1, 1, 1, 1, 1 }, { 2, 2, 1, 1, 2, 2, 1, 1 }, { 2, 2, 2, 2, 2, 2, 1, 1 } }
|
||||
};
|
||||
|
||||
static const Bit8u eg_incdesc[16] = {
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2
|
||||
};
|
||||
|
||||
static const Bit8s eg_incsh[16] = {
|
||||
0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -1, -2
|
||||
};
|
||||
|
||||
//
|
||||
// address decoding
|
||||
//
|
||||
|
||||
static const Bit8s 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 Bit8u ch_slot[18] = { 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 };
|
||||
|
||||
struct opl_chip;
|
||||
struct opl_slot;
|
||||
struct opl_channel;
|
||||
|
||||
struct opl_slot {
|
||||
opl_channel *channel;
|
||||
opl_chip *chip;
|
||||
Bit16s out;
|
||||
Bit16s fbmod;
|
||||
Bit16s *mod;
|
||||
Bit16s prout[2];
|
||||
Bit16s eg_rout;
|
||||
Bit16s eg_out;
|
||||
Bit8u eg_inc;
|
||||
Bit8u eg_gen;
|
||||
Bit8u eg_rate;
|
||||
Bit8u eg_ksl;
|
||||
Bit8u *trem;
|
||||
Bit8u reg_vib;
|
||||
Bit8u reg_type;
|
||||
Bit8u reg_ksr;
|
||||
Bit8u reg_mult;
|
||||
Bit8u reg_ksl;
|
||||
Bit8u reg_tl;
|
||||
Bit8u reg_ar;
|
||||
Bit8u reg_dr;
|
||||
Bit8u reg_sl;
|
||||
Bit8u reg_rr;
|
||||
Bit8u reg_wf;
|
||||
Bit8u key;
|
||||
Bit32u pg_phase;
|
||||
};
|
||||
|
||||
struct opl_channel {
|
||||
opl_slot *slots[2];
|
||||
opl_channel *pair;
|
||||
opl_chip *chip;
|
||||
Bit16s *out[4];
|
||||
Bit8u chtype;
|
||||
Bit16u f_num;
|
||||
Bit8u block;
|
||||
Bit8u fb;
|
||||
Bit8u con;
|
||||
Bit8u alg;
|
||||
Bit8u ksv;
|
||||
Bit16u cha, chb;
|
||||
float fcha, fchb;
|
||||
};
|
||||
|
||||
struct opl_chip {
|
||||
opl_channel channel[18];
|
||||
opl_slot slot[36];
|
||||
Bit16u timer;
|
||||
Bit8u newm;
|
||||
Bit8u nts;
|
||||
Bit8u dvb;
|
||||
Bit8u dam;
|
||||
Bit8u rhy;
|
||||
Bit8u vibpos;
|
||||
Bit8u tremval;
|
||||
Bit8u tremtval;
|
||||
Bit8u tremdir;
|
||||
Bit32u noise;
|
||||
Bit16s zeromod;
|
||||
Bit32s mixbuff[2];
|
||||
Bit8u FullPan;
|
||||
};
|
||||
|
||||
|
||||
class NukedOPL3 : public OPLEmul {
|
||||
private:
|
||||
opl_chip opl3;
|
||||
bool FullPan;
|
||||
public:
|
||||
void Reset();
|
||||
void Update(float* sndptr, int numsamples);
|
||||
void WriteReg(int reg, int v);
|
||||
void SetPanning(int c, float left, float right);
|
||||
|
||||
NukedOPL3(bool stereo);
|
||||
};
|
||||
|
||||
} // namespace NukedOPL3
|
|
@ -1,255 +0,0 @@
|
|||
//
|
||||
// DESCRIPTION:
|
||||
// Endianess handling, swapping 16bit and 32bit.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#ifndef __M_SWAP_H__
|
||||
#define __M_SWAP_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// Endianess handling.
|
||||
// WAD files are stored little endian.
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <libkern/OSByteOrder.h>
|
||||
|
||||
inline short LittleShort(short x)
|
||||
{
|
||||
return (short)OSSwapLittleToHostInt16((uint16_t)x);
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort(unsigned short x)
|
||||
{
|
||||
return OSSwapLittleToHostInt16(x);
|
||||
}
|
||||
|
||||
inline short LittleShort(int x)
|
||||
{
|
||||
return OSSwapLittleToHostInt16((uint16_t)x);
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort(unsigned int x)
|
||||
{
|
||||
return OSSwapLittleToHostInt16((uint16_t)x);
|
||||
}
|
||||
|
||||
inline int LittleLong(int x)
|
||||
{
|
||||
return OSSwapLittleToHostInt32((uint32_t)x);
|
||||
}
|
||||
|
||||
inline unsigned int LittleLong(unsigned int x)
|
||||
{
|
||||
return OSSwapLittleToHostInt32(x);
|
||||
}
|
||||
|
||||
inline short BigShort(short x)
|
||||
{
|
||||
return (short)OSSwapBigToHostInt16((uint16_t)x);
|
||||
}
|
||||
|
||||
inline unsigned short BigShort(unsigned short x)
|
||||
{
|
||||
return OSSwapBigToHostInt16(x);
|
||||
}
|
||||
|
||||
inline int BigLong(int x)
|
||||
{
|
||||
return OSSwapBigToHostInt32((uint32_t)x);
|
||||
}
|
||||
|
||||
inline unsigned int BigLong(unsigned int x)
|
||||
{
|
||||
return OSSwapBigToHostInt32(x);
|
||||
}
|
||||
|
||||
#elif defined __BIG_ENDIAN__
|
||||
|
||||
// Swap 16bit, that is, MSB and LSB byte.
|
||||
// No masking with 0xFF should be necessary.
|
||||
inline short LittleShort (short x)
|
||||
{
|
||||
return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8));
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort (unsigned short x)
|
||||
{
|
||||
return (unsigned short)((x>>8) | (x<<8));
|
||||
}
|
||||
|
||||
inline short LittleShort (int x)
|
||||
{
|
||||
return LittleShort((short)x);
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort (unsigned int x)
|
||||
{
|
||||
return LittleShort((unsigned short)x);
|
||||
}
|
||||
|
||||
// Swapping 32bit.
|
||||
inline unsigned int LittleLong (unsigned int x)
|
||||
{
|
||||
return (unsigned int)(
|
||||
(x>>24)
|
||||
| ((x>>8) & 0xff00)
|
||||
| ((x<<8) & 0xff0000)
|
||||
| (x<<24));
|
||||
}
|
||||
|
||||
inline int LittleLong (int x)
|
||||
{
|
||||
return (int)(
|
||||
(((unsigned int)x)>>24)
|
||||
| ((((unsigned int)x)>>8) & 0xff00)
|
||||
| ((((unsigned int)x)<<8) & 0xff0000)
|
||||
| (((unsigned int)x)<<24));
|
||||
}
|
||||
|
||||
inline short BigShort(short x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline unsigned short BigShort(unsigned short x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline unsigned int BigLong(unsigned int x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline int BigLong(int x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
inline short LittleShort(short x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort(unsigned short x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline unsigned int LittleLong(unsigned int x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline int LittleLong(int x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
inline short BigShort(short x)
|
||||
{
|
||||
return (short)_byteswap_ushort((unsigned short)x);
|
||||
}
|
||||
|
||||
inline unsigned short BigShort(unsigned short x)
|
||||
{
|
||||
return _byteswap_ushort(x);
|
||||
}
|
||||
|
||||
inline int BigLong(int x)
|
||||
{
|
||||
return (int)_byteswap_ulong((unsigned long)x);
|
||||
}
|
||||
|
||||
inline unsigned int BigLong(unsigned int x)
|
||||
{
|
||||
return (unsigned int)_byteswap_ulong((unsigned long)x);
|
||||
}
|
||||
#pragma warning (default: 4035)
|
||||
|
||||
#else
|
||||
|
||||
inline short BigShort (short x)
|
||||
{
|
||||
return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8));
|
||||
}
|
||||
|
||||
inline unsigned short BigShort (unsigned short x)
|
||||
{
|
||||
return (unsigned short)((x>>8) | (x<<8));
|
||||
}
|
||||
|
||||
inline unsigned int BigLong (unsigned int x)
|
||||
{
|
||||
return (unsigned int)(
|
||||
(x>>24)
|
||||
| ((x>>8) & 0xff00)
|
||||
| ((x<<8) & 0xff0000)
|
||||
| (x<<24));
|
||||
}
|
||||
|
||||
inline int BigLong (int x)
|
||||
{
|
||||
return (int)(
|
||||
(((unsigned int)x)>>24)
|
||||
| ((((unsigned int)x)>>8) & 0xff00)
|
||||
| ((((unsigned int)x)<<8) & 0xff0000)
|
||||
| (((unsigned int)x)<<24));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // __BIG_ENDIAN__
|
||||
|
||||
// These may be destructive so they should create errors
|
||||
unsigned long BigLong(unsigned long) = delete;
|
||||
long BigLong(long) = delete;
|
||||
unsigned long LittleLong(unsigned long) = delete;
|
||||
long LittleLong(long) = delete;
|
||||
|
||||
|
||||
// Data accessors, since some data is highly likely to be unaligned.
|
||||
#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__)
|
||||
inline int GetShort(const unsigned char *foo)
|
||||
{
|
||||
return *(const short *)foo;
|
||||
}
|
||||
inline int GetInt(const unsigned char *foo)
|
||||
{
|
||||
return *(const int *)foo;
|
||||
}
|
||||
#else
|
||||
inline int GetShort(const unsigned char *foo)
|
||||
{
|
||||
return short(foo[0] | (foo[1] << 8));
|
||||
}
|
||||
inline int GetInt(const unsigned char *foo)
|
||||
{
|
||||
return int(foo[0] | (foo[1] << 8) | (foo[2] << 16) | (foo[3] << 24));
|
||||
}
|
||||
#endif
|
||||
inline int GetBigInt(const unsigned char *foo)
|
||||
{
|
||||
return int((foo[0] << 24) | (foo[1] << 16) | (foo[2] << 8) | foo[3]);
|
||||
}
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
inline int GetNativeInt(const unsigned char *foo)
|
||||
{
|
||||
return GetBigInt(foo);
|
||||
}
|
||||
#else
|
||||
inline int GetNativeInt(const unsigned char *foo)
|
||||
{
|
||||
return GetInt(foo);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // __M_SWAP_H__
|
|
@ -1,29 +0,0 @@
|
|||
#ifndef OPL_H
|
||||
#define OPL_H
|
||||
|
||||
// Abstract base class for OPL emulators
|
||||
|
||||
class OPLEmul
|
||||
{
|
||||
public:
|
||||
OPLEmul() {}
|
||||
virtual ~OPLEmul() {}
|
||||
|
||||
virtual void Reset() = 0;
|
||||
virtual void WriteReg(int reg, int v) = 0;
|
||||
virtual void Update(float *buffer, int length) = 0;
|
||||
virtual void SetPanning(int c, float left, float right) = 0;
|
||||
};
|
||||
|
||||
OPLEmul *YM3812Create(bool stereo);
|
||||
OPLEmul *DBOPLCreate(bool stereo);
|
||||
OPLEmul *JavaOPLCreate(bool stereo);
|
||||
OPLEmul *NukedOPL3Create(bool stereo);
|
||||
|
||||
#define OPL_SAMPLE_RATE 49716.0
|
||||
#define CENTER_PANNING_POWER 0.70710678118 /* [RH] volume at center for EQP */
|
||||
#define ADLIB_CLOCK_MUL 24.0
|
||||
|
||||
|
||||
|
||||
#endif
|
|
@ -1,238 +0,0 @@
|
|||
// ====================================================================================================================
|
||||
// ====================================================================================================================
|
||||
// xs_Float.h
|
||||
//
|
||||
// Source: "Know Your FPU: Fixing Floating Fast"
|
||||
// http://www.stereopsis.com/sree/fpu2006.html
|
||||
//
|
||||
// xs_CRoundToInt: Round toward nearest, but ties round toward even (just like FISTP)
|
||||
// xs_ToInt: Round toward zero, just like the C (int) cast
|
||||
// xs_FloorToInt: Round down
|
||||
// xs_CeilToInt: Round up
|
||||
// xs_RoundToInt: Round toward nearest, but ties round up
|
||||
// ====================================================================================================================
|
||||
// ====================================================================================================================
|
||||
#ifndef _xs_FLOAT_H_
|
||||
#define _xs_FLOAT_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// ====================================================================================================================
|
||||
// Defines
|
||||
// ====================================================================================================================
|
||||
#ifndef _xs_DEFAULT_CONVERSION
|
||||
#define _xs_DEFAULT_CONVERSION 0
|
||||
#endif //_xs_DEFAULT_CONVERSION
|
||||
|
||||
|
||||
#if __BIG_ENDIAN__
|
||||
#define _xs_iexp_ 0
|
||||
#define _xs_iman_ 1
|
||||
#else
|
||||
#define _xs_iexp_ 1 //intel is little endian
|
||||
#define _xs_iman_ 0
|
||||
#endif //BigEndian_
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define finline inline
|
||||
#else
|
||||
#define finline __forceinline
|
||||
#endif
|
||||
|
||||
typedef double real64;
|
||||
|
||||
|
||||
union _xs_doubleints
|
||||
{
|
||||
real64 val;
|
||||
uint32_t ival[2];
|
||||
};
|
||||
|
||||
#if 0
|
||||
#define _xs_doublecopysgn(a,b) ((int32_t*)&a)[_xs_iexp_]&=~(((int32_t*)&b)[_xs_iexp_]&0x80000000)
|
||||
#define _xs_doubleisnegative(a) ((((int32_t*)&a)[_xs_iexp_])|0x80000000)
|
||||
#endif
|
||||
|
||||
// ====================================================================================================================
|
||||
// Constants
|
||||
// ====================================================================================================================
|
||||
const real64 _xs_doublemagic = real64 (6755399441055744.0); //2^52 * 1.5, uses limited precisicion to floor
|
||||
const real64 _xs_doublemagicdelta = (1.5e-8); //almost .5f = .5f + 1e^(number of exp bit)
|
||||
const real64 _xs_doublemagicroundeps = (.5f-_xs_doublemagicdelta); //almost .5f = .5f - 1e^(number of exp bit)
|
||||
|
||||
|
||||
// ====================================================================================================================
|
||||
// Prototypes
|
||||
// ====================================================================================================================
|
||||
static int32_t xs_CRoundToInt (real64 val, real64 dmr = _xs_doublemagic);
|
||||
static int32_t xs_ToInt (real64 val, real64 dme = -_xs_doublemagicroundeps);
|
||||
static int32_t xs_FloorToInt (real64 val, real64 dme = _xs_doublemagicroundeps);
|
||||
static int32_t xs_CeilToInt (real64 val, real64 dme = _xs_doublemagicroundeps);
|
||||
static int32_t xs_RoundToInt (real64 val);
|
||||
|
||||
//int32_t versions
|
||||
finline static int32_t xs_CRoundToInt (int32_t val) {return val;}
|
||||
finline static int32_t xs_ToInt (int32_t val) {return val;}
|
||||
|
||||
|
||||
|
||||
// ====================================================================================================================
|
||||
// Fix Class
|
||||
// ====================================================================================================================
|
||||
template <int32_t N> class xs_Fix
|
||||
{
|
||||
public:
|
||||
typedef int32_t Fix;
|
||||
|
||||
// ====================================================================================================================
|
||||
// Basic Conversion from Numbers
|
||||
// ====================================================================================================================
|
||||
finline static Fix ToFix (int32_t val) {return val<<N;}
|
||||
finline static Fix ToFix (real64 val) {return xs_ConvertToFixed(val);}
|
||||
|
||||
// ====================================================================================================================
|
||||
// Basic Conversion to Numbers
|
||||
// ====================================================================================================================
|
||||
finline static real64 ToReal (Fix f) {return real64(f)/real64(1<<N);}
|
||||
finline static int32_t ToInt (Fix f) {return f>>N;}
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
// ====================================================================================================================
|
||||
// Helper function - mainly to preserve _xs_DEFAULT_CONVERSION
|
||||
// ====================================================================================================================
|
||||
finline static int32_t xs_ConvertToFixed (real64 val)
|
||||
{
|
||||
#if _xs_DEFAULT_CONVERSION==0
|
||||
return xs_CRoundToInt(val, _xs_doublemagic/(1<<N));
|
||||
#else
|
||||
return (long)((val)*(1<<N));
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
finline static int32_t xs_ToFixed(int32_t n, real64 val)
|
||||
{
|
||||
#if _xs_DEFAULT_CONVERSION==0
|
||||
return xs_CRoundToInt(val, _xs_doublemagic/(1<<n));
|
||||
#else
|
||||
return (long)((val)*(1<<N));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ====================================================================================================================
|
||||
// ====================================================================================================================
|
||||
// Inline implementation
|
||||
// ====================================================================================================================
|
||||
// ====================================================================================================================
|
||||
finline static int32_t xs_CRoundToInt(real64 val, real64 dmr)
|
||||
{
|
||||
#if _xs_DEFAULT_CONVERSION==0
|
||||
_xs_doubleints uval;
|
||||
uval.val = val + dmr;
|
||||
return uval.ival[_xs_iman_];
|
||||
#else
|
||||
return int32_t(floor(val+.5));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================================================================
|
||||
finline static int32_t xs_ToInt(real64 val, real64 dme)
|
||||
{
|
||||
/* unused - something else I tried...
|
||||
_xs_doublecopysgn(dme,val);
|
||||
return xs_CRoundToInt(val+dme);
|
||||
return 0;
|
||||
*/
|
||||
|
||||
#if _MSC_VER >= 1400
|
||||
// VC++ 2005's standard cast is a little bit faster than this
|
||||
// magic number code. (Which is pretty amazing!) SSE has the
|
||||
// fastest C-style float->int conversion, but unfortunately,
|
||||
// checking for SSE support every time you need to do a
|
||||
// conversion completely negates its performance advantage.
|
||||
return int32_t(val);
|
||||
#else
|
||||
#if _xs_DEFAULT_CONVERSION==0
|
||||
return (val<0) ? xs_CRoundToInt(val-dme) :
|
||||
xs_CRoundToInt(val+dme);
|
||||
#else
|
||||
return int32_t(val);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================================================================
|
||||
finline static int32_t xs_FloorToInt(real64 val, real64 dme)
|
||||
{
|
||||
#if _xs_DEFAULT_CONVERSION==0
|
||||
return xs_CRoundToInt (val - dme);
|
||||
#else
|
||||
return floor(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================================================================
|
||||
finline static int32_t xs_CeilToInt(real64 val, real64 dme)
|
||||
{
|
||||
#if _xs_DEFAULT_CONVERSION==0
|
||||
return xs_CRoundToInt (val + dme);
|
||||
#else
|
||||
return ceil(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================================================================
|
||||
finline static int32_t xs_RoundToInt(real64 val)
|
||||
{
|
||||
#if _xs_DEFAULT_CONVERSION==0
|
||||
// Yes, it is important that two fadds be generated, so you cannot override the dmr
|
||||
// passed to xs_CRoundToInt with _xs_doublemagic + _xs_doublemagicdelta. If you do,
|
||||
// you'll end up with Banker's Rounding again.
|
||||
return xs_CRoundToInt (val + _xs_doublemagicdelta);
|
||||
#else
|
||||
return floor(val+.5);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====================================================================================================================
|
||||
// ====================================================================================================================
|
||||
// Unsigned variants
|
||||
// ====================================================================================================================
|
||||
// ====================================================================================================================
|
||||
finline static uint32_t xs_CRoundToUInt(real64 val)
|
||||
{
|
||||
return (uint32_t)xs_CRoundToInt(val);
|
||||
}
|
||||
|
||||
finline static uint32_t xs_FloorToUInt(real64 val)
|
||||
{
|
||||
return (uint32_t)xs_FloorToInt(val);
|
||||
}
|
||||
|
||||
finline static uint32_t xs_CeilToUInt(real64 val)
|
||||
{
|
||||
return (uint32_t)xs_CeilToInt(val);
|
||||
}
|
||||
|
||||
finline static uint32_t xs_RoundToUInt(real64 val)
|
||||
{
|
||||
return (uint32_t)xs_RoundToInt(val);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====================================================================================================================
|
||||
// ====================================================================================================================
|
||||
#endif // _xs_FLOAT_H_
|
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "musicblock.h"
|
||||
|
||||
class OPLmusicBlock : public musicBlock
|
||||
{
|
||||
public:
|
||||
OPLmusicBlock(int core, int numchips);
|
||||
virtual ~OPLmusicBlock();
|
||||
|
||||
bool ServiceStream(void *buff, int numbytes);
|
||||
void ResetChips(int numchips);
|
||||
|
||||
virtual void Restart();
|
||||
|
||||
protected:
|
||||
virtual int PlayTick() = 0;
|
||||
void OffsetSamples(float *buff, int count);
|
||||
|
||||
uint8_t *score;
|
||||
uint8_t *scoredata;
|
||||
double NextTickIn;
|
||||
double SamplesPerTick;
|
||||
double LastOffset;
|
||||
int playingcount;
|
||||
int NumChips;
|
||||
int currentCore;
|
||||
bool Looping;
|
||||
bool FullPan;
|
||||
};
|
||||
|
||||
class OPLmusicFile : public OPLmusicBlock
|
||||
{
|
||||
public:
|
||||
OPLmusicFile(const void *data, size_t length, int core, int numchips, const char *&errormessage);
|
||||
virtual ~OPLmusicFile();
|
||||
|
||||
bool IsValid() const;
|
||||
void SetLooping(bool loop);
|
||||
void Restart();
|
||||
|
||||
protected:
|
||||
OPLmusicFile(int core, int numchips) : OPLmusicBlock(core, numchips) {}
|
||||
int PlayTick();
|
||||
|
||||
enum { RDosPlay, IMF, DosBox1, DosBox2 } RawPlayer;
|
||||
int ScoreLen;
|
||||
int WhichChip;
|
||||
};
|
|
@ -1,99 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
// Operator registers (21 of each):
|
||||
OPL_REGS_TREMOLO = 0x20,
|
||||
OPL_REGS_LEVEL = 0x40,
|
||||
OPL_REGS_ATTACK = 0x60,
|
||||
OPL_REGS_SUSTAIN = 0x80,
|
||||
OPL_REGS_WAVEFORM = 0xE0,
|
||||
|
||||
// Voice registers (9 of each):
|
||||
OPL_REGS_FREQ_1 = 0xA0,
|
||||
OPL_REGS_FREQ_2 = 0xB0,
|
||||
OPL_REGS_FEEDBACK = 0xC0,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
OPL_REG_WAVEFORM_ENABLE = 0x01,
|
||||
OPL_REG_TIMER1 = 0x02,
|
||||
OPL_REG_TIMER2 = 0x03,
|
||||
OPL_REG_TIMER_CTRL = 0x04,
|
||||
OPL_REG_FM_MODE = 0x08,
|
||||
OPL_REG_PERCUSSION_MODE = 0xBD,
|
||||
|
||||
OPL_REG_OPL3_ENABLE = 0x105,
|
||||
OPL_REG_4OPMODE_ENABLE = 0x104,
|
||||
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
NO_VOLUME = 0x3f,
|
||||
MAX_ATTACK_DECAY = 0xff,
|
||||
NO_SUSTAIN_MAX_RELEASE = 0xf,
|
||||
WAVEFORM_ENABLED = 0x20
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
OPL_NUM_VOICES = 9,
|
||||
OPL3_NUM_VOICES = 18,
|
||||
MAXOPL2CHIPS = 8,
|
||||
NUM_VOICES = (OPL_NUM_VOICES * MAXOPL2CHIPS),
|
||||
|
||||
NUM_CHANNELS = 16,
|
||||
CHAN_PERCUSSION = 15,
|
||||
|
||||
VIBRATO_THRESHOLD = 40,
|
||||
MIN_SUSTAIN = 0x40,
|
||||
HIGHEST_NOTE = 127,
|
||||
|
||||
};
|
||||
|
||||
struct GenMidiVoice;
|
||||
struct genmidi_op_t;
|
||||
|
||||
struct OPLio
|
||||
{
|
||||
virtual ~OPLio();
|
||||
|
||||
void WriteOperator(uint32_t regbase, uint32_t channel, int index, uint8_t data2);
|
||||
void LoadOperatorData(uint32_t channel, int op_index, genmidi_op_t *op_data, bool maxlevel, bool vibrato);
|
||||
void WriteValue(uint32_t regbase, uint32_t channel, uint8_t value);
|
||||
void WriteFrequency(uint32_t channel, uint32_t freq, uint32_t octave, uint32_t keyon);
|
||||
void WriteVolume(uint32_t channel, GenMidiVoice *voice, uint32_t v1, uint32_t v2, uint32_t v3);
|
||||
void WritePan(uint32_t channel, GenMidiVoice *voice, int pan);
|
||||
void WriteTremolo(uint32_t channel, GenMidiVoice *voice, bool vibrato);
|
||||
void WriteInstrument(uint32_t channel, GenMidiVoice *voice, bool vibrato);
|
||||
void WriteInitState(bool opl3);
|
||||
void MuteChannel(uint32_t chan);
|
||||
void StopPlayback();
|
||||
|
||||
virtual int Init(int core, uint32_t numchips, bool stereo, bool initopl3);
|
||||
virtual void Reset();
|
||||
virtual void WriteRegister(int which, uint32_t reg, uint8_t data);
|
||||
virtual void SetClockRate(double samples_per_tick);
|
||||
virtual void WriteDelay(int ticks);
|
||||
|
||||
class OPLEmul *chips[OPL_NUM_VOICES];
|
||||
uint32_t NumChannels;
|
||||
uint32_t NumChips;
|
||||
bool IsOPL3;
|
||||
};
|
||||
|
||||
struct OPLChannel
|
||||
{
|
||||
uint32_t Instrument;
|
||||
uint8_t Volume;
|
||||
uint8_t Panning;
|
||||
int8_t Pitch;
|
||||
uint8_t Sustain;
|
||||
bool Vibrato;
|
||||
uint8_t Expression;
|
||||
uint16_t PitchSensitivity;
|
||||
uint16_t RPN;
|
||||
};
|
|
@ -41,7 +41,7 @@ else()
|
|||
endif()
|
||||
|
||||
|
||||
include_directories( "${CMAKE_CURRENT_SOURCE_DIR}/../libraries/dumb/include" "${ZLIB_INCLUDE_DIR}" "${TIMIDITYPP_INCLUDE_DIR}" "${OPLSYNTH_INCLUDE_DIR}" "${GME_INCLUDE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||
include_directories( "${CMAKE_CURRENT_SOURCE_DIR}/../libraries/dumb/include" "${ZLIB_INCLUDE_DIR}" "${TIMIDITYPP_INCLUDE_DIR}" "${GME_INCLUDE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||
|
||||
if (WIN32)
|
||||
set( PLAT_SOURCES
|
||||
|
@ -65,7 +65,6 @@ add_library( zmusic STATIC
|
|||
${HEADER_FILES}
|
||||
i_module.cpp
|
||||
mididevices/music_base_mididevice.cpp
|
||||
mididevices/music_opl_mididevice.cpp
|
||||
mididevices/music_timiditypp_mididevice.cpp
|
||||
mididevices/music_fluidsynth_mididevice.cpp
|
||||
mididevices/music_softsynth_mididevice.cpp
|
||||
|
@ -78,7 +77,6 @@ add_library( zmusic STATIC
|
|||
streamsources/music_dumb.cpp
|
||||
streamsources/music_gme.cpp
|
||||
streamsources/music_libsndfile.cpp
|
||||
streamsources/music_opl.cpp
|
||||
streamsources/music_xa.cpp
|
||||
musicformats/music_stream.cpp
|
||||
musicformats/music_midi.cpp
|
||||
|
@ -90,7 +88,7 @@ add_library( zmusic STATIC
|
|||
zmusic/zmusic.cpp
|
||||
${PLAT_SOURCES}
|
||||
)
|
||||
target_link_libraries( zmusic dumb gme oplsynth timidityplus)
|
||||
target_link_libraries( zmusic dumb gme timidityplus)
|
||||
|
||||
if( NOT DYN_SNDFILE AND SNDFILE_FOUND )
|
||||
include_directories( "${SNDFILE_INCLUDE_DIRS}" )
|
||||
|
|
|
@ -1,326 +0,0 @@
|
|||
/*
|
||||
** music_opl_mididevice.cpp
|
||||
** Provides an emulated OPL implementation of a MIDI output device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/mus2midi.h"
|
||||
#include "oplsynth/opl.h"
|
||||
#include "oplsynth/opl_mus_player.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
OPLConfig oplConfig;
|
||||
|
||||
// OPL implementation of a MIDI output device -------------------------------
|
||||
|
||||
class OPLMIDIDevice : public SoftSynthMIDIDevice, protected OPLmusicBlock
|
||||
{
|
||||
public:
|
||||
OPLMIDIDevice(int core);
|
||||
int OpenRenderer() override;
|
||||
void Close() override;
|
||||
int GetTechnology() const override;
|
||||
std::string GetStats() override;
|
||||
|
||||
protected:
|
||||
void CalcTickRate() override;
|
||||
int PlayTick() override;
|
||||
void HandleEvent(int status, int parm1, int parm2) override;
|
||||
void HandleLongEvent(const uint8_t *data, int len) override;
|
||||
void ComputeOutput(float *buffer, int len) override;
|
||||
bool ServiceStream(void *buff, int numbytes) override;
|
||||
int GetDeviceType() const override { return MDEV_OPL; }
|
||||
};
|
||||
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice Contructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
OPLMIDIDevice::OPLMIDIDevice(int core)
|
||||
: SoftSynthMIDIDevice((int)OPL_SAMPLE_RATE), OPLmusicBlock(core, oplConfig.numchips)
|
||||
{
|
||||
FullPan = oplConfig.fullpan;
|
||||
memcpy(OPLinstruments, oplConfig.OPLinstruments, sizeof(OPLinstruments));
|
||||
StreamBlockSize = 14;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: Open
|
||||
//
|
||||
// Returns 0 on success.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int OPLMIDIDevice::OpenRenderer()
|
||||
{
|
||||
if (io == NULL || 0 == (NumChips = io->Init(currentCore, NumChips, FullPan, true)))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
isMono = !FullPan && !io->IsOPL3;
|
||||
stopAllVoices();
|
||||
resetAllControllers(100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: Close
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::Close()
|
||||
{
|
||||
SoftSynthMIDIDevice::Close();
|
||||
io->Reset();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: GetTechnology
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int OPLMIDIDevice::GetTechnology() const
|
||||
{
|
||||
return MIDIDEV_FMSYNTH;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: CalcTickRate
|
||||
//
|
||||
// Tempo is the number of microseconds per quarter note.
|
||||
// Division is the number of ticks per quarter note.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::CalcTickRate()
|
||||
{
|
||||
SoftSynthMIDIDevice::CalcTickRate();
|
||||
io->SetClockRate(OPLmusicBlock::SamplesPerTick = SoftSynthMIDIDevice::SamplesPerTick);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: PlayTick
|
||||
//
|
||||
// We derive from two base classes that both define PlayTick(), so we need
|
||||
// to be unambiguous about which one to use.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int OPLMIDIDevice::PlayTick()
|
||||
{
|
||||
return SoftSynthMIDIDevice::PlayTick();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: HandleEvent
|
||||
//
|
||||
// Processes a normal MIDI event.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
int command = status & 0xF0;
|
||||
int channel = status & 0x0F;
|
||||
|
||||
// Swap voices 9 and 15, because their roles are reversed
|
||||
// in MUS and MIDI formats.
|
||||
if (channel == 9)
|
||||
{
|
||||
channel = 15;
|
||||
}
|
||||
else if (channel == 15)
|
||||
{
|
||||
channel = 9;
|
||||
}
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case MIDI_NOTEOFF:
|
||||
playingcount--;
|
||||
noteOff(channel, parm1);
|
||||
break;
|
||||
|
||||
case MIDI_NOTEON:
|
||||
playingcount++;
|
||||
noteOn(channel, parm1, parm2);
|
||||
break;
|
||||
|
||||
case MIDI_POLYPRESS:
|
||||
//DEBUGOUT("Unhandled note aftertouch: Channel %d, note %d, value %d\n", channel, parm1, parm2);
|
||||
break;
|
||||
|
||||
case MIDI_CTRLCHANGE:
|
||||
switch (parm1)
|
||||
{
|
||||
// some controllers here get passed on but are not handled by the player.
|
||||
//case 0: changeBank(channel, parm2); break;
|
||||
case 1: changeModulation(channel, parm2); break;
|
||||
case 6: changeExtended(channel, ctrlDataEntryHi, parm2); break;
|
||||
case 7: changeVolume(channel, parm2, false); break;
|
||||
case 10: changePanning(channel, parm2); break;
|
||||
case 11: changeVolume(channel, parm2, true); break;
|
||||
case 38: changeExtended(channel, ctrlDataEntryLo, parm2); break;
|
||||
case 64: changeSustain(channel, parm2); break;
|
||||
//case 67: changeSoftPedal(channel, parm2); break;
|
||||
//case 91: changeReverb(channel, parm2); break;
|
||||
//case 93: changeChorus(channel, parm2); break;
|
||||
case 98: changeExtended(channel, ctrlNRPNLo, parm2); break;
|
||||
case 99: changeExtended(channel, ctrlNRPNHi, parm2); break;
|
||||
case 100: changeExtended(channel, ctrlRPNLo, parm2); break;
|
||||
case 101: changeExtended(channel, ctrlRPNHi, parm2); break;
|
||||
case 120: allNotesOff(channel, parm2); break;
|
||||
case 121: resetControllers(channel, 100); break;
|
||||
case 123: notesOff(channel, parm2); break;
|
||||
//case 126: changeMono(channel, parm2); break;
|
||||
//case 127: changePoly(channel, parm2); break;
|
||||
default:
|
||||
//DEBUGOUT("Unhandled controller: Channel %d, controller %d, value %d\n", channel, parm1, parm2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case MIDI_PRGMCHANGE:
|
||||
programChange(channel, parm1);
|
||||
break;
|
||||
|
||||
case MIDI_CHANPRESS:
|
||||
//DEBUGOUT("Unhandled channel aftertouch: Channel %d, value %d\n", channel, parm1, 0);
|
||||
break;
|
||||
|
||||
case MIDI_PITCHBEND:
|
||||
changePitch(channel, parm1, parm2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: ComputeOutput
|
||||
//
|
||||
// We override ServiceStream, so this function is never actually called.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: ServiceStream
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool OPLMIDIDevice::ServiceStream(void *buff, int numbytes)
|
||||
{
|
||||
return OPLmusicBlock::ServiceStream(buff, numbytes);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: GetStats
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
std::string OPLMIDIDevice::GetStats()
|
||||
{
|
||||
std::string out;
|
||||
for (uint32_t i = 0; i < io->NumChannels; ++i)
|
||||
{
|
||||
char star = '*';
|
||||
if (voices[i].index == ~0u)
|
||||
{
|
||||
star = '/';
|
||||
}
|
||||
else if (voices[i].sustained)
|
||||
{
|
||||
star = '-';
|
||||
}
|
||||
else if (voices[i].current_instr_voice == &voices[i].current_instr->voices[1])
|
||||
{
|
||||
star = '\\';
|
||||
}
|
||||
else
|
||||
{
|
||||
star = '+';
|
||||
}
|
||||
out += star;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
MIDIDevice* CreateOplMIDIDevice(const char *Args)
|
||||
{
|
||||
if (!oplConfig.genmidiset) throw std::runtime_error("Cannot play OPL without GENMIDI data");
|
||||
int core = oplConfig.core;
|
||||
if (Args != NULL && *Args >= '0' && *Args < '4') core = *Args - '0';
|
||||
return new OPLMIDIDevice(core);
|
||||
}
|
|
@ -195,21 +195,6 @@ bool MIDIStreamer::IsValid() const
|
|||
|
||||
EMidiDevice MIDIStreamer::SelectMIDIDevice(EMidiDevice device)
|
||||
{
|
||||
/* MIDI are played as:
|
||||
- OPL:
|
||||
- if explicitly selected by $mididevice
|
||||
- when snd_mididevice is -3 and no midi device is set for the song
|
||||
|
||||
- Timidity:
|
||||
- if explicitly selected by $mididevice
|
||||
- when snd_mididevice is -2 and no midi device is set for the song
|
||||
|
||||
- MMAPI (Win32 only):
|
||||
- if explicitly selected by $mididevice (non-Win32 redirects this to Sound System)
|
||||
- when snd_mididevice is >= 0 and no midi device is set for the song
|
||||
- as fallback when both OPL and Timidity failed and snd_mididevice is >= 0
|
||||
*/
|
||||
|
||||
// Choose the type of MIDI device we want.
|
||||
if (device != MDEV_DEFAULT)
|
||||
{
|
||||
|
@ -220,7 +205,6 @@ EMidiDevice MIDIStreamer::SelectMIDIDevice(EMidiDevice device)
|
|||
case -1: return MDEV_SNDSYS;
|
||||
case -4:
|
||||
case -2: return MDEV_TIMIDITY;
|
||||
case -3: return MDEV_OPL;
|
||||
case -5: return MDEV_FLUIDSYNTH;
|
||||
default:
|
||||
#ifdef _WIN32
|
||||
|
@ -267,10 +251,6 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype, int samplerate)
|
|||
break;
|
||||
#endif // HAVE_FLUIDSYNTH
|
||||
|
||||
case MDEV_OPL:
|
||||
dev = CreateOplMIDIDevice(Args.c_str());
|
||||
break;
|
||||
|
||||
case MDEV_TIMIDITY:
|
||||
dev = CreateTimidityPPMIDIDevice(Args.c_str(), samplerate);
|
||||
break;
|
||||
|
@ -291,7 +271,6 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype, int samplerate)
|
|||
#ifdef _WIN32
|
||||
else if (!checked[MDEV_MMAPI]) devtype = MDEV_MMAPI;
|
||||
#endif
|
||||
else if (!checked[MDEV_OPL]) devtype = MDEV_OPL;
|
||||
|
||||
if (devtype == MDEV_DEFAULT)
|
||||
{
|
||||
|
@ -304,7 +283,7 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype, int samplerate)
|
|||
{
|
||||
static const char *devnames[] = {
|
||||
"Windows Default",
|
||||
"OPL",
|
||||
"",
|
||||
"",
|
||||
"Timidity++",
|
||||
"FluidSynth",
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
/*
|
||||
** music_opl.cpp
|
||||
** Plays raw OPL formats
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "streamsource.h"
|
||||
#include "oplsynth/opl.h"
|
||||
#include "oplsynth/opl_mus_player.h"
|
||||
#include "../../libraries/music_common/fileio.h"
|
||||
#include "zmusic/midiconfig.h"
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPL file played by a software OPL2 synth and streamed through the sound system
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class OPLMUSSong : public StreamSource
|
||||
{
|
||||
public:
|
||||
OPLMUSSong (MusicIO::FileInterface *reader, OPLConfig *config);
|
||||
~OPLMUSSong ();
|
||||
bool Start() override;
|
||||
void ChangeSettingInt(const char *name, int value) override;
|
||||
SoundStreamInfo GetFormat() override;
|
||||
|
||||
protected:
|
||||
bool GetData(void *buffer, size_t len) override;
|
||||
|
||||
OPLmusicFile *Music;
|
||||
int current_opl_core;
|
||||
};
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
OPLMUSSong::OPLMUSSong(MusicIO::FileInterface* reader, OPLConfig* config)
|
||||
{
|
||||
const char* error = nullptr;
|
||||
reader->seek(0, SEEK_END);
|
||||
auto fs = reader->tell();
|
||||
reader->seek(0, SEEK_SET);
|
||||
std::vector<uint8_t> data(fs);
|
||||
reader->read(data.data(), (int)data.size());
|
||||
Music = new OPLmusicFile(data.data(), data.size(), config->core, config->numchips, error);
|
||||
if (error)
|
||||
{
|
||||
delete Music;
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
SoundStreamInfo OPLMUSSong::GetFormat()
|
||||
{
|
||||
int samples = int(OPL_SAMPLE_RATE / 14);
|
||||
return { samples * 4, int(OPL_SAMPLE_RATE), current_opl_core == 0? 1:2 };
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
OPLMUSSong::~OPLMUSSong ()
|
||||
{
|
||||
if (Music != NULL)
|
||||
{
|
||||
delete Music;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMUSSong::ChangeSettingInt(const char * name, int val)
|
||||
{
|
||||
if (!strcmp(name, "opl.numchips"))
|
||||
Music->ResetChips (val);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool OPLMUSSong::Start()
|
||||
{
|
||||
Music->SetLooping (m_Looping);
|
||||
Music->Restart ();
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool OPLMUSSong::GetData(void *buffer, size_t len)
|
||||
{
|
||||
return Music->ServiceStream(buffer, int(len)) ? len : 0;
|
||||
}
|
||||
|
||||
StreamSource *OPL_OpenSong(MusicIO::FileInterface* reader, OPLConfig *config)
|
||||
{
|
||||
return new OPLMUSSong(reader, config);
|
||||
}
|
|
@ -33,7 +33,6 @@
|
|||
*/
|
||||
#include <algorithm>
|
||||
#include "timiditypp/timidity.h"
|
||||
#include "oplsynth/oplio.h"
|
||||
#include "../../libraries/dumb/include/dumb.h"
|
||||
|
||||
#include "zmusic.h"
|
||||
|
@ -61,8 +60,6 @@ void ZMusic_SetCallbacks(const Callbacks* cb)
|
|||
|
||||
void ZMusic_SetGenMidi(const uint8_t* data)
|
||||
{
|
||||
memcpy(oplConfig.OPLinstruments, data, 175 * 36);
|
||||
oplConfig.genmidiset = true;
|
||||
}
|
||||
|
||||
template<class valtype>
|
||||
|
@ -224,28 +221,6 @@ bool ChangeMusicSetting(ZMusic::EIntConfigKey key, MusInfo *currSong, int value,
|
|||
ChangeAndReturn(fluidConfig.fluid_chorus_type, value, pRealValue);
|
||||
return false;
|
||||
|
||||
case opl_numchips:
|
||||
if (value <= 0)
|
||||
value = 1;
|
||||
else if (value > MAXOPL2CHIPS)
|
||||
value = MAXOPL2CHIPS;
|
||||
|
||||
if (currSong != NULL)
|
||||
currSong->ChangeSettingInt("opl.numchips", value);
|
||||
|
||||
ChangeAndReturn(oplConfig.numchips, value, pRealValue);
|
||||
return false;
|
||||
|
||||
case opl_core:
|
||||
if (value < 0) value = 0;
|
||||
else if (value > 3) value = 3;
|
||||
ChangeAndReturn(oplConfig.core, value, pRealValue);
|
||||
return devType() == MDEV_OPL;
|
||||
|
||||
case opl_fullpan:
|
||||
ChangeAndReturn(oplConfig.fullpan, value, pRealValue);
|
||||
return false;
|
||||
|
||||
case timidity_modulation_wheel:
|
||||
ChangeVarSync(TimidityPlus::timidity_modulation_wheel, value);
|
||||
if (pRealValue) *pRealValue = value;
|
||||
|
|
|
@ -56,7 +56,6 @@
|
|||
#define GZIP_FCOMMENT 16
|
||||
|
||||
class MIDIDevice;
|
||||
class OPLmusicFile;
|
||||
class StreamSource;
|
||||
class MusInfo;
|
||||
|
||||
|
@ -230,14 +229,7 @@ MusInfo *ZMusic_OpenSong (MusicIO::FileInterface *reader, EMidiDevice device, co
|
|||
// Check for various raw OPL formats
|
||||
else
|
||||
{
|
||||
if (
|
||||
(id[0] == MAKE_ID('R', 'A', 'W', 'A') && id[1] == MAKE_ID('D', 'A', 'T', 'A')) || // Rdos Raw OPL
|
||||
(id[0] == MAKE_ID('D', 'B', 'R', 'A') && id[1] == MAKE_ID('W', 'O', 'P', 'L')) || // DosBox Raw OPL
|
||||
(id[0] == MAKE_ID('A', 'D', 'L', 'I') && *((uint8_t*)id + 4) == 'B')) // Martin Fernandez's modified IMF
|
||||
{
|
||||
streamsource = OPL_OpenSong(reader, &oplConfig);
|
||||
}
|
||||
else if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'X', 'A')))
|
||||
if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'X', 'A')))
|
||||
{
|
||||
streamsource = XA_OpenSong(reader); // this takes over the reader.
|
||||
reader = nullptr; // We do not own this anymore.
|
||||
|
|
|
@ -961,7 +961,6 @@ endif()
|
|||
|
||||
if( CMAKE_COMPILER_IS_GNUCXX )
|
||||
# GCC misoptimizes this file
|
||||
#set_source_files_properties( oplsynth/fmopl.cpp PROPERTIES COMPILE_FLAGS "-fno-tree-dominator-opts -fno-tree-fre" )
|
||||
endif()
|
||||
|
||||
if( APPLE )
|
||||
|
|
|
@ -215,29 +215,6 @@ static MusicIO::SoundFontReaderInterface* mus_openSoundFont(const char* sfname,
|
|||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Pass some basic working data to the music backend
|
||||
// We do this once at startup for everything available.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void SetupGenMidi()
|
||||
{
|
||||
// The OPL renderer should not care about where this comes from.
|
||||
// Note: No I_Error here - this needs to be consistent with the rest of the music code.
|
||||
auto lump = fileSystem.FindFile("engine/genmidi.op2");
|
||||
if (lump < 0)
|
||||
{
|
||||
Printf("No GENMIDI lump found. OPL playback not available.");
|
||||
return;
|
||||
}
|
||||
auto data = fileSystem.OpenFileReader(lump);
|
||||
|
||||
auto genmidi = data.Read();
|
||||
if (genmidi.Size() < 8 + 175 * 36 || memcmp(genmidi.Data(), "#OPL_II#", 8)) return;
|
||||
ZMusic_SetGenMidi(genmidi.Data()+8);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
|
@ -268,7 +245,6 @@ void Mus_Init(void)
|
|||
//callbacks.DumbVorbisDecode = dumb_decode_vorbis_;
|
||||
|
||||
ZMusic_SetCallbacks(&callbacks);
|
||||
SetupGenMidi();
|
||||
timerSetCallback(S_UpdateMusic);
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Loading…
Reference in a new issue