@jxgame2020/dsh-token-quota 0.1.26 → 0.1.27
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 +544 -285
- package/lib/index.js +10 -2
- package/lib/types/client/index.js +6 -1
- package/lib/types/client/locales.js +2 -2
- package/lib/types/client/music.d.ts +68 -49
- package/lib/types/client/music.js +352 -229
- package/lib/types/client/store.js +1 -1
- package/lib/types/index.js +15 -1
- package/lib/types/types.js +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -27,7 +27,7 @@ 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: "
|
|
30
|
+
style: "pentatonic",
|
|
31
31
|
onlyCurrentSession: true
|
|
32
32
|
};
|
|
33
33
|
/** Build the stable per-model key shared by the counter, settings, and snapshot. */
|
|
@@ -478,7 +478,15 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
478
478
|
});
|
|
479
479
|
this.musicClients.add(res);
|
|
480
480
|
res.write(": token-quota music stream\n\n");
|
|
481
|
+
const hb = setInterval(() => {
|
|
482
|
+
try {
|
|
483
|
+
res.write("event: hb\ndata: 1\n\n");
|
|
484
|
+
} catch {
|
|
485
|
+
clearInterval(hb);
|
|
486
|
+
}
|
|
487
|
+
}, 5e3);
|
|
481
488
|
req.on("close", () => {
|
|
489
|
+
clearInterval(hb);
|
|
482
490
|
this.musicClients.delete(res);
|
|
483
491
|
});
|
|
484
492
|
}
|
|
@@ -507,7 +515,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
507
515
|
return this.upgradeCheck;
|
|
508
516
|
}
|
|
509
517
|
async doRefreshUpgrade() {
|
|
510
|
-
const current = "0.1.
|
|
518
|
+
const current = "0.1.27";
|
|
511
519
|
let latest;
|
|
512
520
|
try {
|
|
513
521
|
const response = await fetch(REGISTRY_LATEST_URL, {
|
|
@@ -43,6 +43,11 @@ export function apply(ctx) {
|
|
|
43
43
|
// output is driven simultaneously.
|
|
44
44
|
const music = new TokenQuotaMusic();
|
|
45
45
|
const sse = new EventSource('/token-quota/events');
|
|
46
|
+
// Host heartbeat every 5s: proves the stream is alive while the model
|
|
47
|
+
// thinks (no actions flow then). If it stops, the engine's watchdog fades
|
|
48
|
+
// the piece out instead of droning forever.
|
|
49
|
+
sse.addEventListener('hb', () => { music.markStreamActivity(); });
|
|
50
|
+
sse.addEventListener('open', () => { music.markStreamActivity(); });
|
|
46
51
|
sse.addEventListener('action', (event) => {
|
|
47
52
|
let action;
|
|
48
53
|
try {
|
|
@@ -65,7 +70,7 @@ export function apply(ctx) {
|
|
|
65
70
|
const musicDefaults = (raw) => ({
|
|
66
71
|
enabled: raw?.enabled ?? false,
|
|
67
72
|
volume: raw?.volume ?? 0.5,
|
|
68
|
-
style: raw?.style ?? '
|
|
73
|
+
style: raw?.style ?? 'pentatonic',
|
|
69
74
|
onlyCurrentSession: raw?.onlyCurrentSession ?? true,
|
|
70
75
|
});
|
|
71
76
|
const keyOf = (selection) => {
|
|
@@ -72,7 +72,7 @@ export const zh = {
|
|
|
72
72
|
musicStyle: '风格',
|
|
73
73
|
musicStyleMajor: '大调',
|
|
74
74
|
musicStyleMinor: '小调',
|
|
75
|
-
musicStylePentatonic: '
|
|
75
|
+
musicStylePentatonic: '古风',
|
|
76
76
|
musicOnlyCurrentSessionLabel: '只播当前会话',
|
|
77
77
|
musicOnlyCurrentSessionHint: '开启后仅当前窗口的会话有音乐,后台子任务静音;关闭则所有会话都参与作曲',
|
|
78
78
|
checkUpdatesNow: '立即检查',
|
|
@@ -148,7 +148,7 @@ export const en = {
|
|
|
148
148
|
musicStyle: 'Style',
|
|
149
149
|
musicStyleMajor: 'Major',
|
|
150
150
|
musicStyleMinor: 'Minor',
|
|
151
|
-
musicStylePentatonic: '
|
|
151
|
+
musicStylePentatonic: 'Gufeng',
|
|
152
152
|
musicOnlyCurrentSessionLabel: 'Current session only',
|
|
153
153
|
musicOnlyCurrentSessionHint: 'When on, only the visible session drives the soundtrack (background tasks stay silent); when off, every session contributes.',
|
|
154
154
|
checkUpdatesNow: 'Check now',
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* tool calls, etc.). Three layers:
|
|
2
|
+
* 古风 live-composed soundtrack driven by host actions.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Design: when a request starts the engine composes a *piece* — a guzheng-like
|
|
5
|
+
* plucked-string voice over a soft open-fifth drone (Web Audio synthesis; the
|
|
6
|
+
* MIDI output is switched to the Koto program so hardware synths match). The
|
|
7
|
+
* piece plays phrase after phrase so it never goes quiet while the model
|
|
8
|
+
* thinks or streams. Incoming events steer the piece instead of interrupting
|
|
9
|
+
* it: a step change glides the drone to the next mode centre and inserts a
|
|
10
|
+
* fast 过门 (fill); tool calls get a high plink; the ending gets a flute-like
|
|
11
|
+
* breath note and a descending glissando.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Anti-runaway guards (the "endless drone" / hanging-MIDI-note problem):
|
|
14
|
+
* - the host sends an SSE heartbeat every 5s; if it stops arriving the whole
|
|
15
|
+
* engine fades out (a dead stream means no stop event will ever come),
|
|
16
|
+
* - every MIDI NoteOn is registered and swept; `stopAll` also sends
|
|
17
|
+
* All Notes Off (CC 123),
|
|
18
|
+
* - Web Audio ramps never target zero exponentially and every oscillator is
|
|
19
|
+
* explicitly stopped.
|
|
15
20
|
*
|
|
16
21
|
* @module @jxgame2020/dsh-token-quota/music
|
|
17
22
|
*/
|
|
18
23
|
import type { TokenQuotaMusicAction, TokenQuotaMusicStyle } from '../types.ts';
|
|
19
24
|
/**
|
|
20
|
-
* Live-composed
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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`).
|
|
25
|
+
* Live-composed gufeng engine. Idle until `ensureStarted()` runs inside a
|
|
26
|
+
* user gesture; silent while `enabled` is false; the piece runs only between
|
|
27
|
+
* `request/header` (start) and `assistant/message` / `turn/end` (stop).
|
|
26
28
|
*/
|
|
27
29
|
export declare class TokenQuotaMusic {
|
|
28
30
|
private audio;
|
|
@@ -31,49 +33,66 @@ export declare class TokenQuotaMusic {
|
|
|
31
33
|
private enabled;
|
|
32
34
|
private volume;
|
|
33
35
|
private style;
|
|
34
|
-
private
|
|
35
|
-
private
|
|
36
|
-
private
|
|
37
|
-
private
|
|
38
|
-
private
|
|
39
|
-
private
|
|
40
|
-
private
|
|
41
|
-
private
|
|
42
|
-
private
|
|
43
|
-
private
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
private currentQuality;
|
|
47
|
-
/** Base MIDI note of the key. C4 = middle C. */
|
|
36
|
+
private playing;
|
|
37
|
+
private tonicIndex;
|
|
38
|
+
private phraseIndex;
|
|
39
|
+
private phraseTimer;
|
|
40
|
+
private droneOscs;
|
|
41
|
+
private droneGain;
|
|
42
|
+
private droneFilter;
|
|
43
|
+
private droneActive;
|
|
44
|
+
private lastStreamActivity;
|
|
45
|
+
private watchdogTimer;
|
|
46
|
+
private readonly activeMidiNotes;
|
|
47
|
+
/** Base MIDI note of the tonic. C4 = middle C. */
|
|
48
48
|
private static readonly BASE_MIDI;
|
|
49
|
-
/** Beat length in ms —
|
|
49
|
+
/** Beat length in ms — the phrase rhythm is measured in these. */
|
|
50
50
|
private static readonly BEAT_MS;
|
|
51
|
+
/** Stop everything if the SSE stream goes silent this long. */
|
|
52
|
+
private static readonly STREAM_TIMEOUT_MS;
|
|
51
53
|
/** True once the audio context has been started (needs a user gesture). */
|
|
52
54
|
get started(): boolean;
|
|
53
55
|
/**
|
|
54
|
-
* Start the audio context and
|
|
55
|
-
*
|
|
56
|
-
* AudioContext can resume. Safe to call multiple times.
|
|
56
|
+
* Start the audio context and probe for a MIDI output (once). Must be
|
|
57
|
+
* called inside a user gesture so the AudioContext can resume.
|
|
57
58
|
*/
|
|
58
59
|
ensureStarted(): Promise<boolean>;
|
|
59
60
|
setEnabled(value: boolean): void;
|
|
60
61
|
setVolume(value: number): void;
|
|
61
62
|
setStyle(value: TokenQuotaMusicStyle): void;
|
|
62
|
-
/**
|
|
63
|
+
/** Feed the engine one SSE activity timestamp (action or heartbeat). */
|
|
64
|
+
markStreamActivity(): void;
|
|
65
|
+
/** Handle one incoming host action and steer the piece. */
|
|
63
66
|
onAction(action: TokenQuotaMusicAction): void;
|
|
64
|
-
/** Start
|
|
65
|
-
private
|
|
66
|
-
/**
|
|
67
|
-
private
|
|
68
|
-
/**
|
|
69
|
-
private
|
|
70
|
-
/**
|
|
71
|
-
private
|
|
72
|
-
|
|
67
|
+
/** Start a new piece: drone + ascending glissando + phrase chain. */
|
|
68
|
+
private startPiece;
|
|
69
|
+
/** Play one phrase, then chain into the next after a one-beat rest. */
|
|
70
|
+
private playPhrase;
|
|
71
|
+
/** Insert a 过门 fill: pause the chain, play the run, resume after. */
|
|
72
|
+
private insertFill;
|
|
73
|
+
/** Stop the melody chain (drone keeps fading naturally). */
|
|
74
|
+
private stopMelody;
|
|
75
|
+
/** Stop the whole piece; `final` adds the closing cadence gestures. */
|
|
76
|
+
private stopPiece;
|
|
77
|
+
/** Hard stop everything (master switch off / teardown). */
|
|
73
78
|
private stopAll;
|
|
74
|
-
/**
|
|
75
|
-
private
|
|
76
|
-
/**
|
|
77
|
-
private
|
|
79
|
+
/** Start the sustained open fifth (root + fifth below octave root). */
|
|
80
|
+
private startDrone;
|
|
81
|
+
/** Glide the drone to a new tonic (mode rotation). */
|
|
82
|
+
private glideDrone;
|
|
83
|
+
/** Fade the drone out and stop its oscillators. */
|
|
84
|
+
private fadeDrone;
|
|
85
|
+
/**
|
|
86
|
+
* Guzheng-like pluck: two slightly detuned oscillators through a sweeping
|
|
87
|
+
* lowpass, fast attack and a long exponential decay (notes overlap, which
|
|
88
|
+
* keeps the piece continuous instead of choppy).
|
|
89
|
+
*/
|
|
90
|
+
private pluck;
|
|
91
|
+
/** Dizi-like breath note: sine with vibrato and a soft envelope. */
|
|
92
|
+
private breath;
|
|
93
|
+
/** Send one MIDI note with a registered NoteOff (anti hanging note). */
|
|
94
|
+
private midiNote;
|
|
95
|
+
/** Silence every registered note and send All Notes Off. */
|
|
96
|
+
private allMidiOff;
|
|
78
97
|
}
|
|
79
98
|
//# sourceMappingURL=music.d.ts.map
|