@solidrt/core 0.0.73 → 0.0.75
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/AGENTS.md +15 -10
- package/docs/index.md +1 -1
- package/package.json +3 -4
- package/src/microphone.ts +37 -18
- package/src/runtime-modules.d.ts +7 -0
- package/src/types.d.ts +0 -12
- package/src/speech-recognition.ts +0 -150
package/AGENTS.md
CHANGED
|
@@ -118,16 +118,21 @@ tsconfig.json - the two load-bearing lines are jsx + jsxImportSource:
|
|
|
118
118
|
Peer deps @solidjs/signals and @solidjs/universal must match (currently
|
|
119
119
|
2.0.0-rc.14); bun resolves them from peerDependencies. On a version bump,
|
|
120
120
|
`bun install` only warns ("incorrect peer dependency") and keeps the old
|
|
121
|
-
ones, and
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
121
|
+
ones, and so does deleting their node_modules copies first (bun 1.3.14
|
|
122
|
+
reinstalls them from bun.lock). `bun update solid-js @solidjs/signals
|
|
123
|
+
@solidjs/universal` does not read the pin either: it adds all three to
|
|
124
|
+
package.json at registry latest (solid-js 1.x, off Solid 2.0 entirely).
|
|
125
|
+
The recipe that works pins all three explicitly at the new version:
|
|
126
|
+
`bun add solid-js@2.0.0-rc.14 @solidjs/signals@2.0.0-rc.14 @solidjs/universal@2.0.0-rc.14`.
|
|
127
|
+
It moves exactly those three lock entries and nothing else. An app `sol
|
|
128
|
+
init` wrote has the three pinned in package.json already (the scaffold
|
|
129
|
+
carries them, and release.yml takes the pins from core's peerDependencies
|
|
130
|
+
at publish), so there the upgrade is the pins edited and `bun install`.
|
|
131
|
+
Do not delete `bun.lock` or its entries to force it: bun then re-resolves
|
|
132
|
+
every dependency, and a package that floats elsewhere fails the typecheck
|
|
133
|
+
in a way that reads exactly like Solid fallout. A Solid bump in this repo
|
|
134
|
+
moves core's peerDependencies and the scaffold's pins
|
|
135
|
+
(packages/cli/src/init/scaffold/package.json) together.
|
|
131
136
|
|
|
132
137
|
## Element model (the parts that are easy to get wrong)
|
|
133
138
|
|
package/docs/index.md
CHANGED
|
@@ -157,7 +157,7 @@ let camera = createCamera()
|
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
The same pattern covers `@solidrt/core/microphone`, `/sound`,
|
|
160
|
-
`/
|
|
160
|
+
`/text-input`, `/image`, `/color`, and `/gpu`.
|
|
161
161
|
|
|
162
162
|
## Lifecycle
|
|
163
163
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.75",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"funding": "https://github.com/sponsors/wellawaretech",
|
|
6
6
|
"author": "Antoine van Wel",
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"./input": "./src/input.ts",
|
|
18
18
|
"./audio": "./src/audio.ts",
|
|
19
19
|
"./microphone": "./src/microphone.ts",
|
|
20
|
-
"./speech-recognition": "./src/speech-recognition.ts",
|
|
21
20
|
"./text-input": "./src/text-input.ts",
|
|
22
21
|
"./textures": "./src/textures.ts",
|
|
23
22
|
"./video": "./src/video.ts",
|
|
@@ -33,8 +32,8 @@
|
|
|
33
32
|
"AGENTS.md"
|
|
34
33
|
],
|
|
35
34
|
"devDependencies": {
|
|
36
|
-
"@solidrt/flux-types": "0.0.
|
|
37
|
-
"@solidrt/test": "0.0.
|
|
35
|
+
"@solidrt/flux-types": "0.0.75",
|
|
36
|
+
"@solidrt/test": "0.0.75"
|
|
38
37
|
},
|
|
39
38
|
"peerDependencies": {
|
|
40
39
|
"@solidjs/signals": "2.0.0-rc.14",
|
package/src/microphone.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// Microphone capture, reactive (SolidJS) layer. A session delivers raw mono
|
|
2
2
|
// float32 samples at the requested sample rate (the device format is converted
|
|
3
|
-
// by SDL); read() drains whatever was captured since the last call
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// by SDL); read() drains whatever was captured since the last call, out of a
|
|
4
|
+
// 10 s window the session keeps, so nothing has to poll: read when the app
|
|
5
|
+
// wants samples, and a runtime module that works on the audio reads the same
|
|
6
|
+
// session on its own thread, given id(). Opening the microphone IS the
|
|
7
|
+
// permission request (SDL semantics): it resolves once capture runs and
|
|
8
|
+
// rejects if the user denies access.
|
|
6
9
|
//
|
|
7
10
|
// The imperative primitive lives in the `flux:microphone` module; import
|
|
8
11
|
// { open, listMicrophones } from "flux:microphone" for non-reactive use.
|
|
@@ -17,33 +20,48 @@ export type MicrophoneOptions = {
|
|
|
17
20
|
sampleRate?: number
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
/** A live microphone
|
|
23
|
+
/** A live microphone as reactive accessors. */
|
|
21
24
|
export type MicrophoneStream = {
|
|
22
|
-
/**
|
|
25
|
+
/** The session id to hand a runtime module that reads this microphone, undefined while opening or if opening failed. */
|
|
26
|
+
id(): number | undefined
|
|
27
|
+
/** Sample rate of read() samples, 0 while opening or if opening failed. */
|
|
23
28
|
sampleRate(): number
|
|
24
29
|
/** Drain the mono float32 samples captured since the last read (empty if not open). */
|
|
25
30
|
read(): Float32Array
|
|
26
|
-
/** Set if opening failed. */
|
|
31
|
+
/** Set if opening failed (e.g. permission denied). */
|
|
27
32
|
error(): Error | undefined
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
/**
|
|
31
|
-
* Opens a microphone and
|
|
32
|
-
*
|
|
33
|
-
*
|
|
36
|
+
* Opens a microphone and exposes it as reactive signals: id() and
|
|
37
|
+
* sampleRate() are set once capture runs, error() if the open failed or the
|
|
38
|
+
* user denied the permission. Closes automatically when the reactive owner
|
|
39
|
+
* is disposed (e.g. the component unmounts). Capture stays pull-based:
|
|
40
|
+
* read() drains the samples captured since the last read, out of the
|
|
41
|
+
* session's 10 s window. For imperative use, call open() from
|
|
42
|
+
* "flux:microphone".
|
|
34
43
|
*/
|
|
35
44
|
export function createMicrophone(options: MicrophoneOptions = {}): MicrophoneStream {
|
|
45
|
+
let [id, setId] = createSignal<number | undefined>(undefined)
|
|
46
|
+
let [sampleRate, setSampleRate] = createSignal(0)
|
|
36
47
|
let [error, setError] = createSignal<Error | undefined>(undefined)
|
|
37
|
-
let session: ReturnType<typeof open
|
|
38
|
-
let
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
let session: Awaited<ReturnType<typeof open>> | undefined
|
|
49
|
+
let disposed = false
|
|
50
|
+
|
|
51
|
+
open(options)
|
|
52
|
+
.then((mic) => {
|
|
53
|
+
if (disposed) {
|
|
54
|
+
mic.close()
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
session = mic
|
|
58
|
+
setId(mic.id)
|
|
59
|
+
setSampleRate(mic.sampleRate)
|
|
60
|
+
})
|
|
61
|
+
.catch((e) => setError(e instanceof Error ? e : new Error(String(e))))
|
|
45
62
|
|
|
46
63
|
onCleanup(() => {
|
|
64
|
+
disposed = true
|
|
47
65
|
if (session) {
|
|
48
66
|
session.close()
|
|
49
67
|
session = undefined
|
|
@@ -51,7 +69,8 @@ export function createMicrophone(options: MicrophoneOptions = {}): MicrophoneStr
|
|
|
51
69
|
})
|
|
52
70
|
|
|
53
71
|
return {
|
|
54
|
-
|
|
72
|
+
id,
|
|
73
|
+
sampleRate,
|
|
55
74
|
read: () => (session ? session.read() : new Float32Array(0)),
|
|
56
75
|
error,
|
|
57
76
|
}
|
package/src/runtime-modules.d.ts
CHANGED
|
@@ -258,6 +258,13 @@ declare module "sol:test" {
|
|
|
258
258
|
* Throws on a size that does not match the pixels.
|
|
259
259
|
*/
|
|
260
260
|
export function camera(width: number, height: number, pixels: Uint8Array): void
|
|
261
|
+
/**
|
|
262
|
+
* Installs a mono float clip at `sampleRate` Hz as the microphone: every
|
|
263
|
+
* microphone the app opens at that rate from now on holds it from the
|
|
264
|
+
* open, with no device, and an open one hears it now. Throws on a rate of
|
|
265
|
+
* 0 or one that differs from an open test microphone's.
|
|
266
|
+
*/
|
|
267
|
+
export function microphone(sampleRate: number, samples: Float32Array): void
|
|
261
268
|
/**
|
|
262
269
|
* Runs one frame: timers due by its time fire, then frame callbacks and
|
|
263
270
|
* the flush, then the draw if anything demanded one. Fulfills once the
|
package/src/types.d.ts
CHANGED
|
@@ -23,18 +23,6 @@ declare global {
|
|
|
23
23
|
*/
|
|
24
24
|
readonly env: { readonly DEV: boolean }
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
let speech: {
|
|
28
|
-
start(options: {
|
|
29
|
-
model: Uint8Array, vadModel: Uint8Array, lang?: string, microphone?: number,
|
|
30
|
-
continuous?: boolean, interimResults?: boolean, wakeWord?: Uint8Array | string | string[], wakeThreshold?: number,
|
|
31
|
-
}): Promise<{ handle: number }>
|
|
32
|
-
setResultCallback(handle: number, callback: (result: { transcript: string, isFinal: boolean }) => void): void
|
|
33
|
-
setSpeechStartCallback(handle: number, callback: () => void): void
|
|
34
|
-
setSpeechEndCallback(handle: number, callback: () => void): void
|
|
35
|
-
setWakeCallback(handle: number, callback: () => void): void
|
|
36
|
-
stop(handle: number): void
|
|
37
|
-
}
|
|
38
26
|
}
|
|
39
27
|
|
|
40
28
|
// JSX value model. The element type is solid-js's (its control-flow components
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
// Speech recognition. A session captures the microphone, segments utterances
|
|
2
|
-
// by silence (Silero VAD) and transcribes each one with Whisper, delivering
|
|
3
|
-
// final transcripts through onResult. With wakeWord the session starts
|
|
4
|
-
// asleep behind an efficient wake word detector (livekit-wakeword) and only
|
|
5
|
-
// transcribes after the wake word. startRecognition resolves once the models
|
|
6
|
-
// are loaded and listening has begun; it rejects when the microphone cannot be
|
|
7
|
-
// opened or the models fail to load.
|
|
8
|
-
// Models are passed as bytes so any source composes: flux:fs file(), fetch
|
|
9
|
-
// (incl. the dev-server file proxy), or a download cache layered on top.
|
|
10
|
-
// Requires a runtime built with speech support.
|
|
11
|
-
|
|
12
|
-
import { createSignal, onCleanup } from "@solidjs/signals"
|
|
13
|
-
|
|
14
|
-
export type SpeechOptions = {
|
|
15
|
-
/** A ggml Whisper model (file contents, e.g. ggml-tiny.en.bin). */
|
|
16
|
-
model: Uint8Array
|
|
17
|
-
/** A ggml Silero VAD model (file contents). */
|
|
18
|
-
vadModel: Uint8Array
|
|
19
|
-
/** Whisper language code; "auto" detects (multilingual models only). Default "en". */
|
|
20
|
-
lang?: string
|
|
21
|
-
/** Explicit microphone device id from listMicrophones(). */
|
|
22
|
-
microphone?: number
|
|
23
|
-
/**
|
|
24
|
-
* Keep transcribing utterance after utterance. Default true. Set false to
|
|
25
|
-
* stop after the first final result (with wakeWord: re-arm instead, one
|
|
26
|
-
* result per wake). Inverse of the Web Speech API default (false there).
|
|
27
|
-
*/
|
|
28
|
-
continuous?: boolean
|
|
29
|
-
/** Also deliver snapshot transcripts (final: false) while an utterance is still being spoken. */
|
|
30
|
-
interimResults?: boolean
|
|
31
|
-
/**
|
|
32
|
-
* Wake word: start asleep, fire onWake when it is heard, then transcribe
|
|
33
|
-
* the speech that follows. How the wake word is specified depends on the
|
|
34
|
-
* engine. The current engine detects with a trained classifier and takes
|
|
35
|
-
* the model's bytes (livekit-wakeword ONNX, e.g. the pretrained "hey
|
|
36
|
-
* livekit"; custom phrases are trained offline with its toolkit). Phrase
|
|
37
|
-
* strings are reserved for engines that match text; passing them to this
|
|
38
|
-
* engine rejects with an error.
|
|
39
|
-
*/
|
|
40
|
-
wakeWord?: Uint8Array | string | string[]
|
|
41
|
-
/** Detector confidence (0..1) that counts as a wake. Default 0.5. */
|
|
42
|
-
wakeThreshold?: number
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type SpeechResult = {
|
|
46
|
-
/** Transcript of the utterance (a snapshot of it when isFinal is false). */
|
|
47
|
-
transcript: string
|
|
48
|
-
/** True for the completed utterance, false for interim snapshots. */
|
|
49
|
-
isFinal: boolean
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export type SpeechSession = {
|
|
53
|
-
/** Receive transcripts (replaces any previous callback). */
|
|
54
|
-
onResult(callback: (result: SpeechResult) => void): void
|
|
55
|
-
/** The user started speaking (replaces any previous callback). */
|
|
56
|
-
onSpeechStart(callback: () => void): void
|
|
57
|
-
/** The utterance ended; its final result follows once transcribed. */
|
|
58
|
-
onSpeechEnd(callback: () => void): void
|
|
59
|
-
/** The wake word was heard (wakeWordModel sessions only). */
|
|
60
|
-
onWake(callback: () => void): void
|
|
61
|
-
/** Release the microphone and discard any utterance in progress. */
|
|
62
|
-
stop(): void
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function startRecognition(options: SpeechOptions): Promise<SpeechSession> {
|
|
66
|
-
let started = await speech.start(options)
|
|
67
|
-
return {
|
|
68
|
-
onResult: (callback: (result: SpeechResult) => void) => speech.setResultCallback(started.handle, callback),
|
|
69
|
-
onSpeechStart: (callback: () => void) => speech.setSpeechStartCallback(started.handle, callback),
|
|
70
|
-
onSpeechEnd: (callback: () => void) => speech.setSpeechEndCallback(started.handle, callback),
|
|
71
|
-
onWake: (callback: () => void) => speech.setWakeCallback(started.handle, callback),
|
|
72
|
-
stop: () => speech.stop(started.handle),
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** A live recognition session as reactive accessors. */
|
|
77
|
-
export type SpeechStream = {
|
|
78
|
-
/** True once the models are loaded and listening has begun. */
|
|
79
|
-
ready(): boolean
|
|
80
|
-
/** Latest transcript, "" until the first result (a snapshot while isFinal is false). */
|
|
81
|
-
transcript(): string
|
|
82
|
-
/** Whether transcript() is the completed utterance rather than an interim snapshot. */
|
|
83
|
-
isFinal(): boolean
|
|
84
|
-
/** True while the user is mid-utterance (between speech start and end). */
|
|
85
|
-
speaking(): boolean
|
|
86
|
-
/** True from when an utterance ends until its transcript arrives. */
|
|
87
|
-
transcribing(): boolean
|
|
88
|
-
/**
|
|
89
|
-
* wakeWord sessions only: true from when the wake word is heard until the
|
|
90
|
-
* following command is transcribed (the next final result), then false.
|
|
91
|
-
* Always false without a wakeWord.
|
|
92
|
-
*/
|
|
93
|
-
awake(): boolean
|
|
94
|
-
/** Set if loading or starting failed. */
|
|
95
|
-
error(): Error | undefined
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Starts speech recognition and exposes it as reactive signals: read
|
|
100
|
-
* transcript()/isFinal() for results, speaking() and awake() for session
|
|
101
|
-
* state. Stops when the reactive owner is disposed. The lower-level
|
|
102
|
-
* startRecognition() is the imperative alternative.
|
|
103
|
-
*/
|
|
104
|
-
export function createSpeechRecognition(options: SpeechOptions): SpeechStream {
|
|
105
|
-
let [ready, setReady] = createSignal(false)
|
|
106
|
-
let [transcript, setTranscript] = createSignal("")
|
|
107
|
-
let [isFinal, setIsFinal] = createSignal(false)
|
|
108
|
-
let [speaking, setSpeaking] = createSignal(false)
|
|
109
|
-
let [transcribing, setTranscribing] = createSignal(false)
|
|
110
|
-
let [awake, setAwake] = createSignal(false)
|
|
111
|
-
let [error, setError] = createSignal<Error | undefined>(undefined)
|
|
112
|
-
let handle: number | undefined
|
|
113
|
-
let disposed = false
|
|
114
|
-
|
|
115
|
-
speech
|
|
116
|
-
.start(options)
|
|
117
|
-
.then((started) => {
|
|
118
|
-
if (disposed) {
|
|
119
|
-
speech.stop(started.handle)
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
handle = started.handle
|
|
123
|
-
speech.setResultCallback(started.handle, (result) => {
|
|
124
|
-
setTranscript(result.transcript)
|
|
125
|
-
setIsFinal(result.isFinal)
|
|
126
|
-
if (result.isFinal) {
|
|
127
|
-
setTranscribing(false)
|
|
128
|
-
setAwake(false)
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
speech.setSpeechStartCallback(started.handle, () => setSpeaking(true))
|
|
132
|
-
speech.setSpeechEndCallback(started.handle, () => {
|
|
133
|
-
setSpeaking(false)
|
|
134
|
-
setTranscribing(true)
|
|
135
|
-
})
|
|
136
|
-
speech.setWakeCallback(started.handle, () => setAwake(true))
|
|
137
|
-
setReady(true)
|
|
138
|
-
})
|
|
139
|
-
.catch((e) => setError(e instanceof Error ? e : new Error(String(e))))
|
|
140
|
-
|
|
141
|
-
onCleanup(() => {
|
|
142
|
-
disposed = true
|
|
143
|
-
if (handle !== undefined) {
|
|
144
|
-
speech.stop(handle)
|
|
145
|
-
handle = undefined
|
|
146
|
-
}
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
return { ready, transcript, isFinal, speaking, transcribing, awake, error }
|
|
150
|
-
}
|