mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
Compare commits
5 Commits
7d89605302
...
e3bd4a1b59
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3bd4a1b59 | ||
|
|
70d953a784 | ||
|
|
f3ece28a10 | ||
|
|
3ecf842ac0 | ||
|
|
6004060344 |
@@ -735,6 +735,87 @@ Peak at maximum settings: $7F × $FF >> 9 = $3F — the full panning range. Retr
|
||||
|
||||
---
|
||||
|
||||
## 7 $xxyy — Pattern Ditto
|
||||
|
||||
**Plain.** A per-channel "fill the rest from above" marker: the engine copies the **$xx rows immediately preceding this cell on the same channel** and pastes them $yy times starting on this row. The destination block therefore covers `$xx × $yy` rows beginning at the ditto row inclusive. Any field (note, instrument, vol-column, pan-column, effect) that the composer has explicitly written into a destination row stays put and patches the corresponding field of the copied source cell — empty fields fall through to the source. The ditto opcode itself is consumed by the marker on its arming row; the rest of that row's columns are patched from the source as usual, so an empty arming row plays back identically to the first row of the source block.
|
||||
|
||||
For example, with `7 $1003` on row 16, rows 16..63 replay the contents of rows 0..15 three times. A `D $0400` punched onto row 22 simply overrides the effect column on that destination row; its note/vol/pan still come from the source row 6 (since (22 − 16) mod 16 = 6, and 0 + 6 = source row 6).
|
||||
|
||||
Boundary rules:
|
||||
|
||||
- The block stops at the end of the pattern: a ditto whose nominal span would overflow the pattern's row count clips silently at the final row.
|
||||
- `$xx = $00`, `$yy = $00`, and any `$xx` greater than the row index on which the ditto sits are all treated as no-ops — there is nothing valid to copy from.
|
||||
- A `7` cell appearing inside a source block is **not** recursively expanded: when that source row is pasted into a destination, its effect column is treated as empty. This keeps expansion single-pass and prevents unbounded nesting.
|
||||
- Flow-control effects (B, C, S$Bx, S$Ex) that fall inside a source block still fire when their copy lands on a destination row, since the engine sees them as ordinary effect cells after expansion. Composers and converters should avoid placing S$Bx loop bounds wholly inside a ditto'd range — the loop counter is per-voice and the same destination row would be revisited twice with the same state.
|
||||
|
||||
**Compatibility.** Unique to Taud — no ST3/IT/PT equivalent. The effect has no memory.
|
||||
|
||||
**Implementation.** Per-voice state, all reset on pattern change alongside the existing pattern-loop / fine-pattern-delay clears:
|
||||
|
||||
- `dittoActive: bool`
|
||||
- `dittoSourceStart: int` — first row of the source block (inclusive)
|
||||
- `dittoLength: int` — $xx, the block size
|
||||
- `dittoEndRow: int` — last destination row (inclusive)
|
||||
|
||||
At the very top of `applyTrackerRow`, before the per-voice reset of row-scope state, build an effective cell view for each voice:
|
||||
|
||||
```
|
||||
raw = patternRows[V.pattern][N] # stored cell on row N for voice V
|
||||
isArmer = (raw.effect == 0x7 and raw.effectArg != 0)
|
||||
|
||||
if isArmer:
|
||||
length = (raw.effectArg >> 8) & 0xFF
|
||||
repeats = raw.effectArg & 0xFF
|
||||
if length > 0 and repeats > 0 and length <= N:
|
||||
V.dittoSourceStart = N - length
|
||||
V.dittoLength = length
|
||||
V.dittoEndRow = min(N + length * repeats - 1, patternLength - 1)
|
||||
V.dittoActive = true
|
||||
# else: malformed argument — fall through with dittoActive unchanged
|
||||
|
||||
armRow = V.dittoSourceStart + V.dittoLength # always equals the row that armed this ditto
|
||||
|
||||
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.instrument = (raw.instrument != 0) ? raw.instrument : src.instrument
|
||||
|
||||
# SEL_FINE / 0 is the canonical no-op encoding for the vol- and pan-columns;
|
||||
# any other (selector, value) pair is a write and patches the source.
|
||||
cell.vol, cell.volEff = (raw.volEff, raw.vol) != (SEL_FINE, 0)
|
||||
? (raw.vol, raw.volEff)
|
||||
: (src.vol, src.volEff)
|
||||
cell.pan, cell.panEff = (raw.panEff, raw.pan) != (SEL_FINE, 0)
|
||||
? (raw.pan, raw.panEff)
|
||||
: (src.pan, src.panEff)
|
||||
|
||||
# On the armer row, the 7-opcode is consumed by the marker, so for effect-column
|
||||
# patching purposes the destination is treated as empty. Source 7-opcodes never
|
||||
# propagate (no recursive expansion).
|
||||
destOp, destArg = isArmer ? (0, 0) : (raw.effect, raw.effectArg)
|
||||
if destOp != 0:
|
||||
cell.effect, cell.effectArg = destOp, destArg
|
||||
elif src.effect != 0x7:
|
||||
cell.effect, cell.effectArg = src.effect, src.effectArg
|
||||
else:
|
||||
cell.effect, cell.effectArg = 0, 0
|
||||
|
||||
else:
|
||||
cell = raw
|
||||
```
|
||||
|
||||
The four ditto fields are not cleared at the natural end of the destination range; they simply stop matching the gating condition once `N` advances past `dittoEndRow`, and a later armer cell in the same pattern overwrites them in place. Explicit clears happen only on cue advance (B / C / natural pattern end) and full playhead reset, alongside the existing pattern-loop counters in `resetPatternLoopState` / `resetParams`.
|
||||
|
||||
The rest of `applyTrackerRow` then dispatches on `cell` exactly as for an undittoed row — note triggering, vol/pan column application, and effect handling are unchanged. The expansion mutates the in-memory cell view only; the stored pattern data is never rewritten.
|
||||
|
||||
Pattern-delay (S$Ex) re-runs `applyTrackerRow` on the same `N` — the ditto bookkeeping is idempotent across those re-entries because `dittoActive`, `dittoSourceStart`, `dittoLength`, and `dittoEndRow` already encode the destination range, and the armer guard `length <= N` makes repeated arming on the same row a no-op (the new state is identical to the old). The `armRow <= N` half of the gating condition is what protects against an S$Bx pattern-loop that jumps back to a row sitting strictly before the armer: rather than synthesising from a phantom source slot, the engine falls through to the raw cell.
|
||||
|
||||
Effect dispatch sees the synthesised effect, never the literal `7` opcode of the armer cell — `OP_7` therefore exists in the engine's opcode table only as an explicit no-op for the rare malformed-armer fallthrough (`length == 0`, `repeats == 0`, or `length > N`).
|
||||
|
||||
---
|
||||
|
||||
## 8 $xyzz — Bitcrusher
|
||||
|
||||
**Plain.** Applies a bitcrusher to the current voice. The crusher has two independent stages — a sample-rate reducer (`zz`, sample-and-hold) and a bit-depth quantiser (`y`) — and shares its clipping mode (`x`) with effect 9 (Overdrive). The two stages are orthogonal: enabling either is sufficient to engage the effect, and either can be active alone.
|
||||
|
||||
@@ -102,7 +102,7 @@ const fxNames = {
|
||||
'4':"UNIMPLEMENTED",
|
||||
'5':"UNIMPLEMENTED",
|
||||
'6':"UNIMPLEMENTED",
|
||||
'7':"UNIMPLEMENTED",
|
||||
'7':"Pattern Ditto",
|
||||
'8':"Bitcrusher ",
|
||||
'9':"Overdrive ",
|
||||
A:"Tick speed ",
|
||||
@@ -169,48 +169,92 @@ const volFxNames = {
|
||||
|
||||
const pitchTablePresets = {
|
||||
// index: pitch table number to be recorded on .taudproj file
|
||||
0:{index:0,name:"null", table:[], sym:[]}, // when null is specified, hex numbers will be displayed instead
|
||||
0:{index:0,name:"Raw format",table:[],interval:0x1000,sym:[]}, // when null is specified, hex numbers will be displayed instead
|
||||
/* Xenharmonic, equal temperament */
|
||||
50:{index:50,name:"5-TET", table:[0x0,0x333,0x666,0x99A,0xCCD],
|
||||
10:{index:10,name:"Octave only", table:[0x0],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`]},
|
||||
20:{index:20,name:"2-TET", table:[0x0,0x800],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`F${sym.sharp}`]},
|
||||
30:{index:30,name:"3-TET", table:[0x0,0x555,0xAAB],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`E${sym.accnull}`,`G${sym.sharp}`]},
|
||||
40:{index:40,name:"4-TET", table:[0x0,0x400,0x800,0xC00],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`D${sym.sharp}`,`F${sym.sharp}`,`A${sym.accnull}`]},
|
||||
50:{index:50,name:"5-TET", table:[0x0,0x333,0x666,0x99A,0xCCD],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`D${sym.accnull}`,`E${sym.accnull}`,`G${sym.accnull}`,`A${sym.accnull}`]},
|
||||
70:{index:70,name:"7-TET", table:[0x0,0x249,0x492,0x6DB,0x925,0xB6E,0xDB7],
|
||||
60:{index:60,name:"6-TET", table:[0x0,0x2AB,0x555,0x800,0xAAB,0xD55],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`D${sym.accnull}`,`E${sym.accnull}`,`F${sym.sharp}`,`G${sym.sharp}`,`A${sym.sharp}`]},
|
||||
70:{index:70,name:"7-TET", table:[0x0,0x249,0x492,0x6DB,0x925,0xB6E,0xDB7],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`D${sym.accnull}`,`E${sym.accnull}`,`F${sym.accnull}`,`G${sym.accnull}`,`A${sym.accnull}`,`B${sym.accnull}`]},
|
||||
100:{index:100,name:"10-TET", table:[0x0,0x19A,0x333,0x4CD,0x666,0x800,0x99A,0xB33,0xCCD,0xE66],
|
||||
80:{index:80,name:"8-TET", table:[0x0,0x200,0x400,0x600,0x800,0xA00,0xC00,0xE00],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`D${sym.accnull}`,`E${sym.accnull}`,`F${sym.accnull}`,`F${sym.sharp}`,`G${sym.sharp}`,`A${sym.accnull}`,`B${sym.accnull}`]},
|
||||
90:{index:90,name:"9-TET", table:[0x0,0x1C7,0x38E,0x555,0x71C,0x8E4,0xAAB,0xC72,0xE39],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`D${sym.accnull}`,`E${sym.accnull}`,`E${sym.sharp}`,`F${sym.accnull}`,`G${sym.accnull}`,`A${sym.accnull}`,`B${sym.accnull}`,`B${sym.sharp}`]},
|
||||
100:{index:100,name:"10-TET", table:[0x0,0x19A,0x333,0x4CD,0x666,0x800,0x99A,0xB33,0xCCD,0xE66],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`D${sym.flat}`,`D${sym.accnull}`,`E${sym.flat}`,`E${sym.accnull}`,`E${sym.sharp}`,`G${sym.accnull}`,`G${sym.sharp}`,`A${sym.accnull}`,`A${sym.sharp}`]},
|
||||
150:{index:150,name:"15-TET", table:[0x0,0x111,0x222,0x333,0x444,0x555,0x666,0x777,0x889,0x99A,0xAAB,0xBBC,0xCCD,0xDDE,0xEEF],
|
||||
150:{index:150,name:"15-TET", table:[0x0,0x111,0x222,0x333,0x444,0x555,0x666,0x777,0x889,0x99A,0xAAB,0xBBC,0xCCD,0xDDE,0xEEF],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`C${sym.sharp}`,`D${sym.accnull}`,`D${sym.sharp}`,`E${sym.flat}`,`E${sym.accnull}`,`E${sym.sharp}`,`F${sym.sharp}`,`G${sym.accnull}`,`G${sym.sharp}`,`A${sym.flat}`,`A${sym.accnull}`,`A${sym.sharp}`,`B${sym.flat}`,`B${sym.accnull}`]},
|
||||
170:{index:170,name:"17-TET", table:[0x0,0xF1,0x1E2,0x2D3,0x3C4,0x4B5,0x5A6,0x697,0x788,0x878,0x969,0xA5A,0xB4B,0xC3C,0xD2D,0xE1E,0xF0F],
|
||||
160:{index:160,name:"16-TET", table:[0x0,0x100,0x200,0x300,0x400,0x500,0x600,0x700,0x800,0x900,0xA00,0xB00,0xC00,0xD00,0xE00,0xF00],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`C${sym.sharp}`,`D${sym.accnull}`,`D${sym.sharp}`,`E${sym.accnull}`,`E${sym.sharp}`,`F${sym.flat}`,`F${sym.accnull}`,`F${sym.sharp}`,`G${sym.accnull}`,`G${sym.sharp}`,`A${sym.accnull}`,`A${sym.sharp}`,`B${sym.accnull}`,`B${sym.sharp}`,`C${sym.flat}`]},
|
||||
170:{index:170,name:"17-TET", table:[0x0,0xF1,0x1E2,0x2D3,0x3C4,0x4B5,0x5A6,0x697,0x788,0x878,0x969,0xA5A,0xB4B,0xC3C,0xD2D,0xE1E,0xF0F],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`D${sym.flat}`,`C${sym.sharp}`,`D${sym.accnull}`,`E${sym.flat}`,`D${sym.sharp}`,`E${sym.accnull}`,`F${sym.accnull}`,`G${sym.flat}`,`F${sym.sharp}`,`G${sym.accnull}`,`A${sym.flat}`,`G${sym.sharp}`,`A${sym.accnull}`,`B${sym.flat}`,`A${sym.sharp}`,`B${sym.accnull}`]},
|
||||
190:{index:190,name:"19-TET", table:[0x0,0xD8,0x1AF,0x287,0x35E,0x436,0x50D,0x5E5,0x6BD,0x794,0x86C,0x943,0xA1B,0xAF3,0xBCA,0xCA2,0xD79,0xE51,0xF28],
|
||||
190:{index:190,name:"19-TET", table:[0x0,0xD8,0x1AF,0x287,0x35E,0x436,0x50D,0x5E5,0x6BD,0x794,0x86C,0x943,0xA1B,0xAF3,0xBCA,0xCA2,0xD79,0xE51,0xF28],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`C${sym.sharp}`,`D${sym.flat}`,`D${sym.accnull}`,`D${sym.sharp}`,`E${sym.flat}`,`E${sym.accnull}`,`E${sym.sharp}`,`F${sym.accnull}`,`F${sym.sharp}`,`G${sym.flat}`,`G${sym.accnull}`,`G${sym.sharp}`,`A${sym.flat}`,`A${sym.accnull}`,`A${sym.sharp}`,`B${sym.flat}`,`B${sym.accnull}`,`B${sym.sharp}`]},
|
||||
220:{index:220,name:"22-TET", table:[0x0,0xBA,0x174,0x22F,0x2E9,0x3A3,0x45D,0x517,0x5D1,0x68C,0x746,0x800,0x8BA,0x974,0xA2F,0xAE9,0xBA3,0xC5D,0xD17,0xDD1,0xE8C,0xF46],
|
||||
220:{index:220,name:"22-TET", table:[0x0,0xBA,0x174,0x22F,0x2E9,0x3A3,0x45D,0x517,0x5D1,0x68C,0x746,0x800,0x8BA,0x974,0xA2F,0xAE9,0xBA3,0xC5D,0xD17,0xDD1,0xE8C,0xF46],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`C${sym.demisharp}`,`C${sym.sharp}`,`D${sym.demiflat}`,`D${sym.accnull}`,`D${sym.demisharp}`,`D${sym.sharp}`,`E${sym.demiflat}`,`E${sym.accnull}`,`F${sym.accnull}`,`F${sym.demisharp}`,`F${sym.sharp}`,`G${sym.demiflat}`,`G${sym.accnull}`,`G${sym.demisharp}`,`G${sym.sharp}`,`A${sym.demiflat}`,`A${sym.accnull}`,`A${sym.demisharp}`,`A${sym.sharp}`,`B${sym.demiflat}`,`B${sym.accnull}`]},
|
||||
240:{index:240,name:"24-TET", table:[0x0,0xAB,0x155,0x200,0x2AB,0x355,0x400,0x4AB,0x555,0x600,0x6AB,0x755,0x800,0x8AB,0x955,0xA00,0xAAB,0xB55,0xC00,0xCAB,0xD55,0xE00,0xEAB,0xF55],
|
||||
240:{index:240,name:"24-TET", table:[0x0,0xAB,0x155,0x200,0x2AB,0x355,0x400,0x4AB,0x555,0x600,0x6AB,0x755,0x800,0x8AB,0x955,0xA00,0xAAB,0xB55,0xC00,0xCAB,0xD55,0xE00,0xEAB,0xF55],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`C${sym.demisharp}`,`C${sym.sharp}`,`D${sym.demiflat}`,`D${sym.accnull}`,`D${sym.demisharp}`,`D${sym.sharp}`,`E${sym.demiflat}`,`E${sym.accnull}`,`E${sym.demisharp}`,`F${sym.accnull}`,`F${sym.demisharp}`,`F${sym.sharp}`,`G${sym.demiflat}`,`G${sym.accnull}`,`G${sym.demisharp}`,`G${sym.sharp}`,`A${sym.demiflat}`,`A${sym.accnull}`,`A${sym.demisharp}`,`A${sym.sharp}`,`B${sym.demiflat}`,`B${sym.accnull}`,`B${sym.demisharp}`]},
|
||||
310:{index:310,name:"31-TET", table:[0x0,0x84,0x108,0x18C,0x211,0x295,0x319,0x39D,0x421,0x4A5,0x529,0x5AD,0x632,0x6B6,0x73A,0x7BE,0x842,0x8C6,0x94A,0x9CE,0xA53,0xAD7,0xB5B,0xBDF,0xC63,0xCE7,0xD6B,0xDEF,0xE74,0xEF8,0xF7C],
|
||||
310:{index:310,name:"31-TET", table:[0x0,0x84,0x108,0x18C,0x211,0x295,0x319,0x39D,0x421,0x4A5,0x529,0x5AD,0x632,0x6B6,0x73A,0x7BE,0x842,0x8C6,0x94A,0x9CE,0xA53,0xAD7,0xB5B,0xBDF,0xC63,0xCE7,0xD6B,0xDEF,0xE74,0xEF8,0xF7C],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`C${sym.demisharp}`,`C${sym.sharp}`,`D${sym.flat}`,`D${sym.demiflat}`,`D${sym.accnull}`,`D${sym.demisharp}`,`D${sym.sharp}`,`E${sym.flat}`,`E${sym.demiflat}`,`E${sym.accnull}`,`E${sym.demisharp}`,`F${sym.demiflat}`,`F${sym.accnull}`,`F${sym.demisharp}`,`F${sym.sharp}`,`G${sym.flat}`,`G${sym.demiflat}`,`G${sym.accnull}`,`G${sym.demisharp}`,`G${sym.sharp}`,`A${sym.flat}`,`A${sym.demiflat}`,`A${sym.accnull}`,`A${sym.demisharp}`,`A${sym.sharp}`,`B${sym.flat}`,`B${sym.demiflat}`,`B${sym.accnull}`,`B${sym.demisharp}`,`C${sym.demiflat}`]},
|
||||
410:{index:410,name:"41-TET", table:[0x0,0x64,0xC8,0x12C,0x190,0x1F4,0x257,0x2BB,0x31F,0x383,0x3E7,0x44B,0x4AF,0x513,0x577,0x5DB,0x63E,0x6A2,0x706,0x76A,0x7CE,0x832,0x896,0x8FA,0x95E,0x9C2,0xA25,0xA89,0xAED,0xB51,0xBB5,0xC19,0xC7D,0xCE1,0xD45,0xDA9,0xE0C,0xE70,0xED4,0xF38,0xF9C],
|
||||
sym:[`-C-`,`${sym.uptick}C-`,`${sym.doubledntick}C${sym.csharp}`,`${sym.dntick}C${sym.csharp}`,`-C${sym.csharp}`,`${sym.uptick}C${sym.csharp}`,`${sym.dntick}D-`,`-D-`,`${sym.uptick}D-`,`${sym.doubledntick}D${sym.csharp}`,`${sym.dntick}D${sym.csharp}`,`-D${sym.csharp}`,`${sym.uptick}D${sym.csharp}`,`${sym.dntick}E-`,`-E-`,`${sym.uptick}E-`,`${sym.doubleuptick}E-`,`-F-`,`${sym.uptick}F-`,`${sym.doubledntick}F${sym.csharp}`,`${sym.dntick}F${sym.csharp}`,`-F${sym.csharp}`,`${sym.uptick}F${sym.csharp}`,`${sym.dntick}G-`,`-G-`,`${sym.uptick}G-`,`${sym.doubledntick}G${sym.csharp}`,`${sym.dntick}G${sym.csharp}`,`-G${sym.csharp}`,`${sym.uptick}G${sym.csharp}`,`${sym.dntick}A-`,`-A-`,`${sym.uptick}A-`,`${sym.doubledntick}A${sym.csharp}`,`${sym.dntick}A${sym.csharp}`,`-A${sym.csharp}`,`${sym.uptick}A${sym.csharp}`,`${sym.dntick}B-`,`-B-`,`${sym.uptick}B-`,`${sym.doubleuptick}B-`]},
|
||||
530:{index:530,name:"53-TET (Kite notation)", table:[0x0,0x4D,0x9B,0xE8,0x135,0x182,0x1D0,0x21D,0x26A,0x2B8,0x305,0x352,0x39F,0x3ED,0x43A,0x487,0x4D5,0x522,0x56F,0x5BC,0x60A,0x657,0x6A4,0x6F2,0x73F,0x78C,0x7D9,0x827,0x874,0x8C1,0x90E,0x95C,0x9A9,0x9F6,0xA44,0xA91,0xADE,0xB2B,0xB79,0xBC6,0xC13,0xC61,0xCAE,0xCFB,0xD48,0xD96,0xDE3,0xE30,0xE7E,0xECB,0xF18,0xF65,0xFB3],
|
||||
sym:[`-C-`,`${sym.uptick}C-`,`${sym.doubleuptick}C-`,`${sym.doubledntick}C${sym.csharp}`,`${sym.dntick}C${sym.csharp}`,`-C${sym.csharp}`,`${sym.uptick}C${sym.csharp}`,`${sym.doubledntick}D-`,`${sym.dntick}D-`,`-D-`,`${sym.uptick}D-`,`${sym.doubleuptick}D-`,`${sym.doubledntick}D${sym.csharp}`,`${sym.dntick}D${sym.csharp}`,`-D${sym.csharp}`,`${sym.uptick}D${sym.csharp}`,`${sym.doubledntick}E-`,`${sym.dntick}E-`,`-E-`,`${sym.uptick}E-`,`${sym.doubleuptick}E-`,`${sym.dntick}F-`,`-F-`,`${sym.uptick}F-`,`${sym.doubleuptick}F-`,`${sym.doubledntick}F${sym.csharp}`,`${sym.dntick}F${sym.csharp}`,`-F${sym.csharp}`,`${sym.uptick}F${sym.csharp}`,`${sym.doubledntick}G-`,`${sym.dntick}G-`,`-G-`,`${sym.uptick}G-`,`${sym.doubleuptick}G-`,`${sym.doubledntick}G${sym.csharp}`,`${sym.dntick}G${sym.csharp}`,`-G${sym.csharp}`,`${sym.uptick}G${sym.csharp}`,`${sym.doubledntick}A-`,`${sym.dntick}A-`,`-A-`,`${sym.uptick}A-`,`${sym.doubleuptick}A-`,`${sym.doubledntick}A${sym.csharp}`,`${sym.dntick}A${sym.csharp}`,`-A${sym.csharp}`,`${sym.uptick}A${sym.csharp}`,`${sym.doubledntick}B-`,`${sym.dntick}B-`,`-B-`,`${sym.uptick}B-`,`${sym.doubleuptick}B-`,`${sym.dntick}C-`]},
|
||||
531:{index:531,name:"53-TET (Pythagorean Notation)", table:[0x0,0x4D,0x9B,0xE8,0x135,0x182,0x1D0,0x21D,0x26A,0x2B8,0x305,0x352,0x39F,0x3ED,0x43A,0x487,0x4D5,0x522,0x56F,0x5BC,0x60A,0x657,0x6A4,0x6F2,0x73F,0x78C,0x7D9,0x827,0x874,0x8C1,0x90E,0x95C,0x9A9,0x9F6,0xA44,0xA91,0xADE,0xB2B,0xB79,0xBC6,0xC13,0xC61,0xCAE,0xCFB,0xD48,0xD96,0xDE3,0xE30,0xE7E,0xECB,0xF18,0xF65,0xFB3],
|
||||
410:{index:410,name:"41-TET (Kite)", table:[0x0,0x64,0xC8,0x12C,0x190,0x1F4,0x257,0x2BB,0x31F,0x383,0x3E7,0x44B,0x4AF,0x513,0x577,0x5DB,0x63E,0x6A2,0x706,0x76A,0x7CE,0x832,0x896,0x8FA,0x95E,0x9C2,0xA25,0xA89,0xAED,0xB51,0xBB5,0xC19,0xC7D,0xCE1,0xD45,0xDA9,0xE0C,0xE70,0xED4,0xF38,0xF9C],interval:0x1000,
|
||||
sym:[`${BIGDOT}C-`,`${sym.uptick}C-`,`${sym.doubledntick}C${sym.csharp}`,`${sym.dntick}C${sym.csharp}`,`${BIGDOT}C${sym.csharp}`,`${sym.uptick}C${sym.csharp}`,`${sym.dntick}D-`,`${BIGDOT}D-`,`${sym.uptick}D-`,`${sym.doubledntick}D${sym.csharp}`,`${sym.dntick}D${sym.csharp}`,`${BIGDOT}D${sym.csharp}`,`${sym.uptick}D${sym.csharp}`,`${sym.dntick}E-`,`${BIGDOT}E-`,`${sym.uptick}E-`,`${sym.doubleuptick}E-`,`${BIGDOT}F-`,`${sym.uptick}F-`,`${sym.doubledntick}F${sym.csharp}`,`${sym.dntick}F${sym.csharp}`,`${BIGDOT}F${sym.csharp}`,`${sym.uptick}F${sym.csharp}`,`${sym.dntick}G-`,`${BIGDOT}G-`,`${sym.uptick}G-`,`${sym.doubledntick}G${sym.csharp}`,`${sym.dntick}G${sym.csharp}`,`${BIGDOT}G${sym.csharp}`,`${sym.uptick}G${sym.csharp}`,`${sym.dntick}A-`,`${BIGDOT}A-`,`${sym.uptick}A-`,`${sym.doubledntick}A${sym.csharp}`,`${sym.dntick}A${sym.csharp}`,`${BIGDOT}A${sym.csharp}`,`${sym.uptick}A${sym.csharp}`,`${sym.dntick}B-`,`${BIGDOT}B-`,`${sym.uptick}B-`,`${sym.doubleuptick}B-`]},
|
||||
530:{index:530,name:"53-TET (Kite)", table:[0x0,0x4D,0x9B,0xE8,0x135,0x182,0x1D0,0x21D,0x26A,0x2B8,0x305,0x352,0x39F,0x3ED,0x43A,0x487,0x4D5,0x522,0x56F,0x5BC,0x60A,0x657,0x6A4,0x6F2,0x73F,0x78C,0x7D9,0x827,0x874,0x8C1,0x90E,0x95C,0x9A9,0x9F6,0xA44,0xA91,0xADE,0xB2B,0xB79,0xBC6,0xC13,0xC61,0xCAE,0xCFB,0xD48,0xD96,0xDE3,0xE30,0xE7E,0xECB,0xF18,0xF65,0xFB3],interval:0x1000,
|
||||
sym:[`${BIGDOT}C-`,`${sym.uptick}C-`,`${sym.doubleuptick}C-`,`${sym.doubledntick}C${sym.csharp}`,`${sym.dntick}C${sym.csharp}`,`${BIGDOT}C${sym.csharp}`,`${sym.uptick}C${sym.csharp}`,`${sym.doubledntick}D-`,`${sym.dntick}D-`,`${BIGDOT}D-`,`${sym.uptick}D-`,`${sym.doubleuptick}D-`,`${sym.doubledntick}D${sym.csharp}`,`${sym.dntick}D${sym.csharp}`,`${BIGDOT}D${sym.csharp}`,`${sym.uptick}D${sym.csharp}`,`${sym.doubledntick}E-`,`${sym.dntick}E-`,`${BIGDOT}E-`,`${sym.uptick}E-`,`${sym.doubleuptick}E-`,`${sym.dntick}F-`,`${BIGDOT}F-`,`${sym.uptick}F-`,`${sym.doubleuptick}F-`,`${sym.doubledntick}F${sym.csharp}`,`${sym.dntick}F${sym.csharp}`,`${BIGDOT}F${sym.csharp}`,`${sym.uptick}F${sym.csharp}`,`${sym.doubledntick}G-`,`${sym.dntick}G-`,`${BIGDOT}G-`,`${sym.uptick}G-`,`${sym.doubleuptick}G-`,`${sym.doubledntick}G${sym.csharp}`,`${sym.dntick}G${sym.csharp}`,`${BIGDOT}G${sym.csharp}`,`${sym.uptick}G${sym.csharp}`,`${sym.doubledntick}A-`,`${sym.dntick}A-`,`${BIGDOT}A-`,`${sym.uptick}A-`,`${sym.doubleuptick}A-`,`${sym.doubledntick}A${sym.csharp}`,`${sym.dntick}A${sym.csharp}`,`${BIGDOT}A${sym.csharp}`,`${sym.uptick}A${sym.csharp}`,`${sym.doubledntick}B-`,`${sym.dntick}B-`,`${BIGDOT}B-`,`${sym.uptick}B-`,`${sym.doubleuptick}B-`,`${sym.dntick}C-`]},
|
||||
531:{index:531,name:"53-TET (Pythagorean)", table:[0x0,0x4D,0x9B,0xE8,0x135,0x182,0x1D0,0x21D,0x26A,0x2B8,0x305,0x352,0x39F,0x3ED,0x43A,0x487,0x4D5,0x522,0x56F,0x5BC,0x60A,0x657,0x6A4,0x6F2,0x73F,0x78C,0x7D9,0x827,0x874,0x8C1,0x90E,0x95C,0x9A9,0x9F6,0xA44,0xA91,0xADE,0xB2B,0xB79,0xBC6,0xC13,0xC61,0xCAE,0xCFB,0xD48,0xD96,0xDE3,0xE30,0xE7E,0xECB,0xF18,0xF65,0xFB3],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`B${sym.sharp}`,`A${sym.triplesharp}`,`E${sym.tripleflat}`,`D${sym.flat}`,`C${sym.sharp}`,`B${sym.doublesharp}`,`F${sym.tripleflat}`,`E${sym.doubleflat}`,`D${sym.accnull}`,`C${sym.doublesharp}`,`B${sym.triplesharp}`,`F${sym.doubleflat}`,`E${sym.flat}`,`D${sym.sharp}`,`C${sym.triplesharp}`,`G${sym.tripleflat}`,`F${sym.flat}`,`E${sym.accnull}`,`D${sym.doublesharp}`,`C${sym.quadsharp}`,`G${sym.doubleflat}`,`F${sym.accnull}`,`E${sym.sharp}`,`D${sym.triplesharp}`,`A${sym.tripleflat}`,`G${sym.flat}`,`F${sym.sharp}`,`E${sym.doublesharp}`,`D${sym.quadsharp}`,`A${sym.doubleflat}`,`G${sym.accnull}`,`F${sym.doublesharp}`,`E${sym.triplesharp}`,`B${sym.tripleflat}`,`A${sym.flat}`,`G${sym.sharp}`,`F${sym.triplesharp}`,`C${sym.tripleflat}`,`B${sym.doubleflat}`,`A${sym.accnull}`,`G${sym.doublesharp}`,`F${sym.quadsharp}`,`C${sym.doubleflat}`,`B${sym.flat}`,`A${sym.sharp}`,`G${sym.triplesharp}`,`D${sym.tripleflat}`,`C${sym.flat}`,`B${sym.accnull}`,`A${sym.doublesharp}`,`G${sym.quadsharp}`,`D${sym.doubleflat}`]},
|
||||
960:{index:960,name:"96-TET", table:[0x0,0x2B,0x55,0x80,0xAB,0xD5,0x100,0x12B,0x155,0x180,0x1AB,0x1D5,0x200,0x22B,0x255,0x280,0x2AB,0x2D5,0x300,0x32B,0x355,0x380,0x3AB,0x3D5,0x400,0x42B,0x455,0x480,0x4AB,0x4D5,0x500,0x52B,0x555,0x580,0x5AB,0x5D5,0x600,0x62B,0x655,0x680,0x6AB,0x6D5,0x700,0x72B,0x755,0x780,0x7AB,0x7D5,0x800,0x82B,0x855,0x880,0x8AB,0x8D5,0x900,0x92B,0x955,0x980,0x9AB,0x9D5,0xA00,0xA2B,0xA55,0xA80,0xAAB,0xAD5,0xB00,0xB2B,0xB55,0xB80,0xBAB,0xBD5,0xC00,0xC2B,0xC55,0xC80,0xCAB,0xCD5,0xD00,0xD2B,0xD55,0xD80,0xDAB,0xDD5,0xE00,0xE2B,0xE55,0xE80,0xEAB,0xED5,0xF00,0xF2B,0xF55,0xF80,0xFAB,0xFD5],
|
||||
sym:[`-C-`,`${sym.uptick}C-`,`${sym.doubleuptick}C-`,`${sym.dntick}C${sym.cdemisharp}`,`-C${sym.cdemisharp}`,`${sym.uptick}C${sym.cdemisharp}`,`${sym.doubleuptick}C${sym.cdemisharp}`,`${sym.dntick}C${sym.csharp}`,`-C${sym.csharp}`,`${sym.uptick}C${sym.csharp}`,`${sym.doubleuptick}C${sym.csharp}`,`${sym.dntick}D${sym.cdemiflat}`,`-D${sym.cdemiflat}`,`${sym.uptick}D${sym.cdemiflat}`,`${sym.doubleuptick}D${sym.cdemiflat}`,`${sym.dntick}D-`,`-D-`,`${sym.uptick}D-`,`${sym.doubleuptick}D-`,`${sym.dntick}D${sym.cdemisharp}`,`-D${sym.cdemisharp}`,`${sym.uptick}D${sym.cdemisharp}`,`${sym.doubleuptick}D${sym.cdemisharp}`,`${sym.dntick}D${sym.csharp}`,`-D${sym.csharp}`,`${sym.uptick}D${sym.csharp}`,`${sym.doubleuptick}D${sym.csharp}`,`${sym.dntick}E${sym.cdemiflat}`,`-E${sym.cdemiflat}`,`${sym.uptick}E${sym.cdemiflat}`,`${sym.doubleuptick}E${sym.cdemiflat}`,`${sym.dntick}E-`,`-E-`,`${sym.uptick}E-`,`${sym.doubleuptick}E-`,`${sym.dntick}E${sym.cdemisharp}`,`-E${sym.cdemisharp}`,`${sym.uptick}E${sym.cdemisharp}`,`${sym.doubleuptick}E${sym.cdemisharp}`,`${sym.dntick}F-`,`-F-`,`${sym.uptick}F-`,`${sym.doubleuptick}F-`,`${sym.dntick}F${sym.cdemisharp}`,`-F${sym.cdemisharp}`,`${sym.uptick}F${sym.cdemisharp}`,`${sym.doubleuptick}F${sym.cdemisharp}`,`${sym.dntick}F${sym.csharp}`,`-F${sym.csharp}`,`${sym.uptick}F${sym.csharp}`,`${sym.doubleuptick}F${sym.csharp}`,`${sym.dntick}G${sym.cdemiflat}`,`-G${sym.cdemiflat}`,`${sym.uptick}G${sym.cdemiflat}`,`${sym.doubleuptick}G${sym.cdemiflat}`,`${sym.dntick}G-`,`-G-`,`${sym.uptick}G-`,`${sym.doubleuptick}G-`,`${sym.dntick}G${sym.cdemisharp}`,`-G${sym.cdemisharp}`,`${sym.uptick}G${sym.cdemisharp}`,`${sym.doubleuptick}G${sym.cdemisharp}`,`${sym.dntick}G${sym.csharp}`,`-G${sym.csharp}`,`${sym.uptick}G${sym.csharp}`,`${sym.doubleuptick}G${sym.csharp}`,`${sym.dntick}A${sym.cdemiflat}`,`-A${sym.cdemiflat}`,`${sym.uptick}A${sym.cdemiflat}`,`${sym.doubleuptick}A${sym.cdemiflat}`,`${sym.dntick}A-`,`-A-`,`${sym.uptick}A-`,`${sym.doubleuptick}A-`,`${sym.dntick}A${sym.cdemisharp}`,`-A${sym.cdemisharp}`,`${sym.uptick}A${sym.cdemisharp}`,`${sym.doubleuptick}A${sym.cdemisharp}`,`${sym.dntick}A${sym.csharp}`,`-A${sym.csharp}`,`${sym.uptick}A${sym.csharp}`,`${sym.doubleuptick}A${sym.csharp}`,`${sym.dntick}B${sym.cdemiflat}`,`-B${sym.cdemiflat}`,`${sym.uptick}B${sym.cdemiflat}`,`${sym.doubleuptick}B${sym.cdemiflat}`,`${sym.dntick}B-`,`-B-`,`${sym.uptick}B-`,`${sym.doubleuptick}B-`,`${sym.dntick}B${sym.cdemisharp}`,`-B${sym.cdemisharp}`,`${sym.uptick}B${sym.cdemisharp}`,`${sym.doubleuptick}B${sym.cdemisharp}`]},
|
||||
960:{index:960,name:"96-TET (Kite)", table:[0x0,0x2B,0x55,0x80,0xAB,0xD5,0x100,0x12B,0x155,0x180,0x1AB,0x1D5,0x200,0x22B,0x255,0x280,0x2AB,0x2D5,0x300,0x32B,0x355,0x380,0x3AB,0x3D5,0x400,0x42B,0x455,0x480,0x4AB,0x4D5,0x500,0x52B,0x555,0x580,0x5AB,0x5D5,0x600,0x62B,0x655,0x680,0x6AB,0x6D5,0x700,0x72B,0x755,0x780,0x7AB,0x7D5,0x800,0x82B,0x855,0x880,0x8AB,0x8D5,0x900,0x92B,0x955,0x980,0x9AB,0x9D5,0xA00,0xA2B,0xA55,0xA80,0xAAB,0xAD5,0xB00,0xB2B,0xB55,0xB80,0xBAB,0xBD5,0xC00,0xC2B,0xC55,0xC80,0xCAB,0xCD5,0xD00,0xD2B,0xD55,0xD80,0xDAB,0xDD5,0xE00,0xE2B,0xE55,0xE80,0xEAB,0xED5,0xF00,0xF2B,0xF55,0xF80,0xFAB,0xFD5],interval:0x1000,
|
||||
sym:[`${BIGDOT}C-`,`${sym.uptick}C-`,`${sym.doubleuptick}C-`,`${sym.dntick}C${sym.cdemisharp}`,`${BIGDOT}C${sym.cdemisharp}`,`${sym.uptick}C${sym.cdemisharp}`,`${sym.doubleuptick}C${sym.cdemisharp}`,`${sym.dntick}C${sym.csharp}`,`${BIGDOT}C${sym.csharp}`,`${sym.uptick}C${sym.csharp}`,`${sym.doubleuptick}C${sym.csharp}`,`${sym.dntick}D${sym.cdemiflat}`,`${BIGDOT}D${sym.cdemiflat}`,`${sym.uptick}D${sym.cdemiflat}`,`${sym.doubleuptick}D${sym.cdemiflat}`,`${sym.dntick}D-`,`${BIGDOT}D-`,`${sym.uptick}D-`,`${sym.doubleuptick}D-`,`${sym.dntick}D${sym.cdemisharp}`,`${BIGDOT}D${sym.cdemisharp}`,`${sym.uptick}D${sym.cdemisharp}`,`${sym.doubleuptick}D${sym.cdemisharp}`,`${sym.dntick}D${sym.csharp}`,`${BIGDOT}D${sym.csharp}`,`${sym.uptick}D${sym.csharp}`,`${sym.doubleuptick}D${sym.csharp}`,`${sym.dntick}E${sym.cdemiflat}`,`${BIGDOT}E${sym.cdemiflat}`,`${sym.uptick}E${sym.cdemiflat}`,`${sym.doubleuptick}E${sym.cdemiflat}`,`${sym.dntick}E-`,`${BIGDOT}E-`,`${sym.uptick}E-`,`${sym.doubleuptick}E-`,`${sym.dntick}E${sym.cdemisharp}`,`${BIGDOT}E${sym.cdemisharp}`,`${sym.uptick}E${sym.cdemisharp}`,`${sym.doubleuptick}E${sym.cdemisharp}`,`${sym.dntick}F-`,`${BIGDOT}F-`,`${sym.uptick}F-`,`${sym.doubleuptick}F-`,`${sym.dntick}F${sym.cdemisharp}`,`${BIGDOT}F${sym.cdemisharp}`,`${sym.uptick}F${sym.cdemisharp}`,`${sym.doubleuptick}F${sym.cdemisharp}`,`${sym.dntick}F${sym.csharp}`,`${BIGDOT}F${sym.csharp}`,`${sym.uptick}F${sym.csharp}`,`${sym.doubleuptick}F${sym.csharp}`,`${sym.dntick}G${sym.cdemiflat}`,`${BIGDOT}G${sym.cdemiflat}`,`${sym.uptick}G${sym.cdemiflat}`,`${sym.doubleuptick}G${sym.cdemiflat}`,`${sym.dntick}G-`,`${BIGDOT}G-`,`${sym.uptick}G-`,`${sym.doubleuptick}G-`,`${sym.dntick}G${sym.cdemisharp}`,`${BIGDOT}G${sym.cdemisharp}`,`${sym.uptick}G${sym.cdemisharp}`,`${sym.doubleuptick}G${sym.cdemisharp}`,`${sym.dntick}G${sym.csharp}`,`${BIGDOT}G${sym.csharp}`,`${sym.uptick}G${sym.csharp}`,`${sym.doubleuptick}G${sym.csharp}`,`${sym.dntick}A${sym.cdemiflat}`,`${BIGDOT}A${sym.cdemiflat}`,`${sym.uptick}A${sym.cdemiflat}`,`${sym.doubleuptick}A${sym.cdemiflat}`,`${sym.dntick}A-`,`${BIGDOT}A-`,`${sym.uptick}A-`,`${sym.doubleuptick}A-`,`${sym.dntick}A${sym.cdemisharp}`,`${BIGDOT}A${sym.cdemisharp}`,`${sym.uptick}A${sym.cdemisharp}`,`${sym.doubleuptick}A${sym.cdemisharp}`,`${sym.dntick}A${sym.csharp}`,`${BIGDOT}A${sym.csharp}`,`${sym.uptick}A${sym.csharp}`,`${sym.doubleuptick}A${sym.csharp}`,`${sym.dntick}B${sym.cdemiflat}`,`${BIGDOT}B${sym.cdemiflat}`,`${sym.uptick}B${sym.cdemiflat}`,`${sym.doubleuptick}B${sym.cdemiflat}`,`${sym.dntick}B-`,`${BIGDOT}B-`,`${sym.uptick}B-`,`${sym.doubleuptick}B-`,`${sym.dntick}B${sym.cdemisharp}`,`${BIGDOT}B${sym.cdemisharp}`,`${sym.uptick}B${sym.cdemisharp}`,`${sym.doubleuptick}B${sym.cdemisharp}`,`${sym.dntick}C-`]},
|
||||
/* 12-TET variations */
|
||||
120:{index:120,name:"12-TET", table:[0x0,0x155,0x2AB,0x400,0x555,0x6AB,0x800,0x955,0xAAB,0xC00,0xD55,0xEAB],
|
||||
120:{index:120,name:"12-TET", table:[0x0,0x155,0x2AB,0x400,0x555,0x6AB,0x800,0x955,0xAAB,0xC00,0xD55,0xEAB],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`C${sym.sharp}`,`D${sym.accnull}`,`D${sym.sharp}`,`E${sym.accnull}`,`F${sym.accnull}`,`F${sym.sharp}`,`G${sym.accnull}`,`G${sym.sharp}`,`A${sym.accnull}`,`A${sym.sharp}`,`B${sym.accnull}`]},
|
||||
10121:{index:10121,name:"Pythagorean Diminished Fifth", table:[0x0,0x134,0x2B8,0x3EC,0x570,0x6A4,0x7D8,0x95C,0xA90,0xC14,0xD48,0xECC],
|
||||
10121:{index:10121,name:"Pythagorean dim. 5th", table:[0x0,0x134,0x2B8,0x3EC,0x570,0x6A4,0x7D8,0x95C,0xA90,0xC14,0xD48,0xECC],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`C${sym.sharp}`,`D${sym.accnull}`,`D${sym.sharp}`,`E${sym.accnull}`,`F${sym.accnull}`,`F${sym.sharp}`,`G${sym.accnull}`,`G${sym.sharp}`,`A${sym.accnull}`,`A${sym.sharp}`,`B${sym.accnull}`]},
|
||||
10122:{index:10122,name:"Pythagorean Augmented Fourth", table:[0x0,0x134,0x2B8,0x3EC,0x570,0x6A4,0x828,0x95C,0xA90,0xC14,0xD48,0xECC],
|
||||
10122:{index:10122,name:"Pythagorean aug. 4th", table:[0x0,0x134,0x2B8,0x3EC,0x570,0x6A4,0x828,0x95C,0xA90,0xC14,0xD48,0xECC],interval:0x1000,
|
||||
sym:[`C${sym.accnull}`,`C${sym.sharp}`,`D${sym.accnull}`,`D${sym.sharp}`,`E${sym.accnull}`,`F${sym.accnull}`,`F${sym.sharp}`,`G${sym.accnull}`,`G${sym.sharp}`,`A${sym.accnull}`,`A${sym.sharp}`,`B${sym.accnull}`]},
|
||||
10123:{index:10123,name:"\u00FC\u00FD\u00FE", table:[0x0,0x184,0x2B8,0x43C,0x570,0x6F4,0x828,0x95C,0xAE0,0xC14,0xD98,0xECC],
|
||||
10123:{index:10123,name:"\u00FC\u00FD\u00FE (shi'er lu)", table:[0x0,0x184,0x2B8,0x43C,0x570,0x6F4,0x828,0x95C,0xAE0,0xC14,0xD98,0xECC],interval:0x1000,
|
||||
sym:[` \u00E0\u00E1`,` \u00E2\u00E3`,` \u00E4\u00E5`,` \u00E6\u00E7`,` \u00E8\u00E9`,` \u00EA\u00EB`,` \u00EC\u00ED`,` \u00EE\u00EF`,` \u00F0\u00F1`,` \u00F2\u00F3`,` \u00F4\u00F5`,` \u00F6\u00F7`]},
|
||||
|
||||
/* non-octave */
|
||||
35130:{index:35130,name:"Equal-Tempered Bohlen-Pierce", table:[0x0,0x1F3,0x3E7,0x5DA,0x7CE,0x9C1,0xBB4,0xDA8,0xF9B,0x118E,0x1382,0x1575,0x1769],interval:0x195C,
|
||||
sym:[`C${sym.accnull}`,`C${sym.sharp}`,`D${sym.accnull}`,`E${sym.accnull}`,`F${sym.accnull}`,`F${sym.sharp}`,`G${sym.accnull}`,`H${sym.accnull}`,`H${sym.sharp}`,`J${sym.accnull}`,`A${sym.accnull}`,`A${sym.sharp}`,`B${sym.accnull}`]},
|
||||
|
||||
|
||||
}
|
||||
|
||||
// check pitchTablePresets integrity
|
||||
function checkPitchTablePresetsIntegrity() {
|
||||
const seenIndices = {}
|
||||
for (const key in pitchTablePresets) {
|
||||
const preset = pitchTablePresets[key]
|
||||
const keyNum = +key
|
||||
if (preset == null) throw Error(`pitchTablePresets[${key}] is null/undefined`)
|
||||
if (typeof preset.index !== 'number') throw Error(`pitchTablePresets[${key}].index is not a number`)
|
||||
if (preset.index !== keyNum) throw Error(`pitchTablePresets[${key}].index (${preset.index}) does not match its key (${key})`)
|
||||
if (seenIndices[preset.index]) throw Error(`duplicate index ${preset.index} in pitchTablePresets`)
|
||||
seenIndices[preset.index] = true
|
||||
if (typeof preset.name !== 'string') throw Error(`pitchTablePresets[${key}].name is not a string`)
|
||||
if (!Array.isArray(preset.table)) throw Error(`pitchTablePresets[${key}].table is not an array`)
|
||||
if (!Array.isArray(preset.sym)) throw Error(`pitchTablePresets[${key}].sym is not an array`)
|
||||
if (preset.table.length !== preset.sym.length) throw Error(`pitchTablePresets[${key}] (${preset.name}): table.length (${preset.table.length}) != sym.length (${preset.sym.length})`)
|
||||
for (let i = 0; i < preset.table.length; i++) {
|
||||
const v = preset.table[i]
|
||||
if (typeof v !== 'number' || !Number.isFinite(v)) throw Error(`pitchTablePresets[${key}] (${preset.name}): table[${i}] is not a finite number`)
|
||||
if (i > 0 && v <= preset.table[i - 1]) throw Error(`pitchTablePresets[${key}] (${preset.name}): table is not strictly ascending at index ${i} (0x${preset.table[i-1].toString(16)} -> 0x${v.toString(16)})`)
|
||||
}
|
||||
for (let i = 0; i < preset.sym.length; i++) {
|
||||
if (typeof preset.sym[i] !== 'string') throw Error(`pitchTablePresets[${key}] (${preset.name}): sym[${i}] is not a string`)
|
||||
}
|
||||
}
|
||||
}
|
||||
checkPitchTablePresetsIntegrity()
|
||||
|
||||
const volEffSym = [sym.volset, sym.volup, sym.voldn, sym.volfineup, sym.volfinedn]
|
||||
const panEffSym = [sym.panset, sym.panri, sym.panle, sym.panfineri, sym.panfinele]
|
||||
@@ -229,24 +273,46 @@ let beatDivSecondary = 16
|
||||
let hasUnsavedChanges = false
|
||||
let patternsOutOfSync = false // in-memory song.patterns has edits not yet pushed to the audio adapter
|
||||
|
||||
// pitchSymLut[pitchInOct] = [symString, octaveOffset]
|
||||
// octaveOffset is 1 when pitchInOct is closer to the next octave's root (wraps up) than to any table entry.
|
||||
// Call rebuildPitchLut() whenever PITCH_PRESET_IDX changes.
|
||||
const pitchSymLut = new Array(0x1000)
|
||||
// Pitch encoding: a 16-bit absolute value with Middle C anchored at 0x5000.
|
||||
// For octave systems (interval == 0x1000) the value decomposes naturally as
|
||||
// (octave << 12) | pitchInOctave. For non-octave systems the "period" (e.g.
|
||||
// the BP tritave at 0x195C) does not align with 4-bit boundaries; the period
|
||||
// index and offset must be computed by integer-divmod against the interval,
|
||||
// using ANCHOR_NOTE / ANCHOR_PERIOD as the fixed reference point.
|
||||
const ANCHOR_NOTE = 0x5000
|
||||
const ANCHOR_PERIOD = 5
|
||||
function decomposeNote(note, interval) {
|
||||
const delta = note - ANCHOR_NOTE
|
||||
const k = Math.floor(delta / interval)
|
||||
return [ANCHOR_PERIOD + k, delta - k * interval]
|
||||
}
|
||||
function composeNote(periodIdx, offset, interval) {
|
||||
return ANCHOR_NOTE + (periodIdx - ANCHOR_PERIOD) * interval + offset
|
||||
}
|
||||
|
||||
// pitchSymLut[offsetInPeriod] = [symString, periodOffset]
|
||||
// periodOffset is 1 when offsetInPeriod is closer to the next period's root
|
||||
// (one `interval` above) than to any table entry — i.e. the note should wrap
|
||||
// up to the first entry of the next period.
|
||||
// Call rebuildPitchLut() whenever PITCH_PRESET_IDX changes; the LUT is sized
|
||||
// to the preset's interval so non-octave tunings (e.g. BP at 0x195C) work.
|
||||
let pitchSymLut = new Array(0x1000)
|
||||
|
||||
function rebuildPitchLut() {
|
||||
const preset = pitchTablePresets[PITCH_PRESET_IDX]
|
||||
if (!preset || preset.table.length === 0) return
|
||||
const table = preset.table
|
||||
const syms = preset.sym
|
||||
for (let p = 0; p < 0x1000; p++) {
|
||||
let best = 0, bestDist = 0x1000
|
||||
const interval = preset.interval
|
||||
if (pitchSymLut.length !== interval) pitchSymLut = new Array(interval)
|
||||
for (let p = 0; p < interval; p++) {
|
||||
let best = 0, bestDist = interval
|
||||
for (let i = 0; i < table.length; i++) {
|
||||
const d = Math.abs(p - table[i])
|
||||
if (d < bestDist) { bestDist = d; best = i }
|
||||
}
|
||||
// Distance to the next octave's root (0x1000) vs nearest table entry.
|
||||
if ((0x1000 - p) < bestDist) {
|
||||
// Distance to the next period's root (one interval up) vs nearest table entry.
|
||||
if ((interval - p) < bestDist) {
|
||||
pitchSymLut[p] = [syms[0], 1]
|
||||
} else {
|
||||
pitchSymLut[p] = [syms[best], 0]
|
||||
@@ -275,17 +341,18 @@ rebuildPitchLut()
|
||||
// real musical fact that it's at fifth-circle distance 5 from tonic and
|
||||
// hence highly tense (cf. Krumhansl's tonal hierarchy: B is the least
|
||||
// stable diatonic note in C, despite sitting a semitone below C).
|
||||
function _cadTension(p, tonic) {
|
||||
function _cadTension(p, tonic, interval) {
|
||||
const FIFTH_PC = 0x95A
|
||||
const TONIC_TOL = 0x40
|
||||
const d = ((p - tonic) % 0x1000 + 0x1000) % 0x1000
|
||||
const cyclic = (d <= 0x800) ? d : (0x1000 - d)
|
||||
const half = interval >>> 1
|
||||
const d = ((p - tonic) % interval + interval) % interval
|
||||
const cyclic = (d <= half) ? d : (interval - d)
|
||||
let bestT = (cyclic <= TONIC_TOL) ? cyclic : Infinity
|
||||
for (let k = -6; k <= 6; k++) {
|
||||
if (k === 0) continue
|
||||
const target = ((k * FIFTH_PC) % 0x1000 + 0x1000) % 0x1000
|
||||
const target = ((k * FIFTH_PC) % interval + interval) % interval
|
||||
let dist = Math.abs(d - target)
|
||||
if (dist > 0x800) dist = 0x1000 - dist
|
||||
if (dist > half) dist = interval - dist
|
||||
const candT = Math.abs(k) * 0x100 + dist
|
||||
if (candT < bestT) bestT = candT
|
||||
}
|
||||
@@ -308,13 +375,14 @@ const _HARM_REFS = [
|
||||
[0xBCB, 3.0], // 5:3 major sixth
|
||||
[0xD3D, 4.0], // 9:5 minor seventh
|
||||
]
|
||||
function _harmonicCost(p, tonic) {
|
||||
const d = ((p - tonic) % 0x1000 + 0x1000) % 0x1000
|
||||
function _harmonicCost(p, tonic, interval) {
|
||||
const half = interval >>> 1
|
||||
const d = ((p - tonic) % interval + interval) % interval
|
||||
let best = Infinity
|
||||
for (let i = 0; i < _HARM_REFS.length; i++) {
|
||||
const ref = _HARM_REFS[i]
|
||||
let dist = Math.abs(d - ref[0])
|
||||
if (dist > 0x800) dist = 0x1000 - dist
|
||||
if (dist > half) dist = interval - dist
|
||||
const cost = ref[1] * dist
|
||||
if (cost < best) best = cost
|
||||
}
|
||||
@@ -358,10 +426,34 @@ function _harmonicCost(p, tonic) {
|
||||
// onto the JI attractor field — "precision during landing".
|
||||
function retuneAllPatterns(newIdx, method) {
|
||||
if (method !== 'delta' && method !== 'cadence' && method !== 'harmonic') method = 'pitch'
|
||||
const preset = pitchTablePresets[newIdx]
|
||||
if (!preset) return
|
||||
const table = preset.table
|
||||
if (table.length > 0) {
|
||||
const newPreset = pitchTablePresets[newIdx]
|
||||
if (!newPreset) return
|
||||
const srcPreset = pitchTablePresets[PITCH_PRESET_IDX]
|
||||
const newTable = newPreset.table
|
||||
const newInterval = newPreset.interval
|
||||
// Tension/harmonic shapes are read out of the SOURCE tuning's modular
|
||||
// space — they describe the composition the user wrote, not the snap
|
||||
// grid we're mapping onto. For octave→octave retunes this collapses to
|
||||
// the original behaviour (both intervals are 0x1000).
|
||||
const srcInterval = srcPreset.interval || 0x1000
|
||||
|
||||
// Yield candidate absolute pitches in the new tuning whose period root
|
||||
// lies within ±1 period of `absRef`. Includes the next period's root
|
||||
// itself so a target that lands just past the top entry can snap up.
|
||||
const forEachCandidate = (absRef, fn) => {
|
||||
const baseK = Math.floor((absRef - ANCHOR_NOTE) / newInterval)
|
||||
for (let dK = -1; dK <= 1; dK++) {
|
||||
const root = ANCHOR_NOTE + (baseK + dK) * newInterval
|
||||
for (let i = 0; i < newTable.length; i++) {
|
||||
const cand = root + newTable[i]
|
||||
if (cand >= 0 && cand <= 0xFFFF) fn(cand)
|
||||
}
|
||||
const nextRoot = root + newInterval
|
||||
if (nextRoot >= 0 && nextRoot <= 0xFFFF) fn(nextRoot)
|
||||
}
|
||||
}
|
||||
|
||||
if (newTable.length > 0) {
|
||||
for (let p = 0; p < song.numPats; p++) {
|
||||
const ptn = song.patterns[p]
|
||||
let prevOrigAbs = -1
|
||||
@@ -372,7 +464,9 @@ function retuneAllPatterns(newIdx, method) {
|
||||
const off = 8 * row
|
||||
const note = ptn[off] | (ptn[off+1] << 8)
|
||||
if (note === 0xFFFF || note === 0xFFFE || note === 0x0000) continue
|
||||
tonic = note & 0xFFF
|
||||
// Use the full absolute pitch as tonic; the modular ops
|
||||
// in _cadTension / _harmonicCost normalise it.
|
||||
tonic = note
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -380,17 +474,14 @@ function retuneAllPatterns(newIdx, method) {
|
||||
const off = 8 * row
|
||||
const note = ptn[off] | (ptn[off+1] << 8)
|
||||
if (note === 0xFFFF || note === 0xFFFE || note === 0x0000) continue
|
||||
let octave = (note >>> 12) & 0xF
|
||||
const pitch = note & 0xFFF
|
||||
const origAbs = (octave << 12) | pitch
|
||||
const origAbs = note
|
||||
let newAbs
|
||||
if ((method === 'delta' || method === 'cadence' || method === 'harmonic') && prevOrigAbs >= 0) {
|
||||
const targetAbs = prevMappedAbs + (origAbs - prevOrigAbs)
|
||||
const baseOc = (targetAbs >> 12)
|
||||
let targetDeltaT = 0, tMappedPrev = 0, lambda = 0
|
||||
if (method === 'cadence') {
|
||||
targetDeltaT = _cadTension(origAbs, tonic) - _cadTension(prevOrigAbs, tonic)
|
||||
tMappedPrev = _cadTension(prevMappedAbs, tonic)
|
||||
targetDeltaT = _cadTension(origAbs, tonic, srcInterval) - _cadTension(prevOrigAbs, tonic, srcInterval)
|
||||
tMappedPrev = _cadTension(prevMappedAbs, tonic, srcInterval)
|
||||
} else if (method === 'harmonic') {
|
||||
let duration = 1
|
||||
for (let r = row + 1; r < ROWS_PER_PAT; r++) {
|
||||
@@ -402,42 +493,27 @@ function retuneAllPatterns(newIdx, method) {
|
||||
lambda = 1 - Math.exp(-(duration - 1) / 4)
|
||||
}
|
||||
let bestAbs = 0, bestScore = Infinity
|
||||
const tryCand = (cand) => {
|
||||
forEachCandidate(targetAbs, (cand) => {
|
||||
const pitchErr = Math.abs(cand - targetAbs)
|
||||
let score = pitchErr
|
||||
if (method === 'cadence') {
|
||||
const candDeltaT = _cadTension(cand, tonic) - tMappedPrev
|
||||
const candDeltaT = _cadTension(cand, tonic, srcInterval) - tMappedPrev
|
||||
score = Math.abs(candDeltaT - targetDeltaT) * 2 + pitchErr
|
||||
} else if (method === 'harmonic') {
|
||||
score = pitchErr + lambda * _harmonicCost(cand, tonic)
|
||||
score = pitchErr + lambda * _harmonicCost(cand, tonic, srcInterval)
|
||||
}
|
||||
if (score < bestScore) { bestScore = score; bestAbs = cand }
|
||||
}
|
||||
for (let dOc = -1; dOc <= 1; dOc++) {
|
||||
const oc = baseOc + dOc
|
||||
if (oc < 0 || oc > 0xF) continue
|
||||
const ocAbs = oc << 12
|
||||
for (let i = 0; i < table.length; i++) tryCand(ocAbs + table[i])
|
||||
// Also consider the next octave's root (0x1000 above
|
||||
// this octave's base) so an interval that lands just
|
||||
// past the top entry can snap up to the octave.
|
||||
if (oc < 0xF) tryCand(ocAbs + 0x1000)
|
||||
}
|
||||
})
|
||||
newAbs = bestAbs
|
||||
} else {
|
||||
let best = 0, bestDist = 0x1000
|
||||
for (let i = 0; i < table.length; i++) {
|
||||
const d = Math.abs(pitch - table[i])
|
||||
if (d < bestDist) { bestDist = d; best = i }
|
||||
}
|
||||
let newPitch, newOctave = octave
|
||||
if ((0x1000 - pitch) < bestDist) {
|
||||
if (newOctave < 0xF) { newOctave += 1; newPitch = 0 }
|
||||
else { newPitch = table[table.length - 1] }
|
||||
} else {
|
||||
newPitch = table[best]
|
||||
}
|
||||
newAbs = (newOctave << 12) | newPitch
|
||||
// Nearest-pitch: snap source absolute pitch to the closest
|
||||
// entry in the new tuning's snap grid.
|
||||
let bestAbs = 0, bestDist = Infinity
|
||||
forEachCandidate(origAbs, (cand) => {
|
||||
const d = Math.abs(cand - origAbs)
|
||||
if (d < bestDist) { bestDist = d; bestAbs = cand }
|
||||
})
|
||||
newAbs = bestAbs
|
||||
}
|
||||
if (newAbs < 0) newAbs = 0
|
||||
if (newAbs > 0xFFFF) newAbs = 0xFFFF
|
||||
@@ -482,9 +558,11 @@ function noteToStr(note) {
|
||||
if (note === 0xFFFF) return sym.middot.repeat(4)
|
||||
if (note === 0xFFFE) return sym.notecut
|
||||
if (note === 0x0000) return sym.keyoff
|
||||
if (pitchTablePresets[PITCH_PRESET_IDX].table.length === 0) return note.hex04()
|
||||
const [s, o] = pitchSymLut[note & 0xFFF]
|
||||
return s + ((note >> 12) - 1 + o).toString(16) // octave 10 -> 'a'
|
||||
const preset = pitchTablePresets[PITCH_PRESET_IDX]
|
||||
if (preset.table.length === 0) return note.hex04()
|
||||
const [period, offset] = decomposeNote(note, preset.interval)
|
||||
const [s, o] = pitchSymLut[offset]
|
||||
return s + (period - 1 + o).toString(16) // period 10 -> 'a'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3318,10 +3396,10 @@ function openRetunePopup() {
|
||||
const methodCycle = ['pitch', 'harmonic', 'delta'/*, 'cadence'*/]
|
||||
let method = 'pitch'
|
||||
|
||||
const pw = 44
|
||||
const listH = Math.min(n, 12)
|
||||
const ph = listH + 6
|
||||
const px = ((SCRW - pw) / 2 | 0) + 1
|
||||
const pw = 42
|
||||
const listH = Math.min(n, 15)
|
||||
const ph = listH + 5
|
||||
const px = ((SCRW - pw) / 2 | 0)
|
||||
const py = ((SCRH - ph) / 2 | 0)
|
||||
const listX = px + 2
|
||||
const listY = py + 3
|
||||
@@ -3340,7 +3418,7 @@ function openRetunePopup() {
|
||||
popup.drawFrame()
|
||||
|
||||
con.move(py + 1, px + 2)
|
||||
con.color_pair(colWHITE, colPopupBack)
|
||||
con.color_pair(colStatus, colPopupBack)
|
||||
print('Select new tuning preset:')
|
||||
|
||||
con.move(py + 2, px + 2)
|
||||
@@ -3396,7 +3474,7 @@ function openRetunePopup() {
|
||||
con.color_pair(colStatus, colPopupBack)
|
||||
print(`Method `)
|
||||
con.color_pair(colVoiceHdr, colPopupBack)
|
||||
print(`q `)
|
||||
print(`Q `)
|
||||
con.color_pair(colStatus, colPopupBack)
|
||||
print(`Cancel`)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -118,7 +118,7 @@ function uploadTaudFile(inFile, songIndex, playhead) {
|
||||
let patBinCompSize = _peekU32LE(filePtr, entryOff + 18)
|
||||
let cueSheetCompSize = _peekU32LE(filePtr, entryOff + 22)
|
||||
|
||||
let bpm = bpmStored + 24
|
||||
let bpm = bpmStored + 25
|
||||
let patsToLoad = numPatsLo | (numPatsHi << 8)
|
||||
|
||||
// -- 6. Decompress + upload patterns --------------------------------------
|
||||
@@ -210,7 +210,7 @@ function captureTrackerDataToFile(outFile) {
|
||||
// -- 3. BPM / tick-rate / volumes from playhead 0 -------------------------
|
||||
let bpm = audio.getBPM(0) || 125
|
||||
let tickRate = audio.getTickRate(0) || 6
|
||||
let bpmStored = (bpm - 24) & 0xFF
|
||||
let bpmStored = (bpm - 25) & 0xFF
|
||||
let songGlobalVolume = audio.getSongGlobalVolume(0)
|
||||
let songMixingVolume = audio.getSongMixingVolume(0)
|
||||
if (songGlobalVolume === undefined || songGlobalVolume === null) songGlobalVolume = 0x80
|
||||
@@ -272,7 +272,7 @@ function captureTrackerDataToFile(outFile) {
|
||||
(songOffset >>> 24) & 0xFF,
|
||||
20, // numVoices
|
||||
numPats & 0xFF, (numPats >>> 8) & 0xFF, // numPatterns Uint16 LE
|
||||
bpmStored, // BPM with −24 bias
|
||||
bpmStored, // BPM with −25 bias
|
||||
tickRate, // initial tick-rate
|
||||
0x00,0xA0, // basenote (0xA000 -- C9)
|
||||
0x00,0xAC,0x02,0x46, // basefreq (8363 Hz)
|
||||
|
||||
@@ -2726,9 +2726,10 @@ prefixes:
|
||||
* Repetition of:
|
||||
Uint8 Notation index (starting from zero) used by songs
|
||||
Uint32 Size of this notation following this field
|
||||
Uint16 Reserved for flags
|
||||
Float32 Interval size (octave system = 2.0f). If you are not using an interval system (which means you are responsible for defining every note expressible), this must be NaN. 0f and Infinity are considered illegal
|
||||
Uint16 Notes between interval MINUS ONE (or octave); 12-TET will have value 11
|
||||
Uint16 Reserved for flags
|
||||
Uint16 Interval size in 4096-TET lattice (octave = 0x1000, tritave = 0x195C). If you are not using an interval system (which means you are responsible for defining every note expressible), this must be 0.
|
||||
Uint16 Reserved for float32 interval size (should it be in 4096-TET which is inexact or frequency multiplier which is exact but difficult to implement?)
|
||||
Uint16 Notes between interval (or octave) MINUS ONE; 12-TET will have value 11
|
||||
Byte[8] Reserved
|
||||
Byte[*] Name, null terminated. Encoding: UTF-8
|
||||
Byte[*] Notation table. 0xFF-separated and null-terminated. Encoding: Taud charset
|
||||
|
||||
@@ -1279,6 +1279,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
private object EffectOp {
|
||||
const val OP_NONE = 0x00
|
||||
const val OP_1 = 0x01
|
||||
const val OP_7 = 0x07
|
||||
const val OP_8 = 0x08
|
||||
const val OP_9 = 0x09
|
||||
const val OP_A = 0x0A
|
||||
@@ -2224,9 +2225,69 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
val patNum = cue.patterns[vi]
|
||||
if (patNum == 0xFFF) continue
|
||||
val patIdx = patNum.coerceIn(0, 4095)
|
||||
val row = playdata[patIdx][ts.rowIndex]
|
||||
val rawRow = playdata[patIdx][ts.rowIndex]
|
||||
val voice = ts.voices[vi]
|
||||
|
||||
// ── Pattern Ditto (effect 7) row-time expansion ──
|
||||
// See TAUD_NOTE_EFFECTS.md §7. Arm the destination range when this row
|
||||
// carries a 7-opcode with a valid argument; then, if the current row
|
||||
// sits inside an active destination block, synthesise an effective cell
|
||||
// that combines the source-block cell with any explicit fields the
|
||||
// composer punched into the destination row.
|
||||
val n = ts.rowIndex
|
||||
val isArmer = (rawRow.effect == EffectOp.OP_7 && rawRow.effectArg != 0)
|
||||
if (isArmer) {
|
||||
val length = (rawRow.effectArg ushr 8) and 0xFF
|
||||
val repeats = rawRow.effectArg and 0xFF
|
||||
if (length > 0 && repeats > 0 && length <= n) {
|
||||
val patLen = (cue.instruction as? PlayInstPatLen)?.rows ?: 64
|
||||
voice.dittoSourceStart = n - length
|
||||
voice.dittoLength = length
|
||||
voice.dittoEndRow = minOf(n + length * repeats - 1, patLen - 1)
|
||||
voice.dittoActive = true
|
||||
}
|
||||
// else: malformed — leave any previously-armed ditto state alone.
|
||||
}
|
||||
|
||||
val dittoArmRow = voice.dittoSourceStart + voice.dittoLength
|
||||
val row: TaudPlayData =
|
||||
if (voice.dittoActive && n in dittoArmRow..voice.dittoEndRow) {
|
||||
val rel = (n - voice.dittoSourceStart) % voice.dittoLength
|
||||
val srcRow = voice.dittoSourceStart + rel
|
||||
val src = playdata[patIdx][srcRow]
|
||||
|
||||
// Vol- / pan-column "no-op" sentinel is SEL_FINE (3) with value 0.
|
||||
val volIsSet = !(rawRow.volumeEff == 3 && rawRow.volume == 0)
|
||||
val panIsSet = !(rawRow.panEff == 3 && rawRow.pan == 0)
|
||||
|
||||
// On the armer row, the 7-opcode is consumed by the marker, so
|
||||
// for effect-column patching purposes the destination is treated
|
||||
// as empty. Source 7-opcodes never propagate (no recursive
|
||||
// expansion).
|
||||
val destOp = if (isArmer) 0 else rawRow.effect
|
||||
val destArg = if (isArmer) 0 else rawRow.effectArg
|
||||
val effOp: Int
|
||||
val effArg: Int
|
||||
when {
|
||||
destOp != 0 -> { effOp = destOp; effArg = destArg }
|
||||
src.effect != EffectOp.OP_7 -> { effOp = src.effect; effArg = src.effectArg }
|
||||
else -> { effOp = 0; effArg = 0 }
|
||||
}
|
||||
|
||||
TaudPlayData(
|
||||
note = if (rawRow.note != 0xFFFF) 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,
|
||||
pan = if (panIsSet) rawRow.pan else src.pan,
|
||||
panEff = if (panIsSet) rawRow.panEff else src.panEff,
|
||||
effect = effOp,
|
||||
effectArg = effArg,
|
||||
)
|
||||
} else {
|
||||
rawRow
|
||||
}
|
||||
|
||||
// Reset per-row transient state.
|
||||
voice.cutAtTick = -1
|
||||
voice.noteDelayTick = -1
|
||||
@@ -2347,6 +2408,16 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
private fun applyEffectRow(ts: TrackerState, playhead: Playhead, voice: Voice, vi: Int, op: Int, rawArg: Int) {
|
||||
when (op) {
|
||||
EffectOp.OP_NONE -> {}
|
||||
EffectOp.OP_7 -> {
|
||||
// 7 $xxyy — Pattern Ditto. See TAUD_NOTE_EFFECTS.md §7.
|
||||
// The opcode is a marker only; the row-time expansion in
|
||||
// [applyTrackerRow] consumes the armer cell and substitutes the
|
||||
// effective row from the source block, so by the time dispatch
|
||||
// reaches here either (a) the cell was an armer and we already
|
||||
// overwrote the synthesised row's effect to 0 / source effect,
|
||||
// or (b) we hit a malformed 7-cell (length == 0 or repeats == 0
|
||||
// or length > N) — both cases are no-ops at dispatch time.
|
||||
}
|
||||
EffectOp.OP_1 -> {
|
||||
// 1 $xx00 — Global behaviour flags byte in the high byte (see TAUD_NOTE_EFFECTS.md §1).
|
||||
// bits 0-1 (ff): 0=linear pitch, 1=Amiga period, 2=linear frequency (Hz/tick),
|
||||
@@ -3054,11 +3125,18 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
playhead.position = ts.cuePos
|
||||
}
|
||||
|
||||
// Per TAUD_NOTE_EFFECTS.md §S$Bx00: on pattern change reset loop_start_row and loop_count.
|
||||
// Per-pattern voice state reset, called on every cue advance (B / C / natural end).
|
||||
// - S$Bx pattern-loop counters (TAUD_NOTE_EFFECTS.md §S$Bx00).
|
||||
// - Pattern-ditto (effect 7) destination range — the source block lives in the
|
||||
// pattern we are leaving and must not bleed into the next one (§7).
|
||||
private fun resetPatternLoopState(ts: TrackerState) {
|
||||
for (voice in ts.voices) {
|
||||
voice.loopStartRow = 0
|
||||
voice.loopCount = 0
|
||||
voice.dittoActive = false
|
||||
voice.dittoSourceStart = 0
|
||||
voice.dittoLength = 0
|
||||
voice.dittoEndRow = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3599,6 +3677,17 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
var loopStartRow = 0
|
||||
var loopCount = 0
|
||||
|
||||
// Pattern ditto (effect 7) — per-channel state. See TAUD_NOTE_EFFECTS.md §7.
|
||||
// dittoActive is the master gate; while true, rows in
|
||||
// [dittoSourceStart + dittoLength .. dittoEndRow] are expanded by copying
|
||||
// the cells from the source block (dittoSourceStart .. dittoSourceStart +
|
||||
// dittoLength − 1) and patching in any non-empty fields from the raw
|
||||
// destination cell. All four reset on cue advance (B / C / natural end).
|
||||
var dittoActive = false
|
||||
var dittoSourceStart = 0
|
||||
var dittoLength = 0
|
||||
var dittoEndRow = 0
|
||||
|
||||
// Tempo slide (T $00xy) — per-channel because T is a per-channel effect, but we apply globally via playhead.
|
||||
var tempoSlideDir = 0 // 0 = none, -1 = down, +1 = up
|
||||
var tempoSlideAmount = 0
|
||||
@@ -3841,6 +3930,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
it.glissandoOn = false
|
||||
it.loopStartRow = 0
|
||||
it.loopCount = 0
|
||||
it.dittoActive = false
|
||||
it.dittoSourceStart = 0
|
||||
it.dittoLength = 0
|
||||
it.dittoEndRow = 0
|
||||
it.funkSpeed = 0
|
||||
it.funkAccumulator = 0
|
||||
it.funkWritePos = 0
|
||||
|
||||
Reference in New Issue
Block a user