2013-09-04 13:37:36 +00:00
|
|
|
|
#include "Hiopl.h"
|
|
|
|
|
|
|
|
|
|
#include <assert.h>
|
2014-11-01 13:26:48 +00:00
|
|
|
|
#include "JuceHeader.h"
|
2013-09-04 13:37:36 +00:00
|
|
|
|
|
2014-09-24 14:53:40 +00:00
|
|
|
|
// A wrapper around the DOSBox and ZDoom OPL emulators.
|
2013-09-04 13:37:36 +00:00
|
|
|
|
|
2014-09-24 14:53:40 +00:00
|
|
|
|
Hiopl::Hiopl(int buflen, Emulator emulator) {
|
2015-01-06 03:13:44 +00:00
|
|
|
|
//InitCaptureVariables();
|
2014-11-02 15:21:51 +00:00
|
|
|
|
|
2013-09-04 13:37:36 +00:00
|
|
|
|
adlib = new DBOPL::Handler();
|
2014-09-24 14:53:40 +00:00
|
|
|
|
zdoom = JavaOPLCreate(false);
|
|
|
|
|
|
2013-09-10 17:30:05 +00:00
|
|
|
|
_op1offset[1] = 0x0;
|
|
|
|
|
_op1offset[2] = 0x1;
|
|
|
|
|
_op1offset[3] = 0x2;
|
|
|
|
|
_op1offset[4] = 0x8;
|
|
|
|
|
_op1offset[5] = 0x9;
|
|
|
|
|
_op1offset[6] = 0xa;
|
|
|
|
|
_op1offset[7] = 0x10;
|
|
|
|
|
_op1offset[8] = 0x11;
|
|
|
|
|
_op1offset[9] = 0x12;
|
|
|
|
|
|
|
|
|
|
_op2offset[1] = 0x3;
|
|
|
|
|
_op2offset[2] = 0x4;
|
|
|
|
|
_op2offset[3] = 0x5;
|
|
|
|
|
_op2offset[4] = 0xb;
|
|
|
|
|
_op2offset[5] = 0xc;
|
|
|
|
|
_op2offset[6] = 0xd;
|
|
|
|
|
_op2offset[7] = 0x13;
|
|
|
|
|
_op2offset[8] = 0x14;
|
|
|
|
|
_op2offset[9] = 0x15;
|
|
|
|
|
|
2014-09-28 06:29:27 +00:00
|
|
|
|
SetEmulator(emulator);
|
|
|
|
|
_ClearRegisters();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hiopl::_ClearRegisters() {
|
2013-09-10 17:30:05 +00:00
|
|
|
|
for (int i = 0; i < 256; i++) {
|
|
|
|
|
_WriteReg(i, 0);
|
|
|
|
|
}
|
2013-09-04 13:37:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-09-24 14:53:40 +00:00
|
|
|
|
void Hiopl::SetEmulator(Emulator emulator) {
|
|
|
|
|
this->emulator = emulator;
|
2013-09-04 13:37:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-04 14:19:22 +00:00
|
|
|
|
void Hiopl::Generate(int length, float* buffer) {
|
|
|
|
|
// This would be better done using Juce's built in audio format conversion.
|
2014-09-24 14:53:40 +00:00
|
|
|
|
if (DOSBOX == emulator) {
|
2014-09-28 07:30:42 +00:00
|
|
|
|
Bit32s* buf32 = new Bit32s[length];
|
|
|
|
|
adlib->Generate(length, buf32);
|
2014-09-24 14:53:40 +00:00
|
|
|
|
for (int i = 0; i < length; i++) {
|
2014-09-28 07:30:42 +00:00
|
|
|
|
// The magic divisor is tuned to match to ZDoom output amplitude.
|
2014-11-01 13:26:48 +00:00
|
|
|
|
buffer[i] = (float)(buf32[i])/8200.0f;
|
2014-09-24 14:53:40 +00:00
|
|
|
|
}
|
2014-09-28 07:30:42 +00:00
|
|
|
|
delete buf32;
|
2014-09-24 14:53:40 +00:00
|
|
|
|
} else if (ZDOOM == emulator) {
|
|
|
|
|
// ZDoom hacked to write mono samples
|
|
|
|
|
zdoom->Update(buffer, length);
|
2013-09-04 14:19:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-04 13:37:36 +00:00
|
|
|
|
void Hiopl::SetSampleRate(int hz) {
|
|
|
|
|
adlib->Init(hz);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-10 17:30:05 +00:00
|
|
|
|
void Hiopl::_WriteReg(Bit32u reg, Bit8u value, Bit8u mask) {
|
|
|
|
|
if (mask > 0) {
|
|
|
|
|
value = (regCache[reg] & (~mask)) | (value & mask);
|
|
|
|
|
}
|
2014-09-28 07:30:42 +00:00
|
|
|
|
// Write to the registers of both emulators.
|
2014-09-28 06:29:27 +00:00
|
|
|
|
//if (DOSBOX == emulator) {
|
2014-09-24 14:53:40 +00:00
|
|
|
|
adlib->WriteReg(reg, value);
|
2014-09-28 06:29:27 +00:00
|
|
|
|
//} else if (ZDOOM == emulator) {
|
2014-09-24 14:53:40 +00:00
|
|
|
|
zdoom->WriteReg(reg, value);
|
2014-09-28 06:29:27 +00:00
|
|
|
|
//}
|
2013-09-04 13:37:36 +00:00
|
|
|
|
regCache[reg] = value;
|
2015-01-06 03:13:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Bit8u Hiopl::_ReadReg(Bit32u reg) {
|
|
|
|
|
return regCache[reg];
|
2013-09-04 13:37:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-04 15:36:55 +00:00
|
|
|
|
void Hiopl::_ClearRegBits(Bit32u reg, Bit8u mask) {
|
|
|
|
|
_WriteReg(reg, regCache[reg] & ~mask);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-12 13:58:22 +00:00
|
|
|
|
void Hiopl::EnableWaveformControl() {
|
|
|
|
|
_WriteReg(0x01, 0x20);
|
2013-09-04 13:37:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-12 13:58:22 +00:00
|
|
|
|
void Hiopl::SetWaveform(int ch, int osc, Waveform wave) {
|
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
2013-11-05 16:50:37 +00:00
|
|
|
|
_WriteReg(0xe0+offset, (Bit8u)wave, 0x7);
|
2013-09-04 13:37:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-10 17:30:05 +00:00
|
|
|
|
void Hiopl::SetAttenuation(int ch, int osc, int level) {
|
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
|
|
|
|
_WriteReg(0x40+offset, (Bit8u)level, 0x3f);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-22 11:10:28 +00:00
|
|
|
|
void Hiopl::SetKsl(int ch, int osc, int level) {
|
2013-09-12 15:52:06 +00:00
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
2013-09-13 17:13:18 +00:00
|
|
|
|
_WriteReg(0x40+offset, (Bit8u)(level<<6), 0xc0);
|
2013-09-12 15:52:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-10 17:30:05 +00:00
|
|
|
|
void Hiopl::SetFrequencyMultiple(int ch, int osc, FreqMultiple mult) {
|
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
2013-09-12 13:58:22 +00:00
|
|
|
|
_WriteReg(0x20+offset, (Bit8u)mult, 0xf);
|
2013-09-10 17:30:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-12 12:17:31 +00:00
|
|
|
|
void Hiopl::SetEnvelopeAttack(int ch, int osc, int t) {
|
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
|
|
|
|
_WriteReg(0x60+offset, (Bit8u)t<<4, 0xf0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hiopl::SetEnvelopeDecay(int ch, int osc, int t) {
|
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
|
|
|
|
_WriteReg(0x60+offset, (Bit8u)t, 0x0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hiopl::SetEnvelopeSustain(int ch, int osc, int level) {
|
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
|
|
|
|
_WriteReg(0x80+offset, (Bit8u)level<<4, 0xf0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hiopl::SetEnvelopeRelease(int ch, int osc, int t) {
|
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
|
|
|
|
_WriteReg(0x80+offset, (Bit8u)t, 0x0f);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-14 16:06:45 +00:00
|
|
|
|
void Hiopl::EnableTremolo(int ch, int osc, bool enable) {
|
2013-09-12 13:10:19 +00:00
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
2013-09-14 16:06:45 +00:00
|
|
|
|
_WriteReg(0x20+offset, enable ? 0x80 : 0x0, 0x80);
|
2013-09-12 13:10:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-14 16:06:45 +00:00
|
|
|
|
void Hiopl::EnableVibrato(int ch, int osc, bool enable) {
|
2013-09-12 15:52:06 +00:00
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
2013-09-14 16:06:45 +00:00
|
|
|
|
_WriteReg(0x20+offset, enable ? 0x40 : 0x0, 0x40);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-22 11:10:28 +00:00
|
|
|
|
void Hiopl::TremoloDepth(bool high) {
|
|
|
|
|
_WriteReg(0xbd, high ? 0x80 : 0x0, 0x80);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hiopl::VibratoDepth(bool high) {
|
|
|
|
|
_WriteReg(0xbd, high ? 0x40 : 0x0, 0x40);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-14 16:06:45 +00:00
|
|
|
|
void Hiopl::EnableSustain(int ch, int osc, bool enable) {
|
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
|
|
|
|
_WriteReg(0x20+offset, enable ? 0x20 : 0x0, 0x20);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-22 11:10:28 +00:00
|
|
|
|
void Hiopl::EnableKsr(int ch, int osc, bool enable) {
|
2013-09-14 16:06:45 +00:00
|
|
|
|
int offset = this->_GetOffset(ch, osc);
|
|
|
|
|
_WriteReg(0x20+offset, enable ? 0x10 : 0x0, 0x10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hiopl::EnableAdditiveSynthesis(int ch, bool enable) {
|
|
|
|
|
int offset = this->_GetOffset(ch);
|
|
|
|
|
_WriteReg(0xc0+offset, enable ? 0x1 : 0x0, 0x1);
|
2013-09-12 15:52:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-12 13:58:22 +00:00
|
|
|
|
void Hiopl::SetModulatorFeedback(int ch, int level) {
|
|
|
|
|
int offset = this->_GetOffset(ch);
|
|
|
|
|
_WriteReg(0xc0+offset, (Bit8u)level, 0x0e);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-01 11:55:03 +00:00
|
|
|
|
void Hiopl::SetPercussionMode(bool enable) {
|
|
|
|
|
_WriteReg(0xbd, enable ? 0x20 : 0x0, 0x20);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hiopl::HitPercussion(Drum drum) {
|
|
|
|
|
Bit8u mask = (Bit8u)drum;
|
|
|
|
|
_WriteReg(0xbd, mask, mask);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hiopl::ReleasePercussion() {
|
|
|
|
|
_WriteReg(0xbd, 0x0, 0x1f);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-04 15:36:55 +00:00
|
|
|
|
void Hiopl::KeyOn(int ch, float frqHz) {
|
2013-12-23 07:51:29 +00:00
|
|
|
|
Hiopl::SetFrequency(ch, frqHz, true);
|
2013-09-04 15:36:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hiopl::KeyOff(int ch) {
|
2013-09-12 13:58:22 +00:00
|
|
|
|
int offset = this->_GetOffset(ch);
|
|
|
|
|
_ClearRegBits(0xb0+offset, 0x20);
|
2013-09-04 15:36:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-06 13:25:51 +00:00
|
|
|
|
bool Hiopl::IsActive(int ch) {
|
|
|
|
|
// check carrier envelope state
|
|
|
|
|
return DBOPL::Operator::State::OFF != adlib->chip.chan[ch - 1].op[1].state;
|
|
|
|
|
}
|
2015-01-06 03:13:44 +00:00
|
|
|
|
|
2013-12-23 07:51:29 +00:00
|
|
|
|
void Hiopl::SetFrequency(int ch, float frqHz, bool keyOn) {
|
|
|
|
|
unsigned int fnum, block;
|
|
|
|
|
int offset = this->_GetOffset(ch);
|
2015-01-06 13:25:51 +00:00
|
|
|
|
// ZDoom emulator seems to be tuned down by two semitones for some reason. Sample rate difference?
|
2014-09-28 07:30:42 +00:00
|
|
|
|
if (ZDOOM == emulator) {
|
|
|
|
|
frqHz *= 1.122461363636364f;
|
|
|
|
|
}
|
2013-12-23 07:51:29 +00:00
|
|
|
|
_milliHertzToFnum((unsigned int)(frqHz * 1000.0), &fnum, &block);
|
|
|
|
|
_WriteReg(0xa0+offset, fnum % 0x100);
|
|
|
|
|
uint8 trig = (regCache[0xb0+offset] & 0x20) | (keyOn ? 0x20 : 0x00);
|
|
|
|
|
_WriteReg(0xb0+offset, trig|((block&0x7)<<2)|(0x3&(fnum/0x100)));
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-04 15:36:55 +00:00
|
|
|
|
// from libgamemusic, opl-util.cpp
|
|
|
|
|
void Hiopl::_milliHertzToFnum(unsigned int milliHertz,
|
|
|
|
|
unsigned int *fnum, unsigned int *block, unsigned int conversionFactor)
|
|
|
|
|
{
|
|
|
|
|
// Special case to avoid divide by zero
|
|
|
|
|
if (milliHertz == 0) {
|
|
|
|
|
*block = 0; // actually any block will work
|
|
|
|
|
*fnum = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Special case for frequencies too high to produce
|
|
|
|
|
if (milliHertz > 6208431) {
|
|
|
|
|
*block = 7;
|
|
|
|
|
*fnum = 1023;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2013-09-04 13:37:36 +00:00
|
|
|
|
|
2013-09-04 15:36:55 +00:00
|
|
|
|
// This is a bit more efficient and doesn't need log2() from math.h
|
|
|
|
|
if (milliHertz > 3104215) *block = 7;
|
|
|
|
|
else if (milliHertz > 1552107) *block = 6;
|
|
|
|
|
else if (milliHertz > 776053) *block = 5;
|
|
|
|
|
else if (milliHertz > 388026) *block = 4;
|
|
|
|
|
else if (milliHertz > 194013) *block = 3;
|
|
|
|
|
else if (milliHertz > 97006) *block = 2;
|
|
|
|
|
else if (milliHertz > 48503) *block = 1;
|
|
|
|
|
else *block = 0;
|
|
|
|
|
|
|
|
|
|
// Slightly more efficient version
|
2013-11-05 16:50:37 +00:00
|
|
|
|
*fnum = (unsigned int)(((unsigned long long)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5);
|
2013-09-04 15:36:55 +00:00
|
|
|
|
if ((*block == 7) && (*fnum > 1023)) {
|
|
|
|
|
// frequency out of range, clipping to maximum value.
|
|
|
|
|
*fnum = 1023;
|
|
|
|
|
}
|
|
|
|
|
assert(*block <= 7);
|
|
|
|
|
assert(*fnum < 1024);
|
|
|
|
|
return;
|
2013-09-04 13:37:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Hiopl::~Hiopl() {
|
2014-09-28 07:30:42 +00:00
|
|
|
|
|
2013-09-04 13:37:36 +00:00
|
|
|
|
};
|
|
|
|
|
|
2013-09-12 13:58:22 +00:00
|
|
|
|
bool Hiopl::_CheckParams(int ch, int osc=OSCILLATORS) {
|
2013-09-10 17:30:05 +00:00
|
|
|
|
return ch > 0 && ch <= CHANNELS && osc > 0 && osc <= OSCILLATORS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Hiopl::_GetOffset(int ch, int osc) {
|
|
|
|
|
assert(_CheckParams(ch, osc));
|
|
|
|
|
return (1 == osc) ? _op1offset[ch] : _op2offset[ch];
|
|
|
|
|
}
|
2013-09-12 13:58:22 +00:00
|
|
|
|
|
|
|
|
|
int Hiopl::_GetOffset(int ch) {
|
|
|
|
|
assert(_CheckParams(ch));
|
|
|
|
|
return ch - 1;
|
|
|
|
|
}
|
2015-01-06 03:13:44 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
2. Two-operator Melodic and Percussion Mode
|
|
|
|
|
|
|
|
|
|
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ
|
|
|
|
|
<EFBFBD> Channel <EFBFBD> 0 1 2 3 4 5 BD SD TT CY HH 9 10 11 12 13 14 15 16 17 <EFBFBD>
|
|
|
|
|
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ
|
|
|
|
|
<EFBFBD> Operator 1 <EFBFBD> 0 1 2 6 7 8 12 16 14 17 13 18 19 20 24 25 26 30 31 32 <EFBFBD>
|
|
|
|
|
<EFBFBD> Operator 2 <EFBFBD> 3 4 5 9 10 11 15 21 22 23 27 28 29 33 34 35 <EFBFBD>
|
|
|
|
|
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
*/
|