mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-08 06:14:04 +09:00
audio: getFreePlayhead()
This commit is contained in:
@@ -57,13 +57,17 @@ let decodedLength = 0
|
||||
|
||||
const bufRealTimeLen = 36 // one MP2 frame at 32 kHz ≈ 36 ms
|
||||
|
||||
audio.resetParams(0)
|
||||
audio.purgeQueue(0)
|
||||
audio.setPcmMode(0)
|
||||
audio.setPcmQueueCapacityIndex(0, 2)
|
||||
const QUEUE_MAX = audio.getPcmQueueCapacity(0)
|
||||
audio.setMasterVolume(0, 255)
|
||||
audio.play(0)
|
||||
// Occupy the first idle playhead rather than always grabbing #0, so playback
|
||||
// doesn't cut off audio already running on another playhead. Falls back to #0
|
||||
// when all four are busy.
|
||||
const PLAYHEAD = audio.getFreePlayhead(0)
|
||||
audio.resetParams(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
audio.setPcmMode(PLAYHEAD)
|
||||
audio.setPcmQueueCapacityIndex(PLAYHEAD, 2)
|
||||
const QUEUE_MAX = audio.getPcmQueueCapacity(PLAYHEAD)
|
||||
audio.setMasterVolume(PLAYHEAD, 255)
|
||||
audio.play(PLAYHEAD)
|
||||
audio.mp2Init()
|
||||
|
||||
function bytesToSec(i) { return i / (FRAME_SIZE * 1000 / bufRealTimeLen) }
|
||||
@@ -91,8 +95,8 @@ try {
|
||||
gui.audioFeedPcm(mp2VisScratch, MP2_VIS_SAMPLE_COUNT)
|
||||
}
|
||||
|
||||
if (audio.getPosition(0) >= QUEUE_MAX) {
|
||||
while (audio.getPosition(0) >= (QUEUE_MAX >>> 1)) {
|
||||
if (audio.getPosition(PLAYHEAD) >= QUEUE_MAX) {
|
||||
while (audio.getPosition(PLAYHEAD) >= (QUEUE_MAX >>> 1)) {
|
||||
if (interactive) gui.audioRender()
|
||||
sys.sleep(bufRealTimeLen)
|
||||
}
|
||||
|
||||
@@ -97,10 +97,14 @@ let startTime = sys.nanoTime()
|
||||
let framesRead = 0
|
||||
let audioFired = false
|
||||
|
||||
audio.resetParams(0)
|
||||
audio.purgeQueue(0)
|
||||
audio.setPcmMode(0)
|
||||
audio.setMasterVolume(0, 255)
|
||||
// Occupy the first idle playhead rather than always grabbing #0, so playback
|
||||
// doesn't cut off audio already running on another playhead. Falls back to #0
|
||||
// when all four are busy.
|
||||
const PLAYHEAD = audio.getFreePlayhead(0)
|
||||
audio.resetParams(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
audio.setPcmMode(PLAYHEAD)
|
||||
audio.setMasterVolume(PLAYHEAD, 255)
|
||||
|
||||
function s16StTou8St(inPtrL, inPtrR, outPtr, length) {
|
||||
for (let k = 0; k < length; k+=2) {
|
||||
@@ -204,7 +208,7 @@ while (!stopPlay && seqread.getReadCount() < FILE_LENGTH) {
|
||||
|
||||
// defer audio playback until a first frame is sent
|
||||
if (!audioFired) {
|
||||
audio.play(0)
|
||||
audio.play(PLAYHEAD)
|
||||
audioFired = true
|
||||
}
|
||||
|
||||
@@ -263,7 +267,7 @@ while (!stopPlay && seqread.getReadCount() < FILE_LENGTH) {
|
||||
|
||||
// defer audio playback until a first frame is sent
|
||||
if (!audioFired) {
|
||||
audio.play(0)
|
||||
audio.play(PLAYHEAD)
|
||||
audioFired = true
|
||||
}
|
||||
|
||||
@@ -326,9 +330,9 @@ while (!stopPlay && seqread.getReadCount() < FILE_LENGTH) {
|
||||
// RAW PCM packets (decode on the fly)
|
||||
else if (packetType == 0x1000 || packetType == 0x1001) {
|
||||
let frame = seqread.readBytes(readLength)
|
||||
audio.putPcmDataByPtr(0, frame, readLength, 0)
|
||||
audio.setSampleUploadLength(0, readLength)
|
||||
audio.startSampleUpload(0)
|
||||
audio.putPcmDataByPtr(PLAYHEAD, frame, readLength, 0)
|
||||
audio.setSampleUploadLength(PLAYHEAD, readLength)
|
||||
audio.startSampleUpload(PLAYHEAD)
|
||||
sys.free(frame)
|
||||
}
|
||||
else {
|
||||
@@ -382,14 +386,14 @@ finally {
|
||||
if (AUDIO_QUEUE_BYTES > 0 && AUDIO_QUEUE_LENGTH > 1) {
|
||||
|
||||
}
|
||||
//audio.stop(0)
|
||||
//audio.stop(PLAYHEAD)
|
||||
|
||||
let timeTook = (endTime - startTime) / 1000000000.0
|
||||
|
||||
//println(`Actual FPS: ${framesRendered / timeTook}`)
|
||||
|
||||
audio.stop(0)
|
||||
audio.purgeQueue(0)
|
||||
audio.stop(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
|
||||
if (interactive) {
|
||||
con.clear()
|
||||
|
||||
@@ -23,10 +23,14 @@ function bytesToSec(i) { return i / byterate }
|
||||
seqread.prepare(filePath)
|
||||
|
||||
const readPtr = sys.malloc(BLOCK_SIZE)
|
||||
audio.resetParams(0)
|
||||
audio.purgeQueue(0)
|
||||
audio.setPcmMode(0)
|
||||
audio.setMasterVolume(0, 255)
|
||||
// Occupy the first idle playhead rather than always grabbing #0, so playback
|
||||
// doesn't cut off audio already running on another playhead. Falls back to #0
|
||||
// when all four are busy.
|
||||
const PLAYHEAD = audio.getFreePlayhead(0)
|
||||
audio.resetParams(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
audio.setPcmMode(PLAYHEAD)
|
||||
audio.setMasterVolume(PLAYHEAD, 255)
|
||||
|
||||
if (interactive) {
|
||||
gui.audioInit({
|
||||
@@ -42,7 +46,7 @@ try {
|
||||
while (!stopPlay && seqread.getReadCount() < FILE_SIZE && readLength > 0) {
|
||||
if (interactive && gui.audioIsExitRequested()) { stopPlay = true; break }
|
||||
|
||||
const queueSize = audio.getPosition(0)
|
||||
const queueSize = audio.getPosition(PLAYHEAD)
|
||||
if (queueSize <= 1) {
|
||||
for (let repeat = QUEUE_MAX - queueSize; repeat > 0; repeat--) {
|
||||
const remainingBytes = FILE_SIZE - seqread.getReadCount()
|
||||
@@ -54,13 +58,13 @@ try {
|
||||
// Raw PCMu8 stereo — sampleCount = bytes / 2.
|
||||
if (interactive) gui.audioFeedPcm(readPtr, readLength >> 1)
|
||||
|
||||
audio.putPcmDataByPtr(0, readPtr, readLength, 0)
|
||||
audio.setSampleUploadLength(0, readLength)
|
||||
audio.startSampleUpload(0)
|
||||
audio.putPcmDataByPtr(PLAYHEAD, readPtr, readLength, 0)
|
||||
audio.setSampleUploadLength(PLAYHEAD, readLength)
|
||||
audio.startSampleUpload(PLAYHEAD)
|
||||
|
||||
if (repeat > 1) sys.sleep(10)
|
||||
}
|
||||
audio.play(0)
|
||||
audio.play(PLAYHEAD)
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
|
||||
@@ -109,13 +109,17 @@ function bytesToSec(i) {
|
||||
return Math.round((i / FILE_SIZE) * (FILE_SIZE / AVG_CHUNK_SIZE) * (bufRealTimeLen / 1000))
|
||||
}
|
||||
|
||||
audio.resetParams(0)
|
||||
audio.purgeQueue(0)
|
||||
audio.setPcmMode(0)
|
||||
audio.setPcmQueueCapacityIndex(0, 2)
|
||||
const QUEUE_MAX = audio.getPcmQueueCapacity(0)
|
||||
audio.setMasterVolume(0, 255)
|
||||
audio.play(0)
|
||||
// Occupy the first idle playhead rather than always grabbing #0, so playback
|
||||
// doesn't cut off audio already running on another playhead. Falls back to #0
|
||||
// when all four are busy.
|
||||
const PLAYHEAD = audio.getFreePlayhead(0)
|
||||
audio.resetParams(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
audio.setPcmMode(PLAYHEAD)
|
||||
audio.setPcmQueueCapacityIndex(PLAYHEAD, 2)
|
||||
const QUEUE_MAX = audio.getPcmQueueCapacity(PLAYHEAD)
|
||||
audio.setMasterVolume(PLAYHEAD, 255)
|
||||
audio.play(PLAYHEAD)
|
||||
|
||||
if (interactive) {
|
||||
gui.audioInit({
|
||||
@@ -163,7 +167,7 @@ try {
|
||||
filebuf.readBytes(7 + payloadSize, TAD_INPUT_ADDR)
|
||||
|
||||
audio.tadDecode()
|
||||
audio.tadUploadDecoded(0, sampleCount)
|
||||
audio.tadUploadDecoded(PLAYHEAD, sampleCount)
|
||||
// After upload tadDecodedBin still holds the chunk until the next
|
||||
// tadDecode call, so it's safe to keep slicing samples out of it
|
||||
// during the playback wait below.
|
||||
|
||||
@@ -304,9 +304,13 @@ function parseTaud(path, songIndex) {
|
||||
const song = parseTaud(filePath, songArg)
|
||||
|
||||
// ── Hand the file to the audio adapter ─────────────────────────────────────
|
||||
audio.resetParams(0)
|
||||
audio.purgeQueue(0)
|
||||
taud.uploadTaudFile(filePath, songArg, 0)
|
||||
// Occupy the first idle playhead rather than always grabbing #0, so launching
|
||||
// playtaud doesn't cut off music already playing on another playhead. Falls
|
||||
// back to #0 when all four are busy.
|
||||
const PLAYHEAD = audio.getFreePlayhead(0)
|
||||
audio.resetParams(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
taud.uploadTaudFile(filePath, songArg, PLAYHEAD)
|
||||
|
||||
// ── Instrument archetype classification ─────────────────────────────────────
|
||||
//
|
||||
@@ -565,8 +569,8 @@ function pad(n, w) {
|
||||
|
||||
let lastStatus = ''
|
||||
function drawStatus(curCue) {
|
||||
const bpm = audio.getBPM(0) || song.bpm
|
||||
const tick = audio.getTickRate(0) || song.tickRate
|
||||
const bpm = audio.getBPM(PLAYHEAD) || song.bpm
|
||||
const tick = audio.getTickRate(PLAYHEAD) || song.tickRate
|
||||
const cueStr = pad(curCue, 3) + '/' + pad(song.lastCue, 3)
|
||||
const s = 'BPM ' + pad(bpm,3) + ' Tick ' + pad(tick,2) +
|
||||
' Voices ' + pad(song.numVoices,2) + ' Cue ' + cueStr
|
||||
@@ -714,7 +718,7 @@ function spawnEventsForRow(cueIdx, rowIdx) {
|
||||
const arch = archByInst[effInst]
|
||||
let pan = 128
|
||||
if (panSel === 0) pan = (panVal / 63 * 255) | 0
|
||||
const livePan = audio.getVoiceEffectivePan(0, v)
|
||||
const livePan = audio.getVoiceEffectivePan(PLAYHEAD, v)
|
||||
if (typeof livePan === 'number' && livePan !== 128) pan = livePan
|
||||
// Replace whatever was in voice v's slot. peakVol seeds at 0 and is
|
||||
// tracked per-frame so the colour ramp normalises by attack peak,
|
||||
@@ -1058,11 +1062,11 @@ function renderEvents() {
|
||||
// The engine's `active` flag is the source of truth — set by note-on,
|
||||
// cleared by note-cut, sample-end, envelope-end-of-decay, or NNA cut.
|
||||
// Once it drops, the voice is genuinely silent so the visual goes too.
|
||||
if (!audio.getVoiceActive(0, v)) { events[v] = null; continue }
|
||||
if (!audio.getVoiceActive(PLAYHEAD, v)) { events[v] = null; continue }
|
||||
|
||||
const liveVol = audio.getVoiceEffectiveVolume(0, v) || 0
|
||||
const livePan = audio.getVoiceEffectivePan(0, v)
|
||||
const liveNote = audio.getVoiceNote(0, v)
|
||||
const liveVol = audio.getVoiceEffectiveVolume(PLAYHEAD, v) || 0
|
||||
const livePan = audio.getVoiceEffectivePan(PLAYHEAD, v)
|
||||
const liveNote = audio.getVoiceNote(PLAYHEAD, v)
|
||||
|
||||
if (liveVol > ev.peakVol) ev.peakVol = liveVol
|
||||
ev.ageFrames++
|
||||
@@ -1094,10 +1098,10 @@ function drawStereo() {
|
||||
const W = LANE_W
|
||||
const bins = new Float32Array(W)
|
||||
for (let v = 0; v < song.numVoices; v++) {
|
||||
if (!audio.getVoiceActive(0, v)) continue
|
||||
const vol = Math.pow(audio.getVoiceEffectiveVolume(0, v) || 0, 0.125)
|
||||
if (!audio.getVoiceActive(PLAYHEAD, v)) continue
|
||||
const vol = Math.pow(audio.getVoiceEffectiveVolume(PLAYHEAD, v) || 0, 0.125)
|
||||
if (vol <= 0) continue
|
||||
const pan = audio.getVoiceEffectivePan(0, v)
|
||||
const pan = audio.getVoiceEffectivePan(PLAYHEAD, v)
|
||||
let col = Math.round((pan / 255) * (W - 1))
|
||||
if (col < 0) col = 0
|
||||
if (col >= W) col = W - 1
|
||||
@@ -1143,7 +1147,7 @@ function drawTickLights(tickInRow, tickRate) {
|
||||
// Voice activity counter on the right.
|
||||
let nActive = 0
|
||||
for (let v = 0; v < song.numVoices; v++) {
|
||||
if (audio.getVoiceActive(0, v)) nActive++
|
||||
if (audio.getVoiceActive(PLAYHEAD, v)) nActive++
|
||||
}
|
||||
colour(COL_DIM, COL_BG)
|
||||
const s = 'ACTIVE ' + pad(nActive, 2) + '/' + pad(song.numVoices, 2)
|
||||
@@ -1157,10 +1161,10 @@ drawStatus(0)
|
||||
drawOrderStrip(0)
|
||||
|
||||
// ── Playback ────────────────────────────────────────────────────────────────
|
||||
audio.setCuePosition(0, 0)
|
||||
audio.setTrackerRow(0, 0)
|
||||
audio.setMasterVolume(0, 255)
|
||||
audio.play(0)
|
||||
audio.setCuePosition(PLAYHEAD, 0)
|
||||
audio.setTrackerRow(PLAYHEAD, 0)
|
||||
audio.setMasterVolume(PLAYHEAD, 255)
|
||||
audio.play(PLAYHEAD)
|
||||
|
||||
let stopReq = false
|
||||
let errorlevel = 0
|
||||
@@ -1174,13 +1178,13 @@ let errorlevel = 0
|
||||
let ticksPerRow = Math.max(1, song.tickRate)
|
||||
let synthTick = 0 // tick within current row, 0..ticksPerRow-1
|
||||
try {
|
||||
while (audio.isPlaying(0) && !stopReq) {
|
||||
while (audio.isPlaying(PLAYHEAD) && !stopReq) {
|
||||
// Backspace polling (mirrors playtad).
|
||||
sys.poke(-40, 1)
|
||||
if (sys.peek(-41) === 67) stopReq = true
|
||||
|
||||
const curCue = audio.getCuePosition(0)
|
||||
const curRow = audio.getTrackerRow(0)
|
||||
const curCue = audio.getCuePosition(PLAYHEAD)
|
||||
const curRow = audio.getTrackerRow(PLAYHEAD)
|
||||
if (curCue !== lastSeenCue || curRow !== lastSeenRow) {
|
||||
// Row boundary — spawn new events, advance the matrix background
|
||||
// (scrolls within a cue, wraps to the top on a cue change), reset
|
||||
@@ -1192,7 +1196,7 @@ try {
|
||||
synthTick = 0
|
||||
// Pull a fresh tickRate read here in case a T effect changed it
|
||||
// mid-song.
|
||||
ticksPerRow = Math.max(1, audio.getTickRate(0) || song.tickRate)
|
||||
ticksPerRow = Math.max(1, audio.getTickRate(PLAYHEAD) || song.tickRate)
|
||||
} else {
|
||||
// Same row — advance the synthetic tick counter against wall time.
|
||||
// Tick period (ms) = (60000 / BPM) / 24 ... but the spec is
|
||||
@@ -1212,7 +1216,7 @@ try {
|
||||
drawStereo()
|
||||
drawTickLights(synthTick, ticksPerRow)
|
||||
|
||||
sys.sleep((2500 / audio.getBPM(0))|0) // one visual frame = one tick
|
||||
sys.sleep((2500 / audio.getBPM(PLAYHEAD))|0) // one visual frame = one tick
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
@@ -1220,7 +1224,7 @@ catch (e) {
|
||||
errorlevel = 1
|
||||
}
|
||||
finally {
|
||||
audio.stop(0)
|
||||
audio.stop(PLAYHEAD)
|
||||
con.move(ROW_BOT_BORDER + 1, 1)
|
||||
con.curs_set(1)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const ADDRESSING_INTERNAL = 0x02
|
||||
const SND_BASE_ADDR = audio.getBaseAddr()
|
||||
const SND_MEM_ADDR = audio.getMemAddr()
|
||||
const pcm = require("pcm")
|
||||
const AUDIO_DEVICE = 0
|
||||
const AUDIO_DEVICE = audio.getFreePlayhead(0)
|
||||
const MP2_FRAME_SIZE = [144,216,252,288,360,432,504,576,720,864,1008,1152,1440,1728]
|
||||
const TAV_TEMPORAL_LEVELS = 2
|
||||
|
||||
|
||||
@@ -100,10 +100,14 @@ graphics.clearPixels(0)
|
||||
graphics.clearPixels2(0)
|
||||
|
||||
// Initialize audio
|
||||
audio.resetParams(0)
|
||||
audio.purgeQueue(0)
|
||||
audio.setPcmMode(0)
|
||||
audio.setMasterVolume(0, 255)
|
||||
// Occupy the first idle playhead rather than always grabbing #0, so playback
|
||||
// doesn't cut off audio already running on another playhead. Falls back to #0
|
||||
// when all four are busy.
|
||||
const PLAYHEAD = audio.getFreePlayhead(0)
|
||||
audio.resetParams(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
audio.setPcmMode(PLAYHEAD)
|
||||
audio.setMasterVolume(PLAYHEAD, 255)
|
||||
|
||||
// set colour zero as half-opaque black
|
||||
graphics.setPalette(0, 0, 0, 0, 9)
|
||||
@@ -791,14 +795,14 @@ try {
|
||||
if (isInterlaced) {
|
||||
// fire audio after frame 1
|
||||
if (!audioFired && frameCount > 0) {
|
||||
audio.play(0)
|
||||
audio.play(PLAYHEAD)
|
||||
audioFired = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
// fire audio after frame 0
|
||||
if (!audioFired) {
|
||||
audio.play(0)
|
||||
audio.play(PLAYHEAD)
|
||||
audioFired = true
|
||||
}
|
||||
}
|
||||
@@ -900,8 +904,8 @@ finally {
|
||||
if (PREV_FIELD_BUFFER > 0) sys.free(PREV_FIELD_BUFFER)
|
||||
if (NEXT_FIELD_BUFFER > 0) sys.free(NEXT_FIELD_BUFFER)
|
||||
|
||||
audio.stop(0)
|
||||
audio.purgeQueue(0)
|
||||
audio.stop(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
|
||||
if (interactive) {
|
||||
//con.clear()
|
||||
|
||||
@@ -131,16 +131,20 @@ try {
|
||||
readPtr = sys.malloc(pcmType === 2 ? BLOCK_SIZE : BLOCK_SIZE * bitsPerSample / 8)
|
||||
decodePtr = sys.malloc(BLOCK_SIZE * pcm.HW_SAMPLING_RATE / samplingRate)
|
||||
|
||||
audio.resetParams(0)
|
||||
audio.purgeQueue(0)
|
||||
audio.setPcmMode(0)
|
||||
audio.setMasterVolume(0, 255)
|
||||
// Occupy the first idle playhead rather than always grabbing #0, so
|
||||
// playback doesn't cut off audio already running on another playhead.
|
||||
// Falls back to #0 when all four are busy.
|
||||
const PLAYHEAD = audio.getFreePlayhead(0)
|
||||
audio.resetParams(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
audio.setPcmMode(PLAYHEAD)
|
||||
audio.setMasterVolume(PLAYHEAD, 255)
|
||||
|
||||
let readLength = 1
|
||||
while (!stopPlay && seqread.getReadCount() < startOffset + chunkSize && readLength > 0) {
|
||||
if (interactive && gui.audioIsExitRequested()) { stopPlay = true; break }
|
||||
|
||||
if (audio.getPosition(0) <= 1) {
|
||||
if (audio.getPosition(PLAYHEAD) <= 1) {
|
||||
for (let repeat = 0; repeat < QUEUE_MAX; repeat++) {
|
||||
const remainingBytes = FILE_SIZE - 8 - seqread.getReadCount()
|
||||
readLength = (remainingBytes < INFILE_BLOCK_SIZE) ? remainingBytes : INFILE_BLOCK_SIZE
|
||||
@@ -153,13 +157,13 @@ try {
|
||||
// before queueing — the buffer is reused next iteration.
|
||||
if (interactive) gui.audioFeedPcm(decodePtr, decodedSampleLength >> 1)
|
||||
|
||||
audio.putPcmDataByPtr(0, decodePtr, decodedSampleLength, 0)
|
||||
audio.setSampleUploadLength(0, decodedSampleLength)
|
||||
audio.startSampleUpload(0)
|
||||
audio.putPcmDataByPtr(PLAYHEAD, decodePtr, decodedSampleLength, 0)
|
||||
audio.setSampleUploadLength(PLAYHEAD, decodedSampleLength)
|
||||
audio.startSampleUpload(PLAYHEAD)
|
||||
|
||||
sys.spin()
|
||||
}
|
||||
audio.play(0)
|
||||
audio.play(PLAYHEAD)
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
|
||||
@@ -5241,7 +5241,10 @@ const panels = [panelTimeline, panelOrders, panelPatterns, panelSamples, panelIn
|
||||
// PLAYBACK STATE
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const PLAYHEAD = 0
|
||||
// Occupy the first idle playhead rather than always grabbing #0, so launching
|
||||
// taut doesn't cut off music already playing on another playhead. Falls back to
|
||||
// #0 when all four are busy.
|
||||
const PLAYHEAD = audio.getFreePlayhead(0)
|
||||
|
||||
// Scratch cue slot used for pattern-only preview; beyond any real cue the song uses
|
||||
const PREVIEW_CUE_IDX = NUM_CUES - 1
|
||||
|
||||
@@ -1162,6 +1162,7 @@ This guide deliberately stops at that overview. The Taud file format, the instru
|
||||
\1\formalsynopsis{play}{playhead: Int}{Starts the playhead.}
|
||||
\1\formalsynopsis{stop}{playhead: Int}{Stops the playhead.}
|
||||
\1\formalsynopsis{isPlaying}{playhead: Int}[Boolean]{Whether the playhead is currently playing.}
|
||||
\1\formalsynopsis{getFreePlayhead}{fallback: Int}[Int]{Returns the lowest-numbered playhead that is not currently playing.}
|
||||
\1\formalsynopsis{getPosition}{playhead: Int}[Int]{Current playback position of the playhead.}
|
||||
\1\formalsynopsis{setMasterVolume}{playhead: Int, volume: Int}{Sets the playhead's output volume.}
|
||||
\1\formalsynopsis{setMasterPan}{playhead: Int, pan: Int}{Sets the playhead's stereo pan.}
|
||||
|
||||
@@ -67,6 +67,17 @@ class AudioJSR223Delegate(private val vm: VM) {
|
||||
fun stop(playhead: Int) { getPlayhead(playhead)?.isPlaying = false }
|
||||
fun isPlaying(playhead: Int) = getPlayhead(playhead)?.isPlaying
|
||||
|
||||
/** Lowest-numbered playhead that is not currently playing, so a player app can
|
||||
* "occupy" an idle playhead instead of always clobbering playhead 0. Returns
|
||||
* [fallback] when every playhead is busy (or no audio device is present). */
|
||||
fun getFreePlayhead(fallback: Int): Int {
|
||||
val playheads = getFirstSnd()?.playheads ?: return fallback
|
||||
for (i in playheads.indices) {
|
||||
if (!playheads[i].isPlaying) return i
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// fun setPosition(playhead: Int, pos: Int) { getPlayhead(playhead)?.position = pos and 65535 }
|
||||
fun getPosition(playhead: Int) = getPlayhead(playhead)?.position
|
||||
|
||||
|
||||
Reference in New Issue
Block a user