Refactor and enhance documentation across the Wolf3D project
- Updated library imports to use the correct package paths for consistency. - Added detailed documentation comments to various classes and methods, improving code readability and maintainability. - Refined the GameSelectScreen, SpriteGallery, and VgaGallery classes with clearer descriptions of their functionality. - Enhanced the CliInput class to better explain the input handling process and its interaction with the engine. - Improved the SixelRasterizer and Opl2Emulator classes with comprehensive comments on their operations and state management. - Removed the deprecated wolf_3d.dart file and consolidated its functionality into wolf_3d_flutter.dart for a cleaner architecture. - Updated the Wolf3dFlutterInput class to clarify its role in merging keyboard and pointer events. - Enhanced the rendering classes to provide better context on their purpose and usage within the Flutter framework. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
/// States used by the simplified ADSR envelope inside the OPL2 emulator.
|
||||
enum EnvelopeState { off, attack, decay, sustain, release }
|
||||
|
||||
/// One OPL2 operator, combining waveform generation and envelope progression.
|
||||
class Opl2Operator {
|
||||
double phase = 0.0;
|
||||
double phaseIncrement = 0.0;
|
||||
@@ -16,24 +18,27 @@ class Opl2Operator {
|
||||
double multiplier = 1.0;
|
||||
double volume = 1.0;
|
||||
|
||||
// Waveform Selection (0-3)
|
||||
/// Selected waveform index as exposed by the OPL2 register set.
|
||||
int waveform = 0;
|
||||
|
||||
/// Recomputes oscillator increment from the shared channel base frequency.
|
||||
void updateFrequency(double baseFreq) {
|
||||
phaseIncrement =
|
||||
(baseFreq * multiplier * 2 * math.pi) / Opl2Emulator.sampleRate;
|
||||
}
|
||||
|
||||
/// Starts a new note by resetting the phase and entering attack.
|
||||
void triggerOn() {
|
||||
phase = 0.0;
|
||||
envState = EnvelopeState.attack;
|
||||
}
|
||||
|
||||
/// Releases the note so the envelope decays back to silence.
|
||||
void triggerOff() {
|
||||
envState = EnvelopeState.release;
|
||||
}
|
||||
|
||||
// Applies the OPL2 hardware waveform math
|
||||
// Waveform handling mirrors the small set of shapes exposed by the OPL2 chip.
|
||||
double _getOscillatorOutput(double currentPhase) {
|
||||
// Normalize phase between 0 and 2*pi
|
||||
double p = currentPhase % (2 * math.pi);
|
||||
@@ -52,6 +57,7 @@ class Opl2Operator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces one sample for this operator using [phaseOffset] modulation.
|
||||
double getSample(double phaseOffset) {
|
||||
switch (envState) {
|
||||
case EnvelopeState.attack:
|
||||
@@ -83,7 +89,8 @@ class Opl2Operator {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Pass the phase + modulation offset into our waveform generator!
|
||||
// Modulation is expressed as a phase offset, which is how the carrier is
|
||||
// driven by the modulator in two-operator FM synthesis.
|
||||
double out = _getOscillatorOutput(phase + phaseOffset);
|
||||
|
||||
phase += phaseIncrement;
|
||||
@@ -93,6 +100,7 @@ class Opl2Operator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Two-operator OPL2 channel with optional additive mode and self-feedback.
|
||||
class Opl2Channel {
|
||||
Opl2Operator modulator = Opl2Operator();
|
||||
Opl2Operator carrier = Opl2Operator();
|
||||
@@ -107,12 +115,14 @@ class Opl2Channel {
|
||||
double _prevModOutput1 = 0.0;
|
||||
double _prevModOutput2 = 0.0;
|
||||
|
||||
/// Updates both operators after frequency register changes.
|
||||
void updateFrequency() {
|
||||
double baseFreq = (fNum * math.pow(2, block)) * (49716.0 / 1048576.0);
|
||||
modulator.updateFrequency(baseFreq);
|
||||
carrier.updateFrequency(baseFreq);
|
||||
}
|
||||
|
||||
/// Mixes one audio sample from the channel's current operator state.
|
||||
double getSample() {
|
||||
if (!keyOn &&
|
||||
carrier.envState == EnvelopeState.off &&
|
||||
@@ -122,6 +132,8 @@ class Opl2Channel {
|
||||
|
||||
double feedbackPhase = 0.0;
|
||||
if (feedbackStrength > 0) {
|
||||
// Feedback reuses the previous modulator outputs to create the harsher
|
||||
// timbres that classic OPL instruments rely on.
|
||||
double averageMod = (_prevModOutput1 + _prevModOutput2) / 2.0;
|
||||
double feedbackFactor =
|
||||
math.pow(2, feedbackStrength - 1) * (math.pi / 16.0);
|
||||
@@ -136,9 +148,11 @@ class Opl2Channel {
|
||||
double channelOutput = 0.0;
|
||||
|
||||
if (isAdditive) {
|
||||
// Additive mode mixes both operators as audible oscillators.
|
||||
double carOutput = carrier.getSample(0.0);
|
||||
channelOutput = modOutput + carOutput;
|
||||
} else {
|
||||
// Standard FM mode feeds the modulator into the carrier's phase.
|
||||
double carOutput = carrier.getSample(modOutput * 2.0);
|
||||
channelOutput = carOutput;
|
||||
}
|
||||
@@ -147,6 +161,7 @@ class Opl2Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lightweight pseudo-random noise source for percussion voices.
|
||||
class Opl2Noise {
|
||||
int _seed = 0xFFFF;
|
||||
|
||||
@@ -158,12 +173,17 @@ class Opl2Noise {
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplified register-driven OPL2 emulator used for IMF playback.
|
||||
///
|
||||
/// The implementation focuses on the subset of FM behavior needed by the game
|
||||
/// assets: melodic channels, rhythm mode, waveform selection, and a practical
|
||||
/// ADSR envelope approximation.
|
||||
class Opl2Emulator {
|
||||
static const int sampleRate = 44100;
|
||||
|
||||
bool rhythmMode = false;
|
||||
|
||||
// Key states for the 5 drums
|
||||
// Rhythm mode steals the final three channels and exposes them as drum bits.
|
||||
bool bassDrumKey = false;
|
||||
bool snareDrumKey = false;
|
||||
bool tomTomKey = false;
|
||||
@@ -174,7 +194,7 @@ class Opl2Emulator {
|
||||
|
||||
final List<Opl2Channel> channels = List.generate(9, (_) => Opl2Channel());
|
||||
|
||||
// The master lock for waveforms
|
||||
// The chip only honors waveform writes after the global enable bit is set.
|
||||
bool _waveformSelectionEnabled = false;
|
||||
|
||||
static const List<int> _operatorMap = [
|
||||
@@ -202,6 +222,7 @@ class Opl2Emulator {
|
||||
8,
|
||||
];
|
||||
|
||||
/// Resolves a register offset to the affected operator, if any.
|
||||
Opl2Operator? _getOperator(int offset) {
|
||||
if (offset < 0 ||
|
||||
offset >= _operatorMap.length ||
|
||||
@@ -215,6 +236,7 @@ class Opl2Emulator {
|
||||
: channels[channelIdx].modulator;
|
||||
}
|
||||
|
||||
/// Applies a single OPL2 register write.
|
||||
void writeRegister(int reg, int data) {
|
||||
// --- 0x01: Test / Waveform Enable ---
|
||||
if (reg == 0x01) {
|
||||
@@ -310,6 +332,7 @@ class Opl2Emulator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates one normalized mono sample from the current register state.
|
||||
double generateSample() {
|
||||
double mixedOutput = 0.0;
|
||||
|
||||
@@ -319,12 +342,12 @@ class Opl2Emulator {
|
||||
}
|
||||
|
||||
if (!rhythmMode) {
|
||||
// Standard mode: play channels 6, 7, and 8 normally
|
||||
// Standard mode keeps the final channels melodic.
|
||||
for (int i = 6; i < 9; i++) {
|
||||
mixedOutput += channels[i].getSample();
|
||||
}
|
||||
} else {
|
||||
// RHYTHM MODE: The last 3 channels are re-routed
|
||||
// Rhythm mode repurposes the last three channels into drum voices.
|
||||
mixedOutput += _generateBassDrum();
|
||||
mixedOutput += _generateSnareAndHiHat();
|
||||
mixedOutput += _generateTomAndCymbal();
|
||||
@@ -333,13 +356,14 @@ class Opl2Emulator {
|
||||
return mixedOutput.clamp(-1.0, 1.0);
|
||||
}
|
||||
|
||||
// Example of Bass Drum logic (Channel 6)
|
||||
/// Generates the bass drum voice routed through channel 6.
|
||||
double _generateBassDrum() {
|
||||
if (!bassDrumKey) return 0.0;
|
||||
// Bass drum uses standard FM (Mod -> Car) but usually with very low frequency
|
||||
return channels[6].getSample();
|
||||
}
|
||||
|
||||
/// Generates the combined snare and hi-hat voices from channel 7.
|
||||
double _generateSnareAndHiHat() {
|
||||
double snareOut = 0.0;
|
||||
double hiHatOut = 0.0;
|
||||
@@ -363,6 +387,7 @@ class Opl2Emulator {
|
||||
return (snareOut + hiHatOut) * 0.1;
|
||||
}
|
||||
|
||||
/// Generates the combined tom and cymbal voices from channel 8.
|
||||
double _generateTomAndCymbal() {
|
||||
double tomOut = 0.0;
|
||||
double cymbalOut = 0.0;
|
||||
|
||||
Reference in New Issue
Block a user