mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
nearest-delta retuning
This commit is contained in:
4
2taud.sh
4
2taud.sh
@@ -1,8 +1,12 @@
|
|||||||
#!/usr/bin/env fish
|
#!/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 *.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 *.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 *.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 *.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
|
||||||
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
|
||||||
|
|||||||
@@ -255,39 +255,84 @@ function rebuildPitchLut() {
|
|||||||
}
|
}
|
||||||
rebuildPitchLut()
|
rebuildPitchLut()
|
||||||
|
|
||||||
// Remap every note in every pattern of the current song so that the lower
|
// Remap every note in every pattern of the current song to `newIdx`'s pitch
|
||||||
// 12 bits snap to the nearest entry in `newIdx`'s pitch table, then switch
|
// table, then switch PITCH_PRESET_IDX. Special note values (empty/cut/keyoff)
|
||||||
// PITCH_PRESET_IDX. Special note values (empty/cut/keyoff) are left alone.
|
// 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).
|
// Two mapping methods are supported:
|
||||||
function retuneAllPatterns(newIdx) {
|
// '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.
|
||||||
|
function retuneAllPatterns(newIdx, method) {
|
||||||
|
method = (method === 'delta') ? 'delta' : 'pitch'
|
||||||
const preset = pitchTablePresets[newIdx]
|
const preset = pitchTablePresets[newIdx]
|
||||||
if (!preset) return
|
if (!preset) return
|
||||||
const table = preset.table
|
const table = preset.table
|
||||||
if (table.length > 0) {
|
if (table.length > 0) {
|
||||||
for (let p = 0; p < song.numPats; p++) {
|
for (let p = 0; p < song.numPats; p++) {
|
||||||
const ptn = song.patterns[p]
|
const ptn = song.patterns[p]
|
||||||
|
let prevOrigAbs = -1
|
||||||
|
let prevMappedAbs = 0
|
||||||
for (let row = 0; row < ROWS_PER_PAT; row++) {
|
for (let row = 0; row < ROWS_PER_PAT; row++) {
|
||||||
const off = 8 * row
|
const off = 8 * row
|
||||||
const note = ptn[off] | (ptn[off+1] << 8)
|
const note = ptn[off] | (ptn[off+1] << 8)
|
||||||
if (note === 0xFFFF || note === 0xFFFE || note === 0x0000) continue
|
if (note === 0xFFFF || note === 0xFFFE || note === 0x0000) continue
|
||||||
let octave = (note >>> 12) & 0xF
|
let octave = (note >>> 12) & 0xF
|
||||||
const pitch = note & 0xFFF
|
const pitch = note & 0xFFF
|
||||||
|
const origAbs = (octave << 12) | pitch
|
||||||
|
let newAbs
|
||||||
|
if (method === 'delta' && prevOrigAbs >= 0) {
|
||||||
|
const targetAbs = prevMappedAbs + (origAbs - prevOrigAbs)
|
||||||
|
const baseOc = (targetAbs >> 12)
|
||||||
|
let bestAbs = 0, bestDist = Infinity
|
||||||
|
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++) {
|
||||||
|
const cand = ocAbs + table[i]
|
||||||
|
const d = Math.abs(cand - targetAbs)
|
||||||
|
if (d < bestDist) { bestDist = d; bestAbs = cand }
|
||||||
|
}
|
||||||
|
// 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) {
|
||||||
|
const cand = ocAbs + 0x1000
|
||||||
|
const d = Math.abs(cand - targetAbs)
|
||||||
|
if (d < bestDist) { bestDist = d; bestAbs = cand }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newAbs = bestAbs
|
||||||
|
} else {
|
||||||
let best = 0, bestDist = 0x1000
|
let best = 0, bestDist = 0x1000
|
||||||
for (let i = 0; i < table.length; i++) {
|
for (let i = 0; i < table.length; i++) {
|
||||||
const d = Math.abs(pitch - table[i])
|
const d = Math.abs(pitch - table[i])
|
||||||
if (d < bestDist) { bestDist = d; best = i }
|
if (d < bestDist) { bestDist = d; best = i }
|
||||||
}
|
}
|
||||||
let newPitch
|
let newPitch, newOctave = octave
|
||||||
if ((0x1000 - pitch) < bestDist) {
|
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[table.length - 1] }
|
||||||
} else {
|
} else {
|
||||||
newPitch = table[best]
|
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] = newNote & 0xFF
|
||||||
ptn[off+1] = (newNote >>> 8) & 0xFF
|
ptn[off+1] = (newNote >>> 8) & 0xFF
|
||||||
|
prevOrigAbs = origAbs
|
||||||
|
prevMappedAbs = newAbs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hasUnsavedChanges = true
|
hasUnsavedChanges = true
|
||||||
@@ -3151,13 +3196,16 @@ function openRetunePopup() {
|
|||||||
const entries = Object.values(pitchTablePresets).sort((a, b) => a.index - b.index)
|
const entries = Object.values(pitchTablePresets).sort((a, b) => a.index - b.index)
|
||||||
const n = entries.length
|
const n = entries.length
|
||||||
|
|
||||||
|
const methodLabels = { pitch: 'Nearest-note', delta: 'Nearest-delta' }
|
||||||
|
let method = 'pitch'
|
||||||
|
|
||||||
const pw = 44
|
const pw = 44
|
||||||
const listH = Math.min(n, 12)
|
const listH = Math.min(n, 12)
|
||||||
const ph = listH + 5
|
const ph = listH + 6
|
||||||
const px = ((SCRW - pw) / 2 | 0) + 1
|
const px = ((SCRW - pw) / 2 | 0) + 1
|
||||||
const py = ((SCRH - ph) / 2 | 0)
|
const py = ((SCRH - ph) / 2 | 0)
|
||||||
const listX = px + 2
|
const listX = px + 2
|
||||||
const listY = py + 2
|
const listY = py + 3
|
||||||
const listW = pw - 4
|
const listW = pw - 4
|
||||||
|
|
||||||
const popup = new win.WindowObject(px, py, pw, ph, ()=>{}, ()=>{}, 'Retune', popupDrawFrame)
|
const popup = new win.WindowObject(px, py, pw, ph, ()=>{}, ()=>{}, 'Retune', popupDrawFrame)
|
||||||
@@ -3176,6 +3224,13 @@ function openRetunePopup() {
|
|||||||
con.color_pair(colWHITE, colPopupBack)
|
con.color_pair(colWHITE, colPopupBack)
|
||||||
print('Select new tuning preset:')
|
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++) {
|
for (let r = 0; r < listH; r++) {
|
||||||
const idx = scroll + r
|
const idx = scroll + r
|
||||||
con.move(listY + r, listX)
|
con.move(listY + r, listX)
|
||||||
@@ -3214,11 +3269,15 @@ function openRetunePopup() {
|
|||||||
con.color_pair(colStatus, colPopupBack)
|
con.color_pair(colStatus, colPopupBack)
|
||||||
print(`Sel `)
|
print(`Sel `)
|
||||||
con.color_pair(colVoiceHdr, colPopupBack)
|
con.color_pair(colVoiceHdr, colPopupBack)
|
||||||
print(`en `)
|
print(`ent `)
|
||||||
con.color_pair(colStatus, colPopupBack)
|
con.color_pair(colStatus, colPopupBack)
|
||||||
print(`OK `)
|
print(`OK `)
|
||||||
con.color_pair(colVoiceHdr, colPopupBack)
|
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)
|
con.color_pair(colStatus, colPopupBack)
|
||||||
print(`Cancel`)
|
print(`Cancel`)
|
||||||
|
|
||||||
@@ -3239,6 +3298,10 @@ function openRetunePopup() {
|
|||||||
|
|
||||||
if (ks === 'Q') { done = true }
|
if (ks === 'Q') { done = true }
|
||||||
else if (ks === '\n') { confirmed = true; done = true }
|
else if (ks === '\n') { confirmed = true; done = true }
|
||||||
|
else if (ks === 'M' || ks === 'm') {
|
||||||
|
method = (method === 'pitch') ? 'delta' : 'pitch'
|
||||||
|
repaint()
|
||||||
|
}
|
||||||
else if (ks === '<UP>') {
|
else if (ks === '<UP>') {
|
||||||
if (sel > 0) {
|
if (sel > 0) {
|
||||||
sel--
|
sel--
|
||||||
@@ -3271,7 +3334,7 @@ function openRetunePopup() {
|
|||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
const target = entries[sel]
|
const target = entries[sel]
|
||||||
if (target && target.index !== PITCH_PRESET_IDX) {
|
if (target && target.index !== PITCH_PRESET_IDX) {
|
||||||
retuneAllPatterns(target.index)
|
retuneAllPatterns(target.index, method)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user