mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
Compare commits
4 Commits
4ea9ade060
...
7d89605302
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d89605302 | ||
|
|
11bc1ca125 | ||
|
|
6a72a81198 | ||
|
|
8380d1e845 |
4
2taud.sh
4
2taud.sh
@@ -1,8 +1,12 @@
|
||||
#!/usr/bin/env fish
|
||||
|
||||
for f in *.mod; python3 mod2taud.py $f assets/disk0/home/music/(basename $f .mod).taud; end
|
||||
for f in *.MOD; python3 mod2taud.py $f assets/disk0/home/music/(basename $f .MOD).taud; end
|
||||
for f in *.s3m; python3 s3m2taud.py $f assets/disk0/home/music/(basename $f .s3m).taud; end
|
||||
for f in *.S3M; python3 s3m2taud.py $f assets/disk0/home/music/(basename $f .S3M).taud; end
|
||||
for f in *.it; python3 it2taud.py $f assets/disk0/home/music/(basename $f .it).taud; end
|
||||
for f in *.IT; python3 it2taud.py $f assets/disk0/home/music/(basename $f .IT).taud; end
|
||||
for f in *.xm; python3 xm2taud.py $f assets/disk0/home/music/(basename $f .xm).taud; end
|
||||
for f in *.XM; python3 xm2taud.py $f assets/disk0/home/music/(basename $f .XM).taud; end
|
||||
for f in *.mon; python3 mon2taud.py $f assets/disk0/home/music/(basename $f .mon).taud; end
|
||||
for f in *.MON; python3 mon2taud.py $f assets/disk0/home/music/(basename $f .MON).taud; end
|
||||
|
||||
@@ -191,9 +191,9 @@ sym:[`C${sym.accnull}`,`C${sym.demisharp}`,`C${sym.sharp}`,`D${sym.demiflat}`,`D
|
||||
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 Microtonal 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],
|
||||
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],
|
||||
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],
|
||||
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}`]},
|
||||
@@ -255,39 +255,197 @@ function rebuildPitchLut() {
|
||||
}
|
||||
rebuildPitchLut()
|
||||
|
||||
// Remap every note in every pattern of the current song so that the lower
|
||||
// 12 bits snap to the nearest entry in `newIdx`'s pitch table, then switch
|
||||
// PITCH_PRESET_IDX. Special note values (empty/cut/keyoff) are left alone.
|
||||
// Pitches closer to the next octave's root (0x1000) than to any table entry
|
||||
// wrap up by one octave (mirrors rebuildPitchLut's octaveOffset logic).
|
||||
function retuneAllPatterns(newIdx) {
|
||||
// Tonal-tension function used by the 'cadence' retune method. Implements
|
||||
// the tonal-distance term D_tonic from cadential_motion.md §3-§4 by locating
|
||||
// each pitch in fifth-circle space relative to `tonic`. The abstract 3:2
|
||||
// fifth (0x95A in 0x1000-per-octave units, ≈ 702 cents) is used as the
|
||||
// fifth-circle generator, which is tuning-agnostic — the same landscape
|
||||
// applies whether the candidate sits in 5-TET, 12-TET, 22-TET, etc.
|
||||
//
|
||||
// For each integer k in [-6, 6], target_k = (k * 0x95A) mod 0x1000 is the
|
||||
// k-th fifth-stack position above the tonic (in pitch-class space). Tension
|
||||
// = |k|*0x100 + |d - target_k|_cyclic, so well-tuned fifth-circle positions
|
||||
// get low values: tonic 0, P5/P4 ≈ 0x105, M2/m7 ≈ 0x209, M6/m3 ≈ 0x30E,
|
||||
// M3/m6 ≈ 0x413, M7/m2 ≈ 0x517, tritone ≈ 0x61C. Pitches that don't sit on
|
||||
// any fifth-stack position degrade gracefully via the residual term.
|
||||
//
|
||||
// The k=0 path is gated to a narrow tonic neighbourhood (TONIC_TOL ≈ 30c).
|
||||
// Otherwise a leading tone would score as "very close to tonic in pitch-
|
||||
// class space" and pick up an artificially low tension via k=0, masking the
|
||||
// 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) {
|
||||
const FIFTH_PC = 0x95A
|
||||
const TONIC_TOL = 0x40
|
||||
const d = ((p - tonic) % 0x1000 + 0x1000) % 0x1000
|
||||
const cyclic = (d <= 0x800) ? d : (0x1000 - 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
|
||||
let dist = Math.abs(d - target)
|
||||
if (dist > 0x800) dist = 0x1000 - dist
|
||||
const candT = Math.abs(k) * 0x100 + dist
|
||||
if (candT < bestT) bestT = candT
|
||||
}
|
||||
return bestT
|
||||
}
|
||||
|
||||
// Just-intonation reference ratios (in 0x1000-per-octave units) and pull
|
||||
// weights used as the harmonic attractor field A(P) for the 'harmonic'
|
||||
// retune method (see cadence_aware_nearest_harmonic.md §4A). Lower weight
|
||||
// = simpler ratio = stronger pull. Cost of a candidate is the minimum
|
||||
// weight*distance across all references.
|
||||
const _HARM_REFS = [
|
||||
[0, 1.0], // 1:1 unison / 2:1 octave
|
||||
[0x1D2, 4.0], // 9:8 major tone
|
||||
[0x435, 3.0], // 6:5 minor third
|
||||
[0x527, 3.0], // 5:4 major third
|
||||
[0x6A4, 2.0], // 4:3 perfect fourth
|
||||
[0x95B, 2.0], // 3:2 perfect fifth
|
||||
[0xAB7, 3.0], // 8:5 minor sixth
|
||||
[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
|
||||
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
|
||||
const cost = ref[1] * dist
|
||||
if (cost < best) best = cost
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
// Remap every note in every pattern of the current song to `newIdx`'s pitch
|
||||
// table, then switch PITCH_PRESET_IDX. Special note values (empty/cut/keyoff)
|
||||
// are left alone.
|
||||
//
|
||||
// Four mapping methods are supported:
|
||||
// 'pitch' (nearest-note) — each note's lower 12 bits snap to the closest
|
||||
// entry in the new table. Pitches closer to the next octave's root
|
||||
// (0x1000) than to any table entry wrap up by one octave (mirrors
|
||||
// rebuildPitchLut's octaveOffset logic).
|
||||
// 'delta' (nearest-delta) — per pattern, the first non-empty note uses the
|
||||
// nearest-pitch rule; each subsequent note is chosen so that the
|
||||
// interval from the previously mapped note is closest to the interval
|
||||
// between the corresponding original notes. Candidates are drawn from
|
||||
// the table across adjacent octaves so the mapping can cross octave
|
||||
// boundaries naturally.
|
||||
// 'cadence' (nearest-cadence) — per pattern, the first non-empty note's
|
||||
// pitch class is taken as the tonic and the first note uses the
|
||||
// nearest-pitch rule. Each subsequent note is chosen so that the
|
||||
// change in tonal tension (see _cadTension) from the previously
|
||||
// mapped note matches the change in the original sequence, with raw
|
||||
// pitch displacement as a tiebreaker. This preserves cadential
|
||||
// trajectories — V→I-style descents stay V→I-style — rather than
|
||||
// absolute pitch positions or raw intervals, mirroring the framing in
|
||||
// cadential_motion.md §2 (motion along -∇T) and §9 (trajectories
|
||||
// carry cadentiality better than coordinates).
|
||||
// 'harmonic' (cadence-aware nearest-harmonic) — implements
|
||||
// P_n = P_{n-1} + Q(Δ_n) + λ_n A(P_n) from
|
||||
// cadence_aware_nearest_harmonic.md §1. Per pattern, the first
|
||||
// non-empty note's pitch class is taken as the tonic. Each subsequent
|
||||
// note is scored as pitchErr + λ_n * harmonicCost where λ_n
|
||||
// = 1 − exp(−(duration−1)/4), with duration measured in rows until
|
||||
// the next event in the (still-original) row sequence. Short notes
|
||||
// get λ ≈ 0 and behave like nearest-delta — "freedom during travel"
|
||||
// (§10) — while sustained / pattern-end notes approach λ → 1 and lock
|
||||
// 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) {
|
||||
for (let p = 0; p < song.numPats; p++) {
|
||||
const ptn = song.patterns[p]
|
||||
let prevOrigAbs = -1
|
||||
let prevMappedAbs = 0
|
||||
let tonic = 0
|
||||
if (method === 'cadence' || method === 'harmonic') {
|
||||
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
|
||||
tonic = note & 0xFFF
|
||||
break
|
||||
}
|
||||
}
|
||||
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
|
||||
let octave = (note >>> 12) & 0xF
|
||||
const pitch = note & 0xFFF
|
||||
const origAbs = (octave << 12) | pitch
|
||||
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)
|
||||
} else if (method === 'harmonic') {
|
||||
let duration = 1
|
||||
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
|
||||
duration++
|
||||
}
|
||||
lambda = 1 - Math.exp(-(duration - 1) / 4)
|
||||
}
|
||||
let bestAbs = 0, bestScore = Infinity
|
||||
const tryCand = (cand) => {
|
||||
const pitchErr = Math.abs(cand - targetAbs)
|
||||
let score = pitchErr
|
||||
if (method === 'cadence') {
|
||||
const candDeltaT = _cadTension(cand, tonic) - tMappedPrev
|
||||
score = Math.abs(candDeltaT - targetDeltaT) * 2 + pitchErr
|
||||
} else if (method === 'harmonic') {
|
||||
score = pitchErr + lambda * _harmonicCost(cand, tonic)
|
||||
}
|
||||
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
|
||||
let newPitch, newOctave = octave
|
||||
if ((0x1000 - pitch) < bestDist) {
|
||||
if (octave < 0xF) { octave += 1; newPitch = 0 }
|
||||
if (newOctave < 0xF) { newOctave += 1; newPitch = 0 }
|
||||
else { newPitch = table[table.length - 1] }
|
||||
} else {
|
||||
newPitch = table[best]
|
||||
}
|
||||
const newNote = ((octave & 0xF) << 12) | (newPitch & 0xFFF)
|
||||
newAbs = (newOctave << 12) | newPitch
|
||||
}
|
||||
if (newAbs < 0) newAbs = 0
|
||||
if (newAbs > 0xFFFF) newAbs = 0xFFFF
|
||||
const newNote = newAbs & 0xFFFF
|
||||
ptn[off] = newNote & 0xFF
|
||||
ptn[off+1] = (newNote >>> 8) & 0xFF
|
||||
prevOrigAbs = origAbs
|
||||
prevMappedAbs = newAbs
|
||||
}
|
||||
}
|
||||
hasUnsavedChanges = true
|
||||
@@ -3151,13 +3309,22 @@ function openRetunePopup() {
|
||||
const entries = Object.values(pitchTablePresets).sort((a, b) => a.index - b.index)
|
||||
const n = entries.length
|
||||
|
||||
const methodLabels = {
|
||||
pitch: 'Nearest-note',
|
||||
delta: 'Nearest-delta',
|
||||
cadence: 'Nearest-cadence',
|
||||
harmonic: 'Nearest-harmonic', // this thing is cadence-aware (hopefully)
|
||||
}
|
||||
const methodCycle = ['pitch', 'harmonic', 'delta'/*, 'cadence'*/]
|
||||
let method = 'pitch'
|
||||
|
||||
const pw = 44
|
||||
const listH = Math.min(n, 12)
|
||||
const ph = listH + 5
|
||||
const ph = listH + 6
|
||||
const px = ((SCRW - pw) / 2 | 0) + 1
|
||||
const py = ((SCRH - ph) / 2 | 0)
|
||||
const listX = px + 2
|
||||
const listY = py + 2
|
||||
const listY = py + 3
|
||||
const listW = pw - 4
|
||||
|
||||
const popup = new win.WindowObject(px, py, pw, ph, ()=>{}, ()=>{}, 'Retune', popupDrawFrame)
|
||||
@@ -3176,6 +3343,13 @@ function openRetunePopup() {
|
||||
con.color_pair(colWHITE, colPopupBack)
|
||||
print('Select new tuning preset:')
|
||||
|
||||
con.move(py + 2, px + 2)
|
||||
con.color_pair(colStatus, colPopupBack)
|
||||
print('Method: ')
|
||||
con.color_pair(colWHITE, colPopupBack)
|
||||
const mLabel = methodLabels[method]
|
||||
print(mLabel.padEnd(listW - 8))
|
||||
|
||||
for (let r = 0; r < listH; r++) {
|
||||
const idx = scroll + r
|
||||
con.move(listY + r, listX)
|
||||
@@ -3214,11 +3388,15 @@ function openRetunePopup() {
|
||||
con.color_pair(colStatus, colPopupBack)
|
||||
print(`Sel `)
|
||||
con.color_pair(colVoiceHdr, colPopupBack)
|
||||
print(`en `)
|
||||
print(`ent `)
|
||||
con.color_pair(colStatus, colPopupBack)
|
||||
print(`OK `)
|
||||
con.color_pair(colVoiceHdr, colPopupBack)
|
||||
print(`Q `)
|
||||
print(`m `)
|
||||
con.color_pair(colStatus, colPopupBack)
|
||||
print(`Method `)
|
||||
con.color_pair(colVoiceHdr, colPopupBack)
|
||||
print(`q `)
|
||||
con.color_pair(colStatus, colPopupBack)
|
||||
print(`Cancel`)
|
||||
|
||||
@@ -3239,6 +3417,10 @@ function openRetunePopup() {
|
||||
|
||||
if (ks === 'Q') { done = true }
|
||||
else if (ks === '\n') { confirmed = true; done = true }
|
||||
else if (ks === 'M' || ks === 'm') {
|
||||
method = methodCycle[(methodCycle.indexOf(method) + 1) % methodCycle.length]
|
||||
repaint()
|
||||
}
|
||||
else if (ks === '<UP>') {
|
||||
if (sel > 0) {
|
||||
sel--
|
||||
@@ -3271,7 +3453,7 @@ function openRetunePopup() {
|
||||
if (confirmed) {
|
||||
const target = entries[sel]
|
||||
if (target && target.index !== PITCH_PRESET_IDX) {
|
||||
retuneAllPatterns(target.index)
|
||||
retuneAllPatterns(target.index, method)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user