Update to v106r113 release.

byuu says:

I've added a waveOut audio driver for Windows (no DRC), and hooked up a lot more
neo geo pocket stuff (gamepad polling, APU/Z80 enable/disable, APU->CPU IRQs,
VPU I/O registers, etc.) still not enough to get any further than we already
were, sigh.
This commit is contained in:
Tim Allen 2019-03-09 13:17:04 +11:00
parent 1a4acbf74b
commit 8b5293cd70
20 changed files with 505 additions and 64 deletions

View File

@ -37,7 +37,7 @@ using namespace nall;
namespace higan {
static const string Name = "higan";
static const string Version = "106.112";
static const string Version = "106.113";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";

View File

@ -3,10 +3,14 @@
namespace higan::NeoGeoPocket {
APU apu;
#include "memory.cpp"
#include "serialization.cpp"
auto APU::main() -> void {
step(1);
if(!io.enable) return step(16);
//static uint ctr=0;
//if(++ctr<20) print(disassemble(r.pc), "\n");
instruction();
}
auto APU::step(uint clocks) -> void {
@ -26,6 +30,21 @@ auto APU::power() -> void {
while(true) scheduler.synchronize(), main();
});
ram.allocate(0x1000);
io = {};
}
auto APU::enable() -> void {
Thread::destroy();
Z80::power();
bus->grant(false);
Thread::create(system.frequency() / 2.0, [&] {
while(true) scheduler.synchronize(), main();
});
io.enable = true;
}
auto APU::disable() -> void {
io.enable = false;
}
}

View File

@ -6,16 +6,22 @@ struct APU : Z80, Z80::Bus, Thread {
auto step(uint clocks) -> void override;
auto synchronizing() const -> bool override;
auto power() -> void;
auto enable() -> void;
auto disable() -> void;
//memory.cpp
auto read(uint16 address) -> uint8 override { return 0; }
auto write(uint16 address, uint8 data) -> void override {}
auto read(uint16 address) -> uint8 override;
auto write(uint16 address, uint8 data) -> void override;
auto in(uint8 address) -> uint8 override { return 0; }
auto out(uint8 address, uint8 data) -> void override {}
auto in(uint8 address) -> uint8 override;
auto out(uint8 address, uint8 data) -> void override;
//serialization.cpp
auto serialize(serializer&) -> void;
struct IO {
uint1 enable;
} io;
};
extern APU apu;

19
higan/ngp/apu/memory.cpp Normal file
View File

@ -0,0 +1,19 @@
auto APU::read(uint16 address) -> uint8 {
if(address >= 0x0000 && address <= 0x0fff) return ram.read(address);
if(address == 0x8000) return cpu.io.apuPort;
return 0xff;
}
auto APU::write(uint16 address, uint8 data) -> void {
if(address >= 0x0000 && address <= 0x0fff) return ram.write(address, data);
if(address == 0x8000) return (void)(cpu.io.apuPort = data);
if(address == 0xc000) return cpu.setInterruptAPU(1);
}
auto APU::in(uint8 address) -> uint8 {
return 0xff;
}
auto APU::out(uint8 address, uint8 data) -> void {
cpu.setInterruptAPU(0); //todo: unconfirmed
}

View File

@ -63,14 +63,12 @@ auto Cartridge::power() -> void {
}
auto Cartridge::read(uint1 chip, uint21 address) -> uint8 {
if(!flash[0]) return 0xff;
if(!flash[1]) chip = 0;
if(!flash[chip]) return 0xff;
return flash[chip].read(address);
}
auto Cartridge::write(uint1 chip, uint21 address, uint8 data) -> void {
if(!flash[0]) return;
if(!flash[1]) chip = 0;
if(!flash[chip]) return;
return flash[chip].write(address, data);
}

View File

