diff --git a/TAUD_NOTE_EFFECTS.md b/TAUD_NOTE_EFFECTS.md index f7d1cff..e969e13 100644 --- a/TAUD_NOTE_EFFECTS.md +++ b/TAUD_NOTE_EFFECTS.md @@ -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; diff --git a/assets/disk0/COPYING b/assets/disk0/COPYING index 72d6365..a4a775b 100644 --- a/assets/disk0/COPYING +++ b/assets/disk0/COPYING @@ -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 diff --git a/assets/disk0/etc/motd b/assets/disk0/etc/motd index 83e57b6..626d80a 100644 --- a/assets/disk0/etc/motd +++ b/assets/disk0/etc/motd @@ -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. \ No newline at end of file diff --git a/assets/disk0/tracker_test.js b/assets/disk0/tracker_test.js index 9ca534d..d07b86f 100644 --- a/assets/disk0/tracker_test.js +++ b/assets/disk0/tracker_test.js @@ -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; }()); diff --git a/assets/disk0/tvdos/bin/taut.js b/assets/disk0/tvdos/bin/taut.js index f12acbc..b73a545 100644 --- a/assets/disk0/tvdos/bin/taut.js +++ b/assets/disk0/tvdos/bin/taut.js @@ -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) { diff --git a/it2taud.py b/it2taud.py index 59a2bdb..e70c1fd 100644 --- a/it2taud.py +++ b/it2taud.py @@ -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 diff --git a/mod2taud.py b/mod2taud.py index 1bb526a..31060d8 100644 --- a/mod2taud.py +++ b/mod2taud.py @@ -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 ────────────────────────────────────────────────── diff --git a/mon2taud.py b/mon2taud.py index 7b34932..689e110 100644 --- a/mon2taud.py +++ b/mon2taud.py @@ -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) ───────────────── diff --git a/s3m2taud.py b/s3m2taud.py index 290bd4a..801b202 100644 --- a/s3m2taud.py +++ b/s3m2taud.py @@ -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, diff --git a/taud_common.py b/taud_common.py index 2d23734..f29c4e7 100644 --- a/taud_common.py +++ b/taud_common.py @@ -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). diff --git a/terranmon.txt b/terranmon.txt index 2c61437..8807ccf 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -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 diff --git a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt index 4372497..d27d705 100644 --- a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt @@ -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 * diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt index 029b86d..e1b5861 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt @@ -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 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 diff --git a/xm2taud.py b/xm2taud.py index 4a36514..af6fec9 100644 --- a/xm2taud.py +++ b/xm2taud.py @@ -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