1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Implements enough wiring that the Master System will instantiate and talk to an OPLL.

This commit is contained in:
Thomas Harte 2020-04-03 20:05:36 -04:00
parent ab81d1093d
commit b0abc4f7bb
5 changed files with 246 additions and 19 deletions

113
Components/OPL2/OPL2.cpp Normal file
View File

@ -0,0 +1,113 @@
//
// OPL2.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/04/2020.
// Copyright © 2020 Thomas Harte. all rights reserved.
//
#include "OPL2.hpp"
namespace {
/*
Credit for the fixed register lists goes to Nuke.YKT; I found them at:
https://siliconpr0n.org/archive/doku.php?id=vendor:yamaha:opl2#ym2413_instrument_rom
The arrays below begin with channel 1, each line is a single channel and the
format per channel is, from first byte to eighth:
Bytes 1 and 2:
Registers 1 and 2, i.e. modulator and carrier
amplitude modulation select, vibrato select, etc.
Byte 3:
b7, b6: modulator key scale level
b5...b0: modulator total level (inverted)
Byte 4:
b7: carrier key scale level
b3...b0: feedback level and waveform selects as per register 4
Bytes 5, 6:
Registers 4 and 5, i.e. decay and attack rate, modulator and carrier.
Bytes 7, 8:
Registers 6 and 6, i.e. decay-sustain level and release rate, modulator and carrier.
*/
constexpr uint8_t opll_patch_set[] = {
0x71, 0x61, 0x1e, 0x17, 0xd0, 0x78, 0x00, 0x17,
0x13, 0x41, 0x1a, 0x0d, 0xd8, 0xf7, 0x23, 0x13,
0x13, 0x01, 0x99, 0x00, 0xf2, 0xc4, 0x11, 0x23,
0x31, 0x61, 0x0e, 0x07, 0xa8, 0x64, 0x70, 0x27,
0x32, 0x21, 0x1e, 0x06, 0xe0, 0x76, 0x00, 0x28,
0x31, 0x22, 0x16, 0x05, 0xe0, 0x71, 0x00, 0x18,
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x10, 0x07,
0x23, 0x21, 0x2d, 0x14, 0xa2, 0x72, 0x00, 0x07,
0x61, 0x61, 0x1b, 0x06, 0x64, 0x65, 0x10, 0x17,
0x41, 0x61, 0x0b, 0x18, 0x85, 0xf7, 0x71, 0x07,
0x13, 0x01, 0x83, 0x11, 0xfa, 0xe4, 0x10, 0x04,
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
0x61, 0x50, 0x0c, 0x05, 0xc2, 0xf5, 0x20, 0x42,
0x01, 0x01, 0x55, 0x03, 0xc9, 0x95, 0x03, 0x02,
0x61, 0x41, 0x89, 0x03, 0xf1, 0xe4, 0x40, 0x13,
};
constexpr uint8_t vrc7_patch_set[] = {
0x03, 0x21, 0x05, 0x06, 0xe8, 0x81, 0x42, 0x27,
0x13, 0x41, 0x14, 0x0d, 0xd8, 0xf6, 0x23, 0x12,
0x11, 0x11, 0x08, 0x08, 0xfa, 0xb2, 0x20, 0x12,
0x31, 0x61, 0x0c, 0x07, 0xa8, 0x64, 0x61, 0x27,
0x32, 0x21, 0x1e, 0x06, 0xe1, 0x76, 0x01, 0x28,
0x02, 0x01, 0x06, 0x00, 0xa3, 0xe2, 0xf4, 0xf4,
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x11, 0x07,
0x23, 0x21, 0x22, 0x17, 0xa2, 0x72, 0x01, 0x17,
0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01,
0xb5, 0x01, 0x0f, 0x0f, 0xa8, 0xa5, 0x51, 0x02,
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16,
0x01, 0x02, 0xd3, 0x05, 0xc9, 0x95, 0x03, 0x02,
0x61, 0x63, 0x0c, 0x00, 0x94, 0xc0, 0x33, 0xf6,
0x21, 0x72, 0x0d, 0x00, 0xc1, 0xd5, 0x56, 0x06,
};
constexpr uint8_t percussion_patch_set[] = {
0x01, 0x01, 0x18, 0x0f, 0xdf, 0xf8, 0x6a, 0x6d,
0x01, 0x01, 0x00, 0x00, 0xc8, 0xd8, 0xa7, 0x48,
0x05, 0x01, 0x00, 0x00, 0xf8, 0xaa, 0x59, 0x55,
};
}
using namespace Yamaha;
OPL2::OPL2(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue): task_queue_(task_queue) {
(void)opll_patch_set;
(void)vrc7_patch_set;
(void)percussion_patch_set;
}
bool OPL2::is_zero_level() {
return true;
}
void OPL2::get_samples(std::size_t number_of_samples, std::int16_t *target) {
// TODO.
}
void OPL2::set_sample_volume_range(std::int16_t range) {
// TODO.
}
void OPL2::write(uint16_t address, uint8_t value) {
// TODO.
printf("OPL2 write: %02x to %d\n", value, address&1);
}
uint8_t OPL2::read(uint16_t address) {
// TODO.
return 0xff;
}

