arcane-os 0.39.0 → 0.39.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/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-entities.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +1 -1
- package/docs/reference/runtime-components.md +8 -0
- package/docs/reference/runtime-entities.md +5 -0
- package/docs/reference/runtime-modules.md +17 -1
- package/package.json +1 -1
- package/runtime/arcane/components/markdown-editor.html +58 -11
- package/runtime/arcane/entities/Chat.js +4 -3
- package/runtime/arcane/modules/VoiceCaptureWorklet.js +31 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.39.1
|
|
4
|
+
|
|
5
|
+
- Preserve the Markdown editor's current source viewport and selection during
|
|
6
|
+
configuration, programmatic value changes, preview rendering and toolbar
|
|
7
|
+
formatting. Keep the live textarea attached; only the preview follows edits.
|
|
8
|
+
Native typing, manual scrolling, complete content and save behavior remain
|
|
9
|
+
independent and unchanged.
|
|
10
|
+
- Generate default Chat filenames with a timestamp and an independent native
|
|
11
|
+
random identifier so sessions created in the same clock tick do not share a
|
|
12
|
+
storage name. Preserve explicit and saved filenames, existing stored content,
|
|
13
|
+
persistence format and request-only `persist:false` behavior.
|
|
14
|
+
- Detect continuous-capture activity using fixed 20 ms RMS audio windows so
|
|
15
|
+
brief background peaks do not indefinitely postpone quiet-gap segments.
|
|
16
|
+
Preserve complete original samples, pre-roll, periodic clip adjacency and
|
|
17
|
+
partial final flush. `activityThreshold` now measures sustained RMS level;
|
|
18
|
+
callers retain control of sensitivity for softer input.
|
|
19
|
+
|
|
3
20
|
## 0.39.0
|
|
4
21
|
|
|
5
22
|
- Add application-owned assistant display names to new Chat turns through
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles share the theme, packaging, event, cancellation,
|
|
20
20
|
and browser run contracts while retaining their selected application layout.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.39.
|
|
22
|
+
This checkout defines the `0.39.1` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
@@ -535,7 +535,7 @@
|
|
|
535
535
|
],
|
|
536
536
|
"availability": "Browser and supported native WebViews",
|
|
537
537
|
"transport": "HTMLImport + DOM; injected Arcane/provider modules where listed",
|
|
538
|
-
"normalization": "Editor values and optional presentation behavior normalized; injected save result mixed. fit and followPreview default false, preserve omitted prior configuration, and accept explicit false resets; data-fit and data-follow-preview select the initial options. Fit requires an application-bounded host and provides independent input/preview scrolling in roomy columns or narrow stacked panes without changing the application shell. Following responds to body typing/format edits, leaves manual preview scrolling independent until the next edit, and does not treat title edits or programmatic value replacement as follow requests. Public parts editor/title/toolbar/input/preview/actions/status/save-action allow application styling while the title stays native plain text. Existing controls, complete save payload, save cancellation, and destruction behavior remain."
|
|
538
|
+
"normalization": "Editor values and optional presentation behavior normalized; injected save result mixed. fit and followPreview default false, preserve omitted prior configuration, and accept explicit false resets; data-fit and data-follow-preview select the initial options. Fit requires an application-bounded host and provides independent input/preview scrolling in roomy columns or narrow stacked panes without changing the application shell. Following responds to body typing/format edits, leaves manual preview scrolling independent until the next edit, and does not treat title edits or programmatic value replacement as follow requests. Preview rendering preserves the current source viewport and selection; configuration/value replacement retain source offsets and applicable selection positions, and layout changes never reattach the textarea. Formatting selects the formatted text without panning the source or its ancestors. Native typing, keyboard selection, and manual source scrolling remain independent without deferred scroll restoration. Public parts editor/title/toolbar/input/preview/actions/status/save-action allow application styling while the title stays native plain text. Existing controls, complete save payload, save cancellation, and destruction behavior remain."
|
|
539
539
|
},
|
|
540
540
|
{
|
|
541
541
|
"file": "runtime/arcane/components/media-embed.html",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"modules": [
|
|
10
10
|
{"file":"runtime/arcane/entities/ApiModelRecord.js","exports":["default"],"availability":"cross-host","normalization":"normalized"},
|
|
11
11
|
{"file":"runtime/arcane/entities/Calculation.js","exports":["default"],"availability":"cross-host","normalization":"normalized"},
|
|
12
|
-
{"file":"runtime/arcane/entities/Chat.js","exports":["default"],"availability":"browser/native-webview","normalization":"Provider-facing recurring messages preserve raw structural protocol only for the one active matching continuation, then replace settled exchanges with complete ordinary visible content; transcript records remain sanitized and readable, including ui_hidden rows. aiName defaults to empty; addAIMessage and addTurn capture an optional exact nonblank application-owned name on new assistant records only. Provider names do not establish display attribution; assistant display names stay out of messages and memory extraction, and existing records are never renamed or rewritten. ordered parallel structural calls require one exact nonblank result for every pending ID in one atomic turn, with rollback-safe persistence."},
|
|
12
|
+
{"file":"runtime/arcane/entities/Chat.js","exports":["default"],"availability":"browser/native-webview","normalization":"New default filenames combine the timestamp with an independent random identifier; explicit and saved filenames remain unchanged. Provider-facing recurring messages preserve raw structural protocol only for the one active matching continuation, then replace settled exchanges with complete ordinary visible content; transcript records remain sanitized and readable, including ui_hidden rows. aiName defaults to empty; addAIMessage and addTurn capture an optional exact nonblank application-owned name on new assistant records only. Provider names do not establish display attribution; assistant display names stay out of messages and memory extraction, and existing records are never renamed or rewritten. ordered parallel structural calls require one exact nonblank result for every pending ID in one atomic turn, with rollback-safe persistence."},
|
|
13
13
|
{"file":"runtime/arcane/entities/CommunicationMessage.js","exports":["communicationChannels","default"],"availability":"cross-host","normalization":"normalized"},
|
|
14
14
|
{"file":"runtime/arcane/entities/CommunicationThread.js","exports":["default"],"availability":"cross-host","normalization":"normalized"},
|
|
15
15
|
{"file":"runtime/arcane/entities/Document.js","exports":["default"],"availability":"browser/native-webview","normalization":"mixed"},
|
|
@@ -1393,7 +1393,7 @@
|
|
|
1393
1393
|
"summary": "Segments microphone samples on the audio rendering thread and transfers complete mono Float32 WAV clips to ContinuousVoiceCapture.",
|
|
1394
1394
|
"availability": "AudioWorkletGlobalScope",
|
|
1395
1395
|
"protocol": "AudioWorklet processor arcane-continuous-voice-capture and its owner-local MessagePort",
|
|
1396
|
-
"normalization": "Rolling pre-roll,
|
|
1396
|
+
"normalization": "Rolling pre-roll, fixed 20 ms RMS activity windows independent of callback length, audio-frame quiet duration, periodic boundaries, sequence, complete original WAV audio, and stop acknowledgement are owned here. activityThreshold is RMS amplitude, not peak amplitude. Stop classifies a partial final window using its actual samples and retains softer trailing audio in the active clip. Inactive capture emits no clips; adjacent periodic clips have no repeated samples.",
|
|
1397
1397
|
"surface": "No ESM exports. ContinuousVoiceCapture loads this static worklet, supplies processorOptions, receives segment messages, and requests stop followed by the stopped acknowledgement."
|
|
1398
1398
|
},
|
|
1399
1399
|
{
|
|
@@ -1067,6 +1067,14 @@ programmatic value replacement, and ordinary preview scrolling do not request
|
|
|
1067
1067
|
edit-position following. Moving the selection alone does not trigger following,
|
|
1068
1068
|
and following never scrolls the input pane or containing page. The title
|
|
1069
1069
|
remains a native plain-text input.
|
|
1070
|
+
Preview rendering preserves the source pane's current horizontal and vertical
|
|
1071
|
+
scroll offsets and selection. Value replacement and reconfiguration retain
|
|
1072
|
+
those offsets and the selection positions that still fit the new text; layout
|
|
1073
|
+
changes move only the preview, keeping the live textarea attached. Toolbar
|
|
1074
|
+
formatting focuses the source without scrolling its ancestors, selects the
|
|
1075
|
+
formatted text, and preserves the source viewport. Native typing, keyboard
|
|
1076
|
+
selection, and manual source scrolling retain their ordinary behavior; there
|
|
1077
|
+
is no scroll lock or deferred restoration of an earlier source position.
|
|
1070
1078
|
Following uses the shared [Markdown block mapping](runtime-modules.md#mdjs);
|
|
1071
1079
|
lists, quotes, and tables follow their block, while invisible source positions
|
|
1072
1080
|
use a nearby available rendered block rather than exact glyph geometry.
|
|
@@ -73,6 +73,11 @@ console.log(new Calculation({expression: '2 + 2', result: 4}).toJSON());
|
|
|
73
73
|
and optional app-scoped DBOPFS persistence. It installs no independent native
|
|
74
74
|
authority; AI and storage dependencies must be available to the host.
|
|
75
75
|
|
|
76
|
+
Each new chat receives a timestamp plus independently generated random identifier
|
|
77
|
+
in its default `.jsonl` filename, so chats created during the same clock tick do
|
|
78
|
+
not share a storage key. Explicit filenames and existing saved files remain
|
|
79
|
+
unchanged; no saved records are renamed or migrated.
|
|
80
|
+
|
|
76
81
|
`messages` returns provider-facing recurring context. An unresolved structural
|
|
77
82
|
call and its matching results remain raw only through their one active provider
|
|
78
83
|
continuation. Once that continuation settles, `messages` replaces the protocol
|
|
@@ -4069,7 +4069,7 @@ Load with a stylesheet link before rendering uPlot charts.
|
|
|
4069
4069
|
### Overview
|
|
4070
4070
|
|
|
4071
4071
|
Internal audio processor for [`ContinuousVoiceCapture.js`](#continuousvoicecapturejs).
|
|
4072
|
-
It keeps rolling pre-roll, detects audio
|
|
4072
|
+
It keeps rolling pre-roll, detects RMS audio level, preserves complete active
|
|
4073
4073
|
audio through quiet and periodic cuts, and encodes mono Float32 WAV clips on
|
|
4074
4074
|
the audio rendering thread. It performs no semantic speech detection or STT.
|
|
4075
4075
|
|
|
@@ -4079,6 +4079,22 @@ No ESM exports. The capture owner loads this static file with
|
|
|
4079
4079
|
`audioContext.audioWorklet.addModule()` and creates the
|
|
4080
4080
|
`arcane-continuous-voice-capture` processor with
|
|
4081
4081
|
`{preRollMs,quietMs,chunkMs,activityThreshold}` in `processorOptions`.
|
|
4082
|
+
`activityThreshold` measures root-mean-square amplitude over fixed 20 ms
|
|
4083
|
+
windows counted from the audio sample rate, independently of browser callback
|
|
4084
|
+
lengths. Brief peaks do not reset the quiet duration when the window's RMS
|
|
4085
|
+
remains below that threshold. This is an RMS threshold, not a peak threshold:
|
|
4086
|
+
a sine wave with peak amplitude `0.025` can remain below the default `0.02`
|
|
4087
|
+
RMS threshold. Applications can select the existing threshold for their audio
|
|
4088
|
+
level; no relative-noise or semantic speech classifier is implied.
|
|
4089
|
+
|
|
4090
|
+
Classification selects segment boundaries without modifying the captured
|
|
4091
|
+
samples. Quiet duration advances by audio frames, pre-roll remains complete,
|
|
4092
|
+
and periodic clips remain adjacent. Stop classifies any partial final window
|
|
4093
|
+
using its actual samples and flushes the final active clip, including softer
|
|
4094
|
+
trailing audio before the quiet boundary. Analysis adds at most one 20 ms
|
|
4095
|
+
window of capture-to-segmentation delay; model processing is independently
|
|
4096
|
+
owned by the transcription queue.
|
|
4097
|
+
|
|
4082
4098
|
The owner sends `{type:'stop'}`. The processor transfers each complete
|
|
4083
4099
|
`{type:'segment',audio:ArrayBuffer,sampleRate,sequence,reason,durationMs}`
|
|
4084
4100
|
and finally `{type:'stopped'}`. Each complete WAV includes its own format,
|
package/package.json
CHANGED
|
@@ -224,6 +224,7 @@
|
|
|
224
224
|
if(destroyed){
|
|
225
225
|
return false;
|
|
226
226
|
}
|
|
227
|
+
const sourceView = captureSourceView();
|
|
227
228
|
const setInitialValue=Object.prototype.hasOwnProperty.call(
|
|
228
229
|
input||{},
|
|
229
230
|
'initialValue'
|
|
@@ -240,9 +241,12 @@
|
|
|
240
241
|
}
|
|
241
242
|
if(setInitialValue){
|
|
242
243
|
supersedeSave('markdown-content-replaced');
|
|
243
|
-
textarea.value
|
|
244
|
+
if (textarea.value !== options.initialValue) {
|
|
245
|
+
textarea.value = options.initialValue;
|
|
246
|
+
}
|
|
244
247
|
}
|
|
245
248
|
updatePreview();
|
|
249
|
+
restoreSourceView(sourceView);
|
|
246
250
|
return getOptions();
|
|
247
251
|
}
|
|
248
252
|
|
|
@@ -250,13 +254,14 @@
|
|
|
250
254
|
host.dataset.fit=String(options.fit);
|
|
251
255
|
editor.classList.toggle('fit',options.fit);
|
|
252
256
|
editor.classList.toggle('without-preview',!options.showPreview);
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
// Move only the preview when the layout changes. Reattaching the live
|
|
258
|
+
// textarea during ordinary configuration would disturb focus/selection.
|
|
259
|
+
if (options.fit) {
|
|
260
|
+
if (textarea.nextSibling !== preview) {
|
|
261
|
+
editor.insertBefore(preview, textarea.nextSibling);
|
|
262
|
+
}
|
|
263
|
+
} else if (preview.nextSibling !== toolbar) {
|
|
264
|
+
editor.insertBefore(preview, toolbar);
|
|
260
265
|
}
|
|
261
266
|
titleInput.placeholder=options.labels.titlePlaceholder;
|
|
262
267
|
titleInput.setAttribute('aria-label',options.labels.titlePlaceholder);
|
|
@@ -331,6 +336,36 @@
|
|
|
331
336
|
previewFollowPending=false;
|
|
332
337
|
}
|
|
333
338
|
|
|
339
|
+
function captureSourceView() {
|
|
340
|
+
return {
|
|
341
|
+
scrollTop: textarea.scrollTop,
|
|
342
|
+
scrollLeft: textarea.scrollLeft,
|
|
343
|
+
selectionStart: textarea.selectionStart,
|
|
344
|
+
selectionEnd: textarea.selectionEnd,
|
|
345
|
+
selectionDirection: textarea.selectionDirection
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function restoreSourceView(view) {
|
|
350
|
+
if (textarea.selectionStart !== view.selectionStart ||
|
|
351
|
+
textarea.selectionEnd !== view.selectionEnd ||
|
|
352
|
+
textarea.selectionDirection !== view.selectionDirection) {
|
|
353
|
+
textarea.setSelectionRange(
|
|
354
|
+
view.selectionStart,
|
|
355
|
+
view.selectionEnd,
|
|
356
|
+
view.selectionDirection
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
// Native value/focus/selection updates may pan the textarea. Restore
|
|
360
|
+
// this operation's viewport, never a position retained across user input.
|
|
361
|
+
if (textarea.scrollTop !== view.scrollTop) {
|
|
362
|
+
textarea.scrollTop = view.scrollTop;
|
|
363
|
+
}
|
|
364
|
+
if (textarea.scrollLeft !== view.scrollLeft) {
|
|
365
|
+
textarea.scrollLeft = view.scrollLeft;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
334
369
|
function updatePreview(follow=false){
|
|
335
370
|
if(destroyed){
|
|
336
371
|
return;
|
|
@@ -340,6 +375,7 @@
|
|
|
340
375
|
}
|
|
341
376
|
previewFrame=0;
|
|
342
377
|
previewFollowPending=false;
|
|
378
|
+
const sourceView = captureSourceView();
|
|
343
379
|
const scrollTop=preview.scrollTop;
|
|
344
380
|
const scrollLeft=preview.scrollLeft;
|
|
345
381
|
const markdown=new MD(textarea.value,{sourceMap:options.followPreview});
|
|
@@ -355,6 +391,7 @@
|
|
|
355
391
|
if(follow&&options.followPreview&&options.showPreview){
|
|
356
392
|
followTypingPosition(markdown.sourceMap);
|
|
357
393
|
}
|
|
394
|
+
restoreSourceView(sourceView);
|
|
358
395
|
}
|
|
359
396
|
|
|
360
397
|
function followTypingPosition(sourceMap){
|
|
@@ -434,6 +471,7 @@
|
|
|
434
471
|
return;
|
|
435
472
|
}
|
|
436
473
|
|
|
474
|
+
const sourceView = captureSourceView();
|
|
437
475
|
const result=applyMarkdownFormat(
|
|
438
476
|
textarea.value,
|
|
439
477
|
textarea.selectionStart,
|
|
@@ -441,8 +479,12 @@
|
|
|
441
479
|
format
|
|
442
480
|
);
|
|
443
481
|
textarea.value=result.value;
|
|
444
|
-
textarea.focus(
|
|
445
|
-
|
|
482
|
+
textarea.focus(
|
|
483
|
+
{preventScroll: true}
|
|
484
|
+
);
|
|
485
|
+
sourceView.selectionStart = result.selectionStart;
|
|
486
|
+
sourceView.selectionEnd = result.selectionEnd;
|
|
487
|
+
restoreSourceView(sourceView);
|
|
446
488
|
contentChanged();
|
|
447
489
|
}
|
|
448
490
|
|
|
@@ -524,8 +566,13 @@
|
|
|
524
566
|
return false;
|
|
525
567
|
}
|
|
526
568
|
supersedeSave('markdown-content-replaced');
|
|
527
|
-
|
|
569
|
+
const sourceView = captureSourceView();
|
|
570
|
+
const nextValue = `${value ?? ''}`;
|
|
571
|
+
if (textarea.value !== nextValue) {
|
|
572
|
+
textarea.value = nextValue;
|
|
573
|
+
}
|
|
528
574
|
updatePreview();
|
|
575
|
+
restoreSourceView(sourceView);
|
|
529
576
|
return textarea.value;
|
|
530
577
|
}
|
|
531
578
|
|
|
@@ -433,8 +433,8 @@ class ChatEntity{
|
|
|
433
433
|
/**
|
|
434
434
|
* Creates a new chat session.
|
|
435
435
|
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
436
|
+
* The default file name combines the current timestamp with an
|
|
437
|
+
* independent random identifier, including across concurrent tabs.
|
|
438
438
|
*
|
|
439
439
|
* Optionally accepts a system prompt which will be added
|
|
440
440
|
* as the first message in the conversation.
|
|
@@ -444,7 +444,8 @@ class ChatEntity{
|
|
|
444
444
|
*/
|
|
445
445
|
constructor(systemPrompt=''){
|
|
446
446
|
|
|
447
|
-
|
|
447
|
+
const identity=globalThis.crypto.getRandomValues(new Uint32Array(4)).join('-');
|
|
448
|
+
this.fileName=`chat-${Date.now()}-${identity}.jsonl`;
|
|
448
449
|
|
|
449
450
|
if(systemPrompt){
|
|
450
451
|
this.#messages.push(
|
|
@@ -7,6 +7,9 @@ class ArcaneVoiceCaptureProcessor extends AudioWorkletProcessor {
|
|
|
7
7
|
#quietFrames;
|
|
8
8
|
#chunkFrames;
|
|
9
9
|
#activityThreshold;
|
|
10
|
+
#analysisSamples;
|
|
11
|
+
#analysisCount = 0;
|
|
12
|
+
#analysisEnergy = 0;
|
|
10
13
|
#active = false;
|
|
11
14
|
#hasActivity = false;
|
|
12
15
|
#quietCount = 0;
|
|
@@ -25,6 +28,9 @@ class ArcaneVoiceCaptureProcessor extends AudioWorkletProcessor {
|
|
|
25
28
|
this.#quietFrames = Math.max(1, Math.round(configuration.quietMs * sampleRate / 1000));
|
|
26
29
|
this.#chunkFrames = Math.max(1, Math.round(configuration.chunkMs * sampleRate / 1000));
|
|
27
30
|
this.#activityThreshold = configuration.activityThreshold;
|
|
31
|
+
// Measure sustained loudness, not individual peaks. The audio clock
|
|
32
|
+
// owns this fixed 20 ms window, independently of callback block sizes.
|
|
33
|
+
this.#analysisSamples = new Float32Array(Math.max(1, Math.round(sampleRate * 0.02)));
|
|
28
34
|
this.port.onmessage = this.#receiveControl.bind(this);
|
|
29
35
|
}
|
|
30
36
|
|
|
@@ -32,6 +38,7 @@ class ArcaneVoiceCaptureProcessor extends AudioWorkletProcessor {
|
|
|
32
38
|
if (this.#stopped || event.data.type !== 'stop') return;
|
|
33
39
|
this.#stopped = true;
|
|
34
40
|
try {
|
|
41
|
+
this.#flushAnalysis();
|
|
35
42
|
this.#emit('stop');
|
|
36
43
|
this.#release();
|
|
37
44
|
this.port.postMessage(
|
|
@@ -50,15 +57,33 @@ class ArcaneVoiceCaptureProcessor extends AudioWorkletProcessor {
|
|
|
50
57
|
const samples = inputs[0]?.[0];
|
|
51
58
|
if (!samples) return true;
|
|
52
59
|
try {
|
|
53
|
-
for (const sample of samples)
|
|
60
|
+
for (const sample of samples) {
|
|
61
|
+
this.#analysisSamples[this.#analysisCount] = sample;
|
|
62
|
+
this.#analysisCount += 1;
|
|
63
|
+
this.#analysisEnergy += sample * sample;
|
|
64
|
+
if (this.#analysisCount === this.#analysisSamples.length) {
|
|
65
|
+
this.#flushAnalysis();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
54
68
|
} catch (error) {
|
|
55
69
|
this.#fail(error);
|
|
56
70
|
}
|
|
57
71
|
return !this.#stopped;
|
|
58
72
|
}
|
|
59
73
|
|
|
60
|
-
#
|
|
61
|
-
|
|
74
|
+
#flushAnalysis() {
|
|
75
|
+
if (!this.#analysisCount) return;
|
|
76
|
+
const activity = Math.sqrt(this.#analysisEnergy / this.#analysisCount) > this.#activityThreshold;
|
|
77
|
+
// Classification selects boundaries only. Every original sample still
|
|
78
|
+
// enters capture in order, including a partial final analysis window.
|
|
79
|
+
for (let index = 0; index < this.#analysisCount; index += 1) {
|
|
80
|
+
this.#capture(this.#analysisSamples[index], activity);
|
|
81
|
+
}
|
|
82
|
+
this.#analysisCount = 0;
|
|
83
|
+
this.#analysisEnergy = 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#capture(sample, activity) {
|
|
62
87
|
if (!this.#active) {
|
|
63
88
|
if (!activity) {
|
|
64
89
|
this.#retainPreRoll(sample);
|
|
@@ -185,6 +210,9 @@ class ArcaneVoiceCaptureProcessor extends AudioWorkletProcessor {
|
|
|
185
210
|
this.#view = null;
|
|
186
211
|
this.#preRoll = new Float32Array(0);
|
|
187
212
|
this.#preRollLength = 0;
|
|
213
|
+
this.#analysisSamples = new Float32Array(0);
|
|
214
|
+
this.#analysisCount = 0;
|
|
215
|
+
this.#analysisEnergy = 0;
|
|
188
216
|
this.#sampleCount = 0;
|
|
189
217
|
this.#hasActivity = false;
|
|
190
218
|
this.#active = false;
|