@ -2,13 +2,17 @@
namespace higan::NeoGeoPocket {
//bool tracing=false;
CPU cpu;
#include "memory.cpp"
#include "io.cpp"
#include "serialization.cpp"
auto CPU::main() -> void {
static uint ctr=0;
if(++ctr<200) print(disassemble(), "\n");
//static uint ctr=0;
//if(tracing&&++ctr<4000) print(disassemble(), "\n");
//else tracing=false;
instruction();
step(1);
}
@ -26,18 +30,33 @@ auto CPU::power() -> void {
while(true) scheduler.synchronize(), main();
});
ram.allocate(0x3000);
r.pc.b.b0 = read(0xffff00);
r.pc.b.b1 = read(0xffff01);
r.pc.b.b2 = read(0xffff02);
r.pc.b.b3 = read(0xffff03);
r.pc.l.l0 = 0xff1800;
io = {};
}
auto CPU::setInterruptAPU(boolean line) -> void {
if(io.irq.apu != line) {
io.irq.apu = line;
if(line) interrupt(0xffff30);
}
}
auto CPU::setInterruptHblank(boolean line) -> void {
io.irq.hblank = line;
//if(line) interrupt(0xffff0c);
if(io.irq.hblank != line) {
io.irq.hblank = line;
//if(line) interrupt(0xffff0c);
}
}
auto CPU::setInterruptVblank(boolean line) -> void {
io.irq.vblank = line;
if(line) interrupt(0xffff10);
if(io.irq.vblank != line) {
io.irq.vblank = line;
if(line) interrupt(0xffff2c);
}
}
}

View File

