@jxgame2020/dsh-token-quota 0.1.24 → 0.1.26
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/lib/client.js +338 -246
- package/lib/index.js +7 -4
- package/lib/types/client/TokenQuotaPanel.d.ts +3 -1
- package/lib/types/client/TokenQuotaPanel.js +3 -2
- package/lib/types/client/index.js +26 -8
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/locales.js +4 -0
- package/lib/types/client/music.d.ts +43 -34
- package/lib/types/client/music.js +217 -128
- package/lib/types/client/store.d.ts +2 -0
- package/lib/types/client/store.js +2 -0
- package/lib/types/index.js +2 -0
- package/lib/types/types.d.ts +7 -0
- package/lib/types/types.js +1 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -27,7 +27,8 @@ const TOKEN_QUOTA_EXCEEDED_CODE = "TOKEN_QUOTA_EXCEEDED";
|
|
|
27
27
|
const TOKEN_QUOTA_DEFAULT_MUSIC = {
|
|
28
28
|
enabled: false,
|
|
29
29
|
volume: .5,
|
|
30
|
-
style: "major"
|
|
30
|
+
style: "major",
|
|
31
|
+
onlyCurrentSession: true
|
|
31
32
|
};
|
|
32
33
|
/** Build the stable per-model key shared by the counter, settings, and snapshot. */
|
|
33
34
|
function tokenQuotaKey(provider, model) {
|
|
@@ -221,7 +222,8 @@ const TOKEN_QUOTA_SETTINGS_SCHEMA = z.object({
|
|
|
221
222
|
"major",
|
|
222
223
|
"minor",
|
|
223
224
|
"pentatonic"
|
|
224
|
-
]).default(TOKEN_QUOTA_DEFAULT_MUSIC.style)
|
|
225
|
+
]).default(TOKEN_QUOTA_DEFAULT_MUSIC.style),
|
|
226
|
+
onlyCurrentSession: z.boolean().default(TOKEN_QUOTA_DEFAULT_MUSIC.onlyCurrentSession)
|
|
225
227
|
}).default({ ...TOKEN_QUOTA_DEFAULT_MUSIC })
|
|
226
228
|
});
|
|
227
229
|
/** Two-digit zero-pad helper. */
|
|
@@ -336,7 +338,8 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
336
338
|
music: {
|
|
337
339
|
enabled: doc.music?.enabled ?? TOKEN_QUOTA_DEFAULT_MUSIC.enabled,
|
|
338
340
|
volume: doc.music?.volume ?? TOKEN_QUOTA_DEFAULT_MUSIC.volume,
|
|
339
|
-
style: doc.music?.style ?? TOKEN_QUOTA_DEFAULT_MUSIC.style
|
|
341
|
+
style: doc.music?.style ?? TOKEN_QUOTA_DEFAULT_MUSIC.style,
|
|
342
|
+
onlyCurrentSession: doc.music?.onlyCurrentSession ?? TOKEN_QUOTA_DEFAULT_MUSIC.onlyCurrentSession
|
|
340
343
|
}
|
|
341
344
|
};
|
|
342
345
|
};
|
|
@@ -504,7 +507,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
504
507
|
return this.upgradeCheck;
|
|
505
508
|
}
|
|
506
509
|
async doRefreshUpgrade() {
|
|
507
|
-
const current = "0.1.
|
|
510
|
+
const current = "0.1.26";
|
|
508
511
|
let latest;
|
|
509
512
|
try {
|
|
510
513
|
const response = await fetch(REGISTRY_LATEST_URL, {
|
|
@@ -26,6 +26,8 @@ export interface TokenQuotaPanelInjected {
|
|
|
26
26
|
setMusicVolume: (volume: number) => void;
|
|
27
27
|
/** Change the live-music harmonic style. */
|
|
28
28
|
setMusicStyle: (style: TokenQuotaMusicStyle) => void;
|
|
29
|
+
/** Toggle whether the soundtrack follows only the current session. */
|
|
30
|
+
setMusicOnlyCurrentSession: (only: boolean) => void;
|
|
29
31
|
/** Ask the Host to check the registry right now, then refresh the snapshot. */
|
|
30
32
|
checkUpdatesNow: () => void;
|
|
31
33
|
/** Ask the Host to clear log entries before a date, then refresh the log. */
|
|
@@ -38,5 +40,5 @@ export type TokenQuotaPanelComponentProps = PropsRuntime<'shell.overlay'> & Prop
|
|
|
38
40
|
* @param props - composed slot props.
|
|
39
41
|
* @returns the panel element tree.
|
|
40
42
|
*/
|
|
41
|
-
export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setMusicEnabled, setMusicVolume, setMusicStyle, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
|
|
43
|
+
export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
|
|
42
44
|
//# sourceMappingURL=TokenQuotaPanel.d.ts.map
|
|
@@ -86,7 +86,7 @@ function loadPanelPos() {
|
|
|
86
86
|
* @param props - composed slot props.
|
|
87
87
|
* @returns the panel element tree.
|
|
88
88
|
*/
|
|
89
|
-
export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setMusicEnabled, setMusicVolume, setMusicStyle, useStore, actions, useSessions, }) {
|
|
89
|
+
export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }) {
|
|
90
90
|
const [collapsed, setCollapsed] = useState(false);
|
|
91
91
|
const [drafts, setDrafts] = useState({});
|
|
92
92
|
const [editingKey, setEditingKey] = useState(null);
|
|
@@ -132,6 +132,7 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
132
132
|
const musicEnabled = useStore(s => s.musicEnabled);
|
|
133
133
|
const musicVolume = useStore(s => s.musicVolume);
|
|
134
134
|
const musicStyle = useStore(s => s.musicStyle);
|
|
135
|
+
const musicOnlyCurrentSession = useStore(s => s.musicOnlyCurrentSession);
|
|
135
136
|
const upgrade = useStore(s => s.upgrade);
|
|
136
137
|
const upgradeDismissed = useStore(s => s.upgradeDismissed);
|
|
137
138
|
const checkingUpdates = useStore(s => s.checkingUpdates);
|
|
@@ -337,7 +338,7 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
337
338
|
hour: reset?.hour ?? 0,
|
|
338
339
|
minute: Number(event.target.value),
|
|
339
340
|
});
|
|
340
|
-
}, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: dimWhenIdle, onChange: (event) => { setDimWhenIdle(event.target.checked); } }), _jsx("span", { children: t('dimWhenIdleLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('dimWhenIdleHint') })] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: musicEnabled, onChange: (event) => { setMusicEnabled(event.target.checked); } }), _jsx("span", { children: t('musicLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('musicHint') }), musicEnabled && (_jsxs(_Fragment, { children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("span", { children: t('musicVolume') }), _jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: musicVolume, onChange: (event) => { setMusicVolume(Number(event.target.value)); } })] }), _jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("span", { children: t('musicStyle') }), _jsxs("select", { value: musicStyle, onChange: (event) => { setMusicStyle(event.target.value); }, children: [_jsx("option", { value: "major", children: t('musicStyleMajor') }), _jsx("option", { value: "minor", children: t('musicStyleMinor') }), _jsx("option", { value: "pentatonic", children: t('musicStylePentatonic') })] })] })] }))] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("div", { className: css.checkUpdatesRow, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: checkUpdates, onChange: (event) => { setCheckUpdates(event.target.checked); } }), _jsx("span", { children: t('checkUpdatesLabel') })] }), _jsx("button", { type: "button", className: css.checkBtn, disabled: checkingUpdates, onClick: checkUpdatesNow, children: checkingUpdates
|
|
341
|
+
}, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: dimWhenIdle, onChange: (event) => { setDimWhenIdle(event.target.checked); } }), _jsx("span", { children: t('dimWhenIdleLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('dimWhenIdleHint') })] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: musicEnabled, onChange: (event) => { setMusicEnabled(event.target.checked); } }), _jsx("span", { children: t('musicLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('musicHint') }), musicEnabled && (_jsxs(_Fragment, { children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("span", { children: t('musicVolume') }), _jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: musicVolume, onChange: (event) => { setMusicVolume(Number(event.target.value)); } })] }), _jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("span", { children: t('musicStyle') }), _jsxs("select", { value: musicStyle, onChange: (event) => { setMusicStyle(event.target.value); }, children: [_jsx("option", { value: "major", children: t('musicStyleMajor') }), _jsx("option", { value: "minor", children: t('musicStyleMinor') }), _jsx("option", { value: "pentatonic", children: t('musicStylePentatonic') })] })] }), _jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: musicOnlyCurrentSession, onChange: (event) => { setMusicOnlyCurrentSession(event.target.checked); } }), _jsx("span", { children: t('musicOnlyCurrentSessionLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('musicOnlyCurrentSessionHint') })] }))] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("div", { className: css.checkUpdatesRow, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: checkUpdates, onChange: (event) => { setCheckUpdates(event.target.checked); } }), _jsx("span", { children: t('checkUpdatesLabel') })] }), _jsx("button", { type: "button", className: css.checkBtn, disabled: checkingUpdates, onClick: checkUpdatesNow, children: checkingUpdates
|
|
341
342
|
? t('checkingUpdates')
|
|
342
343
|
: lastCheckResult === 'up-to-date'
|
|
343
344
|
? t('checkUpToDate')
|
|
@@ -36,6 +36,8 @@ export function apply(ctx) {
|
|
|
36
36
|
let lastMonitored = null;
|
|
37
37
|
let lastOnFull = 'stop';
|
|
38
38
|
let lastReset = null;
|
|
39
|
+
// Whether the soundtrack is scoped to the currently-visible session only.
|
|
40
|
+
let lastOnlyCurrentSession = true;
|
|
39
41
|
// Live music engine: subscribes to the plugin's SSE action stream and
|
|
40
42
|
// composes from host events. Web Audio plays by default; a detected MIDI
|
|
41
43
|
// output is driven simultaneously.
|
|
@@ -49,15 +51,23 @@ export function apply(ctx) {
|
|
|
49
51
|
catch {
|
|
50
52
|
return;
|
|
51
53
|
}
|
|
52
|
-
//
|
|
53
|
-
// panel isn't bound to a specific session, but its display tracks
|
|
54
|
+
// When scoped, only play actions for the currently-visible session; a root
|
|
55
|
+
// overlay panel isn't bound to a specific session, but its display tracks
|
|
54
56
|
// useSessions(s => s.current), so keeping the soundtrack on the same
|
|
55
57
|
// session avoids a cacophony from background conversations.
|
|
56
|
-
if (
|
|
58
|
+
if (lastOnlyCurrentSession && lastSessionId !== undefined
|
|
59
|
+
&& action.sessionId !== String(lastSessionId))
|
|
57
60
|
return;
|
|
58
61
|
music.onAction(action);
|
|
59
62
|
});
|
|
60
63
|
ctx.effect(() => () => { sse.close(); }, 'token-quota-ui: music SSE');
|
|
64
|
+
/** Build a writable music-settings clone with defaults applied. */
|
|
65
|
+
const musicDefaults = (raw) => ({
|
|
66
|
+
enabled: raw?.enabled ?? false,
|
|
67
|
+
volume: raw?.volume ?? 0.5,
|
|
68
|
+
style: raw?.style ?? 'major',
|
|
69
|
+
onlyCurrentSession: raw?.onlyCurrentSession ?? true,
|
|
70
|
+
});
|
|
61
71
|
const keyOf = (selection) => {
|
|
62
72
|
if (selection === null)
|
|
63
73
|
return undefined;
|
|
@@ -213,13 +223,15 @@ export function apply(ctx) {
|
|
|
213
223
|
bound?.setCheckUpdates(doc?.checkUpdates ?? true);
|
|
214
224
|
bound?.setDimWhenIdle(doc?.dimWhenIdle ?? false);
|
|
215
225
|
bound?.setReset(lastReset);
|
|
216
|
-
const musicSettings =
|
|
226
|
+
const musicSettings = musicDefaults(doc?.music);
|
|
227
|
+
lastOnlyCurrentSession = musicSettings.onlyCurrentSession;
|
|
217
228
|
music.setEnabled(musicSettings.enabled);
|
|
218
229
|
music.setVolume(musicSettings.volume);
|
|
219
230
|
music.setStyle(musicSettings.style);
|
|
220
231
|
bound?.setMusicEnabled(musicSettings.enabled);
|
|
221
232
|
bound?.setMusicVolume(musicSettings.volume);
|
|
222
233
|
bound?.setMusicStyle(musicSettings.style);
|
|
234
|
+
bound?.setMusicOnlyCurrentSession(musicSettings.onlyCurrentSession);
|
|
223
235
|
void fetch('/token-quota', { headers: { accept: 'application/json' } }).then((response) => {
|
|
224
236
|
if (!response.ok) {
|
|
225
237
|
bound?.setError(`quota route: ${String(response.status)}`);
|
|
@@ -289,7 +301,7 @@ export function apply(ctx) {
|
|
|
289
301
|
};
|
|
290
302
|
const setMusicEnabled = (enabled) => {
|
|
291
303
|
const doc = scope.getSnapshot().value;
|
|
292
|
-
const current =
|
|
304
|
+
const current = musicDefaults(doc?.music);
|
|
293
305
|
current.enabled = enabled;
|
|
294
306
|
void scope.set('music', current);
|
|
295
307
|
// First enable: start audio inside this user gesture (the panel switch
|
|
@@ -300,16 +312,22 @@ export function apply(ctx) {
|
|
|
300
312
|
};
|
|
301
313
|
const setMusicVolume = (volume) => {
|
|
302
314
|
const doc = scope.getSnapshot().value;
|
|
303
|
-
const current =
|
|
315
|
+
const current = musicDefaults(doc?.music);
|
|
304
316
|
current.volume = Math.max(0, Math.min(1, volume));
|
|
305
317
|
void scope.set('music', current);
|
|
306
318
|
};
|
|
307
319
|
const setMusicStyle = (style) => {
|
|
308
320
|
const doc = scope.getSnapshot().value;
|
|
309
|
-
const current =
|
|
321
|
+
const current = musicDefaults(doc?.music);
|
|
310
322
|
current.style = style;
|
|
311
323
|
void scope.set('music', current);
|
|
312
324
|
};
|
|
325
|
+
const setMusicOnlyCurrentSession = (only) => {
|
|
326
|
+
const doc = scope.getSnapshot().value;
|
|
327
|
+
const current = musicDefaults(doc?.music);
|
|
328
|
+
current.onlyCurrentSession = only;
|
|
329
|
+
void scope.set('music', current);
|
|
330
|
+
};
|
|
313
331
|
/** Ask the Host to check the registry right now, then refresh the snapshot. */
|
|
314
332
|
const checkUpdatesNow = () => {
|
|
315
333
|
bound?.setCheckingUpdates(true);
|
|
@@ -397,7 +415,7 @@ export function apply(ctx) {
|
|
|
397
415
|
return {
|
|
398
416
|
load, setLimit, selectModel, setMonitored, setOnFull, setReset,
|
|
399
417
|
setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle,
|
|
400
|
-
setMusicEnabled, setMusicVolume, setMusicStyle,
|
|
418
|
+
setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession,
|
|
401
419
|
};
|
|
402
420
|
};
|
|
403
421
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
@@ -73,6 +73,8 @@ export declare const zh: {
|
|
|
73
73
|
musicStyleMajor: string;
|
|
74
74
|
musicStyleMinor: string;
|
|
75
75
|
musicStylePentatonic: string;
|
|
76
|
+
musicOnlyCurrentSessionLabel: string;
|
|
77
|
+
musicOnlyCurrentSessionHint: string;
|
|
76
78
|
checkUpdatesNow: string;
|
|
77
79
|
checkingUpdates: string;
|
|
78
80
|
checkUpToDate: string;
|
|
@@ -149,6 +151,8 @@ export declare const en: {
|
|
|
149
151
|
musicStyleMajor: string;
|
|
150
152
|
musicStyleMinor: string;
|
|
151
153
|
musicStylePentatonic: string;
|
|
154
|
+
musicOnlyCurrentSessionLabel: string;
|
|
155
|
+
musicOnlyCurrentSessionHint: string;
|
|
152
156
|
checkUpdatesNow: string;
|
|
153
157
|
checkingUpdates: string;
|
|
154
158
|
checkUpToDate: string;
|
|
@@ -73,6 +73,8 @@ export const zh = {
|
|
|
73
73
|
musicStyleMajor: '大调',
|
|
74
74
|
musicStyleMinor: '小调',
|
|
75
75
|
musicStylePentatonic: '五声',
|
|
76
|
+
musicOnlyCurrentSessionLabel: '只播当前会话',
|
|
77
|
+
musicOnlyCurrentSessionHint: '开启后仅当前窗口的会话有音乐,后台子任务静音;关闭则所有会话都参与作曲',
|
|
76
78
|
checkUpdatesNow: '立即检查',
|
|
77
79
|
checkingUpdates: '检查中…',
|
|
78
80
|
checkUpToDate: '已是最新',
|
|
@@ -147,6 +149,8 @@ export const en = {
|
|
|
147
149
|
musicStyleMajor: 'Major',
|
|
148
150
|
musicStyleMinor: 'Minor',
|
|
149
151
|
musicStylePentatonic: 'Pentatonic',
|
|
152
|
+
musicOnlyCurrentSessionLabel: 'Current session only',
|
|
153
|
+
musicOnlyCurrentSessionHint: 'When on, only the visible session drives the soundtrack (background tasks stay silent); when off, every session contributes.',
|
|
150
154
|
checkUpdatesNow: 'Check now',
|
|
151
155
|
checkingUpdates: 'Checking…',
|
|
152
156
|
checkUpToDate: 'Up to date',
|
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Live-composed soundtrack driven by host actions (turns, steps,
|
|
3
|
-
* etc.).
|
|
4
|
-
* sends MIDI NoteOn/NoteOff to the first available MIDI output when one is
|
|
5
|
-
* detected (both play simultaneously when MIDI is present — use the panel
|
|
6
|
-
* volume for overall level, the MIDI output has its own gain on the receiver).
|
|
2
|
+
* Live-composed ambient soundtrack driven by host actions (turns, steps,
|
|
3
|
+
* tool calls, etc.). Three layers:
|
|
7
4
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* - **Pad**: a sustained three-note chord (triangle waves) that fades in when
|
|
6
|
+
* a request starts and crossfades to the next chord on every step.
|
|
7
|
+
* - **Bass**: one long root note per beat (lower octave), keeps the pulse.
|
|
8
|
+
* - **Melody**: an eighth-note scale line that dances inside the current
|
|
9
|
+
* chord — always in key, never a wrong note.
|
|
10
|
+
*
|
|
11
|
+
* Outputs through Web Audio (built-in synthesis) by default, and also sends
|
|
12
|
+
* MIDI NoteOn/NoteOff to the first available MIDI output when one is detected
|
|
13
|
+
* (both play simultaneously when MIDI is present — use the panel volume for
|
|
14
|
+
* overall level, the MIDI output has its own gain on the receiver).
|
|
12
15
|
*
|
|
13
16
|
* @module @jxgame2020/dsh-token-quota/music
|
|
14
17
|
*/
|
|
15
18
|
import type { TokenQuotaMusicAction, TokenQuotaMusicStyle } from '../types.ts';
|
|
16
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Live-composed music engine driven by host actions.
|
|
21
|
+
*
|
|
22
|
+
* The engine is idle until `ensureStarted()` is called from a user gesture
|
|
23
|
+
* (the panel switch), and only produces sound while `enabled` is true AND a
|
|
24
|
+
* request is in flight (pad + bass + melody are gated by `startPad` /
|
|
25
|
+
* `stopPad`).
|
|
26
|
+
*/
|
|
17
27
|
export declare class TokenQuotaMusic {
|
|
18
28
|
private audio;
|
|
19
29
|
private midiOutput;
|
|
@@ -21,26 +31,23 @@ export declare class TokenQuotaMusic {
|
|
|
21
31
|
private enabled;
|
|
22
32
|
private volume;
|
|
23
33
|
private style;
|
|
24
|
-
/** Current position in the chord progression (advances per step). */
|
|
25
34
|
private chordIndex;
|
|
26
|
-
/** Step counter — used for melody degree selection. */
|
|
27
35
|
private stepCount;
|
|
28
|
-
/** Tool counter — used for tool-note pitch. */
|
|
29
36
|
private toolCount;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
/** Base MIDI note of the
|
|
37
|
+
private padOscs;
|
|
38
|
+
private padGain;
|
|
39
|
+
private padTarget;
|
|
40
|
+
private padActive;
|
|
41
|
+
private beatTimer;
|
|
42
|
+
private beatIndex;
|
|
43
|
+
private melodyDirection;
|
|
44
|
+
private melodyDegree;
|
|
45
|
+
private currentRoot;
|
|
46
|
+
private currentQuality;
|
|
47
|
+
/** Base MIDI note of the key. C4 = middle C. */
|
|
41
48
|
private static readonly BASE_MIDI;
|
|
42
|
-
/**
|
|
43
|
-
private static readonly
|
|
49
|
+
/** Beat length in ms — eighth note = beat / 2. */
|
|
50
|
+
private static readonly BEAT_MS;
|
|
44
51
|
/** True once the audio context has been started (needs a user gesture). */
|
|
45
52
|
get started(): boolean;
|
|
46
53
|
/**
|
|
@@ -52,16 +59,18 @@ export declare class TokenQuotaMusic {
|
|
|
52
59
|
setEnabled(value: boolean): void;
|
|
53
60
|
setVolume(value: number): void;
|
|
54
61
|
setStyle(value: TokenQuotaMusicStyle): void;
|
|
55
|
-
/** Start the quiet in-flight background loop (idempotent). */
|
|
56
|
-
private startProgress;
|
|
57
|
-
/** Stop the in-flight background loop. */
|
|
58
|
-
private stopProgress;
|
|
59
62
|
/** Handle one incoming host action and turn it into sound. */
|
|
60
63
|
onAction(action: TokenQuotaMusicAction): void;
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
/**
|
|
64
|
-
private
|
|
64
|
+
/** Start the pad from silence, fading in to the target level. */
|
|
65
|
+
private startPad;
|
|
66
|
+
/** Smoothly morph the pad to a new root + quality. */
|
|
67
|
+
private crossfadePad;
|
|
68
|
+
/** Fade the pad to silence, then stop the oscillators. */
|
|
69
|
+
private stopPad;
|
|
70
|
+
/** Start the per-beat clock that drives bass and melody. */
|
|
71
|
+
private startBeatClock;
|
|
72
|
+
private stopBeatClock;
|
|
73
|
+
private stopAll;
|
|
65
74
|
/** Play one note: Web Audio + MIDI simultaneously when available. */
|
|
66
75
|
private playNote;
|
|
67
76
|
/** Play a three-note chord plus a bass octave below. */
|