mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
Taud: sentinel values moved to negative octave range
This commit is contained in:
@@ -779,7 +779,7 @@ if V.dittoActive and armRow <= N <= V.dittoEndRow:
|
||||
srcRow = V.dittoSourceStart + ((N - V.dittoSourceStart) mod V.dittoLength)
|
||||
src = patternRows[V.pattern][srcRow]
|
||||
|
||||
cell.note = (raw.note != 0xFFFF) ? raw.note : src.note
|
||||
cell.note = (raw.note != 0x0000) ? raw.note : src.note
|
||||
cell.instrument = (raw.instrument != 0) ? raw.instrument : src.instrument
|
||||
|
||||
# SEL_FINE / 0 is the canonical no-op encoding for the vol- and pan-columns;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020-2024 CuriousTorvald
|
||||
Copyright (c) 2020-2026 CuriousTorvald
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
TVDOS (c) 2020-2024 CuriousTorvald
|
||||
TVDOS (c) 2020-2026 CuriousTorvald
|
||||
|
||||
TVDOS is provided "as is", without warranty of any kind; in no event shall the authors or copyright holders be liable for any claim, damages or other liabilities. Run 'less COPYING' for more information.
|
||||
@@ -19,9 +19,9 @@ var Note = (function() {
|
||||
if (flats[s] !== names[s]) t[flats[s] + oct] = n(oct, s);
|
||||
}
|
||||
}
|
||||
t.OFF = 0x0000; // key-off
|
||||
t.CUT = 0xFFFE; // note cut (immediate)
|
||||
t.NOP = 0xFFFF; // no-op (empty row)
|
||||
t.NOP = 0x0000; // no-op (empty row)
|
||||
t.OFF = 0x0001; // key-off
|
||||
t.CUT = 0x0002; // note cut (immediate)
|
||||
return t;
|
||||
}());
|
||||
|
||||
|
||||
@@ -466,7 +466,7 @@ function retuneAllPatterns(newIdx, method) {
|
||||
for (let row = 0; row < ROWS_PER_PAT; row++) {
|
||||
const off = 8 * row
|
||||
const note = ptn[off] | (ptn[off+1] << 8)
|
||||
if (note === 0xFFFF || note === 0xFFFE || note === 0x0000) continue
|
||||
if (note === 0x0000 || note === 0x0001 || note === 0x0002 || (note >= 0x0010 && note <= 0x001F)) continue
|
||||
// Use the full absolute pitch as tonic; the modular ops
|
||||
// in _cadTension / _harmonicCost normalise it.
|
||||
tonic = note
|
||||
@@ -476,7 +476,7 @@ function retuneAllPatterns(newIdx, method) {
|
||||
for (let row = 0; row < ROWS_PER_PAT; row++) {
|
||||
const off = 8 * row
|
||||
const note = ptn[off] | (ptn[off+1] << 8)
|
||||
if (note === 0xFFFF || note === 0xFFFE || note === 0x0000) continue
|
||||
if (note === 0x0000 || note === 0x0001 || note === 0x0002 || (note >= 0x0010 && note <= 0x001F)) continue
|
||||
const origAbs = note
|
||||
let newAbs
|
||||
if ((method === 'delta' || method === 'cadence' || method === 'harmonic') && prevOrigAbs >= 0) {
|
||||
@@ -490,7 +490,7 @@ function retuneAllPatterns(newIdx, method) {
|
||||
for (let r = row + 1; r < ROWS_PER_PAT; r++) {
|
||||
const noff = 8 * r
|
||||
const n = ptn[noff] | (ptn[noff+1] << 8)
|
||||
if (n !== 0x0000) break
|
||||
if (n !== 0x0001) break
|
||||
duration++
|
||||
}
|
||||
lambda = 1 - Math.exp(-(duration - 1) / 4)
|
||||
@@ -558,9 +558,10 @@ Number.prototype.decD2 = function() {
|
||||
|
||||
|
||||
function noteToStr(note) {
|
||||
if (note === 0xFFFF) return sym.middot.repeat(4)
|
||||
if (note === 0xFFFE) return sym.notecut
|
||||
if (note === 0x0000) return sym.keyoff
|
||||
if (note === 0x0000) return sym.middot.repeat(4)
|
||||
if (note === 0x0001) return sym.keyoff
|
||||
if (note === 0x0002) return sym.notecut
|
||||
if (note >= 0x0010 && note <= 0x001F) return ('Int' + (note & 0xF).toString(16).toUpperCase()).padEnd(4)
|
||||
const preset = pitchTablePresets[PITCH_PRESET_IDX]
|
||||
if (preset.table.length === 0) return note.hex04()
|
||||
const [period, offset] = decomposeNote(note, preset.interval)
|
||||
@@ -656,7 +657,7 @@ const EMPTY_CELL = {
|
||||
sPanArg: sym.middot.repeat(2),
|
||||
sEffOp: sym.middot,
|
||||
sEffArg: sym.middot.repeat(4),
|
||||
_note: 0xFFFF, _effop: 0, _effarg: 0, _voleff: 0, _paneff: 0
|
||||
_note: 0x0000, _effop: 0, _effarg: 0, _voleff: 0, _paneff: 0
|
||||
}
|
||||
|
||||
function drawCellAt(y, x, cell, back) {
|
||||
@@ -692,7 +693,7 @@ function drawCellAtStyled(y, x, cell, back, style) {
|
||||
return
|
||||
}
|
||||
// Styles 1 and 2: note-or-fx field (5 chars) starts on the border column [+ vol-or-pan (2 chars)]
|
||||
const noteEmpty = (cell._note === 0xFFFF)
|
||||
const noteEmpty = (cell._note === 0x0000)
|
||||
const fxEmpty = (cell._effop === 0 && cell._effarg === 0)
|
||||
const volEmpty = (cell._voleff === 0)
|
||||
const panEmpty = (cell._paneff === 0)
|
||||
@@ -1537,8 +1538,8 @@ function drawVoiceDetail(isVerticalLayout = false, ptn = null, activeRow = -1, c
|
||||
if (cumState !== null && lowerH > 0) {
|
||||
const _apo = Math.abs(cumState.pitchOff)
|
||||
const _psgn = cumState.pitchOff > 0 ? '+' : cumState.pitchOff < 0 ? '-' : ' '
|
||||
const _absN = (cumState.lastNote !== 0xFFFF && cumState.pitchOff !== 0)
|
||||
? noteToStr(Math.max(0, Math.min(0xFFFE, cumState.lastNote + cumState.pitchOff))) + ' '
|
||||
const _absN = (cumState.lastNote !== 0x0000 && cumState.pitchOff !== 0)
|
||||
? noteToStr(Math.max(0x20, Math.min(0xFFFF, cumState.lastNote + cumState.pitchOff))) + ' '
|
||||
: ''
|
||||
const _clipNm = ['clamp','fold','wrap','wrap'][cumState.clipMode]
|
||||
const _bcStr = (cumState.bitcrushDepth === 0 && cumState.bitcrushSkip === 0)
|
||||
@@ -2242,7 +2243,7 @@ function simulateRowState(ptnDat, uptoRow) {
|
||||
0x0000, 0x0023, 0x0046, 0x0074, 0x0098, 0x00C8, 0x00F9, 0x0110
|
||||
]
|
||||
|
||||
let lastNote = 0xFFFF, lastInst = 0
|
||||
let lastNote = 0x0000, lastInst = 0
|
||||
let volAbs = 0x3F // 6-bit per-note volume (engine: noteVolume axis;
|
||||
// M / N's per-channel axis is not modelled here)
|
||||
let panAbs = 0x80 // 8-bit channel pan (engine width); centre = $80
|
||||
@@ -2295,8 +2296,8 @@ function simulateRowState(ptnDat, uptoRow) {
|
||||
// not tracked by this simulator. The simulator approximates the seed
|
||||
// as 0x3F (legacy fallback) — see the longer note below.
|
||||
let reloadDefaultVol = false
|
||||
if (note !== 0xFFFF && note !== 0xFFFE) {
|
||||
if (note === 0x0000) {
|
||||
if (note !== 0x0000 && note !== 0x0002 && !(note >= 0x0010 && note <= 0x001F)) {
|
||||
if (note === 0x0001) {
|
||||
// key-off; sample stays referenced
|
||||
} else if (isGRow) {
|
||||
portaTarget = note
|
||||
@@ -2419,7 +2420,7 @@ function simulateRowState(ptnDat, uptoRow) {
|
||||
}
|
||||
else if (effop === OP_G) {
|
||||
if (effarg !== 0) memG = effarg
|
||||
if (portaTarget !== -1 && memG !== 0 && lastNote !== 0xFFFF) {
|
||||
if (portaTarget !== -1 && memG !== 0 && lastNote !== 0x0000) {
|
||||
const curPitch = lastNote + pitchOff
|
||||
const diff = portaTarget - curPitch
|
||||
if (diff !== 0) {
|
||||
|
||||
@@ -698,7 +698,7 @@ def encode_note_it(it_note: int) -> int:
|
||||
# IT C-5 anchors to Taud C-4, so offset = it_note - 60.
|
||||
semis = it_note - 60
|
||||
val = round(TAUD_C4 + semis * 4096 / 12)
|
||||
return max(1, min(0xFFFD, val))
|
||||
return max(0x20, min(0xFFFF, val))
|
||||
return NOTE_NOP
|
||||
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ def period_to_taud_note(period: int) -> int:
|
||||
if period <= 0:
|
||||
return NOTE_NOP
|
||||
val = round(TAUD_C4 + 4096.0 * math.log2(PT_REFERENCE_PERIOD / period))
|
||||
return max(1, min(0xFFFD, val))
|
||||
return max(0x20, min(0xFFFF, val))
|
||||
|
||||
|
||||
# ── PT effect → Taud effect ──────────────────────────────────────────────────
|
||||
|
||||
@@ -139,7 +139,7 @@ def mon_note_to_taud(mon_note: int) -> int:
|
||||
if mon_note == 0x7F:
|
||||
return NOTE_CUT
|
||||
val = TAUD_C4 + round((mon_note - MON_NOTE_C4) * 4096.0 / 12.0)
|
||||
return max(1, min(0xFFFD, val))
|
||||
return max(0x20, min(0xFFFF, val))
|
||||
|
||||
|
||||
# ── Effect mapping (Monotone 3-bit code + 6-bit data → Taud) ─────────────────
|
||||
|
||||
@@ -234,7 +234,7 @@ def encode_note(s3m_note: int) -> int:
|
||||
return NOTE_NOP
|
||||
semitones = (octave - 4) * 12 + pitch
|
||||
val = round(TAUD_C4 + semitones * 4096 / 12)
|
||||
return max(1, min(0xFFFD, val))
|
||||
return max(0x20, min(0xFFFF, val))
|
||||
|
||||
|
||||
def encode_effect(cmd: int, arg: int, ch: int = 0, row: int = 0,
|
||||
|
||||
@@ -96,9 +96,9 @@ NUM_VOICES = 20
|
||||
SAMPLE_LEN_LIMIT = 65535
|
||||
|
||||
# Note word sentinels
|
||||
NOTE_NOP = 0xFFFF
|
||||
NOTE_KEYOFF = 0x0000
|
||||
NOTE_CUT = 0xFFFE
|
||||
NOTE_NOP = 0x0000
|
||||
NOTE_KEYOFF = 0x0001
|
||||
NOTE_CUT = 0x0002
|
||||
TAUD_C4 = 0x5000 # The audio engine's Middle C
|
||||
|
||||
# Cue sheet instruction byte (cue offset 30; offset 31 = arg byte for 2-byte forms).
|
||||
|
||||
@@ -2255,7 +2255,7 @@ from source.
|
||||
* Semantics (matches IT/Schism player/effects.c:1664-1764 csf_check_nna):
|
||||
- Fires on every fresh foreground note trigger on a channel, BEFORE the
|
||||
NNA-spawn step that would ghost the existing voice. Does NOT fire on
|
||||
tone portamento, on note-off (0x0000), on note-cut (0xFFFE), or on
|
||||
tone portamento, on note-off (0x0001), on note-cut (0x0002), or on
|
||||
empty cells.
|
||||
- The DCT/DCA values consulted belong to the EXISTING voice's instrument
|
||||
(i.e. the OLD note's instrument, not the incoming note's). Different
|
||||
@@ -2401,6 +2401,8 @@ TODO:
|
||||
[x] GSLINGER order 0x03 chn 1: L 0100 fades unexpectedly fast? — converter fix
|
||||
[x] do not reset tickspeed on pattern view play / add key to modify tick speed ('[' down/']' up)
|
||||
[x] expose song table on UI (test with `insaniq2.taud`)
|
||||
[x] 0x0000 - no-op; 0x0001 - key-off; 0x0002 - note-cut 0x0010..0x001F - INT0..INTF
|
||||
[ ] establish hooks for the interrupts
|
||||
|
||||
TODO - list of demo songs that MUST ship with Microtone:
|
||||
* 4THSYM (rename to Fourth Symmetriad) — excellent piece for demonstrating NNAs and filter envelopes
|
||||
@@ -2421,13 +2423,14 @@ Play Data: play data are series of tracker-like instructions, visualised as:
|
||||
rr||NOTE|Ins|E.Vol|E.Pan|EE.ff|
|
||||
63||FFFF|255|3 63|3 63|FF FFFF| (8 bytes per line, 512 bytes per pattern, 128 patterns on 64 kB bank, 32 banks available (pattern 0xFFF -- bank 31, pattern 127 is a sentinel value for no-pattern))
|
||||
|
||||
notes are tuned as 4096 Tone-Equal Temperament. Tuning is set per-sample using their Sampling rate value.
|
||||
notes are tuned as 4096 Tone-Equal Temperament. Tuning is set per-sample using their Sampling rate value. 0x1000: C at zeroth octave; 0xF000: C at 14th octave; 0xFFFF: ~C at 15th octave; 0x0000..0x001F: reserved for sentinels (valid playable note range is 0x0020..0xFFFF)
|
||||
|
||||
Special values:
|
||||
|
||||
note 0xFFFF: no-op
|
||||
note 0xFFFE: note cut
|
||||
note 0x0000: key-off
|
||||
note 0x0000: no-op
|
||||
note 0x0001: key-off
|
||||
note 0x0002: note cut
|
||||
note 0x0010..0x001F: Interrupt 0..F (notation: Int0..IntF) — reserved interrupt slots; engine has no default handler.
|
||||
|
||||
inst 0: no instrument change
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ import net.torvald.tsvm.peripheral.MP2Env
|
||||
* 8. Call `setCuePosition(playhead, 0)` then `play(playhead)`.
|
||||
*
|
||||
* Note values: 0x4000 = C3 (sample's native pitch), 4096 steps per octave.
|
||||
* Empty row: note = 0xFFFF (no trigger). All 256 instrument slots (0-255) are valid.
|
||||
* Empty row: note = 0x0000 (no trigger). Note sentinels (0x0000..0x001F): 0x0000 = no-op,
|
||||
* 0x0001 = key-off, 0x0002 = note cut, 0x0010..0x001F = Int0..IntF (reserved interrupts).
|
||||
* Valid playable notes are 0x0020..0xFFFF. All 256 instrument slots (0-255) are valid.
|
||||
*
|
||||
* ## How to upload PCM audio into a playhead
|
||||
*
|
||||
|
||||
@@ -243,7 +243,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
internal val sampleBin = UnsafeHelper.allocate(SAMPLE_BIN_TOTAL, this)
|
||||
@Volatile var sampleBank: Int = 0 // 0..15, controls the 0..524287 window
|
||||
internal val instruments = Array(256) { TaudInst(it) }
|
||||
internal val playdata = Array(4096) { Array(64) { TaudPlayData(0xFFFF, 0, 0, 0, 32, 0, 0, 0) } }
|
||||
internal val playdata = Array(4096) { Array(64) { TaudPlayData(0x0000, 0, 0, 0, 32, 0, 0, 0) } }
|
||||
internal val playheads: Array<Playhead>
|
||||
internal val cueSheet = Array(1024) { PlayCue() }
|
||||
internal val pcmBin = arrayOf(
|
||||
@@ -2275,7 +2275,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
}
|
||||
|
||||
TaudPlayData(
|
||||
note = if (rawRow.note != 0xFFFF) rawRow.note else src.note,
|
||||
note = if (rawRow.note != 0x0000) rawRow.note else src.note,
|
||||
instrment = if (rawRow.instrment != 0) rawRow.instrment else src.instrment,
|
||||
volume = if (volIsSet) rawRow.volume else src.volume,
|
||||
volumeEff = if (volIsSet) rawRow.volumeEff else src.volumeEff,
|
||||
@@ -2329,7 +2329,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
// physical_presence ord 0x1F ch2: every row carries `... 1E A0F/A09/A02`)
|
||||
// silences after the first row because the slide saturates at 0 and there's
|
||||
// nothing to lift the volume back up before the next slide starts.
|
||||
0xFFFF -> {
|
||||
0x0000 -> {
|
||||
if (row.instrment != 0) {
|
||||
voice.instrumentId = row.instrment
|
||||
val seedVol = rowVolumeFromDefault(instruments[voice.instrumentId])
|
||||
@@ -2345,8 +2345,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
// fadeoutVolume reaches 0, or immediately if FT2-mode fadeStep == 0. Setting
|
||||
// voice.active = false here would defeat both — instruments with sustain points
|
||||
// and non-zero fadeout (FT2 sustain-then-fade idiom) would be cut on the spot.
|
||||
0x0000 -> { voice.keyOff = true }
|
||||
0xFFFE -> voice.active = false // note cut (immediate)
|
||||
0x0001 -> { voice.keyOff = true }
|
||||
0x0002 -> voice.active = false // note cut (immediate)
|
||||
in 0x0003..0x000F -> { /* reserved sentinel range, no engine handler */ }
|
||||
in 0x0010..0x001F -> { /* Int0..IntF: reserved interrupt slots, no engine handler yet */ }
|
||||
else -> {
|
||||
if (toneG && voice.active) {
|
||||
// Tone porta: target the note, do not retrigger sample.
|
||||
@@ -2502,7 +2504,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
1 -> amigaSlideOnce(voice.noteVal, -mag) // Amiga: subtract from pitch ⇒ adds period
|
||||
2 -> linearFreqSlideOnce(voice.noteVal, -mag) // Hz/tick: pitch down ⇒ -Hz
|
||||
else -> voice.noteVal - mag // linear 4096-TET
|
||||
}.coerceIn(1, 0xFFFD)
|
||||
}.coerceIn(0x20, 0xFFFF)
|
||||
voice.basePitch = voice.noteVal
|
||||
voice.amigaPeriod = -1.0 // reseed on next per-tick slide
|
||||
voice.linearFreq = -1.0
|
||||
@@ -2521,7 +2523,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
1 -> amigaSlideOnce(voice.noteVal, mag)
|
||||
2 -> linearFreqSlideOnce(voice.noteVal, mag)
|
||||
else -> voice.noteVal + mag
|
||||
}.coerceIn(1, 0xFFFD)
|
||||
}.coerceIn(0x20, 0xFFFF)
|
||||
voice.basePitch = voice.noteVal
|
||||
voice.amigaPeriod = -1.0
|
||||
voice.linearFreq = -1.0
|
||||
@@ -2730,7 +2732,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
}
|
||||
0x1 -> voice.glissandoOn = (x != 0)
|
||||
0x2 -> {
|
||||
voice.noteVal = (voice.noteVal + FINETUNE_OFFSET[x]).coerceIn(1, 0xFFFD)
|
||||
voice.noteVal = (voice.noteVal + FINETUNE_OFFSET[x]).coerceIn(0x20, 0xFFFF)
|
||||
voice.basePitch = voice.noteVal
|
||||
voice.amigaPeriod = -1.0
|
||||
voice.linearFreq = -1.0
|
||||
@@ -2832,7 +2834,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
1 -> amigaSlideTick(voice, voice.slideArg)
|
||||
2 -> linearFreqSlideTick(voice, voice.slideArg)
|
||||
else -> voice.noteVal + voice.slideArg
|
||||
}.coerceIn(1, 0xFFFD)
|
||||
}.coerceIn(0x20, 0xFFFF)
|
||||
voice.basePitch = voice.noteVal
|
||||
}
|
||||
|
||||
@@ -2854,7 +2856,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
voice.noteVal = target
|
||||
voice.tonePortaTarget = -1
|
||||
} else {
|
||||
voice.noteVal = freqHzToNoteVal(voice.linearFreq).coerceIn(1, 0xFFFD)
|
||||
voice.noteVal = freqHzToNoteVal(voice.linearFreq).coerceIn(0x20, 0xFFFF)
|
||||
}
|
||||
voice.basePitch = voice.noteVal
|
||||
voice.amigaPeriod = -1.0
|
||||
@@ -2912,14 +2914,14 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
if (voice.vibratoActive) {
|
||||
val sine = lfoSample(voice.vibratoLfoPos, voice.vibratoWave)
|
||||
val pitchDelta = (sine * voice.mem.huDepth) shr voice.vibratoFineShift
|
||||
pitchToMixer = (voice.noteVal + pitchDelta).coerceIn(1, 0xFFFD)
|
||||
pitchToMixer = (voice.noteVal + pitchDelta).coerceIn(0x20, 0xFFFF)
|
||||
voice.vibratoLfoPos = (voice.vibratoLfoPos + voice.mem.huSpeed * 4) and 0xFF
|
||||
}
|
||||
|
||||
// Glissando (S$1x) — snap pitchToMixer to nearest semitone but leave noteVal smooth.
|
||||
if (voice.glissandoOn) {
|
||||
val semis = ((pitchToMixer * 12 + 2048) / 4096)
|
||||
pitchToMixer = (semis * 4096 / 12).coerceIn(1, 0xFFFD)
|
||||
pitchToMixer = (semis * 4096 / 12).coerceIn(0x20, 0xFFFF)
|
||||
}
|
||||
|
||||
// Tremolo (R) — modulates rowVolume around the per-note volume base. IT's tremolo
|
||||
@@ -2946,7 +2948,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
if (voice.arpActive) {
|
||||
val voiceIdx = ts.tickInRow % 3
|
||||
val arpDelta = when (voiceIdx) { 1 -> voice.arpOff1 shl 8; 2 -> voice.arpOff2 shl 8; else -> 0 }
|
||||
pitchToMixer = (voice.basePitch + arpDelta).coerceIn(1, 0xFFFD)
|
||||
pitchToMixer = (voice.basePitch + arpDelta).coerceIn(0x20, 0xFFFF)
|
||||
voice.lastArpVoice = voiceIdx
|
||||
}
|
||||
|
||||
@@ -2983,7 +2985,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
((voice.envPfValue - 0.5) * 2.0 * 16.0 * 4096.0 / 12.0).toInt()
|
||||
else 0
|
||||
|
||||
val finalPitch = (pitchToMixer + autoVibDelta + pitchEnvDelta).coerceIn(1, 0xFFFD)
|
||||
val finalPitch = (pitchToMixer + autoVibDelta + pitchEnvDelta).coerceIn(0x20, 0xFFFF)
|
||||
voice.playbackRate = computePlaybackRate(inst, finalPitch)
|
||||
|
||||
// Filter envelope (filter mode): scale baseCut by envValue (0..1, 0.5 = unity).
|
||||
@@ -3087,7 +3089,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
val pitchEnvDelta = if (bg.hasPfEnv && bg.pfEnvOn && !bg.envPfIsFilter)
|
||||
((bg.envPfValue - 0.5) * 2.0 * 16.0 * 4096.0 / 12.0).toInt()
|
||||
else 0
|
||||
val finalPitch = (bg.noteVal + autoVibDelta + pitchEnvDelta).coerceIn(1, 0xFFFD)
|
||||
val finalPitch = (bg.noteVal + autoVibDelta + pitchEnvDelta).coerceIn(0x20, 0xFFFF)
|
||||
bg.playbackRate = computePlaybackRate(inst, finalPitch)
|
||||
// Filter-mode pf envelope: same scaling rule as foreground.
|
||||
if (bg.hasPfEnv && bg.pfEnvOn && bg.envPfIsFilter) {
|
||||
@@ -3603,7 +3605,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
var randomPanBias = 0 // signed
|
||||
|
||||
// Pitch state (4096-TET units, signed when slid).
|
||||
var noteVal = 0xFFFF // The currently sounding base note (no per-row vibrato/arp added)
|
||||
var noteVal = 0x0000 // The currently sounding base note (no per-row vibrato/arp added); 0 = none yet
|
||||
var basePitch = 0x4000 // Saved pre-effect pitch for vibrato/arp/glissando overlay
|
||||
// Amiga-mode period state, persisted across ticks so multi-tick E/F slides don't lose
|
||||
// sub-noteVal precision through repeated round-trip rounding (see amigaSlideTick).
|
||||
@@ -3965,7 +3967,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
it.hasPfEnv = false; it.envPfIsFilter = false
|
||||
it.fadeoutVolume = 1.0
|
||||
it.rampOutSamples = 0; it.rampOutGain = 0.0; it.rampOutStep = 0.0
|
||||
it.noteVal = 0xFFFF; it.basePitch = 0x4000
|
||||
it.noteVal = 0x0000; it.basePitch = 0x4000
|
||||
it.amigaPeriod = -1.0; it.linearFreq = -1.0
|
||||
it.tonePortaTarget = -1; it.tonePortaSpeed = 0
|
||||
it.filterY1 = 0.0; it.filterY2 = 0.0
|
||||
|
||||
@@ -387,7 +387,7 @@ def encode_note_xm(xm_note: int) -> int:
|
||||
if 1 <= xm_note <= 96:
|
||||
semis = xm_note - XM_RELNOTE_C4
|
||||
val = round(TAUD_C4 + semis * 4096 / 12)
|
||||
return max(1, min(0xFFFD, val))
|
||||
return max(0x20, min(0xFFFF, val))
|
||||
return NOTE_NOP
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user