@modplayjs/playhead 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/playhead.d.ts +94 -0
- package/dist/playhead.d.ts.map +1 -0
- package/dist/playhead.js +235 -0
- package/dist/playhead.js.map +1 -0
- package/package.json +46 -4
- package/README.md +0 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,sDAAsD;AACtD,yBAAyB;AACzB,kCAAkC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Row-resolution snapshot of the player at the moment of a pull. Matches
|
|
2
|
+
* the fields of `core.playState` so it can be passed straight through. */
|
|
3
|
+
export interface PlaybackPosition {
|
|
4
|
+
/** Order index (pattern position in the sequence). */
|
|
5
|
+
ord: number;
|
|
6
|
+
/** Row inside the current pattern. */
|
|
7
|
+
row: number;
|
|
8
|
+
/** Tick inside the row (0-based). */
|
|
9
|
+
frame: number;
|
|
10
|
+
/** Ticks per row (effect Fxx / Axx). */
|
|
11
|
+
speed: number;
|
|
12
|
+
/** Beats per minute (effect Fxx). */
|
|
13
|
+
bpm: number;
|
|
14
|
+
/** Elapsed playback time in ms as reported by the core. */
|
|
15
|
+
timeMs: number;
|
|
16
|
+
}
|
|
17
|
+
/** The position that is audible at the query time. */
|
|
18
|
+
export interface AudiblePosition extends PlaybackPosition {
|
|
19
|
+
/** Frames actually played out at the query time. */
|
|
20
|
+
consumedFrames: number;
|
|
21
|
+
/** How far into the row the playhead is, 0..1 (estimated). */
|
|
22
|
+
rowProgress: number;
|
|
23
|
+
/** How far render is ahead of the sink, in frames (ring + device). */
|
|
24
|
+
queuedFrames: number;
|
|
25
|
+
/** How old (ms) the newest mark was at the query time. */
|
|
26
|
+
markAgeMs: number;
|
|
27
|
+
}
|
|
28
|
+
export interface PlayheadOptions {
|
|
29
|
+
/** Output rate in Hz; must match `core.setSampleRate`. Default 44100. */
|
|
30
|
+
rate?: number;
|
|
31
|
+
/** History retained for backward lookups, ms. Default 5000. */
|
|
32
|
+
historyMs?: number;
|
|
33
|
+
/** Extra offset applied on top of the sink's accounting, in ms.
|
|
34
|
+
* Positive makes the view run LATER (use when the sink's counter is
|
|
35
|
+
* known to under-report the buffered audio). Default 0. */
|
|
36
|
+
latencyMs?: number;
|
|
37
|
+
/** Monotonic clock in ms. Defaults to performance.now(), else Date.now(). */
|
|
38
|
+
clock?: () => number;
|
|
39
|
+
}
|
|
40
|
+
export declare class Playhead {
|
|
41
|
+
private marks;
|
|
42
|
+
private rate;
|
|
43
|
+
private readonly historyMs;
|
|
44
|
+
private latencyMs;
|
|
45
|
+
private readonly clock;
|
|
46
|
+
private provider;
|
|
47
|
+
/** Frames per row is derived from the core's binding time model:
|
|
48
|
+
* ticksize = (int)(rate × 2.5 / bpm), row = ticksize × speed. */
|
|
49
|
+
constructor(opts?: PlayheadOptions);
|
|
50
|
+
/** Set the output rate (call after `core.setSampleRate`). Existing marks
|
|
51
|
+
* keep the row timing they were recorded with. */
|
|
52
|
+
setRate(hz: number): void;
|
|
53
|
+
/** Extra latency in ms on top of the sink's own accounting. */
|
|
54
|
+
setLatencyMs(ms: number): void;
|
|
55
|
+
getLatencyMs(): number;
|
|
56
|
+
/**
|
|
57
|
+
* Install the sink's played-frames counter. The callback runs on every
|
|
58
|
+
* `positionAt()`; it must be cheap (Atomic load / field read) and must
|
|
59
|
+
* return the count of frames that have LEFT the sink (not the count
|
|
60
|
+
* still buffered in it).
|
|
61
|
+
*/
|
|
62
|
+
setConsumedProvider(fn: (() => number) | null): void;
|
|
63
|
+
/** Drop all history (new module, seek, transport restart). */
|
|
64
|
+
reset(): void;
|
|
65
|
+
/** Number of recorded marks (diagnostics). */
|
|
66
|
+
get markCount(): number;
|
|
67
|
+
/**
|
|
68
|
+
* Record the player state reached at the end of one pull.
|
|
69
|
+
* `frames` is the CUMULATIVE number of frames handed to the sink.
|
|
70
|
+
* Monotonic frames are the timeline; the wall clock is only used for
|
|
71
|
+
* history expiry and for the fallback mode.
|
|
72
|
+
*/
|
|
73
|
+
mark(frames: number, pos: PlaybackPosition, atMs?: number): void;
|
|
74
|
+
/**
|
|
75
|
+
* The position audible now (or at `atMs` / at frame `atFrames` when
|
|
76
|
+
* given). Returns null before the first mark — callers fall back to
|
|
77
|
+
* `core.playState`, which is correct while nothing has been rendered.
|
|
78
|
+
*
|
|
79
|
+
* The lookup is O(log n) and allocates one small object; it is meant to
|
|
80
|
+
* be called from a 60 fps render loop.
|
|
81
|
+
*/
|
|
82
|
+
positionAt(atMs?: number): AudiblePosition | null;
|
|
83
|
+
private consumedFrames;
|
|
84
|
+
/** frames per row from the core's binding time model. */
|
|
85
|
+
private rowFrames;
|
|
86
|
+
/** ticksize (samples per tick) — truncated, like libxmp's (int) cast. */
|
|
87
|
+
private tickFrames;
|
|
88
|
+
/** Output frame at which `pos.row` started. `pos.frame` is the index of
|
|
89
|
+
* the tick that just finished, so the row began `pos.frame + 1` ticks
|
|
90
|
+
* before the reported position. */
|
|
91
|
+
private rowStart;
|
|
92
|
+
private trim;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=playhead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playhead.d.ts","sourceRoot":"","sources":["../src/playhead.ts"],"names":[],"mappings":"AAuBA;2EAC2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,sDAAsD;AACtD,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;gEAE4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAuBD,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAA+B;IAC/C;sEACkE;IAElE,YAAY,IAAI,GAAE,eAAoB,EAKrC;IAED;uDACmD;IACnD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAExB;IAED,+DAA+D;IAC/D,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE7B;IAED,YAAY,IAAI,MAAM,CAErB;IAED;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAEnD;IAED,8DAA8D;IAC9D,KAAK,IAAI,IAAI,CAEZ;IAED,8CAA8C;IAC9C,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;;;;OAKG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CA0B/D;IAED;;;;;;;OAOG;IACH,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAqEhD;IAED,OAAO,CAAC,cAAc;IAYtB,yDAAyD;IACzD,OAAO,CAAC,SAAS;IAMjB,yEAAyE;IACzE,OAAO,CAAC,UAAU;IAIlB;;wCAEoC;IACpC,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,IAAI;CAQb"}
|
package/dist/playhead.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
// Copyright (c) 2026 Bitti09 — modplayjs contributors
|
|
3
|
+
// Project-original code.
|
|
4
|
+
// @modplayjs/playhead — playback clock for UIs.
|
|
5
|
+
//
|
|
6
|
+
// A tracker player renders AHEAD of the speaker: out-webaudio keeps a ring
|
|
7
|
+
// at its high-water mark (HIGH_WATER_FRAMES = 0.68 s at 48 kHz) and a
|
|
8
|
+
// terminal sink adds its own buffer on top. Reading `core.playState` from a
|
|
9
|
+
// UI therefore shows the row the renderer is working on, not the row the
|
|
10
|
+
// listener hears — several rows early on a fast song.
|
|
11
|
+
//
|
|
12
|
+
// This module records (rendered-frames, position) marks while the output
|
|
13
|
+
// pulls `core.playBuffer`, then answers "which row was audible at time T"
|
|
14
|
+
// by looking up the frame the SINK has consumed. Nothing here knows about
|
|
15
|
+
// AudioWorklet, ALSA or a pipe: the output supplies the consumption counter
|
|
16
|
+
// (a provider), and the UI only asks for a position.
|
|
17
|
+
//
|
|
18
|
+
// Two modes:
|
|
19
|
+
// - provider mode: consumedFrames comes from the sink (exact; the ring
|
|
20
|
+
// depth, the device latency and pause/resume are all already in it).
|
|
21
|
+
// - fallback mode (no provider): consumed ≈ last mark − latency − elapsed,
|
|
22
|
+
// for sinks that cannot report depth (a plain pipe, a file writer).
|
|
23
|
+
const FALLBACK_CLOCK = () => {
|
|
24
|
+
// `performance` is not in the ES2022 lib (this package compiles without
|
|
25
|
+
// DOM/Node types); reach it through globalThis so a browser/Node runtime
|
|
26
|
+
// gets the monotonic clock and anything else falls back to wall time.
|
|
27
|
+
const perf = globalThis.performance;
|
|
28
|
+
if (perf !== undefined && typeof perf.now === 'function')
|
|
29
|
+
return perf.now();
|
|
30
|
+
return Date.now();
|
|
31
|
+
};
|
|
32
|
+
export class Playhead {
|
|
33
|
+
marks = [];
|
|
34
|
+
rate;
|
|
35
|
+
historyMs;
|
|
36
|
+
latencyMs;
|
|
37
|
+
clock;
|
|
38
|
+
provider = null;
|
|
39
|
+
/** Frames per row is derived from the core's binding time model:
|
|
40
|
+
* ticksize = (int)(rate × 2.5 / bpm), row = ticksize × speed. */
|
|
41
|
+
constructor(opts = {}) {
|
|
42
|
+
this.rate = opts.rate ?? 44100;
|
|
43
|
+
this.historyMs = opts.historyMs ?? 5000;
|
|
44
|
+
this.latencyMs = opts.latencyMs ?? 0;
|
|
45
|
+
this.clock = opts.clock ?? FALLBACK_CLOCK;
|
|
46
|
+
}
|
|
47
|
+
/** Set the output rate (call after `core.setSampleRate`). Existing marks
|
|
48
|
+
* keep the row timing they were recorded with. */
|
|
49
|
+
setRate(hz) {
|
|
50
|
+
if (hz > 0 && hz !== this.rate)
|
|
51
|
+
this.rate = hz;
|
|
52
|
+
}
|
|
53
|
+
/** Extra latency in ms on top of the sink's own accounting. */
|
|
54
|
+
setLatencyMs(ms) {
|
|
55
|
+
this.latencyMs = Number.isFinite(ms) ? ms : 0;
|
|
56
|
+
}
|
|
57
|
+
getLatencyMs() {
|
|
58
|
+
return this.latencyMs;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Install the sink's played-frames counter. The callback runs on every
|
|
62
|
+
* `positionAt()`; it must be cheap (Atomic load / field read) and must
|
|
63
|
+
* return the count of frames that have LEFT the sink (not the count
|
|
64
|
+
* still buffered in it).
|
|
65
|
+
*/
|
|
66
|
+
setConsumedProvider(fn) {
|
|
67
|
+
this.provider = fn;
|
|
68
|
+
}
|
|
69
|
+
/** Drop all history (new module, seek, transport restart). */
|
|
70
|
+
reset() {
|
|
71
|
+
this.marks.length = 0;
|
|
72
|
+
}
|
|
73
|
+
/** Number of recorded marks (diagnostics). */
|
|
74
|
+
get markCount() {
|
|
75
|
+
return this.marks.length;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Record the player state reached at the end of one pull.
|
|
79
|
+
* `frames` is the CUMULATIVE number of frames handed to the sink.
|
|
80
|
+
* Monotonic frames are the timeline; the wall clock is only used for
|
|
81
|
+
* history expiry and for the fallback mode.
|
|
82
|
+
*/
|
|
83
|
+
mark(frames, pos, atMs) {
|
|
84
|
+
const at = atMs ?? this.clock();
|
|
85
|
+
const last = this.marks[this.marks.length - 1];
|
|
86
|
+
if (last !== undefined) {
|
|
87
|
+
// A counter that went backwards means the transport restarted (a new
|
|
88
|
+
// start() resets the ring / the posted-frame counter). Start a new
|
|
89
|
+
// epoch instead of corrupting the lookup.
|
|
90
|
+
if (frames < last.frames)
|
|
91
|
+
this.marks.length = 0;
|
|
92
|
+
else if (frames === last.frames) {
|
|
93
|
+
// Same pull reported twice: keep the newer position.
|
|
94
|
+
last.pos = pos;
|
|
95
|
+
last.atMs = at;
|
|
96
|
+
last.rowFrames = this.rowFrames(pos);
|
|
97
|
+
last.rowStart = this.rowStart(frames, pos, last.rowFrames);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const rowFrames = this.rowFrames(pos);
|
|
102
|
+
this.marks.push({
|
|
103
|
+
frames,
|
|
104
|
+
atMs: at,
|
|
105
|
+
pos,
|
|
106
|
+
rowFrames,
|
|
107
|
+
rowStart: this.rowStart(frames, pos, rowFrames),
|
|
108
|
+
});
|
|
109
|
+
this.trim(at);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The position audible now (or at `atMs` / at frame `atFrames` when
|
|
113
|
+
* given). Returns null before the first mark — callers fall back to
|
|
114
|
+
* `core.playState`, which is correct while nothing has been rendered.
|
|
115
|
+
*
|
|
116
|
+
* The lookup is O(log n) and allocates one small object; it is meant to
|
|
117
|
+
* be called from a 60 fps render loop.
|
|
118
|
+
*/
|
|
119
|
+
positionAt(atMs) {
|
|
120
|
+
const marks = this.marks;
|
|
121
|
+
const len = marks.length;
|
|
122
|
+
if (len === 0)
|
|
123
|
+
return null;
|
|
124
|
+
const now = atMs ?? this.clock();
|
|
125
|
+
const last = marks[len - 1];
|
|
126
|
+
const first = marks[0];
|
|
127
|
+
let consumed = this.consumedFrames(now, last);
|
|
128
|
+
consumed -= Math.round((this.latencyMs * this.rate) / 1000);
|
|
129
|
+
let target = consumed;
|
|
130
|
+
// Clamp into the recorded window: before the first pull nothing is
|
|
131
|
+
// audible yet (the sink has not been fed), after the last pull the
|
|
132
|
+
// renderer has nothing newer to show.
|
|
133
|
+
if (target < first.frames)
|
|
134
|
+
target = first.frames;
|
|
135
|
+
if (target > last.frames)
|
|
136
|
+
target = last.frames;
|
|
137
|
+
// First mark whose cumulative frame count reaches the target: the
|
|
138
|
+
// target frame lies inside (marks[i-1].frames, marks[i].frames].
|
|
139
|
+
let lo = 0;
|
|
140
|
+
let hi = len - 1;
|
|
141
|
+
while (lo < hi) {
|
|
142
|
+
const mid = (lo + hi) >> 1;
|
|
143
|
+
if (marks[mid].frames < target)
|
|
144
|
+
lo = mid + 1;
|
|
145
|
+
else
|
|
146
|
+
hi = mid;
|
|
147
|
+
}
|
|
148
|
+
const m = marks[lo];
|
|
149
|
+
const prev = lo > 0 ? marks[lo - 1] : null;
|
|
150
|
+
// Resolve the row from where it STARTED, not from the pull boundary:
|
|
151
|
+
// a pull spans several ticks, so the last tick's row began mid-pull
|
|
152
|
+
// and the frames before that belonged to the previous row.
|
|
153
|
+
let row = m.pos.row;
|
|
154
|
+
let rowProgress = 1;
|
|
155
|
+
let timeMs = m.pos.timeMs - ((m.frames - target) / this.rate) * 1000;
|
|
156
|
+
if (m.rowFrames > 0) {
|
|
157
|
+
const intoRow = target - m.rowStart;
|
|
158
|
+
if (intoRow > 0) {
|
|
159
|
+
// Frames have been played past the row start, so this row sounds.
|
|
160
|
+
rowProgress = intoRow / m.rowFrames;
|
|
161
|
+
if (rowProgress > 1)
|
|
162
|
+
rowProgress = 1;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Target is at (or before) the row start: the newest played frame
|
|
166
|
+
// still belongs to the previous row. Step back whole rows at this
|
|
167
|
+
// tempo/speed; a tempo or speed change inside the pull makes the
|
|
168
|
+
// earlier rows a different length, so the previous mark bounds it.
|
|
169
|
+
const back = 1 + Math.floor(-intoRow / m.rowFrames);
|
|
170
|
+
row = m.pos.row - back;
|
|
171
|
+
rowProgress = (back * m.rowFrames + intoRow) / m.rowFrames;
|
|
172
|
+
}
|
|
173
|
+
if (prev !== null && row < prev.pos.row)
|
|
174
|
+
row = prev.pos.row;
|
|
175
|
+
if (row > m.pos.row)
|
|
176
|
+
row = m.pos.row;
|
|
177
|
+
if (row < 0)
|
|
178
|
+
row = 0; // crossed into the previous pattern: clamp
|
|
179
|
+
if (rowProgress < 0)
|
|
180
|
+
rowProgress = 0;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
ord: m.pos.ord,
|
|
184
|
+
row,
|
|
185
|
+
frame: m.pos.frame,
|
|
186
|
+
speed: m.pos.speed,
|
|
187
|
+
bpm: m.pos.bpm,
|
|
188
|
+
timeMs: timeMs > 0 ? timeMs : 0,
|
|
189
|
+
consumedFrames: consumed,
|
|
190
|
+
rowProgress,
|
|
191
|
+
queuedFrames: last.frames - consumed,
|
|
192
|
+
markAgeMs: now - m.atMs,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
consumedFrames(now, last) {
|
|
196
|
+
if (this.provider !== null) {
|
|
197
|
+
const v = this.provider();
|
|
198
|
+
if (Number.isFinite(v))
|
|
199
|
+
return v;
|
|
200
|
+
}
|
|
201
|
+
// Fallback: assume the sink holds `latencyMs` of audio and drains at
|
|
202
|
+
// the output rate; clamp into the recorded window.
|
|
203
|
+
const elapsed = ((now - last.atMs) * this.rate) / 1000;
|
|
204
|
+
const v = last.frames - elapsed;
|
|
205
|
+
return v < 0 ? 0 : v;
|
|
206
|
+
}
|
|
207
|
+
/** frames per row from the core's binding time model. */
|
|
208
|
+
rowFrames(pos) {
|
|
209
|
+
if (!(pos.bpm > 0) || !(pos.speed > 0))
|
|
210
|
+
return 0;
|
|
211
|
+
const ticks = this.tickFrames(pos.bpm);
|
|
212
|
+
return ticks * pos.speed;
|
|
213
|
+
}
|
|
214
|
+
/** ticksize (samples per tick) — truncated, like libxmp's (int) cast. */
|
|
215
|
+
tickFrames(bpm) {
|
|
216
|
+
return Math.trunc((this.rate * 2.5) / bpm);
|
|
217
|
+
}
|
|
218
|
+
/** Output frame at which `pos.row` started. `pos.frame` is the index of
|
|
219
|
+
* the tick that just finished, so the row began `pos.frame + 1` ticks
|
|
220
|
+
* before the reported position. */
|
|
221
|
+
rowStart(frames, pos, rowFrames) {
|
|
222
|
+
if (rowFrames <= 0)
|
|
223
|
+
return frames;
|
|
224
|
+
return frames - (pos.frame + 1) * this.tickFrames(pos.bpm);
|
|
225
|
+
}
|
|
226
|
+
trim(now) {
|
|
227
|
+
const marks = this.marks;
|
|
228
|
+
// Keep a short tail even when the render loop stalls, and never drop
|
|
229
|
+
// below two marks (a lookup needs a bounding interval).
|
|
230
|
+
while (marks.length > 4 && now - marks[0].atMs > this.historyMs) {
|
|
231
|
+
marks.shift();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=playhead.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playhead.js","sourceRoot":"","sources":["../src/playhead.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modplayjs/playhead",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Playback clock for modplayjs UIs: maps sink consumption time to the song row that is actually audible (pattern-view sync).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.build.json",
|
|
19
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"tracker",
|
|
24
|
+
"mod",
|
|
25
|
+
"libxmp",
|
|
26
|
+
"chiptune",
|
|
27
|
+
"playback-clock",
|
|
28
|
+
"latency",
|
|
29
|
+
"subtitles"
|
|
30
|
+
],
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/modplayjs/modplayjs.git",
|
|
38
|
+
"directory": "packages/playhead"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/modplayjs/modplayjs/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/modplayjs/modplayjs#readme",
|
|
44
|
+
"license": "BSD-3-Clause",
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"registry": "https://npm.pkg.github.com"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/README.md
DELETED