Plays DefleMask modules (.dmf) across every DefleMask target — Sega
Genesis/Mega Drive (YM2612 + SN76489), Sega Master System (SN76489), Game Boy,
PC Engine (HuC6280), NES (2A03), Commodore 64 (SID 6581/8580), Arcade
(YM2151 + SegaPCM) and Neo Geo (YM2610).
DefleMask .dmf shares its extension with the unrelated X-Tracker DMF
(DDMF) format handled by libopenmpt. DefleMask files are zlib-compressed
(first byte 0x78) and inflate to the magic .DelekDefleMask.; both
DMFPlugin::canHandle and OpenMPTPlugin::canHandle content-gate on that byte
so the two formats coexist (DMFPlugin::priority() returns 1 to win the
extension). Prior to this plugin, DefleMask .dmf was declined with
"Deflemask DMF format is unsupported".
The plugin wraps a headless slice of the Furnace engine (DivEngine),
driven directly:
DivEngine::load()inflates and parses the module (loader:furnace/src/engine/fileOps/dmf.cpp).DivEngine::init()withDIV_AUDIO_DUMMY(no live audio backend, no SDL).getSamples()pulls float PCM viaDivEngine::nextBuf()and converts to interleaved int16.
Large-stack load. DivEngine::loadDMF (like every Furnace loader) puts a
full DivSong — ~744 KB — on the stack. chipmachine loads songs from a
MusicPlayerList worker thread whose default stack is ~512 KB on macOS, so the
load overflowed and bus-errored ("thread stack size exceeded"); the main thread
(cmtest, CLI, -X textmode) has 8 MB and never hit it. DMFPlayer therefore
runs engine->load() on a dedicated 16 MB-stack thread. Regression:
TEST_CASE("DMF loads on a small-stack worker thread") invokes fromFile() from
a 512 KB-stack thread.
- Upstream: tildearrow/furnace,
commit
ebb8a9d806226fc389cc8f6d4e1bb3b28446f22b. - License: GPL v2 or later (see
../../../../furnace/LICENSE). Bundled chip-emulation cores underfurnace/extern/carry their own compatible licenses (BSD/MIT/LGPL/GPL) — see each subdirectory. - Slice: exactly the source set Furnace's own
BUILD_GUI=OFFheadless target compiles, minussrc/main.cpp/src/cli(we provide our own entry point). The GUI, SDL, PortAudio, libsndfile and RtMidi are not vendored.
Furnace bundles its own copies of chip cores (sn76496, ay8910, segapcm,
NES apu, Nuked-OPN/OPLL/PSG, emu2413, reSID-family, …) whose global C symbols
collide with GME / s98 / famitracker. As with goattrackerplugin /
nedplugin / mikmodplugin, everything is compiled with hidden visibility and
partial-linked with ld -r, leaving musix::DMFPlugin + dmfplugin_register
as the only exported symbols.
Grep the tree for [chipmachine local patch]:
furnace/src/fileutils.h— add#include <cerrno>(ENXIOwas only transitively available on Furnace's own toolchain).furnace/src/fileutils/cfile.cpp— add#include <cstring>(memset).furnace/src/engine/dispatchContainer.cpp— licence fix: drop theplatform/vic20.hinclude and thecase DIV_SYSTEM_VIC20arm.sound/vic20sound.cis lifted from VICE and carries VICE's GPLv2 header (it is the same filevictrackerplugin's VIC-I core was extracted from), andplatform/vic20.cppwas its only user.DIV_SYSTEM_VIC20is a Furnace.fur-only target — DefleMask has no VIC-20 system andDMFPlugin::canHandleclaims onlydmf— so the code was unreachable, and both files are omitted from the source list inCMakeLists.txtfor both build variants. The switch'sdefault:arm already falls back toDivPlatformDummy, so nothing else changes. Verify withnm -a <binary> | grep voltagefunction— it must print nothing.furnace/src/engine/engine.h— memory-safety fix (report upstream): theDivEngineconstructor zeroed thefloat* filePlayerBuf[DIV_MAX_OUTPUTS]pointer array withsizeof(float)instead ofsizeof(float*), leaving the upper half uninitialized.nextBuf()'s buffer-resize path then doesdelete[] filePlayerBuf[i]on those garbage pointers. It only appeared benign because a fresh process's heap is zero-filled; running the engine after other allocators had dirtied the heap (e.g. later in a test suite) turned it into apointer being freed was not allocatedabort.
Additionally, the Furnace slice is compiled at C++14 (upstream's standard);
at C++17 Furnace's bundled fmt 10.1 selects a std::string_view vsprintf
overload in log.cpp that it does not accept. Only DMFPlugin.cpp is compiled
at C++17 (it needs chipplugin.h's static inline). Furnace's bundled fmt is
compiled in and pinned ahead of chipmachine's newer fmt via BEFORE.
sndfile_stub/ provides no-op stubs for the handful of libsndfile symbols the
engine references (external audio-file instruments / audio export — never
exercised by .dmf playback), so libsndfile need not be vendored.
Furnace shipped the YM2608's internal ADPCM-A rhythm ROM here — copyrighted
Yamaha firmware under no licence, byte-for-byte the same dump libvgm carried.
It has been replaced with synthesized voices. Regenerate with
libvgmplugin/gen_2608rom_synth.c, which
emits this file and libvgm's copy from one source:
/tmp/g ../dmfplugin/furnace/src/engine/platform/sound/rss.h nonstatic
The nonstatic argument matters: ym2608Interface.cpp includes this as plain
const, whereas libvgm's fmopn_2608rom.h is static const. Furnace's address
layout is identical (set_start_end() in sound/ymfm/ymfm_opn.cpp), so nothing
else needs changing. Re-apply on revendor — and note there is deliberately no
.orig copy, since keeping one would mean keeping the ROM. See that generator's
README section for what the voices are and are not.
chipmachine/testmus/dmf/ fixtures span Genesis (Spring Yard.dmf), SMS
(SuPeHaRiMAIN-StarrySky.dmf) and Game Boy (darkman_bonus.dmf);
TEST_CASE("DMF") in chipmachine/test.cpp asserts each loads and produces
non-silent audio.