52static constexpr int MAX_ATT_INDEX = 0x280;
53static constexpr int MIN_ATT_INDEX = 0;
54static constexpr int TL_SHIFT = 2;
56static constexpr unsigned LFO_SHIFT = 18;
57static constexpr unsigned LFO_PERIOD = 1 << LFO_SHIFT;
60static constexpr int EG_ATT = 4;
61static constexpr int EG_DEC = 3;
62static constexpr int EG_SUS = 2;
63static constexpr int EG_REL = 1;
64static constexpr int EG_OFF = 0;
66static constexpr int EG_REV = 5;
67static constexpr int EG_DMP = 6;
70static constexpr std::array<uint8_t, 16> pan_left = {
71 0, 8, 16, 24, 32, 40, 48, 255, 255, 0, 0, 0, 0, 0, 0, 0
73static constexpr std::array<uint8_t, 16> pan_right = {
74 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 48, 40, 32, 24, 16, 8
79static constexpr int16_t SC(
int dB) {
return int16_t(dB / 3 * 0x20); }
80static constexpr std::array<int16_t, 16> dl_tab = {
81 SC( 0), SC( 3), SC( 6), SC( 9), SC(12), SC(15), SC(18), SC(21),
82 SC(24), SC(27), SC(30), SC(33), SC(36), SC(39), SC(42), SC(93)
85static constexpr uint8_t RATE_STEPS = 8;
86static constexpr std::array<uint8_t, 15 * RATE_STEPS> eg_inc = {
88 0, 1, 0, 1, 0, 1, 0, 1,
89 0, 1, 0, 1, 1, 1, 0, 1,
90 0, 1, 1, 1, 0, 1, 1, 1,
91 0, 1, 1, 1, 1, 1, 1, 1,
93 1, 1, 1, 1, 1, 1, 1, 1,
94 1, 1, 1, 2, 1, 1, 1, 2,
95 1, 2, 1, 2, 1, 2, 1, 2,
96 1, 2, 2, 2, 1, 2, 2, 2,
98 2, 2, 2, 2, 2, 2, 2, 2,
99 2, 2, 2, 4, 2, 2, 2, 4,
100 2, 4, 2, 4, 2, 4, 2, 4,
101 2, 4, 4, 4, 2, 4, 4, 4,
103 4, 4, 4, 4, 4, 4, 4, 4,
104 8, 8, 8, 8, 8, 8, 8, 8,
105 0, 0, 0, 0, 0, 0, 0, 0,
108[[nodiscard]]
static constexpr uint8_t O(
int a) {
return narrow<uint8_t>(a * RATE_STEPS); }
109static constexpr std::array<uint8_t, 64> eg_rate_select = {
110 O(14),O(14),O(14),O(14),
111 O( 0),O( 1),O( 2),O( 3),
112 O( 0),O( 1),O( 2),O( 3),
113 O( 0),O( 1),O( 2),O( 3),
114 O( 0),O( 1),O( 2),O( 3),
115 O( 0),O( 1),O( 2),O( 3),
116 O( 0),O( 1),O( 2),O( 3),
117 O( 0),O( 1),O( 2),O( 3),
118 O( 0),O( 1),O( 2),O( 3),
119 O( 0),O( 1),O( 2),O( 3),
120 O( 0),O( 1),O( 2),O( 3),
121 O( 0),O( 1),O( 2),O( 3),
122 O( 0),O( 1),O( 2),O( 3),
123 O( 4),O( 5),O( 6),O( 7),
124 O( 8),O( 9),O(10),O(11),
125 O(12),O(12),O(12),O(12),
131static constexpr std::array<uint8_t, 64> eg_rate_shift = {
153[[nodiscard]]
static constexpr int L(
double a) {
return int((LFO_PERIOD * a) / 44100.0 + 0.5); }
154static constexpr std::array<int, 8> lfo_period = {
168static constexpr std::array<int16_t, 8> vib_depth = {
186static constexpr std::array<uint8_t, 8> am_depth = {
205[[nodiscard]]
static constexpr int8_t sign_extend_4(
int x)
207 return narrow<int8_t>((x ^ 8) - 8);
216[[nodiscard]]
static constexpr unsigned calcStep(int8_t oct, uint16_t fn, int16_t vib = 0)
218 if (oct == -8)
return 0;
219 unsigned t = (fn + 1024 + vib) << (8 + oct);
223void YMF278::Slot::reset()
225 wave = FN = TLdest = TL = pan = vib = AM = 0;
228 AR = D1R = D2R = RC = RR = 0;
229 PRVB = keyon = DAMP =
false;
231 step = calcStep(OCT, FN);
234 loopAddr = endAddr = 0;
235 env_vol = MAX_ATT_INDEX;
247uint8_t YMF278::Slot::compute_rate(
int val)
const
251 }
else if (val == 15) {
257 res += 2 * std::clamp(OCT + RC, 0, 15);
258 res += (FN & 0x200) ? 1 : 0;
260 return narrow<uint8_t>(std::clamp(res, 0, 63));
263uint8_t YMF278::Slot::compute_decay_rate(
int val)
const
278 if (env_vol < dl_tab[4]) {
294 if (env_vol >= dl_tab[6]) {
298 return compute_rate(val);
301int16_t YMF278::Slot::compute_vib()
const
310 auto lfo_fm = narrow<int16_t>(lfo_cnt / (LFO_PERIOD / 0x40));
312 if (lfo_fm & 0x10) lfo_fm ^= 0x1F;
313 if (lfo_fm & 0x20) lfo_fm = narrow<int16_t>(-(lfo_fm & 0x0F));
315 return narrow<int16_t>((lfo_fm * vib_depth[vib]) / 12);
318uint16_t YMF278::Slot::compute_am()
const
324 auto lfo_am = narrow<uint16_t>(lfo_cnt / (LFO_PERIOD / 0x100));
326 if (lfo_am >= 0x80) lfo_am ^= 0xFF;
328 return narrow<uint16_t>((lfo_am * am_depth[AM]) >> 7);
332void YMF278::advance()
337 auto tl_int_cnt = eg_cnt % 9;
338 auto tl_int_step = (eg_cnt / 9) % 3;
340 for (
auto& op : slots) {
342 if (tl_int_cnt == 0) {
343 if (tl_int_step == 0) {
345 if (op.TL < op.TLdest) ++op.TL;
348 if (op.TL > op.TLdest) --op.TL;
353 op.lfo_cnt = (op.lfo_cnt + lfo_period[op.lfo]) & (LFO_PERIOD - 1);
359 uint8_t rate = op.compute_rate(op.AR);
366 uint8_t shift = eg_rate_shift[rate];
367 if (!(eg_cnt & ((1 << shift) - 1))) {
368 uint8_t select = eg_rate_select[rate];
370 op.env_vol = narrow<int16_t>(op.env_vol + ((~op.env_vol * eg_inc[select + ((eg_cnt >> shift) & 7)]) >> 4));
371 if (op.env_vol <= MIN_ATT_INDEX) {
372 op.env_vol = MIN_ATT_INDEX;
375 op.state = op.DL ? EG_DEC : EG_SUS;
381 uint8_t rate = op.compute_decay_rate(op.D1R);
382 uint8_t shift = eg_rate_shift[rate];
383 if (!(eg_cnt & ((1 << shift) - 1))) {
384 uint8_t select = eg_rate_select[rate];
385 op.env_vol = narrow<int16_t>(op.env_vol + eg_inc[select + ((eg_cnt >> shift) & 7)]);
386 if (op.env_vol >= op.DL) {
387 op.state = (op.env_vol < MAX_ATT_INDEX) ? EG_SUS : EG_OFF;
393 uint8_t rate = op.compute_decay_rate(op.D2R);
394 uint8_t shift = eg_rate_shift[rate];
395 if (!(eg_cnt & ((1 << shift) - 1))) {
396 uint8_t select = eg_rate_select[rate];
397 op.env_vol = narrow<int16_t>(op.env_vol + eg_inc[select + ((eg_cnt >> shift) & 7)]);
398 if (op.env_vol >= MAX_ATT_INDEX) {
399 op.env_vol = MAX_ATT_INDEX;
406 uint8_t rate = op.compute_decay_rate(op.RR);
407 uint8_t shift = eg_rate_shift[rate];
408 if (!(eg_cnt & ((1 << shift) - 1))) {
409 uint8_t select = eg_rate_select[rate];
410 op.env_vol = narrow<int16_t>(op.env_vol + eg_inc[select + ((eg_cnt >> shift) & 7)]);
411 if (op.env_vol >= MAX_ATT_INDEX) {
412 op.env_vol = MAX_ATT_INDEX;
428int16_t YMF278::getSample(
const Slot& slot, uint16_t pos)
const
436 return narrow_cast<int16_t>(
readMem(slot.startAddr + pos) << 8);
440 unsigned addr = slot.startAddr + ((pos / 2) * 3);
442 return narrow_cast<int16_t>(
446 return narrow_cast<int16_t>(
448 ((
readMem(addr + 1) << 4) & 0xF0));
453 unsigned addr = slot.startAddr + (pos * 2);
454 return narrow_cast<int16_t>(
464uint16_t YMF278::nextPos(
const Slot& slot, uint16_t pos, uint16_t increment)
470 if ((uint32_t(pos) + slot.endAddr) >= 0x10000)
471 pos += narrow_cast<uint16_t>(slot.endAddr + slot.loopAddr);
475bool YMF278::anyActive()
477 return ranges::any_of(slots, [](
auto& op) {
return op.state != EG_OFF; });
484static constexpr int vol_factor(
int x,
unsigned envVol)
486 if (envVol >= MAX_ATT_INDEX)
return 0;
487 int vol_mul = 0x80 - narrow<int>(envVol & 0x3F);
488 int vol_shift = 7 + narrow<int>(envVol >> 6);
489 return (x * ((0x8000 * vol_mul) >> vol_shift)) >> 15;
494 static constexpr std::array<float, 8> level = {
507void YMF278::generateChannels(std::span<float*> bufs,
unsigned num)
516 for (
auto j :
xrange(num)) {
517 for (
auto i :
xrange(24)) {
519 if (sl.state == EG_OFF) {
525 auto sample = narrow_cast<int16_t>(
526 (getSample(sl, sl.pos) * (0x10000 - sl.stepPtr) +
527 getSample(sl, nextPos(sl, sl.pos, 1)) * sl.stepPtr) >> 16);
534 auto envVol = narrow_cast<uint16_t>(
535 std::min(sl.env_vol + ((sl.lfo_active && sl.AM) ? sl.compute_am() : 0),
537 int smplOut = vol_factor(vol_factor(sample, envVol), sl.TL << TL_SHIFT);
542 int32_t volLeft = pan_left [sl.pan];
543 int32_t volRight = pan_right[sl.pan];
545 volLeft = (0x20 - (volLeft & 0x0f)) >> (volLeft >> 4);
546 volRight = (0x20 - (volRight & 0x0f)) >> (volRight >> 4);
548 bufs[i][2 * j + 0] += narrow_cast<float>((smplOut * volLeft ) >> 5);
549 bufs[i][2 * j + 1] += narrow_cast<float>((smplOut * volRight) >> 5);
551 unsigned step = (sl.lfo_active && sl.vib)
552 ? calcStep(sl.OCT, sl.FN, sl.compute_vib())
556 if (sl.stepPtr >= 0x10000) {
557 sl.pos = nextPos(sl, sl.pos, narrow<uint16_t>(sl.stepPtr >> 16));
558 sl.stepPtr &= 0xffff;
565void YMF278::keyOnHelper(YMF278::Slot& slot)
const
568 slot.env_vol = MAX_ATT_INDEX;
569 if (slot.compute_rate(slot.AR) < 63) {
574 slot.env_vol = MIN_ATT_INDEX;
576 slot.state = slot.DL ? EG_DEC : EG_SUS;
585 writeRegDirect(reg, data, time);
588void YMF278::writeRegDirect(uint8_t reg, uint8_t data, EmuTime::param time)
591 if (reg >= 0x08 && reg <= 0xF7) {
592 int sNum = (reg - 8) % 24;
593 auto& slot = slots[sNum];
594 switch ((reg - 8) / 24) {
596 slot.wave = (slot.wave & 0x100) | data;
597 int waveTblHdr = (regs[2] >> 2) & 0x7;
598 int base = (slot.wave < 384 || !waveTblHdr) ?
600 (waveTblHdr * 0x80000 + ((slot.wave - 384) * 12));
601 std::array<uint8_t, 12> buf;
602 for (
auto i :
xrange(12)) {
607 slot.bits = (buf[0] & 0xC0) >> 6;
608 slot.startAddr = buf[2] | (buf[1] << 8) | ((buf[0] & 0x3F) << 16);
609 slot.loopAddr = uint16_t(buf[4] | (buf[3] << 8));
610 slot.endAddr = uint16_t(buf[6] | (buf[5] << 8));
611 for (
auto i :
xrange(7, 12)) {
615 writeRegDirect(narrow<uint8_t>(8 + sNum + (i - 2) * 24), buf[i], time);
626 slot.wave = uint16_t((slot.wave & 0xFF) | ((data & 0x1) << 8));
627 slot.FN = (slot.FN & 0x380) | (data >> 1);
628 slot.step = calcStep(slot.OCT, slot.FN);
632 slot.FN = uint16_t((slot.FN & 0x07F) | ((data & 0x07) << 7));
633 slot.PRVB = (data & 0x08) != 0;
634 slot.OCT = sign_extend_4((data & 0xF0) >> 4);
635 slot.step = calcStep(slot.OCT, slot.FN);
639 uint8_t
t = data >> 1;
640 slot.TLdest = (
t != 0x7f) ?
t : 0xff;
643 slot.TL = slot.TLdest;
656 slot.pan = data & 0x0F;
661 slot.lfo_active =
false;
665 slot.lfo_active =
true;
668 slot.DAMP = (data & 0x40) != 0;
683 slot.lfo = (data >> 3) & 0x7;
684 slot.vib = data & 0x7;
688 slot.D1R = data & 0xF;
691 slot.DL = dl_tab[data >> 4];
692 slot.D2R = data & 0xF;
696 slot.RR = data & 0xF;
699 slot.AM = data & 0x7;
737 memAdr = (regs[3] << 16) | (regs[4] << 8) | data;
779 return (regs[2] & 0x1F) | 0x20;
794static constexpr unsigned INPUT_RATE = 44100;
799 24, INPUT_RATE, true)
800 , motherBoard(config.getMotherBoard())
801 , debugRegisters(motherBoard, getName())
802 , debugMemory (motherBoard, getName())
803 , rom(getName() +
" ROM",
"rom", config)
804 , ram(config, getName() +
" RAM",
"YMF278 sample RAM", ramSize)
805 , setupMemPtrs(setupMemPtrs_)
807 if (rom.
size() != 0x200000) {
809 "Wrong ROM for OPL4 (YMF278B). The ROM (usually "
810 "called yrw801.rom) should have a size of exactly 2MB.");
836 for (
auto& op : slots) {
840 for (
int i = 0xf7; i >= 0; --i) {
841 writeRegDirect(narrow<uint8_t>(i), 0, time);
869 bool mode0 = (regs[2] & 2) == 0;
870 setupMemPtrs(mode0, rom, ram, memPtrs);
876 address &= 0x3F'FFFF;
877 if (
auto chunk = memPtrs[address >> 17].asOptional()) {
878 return (*chunk)[address & 0x1'FFFF];
885 address &= 0x3F'FFFF;
886 if (
auto chunk = memPtrs[address >> 17].asOptional()) {
887 auto* ptr = chunk->data() + (address & 0x1'ffff);
888 if ((&ram[0] <= ptr) && (ptr < (&ram[0] + ram.
size()))) {
890 auto ramOffset = ptr - &ram[0];
891 ram.
write(ramOffset, value);
915template<
typename Archive>
916void YMF278::Slot::serialize(Archive& ar,
unsigned version)
919 ar.serialize(
"startaddr", startAddr,
920 "loopaddr", loopAddr,
928 if (ar.versionAtLeast(version, 4)) {
929 ar.serialize(
"endaddr", endAddr,
932 unsigned e = 0; ar.serialize(
"endaddr", e); endAddr = uint16_t((e ^ 0xffff) + 1);
935 if (ar.versionAtLeast(version, 2)) {
936 ar.serialize(
"OCT", O);
938 ar.serializeChar(
"OCT", O);
940 OCT = sign_extend_4(O);
943 if (ar.versionAtLeast(version, 2)) {
944 ar.serialize(
"PRVB", PRVB,
956 char PRVB_ = 0; ar.serializeChar(
"PRVB", PRVB_); PRVB = PRVB_;
957 char TL_ = 0; ar.serializeChar(
"TL", TL_ ); TL = TL_;
958 char pan_ = 0; ar.serializeChar(
"pan", pan_); pan = pan_;
959 char vib_ = 0; ar.serializeChar(
"vib", vib_); vib = vib_;
960 char AM_ = 0; ar.serializeChar(
"AM", AM_ ); AM = AM_;
961 char AR_ = 0; ar.serializeChar(
"AR", AR_ ); AR = AR_;
962 char D1R_ = 0; ar.serializeChar(
"D1R", D1R_); D1R = D1R_;
963 char D2R_ = 0; ar.serializeChar(
"D2R", D2R_); D2R = D2R_;
964 char RC_ = 0; ar.serializeChar(
"RC", RC_ ); RC = RC_;
965 char RR_ = 0; ar.serializeChar(
"RR", RR_ ); RR = RR_;
967 ar.serialize(
"bits", bits,
968 "lfo_active", lfo_active);
970 ar.serialize(
"state", state);
971 if (ar.versionBelow(version, 4)) {
972 assert(Archive::IS_LOADER);
973 if (state ==
one_of(EG_REV, EG_DMP)) {
979 if constexpr (Archive::IS_LOADER) {
980 step = calcStep(OCT, FN);
999template<
typename Archive>
1002 ar.serialize(
"slots", slots,
1004 if (ar.versionAtLeast(version, 4)) {
1005 ar.serialize(
"ram", ram);
1009 ar.serialize_blob(
"registers", regs);
1010 if (ar.versionAtLeast(version, 3)) {
1011 ar.serialize(
"memadr", memAdr);
1013 assert(Archive::IS_LOADER);
1018 memAdr = (regs[3] << 16) | (regs[4] << 8) | regs[5];
1022 if constexpr (Archive::IS_LOADER) {
1024 uint8_t
t = regs[0x50 + i] >> 1;
1025 sl.TLdest = (
t != 0x7f) ?
t : 0xff;
1027 sl.keyon = (regs[0x68 + i] & 0x80) != 0;
1028 sl.DAMP = (regs[0x68 + i] & 0x40) != 0;
1029 sl.lfo = (regs[0x80 + i] >> 3) & 7;
1039YMF278::DebugRegisters::DebugRegisters(
MSXMotherBoard& motherBoard_,
1040 const std::string& name_)
1042 "OPL4 registers", 0x100)
1046uint8_t YMF278::DebugRegisters::read(
unsigned address)
1048 const auto& ymf278 =
OUTER(YMF278, debugRegisters);
1049 return ymf278.peekReg(narrow<uint8_t>(address));
1052void YMF278::DebugRegisters::write(
unsigned address, uint8_t value, EmuTime::param time)
1054 auto& ymf278 =
OUTER(YMF278, debugRegisters);
1055 ymf278.writeReg(narrow<uint8_t>(address), value, time);
1061YMF278::DebugMemory::DebugMemory(MSXMotherBoard& motherBoard_,
1062 const std::string& name_)
1063 : SimpleDebuggable(motherBoard_, name_ +
" mem",
1064 "OPL4 memory (includes both ROM and RAM)", 0x400000)
1068uint8_t YMF278::DebugMemory::read(
unsigned address)
1070 const auto& ymf278 =
OUTER(YMF278, debugMemory);
1071 return ymf278.readMem(address);
1074void YMF278::DebugMemory::write(
unsigned address, uint8_t value)
1076 auto& ymf278 =
OUTER(YMF278, debugMemory);
1077 ymf278.writeMem(address, value);
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
void updateStream(EmuTime::param time)
void setSoftwareVolume(float volume, EmuTime::param time)
Change the 'software volume' of this sound device.
void unregisterSound()
Unregisters this sound device with the Mixer.
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
void write(size_t addr, byte value)
std::span< byte > getWriteBackdoor()
YMF278(const std::string &name, size_t ramSize, const DeviceConfig &config, SetupMemPtrFunc setupMemPtrs_)
uint8_t readReg(uint8_t reg)
void writeReg(uint8_t reg, uint8_t data, EmuTime::param time)
void setMixLevel(uint8_t x, EmuTime::param time)
void reset(EmuTime::param time)
void writeMem(unsigned address, uint8_t value)
uint8_t readMem(unsigned address) const
void setupMemoryPointers()
std::function< void(bool, std::span< const uint8_t >, std::span< const uint8_t >, std::span< YMF278::Block128, 32 >)> SetupMemPtrFunc
void serialize(Archive &ar, unsigned version)
uint8_t peekReg(uint8_t reg) const
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
This file implemented 3 utility functions:
constexpr void fill(ForwardRange &&range, const T &value)
constexpr bool any_of(InputRange &&range, UnaryPredicate pred)
#define OUTER(type, member)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
constexpr auto xrange(T e)