Update to v102r05 release.

byuu says:

Changelog:

  - higan: added Makefile option,
    `build=(release|debug|instrument|optimize)` , defaults to release
  - PCE: added preliminary PSG (sound) emulation

The Makefile thing is just to make it easier to build debug releases
without having to hand-edit the Makefile. Just say "gmake build=debug"
and you'll get -g, otherwise you'll get -O3 -s. I'll probably start
adding these build= blocks to my other projects. Or maybe I'll put it
into nall, in which case release will need a different name ... a stable
-01, and a fast -03 mode. I also want to add a mode to generate
profiling information (via gprof.)

Unfortunately, the existing documentation on the PCE's PSG is as
barebones as humanly possible.

Right now, I support waveform mode, direct D/A mode, and noise
generation mode. However for noise, I'm not actually generating a proper
square wave, and I don't know the PRNG algorithm used for choosing the
random values. So for now, I'm just feeding in nall::random() values to
it.

I'm also not sure about the noise mode's frequency counter. Magic Kit is
implying it's 64*~frequency, but that results in an 11-bit period. It
seems only logical that we'd want a 12-bit period. So my guess is that
it's actually 12-bit, and halfway through it alternates between two
randomly generated values every 32 samples, and the two values are
generated every time the period hits zero.

Next up, it's not clear when the period counter is reloaded, either for
the waveform or the noise mode. So for now, when enabling the channel, I
reload the waveform period. And when enabling noise mode, I reload the
noise period. I don't know if you need to do it when writing to the
frequency registers or not.

Next, it's not clear whether the period is a decrement-and-compare, or a
compare-and-decrement, and whether we reload with frequency,
frequency-1, or frequency+1. There's this cryptic note in
pcetext.txt:

> The PSG channel frequency is 12 bits, $001 is the highest frequency,
> $FFF is the next to lowest frequency, and $000 is the lowest frequency.

As best I can tell, he's trying to say that it's decrement-and-compare.

Whatever the case, there's periodic popping noises every few seconds. I
thought it might be because this is the first system with a fractional
sampling rate (~3.57MHz), but rounding the frequency to a whole number
doesn't help at all, and emulator/audio should be able to handle
fractional resampling rates anyway.

The popping noises could also be due to PSG writes being cycle-timed,
and my HuC6280 cycle timings not being very great yet. The PSG has no
kind of interrupts, so I think careful timing is the only way to do
certain things, especially D/A mode.

Next up, I really don't understand the frequency modulation mode at all.
I don't have any idea whatsoever how to support that. It also has a
frequency value that we'll need to understand how the period works and
reloads. Basic idea though is the channel 1 output turns into a value to
modulate channel 0's frequency by, and channel 1's output gets muted.

Next up, I don't know how the volume controls work at all. There's a
master volume left+right, per-channel volume left+right, and per-channel
overall volume. The documentation lists their effects in terms of
decibels. I have no fucking clue how to turn decibels into multiply-by
values. Let alone how to stack THREE levels of audio volume controls
>_>

Next, it looks like the output is always 5-bit unsigned per-channel, but
there's also all the volume adjustments. So I don't know the final
bit-depth of the final output to normalize the value into a signed
floating point value between -1.0 and +1.0. So for now, half the
potential speaker range (anything below zero) isn't used in the
generated output.

As bad as all this sounds, and it is indeed bad ... the audio's about
~75% correct, so you can definitely play games like this, it just won't
be all that much fun.
This commit is contained in:
Tim Allen 2017-02-10 07:10:38 +11:00
parent ee7662a8be
commit bf70044edc
8 changed files with 190 additions and 18 deletions

View File

