Compare commits

...

3 Commits

Author SHA1 Message Date
minjaesong
27e4bc1ae5 2taud update 2026-04-29 11:27:29 +09:00
minjaesong
2282e0c10b it2taud, instrument format changes 2026-04-29 09:21:28 +09:00
minjaesong
e7287fae37 taut: graphical buttons 2026-04-29 03:53:40 +09:00
8 changed files with 1738 additions and 44 deletions

View File

@@ -537,6 +537,7 @@ function loadTaud(filePath, songIndex) {
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
const [SCRH, SCRW] = con.getmaxyx() const [SCRH, SCRW] = con.getmaxyx()
const [SCRPW, SCRPH] = graphics.getPixelDimension()
const PTNVIEW_OFFSET_X = 3 const PTNVIEW_OFFSET_X = 3
const PTNVIEW_OFFSET_Y = 5 const PTNVIEW_OFFSET_Y = 5
const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y
@@ -612,6 +613,7 @@ const transportControlReverse = [PLAYMODE_NONE, PLAYMODE_ROW, PLAYMODE_CUE, PLAY
const transportControlSymbol = [sym.stop, sym.playrow, sym.playcue, sym.playall] const transportControlSymbol = [sym.stop, sym.playrow, sym.playcue, sym.playall]
const transportControlColour = [160,20,20,20] const transportControlColour = [160,20,20,20]
const transportControlHint = ["O","I","U","Y"] const transportControlHint = ["O","I","U","Y"]
let transportControlOldPos = 3 // index for transportControlReverse
function drawStatusBar() { function drawStatusBar() {
fillLine(1, colStatus, 255) fillLine(1, colStatus, 255)
fillLine(2, colStatus, 255) fillLine(2, colStatus, 255)
@@ -627,25 +629,38 @@ function drawStatusBar() {
const sSpd = ''+audio.getTickRate(PLAYHEAD) const sSpd = ''+audio.getTickRate(PLAYHEAD)
// transport control and its control hints // transport control and its control hints
let transportControlNewPos = transportControlOldPos
transportControlReverse.forEach((thisMode, j) => { transportControlReverse.forEach((thisMode, j) => {
let active = (playbackMode == thisMode) let active = (playbackMode == thisMode)
if (active) if (active)
con.color_pair(transportControlColour[j], colPushBtnBack) con.color_pair(transportControlColour[j], 255)
else else
con.color_pair(colStatus, 255) con.color_pair(colStatus, 255)
con.move(1, SCRW - 5*(j+1) + 1) con.move(1, SCRW - 5*(j+1) + 1 + 2)
print(` ${transportControlSymbol[j]} `) print(transportControlSymbol[j])
if (active) if (active)
con.color_pair(transportControlColour[j], colPushBtnBack) con.color_pair(transportControlColour[j], 255)
else else
con.color_pair(colVoiceHdr, 255) con.color_pair(colVoiceHdr, 255)
con.move(2, SCRW - 5*(j+1) + 1) con.move(2, SCRW - 5*(j+1) + 1 + 2)
print(` ${transportControlHint[j]} `) print(transportControlHint[j])
if (active) transportControlNewPos = j;
}) })
// draw button background
if (transportControlOldPos != transportControlNewPos) {
// erase button from old pos
gl.drawTexImage(buttonNullTexture, SCRPW - 35*(transportControlOldPos+1), 0)
// paint button in new pos
gl.drawTexImage(buttonTexture, SCRPW - 35*(transportControlNewPos+1), 0)
// update pos tracking
transportControlOldPos = transportControlNewPos
}
// current audio device status // current audio device status
// play/stop sym // play/stop sym
@@ -680,7 +695,7 @@ function drawStatusBar() {
con.move(2, (SCRW - (s2.length & 254)) >>> 1) con.move(2, (SCRW - (s2.length & 254)) >>> 1)
con.color_pair(colSep, 255); print('tracker for ') con.color_pair(colSep, 255); print('tracker for ')
con.color_pair(74, 255); print('tsvm')*/ con.color_pair(74, 255); print('tsvm')*/
gl.drawTexImage(logoTexture, (graphics.getPixelDimension()[0]-logoTexture.width) >>> 1, 6) gl.drawTexImage(logoTexture, (SCRPW-logoTexture.width) >>> 1, 6)
} }
@@ -1166,6 +1181,12 @@ if (fullPathObj === undefined) {
const logofile = files.open("A:/tvdos/bin/tauthdr.r8") const logofile = files.open("A:/tvdos/bin/tauthdr.r8")
const logoBytes = logofile.bread(); logofile.close() const logoBytes = logofile.bread(); logofile.close()
const logoTexture = new gl.Texture(88, 12, logoBytes) const logoTexture = new gl.Texture(88, 12, logoBytes)
const buttonfile = files.open("A:/tvdos/bin/tautbtn.r8")
const buttonBytes = buttonfile.bread(); buttonfile.close()
const buttonTexture = new gl.Texture(35, 28, buttonBytes)
const buttonNullfile = files.open("A:/tvdos/bin/tautbtn0.r8")
const buttonNullBytes = buttonNullfile.bread(); buttonNullfile.close()
const buttonNullTexture = new gl.Texture(35, 28, buttonNullBytes)
font.setLowRom("A:/tvdos/bin/tautfont_low.chr") font.setLowRom("A:/tvdos/bin/tautfont_low.chr")
font.setHighRom("A:/tvdos/bin/tautfont_high.chr") font.setHighRom("A:/tvdos/bin/tautfont_high.chr")

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

View File

@@ -0,0 +1 @@
﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾ﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﻻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺ﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠。慡慡慡慡慡慡慡慡慡慡慡慡慡慡慡慡。

View File

@@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

1580
it2taud.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -624,8 +624,8 @@ def build_sample_inst_bin(instruments: list) -> tuple:
struct.pack_into('<H', inst_bin, base + 8, ls) struct.pack_into('<H', inst_bin, base + 8, ls)
struct.pack_into('<H', inst_bin, base + 10, le) struct.pack_into('<H', inst_bin, base + 10, le)
inst_bin[base + 12] = flags_byte inst_bin[base + 12] = flags_byte
# Volume envelope: hold at instrument volume (vol*4 clamped to 255) # Volume envelope: hold at instrument volume (clamped to 0x3F)
env_vol = min(inst.volume * 4, 255) env_vol = min(inst.volume, 63)
inst_bin[base + 16] = env_vol # volume inst_bin[base + 16] = env_vol # volume
inst_bin[base + 17] = 0 # offset minifloat = 0 → hold inst_bin[base + 17] = 0 # offset minifloat = 0 → hold

View File

@@ -2001,13 +2001,28 @@ Instrument bin: Registry for 256 instruments, formatted as:
Uint16 Play Start (usually 0 but not always) Uint16 Play Start (usually 0 but not always)
Uint16 Loop Start (can be smaller than Play Start) Uint16 Loop Start (can be smaller than Play Start)
Uint16 Loop End Uint16 Loop End
Bit32 Flags Bit8 Sample Flags
0b hhhh 00pp 0b hhhh 00pp
h: sample pointer high bit h: sample pointer high bit
pp: loop mode. 0-no loop, 1-loop, 2-backandforth, 3-oneshot (ignores note length unless overridden by other notes) pp: loop mode. 0-no loop, 1-loop, 2-backandforth, 3-oneshot (ignores note length unless overridden by other notes)
Bit16x24 Volume envelopes Bit8 Volume envelope sustain loops
Byte 1: Volume 0b u0 eee sss
Byte 2: Second offset from the prev point, in 3.5 Unsigned Minifloat s: sustain loop start index
e: sustain loop end index
u: set to enable the loop
Bit8 Panning envelope sustain loops
0b u0 eee sss
s: sustain loop start index
e: sustain loop end index
u: set to enable the loop
Bit8 Reserved
Bit16x8 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
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: Play Data: play data are series of tracker-like instructions, visualised as:
@@ -2177,7 +2192,7 @@ Tracker Note Effects has been moved to `TAUD_NOTE_EFFECTS.md`
**Taud serialisation format** **Taud serialisation format**
Created by CuriousTorvald on 2026-04-19 Created by CuriousTorvald on 2026-04-19
This is a file format for Taud tracker data. Taud can be extended with project data in backward-and-forward-compatible manner. This is a file format for Taud tracker data. Taud can be extended with Microtone (taut.js) project data in backward-and-forward-compatible manner.
Endianness: Little Endianness: Little

View File

@@ -1166,24 +1166,68 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
inst.samplingRate.toDouble() / SAMPLING_RATE * 2.0.pow((noteVal - TRACKER_C3) / 4096.0) inst.samplingRate.toDouble() / SAMPLING_RATE * 2.0.pow((noteVal - TRACKER_C3) / 4096.0)
private fun advanceEnvelope(voice: Voice, inst: TaudInst, tickSec: Double) { private fun advanceEnvelope(voice: Voice, inst: TaudInst, tickSec: Double) {
if (voice.envIndex >= 23) { // Volume envelope
voice.envVolume = inst.envelopes[23].volume / 255.0 // sustain byte: bit7=enabled, bits[5:3]=end_idx, bits[2:0]=start_idx
return val vSus = inst.volEnvSustain
} val vSusOn = (vSus and 0x80) != 0 && !voice.keyOff
val offset = inst.envelopes[voice.envIndex].offset.toFloat().toDouble() val vSusStart = vSus and 7
if (offset == 0.0) { val vSusEnd = (vSus ushr 3) and 7
voice.envVolume = inst.envelopes[voice.envIndex].volume / 255.0
return if (voice.envIndex >= 7) {
} voice.envVolume = (inst.volEnvelopes[7].value / 63.0).coerceIn(0.0, 1.0)
voice.envTimeSec += tickSec } else if (vSusOn && voice.envIndex == vSusEnd && vSusStart == vSusEnd) {
if (voice.envTimeSec >= offset) { // slb == sle: hold at this node until key-off (no cycling)
voice.envTimeSec -= offset voice.envVolume = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0)
voice.envIndex = (voice.envIndex + 1).coerceAtMost(23)
voice.envVolume = inst.envelopes[voice.envIndex].volume / 255.0
} else { } else {
val cur = inst.envelopes[voice.envIndex].volume / 255.0 val vOffset = inst.volEnvelopes[voice.envIndex].offset.toDouble()
val nxt = inst.envelopes[(voice.envIndex + 1).coerceAtMost(23)].volume / 255.0 if (vOffset == 0.0) {
voice.envVolume = cur + (nxt - cur) * (voice.envTimeSec / offset) voice.envVolume = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0)
} else {
voice.envTimeSec += tickSec
if (voice.envTimeSec >= vOffset) {
voice.envTimeSec -= vOffset
val nextIdx = if (vSusOn && voice.envIndex == vSusEnd) vSusStart
else (voice.envIndex + 1).coerceAtMost(7)
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)
voice.envVolume = cur + (nxt - cur) * (voice.envTimeSec / vOffset)
}
}
}
// Pan envelope (only when active for this instrument)
if (!voice.hasPanEnv) return
val pSus = inst.panEnvSustain
val pSusOn = (pSus and 0x80) != 0 && !voice.keyOff
val pSusStart = pSus and 7
val pSusEnd = (pSus ushr 3) and 7
if (voice.envPanIndex >= 7) {
voice.envPan = inst.panEnvelopes[7].value / 255.0
} else if (pSusOn && voice.envPanIndex == pSusEnd && pSusStart == pSusEnd) {
// slb == sle: hold at this pan node until key-off
voice.envPan = inst.panEnvelopes[voice.envPanIndex].value / 255.0
} else {
val pOffset = inst.panEnvelopes[voice.envPanIndex].offset.toDouble()
if (pOffset == 0.0) {
voice.envPan = inst.panEnvelopes[voice.envPanIndex].value / 255.0
} else {
voice.envPanTimeSec += tickSec
if (voice.envPanTimeSec >= pOffset) {
voice.envPanTimeSec -= pOffset
val nextIdx = if (pSusOn && voice.envPanIndex == pSusEnd) pSusStart
else (voice.envPanIndex + 1).coerceAtMost(7)
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
voice.envPan = cur + (nxt - cur) * (voice.envPanTimeSec / pOffset)
}
}
} }
} }
@@ -1238,9 +1282,14 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
voice.samplePos = inst.samplePlayStart.toDouble() voice.samplePos = inst.samplePlayStart.toDouble()
voice.forward = true voice.forward = true
voice.active = true voice.active = true
voice.keyOff = false
voice.envIndex = 0 voice.envIndex = 0
voice.envTimeSec = 0.0 voice.envTimeSec = 0.0
voice.envVolume = inst.envelopes[0].volume / 255.0 voice.envVolume = (inst.volEnvelopes[0].value / 63.0).coerceIn(0.0, 1.0)
voice.envPanIndex = 0
voice.envPanTimeSec = 0.0
voice.envPan = inst.panEnvelopes[0].value / 255.0
voice.hasPanEnv = inst.panEnvelopes.any { it.offset.toFloat() > 0.0f }
voice.noteVal = noteVal voice.noteVal = noteVal
voice.basePitch = noteVal voice.basePitch = noteVal
voice.playbackRate = computePlaybackRate(inst, noteVal) voice.playbackRate = computePlaybackRate(inst, noteVal)
@@ -1322,7 +1371,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
val toneG = (row.effect == EffectOp.OP_G) val toneG = (row.effect == EffectOp.OP_G)
when (row.note) { when (row.note) {
0xFFFF -> {} // no-op 0xFFFF -> {} // no-op
0x0000 -> voice.active = false // key-off (TODO release envelope) 0x0000 -> { voice.keyOff = true; voice.active = false } // key-off; breaks sustain loop
0xFFFE -> voice.active = false // note cut 0xFFFE -> voice.active = false // note cut
else -> { else -> {
if (toneG && voice.active) { if (toneG && voice.active) {
@@ -1652,8 +1701,12 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
voice.retrigCounter++ voice.retrigCounter++
if (voice.retrigCounter >= voice.retrigInterval) { if (voice.retrigCounter >= voice.retrigInterval) {
voice.retrigCounter = 0 voice.retrigCounter = 0
voice.samplePos = instruments[voice.instrumentId].samplePlayStart.toDouble() val retrigInst = instruments[voice.instrumentId]
voice.samplePos = retrigInst.samplePlayStart.toDouble()
voice.keyOff = false
voice.envIndex = 0; voice.envTimeSec = 0.0 voice.envIndex = 0; voice.envTimeSec = 0.0
voice.envPanIndex = 0; voice.envPanTimeSec = 0.0
voice.envPan = retrigInst.panEnvelopes[0].value / 255.0
voice.rowVolume = applyRetrigVolMod(voice.rowVolume, voice.retrigVolMod) voice.rowVolume = applyRetrigVolMod(voice.rowVolume, voice.retrigVolMod)
voice.channelVolume = voice.rowVolume voice.channelVolume = voice.rowVolume
} }
@@ -1741,7 +1794,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
if (!voice.active || voice.muted) continue if (!voice.active || voice.muted) continue
val s = fetchTrackerSample(voice, instruments[voice.instrumentId]) val s = fetchTrackerSample(voice, instruments[voice.instrumentId])
val vol = voice.envVolume * voice.rowVolume / 63.0 * gvol * playhead.masterVolume / 255.0 val vol = voice.envVolume * voice.rowVolume / 63.0 * gvol * playhead.masterVolume / 255.0
val pan = voice.channelPan val pan = if (voice.hasPanEnv) {
val envPanRaw = (voice.envPan * 255.0).roundToInt().coerceIn(0, 255)
(voice.channelPan + envPanRaw - 128).coerceIn(0, 255)
} else voice.channelPan
val lGain: Double val lGain: Double
val rGain: Double val rGain: Double
when (ts.panLaw) { when (ts.panLaw) {
@@ -1915,9 +1971,14 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
var channelPan = 0x80 // 8-bit; $80 centre. Cell column packs into 6-bit, S$80xx writes the full 8-bit. var channelPan = 0x80 // 8-bit; $80 centre. Cell column packs into 6-bit, S$80xx writes the full 8-bit.
var rowPan = 32 // 6-bit pan used by mixer, derived from channelPan var rowPan = 32 // 6-bit pan used by mixer, derived from channelPan
var keyOff = false
var envIndex = 0 var envIndex = 0
var envTimeSec = 0.0 var envTimeSec = 0.0
var envVolume = 1.0 var envVolume = 1.0
var envPanIndex = 0
var envPanTimeSec = 0.0
var envPan = 0.5 // 0.0=full-left, 1.0=full-right, 0.5=centre
var hasPanEnv = false
// Pitch state (4096-TET units, signed when slid). // Pitch state (4096-TET units, signed when slid).
var noteVal = 0xFFFF // The currently sounding base note (no per-row vibrato/arp added) var noteVal = 0xFFFF // The currently sounding base note (no per-row vibrato/arp added)
@@ -2208,7 +2269,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
} }
data class TaudInstVolEnv(var volume: Int, var offset: ThreeFiveMiniUfloat) data class TaudInstEnvPoint(var value: Int, var offset: ThreeFiveMiniUfloat)
data class TaudInst( data class TaudInst(
var index: Int, var index: Int,
@@ -2220,9 +2281,14 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
var sampleLoopEnd: Int, var sampleLoopEnd: Int,
// flags // flags
var loopMode: Int, var loopMode: Int,
var envelopes: Array<TaudInstVolEnv> // first int: volume (0..255), second int: offsets (minifloat indices) var volEnvSustain: Int, // byte 13: 00 eee sss (0 = no sustain loop)
var panEnvSustain: Int, // byte 14: 00 eee sss (0 = no sustain loop)
var volEnvelopes: Array<TaudInstEnvPoint>, // 8 points, value 0x00-0x3F
var panEnvelopes: Array<TaudInstEnvPoint> // 8 points, value 0x00-0xFF (0x80 = centre)
) { ) {
constructor(index: Int) : this(index, 0, 0, 0, 0, 0, 0, 0, Array(24) { TaudInstVolEnv(0, ThreeFiveMiniUfloat(0)) }) constructor(index: Int) : this(index, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Array(8) { TaudInstEnvPoint(0x3F, ThreeFiveMiniUfloat(0)) },
Array(8) { TaudInstEnvPoint(0x80, ThreeFiveMiniUfloat(0)) })
// Funk repeat (S$Fx00) bit-mask — non-destructive XOR overlay across the loop region. // 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. // Lazily allocated; a 1-bit flips the byte, a 0-bit leaves it intact.
@@ -2260,9 +2326,14 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
11 -> sampleLoopEnd.ushr(8).toByte() 11 -> sampleLoopEnd.ushr(8).toByte()
12 -> (samplePtr.ushr(16).and(15).shl(4) or loopMode.and(3)).toByte() 12 -> (samplePtr.ushr(16).and(15).shl(4) or loopMode.and(3)).toByte()
13,14,15 -> 0 13 -> volEnvSustain.toByte()
in 16..63 step 2 -> envelopes[(offset - 16) / 2].volume.toByte() 14 -> panEnvSustain.toByte()
in 17..63 step 2 -> envelopes[(offset - 17) / 2].offset.index.toByte() 15 -> 0
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
else -> throw InternalError("Bad offset $offset") else -> throw InternalError("Bad offset $offset")
} }
@@ -2290,10 +2361,15 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
else samplePtr and 0x0ffff else samplePtr and 0x0ffff
loopMode = byte and 3 loopMode = byte and 3
} }
13, 14, 15 -> {} 13 -> { volEnvSustain = byte }
14 -> { panEnvSustain = byte }
15 -> {}
in 16..63 step 2 -> envelopes[(offset - 16) / 2].volume = byte in 16..30 step 2 -> volEnvelopes[(offset - 16) / 2].value = byte
in 17..63 step 2 -> envelopes[(offset - 17) / 2].offset = ThreeFiveMiniUfloat(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 -> {}
else -> throw InternalError("Bad offset $offset") else -> throw InternalError("Bad offset $offset")
} }
} }