diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 1a4131ad..e3c29ab8 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "101.06"; + static const string Version = "101.07"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/md/cpu/cpu.cpp b/higan/md/cpu/cpu.cpp index 435d95ed..765e48b3 100644 --- a/higan/md/cpu/cpu.cpp +++ b/higan/md/cpu/cpu.cpp @@ -21,17 +21,51 @@ auto CPU::main() -> void { fp.print(pad(disassemble(r.pc), -60, ' '), " ", disassembleRegisters().replace("\n", " "), "\n"); #endif + if(state.interruptPending) { + if(state.interruptPending.bit((uint)Interrupt::HorizontalBlank)) { + state.interruptPending.bit((uint)Interrupt::HorizontalBlank) = 0; + r.i = 4; + return exception(Exception::Interrupt, Vector::HorizontalBlank); + } + + if(state.interruptPending.bit((uint)Interrupt::VerticalBlank)) { + state.interruptPending.bit((uint)Interrupt::VerticalBlank) = 0; + r.i = 6; + return exception(Exception::Interrupt, Vector::VerticalBlank); + } + } + instruction(); } auto CPU::step(uint clocks) -> void { + while(wait) { + Thread::step(1); + synchronize(); + } + Thread::step(clocks); + synchronize(); +} + +auto CPU::synchronize() -> void { synchronize(apu); synchronize(vdp); synchronize(psg); synchronize(ym2612); } +auto CPU::raise(Interrupt interrupt) -> void { + if(!state.interruptLine.bit((uint)interrupt)) { + state.interruptLine.bit((uint)interrupt) = 1; + state.interruptPending.bit((uint)interrupt) = 1; + } +} + +auto CPU::lower(Interrupt interrupt) -> void { + state.interruptLine.bit((uint)interrupt) = 0; +} + auto CPU::power() -> void { M68K::power(); @@ -41,7 +75,8 @@ auto CPU::power() -> void { auto CPU::reset() -> void { M68K::reset(); create(CPU::Enter, system.colorburst() * 15.0 / 7.0); - cycles = 0; + + memory::fill(&state, sizeof(State)); } auto CPU::readByte(uint24 addr) -> uint8 { diff --git a/higan/md/cpu/cpu.hpp b/higan/md/cpu/cpu.hpp index 602795aa..d7a79232 100644 --- a/higan/md/cpu/cpu.hpp +++ b/higan/md/cpu/cpu.hpp @@ -1,10 +1,21 @@ //Motorola 68000 struct CPU : Processor::M68K, Thread { + enum class Interrupt : uint { + HorizontalBlank, + VerticalBlank, + }; + + using Thread::synchronize; + static auto Enter() -> void; auto boot() -> void; auto main() -> void; auto step(uint clocks) -> void override; + auto synchronize() -> void; + + auto raise(Interrupt) -> void; + auto lower(Interrupt) -> void; auto power() -> void; auto reset() -> void; @@ -17,7 +28,10 @@ struct CPU : Processor::M68K, Thread { private: uint8 ram[64 * 1024]; - uint cycles = 0; + struct State { + uint32 interruptLine; + uint32 interruptPending; + } state; }; extern CPU cpu; diff --git a/higan/md/md.hpp b/higan/md/md.hpp index aa5bd036..43590e35 100644 --- a/higan/md/md.hpp +++ b/higan/md/md.hpp @@ -15,15 +15,24 @@ namespace MegaDrive { using Scheduler = Emulator::Scheduler; extern Scheduler scheduler; + struct Wait { + enum : uint { + VDP_DMA = 1 << 0, + }; + }; + struct Thread : Emulator::Thread { auto create(auto (*entrypoint)() -> void, double frequency) -> void { Emulator::Thread::create(entrypoint, frequency); scheduler.append(*this); + wait = 0; } inline auto synchronize(Thread& thread) -> void { if(clock() >= thread.clock()) scheduler.resume(thread); } + + uint wait = 0; }; #include diff --git a/higan/md/vdp/background.cpp b/higan/md/vdp/background.cpp index e572721d..2e7ab26d 100644 --- a/higan/md/vdp/background.cpp +++ b/higan/md/vdp/background.cpp @@ -1,4 +1,4 @@ -auto VDP::Background::scanline() -> void { +auto VDP::Background::scanline(uint y) -> void { } auto VDP::Background::run(uint x, uint y) -> void { @@ -17,9 +17,9 @@ auto VDP::Background::run(uint x, uint y) -> void { tileAddress += pixelY << 1 | pixelX >> 2; uint16 tileData = vdp.vram[tileAddress]; - uint4 palette = tileData >> (((pixelX & 3) ^ 3) << 2); - if(palette) { - output.color = tileAttributes.bits(13,14) << 4 | palette; + uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2); + if(color) { + output.color = tileAttributes.bits(13,14) << 4 | color; output.priority = tileAttributes.bit(15); } } diff --git a/higan/md/vdp/dma.cpp b/higan/md/vdp/dma.cpp index ec8bb6e3..1e01b1d7 100644 --- a/higan/md/vdp/dma.cpp +++ b/higan/md/vdp/dma.cpp @@ -1,18 +1,36 @@ auto VDP::dmaRun() -> void { if(!io.dmaEnable) return; - if(!io.dmaActive) return; + if(io.command.bits(4,5) != 2) return; - if(io.dmaMode == 2) return dmaFillVRAM(); + if(io.dmaMode <= 1) return dmaLoad(); + if(io.dmaMode == 2) return dmaFill(); + if(io.dmaMode == 3) return dmaCopy(); } -auto VDP::dmaFillVRAM() -> void { - auto address = io.address.bits(1,15); - auto data = io.dmaFillWord; - if(io.address.bit(0)) data = data >> 8 | data << 8; - vram[address] = data; - io.address += io.dataIncrement; +auto VDP::dmaLoad() -> void { + cpu.wait |= Wait::VDP_DMA; + auto data = cpu.readWord(io.dmaSource); + writeDataPort(data); + + io.dmaSource.bits(0,15) += 2; if(--io.dmaLength == 0) { - io.dmaActive = false; + io.command.bit(5) = 0; + cpu.wait &=~ Wait::VDP_DMA; } } + +auto VDP::dmaFill() -> void { + if(io.dmaFillWait) return; + + auto data = io.dmaFillWord; + writeDataPort(data); + + io.dmaSource.bits(0,15) += 2; + if(--io.dmaLength == 0 || --io.dmaLength == 0) { + io.command.bit(5) = 0; + } +} + +auto VDP::dmaCopy() -> void { +} diff --git a/higan/md/vdp/io.cpp b/higan/md/vdp/io.cpp index c2d3d3f0..2cf5c945 100644 --- a/higan/md/vdp/io.cpp +++ b/higan/md/vdp/io.cpp @@ -72,8 +72,7 @@ auto VDP::writeDataPort(uint16 data) -> void { io.commandPending = false; //DMA VRAM fill - if(io.command.bits(4,5) == 2) { - io.dmaActive = true; + if(io.dmaFillWait.lower()) { io.dmaFillWord = data; return; } @@ -113,7 +112,10 @@ auto VDP::readControlPort() -> uint16 { io.commandPending = false; uint16 result = 0b0011'0100'0000'0000; - result |= io.dmaActive << 1; + result |= 1 << 9; //FIFO empty + result |= (state.y >= 240) << 3; //vertical blank + result |= (state.y >= 240 || state.x >= 320) << 2; //horizontal blank + result |= io.command.bit(5) << 1; //DMA active return result; } @@ -126,7 +128,7 @@ auto VDP::writeControlPort(uint16 data) -> void { io.command.bits(2,5) = data.bits(4,7); io.address.bits(14,15) = data.bits(0,1); - + io.dmaFillWait = io.dmaMode == 2 && io.command.bits(4,5) == 2; return; } @@ -147,7 +149,7 @@ auto VDP::writeControlPort(uint16 data) -> void { case 0x00: { io.displayOverlayEnable = data.bit(0); io.counterLatch = data.bit(1); - io.horizontalInterruptEnable = data.bit(4); + io.horizontalBlankInterruptEnable = data.bit(4); io.leftColumnBlank = data.bit(5); return; } @@ -186,13 +188,13 @@ auto VDP::writeControlPort(uint16 data) -> void { //sprite attribute table location case 0x05: { - io.attrtableSprite = data.bits(0,7); + sprite.io.attributeAddress = data.bits(0,7) << 8; return; } //sprite pattern base address case 0x06: { - io.nametableBaseSprite = data.bit(5); + sprite.io.nametableAddressBase = data.bit(5); return; } diff --git a/higan/md/vdp/render.cpp b/higan/md/vdp/render.cpp index 571fc2a3..08867c6e 100644 --- a/higan/md/vdp/render.cpp +++ b/higan/md/vdp/render.cpp @@ -3,6 +3,17 @@ auto VDP::scanline() -> void { if(++state.y >= 262) state.y = 0; if(state.y == 0) scheduler.exit(Scheduler::Event::Frame); + if(state.y == 0) { + sprite.frame(); + } + + if(state.y < 240) { + planeA.scanline(state.y); + window.scanline(state.y); + planeB.scanline(state.y); + sprite.scanline(state.y); + } + state.output = buffer + (state.y * 2 + 0) * 1280; } @@ -16,12 +27,15 @@ auto VDP::run() -> void { planeA.run(state.x, state.y); planeB.run(state.x, state.y); + sprite.run(state.x, state.y); auto output = io.backgroundColor; if(auto color = planeB.output.color) output = color; if(auto color = planeA.output.color) output = color; + if(auto color = sprite.output.color) output = color; if(planeB.output.priority) if(auto color = planeB.output.color) output = color; if(planeA.output.priority) if(auto color = planeA.output.color) output = color; + if(sprite.output.priority) if(auto color = sprite.output.color) output = color; outputPixel(cram[output]); state.x++; diff --git a/higan/md/vdp/sprite.cpp b/higan/md/vdp/sprite.cpp new file mode 100644 index 00000000..667084c5 --- /dev/null +++ b/higan/md/vdp/sprite.cpp @@ -0,0 +1,76 @@ +auto VDP::Sprite::frame() -> void { + uint15 address = io.attributeAddress; + uint7 link = 0; + + oam.reset(); + while(oam.size() < 80) { + uint64 attributes; + attributes |= (uint64)vdp.vram[address + (link << 2) + 0] << 48; + attributes |= (uint64)vdp.vram[address + (link << 2) + 1] << 32; + attributes |= (uint64)vdp.vram[address + (link << 2) + 2] << 16; + attributes |= (uint64)vdp.vram[address + (link << 2) + 3] << 0; + + auto& object = oam.append(); + object.x = attributes.bits( 0, 9) - 128; + object.address = attributes.bits(16,26) << 4; + object.horizontalFlip = attributes.bit (27); + object.verticalFlip = attributes.bit (28); + object.palette = attributes.bits(29,30); + object.priority = attributes.bit (31); + object.height = attributes.bits(40,41) << 3; + object.width = attributes.bits(42,43) << 3; + object.y = attributes.bits(48,57) - 128; + + link = attributes.bits(32,38); + if(!link) break; + } +} + +auto VDP::Sprite::scanline(uint y) -> void { + object.reset(); + for(auto& o : oam) { + if((uint9)(o.y + o.height - 1) < y) continue; + if((uint9)(y + o.height - 1) < o.y) continue; + if(o.x == 0) break; + + object.append(o); + if(object.size() >= object.capacity()) break; + } +} + +auto VDP::Sprite::run(uint x, uint y) -> void { + output.priority = 0; + output.color = 0; + + for(auto& o : object) { + if((uint9)(o.x + o.width - 1) < x) continue; + if((uint9)(y + o.width - 1) < o.x) continue; + + auto objectX = (uint9)(x - o.x); + auto objectY = (uint9)(y - o.y); + if(o.horizontalFlip) objectX = (o.width - 1) - objectX; + if(o.verticalFlip) objectY = (o.height - 1) - objectY; + + uint tileX = objectX >> 3; + uint tileY = objectY >> 3; + uint tileNumber = tileX * (o.width >> 3) + tileY; + uint15 tileAddress = o.address + (tileNumber << 4); + uint pixelX = objectX & 7; + uint pixelY = objectY & 7; + tileAddress += pixelY << 1 | pixelX >> 2; + + uint16 tileData = vdp.vram[tileAddress]; + uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2); + if(color) { + output.color = o.palette << 4 | color; + output.priority = o.priority; + } + } +} + +auto VDP::Sprite::power() -> void { +} + +auto VDP::Sprite::reset() -> void { + memory::fill(&io, sizeof(IO)); +} diff --git a/higan/md/vdp/vdp.cpp b/higan/md/vdp/vdp.cpp index 3cde2ef1..8c8dd36d 100644 --- a/higan/md/vdp/vdp.cpp +++ b/higan/md/vdp/vdp.cpp @@ -10,6 +10,7 @@ VDP vdp; #include "dma.cpp" #include "render.cpp" #include "background.cpp" +#include "sprite.cpp" auto VDP::Enter() -> void { while(true) scheduler.synchronize(), vdp.main(); @@ -18,14 +19,26 @@ auto VDP::Enter() -> void { auto VDP::main() -> void { scanline(); if(state.y < 240) { + if(state.y == 0) { + cpu.lower(CPU::Interrupt::VerticalBlank); + } + cpu.lower(CPU::Interrupt::HorizontalBlank); for(uint x : range(320)) { run(); step(1); } + if(io.horizontalBlankInterruptEnable) { + cpu.raise(CPU::Interrupt::HorizontalBlank); + } + step(22); } else { + if(state.y == 240) { + if(io.verticalBlankInterruptEnable) { + cpu.raise(CPU::Interrupt::VerticalBlank); + } + } step(342); } - step(22); } auto VDP::step(uint clocks) -> void { @@ -44,6 +57,7 @@ auto VDP::power() -> void { planeA.power(); window.power(); planeB.power(); + sprite.power(); } auto VDP::reset() -> void { @@ -54,6 +68,7 @@ auto VDP::reset() -> void { planeA.reset(); window.reset(); planeB.reset(); + sprite.reset(); } } diff --git a/higan/md/vdp/vdp.hpp b/higan/md/vdp/vdp.hpp index f298f7eb..14e2c9ba 100644 --- a/higan/md/vdp/vdp.hpp +++ b/higan/md/vdp/vdp.hpp @@ -24,7 +24,9 @@ struct VDP : Thread { //dma.cpp auto dmaRun() -> void; - auto dmaFillVRAM() -> void; + auto dmaLoad() -> void; + auto dmaFill() -> void; + auto dmaCopy() -> void; //render.cpp auto scanline() -> void; @@ -33,7 +35,7 @@ struct VDP : Thread { //background.cpp struct Background { - auto scanline() -> void; + auto scanline(uint y) -> void; auto run(uint x, uint y) -> void; auto power() -> void; @@ -54,15 +56,51 @@ struct VDP : Thread { Background window; Background planeB; + //sprite.cpp + struct Sprite { + auto frame() -> void; + auto scanline(uint y) -> void; + auto run(uint x, uint y) -> void; + + auto power() -> void; + auto reset() -> void; + + struct IO { + uint15 attributeAddress; + uint1 nametableAddressBase; + } io; + + struct Object { + uint10 x; + uint10 y; + uint width; + uint height; + bool horizontalFlip; + bool verticalFlip; + uint2 palette; + uint1 priority; + uint15 address; + }; + + struct Output { + uint6 color; + boolean priority; + } output; + + array oam; + array object; + }; + Sprite sprite; + +private: uint16 vram[32768]; uint16 vramExpansion[32768]; //not present in stock Mega Drive hardware uint9 cram[64]; uint10 vsram[40]; -private: struct IO { //internal state - boolean dmaActive; + boolean dmaFillWait; uint8 dmaFillWord; //command @@ -73,7 +111,7 @@ private: //$00 mode register 1 uint1 displayOverlayEnable; uint1 counterLatch; - uint1 horizontalInterruptEnable; + uint1 horizontalBlankInterruptEnable; uint1 leftColumnBlank; //$01 mode register 2 @@ -84,12 +122,6 @@ private: uint1 displayEnable; uint1 externalVRAM; - //$05 sprite attribute table location - uint8 attrtableSprite; - - //$06 sprite pattern base address - uint1 nametableBaseSprite; - //$07 background color uint6 backgroundColor; diff --git a/higan/processor/m68k/m68k.cpp b/higan/processor/m68k/m68k.cpp index a6cd699a..90d712e5 100644 --- a/higan/processor/m68k/m68k.cpp +++ b/higan/processor/m68k/m68k.cpp @@ -46,6 +46,16 @@ auto M68K::supervisor() -> bool { } auto M68K::exception(uint exception, uint vector) -> void { + auto pc = r.pc; + auto sr = readSR(); + + r.s = 1; + r.t = 0; + + push(pc); + push(sr); + + r.pc = read(vector << 2); } } diff --git a/higan/processor/m68k/m68k.hpp b/higan/processor/m68k/m68k.hpp index 81b17a44..7b26e5b1 100644 --- a/higan/processor/m68k/m68k.hpp +++ b/higan/processor/m68k/m68k.hpp @@ -32,16 +32,19 @@ struct M68K { Unprivileged, Trap, + Interrupt, };}; struct Vector { enum : uint { - Illegal = 4, - DivisionByZero = 5, - BoundsCheck = 6, - Overflow = 7, - Unprivileged = 8, - IllegalLineA = 10, - IllegalLineF = 11, + Illegal = 4, + DivisionByZero = 5, + BoundsCheck = 6, + Overflow = 7, + Unprivileged = 8, + IllegalLineA = 10, + IllegalLineF = 11, + HorizontalBlank = 28, + VerticalBlank = 30, };}; M68K(); diff --git a/higan/target-tomoko/presentation/presentation.cpp b/higan/target-tomoko/presentation/presentation.cpp index 88e7240e..af4c3bb0 100644 --- a/higan/target-tomoko/presentation/presentation.cpp +++ b/higan/target-tomoko/presentation/presentation.cpp @@ -199,7 +199,28 @@ auto Presentation::updateEmulator() -> void { emulator->set("Scanline Emulation", scanlineEmulation.checked()); } +auto Presentation::clearViewport() -> void { + if(!video) return; + + uint32_t* output; + uint length = 0; + uint width = viewport.geometry().width(); + uint height = viewport.geometry().height(); + if(video->lock(output, length, width, height)) { + for(uint y : range(height)) { + auto dp = output + y * (length >> 2); + for(uint x : range(width)) *dp++ = 0xff000000; + } + + video->unlock(); + video->refresh(); + } +} + auto Presentation::resizeViewport() -> void { + //clear video area before resizing to avoid seeing distorted video momentarily + clearViewport(); + uint scale = 2; if(settings["Video/Scale"].text() == "Small" ) scale = 2; if(settings["Video/Scale"].text() == "Medium") scale = 3; @@ -219,7 +240,6 @@ auto Presentation::resizeViewport() -> void { if(!emulator) { viewport.setGeometry({0, 0, windowWidth, windowHeight}); - draw(Resource::Logo::higan); } else { auto videoSize = emulator->videoSize(windowWidth, windowHeight, aspectCorrection); viewport.setGeometry({ @@ -227,6 +247,9 @@ auto Presentation::resizeViewport() -> void { videoSize.width, videoSize.height }); } + + //clear video area again to ensure entire viewport area has been painted in + clearViewport(); } auto Presentation::toggleFullScreen() -> void { @@ -243,43 +266,9 @@ auto Presentation::toggleFullScreen() -> void { menuBar.setVisible(true); statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean()); } - - Application::processEvents(); resizeViewport(); } -auto Presentation::draw(image logo) -> void { - if(!video) return; - - uint32_t* output; - uint length = 0; - uint width = viewport.geometry().width(); - uint height = viewport.geometry().height(); - if(video->lock(output, length, width, height)) { - uint cx = (width - logo.width()) - 10; - uint cy = (height - logo.height()) - 10; - - image backdrop; - backdrop.allocate(width, height); - if(logo && !program->hasQuit) { - backdrop.sphericalGradient(0xff0000bf, 0xff000000, logo.width(), logo.height() / 2, width, height); - backdrop.impose(image::blend::sourceAlpha, cx, cy, logo, 0, 0, logo.width(), logo.height()); - } else { - backdrop.fill(0xff000000); - } - - auto data = (uint32_t*)backdrop.data(); - for(auto y : range(height)) { - auto dp = output + y * (length >> 2); - auto sp = data + y * width; - for(auto x : range(width)) *dp++ = *sp++; - } - - video->unlock(); - video->refresh(); - } -} - auto Presentation::loadShaders() -> void { auto pathname = locate("Video Shaders/"); diff --git a/higan/target-tomoko/presentation/presentation.hpp b/higan/target-tomoko/presentation/presentation.hpp index e55a5412..169a41d6 100644 --- a/higan/target-tomoko/presentation/presentation.hpp +++ b/higan/target-tomoko/presentation/presentation.hpp @@ -11,9 +11,9 @@ struct AboutWindow : Window { struct Presentation : Window { Presentation(); auto updateEmulator() -> void; + auto clearViewport() -> void; auto resizeViewport() -> void; auto toggleFullScreen() -> void; - auto draw(image logo = {}) -> void; auto loadShaders() -> void; MenuBar menuBar{this}; diff --git a/higan/target-tomoko/program/medium.cpp b/higan/target-tomoko/program/medium.cpp index 1f4825fd..afb13f91 100644 --- a/higan/target-tomoko/program/medium.cpp +++ b/higan/target-tomoko/program/medium.cpp @@ -29,7 +29,6 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa } updateAudioDriver(); updateAudioEffects(); - presentation->draw(); emulator->power(); presentation->resizeViewport(); @@ -45,14 +44,13 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa auto Program::unloadMedium() -> void { if(!emulator) return; - presentation->draw(); + presentation->clearViewport(); toolsManager->cheatEditor.saveCheats(); emulator->unload(); emulator = nullptr; mediumPaths.reset(); presentation->resizeViewport(); - presentation->draw(Resource::Logo::higan); presentation->setTitle({"higan v", Emulator::Version}); presentation->systemMenu.setVisible(false); presentation->toolsMenu.setVisible(false); diff --git a/higan/target-tomoko/program/program.cpp b/higan/target-tomoko/program/program.cpp index 59b65b14..5cbba905 100644 --- a/higan/target-tomoko/program/program.cpp +++ b/higan/target-tomoko/program/program.cpp @@ -31,7 +31,7 @@ Program::Program(string_vector args) { video->set(Video::Synchronize, settings["Video/Synchronize"].boolean()); if(!video->init()) video = Video::create("None"); - presentation->draw(Resource::Logo::higan); + presentation->clearViewport(); audio = Audio::create(settings["Audio/Driver"].text()); audio->set(Audio::Device, settings["Audio/Device"].text()); diff --git a/nall/array.hpp b/nall/array.hpp new file mode 100644 index 00000000..37b4b5c4 --- /dev/null +++ b/nall/array.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include + +namespace nall { + +template +struct array { + auto capacity() const -> uint { return Capacity; } + auto size() const -> uint { return _size; } + + auto reset() -> void { + for(uint n : range(_size)) _pool.t[n].~T(); + _size = 0; + } + + auto operator[](uint index) -> T& { + return _pool.t[index]; + } + + auto operator[](uint index) const -> const T& { + return _pool.t[index]; + } + + auto append() -> T& { + new(_pool.t + _size) T; + return _pool.t[_size++]; + } + + auto append(const T& value) -> void { + new(_pool.t + _size++) T(value); + } + + auto append(T&& value) -> void { + new(_pool.t + _size++) T(move(value)); + } + + auto begin() { return &_pool.t[0]; } + auto end() { return &_pool.t[_size]; } + + auto begin() const { return &_pool.t[0]; } + auto end() const { return &_pool.t[_size]; } + +private: + union U { + U() {} + ~U() {} + T t[Capacity]; + } _pool; + uint _size = 0; +}; + +} diff --git a/nall/nall.hpp b/nall/nall.hpp index 648da7f1..66560adb 100644 --- a/nall/nall.hpp +++ b/nall/nall.hpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include diff --git a/ruby/audio/oss.cpp b/ruby/audio/oss.cpp index eb9628cb..9e077f36 100644 --- a/ruby/audio/oss.cpp +++ b/ruby/audio/oss.cpp @@ -3,12 +3,7 @@ #include #include -//OSS4 soundcard.h includes below SNDCTL defines, but OSS3 does not -//However, OSS4 soundcard.h does not reside in -//Therefore, attempt to manually define SNDCTL values if using OSS3 header -//Note that if the defines below fail to work on any specific platform, one can point soundcard.h -//above to the correct location for OSS4 (usually /usr/lib/oss/include/sys/soundcard.h) -//Failing that, one can disable OSS4 ioctl calls inside init() and remove the below defines +//OSSv4 features: define fallbacks for OSSv3 (where these ioctls are ignored) #ifndef SNDCTL_DSP_COOKEDMODE #define SNDCTL_DSP_COOKEDMODE _IOW('P', 30, int) @@ -31,12 +26,14 @@ struct AudioOSS : Audio { string device = "/dev/dsp"; bool synchronize = true; uint frequency = 48000; + uint latency = 60; } settings; auto cap(const string& name) -> bool { if(name == Audio::Device) return true; if(name == Audio::Synchronize) return true; if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; return false; } @@ -44,6 +41,7 @@ struct AudioOSS : Audio { if(name == Audio::Device) return settings.device; if(name == Audio::Synchronize) return settings.synchronize; if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; return {}; } @@ -66,6 +64,12 @@ struct AudioOSS : Audio { return true; } + if(name == Audio::Latency && value.is()) { + settings.latency = value.get(); + if(device.fd >= 0) init(); + return true; + } + return false; } @@ -81,13 +85,11 @@ struct AudioOSS : Audio { device.fd = open(settings.device, O_WRONLY, O_NONBLOCK); if(device.fd < 0) return false; - #if 1 //SOUND_VERSION >= 0x040000 - //attempt to enable OSS4-specific features regardless of version - //OSS3 ioctl calls will silently fail, but sound will still work - int cooked = 1, policy = 4; //policy should be 0 - 10, lower = less latency, more CPU usage + int cooked = 1; ioctl(device.fd, SNDCTL_DSP_COOKEDMODE, &cooked); + //policy: 0 = minimum latency (higher CPU usage); 10 = maximum latency (lower CPU usage) + int policy = min(10, settings.latency / 20); //note: latency measurement isn't exact ioctl(device.fd, SNDCTL_DSP_POLICY, &policy); - #endif int frequency = settings.frequency; ioctl(device.fd, SNDCTL_DSP_CHANNELS, &device.channels); ioctl(device.fd, SNDCTL_DSP_SETFMT, &device.format);