-
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathcache.js
More file actions
89 lines (79 loc) · 3.2 KB
/
cache.js
File metadata and controls
89 lines (79 loc) · 3.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
* Page cache — LRU eviction to OPFS and lazy restore.
* Self-registers on import — exposes opfsCache/evict/ensurePages on audio.
*/
import audio from './core.js'
const DEFAULT_BUDGET = 500 * 1024 * 1024 // 500MB
/** Evict pages to cache until resident bytes fit within budget. True LRU. */
async function evict(a) {
if (!a.cache || a.budget === Infinity) return
let bytes = p => p ? p.reduce((s, ch) => s + ch.byteLength, 0) : 0
let current = a.pages.reduce((sum, p) => sum + bytes(p), 0)
if (current <= a.budget) return
// Build eviction order: LRU (oldest first) if tracked, else FIFO fallback
let order = a._.lru && a._.lru.size
? [...a._.lru]
: a.pages.map((_, i) => i)
for (let i of order) {
if (current <= a.budget) break
if (!a.pages[i]) continue
await a.cache.write(i, a.pages[i])
current -= bytes(a.pages[i])
a.pages[i] = null
if (a._.lru) a._.lru.delete(i)
}
}
/** Restore evicted pages covering a sample range from cache. */
async function ensurePages(a, offset, duration) {
if (!a.cache) return
let PS = audio.PAGE_SIZE, sr = a.sampleRate
let s = offset != null ? Math.max(0, Math.round(offset * sr)) : 0
let len = duration != null ? Math.round(duration * sr) : a._.len - s
let p0 = Math.floor(s / PS), pEnd = Math.min(Math.ceil((s + len) / PS), a.pages.length)
for (let i = p0; i < pEnd; i++)
if (a.pages[i] === null && await a.cache.has(i)) a.pages[i] = await a.cache.read(i)
}
/** Create an OPFS-backed cache backend. Browser only. */
async function opfsCache(dirName = 'audio-cache') {
if (typeof navigator === 'undefined' || !navigator.storage?.getDirectory)
throw new Error('OPFS not available in this environment')
let root = await navigator.storage.getDirectory()
let dir = await root.getDirectoryHandle(dirName, { create: true })
return {
async read(i) {
let handle = await dir.getFileHandle(`p${i}`)
let file = await handle.getFile()
let buf = await file.arrayBuffer()
let view = new Float32Array(buf)
let ch = view[0] | 0, samplesPerCh = ((view.length - 1) / ch) | 0
let data = []
for (let c = 0; c < ch; c++) data.push(view.slice(1 + c * samplesPerCh, 1 + (c + 1) * samplesPerCh))
return data
},
async write(i, data) {
let handle = await dir.getFileHandle(`p${i}`, { create: true })
let writable = await handle.createWritable()
let total = 1 + data.reduce((s, ch) => s + ch.length, 0)
let packed = new Float32Array(total)
packed[0] = data.length
let off = 1
for (let ch of data) { packed.set(ch, off); off += ch.length }
await writable.write(packed.buffer)
await writable.close()
},
has(i) {
return dir.getFileHandle(`p${i}`).then(() => true, () => false)
},
async evict(i) {
try { await dir.removeEntry(`p${i}`) } catch {}
},
async clear() {
for await (let [name] of dir) await dir.removeEntry(name)
}
}
}
// ── Self-register ────────────────────────────────────────────────
audio.opfsCache = opfsCache
audio.evict = evict
audio.ensurePages = ensurePages
audio.DEFAULT_BUDGET = DEFAULT_BUDGET