funk repeat OOB fix

This commit is contained in:
minjaesong
2026-05-09 03:00:54 +09:00
parent 935fbe04a6
commit 3c57e33f8f
4 changed files with 26 additions and 3 deletions

View File

@@ -37,6 +37,7 @@ Current topics:
- `reference_materials/impulse-tracker` — The original source code for ImpulseTracker
- `reference_materials/MilkyTracker` — FastTracker 2 compatible tracker
- `reference_materials/schismtracker` — Open-source re-implementation of ImpulseTracker
- `reference_materials/pt2-clone` — Open-source re-implementation of ProTracker 2
When fetching new references, copy the relevant upstream files verbatim into
a topic folder, write a `README.md` summarising the relevant maths /

View File

@@ -1190,7 +1190,7 @@ There is no separate "use fadeout" flag — both extremes share the same field,
This table maps each PT effect to its Taud equivalent. Arguments follow PT's two-nibble form and expand to Taud's 16-bit form as shown.
| PT effect | Taud effect | Notes |
|---------|-----------|-------|
|---------|---------|-------|
| `0 $xy` | `J $xxyy` | Arpeggio; nibble-repeat each byte. See the 12-TET → Taud table above for conversion losses |
| `1 $xx` | `F $00xx` (Amiga mode, `f` set) | Portamento up; raw PT period units, applied in period space |
| `2 $xx` | `E $00xx` (Amiga mode, `f` set) | Portamento down; raw PT period units, applied in period space |
@@ -1220,7 +1220,7 @@ This table maps each PT effect to its Taud equivalent. Arguments follow PT's two
| `E $Cx` | `S $Cx00` | Note cut |
| `E $Dx` | `S $Dx00` | Note delay |
| `E $Ex` | `S $Ex00` | Pattern delay |
| `E $Fx` | `S $Fx00` | Funk repeat |
| `E $Fx` | `S $Fyyy` | Funk repeat, where `yyy = funk_table[x]` |
| `F $xx` (xx < $20) | `A $xx00` | Set speed |
| `F $xx` (xx ≥ $20) | `T $(xx$18)00` | Set tempo |

View File

@@ -2395,6 +2395,19 @@ TODO:
updated; legacy `.taud` files (byte 196 == 0) fall back to the
previous "row volume default = 63" behaviour.
TODO - list of demo songs that MUST ship with Microtone:
* 4THSYM (rename to Fourth Symmetriad) — excellent piece for demonstrating NNAs and filter envelopes
(C) Skaven 1998
* Slumberjack — for demonstrating XM-compatible instrument definitions
(C) raina 2005
* Space Debris — MOD with tons of effects
(C) Captain/Image 1991
* Changing Waves — for Funk Repeat emulation
(C) 4mat/orb 2023
* Aboriginal Derivatives — for demonstrating Monotone compatibility.
(C) Jakim 2010
* SWINGIN1 (rename to Swinging Waste) — for demonstrating Monotone compatibility.
(C) Phoenix/Hornet 2015
Play Data: play data are series of tracker-like instructions, visualised as:

View File

@@ -3613,16 +3613,25 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
// Funk repeat (S$Fx00) bit-mask — non-destructive XOR overlay across the loop region.
// Lazily allocated; a 1-bit flips the byte, a 0-bit leaves it intact.
// Mask is sized for the loop length at allocation time; if the loop bounds change
// (e.g. a new song reuses this instrument slot with different sample data) the old
// mask is stale and must be discarded — otherwise indexing past its end crashes the
// render thread with ArrayIndexOutOfBoundsException.
var funkMask: ByteArray? = null
fun toggleFunkBit(loopOffset: Int) {
val len = (sampleLoopEnd - sampleLoopStart).coerceAtLeast(1)
val mask = funkMask ?: ByteArray((len + 7) / 8).also { funkMask = it }
val expectedSize = (len + 7) / 8
var mask = funkMask
if (mask == null || mask.size != expectedSize) {
mask = ByteArray(expectedSize).also { funkMask = it }
}
val idx = loopOffset.coerceIn(0, len - 1)
mask[idx / 8] = (mask[idx / 8].toInt() xor (1 shl (idx and 7))).toByte()
}
fun funkBit(loopOffset: Int): Boolean {
val mask = funkMask ?: return false
val len = (sampleLoopEnd - sampleLoopStart).coerceAtLeast(1)
if (mask.size != (len + 7) / 8) { funkMask = null; return false }
val idx = loopOffset.coerceIn(0, len - 1)
return (mask[idx / 8].toInt() ushr (idx and 7)) and 1 != 0
}