Releases: RealBitdancer/opal
Releases · RealBitdancer/opal
Release list
opal v2.0.2
Added
OPAL_VERSION_MAJOR,OPAL_VERSION_MINOR,OPAL_VERSION_PATCH, and the packed
OPAL_VERSION(major * 10000 + minor * 100 + patch) inopal.h, so a consumer can
test the release at compile time without going through CMake.
Fixed
- The DRO v1 loader required a 24-byte file even for the 21-byte early DOSBox header.
Length versus file size now picks 21 or 24 bytes, with the old padding test as a
fallback. HSC effect5xno longer writes OPL AM or vibrato depth for nibbles 6 and 7. - Disabling 4-op mode no longer clears the primary channel's
pairIndex. That field is
the physical partner (orOPAL_CH_NONEfor channels that cannot pair).chanTypesays
whether 4-op is active.opalInitnow setsegOutto 0x1FF so a snapshot before the
first sample is silent, matchingenvelopeLevel. The example player and web loader use
the native OPL rate if the audio device reports a zero sample rate. - NEW (register 105h bit 0) was stored and then ignored. Bank 1 writes still took
effect while it was clear, channels 9-17 still mixed, and C0 stereo bits written in
OPL2 mode were forced to both speakers and discarded. The YMF262 ignores A1 except
for 105h itself. The extra channels stay silent. CHA and CHB are stored as written
and apply only after NEW is set. Waveforms 4-7 stay stored and play as 0-3 while NEW
is clear. Four-operator pairs wire as two-operator until NEW comes back. C0 resets to
zero, so enabling OPL3 without writing C0 is silent. - A masked timer overflow left FT1 and FT2 clear. Code that polls the flags and ignores
IRQ never saw the tick. Overflow now always sets the timer flag. IRQ (bit 7) is set
only when that timer is unmasked. Writing register 4 with ST1 or ST2 already set also
reloaded the counter. The load is the 0 to 1 edge, so a mask-only write no longer
restarts a running timer.
Changed
opalPannames its channel argumentchannel. It is still a channel index 0 to 17,
or 0 to 8 with bit 8 set for the second bank.
Documentation
- README treats NEW as a live mode bit. Bank 1, waveforms 4-7, CHA/CHB, and
four-operator pairing apply only while it is set. - The README
find_packageexample asks for 2.0, which is whatSameMajorVersion
actually accepts from this release. IMF type 0 versus type 1 is listed with the other
player limits.OPAL_ENVELOPE_STAGE_OFFis documented as unused by the core. Bindings
listed in BINDINGS.md are opal-zig and opal-rust at2.0.2-1. Security support is 2.x
and current main. GitHub Actions useactions/checkout@v7. The Android matrix includes
x86. First-party targets compile with warnings as errors.
opal v2.0.1
Fixed
- The output resampler in
opalSamplemultiplied the 16.16 interpolation fraction by the
step between two consecutive chip samples inint. Both factors reach 65535, so the
product overflows a 32-bit signed integer whenever a loud track swings more than half
of full scale in one chip sample, which OPL3 material with both banks and rhythm mode
does routinely. The result is undefined behavior: a garbage sample under a plain
compiler, and an immediate abort under UBSan or any toolchain that traps on signed
overflow. The multiply is now 64 bit, in the newopalInterpolatehelper. Output for
every non-overflowing sample is unchanged, so this is bit-identical to 2.0.0 wherever
2.0.0 was defined at all.
opal v2.0.0
This release breaks the public API deliberately. Every name now follows one consistent
convention, and the index-based layout removes the structure copy problem that plagued
the original public-domain source and every previous opal release, where an initialized
chip could never be copied or moved.
Added
- Public constants for the two state fields that previously spoke in magic numbers:
OPAL_ENVELOPE_STAGE_*(OFF, ATTACK, DECAY, SUSTAIN, RELEASE) for
OpalOperator::envelopeStageandOPAL_CHANNEL_TYPE_*(2OP, 4OP, 4OP2, DRUM) for
OpalChannel::chanType. Visualizers no longer hardcode the values.
Changed
- The whole library follows one naming convention: functions are
opalplus PascalCase
(opalWriteReg), types areOpalplus PascalCase with the struct tag matching the
typedef (OpalChannel, no_tsuffix), struct members are camelCase (egOut,
opSlot), and macros stayOPAL_SCREAMING_CASE. The old mixed style kept the
public-domain source readable against upstream. That concern is retired, and the
internal helpers insrc/opal.cfollow the same convention now. - Three functions are renamed for accuracy as well as style:
Opal_Portis now
opalWriteReg(the two-port address/data dance was never emulated, a register is
written directly),Opal_PortBufferedis nowopalWriteRegBuffered, andOpal_Read
is nowopalReadStatus(the status register is the only readable thing). The other
five change style only:opalInit,opalSetSampleRate,opalFlushWriteBuf,
opalPan, andopalSample. Signatures and behavior are unchanged. - The
Opalstruct is position independent. Every internal cross reference is an index
instead of a pointer:OpalChannel::Op[]becameopSlot[](operator indices),
ChannelPairbecamepairIndex,OutPtr[]becameoutSource[](operator indices
whose outputs sum into the channel), andOpalOperator::ModbecamemodSource
(an operator index,OPAL_MOD_OWN_FB, orOPAL_OP_NONE). Copying, assigning, or
memcpying an instance yields a fully working chip, so a plain copy is a save state.
Rendered output is bit-identical to 1.0.2. - This is a breaking layout change for consumers that read
Opalinternals, such as
visualizers. The new index fields make the active FM routing readable as data, which
pointers never were. - The boolean flags (
noteSel,tremoloEnable, and friends) are C11boolinstead of
theopal_boolint typedef. - The constants moved from an enum to defines:
OpalOPL3SampleRateis now
OPAL_OPL3_SAMPLE_RATE,OpalNumChannelsisOPAL_CHANNEL_COUNT, and
OpalNumOperatorsisOPAL_OPERATOR_COUNT. The header guard is
OPAL_OPAL_H_INCLUDED.
Removed
- The
MasterandChanback pointers on operators, theMasterand pointer-based
pair and routing fields on channels, and theZeroModfield on the chip. The no-copy
rule and every warning about it are gone with them. opal_bool,OPAL_TRUE, andOPAL_FALSE.
Documentation
- README and CONTRIBUTING describe the single naming convention. The section explaining
why the core kept its upstream names is gone, since the reason went with the names. - BINDINGS.md lists binding versions as the bound opal release plus a packaging
revision (2.0.0-1is the first opal-zig release binding opal2.0.0).
opal v1.0.2
Added
- The web player accepts a
?play=1query parameter and starts the first track on load, or
on the first tap where the browser demands a gesture. The README's live demo links use it. - BINDINGS.md lists third-party language bindings, starting with
opal-zig.
Fixed
- The example player's DRO decoder assumed the 24-byte v1 header. The earliest DOSBox builds
wrote a single-byte hardware type (21-byte header), so those files lost their first commands
or desynced. Nonzero bytes in the padding now identify them (AdPlug's heuristic). - The web player's nine channel meters no longer run a cosmetic CSS animation. They track the
live OPL envelopes (EgOuton each channel's modulator and carrier) via a newwebLevels
export, so bars rise and fall with the music being played.
Documentation
opal.hdocuments the channel-to-operator slot layout (modulatorOp[slot], carrier
Op[slot + 3], with thech_slottable inlined) so state snapshot consumers such as
visualizers stop guessing a sequential pair layout that is wrong for every channel but 0.opal.hdocuments the operator fields most useful to visualizers:EnvelopeStagevalues,
EgOutas total attenuation in 0.1875 dB steps, andKeyas a key-on source bitmask.opal.hwarns thatOpal_Initwires internal cross pointers, so an instance must be
initialized in place and never copied or relocated afterwards.- README and CONTRIBUTING cover DRO early headers,
Opalpointer stability, buffered ports,
CI on main only, and core versus player naming.
opal v1.0.1
Fixed
- Two operator channels ignored the connection bit (C0 bit 0) and always played serial FM.
Additive instruments are additive again. - Enabling four operator mode via register 0x104, or writing C0 on the primary channel of a
pair, never installed the operator routing. Only a C0 write on the secondary channel did. - Four operator connection modes 2 and 3 used wrong operator topologies. All four modes now
match the YMF262 algorithms. - Disabling four operator mode left both channels of the pair wired as halves of the old
four operator channel. - Note select (register 08 bit 6) picked the wrong F-Number bit for the key scale number,
which skewed KSR and KSL rates. The value is also latched on frequency writes now, so
changing NTS no longer rescales notes already sounding. - An A0 write to a four operator primary recomputed the secondary key scale number from the
secondary octave instead of inheriting the primary value. - OPL3 waveforms 4 to 7 were folded back to 0 to 3 the moment OPL3 mode switched off. The
hardware keeps playing the stored waveform, so opal does too.
Changed
- The status register now uses the hardware layout. Bit 7 is IRQ, bit 6 timer 1 overflow,
bit 5 timer 2 overflow, and flags persist until a reset write to register 4 instead of
clearing on read. - The
Opal_Pancomment described a channel number multiplied by 256. The function wants a
plain channel index 0 to 17 and the comment now says so. - The README no longer lists OPL2 waveform select as a feature. The enable bit is stored
and ignored, since the YMF262 ignores it too.
opal v1.0.0
Changelog
All notable changes to this project are documented here.
[1.0.0] - 2026-07-07
Added
- C11 static library with an eight-function public API (
Opal_Init,Opal_SetSampleRate,
Opal_Port,Opal_PortBuffered,Opal_FlushWriteBuf,Opal_Pan,Opal_Sample,Opal_Read) - Full OPL2 and OPL3 stereo synthesis with two and four operator FM, eight waveforms, tremolo
and vibrato LFOs, rhythm mode, timers, and the status register - Linear resampling from the native 49716 Hz to any output rate
- Example
playerthat streams DRO, HSC, and IMF or WLF files to the audio device via miniaudio - Seven public-domain HSC demo tracks under
music/from HSCDEMO3.EXE (1992) by Hannes Seifert,
limited to the tracks the demo itself marksindependentorpublic domain - WebAssembly player (
examples/web) that compiles the core and format decoders with Emscripten
behind a tracker-styled browser front end, built by thewebCMake preset - Audio unlock on iOS so the ring switch no longer mutes Web Audio, done by starting the device
inside the tap and playing a silent looping media element - CMake install rules and a
find_package(opal)package config - CMake presets for Windows (MSVC, Clang, and MinGW in 32 and 64 bit), Linux (GCC), and macOS
(Clang), plus library-only cross presets for Android (NDK), iOS, and WebAssembly - GitHub Actions workflows that build every preset on Windows, Linux, macOS, Android, iOS, and
the web on each push to main, deploy the web player to GitHub Pages, and drive the
per-platform status badges in the README
Notes
- Stereo output matches established YMF262 reference emulators at chip rate
- DRO playback handles version 1 only, meaning
DBRAWOPLwith a version word of zero - The CSW register bit is decoded but left inert, as on real YMF262 hardware