mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
ee7662a8be
commit
bf70044edc
|
@ -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
|
||||
|
|
|
@ -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/";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue