mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
taut.js: view multiple songs
This commit is contained in:
@@ -256,7 +256,7 @@ The unit of `$xxxx` depends on the song-table tone mode (effect `1`, bits 0-1):
|
||||
- `ff = 0` (linear) and `ff = 1` (Amiga): 4096-TET pitch units per tick. Amiga sources should be converted to linear units on G, since the original PT G slide already operated semi-linearly within a small range and the shared-memory pitfall of E/F does not apply here.
|
||||
- `ff = 2` (linear-frequency): Hz/tick. The engine walks the channel's *frequency* toward the target note's frequency by `±$xxxx` Hz each non-first tick. This is MONOTONE's `3xx` behaviour verbatim (MTSRC/MT_PLAY.PAS:620-630).
|
||||
|
||||
**Compatibility.** ST3 `Gxx` uses an 8-bit value in period-table units; convert to Taud using the same `round(× 64/3)` scale as E/F coarse (1/16 semitone per ST3 slide unit). ST3 linear mode is the expected import source; Amiga-mode G sources should be treated as linear. MONOTONE `3xx` → Taud `G $00xx` verbatim under ff=2. G has its **own** memory slot in both ST3 and Taud, so conversion is straightforward and does not suffer the shared-memory problem of E/F.
|
||||
**Compatibility.** ST3 `Gxx` uses an 8-bit value in period-table units; convert to Taud using the same `round(× 64/3)` scale as E/F coarse (1/16 semitone per ST3 slide unit). Amiga-mode G sources should be treated as linear. MONOTONE `3xx` → Taud `G $00xx` verbatim under ff=2. G has its **own** memory slot in both ST3 and Taud, so conversion is straightforward and does not suffer the shared-memory problem of E/F.
|
||||
|
||||
**Implementation.**
|
||||
|
||||
@@ -1140,8 +1140,8 @@ Effects in this section modifies the behaviour of the mixer. Primary intention o
|
||||
|
||||
0b 000 rrr ff
|
||||
|
||||
- ff = 0: Linear tone mode. Pitch shift will behave like MIDI/ImpulseTracker/ScreamTracker linear mode. **Coarse and fine E/F arguments are stored as 4096-TET pitch units** and subtracted/added directly from the stored pitch.
|
||||
- ff = 1: Amiga (cycle-based) tone mode. Pitch shift will behave like ProTracker/ScreamTracker default mode. **Coarse and fine E/F arguments are stored as raw tracker period units** (the unscaled byte/nibble from the source PT/S3M/IT file) and applied in Amiga period space. Tone portamento (G) remains linear regardless of mode.
|
||||
- ff = 0: Linear tone mode. Pitch shift will behave like MIDI/ImpulseTracker. **Coarse and fine E/F arguments are stored as 4096-TET pitch units** and subtracted/added directly from the stored pitch.
|
||||
- ff = 1: Amiga (cycle-based) tone mode. Pitch shift will behave like ProTracker/ScreamTracker. **Coarse and fine E/F arguments are stored as raw tracker period units** (the unscaled byte/nibble from the source PT/S3M/IT file) and applied in Amiga period space. Tone portamento (G) remains linear regardless of mode.
|
||||
- ff = 2: Linear-frequency tone mode (MONOTONE compat). **E, F, and G arguments are stored as Hz/tick** (a signed change in audible frequency per song tick), and the engine converts the channel's stored 4096-TET pitch back to a frequency, adds/subtracts the argument, then converts back to 4096-TET. Reference is fixed at 12-TET A4 = 440 Hz / C4 ≈ 261.6256 Hz, which matches MONOTONE's MT_PLAY.PAS `notesHz` table (A0 = 27.5 Hz, equal-temperament). Unlike Amiga mode, *all three* slide effects use the new arithmetic — Monotone's `1xx`, `2xx`, and `3xx` are all in Hz/tick (see MTSRC/MT_PLAY.PAS:606-630).
|
||||
|
||||
- rrr = 0: Yes interpolation. Actual interpolation algorithm is implementation-dependent, but recommended to use either Fast Sinc or Linear.
|
||||
@@ -1277,10 +1277,9 @@ These quirks of ST3 are worth preserving or flagging when importing S3M files in
|
||||
|
||||
**Global volume scale.** ST3's 0..$40 maps to Taud's 0..$FF with a ×4 scale on import, truncated ÷4 on export.
|
||||
|
||||
**Linear pitch slides.** ST3's slide arithmetic is period-based (Amiga) or linear-table-indexed; Taud carries both interpretations and selects between them via the song-table `f` flag. Conversion rules:
|
||||
**Linear pitch slides.** ST3's slide arithmetic is period-based; Taud supports both linear and period-based and selects between them via the song-table `f` flag. Conversion rules:
|
||||
|
||||
- **ST3 linear mode** (`linear_slides` set in S3M flags): coarse forms (Exx/Fxx) use `round(× 64/3)` (1/16 semitone per ST3 unit); fine/extra-fine (EFx/EEx/FFx/FEx) use `round(× 16/3)` (1/64 semitone per ST3 unit). Taud `f` flag is **clear**; the engine subtracts the stored 4096-TET argument directly from the channel pitch.
|
||||
- **ST3 Amiga mode** (`linear_slides` clear): both coarse (Exx/Fxx) and fine/extra-fine (EFx/EEx/FFx/FEx) are stored **verbatim** as raw ST3 period units — coarse as `E/F $00xx`, fine as `E/F $F00x` — with no scaling. Taud `f` flag is **set**; the engine applies both forms in Amiga period space at playback, exactly recovering the source's period-step count and the non-linear pitch character.
|
||||
- Clear `linear_slides`. Both coarse (Exx/Fxx) and fine/extra-fine (EFx/EEx/FFx/FEx) are stored **verbatim** as raw ST3 period units — coarse as `E/F $00xx`, fine as `E/F $F00x` — with no scaling. Taud `f` flag is **set**; the engine applies both forms in Amiga period space at playback, exactly recovering the source's period-step count and the non-linear pitch character.
|
||||
- G (tone portamento) is always converted with `round(× 64/3)` and treated as linear, regardless of mode.
|
||||
|
||||
**Default tempo byte.** Taud's default $64 equals 125 BPM under the $19 offset; this is not the same as ST3's `$7D` default, which maps to Taud `$64` after subtracting $19. Converters must remap on both import and export.
|
||||
|
||||
@@ -537,12 +537,127 @@ function loadTaud(filePath, songIndex) {
|
||||
sys.free(ptr)
|
||||
|
||||
return {
|
||||
filePath, version, numSongs, numVoices, numPats,
|
||||
filePath, songIndex, version, numSongs, numVoices, numPats,
|
||||
bpm: bpmStored + 25, tickRate,
|
||||
patterns, cues, lastActiveCue
|
||||
}
|
||||
}
|
||||
|
||||
// Read header + song-table + (optional) sMet from a .taud and return a per-song
|
||||
// metadata list. Does NOT load patterns / cues / samples — that's loadTaud's job.
|
||||
// Returned shape:
|
||||
// { numSongs, projectName, songs: [
|
||||
// { index, numVoices, numPats, bpm, tickRate, songGlobalVolume,
|
||||
// songMixingVolume, mixerflags, name, composer, copyright } ] }
|
||||
function loadTaudSongList(filePath) {
|
||||
const fh = files.open(filePath)
|
||||
if (!fh.exists) throw Error(`taut: file not exists: ${filePath}`)
|
||||
const fileSize = fh.size
|
||||
const ptr = sys.malloc(fileSize)
|
||||
fh.pread(ptr, fileSize, 0)
|
||||
fh.close()
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if ((sys.peek(ptr + i) & 0xFF) !== TAUD_MAGIC[i]) {
|
||||
sys.free(ptr)
|
||||
throw Error(`taut: bad magic byte at ${i}`)
|
||||
}
|
||||
}
|
||||
|
||||
const numSongs = sys.peek(ptr + 9) & 0xFF
|
||||
const compSize = _peekU32LE(ptr, 10)
|
||||
const projOff = _peekU32LE(ptr, 14)
|
||||
const songTableOff = TAUD_HEADER_SIZE + compSize
|
||||
|
||||
const songs = new Array(numSongs)
|
||||
for (let i = 0; i < numSongs; i++) {
|
||||
const entryOff = songTableOff + i * TAUD_SONG_ENTRY
|
||||
songs[i] = {
|
||||
index: i,
|
||||
numVoices: sys.peek(ptr + entryOff + 4) & 0xFF,
|
||||
numPats: (sys.peek(ptr + entryOff + 5) & 0xFF) |
|
||||
((sys.peek(ptr + entryOff + 6) & 0xFF) << 8),
|
||||
bpm: ((sys.peek(ptr + entryOff + 7) & 0xFF) + 25),
|
||||
tickRate: sys.peek(ptr + entryOff + 8) & 0xFF,
|
||||
mixerflags: sys.peek(ptr + entryOff + 15) & 0xFF,
|
||||
songGlobalVolume: sys.peek(ptr + entryOff + 16) & 0xFF,
|
||||
songMixingVolume: sys.peek(ptr + entryOff + 17) & 0xFF,
|
||||
name: '',
|
||||
composer: '',
|
||||
copyright: '',
|
||||
}
|
||||
}
|
||||
|
||||
let projectName = ''
|
||||
|
||||
// Parse Project Data section (\x1ETaudPrJ) for song names / project name.
|
||||
// See terranmon.txt "Project Data" / "sMet" for the format.
|
||||
if (projOff !== 0 && projOff + 16 <= fileSize) {
|
||||
const projMagic = [0x1E,0x54,0x61,0x75,0x64,0x50,0x72,0x4A] // \x1ETaudPrJ
|
||||
let magicOK = true
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if ((sys.peek(ptr + projOff + i) & 0xFF) !== projMagic[i]) { magicOK = false; break }
|
||||
}
|
||||
if (magicOK) {
|
||||
let p = projOff + 16 // skip magic(8) + reserved(8)
|
||||
while (p + 8 <= fileSize) {
|
||||
const fc0 = sys.peek(ptr + p) & 0xFF
|
||||
const fc1 = sys.peek(ptr + p + 1) & 0xFF
|
||||
const fc2 = sys.peek(ptr + p + 2) & 0xFF
|
||||
const fc3 = sys.peek(ptr + p + 3) & 0xFF
|
||||
const secLen = _peekU32LE(ptr, p + 4)
|
||||
const payloadStart = p + 8
|
||||
if (payloadStart + secLen > fileSize) break
|
||||
|
||||
// 'PNam' = 0x50,0x4E,0x61,0x6D
|
||||
if (fc0 === 0x50 && fc1 === 0x4E && fc2 === 0x61 && fc3 === 0x6D) {
|
||||
let s = ''
|
||||
for (let k = 0; k < secLen; k++) {
|
||||
const b = sys.peek(ptr + payloadStart + k) & 0xFF
|
||||
if (b === 0) break
|
||||
s += String.fromCharCode(b)
|
||||
}
|
||||
projectName = s
|
||||
}
|
||||
// 'sMet' = 0x73,0x4D,0x65,0x74
|
||||
else if (fc0 === 0x73 && fc1 === 0x4D && fc2 === 0x65 && fc3 === 0x74) {
|
||||
let q = payloadStart
|
||||
const qEnd = payloadStart + secLen
|
||||
while (q + 5 <= qEnd) {
|
||||
const idx = sys.peek(ptr + q) & 0xFF
|
||||
const subLen = _peekU32LE(ptr, q + 1)
|
||||
const subStart = q + 5
|
||||
if (subStart + subLen > qEnd) break
|
||||
// payload: notation(u16) + beat_pri(u8) + beat_sec(u8) + name\0 + composer\0 + copyright\0
|
||||
let r = subStart + 4 // skip notation(2) + pri(1) + sec(1)
|
||||
const strs = []
|
||||
while (strs.length < 3 && r < subStart + subLen) {
|
||||
let s = ''
|
||||
while (r < subStart + subLen) {
|
||||
const b = sys.peek(ptr + r) & 0xFF; r++
|
||||
if (b === 0) break
|
||||
s += String.fromCharCode(b)
|
||||
}
|
||||
strs.push(s)
|
||||
}
|
||||
if (idx < numSongs) {
|
||||
if (strs[0] !== undefined) songs[idx].name = strs[0]
|
||||
if (strs[1] !== undefined) songs[idx].composer = strs[1]
|
||||
if (strs[2] !== undefined) songs[idx].copyright = strs[2]
|
||||
}
|
||||
q = subStart + subLen
|
||||
}
|
||||
}
|
||||
|
||||
p = payloadStart + secLen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sys.free(ptr)
|
||||
return { numSongs, projectName, songs }
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// GUI DEFINITION
|
||||
@@ -1355,7 +1470,10 @@ const buttonTexture = new gl.Texture(2, 28, buttonBytes)
|
||||
|
||||
font.setLowRom("A:/tvdos/bin/tautfont_low.chr")
|
||||
font.setHighRom("A:/tvdos/bin/tautfont_high.chr")
|
||||
const song = loadTaud(fullPathObj.full, 0)
|
||||
const songsMeta = loadTaudSongList(fullPathObj.full)
|
||||
let currentSongIndex = 0
|
||||
let projectSongCursor = 0
|
||||
let song = loadTaud(fullPathObj.full, currentSongIndex)
|
||||
|
||||
const voiceMutes = new Array(NUM_VOICES).fill(false)
|
||||
let timelineMuteSnapshot = null
|
||||
@@ -1382,6 +1500,47 @@ function applyMuteTransition(toPanel) {
|
||||
}
|
||||
}
|
||||
|
||||
// Switch the active song within the currently-open multi-song .taud file.
|
||||
// Re-uploads patterns+cues (and the shared sample/inst bin) to the audio
|
||||
// adapter, reloads song metadata, and resets per-song UI / playback state.
|
||||
function switchSong(newIndex) {
|
||||
if (newIndex < 0 || newIndex >= songsMeta.numSongs) return
|
||||
if (newIndex === currentSongIndex) return
|
||||
|
||||
stopPlayback()
|
||||
resetAudioDevice()
|
||||
|
||||
currentSongIndex = newIndex
|
||||
song = loadTaud(fullPathObj.full, newIndex)
|
||||
|
||||
taud.uploadTaudFile(fullPathObj.full, newIndex, PLAYHEAD)
|
||||
audio.setMasterVolume(PLAYHEAD, 255)
|
||||
audio.setMasterPan(PLAYHEAD, 128)
|
||||
initialTrackerMixerflags = audio.getTrackerMixerFlags(PLAYHEAD)
|
||||
initialGlobalVolume = audio.getSongGlobalVolume(PLAYHEAD)
|
||||
initialMixingVolume = audio.getSongMixingVolume(PLAYHEAD)
|
||||
|
||||
// Reset per-song UI state
|
||||
cueIdx = 0; cursorRow = 0; scrollRow = 0; voiceOff = 0; cursorVox = 0
|
||||
timelineColCursor = 0
|
||||
ordersCursor = 0; ordersScroll = 0; ordersColCursor = 0; ordersVoiceOff = 0
|
||||
patternIdx = 0; patternListScroll = 0
|
||||
patternGridRow = 0; patternGridScroll = 0; patternGridCol = 0
|
||||
simState = null; simStateKey = ''
|
||||
|
||||
for (let i = 0; i < NUM_VOICES; i++) {
|
||||
voiceMutes[i] = false
|
||||
audio.setVoiceMute(PLAYHEAD, i, false)
|
||||
}
|
||||
timelineMuteSnapshot = null
|
||||
|
||||
pbCue = 0; pbRow = 0
|
||||
previewActive = false
|
||||
|
||||
clampCursor(); clampVoice(); clampCue(); clampOrdersHoriz(); clampPatternIdx(); clampPatternGrid()
|
||||
drawAll()
|
||||
}
|
||||
|
||||
function redrawFull() { drawAll() }
|
||||
|
||||
function redrawPanel() {
|
||||
@@ -2270,12 +2429,13 @@ function drawProjectContents(wo) {
|
||||
|
||||
let mixerflag = initialTrackerMixerflags
|
||||
let toneModeStr = ['Linear pitch','Amiga pitch','Linear freq',''][mixerflag & 3]
|
||||
let intpModeStr = ['Fast Sinc','No intp.','A500 intp.','A1200 intp.'][(mixerflag >>> 2) & 3]
|
||||
let intpModeStr = ['Default','None','A500','A1200','SNES','DPCM','',''][(mixerflag >>> 2) & 7]
|
||||
let flagStrSelected = [toneModeStr, intpModeStr]
|
||||
|
||||
|
||||
let projMeta = {
|
||||
Filename: fullPathObj.string.split('\\').last(),
|
||||
ProjName: songsMeta.projectName || '(unnamed)',
|
||||
Patterns: `${song.numPats}/4095 ($${song.numPats.hex03()})`,
|
||||
Cues: `${song.lastActiveCue}/1024 ($${song.lastActiveCue.hex03()})`,
|
||||
Notation: pitchTablePresets[PITCH_PRESET_IDX].name,
|
||||
@@ -2291,13 +2451,127 @@ function drawProjectContents(wo) {
|
||||
con.color_pair(colVoiceHdr, colBLACK); print(value)
|
||||
})
|
||||
|
||||
drawProjectSongList()
|
||||
|
||||
con.color_pair(colStatus, 255) // reset colour
|
||||
}
|
||||
|
||||
const PROJ_SONGLIST_Y = PTNVIEW_OFFSET_Y + 9 // header row of the song list
|
||||
const PROJ_SONGLIST_X = 2
|
||||
|
||||
function projectSongListRowsVisible() {
|
||||
return Math.max(0, SCRH - PROJ_SONGLIST_Y - 1)
|
||||
}
|
||||
|
||||
let projectSongScroll = 0
|
||||
|
||||
function clampProjectSongCursor() {
|
||||
const n = songsMeta.numSongs
|
||||
if (projectSongCursor < 0) projectSongCursor = 0
|
||||
if (projectSongCursor > n - 1) projectSongCursor = n - 1
|
||||
const rowsVis = projectSongListRowsVisible()
|
||||
if (projectSongCursor < projectSongScroll) projectSongScroll = projectSongCursor
|
||||
else if (projectSongCursor >= projectSongScroll + rowsVis)
|
||||
projectSongScroll = projectSongCursor - rowsVis + 1
|
||||
if (projectSongScroll < 0) projectSongScroll = 0
|
||||
}
|
||||
|
||||
function drawProjectSongList() {
|
||||
const headerY = PROJ_SONGLIST_Y
|
||||
con.move(headerY, PROJ_SONGLIST_X)
|
||||
con.color_pair(colStatus, 255)
|
||||
print(`Songs: ${songsMeta.numSongs}`)
|
||||
|
||||
const rowsVis = projectSongListRowsVisible()
|
||||
const colW = SCRW - PROJ_SONGLIST_X - 1
|
||||
for (let row = 0; row < rowsVis; row++) {
|
||||
const idx = projectSongScroll + row
|
||||
const y = headerY + 1 + row
|
||||
con.move(y, PROJ_SONGLIST_X)
|
||||
if (idx >= songsMeta.numSongs) {
|
||||
con.color_pair(colStatus, colBackPtn)
|
||||
print(' '.repeat(colW))
|
||||
continue
|
||||
}
|
||||
const s = songsMeta.songs[idx]
|
||||
const isActive = (idx === currentSongIndex)
|
||||
const isSel = (idx === projectSongCursor)
|
||||
const back = isSel ? colHighlight : colBackPtn
|
||||
|
||||
const marker = isActive ? sym.playhead : ' '
|
||||
const numStr = (idx + 1).toString().padStart(2, '0')
|
||||
const nameRaw = s.name || `(song ${idx + 1})`
|
||||
const META_W = 28
|
||||
const nameW = Math.max(4, colW - 6 - META_W)
|
||||
const nameStr = nameRaw.length > nameW ? nameRaw.substring(0, nameW) : nameRaw.padEnd(nameW)
|
||||
const meta = `V${s.numVoices.dec02()} P${s.numPats.toString().padStart(3,'0')}` +
|
||||
` BPM${s.bpm.toString().padStart(3,'0')} tk${s.tickRate.dec02()}` +
|
||||
` g${s.songGlobalVolume.hex02()}`
|
||||
|
||||
con.color_pair(isActive ? colBrand : colVoiceHdr, back)
|
||||
print(`${marker} ${numStr} ${nameStr} ${meta}`)
|
||||
}
|
||||
|
||||
// scroll indicator on the right edge
|
||||
if (songsMeta.numSongs > rowsVis) {
|
||||
const maxScroll = songsMeta.numSongs - rowsVis
|
||||
const indPos = (maxScroll === 0) ? 0 : ((projectSongScroll * (rowsVis - 1) / maxScroll) | 0)
|
||||
for (let r = 0; r < rowsVis; r++) {
|
||||
con.move(headerY + 1 + r, SCRW)
|
||||
con.color_pair(colStatus, colBackPtn)
|
||||
print(r === indPos ? sym.ticked : sym.unticked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function projectInput(wo, event) {
|
||||
if (event[0] !== 'key_down') return
|
||||
const keysym = event[1]
|
||||
const keyJustHit = (1 == event[2])
|
||||
const shiftDown = (event.includes(59) || event.includes(60))
|
||||
const moveDelta = shiftDown ? 4 : 1
|
||||
|
||||
if (playbackMode !== PLAYMODE_NONE) {
|
||||
if (keysym === ' ' || (keyJustHit && shiftDown && (event.includes(keys.Y) || event.includes(keys.O)))) {
|
||||
stopPlayback(); drawAlwaysOnElems()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!keyJustHit) return
|
||||
|
||||
if (keysym === '<UP>') {
|
||||
projectSongCursor -= moveDelta; clampProjectSongCursor(); redrawPanel(); return
|
||||
}
|
||||
if (keysym === '<DOWN>') {
|
||||
projectSongCursor += moveDelta; clampProjectSongCursor(); redrawPanel(); return
|
||||
}
|
||||
if (keysym === '<PAGE_UP>') {
|
||||
projectSongCursor -= projectSongListRowsVisible(); clampProjectSongCursor(); redrawPanel(); return
|
||||
}
|
||||
if (keysym === '<PAGE_DOWN>') {
|
||||
projectSongCursor += projectSongListRowsVisible(); clampProjectSongCursor(); redrawPanel(); return
|
||||
}
|
||||
if (keysym === '<HOME>') {
|
||||
projectSongCursor = 0; clampProjectSongCursor(); redrawPanel(); return
|
||||
}
|
||||
if (keysym === '<END>') {
|
||||
projectSongCursor = songsMeta.numSongs - 1; clampProjectSongCursor(); redrawPanel(); return
|
||||
}
|
||||
if (keysym === '\n') {
|
||||
if (projectSongCursor !== currentSongIndex) switchSong(projectSongCursor)
|
||||
return
|
||||
}
|
||||
if (keysym === ' ') {
|
||||
stopPlayback(); drawAlwaysOnElems(); return
|
||||
}
|
||||
}
|
||||
|
||||
function externalPanelInput(wo, event) {}
|
||||
|
||||
const panelSamples = new win.WindowObject(1, PTNVIEW_OFFSET_Y, SCRW, PTNVIEW_HEIGHT, externalPanelInput, makeExternalPanelDraw('taut_sampleedit'), undefined, ()=>{})
|
||||
const panelInstrmnt = new win.WindowObject(1, PTNVIEW_OFFSET_Y, SCRW, PTNVIEW_HEIGHT, externalPanelInput, makeExternalPanelDraw('taut_instredit'), undefined, ()=>{})
|
||||
const panelProject = new win.WindowObject(1, PTNVIEW_OFFSET_Y, SCRW, PTNVIEW_HEIGHT, externalPanelInput, drawProjectContents, undefined, ()=>{})
|
||||
const panelProject = new win.WindowObject(1, PTNVIEW_OFFSET_Y, SCRW, PTNVIEW_HEIGHT, projectInput, drawProjectContents, undefined, ()=>{})
|
||||
const panelFile = new win.WindowObject(1, PTNVIEW_OFFSET_Y, SCRW, PTNVIEW_HEIGHT, externalPanelInput, makeExternalPanelDraw('taut_fileop'), undefined, ()=>{})
|
||||
|
||||
const panels = [panelTimeline, panelOrders, panelPatterns, panelSamples, panelInstrmnt, panelProject, panelFile]
|
||||
@@ -2811,12 +3085,12 @@ clampCursor(); clampVoice(); clampCue(); clampOrdersHoriz(); clampPatternIdx();
|
||||
drawAll()
|
||||
|
||||
resetAudioDevice()
|
||||
taud.uploadTaudFile(fullPathObj.full, 0, PLAYHEAD)
|
||||
taud.uploadTaudFile(fullPathObj.full, currentSongIndex, PLAYHEAD)
|
||||
audio.setMasterVolume(PLAYHEAD, 255)
|
||||
audio.setMasterPan(PLAYHEAD, 128)
|
||||
const initialTrackerMixerflags = audio.getTrackerMixerFlags(PLAYHEAD)
|
||||
const initialGlobalVolume = audio.getSongGlobalVolume(PLAYHEAD)
|
||||
const initialMixingVolume = audio.getSongMixingVolume(PLAYHEAD)
|
||||
let initialTrackerMixerflags = audio.getTrackerMixerFlags(PLAYHEAD)
|
||||
let initialGlobalVolume = audio.getSongGlobalVolume(PLAYHEAD)
|
||||
let initialMixingVolume = audio.getSongMixingVolume(PLAYHEAD)
|
||||
|
||||
function isExternalPanel(p) {
|
||||
return p === VIEW_SAMPLES || p === VIEW_INSTRMNT || p === VIEW_FILE
|
||||
|
||||
Reference in New Issue
Block a user