@ -1,23 +1,24 @@
include ../nall/GNUmakefile
target := tomoko
objects := libco emulator audio video resource
build := release
# console := true
flags += -I. -I.. -O3
objects := libco emulator audio video resource
flags += -I. -I..
# profile-guided optimization mode
# pgo := instrument
# pgo := optimize
ifeq ($(pgo),instrument)
flags += -fprofile-generate
ifeq ($(build),release)
flags += -O3
link += -s
else ifeq ($(build),debug)
flags += -g
else ifeq ($(build),instrument)
flags += -O3 -fprofile-generate
link += -lgcov
else ifeq ($(pgo),optimize)
flags += -fprofile-use
else ifeq ($(build),optimize)
flags += -O3 -fprofile-use
endif
# platform
ifeq ($(platform),windows)
ifeq ($(console),true)
link += -mconsole

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "102.04";
static const string Version = "102.05";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -134,8 +134,7 @@ auto CPU::write(uint8 bank, uint13 addr, uint8 data) -> void {
//$0800-0bff PSG
if((addr & 0x1c00) == 0x0800) {
io.mdr = data;
return;
return psg.write(addr, io.mdr = data);
}
//$0c00-0fff Timer

View File

@ -75,7 +75,7 @@ auto Interface::videoColor(uint32 color) -> uint64 {
}
auto Interface::audioFrequency() -> double {
return 44'100.0; //todo: not accurate
return 315.0 / 88.0 * 1'000'000.0; //3.57MHz
}
auto Interface::loaded() -> bool {

31
higan/pce/psg/channel.cpp Normal file
View File

@ -0,0 +1,31 @@
auto PSG::Channel::power() -> void {
memory::fill(&io, sizeof(IO));
memory::fill(&output, sizeof(Output));
}
auto PSG::Channel::run() -> void {
if(!io.enable) return sample(0);
if(io.noiseEnable) {
if(--io.noisePeriod == 0) {
io.noisePeriod = ~io.noiseFrequency << 7;
//todo: this should be a square wave; PRNG algorithm is also unknown
io.noiseSample = nall::random();
}
return sample(io.noiseSample);
}
if(io.direct) return sample(io.waveDirect);
if(--io.period == 0) {
io.period = io.frequency;
io.waveOffset++;
}
return sample(io.waveData[io.waveOffset]);
}
auto PSG::Channel::sample(uint5 sample) -> void {
output.left = sample << 8; //<< io.volume << io.volumeLeft;
output.right = sample << 8; //<< io.volume << io.volumeRight;
}

81
higan/pce/psg/io.cpp Normal file
View File

@ -0,0 +1,81 @@
auto PSG::write(uint4 addr, uint8 data) -> void {
if(addr == 0x00) {
io.channel = data.bits(0,2);
return;
}
if(addr == 0x01) {
io.volumeRight = data.bits(0,3);
io.volumeLeft = data.bits(4,7);
return;
}
uint3 C = io.channel;
if(addr == 0x02) {
if(C == 6 || C == 7) return;
channel[C].io.frequency.bits(0,7) = data.bits(0,7);
return;
}
if(addr == 0x03) {
if(C == 6 || C == 7) return;
channel[C].io.frequency.bits(8,11) = data.bits(0,3);
return;
}
if(addr == 0x04) {
if(C == 6 || C == 7) return;
if(channel[C].io.direct && !data.bit(6)) {
channel[C].io.waveOffset = 0;
}
if(!channel[C].io.enable && data.bit(7)) {
channel[C].io.waveOffset = 0;
channel[C].io.period = channel[C].io.frequency;
}
channel[C].io.volume = data.bits(0,3);
channel[C].io.direct = data.bit(6);
channel[C].io.enable = data.bit(7);
return;
}
if(addr == 0x05) {
if(C == 6 || C == 7) return;
channel[C].io.volumeRight = data.bits(0,3);
channel[C].io.volumeLeft = data.bits(4,7);
return;
}
if(addr == 0x06) {
if(C == 6 || C == 7) return;
if(channel[C].io.direct) {
channel[C].io.waveDirect = data.bits(0,4);
} else if(!channel[C].io.enable) {
uint5 O = channel[C].io.waveOffset++;
channel[C].io.waveData[O] = data.bits(0,4);
}
return;
}
if(addr == 0x07) {
if(C != 4 && C != 5) return;
if(!channel[C].io.noiseEnable && data.bit(7)) {
channel[C].io.noisePeriod = ~data.bits(0,4) << 7;
channel[C].io.noiseSample = 0;
}
channel[C].io.noiseFrequency = data.bits(0,4);
channel[C].io.noiseEnable = data.bit(7);
return;
}
if(addr == 0x08) {
io.lfoFrequency = data;
return;
}
if(addr == 0x09) {
io.lfoControl = data.bits(0,1);
io.lfoEnable = data.bit(7);
return;
}
}

View File

@ -3,14 +3,28 @@
namespace PCEngine {
PSG psg;
#include "io.cpp"
#include "channel.cpp"
auto PSG::Enter() -> void {
while(true) scheduler.synchronize(), psg.main();
}
auto PSG::main() -> void {
uint left = 0, right = 0;
for(auto C : range(6)) {
channel[C].run();
if(C == 1 && io.lfoEnable) {
//todo: frequency modulation of channel 0 using channel 1's output
} else {
left += channel[C].output.left;
right += channel[C].output.right;
}
}
stream->sample(left / 32768.0, right / 32768.0);
step(1);
stream->sample(0.0, 0.0);
}
auto PSG::step(uint clocks) -> void {
@ -19,8 +33,11 @@ auto PSG::step(uint clocks) -> void {
}
auto PSG::power() -> void {
create(PSG::Enter, 44'100.0);
stream = Emulator::audio.createStream(2, 44'100.0);
create(PSG::Enter, system.colorburst());
stream = Emulator::audio.createStream(2, system.colorburst());
memory::fill(&io, sizeof(IO));
for(auto C : range(6)) channel[C].power();
}
}

View File

@ -8,6 +8,49 @@ struct PSG : Thread {
auto step(uint clocks) -> void;
auto power() -> void;
//io.cpp
auto write(uint4 addr, uint8 data) -> void;
private:
struct IO {
uint3 channel;
uint4 volumeLeft;
uint4 volumeRight;
uint8 lfoFrequency;
uint2 lfoControl;
uint1 lfoEnable;
} io;
struct Channel {
//channel.cpp
auto power() -> void;
auto run() -> void;
auto sample(uint5 sample) -> void;
struct IO {
uint12 frequency;
uint4 volume;
uint1 direct;
uint1 enable;
uint4 volumeLeft;
uint4 volumeRight;
uint5 waveData[32];
uint5 waveOffset;
uint5 waveDirect;
uint5 noiseFrequency; //channels 4 and 5 only
uint1 noiseEnable; //channels 4 and 5 only
uint12 period;
uint12 noisePeriod;
uint5 noiseSample;
} io;
struct Output {
uint left;
uint right;
} output;
} channel[6];
};
extern PSG psg;