mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-10 23:04:04 +09:00
playmov: coloured ascii mode
This commit is contained in:
@@ -11,13 +11,14 @@
|
|||||||
// [draw] dec.blit() (graphics) OR sampleGray + aa.mjs (ASCII),
|
// [draw] dec.blit() (graphics) OR sampleGray + aa.mjs (ASCII),
|
||||||
// then subtitle overlay + playgui chrome
|
// then subtitle overlay + playgui chrome
|
||||||
//
|
//
|
||||||
// Usage: playmov FILE [-i] [-ascii] [-deblock] [-boundaryaware]
|
// Usage: playmov FILE [-i] [-ascii] [-colour] [-deblock] [-boundaryaware]
|
||||||
// [-deinterlace=yadif|bwdif] [-debug-mv]
|
// [-deinterlace=yadif|bwdif] [-debug-mv]
|
||||||
// -i interactive (controls + on-screen chrome)
|
// -i interactive (controls + on-screen chrome)
|
||||||
// -ascii start in ASCII-render mode (proves the framebuffer flow; aa.mjs)
|
// -ascii start in ASCII-render mode (proves the framebuffer flow; aa.mjs)
|
||||||
|
// -colour colourise ASCII glyphs from the video (implies -ascii); -color alias
|
||||||
// (others forwarded to the TEV backend, matching playtev)
|
// (others forwarded to the TEV backend, matching playtev)
|
||||||
// Controls: Bksp quit | Space pause | Left/Right seek | Up/Down volume
|
// Controls: Bksp quit | Space pause | Left/Right seek | Up/Down volume
|
||||||
// PgUp/PgDn cue prev/next | A toggle ASCII
|
// PgUp/PgDn cue prev/next | A toggle ASCII | C toggle colour
|
||||||
|
|
||||||
const mediadec = require("mediadec")
|
const mediadec = require("mediadec")
|
||||||
const gui = require("playgui")
|
const gui = require("playgui")
|
||||||
@@ -40,29 +41,120 @@ const COL_TRANSPARENT = 255
|
|||||||
const COL_PURE_BLACK = 240
|
const COL_PURE_BLACK = 240
|
||||||
const GUI_BG = 0
|
const GUI_BG = 0
|
||||||
|
|
||||||
// Text back-plane addressing (mirrors aa.mjs _TA_BACK / _TA_BASE), VT-aware.
|
// Text fore/back-plane addressing (mirrors aa.mjs _TA_FORE / _TA_BACK / _TA_BASE),
|
||||||
|
// VT-aware.
|
||||||
|
const TXT_FORE_OFF = 2
|
||||||
const TXT_BACK_OFF = 2562
|
const TXT_BACK_OFF = 2562
|
||||||
const TXT_AREA_BASE = 253950
|
const TXT_AREA_BASE = 253950
|
||||||
const asciiBackFill = new Uint8Array(80 * 32).fill(COL_PURE_BLACK)
|
const AA_W = 80, AA_H = 32
|
||||||
|
const asciiBackFill = new Uint8Array(AA_W * AA_H).fill(COL_PURE_BLACK)
|
||||||
|
|
||||||
|
// Resolve the address of text-area byte `off` for the current environment
|
||||||
|
// (VT pane: forward from VT_TEXT_PLANE; physical: backward from the GPU base),
|
||||||
|
// exactly as aa.mjs's _va() does, so writes land in the same plane aa.flush uses.
|
||||||
|
function txtAddr(off) {
|
||||||
|
if (typeof globalThis.VT_TEXT_PLANE !== 'undefined')
|
||||||
|
return globalThis.VT_TEXT_PLANE + off
|
||||||
|
return graphics.getGpuMemBase() - TXT_AREA_BASE - off
|
||||||
|
}
|
||||||
|
|
||||||
// Overwrite every text cell's background with opaque pure-black (240), so ASCII
|
// Overwrite every text cell's background with opaque pure-black (240), so ASCII
|
||||||
// glyphs sit on solid black instead of aa.mjs's transparent (255) cells.
|
// glyphs sit on solid black instead of aa.mjs's transparent (255) cells.
|
||||||
function paintAsciiBgOpaque() {
|
function paintAsciiBgOpaque() {
|
||||||
if (typeof globalThis.VT_TEXT_PLANE !== 'undefined')
|
sys.pokeBytes(txtAddr(TXT_BACK_OFF), asciiBackFill, asciiBackFill.length)
|
||||||
sys.pokeBytes(globalThis.VT_TEXT_PLANE + TXT_BACK_OFF, asciiBackFill, asciiBackFill.length)
|
}
|
||||||
else
|
|
||||||
sys.pokeBytes(graphics.getGpuMemBase() - TXT_AREA_BASE - TXT_BACK_OFF, asciiBackFill, asciiBackFill.length)
|
// ── Colour postprocessor (-colour) ───────────────────────────────────────────
|
||||||
|
// AAlib chooses each glyph from brightness; colour mode additionally tints the
|
||||||
|
// glyph's FOREGROUND (never the background) with the nearest opaque colour of
|
||||||
|
// the TSVM 256-palette, sampled from the video's RGB plane.
|
||||||
|
//
|
||||||
|
// That palette is a *separable* 6×8×5 RGB cube (indices 0–239, white corner at
|
||||||
|
// 239) plus a 15-step grey ramp (indices 240–254 = 0,17,…,238; index 255 is
|
||||||
|
// always transparent and cube index 0 is translucent, so both are excluded as
|
||||||
|
// ink). Because the cube is separable, its nearest entry is just the independent
|
||||||
|
// nearest level per channel; the global nearest opaque colour is then whichever
|
||||||
|
// of {best cube, best grey} is closer — all via small precomputed LUTs, O(1)/cell.
|
||||||
|
const CUBE_R = [0, 51, 102, 153, 204, 255]
|
||||||
|
const CUBE_G = [0, 34, 68, 102, 153, 187, 221, 255]
|
||||||
|
const CUBE_B = [0, 68, 136, 187, 255]
|
||||||
|
|
||||||
|
let _rNear = null, _gNear = null, _bNear = null // 0–255 value → cube level index
|
||||||
|
let _greyIdx = null, _greyVal = null // 0–255 mean → grey palette idx / value
|
||||||
|
const colourBuf = new Uint8Array(AA_W * AA_H * 3) // sampled R,G,B per cell
|
||||||
|
const foreBuf = new Uint8Array(AA_W * AA_H) // resolved palette ink per cell
|
||||||
|
|
||||||
|
function _nearestLevel(levels) {
|
||||||
|
const lut = new Uint8Array(256)
|
||||||
|
for (let v = 0; v < 256; v++) {
|
||||||
|
let best = 0, bestD = 1e9
|
||||||
|
for (let k = 0; k < levels.length; k++) {
|
||||||
|
const d = Math.abs(v - levels[k])
|
||||||
|
if (d < bestD) { bestD = d; best = k }
|
||||||
|
}
|
||||||
|
lut[v] = best
|
||||||
|
}
|
||||||
|
return lut
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureColourLuts() {
|
||||||
|
if (_rNear) return
|
||||||
|
_rNear = _nearestLevel(CUBE_R)
|
||||||
|
_gNear = _nearestLevel(CUBE_G)
|
||||||
|
_bNear = _nearestLevel(CUBE_B)
|
||||||
|
// Grey-ramp candidates: palette idx 240+k holds grey value 17·k, k = 0..14
|
||||||
|
// (idx 240 = black … 254 = 238; idx 255 is transparent, so it is excluded).
|
||||||
|
const gv = [], gi = []
|
||||||
|
for (let k = 0; k < 15; k++) { gv.push(17 * k); gi.push(240 + k) }
|
||||||
|
_greyIdx = new Uint8Array(256)
|
||||||
|
_greyVal = new Uint8Array(256)
|
||||||
|
for (let m = 0; m < 256; m++) {
|
||||||
|
let best = 0, bestD = 1e9
|
||||||
|
for (let k = 0; k < gv.length; k++) {
|
||||||
|
const d = Math.abs(m - gv[k])
|
||||||
|
if (d < bestD) { bestD = d; best = k }
|
||||||
|
}
|
||||||
|
_greyIdx[m] = gi[best]; _greyVal[m] = gv[best]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nearestPaletteIndex(r, g, b) {
|
||||||
|
const ri = _rNear[r], gi = _gNear[g], bi = _bNear[b]
|
||||||
|
const cr = CUBE_R[ri], cg = CUBE_G[gi], cb = CUBE_B[bi]
|
||||||
|
const dCube = (r - cr) * (r - cr) + (g - cg) * (g - cg) + (b - cb) * (b - cb)
|
||||||
|
// Nearest grey level sits at the rounded mean of the channels (the vertex of
|
||||||
|
// the achromatic-distance parabola); rounding — not flooring — makes the
|
||||||
|
// {cube vs grey} pick the exact global nearest opaque palette entry.
|
||||||
|
const m = ((r + g + b) / 3 + 0.5) | 0
|
||||||
|
const gvv = _greyVal[m]
|
||||||
|
const dGrey = (r - gvv) * (r - gvv) + (g - gvv) * (g - gvv) + (b - gvv) * (b - gvv)
|
||||||
|
// Prefer grey on ties (so near-black resolves to opaque grey idx 240, not the
|
||||||
|
// translucent cube corner); `|| 240` is a belt-and-braces guard for idx 0.
|
||||||
|
const cubeIdx = ri * 40 + gi * 5 + bi
|
||||||
|
return (dGrey <= dCube) ? _greyIdx[m] : (cubeIdx || 240)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample the frame's colour per cell, map to nearest palette ink, and write the
|
||||||
|
// foreground plane (over what aa.flush wrote). Background is left to
|
||||||
|
// paintAsciiBgOpaque(); only the FG is colourised, per spec.
|
||||||
|
function applyColourFore(dec) {
|
||||||
|
dec.sampleColour(colourBuf, AA_W, AA_H)
|
||||||
|
for (let i = 0, n = AA_W * AA_H; i < n; i++)
|
||||||
|
foreBuf[i] = nearestPaletteIndex(colourBuf[i * 3], colourBuf[i * 3 + 1], colourBuf[i * 3 + 2])
|
||||||
|
sys.pokeBytes(txtAddr(TXT_FORE_OFF), foreBuf, foreBuf.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Parse args ───────────────────────────────────────────────────────────────
|
// ── Parse args ───────────────────────────────────────────────────────────────
|
||||||
let interactive = false
|
let interactive = false
|
||||||
let asciiMode = false
|
let asciiMode = false
|
||||||
|
let colourMode = false
|
||||||
const decOpts = { interactive: false, deinterlaceAlgorithm: "yadif" }
|
const decOpts = { interactive: false, deinterlaceAlgorithm: "yadif" }
|
||||||
|
|
||||||
for (let i = 2; i < exec_args.length; i++) {
|
for (let i = 2; i < exec_args.length; i++) {
|
||||||
const arg = ("" + exec_args[i]).toLowerCase()
|
const arg = ("" + exec_args[i]).toLowerCase()
|
||||||
if (arg === "-i") { interactive = true; decOpts.interactive = true }
|
if (arg === "-i") { interactive = true; decOpts.interactive = true }
|
||||||
else if (arg === "-ascii") asciiMode = true
|
else if (arg === "-ascii") asciiMode = true
|
||||||
|
else if (arg === "-colour" || arg === "-color") { asciiMode = true; colourMode = true }
|
||||||
else if (arg === "-debug-mv") decOpts.debugMotionVectors = true
|
else if (arg === "-debug-mv") decOpts.debugMotionVectors = true
|
||||||
else if (arg === "-deblock") decOpts.enableDeblocking = true
|
else if (arg === "-deblock") decOpts.enableDeblocking = true
|
||||||
else if (arg === "-boundaryaware") decOpts.enableBoundaryAwareDecoding = true
|
else if (arg === "-boundaryaware") decOpts.enableBoundaryAwareDecoding = true
|
||||||
@@ -73,13 +165,14 @@ for (let i = 2; i < exec_args.length; i++) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graceful degradation: ASCII mode needs aa.mjs.
|
// Graceful degradation: ASCII (and therefore colour) mode needs aa.mjs.
|
||||||
if (asciiMode && !aa) {
|
if (asciiMode && !aa) {
|
||||||
serial.println("playmov: aa.mjs not found; ASCII mode unavailable, -ascii ignored")
|
serial.println("playmov: aa.mjs not found; ASCII mode unavailable, -ascii/-colour ignored")
|
||||||
asciiMode = false
|
asciiMode = false
|
||||||
|
colourMode = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exec_args[1]) { printerrln("usage: playmov FILE [-i] [-ascii] [options]"); return 1 }
|
if (!exec_args[1]) { printerrln("usage: playmov FILE [-i] [-ascii] [-colour] [options]"); return 1 }
|
||||||
const fullPath = _G.shell.resolvePathInput(exec_args[1]).full
|
const fullPath = _G.shell.resolvePathInput(exec_args[1]).full
|
||||||
|
|
||||||
// ── ASCII-render state (aa.mjs) — lazily initialised on first use ────────────
|
// ── ASCII-render state (aa.mjs) — lazily initialised on first use ────────────
|
||||||
@@ -88,9 +181,10 @@ let aaParams = null
|
|||||||
function ensureAscii() {
|
function ensureAscii() {
|
||||||
if (aaCtx) return
|
if (aaCtx) return
|
||||||
const font = aa.loadChrFontROM(AA_FONT_PATH)
|
const font = aa.loadChrFontROM(AA_FONT_PATH)
|
||||||
aaCtx = aa.init(80, 32, { font: font })
|
aaCtx = aa.init(AA_W, AA_H, { font: font })
|
||||||
aaParams = aa.getrenderparams()
|
aaParams = aa.getrenderparams()
|
||||||
aaParams.dither = aa.AA_FLOYD_S
|
aaParams.dither = aa.AA_FLOYD_S
|
||||||
|
ensureColourLuts() // cheap; keeps the C-key colour toggle ready
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Open ─────────────────────────────────────────────────────────────────────
|
// ── Open ─────────────────────────────────────────────────────────────────────
|
||||||
@@ -156,16 +250,25 @@ try {
|
|||||||
else exitAsciiVisual()
|
else exitAsciiVisual()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Colour only affects the foreground plane and is re-applied every drawn
|
||||||
|
// frame, so toggling it just flips the flag; the next flush+draw reverts the
|
||||||
|
// ink to aa.mjs's grey when off. Ensure the LUTs exist if A was never pressed.
|
||||||
|
function toggleColour() {
|
||||||
|
if (!aaCtx) ensureColourLuts()
|
||||||
|
colourMode = !colourMode
|
||||||
|
}
|
||||||
|
|
||||||
// ── Input ─────────────────────────────────────────────────────────────────
|
// ── Input ─────────────────────────────────────────────────────────────────
|
||||||
// Bksp is hold-to-quit (like the old players); everything else is edge-
|
// Bksp is hold-to-quit (like the old players); everything else is edge-
|
||||||
// triggered so a held key fires once. Quit + ASCII toggle work even without
|
// triggered so a held key fires once. Quit + ASCII/colour toggles work even
|
||||||
// -i; the rest of the transport is interactive-only.
|
// without -i; the rest of the transport is interactive-only.
|
||||||
function readInput() {
|
function readInput() {
|
||||||
sys.poke(-40, 1)
|
sys.poke(-40, 1)
|
||||||
const key = sys.peek(-41)
|
const key = sys.peek(-41)
|
||||||
if (key == K.BACKSPACE) { quit = true; return }
|
if (key == K.BACKSPACE) { quit = true; return }
|
||||||
if (key && key !== lastKey) {
|
if (key && key !== lastKey) {
|
||||||
if (key == K.A) { if (aa) toggleAscii() } // inert when aa.mjs is absent
|
if (key == K.A) { if (aa) toggleAscii() } // inert when aa.mjs is absent
|
||||||
|
else if (key == K.C) { if (aa) toggleColour() } // colour shows only while in ASCII
|
||||||
else if (interactive) {
|
else if (interactive) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case K.SPACE: dec.pause(!dec.isPaused()); break
|
case K.SPACE: dec.pause(!dec.isPaused()); break
|
||||||
@@ -186,10 +289,11 @@ try {
|
|||||||
if (asciiMode) {
|
if (asciiMode) {
|
||||||
// Sample the frame off the framebuffer, then cover the picture with
|
// Sample the frame off the framebuffer, then cover the picture with
|
||||||
// solid-black (240) text cells — cheaper than clearing the pixel planes.
|
// solid-black (240) text cells — cheaper than clearing the pixel planes.
|
||||||
dec.blit() // frame -> framebuffer (so sampleGray can read it)
|
dec.blit() // frame -> framebuffer (so sample* can read it)
|
||||||
dec.sampleGray(aaCtx.imagebuffer, aaCtx.imgW, aaCtx.imgH)
|
dec.sampleGray(aaCtx.imagebuffer, aaCtx.imgW, aaCtx.imgH)
|
||||||
aa.render(aaCtx, aaParams)
|
aa.render(aaCtx, aaParams)
|
||||||
aa.flush(aaCtx)
|
aa.flush(aaCtx)
|
||||||
|
if (colourMode) applyColourFore(dec) // recolour the FG plane from the video's RGB
|
||||||
paintAsciiBgOpaque() // cover with opaque 240 (not transparent 255)
|
paintAsciiBgOpaque() // cover with opaque 240 (not transparent 255)
|
||||||
} else {
|
} else {
|
||||||
dec.blit() // copy the frame to the framebuffer
|
dec.blit() // copy the frame to the framebuffer
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"symbols": {
|
"symbols": {
|
||||||
"interactive": { "kind": "option", "short": "-i", "summary": "Interactive mode (controls + on-screen info)" },
|
"interactive": { "kind": "option", "short": "-i", "summary": "Interactive mode (controls + on-screen info)" },
|
||||||
"ascii": { "kind": "option", "long": "-ascii", "summary": "Start in ASCII-render mode" },
|
"ascii": { "kind": "option", "long": "-ascii", "summary": "Start in ASCII-render mode" },
|
||||||
|
"colour": { "kind": "option", "long": "-colour", "summary": "Colourise ASCII glyphs from the video (implies -ascii); -color alias" },
|
||||||
"deblock": { "kind": "option", "long": "-deblock", "summary": "TEV: enable deblocking filter" },
|
"deblock": { "kind": "option", "long": "-deblock", "summary": "TEV: enable deblocking filter" },
|
||||||
"boundaryAware": { "kind": "option", "long": "-boundaryaware", "summary": "TEV: boundary-aware decoding" },
|
"boundaryAware": { "kind": "option", "long": "-boundaryaware", "summary": "TEV: boundary-aware decoding" },
|
||||||
"debugMv": { "kind": "option", "long": "-debug-mv", "summary": "TEV: show motion-vector debug overlay" },
|
"debugMv": { "kind": "option", "long": "-debug-mv", "summary": "TEV: show motion-vector debug overlay" },
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"kind": "group",
|
"kind": "group",
|
||||||
"summary": "Options",
|
"summary": "Options",
|
||||||
"members": ["interactive", "ascii", "deblock", "boundaryAware", "debugMv", "deinterlace", "filmGrain"]
|
"members": ["interactive", "ascii", "colour", "deblock", "boundaryAware", "debugMv", "deinterlace", "filmGrain"]
|
||||||
},
|
},
|
||||||
"file": { "kind": "positional", "type": "file", "name": "FILE", "summary": "Movie file to play" }
|
"file": { "kind": "positional", "type": "file", "name": "FILE", "summary": "Movie file to play" }
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
* .step() -> { type:'frame'|'idle'|'eof'|'newfile'|'error', frameCount }
|
* .step() -> { type:'frame'|'idle'|'eof'|'newfile'|'error', frameCount }
|
||||||
* .blit() present the current native frame to the screen
|
* .blit() present the current native frame to the screen
|
||||||
* .sampleGray(dst,w,h) fill an ASCII brightness buffer from the framebuffer
|
* .sampleGray(dst,w,h) fill an ASCII brightness buffer from the framebuffer
|
||||||
|
* .sampleColour(dst,w,h) fill a per-cell RGB buffer (w*h*3) from the framebuffer
|
||||||
* .subtitle {visible,text,position,useUnicode,dirty} (resolved by the lib)
|
* .subtitle {visible,text,position,useUnicode,dirty} (resolved by the lib)
|
||||||
* .pause(b)/.isPaused() .setVolume(v)/.getVolume()
|
* .pause(b)/.isPaused() .setVolume(v)/.getVolume()
|
||||||
* .seekSeconds(n) .cue(d) .cues
|
* .seekSeconds(n) .cue(d) .cues
|
||||||
|
|||||||
@@ -360,6 +360,42 @@ function sampleGrayScreen(width, height, dst, dstW, dstH, mode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── sampleColour source ──────────────────────────────────────────────────────
|
||||||
|
// Companion to sampleGrayScreen: fill an RGB buffer (dst, length dstW·dstH·3,
|
||||||
|
// laid out R,G,B per cell) by point-sampling the GPU framebuffer at the CENTRE
|
||||||
|
// of each cell. Used by the player's colour-ASCII postprocessor — aa.mjs picks
|
||||||
|
// each glyph from brightness, this supplies the per-cell ink colour. Same
|
||||||
|
// backend-specific `mode` (4/5/8-bpp unpacking) and same cheap ~dstW·dstH peek
|
||||||
|
// count as sampleGrayScreen.
|
||||||
|
function sampleColourScreen(width, height, dst, dstW, dstH, mode) {
|
||||||
|
for (let y = 0; y < dstH; y++) {
|
||||||
|
let sy = ((y + 0.5) * height / dstH) | 0
|
||||||
|
if (sy >= height) sy = height - 1
|
||||||
|
let dstRow = y * dstW * 3
|
||||||
|
for (let x = 0; x < dstW; x++) {
|
||||||
|
let sx = ((x + 0.5) * width / dstW) | 0
|
||||||
|
if (sx >= width) sx = width - 1
|
||||||
|
let off = sy * 560 + sx
|
||||||
|
let fb1 = sys.peek(DISP_RG - off) & 255
|
||||||
|
let fb2 = sys.peek(DISP_BA - off) & 255
|
||||||
|
let r, g, b
|
||||||
|
if (mode == 5) {
|
||||||
|
r = ((fb1 >>> 2) & 31) * 255 / 31
|
||||||
|
g = (((fb1 & 3) << 3) | ((fb2 >>> 5) & 7)) * 255 / 31
|
||||||
|
b = (fb2 & 31) * 255 / 31
|
||||||
|
} else if (mode == 8) {
|
||||||
|
r = fb1; g = fb2; b = sys.peek(DISP_PLANE3 - off) & 255
|
||||||
|
} else { // mode 4
|
||||||
|
r = (fb1 >>> 4) * 17
|
||||||
|
g = (fb1 & 15) * 17
|
||||||
|
b = (fb2 >>> 4) * 17
|
||||||
|
}
|
||||||
|
let di = dstRow + x * 3
|
||||||
|
dst[di] = r | 0; dst[di + 1] = g | 0; dst[di + 2] = b | 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
exports = {
|
exports = {
|
||||||
MAGIC_MOV, MAGIC_TEV, MAGIC_TAV, MAGIC_TAP, MAGIC_UCF,
|
MAGIC_MOV, MAGIC_TEV, MAGIC_TAV, MAGIC_TAP, MAGIC_UCF,
|
||||||
MP2_FRAME_SIZE, QLUT,
|
MP2_FRAME_SIZE, QLUT,
|
||||||
@@ -369,5 +405,5 @@ exports = {
|
|||||||
openSeqread, readMagic, detectFormat, magicEquals,
|
openSeqread, readMagic, detectFormat, magicEquals,
|
||||||
luma8,
|
luma8,
|
||||||
makeAudioRouter, makeSubtitleEngine, makeBias,
|
makeAudioRouter, makeSubtitleEngine, makeBias,
|
||||||
sampleGrayScreen
|
sampleGrayScreen, sampleColourScreen
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ function create(magic, sr, fileLength, opts, common) {
|
|||||||
|
|
||||||
// Frame is already on the display planes, so the player can sample the screen.
|
// Frame is already on the display planes, so the player can sample the screen.
|
||||||
function sampleGray(dst, w, h) { common.sampleGrayScreen(width, height, dst, w, h, 4) }
|
function sampleGray(dst, w, h) { common.sampleGrayScreen(width, height, dst, w, h, 4) }
|
||||||
|
function sampleColour(dst, w, h) { common.sampleColourScreen(width, height, dst, w, h, 4) }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
info: info,
|
info: info,
|
||||||
@@ -164,6 +165,7 @@ function create(magic, sr, fileLength, opts, common) {
|
|||||||
blit: blit,
|
blit: blit,
|
||||||
bias() { if (autoBg) applyBias() }, // skipped when an explicit bg packet set the colour
|
bias() { if (autoBg) applyBias() }, // skipped when an explicit bg packet set the colour
|
||||||
sampleGray: sampleGray,
|
sampleGray: sampleGray,
|
||||||
|
sampleColour: sampleColour,
|
||||||
pause(p) { paused = p; if (p) audioR.stop(); else { audioR.resume(); lastT = sys.nanoTime() } },
|
pause(p) { paused = p; if (p) audioR.stop(); else { audioR.resume(); lastT = sys.nanoTime() } },
|
||||||
isPaused() { return paused },
|
isPaused() { return paused },
|
||||||
setVolume(v) { audioR.setVolume(v) },
|
setVolume(v) { audioR.setVolume(v) },
|
||||||
|
|||||||
@@ -615,6 +615,7 @@ function create(magic, sr, fileLength, opts, common, isTap) {
|
|||||||
// Player calls blit() before sampleGray() in ASCII mode, so the framebuffer
|
// Player calls blit() before sampleGray() in ASCII mode, so the framebuffer
|
||||||
// already holds the current frame regardless of kind.
|
// already holds the current frame regardless of kind.
|
||||||
function sampleGray(dst, w, h) { common.sampleGrayScreen(width, height, dst, w, h, gpuGraphicsMode) }
|
function sampleGray(dst, w, h) { common.sampleGrayScreen(width, height, dst, w, h, gpuGraphicsMode) }
|
||||||
|
function sampleColour(dst, w, h) { common.sampleColourScreen(width, height, dst, w, h, gpuGraphicsMode) }
|
||||||
|
|
||||||
// ── TAP still: decode the single image now ──────────────────────────────
|
// ── TAP still: decode the single image now ──────────────────────────────
|
||||||
if (isTap) {
|
if (isTap) {
|
||||||
@@ -658,6 +659,7 @@ function create(magic, sr, fileLength, opts, common, isTap) {
|
|||||||
blit: blit,
|
blit: blit,
|
||||||
bias() { applyBias() },
|
bias() { applyBias() },
|
||||||
sampleGray: sampleGray,
|
sampleGray: sampleGray,
|
||||||
|
sampleColour: sampleColour,
|
||||||
|
|
||||||
pause(p) {
|
pause(p) {
|
||||||
paused = p
|
paused = p
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ function create(magic, sr, fileLength, opts, common) {
|
|||||||
// Player calls blit() (which uploads currentFrameSrc) before sampleGray in
|
// Player calls blit() (which uploads currentFrameSrc) before sampleGray in
|
||||||
// ASCII mode, so we read the framebuffer the upload just produced.
|
// ASCII mode, so we read the framebuffer the upload just produced.
|
||||||
function sampleGray(dst, w, h) { common.sampleGrayScreen(width, height, dst, w, h, 4) }
|
function sampleGray(dst, w, h) { common.sampleGrayScreen(width, height, dst, w, h, 4) }
|
||||||
|
function sampleColour(dst, w, h) { common.sampleColourScreen(width, height, dst, w, h, 4) }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
info: info,
|
info: info,
|
||||||
@@ -204,6 +205,7 @@ function create(magic, sr, fileLength, opts, common) {
|
|||||||
blit: blit,
|
blit: blit,
|
||||||
bias() { applyBias() },
|
bias() { applyBias() },
|
||||||
sampleGray: sampleGray,
|
sampleGray: sampleGray,
|
||||||
|
sampleColour: sampleColour,
|
||||||
pause(p) { paused = p; if (p) audioR.stop(); else { audioR.resume(); lastT = sys.nanoTime() } },
|
pause(p) { paused = p; if (p) audioR.stop(); else { audioR.resume(); lastT = sys.nanoTime() } },
|
||||||
isPaused() { return paused },
|
isPaused() { return paused },
|
||||||
setVolume(v) { audioR.setVolume(v) },
|
setVolume(v) { audioR.setVolume(v) },
|
||||||
|
|||||||
Reference in New Issue
Block a user