mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 13:38:30 +09:00
Compare commits
3 Commits
65d89db9c6
...
27e4bc1ae5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27e4bc1ae5 | ||
|
|
2282e0c10b | ||
|
|
e7287fae37 |
@@ -537,6 +537,7 @@ function loadTaud(filePath, songIndex) {
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const [SCRH, SCRW] = con.getmaxyx()
|
||||
const [SCRPW, SCRPH] = graphics.getPixelDimension()
|
||||
const PTNVIEW_OFFSET_X = 3
|
||||
const PTNVIEW_OFFSET_Y = 5
|
||||
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 transportControlColour = [160,20,20,20]
|
||||
const transportControlHint = ["O","I","U","Y"]
|
||||
let transportControlOldPos = 3 // index for transportControlReverse
|
||||
function drawStatusBar() {
|
||||
fillLine(1, colStatus, 255)
|
||||
fillLine(2, colStatus, 255)
|
||||
@@ -627,25 +629,38 @@ function drawStatusBar() {
|
||||
const sSpd = ''+audio.getTickRate(PLAYHEAD)
|
||||
|
||||
// transport control and its control hints
|
||||
let transportControlNewPos = transportControlOldPos
|
||||
transportControlReverse.forEach((thisMode, j) => {
|
||||
let active = (playbackMode == thisMode)
|
||||
|
||||
if (active)
|
||||
con.color_pair(transportControlColour[j], colPushBtnBack)
|
||||
con.color_pair(transportControlColour[j], 255)
|
||||
else
|
||||
con.color_pair(colStatus, 255)
|
||||
|
||||
con.move(1, SCRW - 5*(j+1) + 1)
|
||||
print(` ${transportControlSymbol[j]} `)
|
||||
con.move(1, SCRW - 5*(j+1) + 1 + 2)
|
||||
print(transportControlSymbol[j])
|
||||
|
||||
if (active)
|
||||
con.color_pair(transportControlColour[j], colPushBtnBack)
|
||||
con.color_pair(transportControlColour[j], 255)
|
||||
else
|
||||
con.color_pair(colVoiceHdr, 255)
|
||||
|
||||
con.move(2, SCRW - 5*(j+1) + 1)
|
||||
print(` ${transportControlHint[j]} `)
|
||||
con.move(2, SCRW - 5*(j+1) + 1 + 2)
|
||||
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
|
||||
// play/stop sym
|
||||
@@ -680,7 +695,7 @@ function drawStatusBar() {
|
||||
con.move(2, (SCRW - (s2.length & 254)) >>> 1)
|
||||
con.color_pair(colSep, 255); print('tracker for ')
|
||||
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 logoBytes = logofile.bread(); logofile.close()
|
||||
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.setHighRom("A:/tvdos/bin/tautfont_high.chr")
|
||||
|
||||
BIN
assets/disk0/tvdos/bin/tautbtn.png
Normal file
BIN
assets/disk0/tvdos/bin/tautbtn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 214 B |
1
assets/disk0/tvdos/bin/tautbtn.r8
Normal file
1
assets/disk0/tvdos/bin/tautbtn.r8
Normal file
@@ -0,0 +1 @@
|
||||
ﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﻻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺ粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠。慡慡慡慡慡慡慡慡慡慡慡慡慡慡慡慡。
|
||||
1
assets/disk0/tvdos/bin/tautbtn0.r8
Normal file
1
assets/disk0/tvdos/bin/tautbtn0.r8
Normal 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
1580
it2taud.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 + 10, le)
|
||||
inst_bin[base + 12] = flags_byte
|
||||
# Volume envelope: hold at instrument volume (vol*4 clamped to 255)
|
||||
env_vol = min(inst.volume * 4, 255)
|
||||
# Volume envelope: hold at instrument volume (clamped to 0x3F)
|
||||
env_vol = min(inst.volume, 63)
|
||||
inst_bin[base + 16] = env_vol # volume
|
||||
inst_bin[base + 17] = 0 # offset minifloat = 0 → hold
|
||||
|
||||
|
||||
@@ -2001,13 +2001,28 @@ Instrument bin: Registry for 256 instruments, formatted as:
|
||||
Uint16 Play Start (usually 0 but not always)
|
||||
Uint16 Loop Start (can be smaller than Play Start)
|
||||
Uint16 Loop End
|
||||
Bit32 Flags
|
||||
Bit8 Sample Flags
|
||||
0b hhhh 00pp
|
||||
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)
|
||||
Bit16x24 Volume envelopes
|
||||
Byte 1: Volume
|
||||
Byte 2: Second offset from the prev point, in 3.5 Unsigned Minifloat
|
||||
Bit8 Volume envelope sustain loops
|
||||
0b u0 eee sss
|
||||
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:
|
||||
|
||||
@@ -2177,7 +2192,7 @@ Tracker Note Effects has been moved to `TAUD_NOTE_EFFECTS.md`
|
||||
**Taud serialisation format**
|
||||
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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
private fun advanceEnvelope(voice: Voice, inst: TaudInst, tickSec: Double) {
|
||||
if (voice.envIndex >= 23) {
|
||||
voice.envVolume = inst.envelopes[23].volume / 255.0
|
||||
return
|
||||
}
|
||||
val offset = inst.envelopes[voice.envIndex].offset.toFloat().toDouble()
|
||||
if (offset == 0.0) {
|
||||
voice.envVolume = inst.envelopes[voice.envIndex].volume / 255.0
|
||||
return
|
||||
}
|
||||
voice.envTimeSec += tickSec
|
||||
if (voice.envTimeSec >= offset) {
|
||||
voice.envTimeSec -= offset
|
||||
voice.envIndex = (voice.envIndex + 1).coerceAtMost(23)
|
||||
voice.envVolume = inst.envelopes[voice.envIndex].volume / 255.0
|
||||
// Volume envelope
|
||||
// sustain byte: bit7=enabled, bits[5:3]=end_idx, bits[2:0]=start_idx
|
||||
val vSus = inst.volEnvSustain
|
||||
val vSusOn = (vSus and 0x80) != 0 && !voice.keyOff
|
||||
val vSusStart = vSus and 7
|
||||
val vSusEnd = (vSus ushr 3) and 7
|
||||
|
||||
if (voice.envIndex >= 7) {
|
||||
voice.envVolume = (inst.volEnvelopes[7].value / 63.0).coerceIn(0.0, 1.0)
|
||||
} else if (vSusOn && voice.envIndex == vSusEnd && vSusStart == vSusEnd) {
|
||||
// slb == sle: hold at this node until key-off (no cycling)
|
||||
voice.envVolume = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0)
|
||||
} else {
|
||||
val cur = inst.envelopes[voice.envIndex].volume / 255.0
|
||||
val nxt = inst.envelopes[(voice.envIndex + 1).coerceAtMost(23)].volume / 255.0
|
||||
voice.envVolume = cur + (nxt - cur) * (voice.envTimeSec / offset)
|
||||
val vOffset = inst.volEnvelopes[voice.envIndex].offset.toDouble()
|
||||
if (vOffset == 0.0) {
|
||||
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.forward = true
|
||||
voice.active = true
|
||||
voice.keyOff = false
|
||||
voice.envIndex = 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.basePitch = 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)
|
||||
when (row.note) {
|
||||
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
|
||||
else -> {
|
||||
if (toneG && voice.active) {
|
||||
@@ -1652,8 +1701,12 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
voice.retrigCounter++
|
||||
if (voice.retrigCounter >= voice.retrigInterval) {
|
||||
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.envPanIndex = 0; voice.envPanTimeSec = 0.0
|
||||
voice.envPan = retrigInst.panEnvelopes[0].value / 255.0
|
||||
voice.rowVolume = applyRetrigVolMod(voice.rowVolume, voice.retrigVolMod)
|
||||
voice.channelVolume = voice.rowVolume
|
||||
}
|
||||
@@ -1741,7 +1794,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
if (!voice.active || voice.muted) continue
|
||||
val s = fetchTrackerSample(voice, instruments[voice.instrumentId])
|
||||
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 rGain: Double
|
||||
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 rowPan = 32 // 6-bit pan used by mixer, derived from channelPan
|
||||
|
||||
var keyOff = false
|
||||
var envIndex = 0
|
||||
var envTimeSec = 0.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).
|
||||
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(
|
||||
var index: Int,
|
||||
|
||||
@@ -2220,9 +2281,14 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
var sampleLoopEnd: Int,
|
||||
// flags
|
||||
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.
|
||||
// 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()
|
||||
|
||||
12 -> (samplePtr.ushr(16).and(15).shl(4) or loopMode.and(3)).toByte()
|
||||
13,14,15 -> 0
|
||||
in 16..63 step 2 -> envelopes[(offset - 16) / 2].volume.toByte()
|
||||
in 17..63 step 2 -> envelopes[(offset - 17) / 2].offset.index.toByte()
|
||||
13 -> volEnvSustain.toByte()
|
||||
14 -> panEnvSustain.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")
|
||||
}
|
||||
|
||||
@@ -2290,10 +2361,15 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
else samplePtr and 0x0ffff
|
||||
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 17..63 step 2 -> envelopes[(offset - 17) / 2].offset = ThreeFiveMiniUfloat(byte)
|
||||
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 -> {}
|
||||
else -> throw InternalError("Bad offset $offset")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user