From 80c26c6b35a48a203ff915291040c739671a5d68 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Fri, 1 May 2026 01:35:52 +0900 Subject: [PATCH] taud: 12 envelope nodes; taut proj tab --- assets/disk0/tvdos/bin/taut.js | 129 ++++++++++++++++-- assets/disk0/tvdos/bin/tautfont.kra | 4 +- assets/disk0/tvdos/bin/tautfont_high.chr | Bin 1920 -> 1920 bytes assets/disk0/tvdos/include/taud.mjs | 12 +- terranmon.txt | 5 +- .../net/torvald/tsvm/AudioJSR223Delegate.kt | 8 ++ .../torvald/tsvm/peripheral/AudioAdapter.kt | 42 +++--- 7 files changed, 158 insertions(+), 42 deletions(-) diff --git a/assets/disk0/tvdos/bin/taut.js b/assets/disk0/tvdos/bin/taut.js index 97f03fd..3ffba58 100644 --- a/assets/disk0/tvdos/bin/taut.js +++ b/assets/disk0/tvdos/bin/taut.js @@ -196,7 +196,7 @@ sym:[`C${sym.accnull}`,`C${sym.sharp}`,`D${sym.accnull}`,`D${sym.sharp}`,`E${sym 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], 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:"Shi'er lu", table:[0x0,0x184,0x2B8,0x43C,0x570,0x6F4,0x828,0x95C,0xAE0,0xC14,0xD98,0xECC], +10123:{index:10123,name:"\u00FC\u00FD\u00FE", table:[0x0,0x184,0x2B8,0x43C,0x570,0x6F4,0x828,0x95C,0xAE0,0xC14,0xD98,0xECC], 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`]}, @@ -218,6 +218,7 @@ const colBackPtn = 255 let PITCH_PRESET_IDX = 240 // TODO read from the Project Data section of the .taud let beatDivPrimary = 4 // TODO read from the Project Data section of the .taud let beatDivSecondary = 16 +let hasUnsavedChanges = false // pitchSymLut[pitchInOct] = [symString, octaveOffset] // octaveOffset is 1 when pitchInOct is closer to the next octave's root (wraps up) than to any table entry. @@ -850,18 +851,22 @@ function drawControlHint() { [`\u008428u\u008429u`,'Nav'], [`Pg\u008418u`,'Cue'], ['sep'], - ['WER','ViewMode'], + ['WER','View'], + ['sep'], + ['Sp','Edit'], ['sep'], ['m','Mute'], ['s','Solo'], ['sep'], - ['Tab','Panel'], + ['Tab','Panel'] // ['sep'], // ['q','Quit'], ] let hintElemOrders = [ [`\u008428u\u008429u`,'Nav'], [`Ent`,'Go to cue'], + ['sep'], + ['Sp','Edit'], ['sep'], ['Tab','Panel'], // ['sep'], @@ -871,14 +876,84 @@ function drawControlHint() { let hintElemPatterns = [ [`\u008428u\u008429u`,'Nav'], [`Pg\u008418u`,'Ptn'], + ['sep'], + ['Sp','Edit'], ['sep'], ['Tab','Panel'], // ['sep'], // ['q','Quit'], ] + let hintElemEditNoteValue = [ // only enabled in viewmode 'E' or in pattern editor + [`\u008428u\u008429u`,'Nav'], + [`Pg\u008418u`,'Cue'], + ['sep'], + [`A${sym.doubledot}G`,'Note'], + [`0${sym.doubledot}9`,'Oct'], + ['[]',`Tone\u008418u`], + ['sep'], + ['#',sym.sharp], + ['@','Acc'], + ['sep'], + ['=','KOff'], + ['^','KCut'], +// ['sep'], +// ['Sp','ExitEdit'], + ] + let hintElemEditInstValue = [ + [`\u008428u\u008429u`,'Nav'], + [`Pg\u008418u`,'Cue'], + ['sep'], + [`0${sym.doubledot}9 A${sym.doubledot}F`,'Instrument'], + ['sep'], + ['Sp','ExitEdit'], + ] + let hintElemEditVolEff = [ + [`\u008428u\u008429u`,'Nav'], + [`Pg\u008418u`,'Cue'], + ['sep'], + ['h','Set'], + ['j','SlideDn'], + ['k','SlideUp'], + ['u','FineDn'], + ['i','FineUp'], + [`0${sym.doubledot}9 A${sym.doubledot}F`,'Val'], +// ['sep'], +// ['Sp','ExitEdit'], + ] + let hintElemEditPanEff = [ + [`\u008428u\u008429u`,'Nav'], + [`Pg\u008418u`,'Cue'], + ['sep'], + ['h','Set'], + ['j','SlideL'], + ['k','SlideR'], + ['u','FineL'], + ['i','FineR'], + [`0${sym.doubledot}9 A${sym.doubledot}F`,'Val'], +// ['sep'], +// ['Sp','ExitEdit'], + ] + let hintElemEditFxSym = [ + [`\u008428u\u008429u`,'Nav'], + [`Pg\u008418u`,'Cue'], + ['sep'], + [`0${sym.doubledot}9 A${sym.doubledot}F`,`FxSym`], + ['sep'], + ['Sp','ExitEdit'], + ] + let hintElemEditFxVal = [ + [`\u008428u\u008429u`,'Nav'], + [`Pg\u008418u`,'Cue'], + ['sep'], + [`0${sym.doubledot}9 A${sym.doubledot}F`,`FxVal`], + ['sep'], + ['Sp','ExitEdit'], + ] + const hintElemExternal = [['Tab','Panel']] let hintElems = [hintElemTimeline, hintElemOrders, hintElemPatterns, hintElemExternal, hintElemExternal, hintElemExternal, hintElemExternal] + let hintElemPat = [hintElemEditNoteValue, hintElemEditInstValue, hintElemEditVolEff, hintElemEditPanEff, hintElemEditFxSym, hintElemEditFxVal] // erase current line con.move(SCRH, 1) @@ -1837,9 +1912,36 @@ function makeExternalPanelDraw(progName) { function drawProjectContents(wo) { fillLine(PTNVIEW_OFFSET_Y - 1, colVoiceHdr, 255) for (let y = PTNVIEW_OFFSET_Y; y < SCRH; y++) fillLine(y, colBackPtn, 255) - con.move(PTNVIEW_OFFSET_Y + 2, 3) - con.color_pair(colStatus, 255) - print('[Project settings — not yet implemented]') + + let mixerflag = initialTrackerMixerflags + let flagstrbuf = '' + let flagstr = [ + ['Linear pan','Equal-energy pan'], + ['Linear tone','Amiga tone'], + ] + for (let i = 0; i < flagstr.length; i++) { + let s = flagstr[i][(mixerflag >>> i) & 1 != 0] + if (i > 0) flagstrbuf += ', '; + flagstrbuf += s + } + + + let projMeta = { + Filename: fullPathObj.string.split('\\').last(), + Patterns: `${song.numPats}/4095 ($${song.numPats.hex03()})`, + Cues: `${song.lastActiveCue}/1024 ($${song.lastActiveCue.hex03()})`, + Notation: pitchTablePresets[PITCH_PRESET_IDX].name, + Flags: `${flagstrbuf} ($${mixerflag.hex02()})`, + } + + Object.entries(projMeta).forEach(([key, value], index) => { + con.move(PTNVIEW_OFFSET_Y + index, 2) + con.color_pair(colStatus, 255); print(key) + con.move(PTNVIEW_OFFSET_Y + index, 12) + con.color_pair(colVoiceHdr, colBLACK); print(value) + }) + + con.color_pair(colStatus, 255) // reset colour } function externalPanelInput(wo, event) {} @@ -2148,7 +2250,7 @@ function drawGotoPopup(popup, buf) { const promptStr = prompts[currentPanel] || 'Number:' con.move(popup.y + 2, popup.x + 2) - con.color_pair(colStatus, colPopupBack) + con.color_pair(colWHITE, colPopupBack) print(promptStr + ' ') con.color_pair(230, 240) print('[' + buf.padEnd(3, '_') + ']') @@ -2171,8 +2273,8 @@ function applyGoto(num) { } function openConfirmQuit() { - const pw = 25 - const ph = 5 + const pw = 25 + hasUnsavedChanges * 4 + const ph = 5 + hasUnsavedChanges const px = ((SCRW - pw) / 2 | 0) + 1 const py = ((SCRH - ph) / 2 | 0) @@ -2184,11 +2286,17 @@ function openConfirmQuit() { popup.drawFrame() con.move(py + 2, px + 2) - con.color_pair(colStatus, colPopupBack) + con.color_pair(colWHITE, colPopupBack) print('Exit Microtone? ') con.color_pair(230, 240) print('[Y/N]') + if (hasUnsavedChanges) { + con.move(py + 3, px + 2) + con.color_pair(colWHITE, colPopupBack) + print('You have unsaved changes.') + } + con.color_pair(colStatus, 255) // reset colour let result = false @@ -2261,6 +2369,7 @@ resetAudioDevice() taud.uploadTaudFile(fullPathObj.full, 0, PLAYHEAD) audio.setMasterVolume(PLAYHEAD, 255) audio.setMasterPan(PLAYHEAD, 128) +const initialTrackerMixerflags = audio.getTrackerMixerFlags(PLAYHEAD) function isExternalPanel(p) { return p === VIEW_SAMPLES || p === VIEW_INSTRMNT || p === VIEW_FILE diff --git a/assets/disk0/tvdos/bin/tautfont.kra b/assets/disk0/tvdos/bin/tautfont.kra index 34d5f4b..22ab852 100644 --- a/assets/disk0/tvdos/bin/tautfont.kra +++ b/assets/disk0/tvdos/bin/tautfont.kra @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a33e1854f685302cb9c6c5e9af53178e20584584d11b560170814a05b76fd413 -size 129166 +oid sha256:ebaddd9a60bffd30adaec9366038b8799278a4c88167efffc1d3da8ffafd1bf3 +size 138887 diff --git a/assets/disk0/tvdos/bin/tautfont_high.chr b/assets/disk0/tvdos/bin/tautfont_high.chr index e03a16a190eceb4425139c19f15d714bb1b17bec..7700b83cd7b8051970dbe3430f260a4a3a673c97 100644 GIT binary patch delta 53 zcmZqRZ{Xi>f=!cwjg8G7h#BM=80;AsnAmxu*!ZQ`+}POD7#Ixd%z)6qpvIuiVDd+{ FegN~k3e5lj delta 53 mcmZqRZ{Xi>f=#m`Cntvi4lH~m3~Jy!Fhd3iK#a*B+4=zlcnDJf diff --git a/assets/disk0/tvdos/include/taud.mjs b/assets/disk0/tvdos/include/taud.mjs index 9763cb5..71aff61 100644 --- a/assets/disk0/tvdos/include/taud.mjs +++ b/assets/disk0/tvdos/include/taud.mjs @@ -43,9 +43,9 @@ function _pokeU32LE(ptr, off, v) { * * @param inFile Full path with drive letter, e.g. "A:/music/song.taud" * @param songIndex 0-based index of the song in the SONG TABLE - * @param targetPlaydataSlot Playhead number (0-3) to configure + * @param playhead Playhead number (0-3) to configure */ -function uploadTaudFile(inFile, songIndex, targetPlaydataSlot) { +function uploadTaudFile(inFile, songIndex, playhead) { const drive = inFile[0].toUpperCase() const diskPath = inFile.substring(2) @@ -107,6 +107,7 @@ function uploadTaudFile(inFile, songIndex, targetPlaydataSlot) { let numPatsHi = sys.peek(filePtr + entryOff + 6) & 0xFF let bpmStored = sys.peek(filePtr + entryOff + 7) & 0xFF let tickRate = sys.peek(filePtr + entryOff + 8) & 0xFF + let mixerflags = sys.peek(filePtr + entryOff + 15) & 0xFF let bpm = bpmStored + 24 let patsToLoad = numPatsLo | (numPatsHi << 8) @@ -130,9 +131,10 @@ function uploadTaudFile(inFile, songIndex, targetPlaydataSlot) { } // -- 8. Configure playhead ------------------------------------------------ - audio.setTrackerMode(targetPlaydataSlot) - audio.setBPM(targetPlaydataSlot, bpm) - audio.setTickRate(targetPlaydataSlot, tickRate > 0 ? tickRate : 6) + audio.setTrackerMode(playhead) + audio.setBPM(playhead, bpm) + audio.setTickRate(playhead, tickRate > 0 ? tickRate : 6) + audio.setTrackerMixerFlags(playhead, mixerflags) fileHandle.close() diff --git a/terranmon.txt b/terranmon.txt index 693c527..902db2b 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -2022,13 +2022,12 @@ Instrument bin: Registry for 256 instruments, formatted as: Uint8 Instrument Global Volume (0..255) * ImpulseTracker has range of 0..128; multiply by (255/128) then round to int * FastTracker2 has range of 0..64; multiply by (255/64) then round to int - Bit16x8 Volume envelopes + Bit16x12 Volume envelopes Byte 1: Volume (00..3F) Byte 2: Time until the next point, in seconds (3.5 Unsigned Minifloat). 0 = hold at this point indefinitely. - Bit16x8 Panning envelopes + Bit16x12 Panning envelopes Byte 1: Pan (00..FF) Byte 2: Time until the next point, in seconds (3.5 Unsigned Minifloat). 0 = hold at this point indefinitely. - Bit16x8 Reserved Play Data: play data are series of tracker-like instructions, visualised as: diff --git a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt index 8004f2c..89523fc 100644 --- a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt @@ -131,6 +131,14 @@ class AudioJSR223Delegate(private val vm: VM) { } } + fun setTrackerMixerFlags(playhead: Int, flags: Int) { + getFirstSnd()?.playheads?.get(playhead)?.initialGlobalFlags = flags + } + + fun getTrackerMixerFlags(playhead: Int): Int? { + return getFirstSnd()?.playheads?.get(playhead)?.initialGlobalFlags + } + fun putPcmDataByPtr(playhead: Int, ptr: Int, length: Int, destOffset: Int) { getFirstSnd()?.let { val vkMult = if (ptr >= 0) 1 else -1 diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt index 241a052..712fce7 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt @@ -1202,8 +1202,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { voice.envTimeSec = 0.0 voice.envIndex = vSusStart voice.envVolume = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0) - } else if (voice.envIndex >= 7) { - voice.envVolume = (inst.volEnvelopes[7].value / 63.0).coerceIn(0.0, 1.0) + } else if (voice.envIndex >= 11) { + voice.envVolume = (inst.volEnvelopes[11].value / 63.0).coerceIn(0.0, 1.0) } else { val vOffset = inst.volEnvelopes[voice.envIndex].offset.toDouble() if (vOffset == 0.0) { @@ -1213,12 +1213,12 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { if (voice.envTimeSec >= vOffset) { voice.envTimeSec -= vOffset val nextIdx = if (vSusOn && voice.envIndex == vSusEnd) vSusStart - else (voice.envIndex + 1).coerceAtMost(7) + else (voice.envIndex + 1).coerceAtMost(11) voice.envIndex = nextIdx voice.envVolume = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0) } else { val cur = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0) - val nxt = (inst.volEnvelopes[(voice.envIndex + 1).coerceAtMost(7)].value / 63.0).coerceIn(0.0, 1.0) + val nxt = (inst.volEnvelopes[(voice.envIndex + 1).coerceAtMost(11)].value / 63.0).coerceIn(0.0, 1.0) voice.envVolume = cur + (nxt - cur) * (voice.envTimeSec / vOffset) } } @@ -1242,8 +1242,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { voice.envPanTimeSec = 0.0 voice.envPanIndex = pSusStart voice.envPan = inst.panEnvelopes[voice.envPanIndex].value / 255.0 - } else if (voice.envPanIndex >= 7) { - voice.envPan = inst.panEnvelopes[7].value / 255.0 + } else if (voice.envPanIndex >= 11) { + voice.envPan = inst.panEnvelopes[11].value / 255.0 } else { val pOffset = inst.panEnvelopes[voice.envPanIndex].offset.toDouble() if (pOffset == 0.0) { @@ -1253,12 +1253,12 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { if (voice.envPanTimeSec >= pOffset) { voice.envPanTimeSec -= pOffset val nextIdx = if (pSusOn && voice.envPanIndex == pSusEnd) pSusStart - else (voice.envPanIndex + 1).coerceAtMost(7) + else (voice.envPanIndex + 1).coerceAtMost(11) voice.envPanIndex = nextIdx voice.envPan = inst.panEnvelopes[voice.envPanIndex].value / 255.0 } else { val cur = inst.panEnvelopes[voice.envPanIndex].value / 255.0 - val nxt = inst.panEnvelopes[(voice.envPanIndex + 1).coerceAtMost(7)].value / 255.0 + val nxt = inst.panEnvelopes[(voice.envPanIndex + 1).coerceAtMost(11)].value / 255.0 voice.envPan = cur + (nxt - cur) * (voice.envPanTimeSec / pOffset) } } @@ -2336,12 +2336,12 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { var volEnvSustain: Int, // byte 13: ut eee sss (u=enable, t=sustain (1=breaks on key-off, 0=loops forever)) var panEnvSustain: Int, // byte 14: ut eee sss (u=enable, t=sustain (1=breaks on key-off, 0=loops forever)) var instGlobalVolume: Int, // byte 15: instrument global volume (0..255, 255 = unity) - var volEnvelopes: Array, // 8 points, value 0x00-0x3F - var panEnvelopes: Array // 8 points, value 0x00-0xFF (0x80 = centre) + var volEnvelopes: Array, // 12 points, value 0x00-0x3F + var panEnvelopes: Array // 12 points, value 0x00-0xFF (0x80 = centre) ) { constructor(index: Int) : this(index, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, - Array(8) { TaudInstEnvPoint(0x3F, ThreeFiveMiniUfloat(0)) }, - Array(8) { TaudInstEnvPoint(0x80, ThreeFiveMiniUfloat(0)) }) + Array(12) { TaudInstEnvPoint(0x3F, ThreeFiveMiniUfloat(0)) }, + Array(12) { TaudInstEnvPoint(0x80, ThreeFiveMiniUfloat(0)) }) // 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. @@ -2382,11 +2382,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { 13 -> volEnvSustain.toByte() 14 -> panEnvSustain.toByte() 15 -> instGlobalVolume.toByte() - in 16..30 step 2 -> volEnvelopes[(offset - 16) / 2].value.toByte() - in 17..31 step 2 -> volEnvelopes[(offset - 17) / 2].offset.index.toByte() - in 32..46 step 2 -> panEnvelopes[(offset - 32) / 2].value.toByte() - in 33..47 step 2 -> panEnvelopes[(offset - 33) / 2].offset.index.toByte() - in 48..63 -> 0 + in 16..38 step 2 -> volEnvelopes[(offset - 16) / 2].value.toByte() + in 17..39 step 2 -> volEnvelopes[(offset - 17) / 2].offset.index.toByte() + in 40..62 step 2 -> panEnvelopes[(offset - 40) / 2].value.toByte() + in 41..63 step 2 -> panEnvelopes[(offset - 41) / 2].offset.index.toByte() else -> throw InternalError("Bad offset $offset") } @@ -2418,11 +2417,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { 14 -> { panEnvSustain = byte } 15 -> { instGlobalVolume = byte and 0xFF } - in 16..30 step 2 -> volEnvelopes[(offset - 16) / 2].value = byte - in 17..31 step 2 -> volEnvelopes[(offset - 17) / 2].offset = ThreeFiveMiniUfloat(byte) - in 32..46 step 2 -> panEnvelopes[(offset - 32) / 2].value = byte - in 33..47 step 2 -> panEnvelopes[(offset - 33) / 2].offset = ThreeFiveMiniUfloat(byte) - in 48..63 -> {} + in 16..38 step 2 -> volEnvelopes[(offset - 16) / 2].value = byte + in 17..39 step 2 -> volEnvelopes[(offset - 17) / 2].offset = ThreeFiveMiniUfloat(byte) + in 40..62 step 2 -> panEnvelopes[(offset - 40) / 2].value = byte + in 41..63 step 2 -> panEnvelopes[(offset - 41) / 2].offset = ThreeFiveMiniUfloat(byte) else -> throw InternalError("Bad offset $offset") } }