51
Components/OPL2/OPL2.hpp Normal file
View File

@ -0,0 +1,51 @@
//
// OPL2.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/04/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef OPL2_hpp
#define OPL2_hpp
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace Yamaha {
/*!
Provides an emulation of the OPL2 core, along with its OPLL and VRC7 specialisations.
*/
class OPL2: public ::Outputs::Speaker::SampleSource {
public:
enum class Personality {
OPL2, // Provides full configuration of all channels.
OPLL, // Uses the OPLL sound font, permitting full configuration of only a single channel.
VRC7, // Uses the VRC7 sound font, permitting full configuration of only a single channel.
};
// Creates a new OPL2, OPLL or VRC7.
OPL2(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue);
/// As per ::SampleSource; provides a broadphase test for silence.
bool is_zero_level();
/// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
void set_sample_volume_range(std::int16_t range);
/// Writes to the OPL.
void write(uint16_t address, uint8_t value);
/// Reads from the OPL.
uint8_t read(uint16_t address);
private:
Concurrency::DeferringAsyncTaskQueue &task_queue_;
};
}
#endif /* OPL2_hpp */

View File

@ -12,6 +12,7 @@
#include "../../Components/9918/9918.hpp"
#include "../../Components/SN76489/SN76489.hpp"
#include "../../Components/OPL2/OPL2.hpp"
#include "../MachineTypes.hpp"
#include "../../Configurable/Configurable.hpp"
@ -20,6 +21,7 @@
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
#define LOG_PREFIX "[SMS] "
#include "../../Outputs/Log.hpp"
@ -97,7 +99,9 @@ class ConcreteMachine:
(target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS,
audio_queue_,
sn76489_divider),
speaker_(sn76489_),
opll_(Yamaha::OPL2::Personality::OPLL, audio_queue_),
mixer_(sn76489_, opll_),
speaker_(mixer_),
keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}, {}) {
// Pick the clock rate based on the region.
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
@ -250,17 +254,29 @@ class ConcreteMachine:
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xc0: {
Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get());
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
*cycle.value = static_cast<uint8_t>(joypad1->get_state() | (joypad2->get_state() << 6));
if(memory_control_ & 0x4) {
if(has_fm_audio_ && (address & 0xff) == 0xf2) {
*cycle.value = opll_detection_word_;
} else {
*cycle.value = 0xff;
}
} else {
Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get());
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
*cycle.value = static_cast<uint8_t>(joypad1->get_state() | (joypad2->get_state() << 6));
}
} break;
case 0xc1: {
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
if(memory_control_ & 0x4) {
*cycle.value = 0xff;
} else {
Joystick *const joypad2 = static_cast<Joystick *>(joysticks_[1].get());
*cycle.value =
(joypad2->get_state() >> 2) |
0x30 |
get_th_values();
*cycle.value =
(joypad2->get_state() >> 2) |
0x30 |
get_th_values();
}
} break;
default:
@ -271,14 +287,15 @@ class ConcreteMachine:
case CPU::Z80::PartialMachineCycle::Output:
switch(address & 0xc1) {
case 0x00:
case 0x00: // i.e. even ports less than 0x40.
if(is_master_system(model_)) {
// TODO: Obey the RAM enable.
printf("Memory control: %02x\n", memory_control_);
memory_control_ = *cycle.value;
page_cartridge();
}
break;
case 0x01: {
case 0x01: { // i.e. odd ports less than 0x40.
// A programmer can force the TH lines to 0 here,
// causing a phoney lightgun latch, so check for any
// discontinuity in TH inputs.
@ -291,20 +308,26 @@ class ConcreteMachine:
vdp_->latch_horizontal_counter();
}
} break;
case 0x40: case 0x41:
case 0x40: case 0x41: // i.e. ports 0x400x7f.
update_audio();
sn76489_.write(*cycle.value);
break;
case 0x80: case 0x81:
case 0x80: case 0x81: // i.e. ports 0x800xbf.
vdp_->write(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xc0:
LOG("TODO: [output] I/O port A/N; " << int(*cycle.value));
break;
case 0xc1:
LOG("TODO: [output] I/O port B/misc");
case 0xc1: case 0xc0: // i.e. ports 0xc00xff.
if(has_fm_audio_) {
switch(address & 0xff) {
case 0xf0: case 0xf1:
opll_.write(address, *cycle.value);
break;
case 0xf2:
opll_detection_word_ = *cycle.value;
break;
}
}
break;
default:
@ -313,6 +336,19 @@ class ConcreteMachine:
}
break;
/*
TODO: implementation of the below is incomplete.
Re: io_port_control_
Set the TH pins for ports A and B as outputs. Set their output level
to any value desired by writing to bits 7 and 5. Read the state of both
TH pins back through bits 7 and 6 of port $DD. If the data returned is
the same as the data written, it's an export machine, otherwise it's
a domestic one.
Charles MacDonald
*/
case CPU::Z80::PartialMachineCycle::Interrupt:
*cycle.value = 0xff;
break;
@ -417,7 +453,10 @@ class ConcreteMachine:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;
Outputs::Speaker::LowpassSpeaker<TI::SN76489> speaker_;
Yamaha::OPL2 opll_;
Outputs::Speaker::CompoundSource<TI::SN76489, Yamaha::OPL2> mixer_;
Outputs::Speaker::LowpassSpeaker<decltype(mixer_)> speaker_;
uint8_t opll_detection_word_ = 0xff;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Inputs::Keyboard keyboard_;
@ -433,6 +472,9 @@ class ConcreteMachine:
uint8_t io_port_control_ = 0x0f;
// This is a static constexpr for now; I may use it in the future.
static constexpr bool has_fm_audio_ = true;
// The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM.
const uint8_t *read_pointers_[64];
uint8_t *write_pointers_[64];

View File

@ -785,6 +785,8 @@
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.cpp */; };
4BC57CD92436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
4BC57CDA2436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
4BC57CE22436BFE000FBC404 /* OPL2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CE02436BFE000FBC404 /* OPL2.cpp */; };
4BC57CE32436BFE000FBC404 /* OPL2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CE02436BFE000FBC404 /* OPL2.cpp */; };
4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */; };
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
@ -1659,6 +1661,8 @@
4BC57CD424342E0600FBC404 /* MachineTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MachineTypes.hpp; sourceTree = "<group>"; };
4BC57CD72436A61300FBC404 /* State.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = "<group>"; };
4BC57CD82436A62900FBC404 /* State.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = "<group>"; };
4BC57CE02436BFE000FBC404 /* OPL2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OPL2.cpp; sourceTree = "<group>"; };
4BC57CE12436BFE000FBC404 /* OPL2.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OPL2.hpp; sourceTree = "<group>"; };
4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000MoveTests.mm; sourceTree = "<group>"; };
4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; };
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
@ -3518,6 +3522,15 @@
path = State;
sourceTree = "<group>";
};
4BC57CDF2436BFE000FBC404 /* OPL2 */ = {
isa = PBXGroup;
children = (
4BC57CE02436BFE000FBC404 /* OPL2.cpp */,
4BC57CE12436BFE000FBC404 /* OPL2.hpp */,
);
path = OPL2;
sourceTree = "<group>";
};
4BC9DF4A1D04691600F44158 /* Components */ = {
isa = PBXGroup;
children = (
@ -3537,6 +3550,7 @@
4B4A762D1DB1A35C007AAE2E /* AY38910 */,
4B302181208A550100773308 /* DiskII */,
4B4B1A39200198C900A0F866 /* KonamiSCC */,
4BC57CDF2436BFE000FBC404 /* OPL2 */,
4B0ACBFF237756EC008902D0 /* Serial */,
4BB0A6582044FD3000FB3688 /* SN76489 */,
);
@ -4312,6 +4326,7 @@
4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */,
4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */,
4BC57CE32436BFE000FBC404 /* OPL2.cpp in Sources */,
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */,
4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */,
4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
@ -4584,6 +4599,7 @@
4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */,
4BD67DD0209BF27B00AB2146 /* Encoder.cpp in Sources */,
4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */,
4BC57CE22436BFE000FBC404 /* OPL2.cpp in Sources */,
4B89451A201967B4007DE474 /* ConfidenceSummary.cpp in Sources */,
4BE0A3EE237BB170002AB46F /* ST.cpp in Sources */,
4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */,

View File

@ -53,6 +53,11 @@ class SampleSource {
*/
void set_sample_volume_range(std::int16_t volume) {
}
/*!
Indicates whether this component will write stereo samples.
*/
static constexpr bool get_is_stereo() { return false; }
};
}