@ -6,6 +6,7 @@ struct CPU : TLCS900H, Thread {
auto step(uint clocks) -> void override;
auto power() -> void;
auto setInterruptAPU(boolean line) -> void;
auto setInterruptHblank(boolean line) -> void;
auto setInterruptVblank(boolean line) -> void;
@ -13,15 +14,22 @@ struct CPU : TLCS900H, Thread {
auto read(uint24 address) -> uint8 override;
auto write(uint24 address, uint8 data) -> void override;
//io.cpp
auto readIO(uint8 address) -> uint8;
auto writeIO(uint8 address, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private:
//private:
struct IO {
struct IRQ {
boolean apu;
boolean hblank;
boolean vblank;
} irq;
uint8 apuPort;
} io;
};

45
higan/ngp/cpu/io.cpp Normal file
View File

@ -0,0 +1,45 @@
auto CPU::readIO(uint8 address) -> uint8 {
uint8 data = 0xff;
switch(address) {
case 0xb0:
controls.poll();
data.bit(0) = controls.upLatch;
data.bit(1) = controls.downLatch;
data.bit(2) = controls.leftLatch;
data.bit(3) = controls.rightLatch;
data.bit(4) = controls.a->value;
data.bit(5) = controls.b->value;
data.bit(6) = controls.option->value;
data.bit(7) = 0; //unused?
break;
case 0xb1:
data.bit(0) = 0; //power button
data.bit(1) = 1; //sub battery (CR2032)
break;
case 0xbc:
data = io.apuPort;
break;
}
return data;
}
auto CPU::writeIO(uint8 address, uint8 data) -> void {
switch(address) {
case 0xb9:
if(data == 0x55) {
apu.enable();
}
if(data == 0xaa) {
apu.disable();
}
break;
case 0xbc:
io.apuPort = data;
break;
}
}

View File

@ -1,27 +1,20 @@
auto CPU::read(uint24 address) -> uint8 {
if(address < 0x000100) return 0xff;
if(address < 0x004000) return 0xff;
if(address < 0x007000) return cpu.ram.read((uint14)address);
if(address < 0x008000) return apu.ram.read((uint12)address);
if(address < 0x00c000) return vpu.ram.read((uint14)address);
if(address < 0x200000) return 0xff;
if(address < 0x400000) return cartridge.read(0, (uint21)address);
if(address < 0x800000) return 0xff;
if(address < 0xa00000) return cartridge.read(1, (uint21)address);
if(address < 0xff0000) return 0xff;
return system.bios.read((uint16)address);
if(address >= 0x000000 && address <= 0x0000ff) return cpu.readIO(address);
if(address >= 0x004000 && address <= 0x006fff) return cpu.ram.read(address);
if(address >= 0x007000 && address <= 0x007fff) return apu.ram.read(address);
if(address >= 0x008000 && address <= 0x00bfff) return vpu.read(address);
if(address >= 0x200000 && address <= 0x3fffff) return cartridge.read(0, address);
if(address >= 0x800000 && address <= 0x9fffff) return cartridge.read(1, address);
if(address >= 0xff0000 && address <= 0xffffff) return system.bios.read(address);
return 0xff;
}
auto CPU::write(uint24 address, uint8 data) -> void {
if(address < 0x000100) return;
if(address < 0x004000) return;
if(address < 0x007000) return cpu.ram.write((uint14)address, data);
if(address < 0x008000) return apu.ram.write((uint12)address, data);
if(address < 0x00c000) return vpu.ram.write((uint14)address, data);
if(address < 0x200000) return;
if(address < 0x400000) return cartridge.write(0, (uint21)address, data);
if(address < 0x800000) return;
if(address < 0xa00000) return cartridge.write(1, (uint21)address, data);
if(address < 0xff0000) return;
return system.bios.write((uint16)address, data);
if(address >= 0x000000 && address <= 0x0000ff) return cpu.writeIO(address, data);
if(address >= 0x004000 && address <= 0x006fff) return cpu.ram.write(address, data);
if(address >= 0x007000 && address <= 0x007fff) return apu.ram.write(address, data);
if(address >= 0x008000 && address <= 0x00bfff) return vpu.write(address, data);
if(address >= 0x200000 && address <= 0x3fffff) return cartridge.write(0, address, data);
if(address >= 0x800000 && address <= 0x9fffff) return cartridge.write(1, address, data);
if(address >= 0xff0000 && address <= 0xffffff) return system.bios.write(address, data);
}

View File

@ -11,7 +11,7 @@ struct Controls {
auto load(Node::Object, Node::Object) -> void;
auto poll() -> void;
private:
//private:
bool yHold = 0;
bool upLatch = 0;
bool downLatch = 0;

149
higan/ngp/vpu/memory.cpp Normal file
View File

@ -0,0 +1,149 @@
auto VPU::read(uint24 address) -> uint8 {
address = 0x8000 | (uint14)address;
if(address >= 0x8800 && address <= 0x88ff) return spriteRAM.read(address);
if(address >= 0x8900 && address <= 0x8fff) return 0xff; //todo: does spriteRAM mirror here?
if(address >= 0x9000 && address <= 0x9fff) return scrollRAM.read(address);
if(address >= 0xa000 && address <= 0xbfff) return characterRAM.read(address);
uint8 data = 0xff;
switch(address) {
case 0x8000:
data.bit(6) = io.hblankEnableIRQ;
data.bit(7) = io.vblankEnableIRQ;
break;
case 0x8002: data = io.window.horigin; break;
case 0x8003: data = io.window.vorigin; break;
case 0x8004: data = io.window.hsize; break;
case 0x8005: data = io.window.vsize; break;
case 0x8008: data = io.hcounter >> 1; break;
case 0x8009: data = io.vcounter; break;
case 0x8010:
data.bit(6) = io.vblankActive;
data.bit(7) = io.characterOver;
break;
case 0x8012:
data.bits(0,2) = io.outsideWindowColor;
data.bit(7) = io.negateScreen;
break;
case 0x8020: data = io.sprite.hoffset; break;
case 0x8021: data = io.sprite.voffset; break;
case 0x8030: data.bit(7) = io.planePriority; break;
case 0x8032: data = io.plane1.hscroll; break;
case 0x8033: data = io.plane1.vscroll; break;
case 0x8034: data = io.plane2.hscroll; break;
case 0x8035: data = io.plane2.vscroll; break;
case 0x8100: break;
case 0x8101: data.bit(0) = io.sprite.palette[0].bit(1); break;
case 0x8102: data.bit(0) = io.sprite.palette[0].bit(2); break;
case 0x8103: data.bit(0) = io.sprite.palette[0].bit(3); break;
case 0x8104: break;
case 0x8105: data.bit(0) = io.sprite.palette[1].bit(1); break;
case 0x8106: data.bit(0) = io.sprite.palette[1].bit(2); break;
case 0x8107: data.bit(0) = io.sprite.palette[1].bit(3); break;
case 0x8108: break;
case 0x8109: data.bit(0) = io.plane1.palette[0].bit(1); break;
case 0x810a: data.bit(0) = io.plane1.palette[0].bit(2); break;
case 0x810b: data.bit(0) = io.plane1.palette[0].bit(3); break;
case 0x810c: break;
case 0x810d: data.bit(0) = io.plane1.palette[1].bit(1); break;
case 0x810e: data.bit(0) = io.plane1.palette[1].bit(2); break;
case 0x810f: data.bit(0) = io.plane1.palette[1].bit(3); break;
case 0x8110: break;
case 0x8111: data.bit(0) = io.plane2.palette[0].bit(1); break;
case 0x8112: data.bit(0) = io.plane2.palette[0].bit(2); break;
case 0x8113: data.bit(0) = io.plane2.palette[0].bit(3); break;
case 0x8114: break;
case 0x8115: data.bit(0) = io.plane2.palette[1].bit(1); break;
case 0x8116: data.bit(0) = io.plane2.palette[1].bit(2); break;
case 0x8117: data.bit(0) = io.plane2.palette[1].bit(3); break;
case 0x8400: data = io.led.control; break;
case 0x8402: data = io.led.frequency; break;
case 0x87fe: data = 0x3f; break; //input port register (reserved)
}
return data;
}
auto VPU::write(uint24 address, uint8 data) -> void {
address = 0x8000 | (uint14)address;
if(address >= 0x8800 && address <= 0x88ff) return spriteRAM.write(address, data);
if(address >= 0x8900 && address <= 0x8fff) return; //todo: does spriteRAM mirror here?
if(address >= 0x9000 && address <= 0x9fff) return scrollRAM.write(address, data);
if(address >= 0xa000 && address <= 0xbfff) return characterRAM.write(address, data);
switch(address) {
case 0x8000:
io.hblankEnableIRQ = data.bit(6);
io.vblankEnableIRQ = data.bit(7);
cpu.setInterruptHblank(io.hblankActive & io.hblankEnableIRQ);
cpu.setInterruptVblank(io.vblankActive & io.vblankEnableIRQ);
break;
case 0x8002: io.window.horigin = data; break;
case 0x8003: io.window.vorigin = data; break;
case 0x8004: io.window.hsize = data; break;
case 0x8005: io.window.vsize = data; break;
case 0x8012:
io.outsideWindowColor = data.bits(0,2);
io.negateScreen = data.bit(7);
break;
case 0x8020: io.sprite.hoffset = data; break;
case 0x8021: io.sprite.voffset = data; break;
case 0x8030: io.planePriority = data.bit(7); break;
case 0x8032: io.plane1.hscroll = data; break;
case 0x8033: io.plane1.vscroll = data; break;
case 0x8034: io.plane2.hscroll = data; break;
case 0x8035: io.plane2.vscroll = data; break;
case 0x8100: break;
case 0x8101: io.sprite.palette[0].bit(1) = data.bit(0); break;
case 0x8102: io.sprite.palette[0].bit(2) = data.bit(0); break;
case 0x8103: io.sprite.palette[0].bit(3) = data.bit(0); break;
case 0x8104: break;
case 0x8105: io.sprite.palette[1].bit(1) = data.bit(0); break;
case 0x8106: io.sprite.palette[1].bit(2) = data.bit(0); break;
case 0x8107: io.sprite.palette[1].bit(3) = data.bit(0); break;
case 0x8108: break;
case 0x8109: io.plane1.palette[0].bit(1) = data.bit(0); break;
case 0x810a: io.plane1.palette[0].bit(2) = data.bit(0); break;
case 0x810b: io.plane1.palette[0].bit(3) = data.bit(0); break;
case 0x810c: break;
case 0x810d: io.plane1.palette[1].bit(1) = data.bit(0); break;
case 0x810e: io.plane1.palette[1].bit(2) = data.bit(0); break;
case 0x810f: io.plane1.palette[1].bit(3) = data.bit(0); break;
case 0x8110: break;
case 0x8111: io.plane2.palette[0].bit(1) = data.bit(0); break;
case 0x8112: io.plane2.palette[0].bit(2) = data.bit(0); break;
case 0x8113: io.plane2.palette[0].bit(3) = data.bit(0); break;
case 0x8114: break;
case 0x8115: io.plane2.palette[1].bit(1) = data.bit(0); break;
case 0x8116: io.plane2.palette[1].bit(2) = data.bit(0); break;
case 0x8117: io.plane2.palette[1].bit(3) = data.bit(0); break;
case 0x8400: io.led.control.bits(3,7) = data.bits(3,7); break;
case 0x8402: io.led.frequency = data; break;
case 0x87e0:
if(data == 0x52) io = {};
break;
}
}

View File

@ -3,36 +3,42 @@
namespace higan::NeoGeoPocket {
VPU vpu;
#include "memory.cpp"
#include "serialization.cpp"
auto VPU::main() -> void {
cpu.setInterruptHblank(0);
for(uint hclock : range(480)) {
io.hcounter++;
step(1);
}
if(io.vcounter <= 150) {
if(ram[0x0000].bit(6)) cpu.setInterruptHblank(1);
io.hblankActive = 1;
cpu.setInterruptHblank(io.hblankEnableIRQ);
}
for(uint hclock : range(35)) {
io.hcounter++;
step(1);
}
cpu.setInterruptHblank(0);
io.hcounter = 0;
io.hblankActive = 0;
cpu.setInterruptHblank(0);
io.vcounter++;
if(io.vcounter == 152) {
ram[0x0010].bit(6) = 1;
if(ram[0x0000].bit(7)) cpu.setInterruptVblank(1);
io.vblankActive = 1;
cpu.setInterruptVblank(io.vblankEnableIRQ);
scheduler.exit(Scheduler::Event::Frame);
}
if(io.vcounter == 198) {
if(ram[0x0000].bit(6)) cpu.setInterruptHblank(1);
io.hblankActive = 1;
cpu.setInterruptHblank(io.hblankEnableIRQ);
}
if(io.vcounter == 199) {
ram[0x0010].bit(6) = 0;
cpu.setInterruptVblank(0);
io.vcounter = 0;
io.vblankActive = 0;
io.characterOver = 0;
cpu.setInterruptVblank(0);
}
}
@ -42,8 +48,9 @@ auto VPU::step(uint clocks) -> void {
}
auto VPU::refresh() -> void {
for(uint address : range(0x4000)) buffer[address] = ram[address];
for(uint address : range(0x1f00)) buffer[address + 0x4000] = cpu.ram[address + 0x3000 - 0x1f00];
for(uint address : range(0x1000)) buffer[address] = scrollRAM[address];
for(uint address : range(0x2000)) buffer[address + 0x1000] = characterRAM[address];
for(uint address : range(0x2f00)) buffer[address + 0x3000] = cpu.ram[address + 0x100];
display.screen->refresh(buffer, 160 * sizeof(uint32), 160, 152);
}
@ -51,7 +58,9 @@ auto VPU::power() -> void {
Thread::create(system.frequency(), [&] {
while(true) scheduler.synchronize(), main();
});
ram.allocate(0x4000);
spriteRAM.allocate(0x100);
scrollRAM.allocate(0x1000);
characterRAM.allocate(0x2000);
io = {};
}

View File

@ -2,7 +2,9 @@
//K2GE (Neo Geo Pocket Color)
struct VPU : Thread {
Memory::Writable<uint8> ram;
Memory::Writable<uint8> spriteRAM;
Memory::Writable<uint8> scrollRAM;
Memory::Writable<uint8> characterRAM;
//vpu.cpp
auto main() -> void;
@ -10,15 +12,55 @@ struct VPU : Thread {
auto refresh() -> void;
auto power() -> void;
//memory.cpp
auto read(uint24 address) -> uint8;
auto write(uint24 address, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private:
//private:
uint32 buffer[160 * 152];
struct IO {
uint8 vcounter;
uint10 hcounter;
uint1 hblankEnableIRQ = 1; //todo: should be 0
uint1 vblankEnableIRQ = 1; //todo: should be 0
uint1 hblankActive;
uint1 vblankActive;
uint3 outsideWindowColor;
uint1 negateScreen;
uint1 characterOver;
uint1 planePriority; //0 = plane1 > plane2; 1 = plane2 > plane1
struct Plane {
uint8 hscroll;
uint8 vscroll;
uint4 palette[2];
} plane1, plane2;
struct Sprite {
uint8 hoffset;
uint8 voffset;
uint4 palette[2];
} sprite;
struct Window {
uint8 horigin;
uint8 vorigin;
uint8 hsize;
uint8 vsize;
} window;
struct LED {
uint8 control = 0x07;
uint8 frequency = 0x80;
} led;
} io;
};

View File

@ -57,6 +57,8 @@ auto Emulator::create(shared_pointer<higan::Interface> instance, string location
}
auto Emulator::main() -> void {
if(Application::modal()) return (void)usleep(20 * 1000);
inputManager.poll();
hotkeys.poll();

View File

@ -5,7 +5,6 @@ namespace nall {
auto image::impose(blend mode, unsigned targetX, unsigned targetY, image source, unsigned sourceX, unsigned sourceY, unsigned sourceWidth, unsigned sourceHeight) -> void {
source.transform(_endian, _depth, _alpha.mask(), _red.mask(), _green.mask(), _blue.mask());
#pragma omp parallel for
for(unsigned y = 0; y < sourceHeight; y++) {
const uint8_t* sp = source._data + source.pitch() * (sourceY + y) + source.stride() * sourceX;
uint8_t* dp = _data + pitch() * (targetY + y) + stride() * targetX;

View File

@ -27,7 +27,6 @@ auto image::scaleLinearWidth(unsigned outputWidth) -> void {
unsigned outputPitch = outputWidth * stride();
uint64_t xstride = ((uint64_t)(_width - 1) << 32) / max(1u, outputWidth - 1);
#pragma omp parallel for
for(unsigned y = 0; y < _height; y++) {
uint64_t xfraction = 0;
@ -63,7 +62,6 @@ auto image::scaleLinearHeight(unsigned outputHeight) -> void {
uint8_t* outputData = allocate(_width, outputHeight, stride());
uint64_t ystride = ((uint64_t)(_height - 1) << 32) / max(1u, outputHeight - 1);
#pragma omp parallel for
for(unsigned x = 0; x < _width; x++) {
uint64_t yfraction = 0;
@ -102,7 +100,6 @@ auto image::scaleLinear(unsigned outputWidth, unsigned outputHeight) -> void {
uint64_t xstride = ((uint64_t)(_width - 1) << 32) / max(1u, outputWidth - 1);
uint64_t ystride = ((uint64_t)(_height - 1) << 32) / max(1u, outputHeight - 1);
#pragma omp parallel for
for(unsigned y = 0; y < outputHeight; y++) {
uint64_t yfraction = ystride * y;
uint64_t xfraction = 0;
@ -147,7 +144,6 @@ auto image::scaleNearest(unsigned outputWidth, unsigned outputHeight) -> void {
uint64_t xstride = ((uint64_t)_width << 32) / outputWidth;
uint64_t ystride = ((uint64_t)_height << 32) / outputHeight;
#pragma omp parallel for
for(unsigned y = 0; y < outputHeight; y++) {
uint64_t yfraction = ystride * y;
uint64_t xfraction = 0;

View File

@ -74,7 +74,6 @@ auto image::crop(unsigned outputX, unsigned outputY, unsigned outputWidth, unsig
uint8_t* outputData = allocate(outputWidth, outputHeight, stride());
unsigned outputPitch = outputWidth * stride();
#pragma omp parallel for
for(unsigned y = 0; y < outputHeight; y++) {
const uint8_t* sp = _data + pitch() * (outputY + y) + stride() * outputX;
uint8_t* dp = outputData + outputPitch * y;
@ -97,7 +96,6 @@ auto image::alphaBlend(uint64_t alphaColor) -> void {
uint64_t alphaG = (alphaColor & _green.mask()) >> _green.shift();
uint64_t alphaB = (alphaColor & _blue.mask() ) >> _blue.shift();
#pragma omp parallel for
for(unsigned y = 0; y < _height; y++) {
uint8_t* dp = _data + pitch() * y;
for(unsigned x = 0; x < _width; x++) {
@ -123,7 +121,6 @@ auto image::alphaBlend(uint64_t alphaColor) -> void {
auto image::alphaMultiply() -> void {
unsigned divisor = (1 << _alpha.depth()) - 1;
#pragma omp parallel for
for(unsigned y = 0; y < _height; y++) {
uint8_t* dp = _data + pitch() * y;
for(unsigned x = 0; x < _width; x++) {
@ -154,7 +151,6 @@ auto image::transform(bool outputEndian, unsigned outputDepth, uint64_t outputAl
image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask);
output.allocate(_width, _height);
#pragma omp parallel for
for(unsigned y = 0; y < _height; y++) {
const uint8_t* sp = _data + pitch() * y;
uint8_t* dp = output._data + output.pitch() * y;

View File

@ -1,7 +1,7 @@
ifeq ($(ruby),)
ifeq ($(platform),windows)
ruby += video.wgl video.direct3d video.directdraw video.gdi
ruby += audio.asio audio.wasapi audio.xaudio2 audio.directsound
ruby += audio.asio audio.wasapi audio.xaudio2 audio.directsound audio.waveout
ruby += input.windows
else ifeq ($(platform),macos)
ruby += video.cgl
@ -42,6 +42,7 @@ ruby.options += $(if $(findstring audio.directsound,$(ruby)),-ldsound -luuid)
ruby.options += $(if $(findstring audio.pulseaudio,$(ruby)),-lpulse)
ruby.options += $(if $(findstring audio.pulseaudiosimple,$(ruby)),-lpulse-simple)
ruby.options += $(if $(findstring audio.wasapi,$(ruby)),-lavrt -luuid)
ruby.options += $(if $(findstring audio.waveout,$(ruby)),-lwinmm)
ruby.options += $(if $(findstring audio.xaudio2,$(ruby)),-lole32)
ruby.options += $(if $(findstring input.sdl,$(ruby)),$(shell sdl2-config --libs))

View File

@ -34,6 +34,10 @@
#include <ruby/audio/wasapi.cpp>
#endif
#if defined(AUDIO_WAVEOUT)
#include <ruby/audio/waveout.cpp>
#endif
#if defined(AUDIO_XAUDIO2)
#include <ruby/audio/xaudio2.cpp>
#endif
@ -174,6 +178,10 @@ auto Audio::create(string driver) -> bool {
if(driver == "WASAPI") self.instance = new AudioWASAPI(*this);
#endif
#if defined(AUDIO_WAVEOUT)
if(driver == "waveOut") self.instance = new AudioWaveOut(*this);
#endif
#if defined(AUDIO_XAUDIO2)
if(driver == "XAudio 2.1") self.instance = new AudioXAudio2(*this);
#endif
@ -202,6 +210,10 @@ auto Audio::hasDrivers() -> vector<string> {
"DirectSound 7.0",
#endif
#if defined(AUDIO_WAVEOUT)
"waveOut",
#endif
#if defined(AUDIO_ALSA)
"ALSA",
#endif
@ -238,6 +250,8 @@ auto Audio::optimalDriver() -> string {
return "XAudio 2.1";
#elif defined(AUDIO_DIRECTSOUND)
return "DirectSound 7.0";
#elif defined(AUDIO_WAVEOUT)
return "waveOut";
#elif defined(AUDIO_ALSA)
return "ALSA";
#elif defined(AUDIO_OSS)
@ -256,7 +270,9 @@ auto Audio::optimalDriver() -> string {
}
auto Audio::safestDriver() -> string {
#if defined(AUDIO_DIRECTSOUND)
#if defined(AUDIO_WAVEOUT)
return "waveOut";
#elif defined(AUDIO_DIRECTSOUND)
return "DirectSound 7.0";
#elif defined(AUDIO_WASAPI)
return "WASAPI";

124
ruby/audio/waveout.cpp Normal file
View File

@ -0,0 +1,124 @@
#include <mmsystem.h>
auto CALLBACK waveOutCallback(HWAVEOUT handle, UINT message, DWORD_PTR userData, DWORD_PTR, DWORD_PTR) -> void;
struct AudioWaveOut : AudioDriver {
AudioWaveOut& self = *this;
AudioWaveOut(Audio& super) : AudioDriver(super) {}
~AudioWaveOut() { terminate(); }
auto create() -> bool override {
super.setChannels(2);
super.setFrequency(44100);
super.setLatency(0);
return initialize();
}
auto driver() -> string override { return "waveOut"; }
auto ready() -> bool override { return true; }
auto hasDevices() -> vector<string> override {
vector<string> devices{"Default"};
for(uint index : range(waveOutGetNumDevs())) {
WAVEOUTCAPS caps{};
if(waveOutGetDevCaps(index, &caps, sizeof(WAVEOUTCAPS)) == MMSYSERR_NOERROR) {
devices.append((const char*)utf8_t(caps.szPname));
}
}
return devices;
}
auto hasBlocking() -> bool override { return true; }
auto hasFrequencies() -> vector<uint> override { return {44100}; }
auto hasLatencies() -> vector<uint> override { return {0}; }
auto setBlocking(bool blocking) -> bool override { return true; }
auto clear() -> void override {
for(auto& header : headers) {
memory::fill(header.lpData, frameCount * 4);
}
}
auto output(const double samples[]) -> void override {
uint16_t lsample = sclamp<16>(samples[0] * 32767.0);
uint16_t rsample = sclamp<16>(samples[1] * 32767.0);
auto block = (uint32_t*)headers[blockIndex].lpData;
block[frameIndex] = lsample << 0 | rsample << 16;
if(++frameIndex >= frameCount) {
frameIndex = 0;
while(true) {
auto result = waveOutWrite(handle, &headers[blockIndex], sizeof(WAVEHDR));
if(!self.blocking || result != WAVERR_STILLPLAYING) break;
InterlockedIncrement(&blockQueue);
}
if(++blockIndex >= blockCount) {
blockIndex = 0;
}
}
}
private:
auto initialize() -> bool {
terminate();
auto deviceIndex = hasDevices().find(self.device);
if(!deviceIndex) deviceIndex = 0;
WAVEFORMATEX format{};
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = 2;
format.nSamplesPerSec = 44100;
format.nBlockAlign = 4;
format.wBitsPerSample = 16;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0; //not sizeof(WAVEFORMAT); size of extra information after WAVEFORMATEX
//-1 = default; 0+ = specific device; subtract -1 as hasDevices() includes "Default" entry
waveOutOpen(&handle, (int)*deviceIndex - 1, &format, (DWORD_PTR)waveOutCallback, (DWORD_PTR)this, CALLBACK_FUNCTION);
headers.resize(blockCount);
for(auto& header : headers) {
memory::fill(&header, sizeof(WAVEHDR));
header.lpData = (LPSTR)LocalAlloc(LMEM_FIXED, frameCount * 4);
header.dwBufferLength = frameCount * 4;
waveOutPrepareHeader(handle, &header, sizeof(WAVEHDR));
}
frameIndex = 0;
blockIndex = 0;
waveOutSetVolume(handle, 0xffff); //100% volume (255 left, 255 right)
waveOutRestart(handle);
return true;
}
auto terminate() -> void {
if(!handle) return;
waveOutPause(handle);
waveOutReset(handle);
for(auto& header : headers) {
waveOutUnprepareHeader(handle, &header, sizeof(WAVEHDR));
LocalFree(header.lpData);
}
waveOutClose(handle);
handle = nullptr;
headers.reset();
}
HWAVEOUT handle = nullptr;
vector<WAVEHDR> headers;
const uint frameCount = 256;
const uint blockCount = 8;
uint frameIndex = 0;
uint blockIndex = 0;
public:
LONG blockQueue = 0;
};
auto CALLBACK waveOutCallback(HWAVEOUT handle, UINT message, DWORD_PTR userData, DWORD_PTR, DWORD_PTR) -> void {
auto instance = (AudioWaveOut*)userData;
InterlockedDecrement(&instance->blockQueue);
}