@vortex-os/computer-use 0.7.0 → 0.7.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/README.md +179 -177
- package/computer-use.config.example.json +29 -28
- package/package.json +74 -73
- package/scripts/activity.mjs +92 -92
- package/scripts/audio-duck.ps1 +180 -180
- package/scripts/classify.ps1 +8 -8
- package/scripts/fetch-supertonic.mjs +82 -65
- package/scripts/lib.ps1 +679 -679
- package/scripts/mcp-stdio.mjs +1337 -1324
- package/scripts/noise-filter.mjs +135 -135
- package/scripts/ocr.ps1 +92 -92
- package/scripts/speak-supertonic.mjs +296 -296
- package/scripts/speak.ps1 +58 -58
- package/scripts/speech-safety.mjs +104 -104
- package/scripts/vlm.mjs +106 -106
package/scripts/audio-duck.ps1
CHANGED
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
# computer-use — audio ducking helper (pwsh; WASAPI Core Audio via inline C#, NO install).
|
|
2
|
-
#
|
|
3
|
-
# When the companion speaks, briefly lower OTHER apps' audio sessions (game / video / music) so the voice
|
|
4
|
-
# stands out, then restore EXACTLY on completion. Per-app, not master volume — our own voice is excluded.
|
|
5
|
-
#
|
|
6
|
-
# SAFETY: restore is the whole point. Volumes are snapshotted and always restored in a finally block, even if
|
|
7
|
-
# playback throws or the process is asked to stop — a failed restore would leave the user's other apps quiet.
|
|
8
|
-
#
|
|
9
|
-
# Two uses:
|
|
10
|
-
# 1. Dot-source and call [CU.AudioDuck]::Duck(factor, excludePids) -> handle ; [CU.AudioDuck]::Restore(handle).
|
|
11
|
-
# (speak.ps1 / Heami wraps System.Speech this way, excluding its own $PID.)
|
|
12
|
-
# 2. -WavPath <wav>: duck (excluding THIS process), play the WAV via SoundPlayer, restore in finally.
|
|
13
|
-
# (speak-supertonic.mjs spawns this to play its synthesized WAV.)
|
|
14
|
-
#
|
|
15
|
-
# factor is the multiplier applied to other sessions (0.45 = reduce to 45%). 1.0 disables ducking.
|
|
16
|
-
param(
|
|
17
|
-
[string]$WavPath = '',
|
|
18
|
-
[double]$Factor = 0.45,
|
|
19
|
-
[int[]]$ExcludePid = @()
|
|
20
|
-
)
|
|
21
|
-
$ErrorActionPreference = 'Stop'
|
|
22
|
-
try { [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) } catch {}
|
|
23
|
-
|
|
24
|
-
if (-not ('CU.AudioDuck' -as [type])) {
|
|
25
|
-
Add-Type -TypeDefinition @'
|
|
26
|
-
using System;
|
|
27
|
-
using System.Collections.Generic;
|
|
28
|
-
using System.Runtime.InteropServices;
|
|
29
|
-
|
|
30
|
-
namespace CU {
|
|
31
|
-
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
|
|
32
|
-
|
|
33
|
-
enum EDataFlow { eRender = 0, eCapture = 1, eAll = 2 }
|
|
34
|
-
enum ERole { eConsole = 0, eMultimedia = 1, eCommunications = 2 }
|
|
35
|
-
|
|
36
|
-
[ComImport, Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
37
|
-
interface IMMDeviceEnumerator {
|
|
38
|
-
[PreserveSig] int EnumAudioEndpoints(EDataFlow dataFlow, int dwStateMask, out IMMDeviceCollection ppDevices);
|
|
39
|
-
[PreserveSig] int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppEndpoint);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
[ComImport, Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
43
|
-
interface IMMDeviceCollection {
|
|
44
|
-
[PreserveSig] int GetCount(out uint pcDevices);
|
|
45
|
-
[PreserveSig] int Item(uint nDevice, out IMMDevice ppDevice);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
[ComImport, Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
49
|
-
interface IMMDevice {
|
|
50
|
-
[PreserveSig] int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams,
|
|
51
|
-
[MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
[ComImport, Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
55
|
-
interface IAudioSessionManager2 {
|
|
56
|
-
[PreserveSig] int NotImpl1();
|
|
57
|
-
[PreserveSig] int NotImpl2();
|
|
58
|
-
[PreserveSig] int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
[ComImport, Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
62
|
-
interface IAudioSessionEnumerator {
|
|
63
|
-
[PreserveSig] int GetCount(out int SessionCount);
|
|
64
|
-
[PreserveSig] int GetSession(int SessionCount, out IAudioSessionControl Session);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
[ComImport, Guid("F4B1A599-7266-4319-A8CA-E70ACB11E8CD"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
68
|
-
interface IAudioSessionControl { }
|
|
69
|
-
|
|
70
|
-
[ComImport, Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
71
|
-
interface IAudioSessionControl2 {
|
|
72
|
-
// 9 inherited IAudioSessionControl slots (unused) ...
|
|
73
|
-
[PreserveSig] int N1(); [PreserveSig] int N2(); [PreserveSig] int N3();
|
|
74
|
-
[PreserveSig] int N4(); [PreserveSig] int N5(); [PreserveSig] int N6();
|
|
75
|
-
[PreserveSig] int N7(); [PreserveSig] int N8(); [PreserveSig] int N9();
|
|
76
|
-
// IAudioSessionControl2 own slots:
|
|
77
|
-
[PreserveSig] int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string s);
|
|
78
|
-
[PreserveSig] int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string s);
|
|
79
|
-
[PreserveSig] int GetProcessId(out uint pid);
|
|
80
|
-
[PreserveSig] int IsSystemSoundsSession();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
[ComImport, Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
84
|
-
interface ISimpleAudioVolume {
|
|
85
|
-
[PreserveSig] int SetMasterVolume(float level, ref Guid ctx);
|
|
86
|
-
[PreserveSig] int GetMasterVolume(out float level);
|
|
87
|
-
[PreserveSig] int SetMute(bool mute, ref Guid ctx);
|
|
88
|
-
[PreserveSig] int GetMute(out bool mute);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
public class Handle { internal ISimpleAudioVolume Vol; public float Original; public uint Pid; }
|
|
92
|
-
|
|
93
|
-
public static class AudioDuck {
|
|
94
|
-
static Guid IID_IAudioSessionManager2 = new Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F");
|
|
95
|
-
static Guid CTX = Guid.Empty;
|
|
96
|
-
|
|
97
|
-
static IAudioSessionEnumerator SessionsForDevice(IMMDevice dev) {
|
|
98
|
-
object o; if (dev.Activate(ref IID_IAudioSessionManager2, 23 /*CLSCTX_ALL*/, IntPtr.Zero, out o) != 0) return null;
|
|
99
|
-
var mgr = (IAudioSessionManager2)o;
|
|
100
|
-
IAudioSessionEnumerator en; if (mgr.GetSessionEnumerator(out en) != 0) return null;
|
|
101
|
-
return en;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Lower every session (except excludePids and system-sounds) to original*factor, across ALL active render
|
|
105
|
-
// devices (not just the default endpoint) so virtual-audio routing (e.g. VoiceMeeter) is still caught.
|
|
106
|
-
// Returns restore handles. DEVICE_STATE_ACTIVE = 0x1.
|
|
107
|
-
public static List<Handle> Duck(double factor, int[] excludePids) {
|
|
108
|
-
var handles = new List<Handle>();
|
|
109
|
-
var deo = (IMMDeviceEnumerator)(new MMDeviceEnumeratorComObject());
|
|
110
|
-
IMMDeviceCollection coll;
|
|
111
|
-
if (deo.EnumAudioEndpoints(EDataFlow.eRender, 0x1, out coll) != 0 || coll == null) return handles;
|
|
112
|
-
uint dcount; if (coll.GetCount(out dcount) != 0) return handles;
|
|
113
|
-
var excl = new HashSet<uint>(); foreach (var p in excludePids) excl.Add((uint)p);
|
|
114
|
-
for (uint di = 0; di < dcount; di++) {
|
|
115
|
-
IMMDevice dev; if (coll.Item(di, out dev) != 0 || dev == null) continue;
|
|
116
|
-
var en = SessionsForDevice(dev); if (en == null) continue;
|
|
117
|
-
int count; if (en.GetCount(out count) != 0) continue;
|
|
118
|
-
for (int i = 0; i < count; i++) {
|
|
119
|
-
IAudioSessionControl ctl; if (en.GetSession(i, out ctl) != 0 || ctl == null) continue;
|
|
120
|
-
try {
|
|
121
|
-
var c2 = (IAudioSessionControl2)ctl;
|
|
122
|
-
if (c2.IsSystemSoundsSession() == 0) continue; // S_OK(0) means IS system sounds -> skip it
|
|
123
|
-
uint pid; if (c2.GetProcessId(out pid) != 0) continue;
|
|
124
|
-
if (excl.Contains(pid)) continue;
|
|
125
|
-
var vol = (ISimpleAudioVolume)ctl;
|
|
126
|
-
float cur; if (vol.GetMasterVolume(out cur) != 0) continue;
|
|
127
|
-
var h = new Handle { Vol = vol, Original = cur, Pid = pid };
|
|
128
|
-
vol.SetMasterVolume((float)(cur * factor), ref CTX);
|
|
129
|
-
handles.Add(h);
|
|
130
|
-
} catch { }
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return handles;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
public static void Restore(List<Handle> handles) {
|
|
137
|
-
if (handles == null) return;
|
|
138
|
-
foreach (var h in handles) { try { h.Vol.SetMasterVolume(h.Original, ref CTX); } catch { } }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Inspection helpers (for the isolation test): current live level / snapshotted original per handle.
|
|
142
|
-
public static float[] Levels(List<Handle> handles) {
|
|
143
|
-
var a = new float[handles.Count];
|
|
144
|
-
for (int i = 0; i < handles.Count; i++) { float c = 0; try { handles[i].Vol.GetMasterVolume(out c); } catch { } a[i] = c; }
|
|
145
|
-
return a;
|
|
146
|
-
}
|
|
147
|
-
public static float[] Originals(List<Handle> handles) {
|
|
148
|
-
var a = new float[handles.Count];
|
|
149
|
-
for (int i = 0; i < handles.Count; i++) a[i] = handles[i].Original;
|
|
150
|
-
return a;
|
|
151
|
-
}
|
|
152
|
-
public static uint[] Pids(List<Handle> handles) {
|
|
153
|
-
var a = new uint[handles.Count];
|
|
154
|
-
for (int i = 0; i < handles.Count; i++) a[i] = handles[i].Pid;
|
|
155
|
-
return a;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
'@
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function Invoke-Duck([double]$factor, [int[]]$exclude) {
|
|
163
|
-
if ($factor -ge 1.0 -or $factor -lt 0) { return $null }
|
|
164
|
-
try { return [CU.AudioDuck]::Duck($factor, $exclude) } catch { return $null }
|
|
165
|
-
}
|
|
166
|
-
function Restore-Duck($handles) {
|
|
167
|
-
if ($null -eq $handles) { return }
|
|
168
|
-
try { [CU.AudioDuck]::Restore($handles) } catch { }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
# -WavPath mode: duck others (excluding THIS process, which owns the playback), play, restore in finally.
|
|
172
|
-
if ($WavPath) {
|
|
173
|
-
$h = Invoke-Duck $Factor @($PID)
|
|
174
|
-
try {
|
|
175
|
-
$p = New-Object System.Media.SoundPlayer $WavPath
|
|
176
|
-
$p.PlaySync()
|
|
177
|
-
} finally {
|
|
178
|
-
Restore-Duck $h
|
|
179
|
-
}
|
|
180
|
-
}
|
|
1
|
+
# computer-use — audio ducking helper (pwsh; WASAPI Core Audio via inline C#, NO install).
|
|
2
|
+
#
|
|
3
|
+
# When the companion speaks, briefly lower OTHER apps' audio sessions (game / video / music) so the voice
|
|
4
|
+
# stands out, then restore EXACTLY on completion. Per-app, not master volume — our own voice is excluded.
|
|
5
|
+
#
|
|
6
|
+
# SAFETY: restore is the whole point. Volumes are snapshotted and always restored in a finally block, even if
|
|
7
|
+
# playback throws or the process is asked to stop — a failed restore would leave the user's other apps quiet.
|
|
8
|
+
#
|
|
9
|
+
# Two uses:
|
|
10
|
+
# 1. Dot-source and call [CU.AudioDuck]::Duck(factor, excludePids) -> handle ; [CU.AudioDuck]::Restore(handle).
|
|
11
|
+
# (speak.ps1 / Heami wraps System.Speech this way, excluding its own $PID.)
|
|
12
|
+
# 2. -WavPath <wav>: duck (excluding THIS process), play the WAV via SoundPlayer, restore in finally.
|
|
13
|
+
# (speak-supertonic.mjs spawns this to play its synthesized WAV.)
|
|
14
|
+
#
|
|
15
|
+
# factor is the multiplier applied to other sessions (0.45 = reduce to 45%). 1.0 disables ducking.
|
|
16
|
+
param(
|
|
17
|
+
[string]$WavPath = '',
|
|
18
|
+
[double]$Factor = 0.45,
|
|
19
|
+
[int[]]$ExcludePid = @()
|
|
20
|
+
)
|
|
21
|
+
$ErrorActionPreference = 'Stop'
|
|
22
|
+
try { [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) } catch {}
|
|
23
|
+
|
|
24
|
+
if (-not ('CU.AudioDuck' -as [type])) {
|
|
25
|
+
Add-Type -TypeDefinition @'
|
|
26
|
+
using System;
|
|
27
|
+
using System.Collections.Generic;
|
|
28
|
+
using System.Runtime.InteropServices;
|
|
29
|
+
|
|
30
|
+
namespace CU {
|
|
31
|
+
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
|
|
32
|
+
|
|
33
|
+
enum EDataFlow { eRender = 0, eCapture = 1, eAll = 2 }
|
|
34
|
+
enum ERole { eConsole = 0, eMultimedia = 1, eCommunications = 2 }
|
|
35
|
+
|
|
36
|
+
[ComImport, Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
37
|
+
interface IMMDeviceEnumerator {
|
|
38
|
+
[PreserveSig] int EnumAudioEndpoints(EDataFlow dataFlow, int dwStateMask, out IMMDeviceCollection ppDevices);
|
|
39
|
+
[PreserveSig] int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppEndpoint);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[ComImport, Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
43
|
+
interface IMMDeviceCollection {
|
|
44
|
+
[PreserveSig] int GetCount(out uint pcDevices);
|
|
45
|
+
[PreserveSig] int Item(uint nDevice, out IMMDevice ppDevice);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[ComImport, Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
49
|
+
interface IMMDevice {
|
|
50
|
+
[PreserveSig] int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams,
|
|
51
|
+
[MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[ComImport, Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
55
|
+
interface IAudioSessionManager2 {
|
|
56
|
+
[PreserveSig] int NotImpl1();
|
|
57
|
+
[PreserveSig] int NotImpl2();
|
|
58
|
+
[PreserveSig] int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[ComImport, Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
62
|
+
interface IAudioSessionEnumerator {
|
|
63
|
+
[PreserveSig] int GetCount(out int SessionCount);
|
|
64
|
+
[PreserveSig] int GetSession(int SessionCount, out IAudioSessionControl Session);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
[ComImport, Guid("F4B1A599-7266-4319-A8CA-E70ACB11E8CD"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
68
|
+
interface IAudioSessionControl { }
|
|
69
|
+
|
|
70
|
+
[ComImport, Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
71
|
+
interface IAudioSessionControl2 {
|
|
72
|
+
// 9 inherited IAudioSessionControl slots (unused) ...
|
|
73
|
+
[PreserveSig] int N1(); [PreserveSig] int N2(); [PreserveSig] int N3();
|
|
74
|
+
[PreserveSig] int N4(); [PreserveSig] int N5(); [PreserveSig] int N6();
|
|
75
|
+
[PreserveSig] int N7(); [PreserveSig] int N8(); [PreserveSig] int N9();
|
|
76
|
+
// IAudioSessionControl2 own slots:
|
|
77
|
+
[PreserveSig] int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string s);
|
|
78
|
+
[PreserveSig] int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string s);
|
|
79
|
+
[PreserveSig] int GetProcessId(out uint pid);
|
|
80
|
+
[PreserveSig] int IsSystemSoundsSession();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
[ComImport, Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
84
|
+
interface ISimpleAudioVolume {
|
|
85
|
+
[PreserveSig] int SetMasterVolume(float level, ref Guid ctx);
|
|
86
|
+
[PreserveSig] int GetMasterVolume(out float level);
|
|
87
|
+
[PreserveSig] int SetMute(bool mute, ref Guid ctx);
|
|
88
|
+
[PreserveSig] int GetMute(out bool mute);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public class Handle { internal ISimpleAudioVolume Vol; public float Original; public uint Pid; }
|
|
92
|
+
|
|
93
|
+
public static class AudioDuck {
|
|
94
|
+
static Guid IID_IAudioSessionManager2 = new Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F");
|
|
95
|
+
static Guid CTX = Guid.Empty;
|
|
96
|
+
|
|
97
|
+
static IAudioSessionEnumerator SessionsForDevice(IMMDevice dev) {
|
|
98
|
+
object o; if (dev.Activate(ref IID_IAudioSessionManager2, 23 /*CLSCTX_ALL*/, IntPtr.Zero, out o) != 0) return null;
|
|
99
|
+
var mgr = (IAudioSessionManager2)o;
|
|
100
|
+
IAudioSessionEnumerator en; if (mgr.GetSessionEnumerator(out en) != 0) return null;
|
|
101
|
+
return en;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Lower every session (except excludePids and system-sounds) to original*factor, across ALL active render
|
|
105
|
+
// devices (not just the default endpoint) so virtual-audio routing (e.g. VoiceMeeter) is still caught.
|
|
106
|
+
// Returns restore handles. DEVICE_STATE_ACTIVE = 0x1.
|
|
107
|
+
public static List<Handle> Duck(double factor, int[] excludePids) {
|
|
108
|
+
var handles = new List<Handle>();
|
|
109
|
+
var deo = (IMMDeviceEnumerator)(new MMDeviceEnumeratorComObject());
|
|
110
|
+
IMMDeviceCollection coll;
|
|
111
|
+
if (deo.EnumAudioEndpoints(EDataFlow.eRender, 0x1, out coll) != 0 || coll == null) return handles;
|
|
112
|
+
uint dcount; if (coll.GetCount(out dcount) != 0) return handles;
|
|
113
|
+
var excl = new HashSet<uint>(); foreach (var p in excludePids) excl.Add((uint)p);
|
|
114
|
+
for (uint di = 0; di < dcount; di++) {
|
|
115
|
+
IMMDevice dev; if (coll.Item(di, out dev) != 0 || dev == null) continue;
|
|
116
|
+
var en = SessionsForDevice(dev); if (en == null) continue;
|
|
117
|
+
int count; if (en.GetCount(out count) != 0) continue;
|
|
118
|
+
for (int i = 0; i < count; i++) {
|
|
119
|
+
IAudioSessionControl ctl; if (en.GetSession(i, out ctl) != 0 || ctl == null) continue;
|
|
120
|
+
try {
|
|
121
|
+
var c2 = (IAudioSessionControl2)ctl;
|
|
122
|
+
if (c2.IsSystemSoundsSession() == 0) continue; // S_OK(0) means IS system sounds -> skip it
|
|
123
|
+
uint pid; if (c2.GetProcessId(out pid) != 0) continue;
|
|
124
|
+
if (excl.Contains(pid)) continue;
|
|
125
|
+
var vol = (ISimpleAudioVolume)ctl;
|
|
126
|
+
float cur; if (vol.GetMasterVolume(out cur) != 0) continue;
|
|
127
|
+
var h = new Handle { Vol = vol, Original = cur, Pid = pid };
|
|
128
|
+
vol.SetMasterVolume((float)(cur * factor), ref CTX);
|
|
129
|
+
handles.Add(h);
|
|
130
|
+
} catch { }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return handles;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public static void Restore(List<Handle> handles) {
|
|
137
|
+
if (handles == null) return;
|
|
138
|
+
foreach (var h in handles) { try { h.Vol.SetMasterVolume(h.Original, ref CTX); } catch { } }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Inspection helpers (for the isolation test): current live level / snapshotted original per handle.
|
|
142
|
+
public static float[] Levels(List<Handle> handles) {
|
|
143
|
+
var a = new float[handles.Count];
|
|
144
|
+
for (int i = 0; i < handles.Count; i++) { float c = 0; try { handles[i].Vol.GetMasterVolume(out c); } catch { } a[i] = c; }
|
|
145
|
+
return a;
|
|
146
|
+
}
|
|
147
|
+
public static float[] Originals(List<Handle> handles) {
|
|
148
|
+
var a = new float[handles.Count];
|
|
149
|
+
for (int i = 0; i < handles.Count; i++) a[i] = handles[i].Original;
|
|
150
|
+
return a;
|
|
151
|
+
}
|
|
152
|
+
public static uint[] Pids(List<Handle> handles) {
|
|
153
|
+
var a = new uint[handles.Count];
|
|
154
|
+
for (int i = 0; i < handles.Count; i++) a[i] = handles[i].Pid;
|
|
155
|
+
return a;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
'@
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function Invoke-Duck([double]$factor, [int[]]$exclude) {
|
|
163
|
+
if ($factor -ge 1.0 -or $factor -lt 0) { return $null }
|
|
164
|
+
try { return [CU.AudioDuck]::Duck($factor, $exclude) } catch { return $null }
|
|
165
|
+
}
|
|
166
|
+
function Restore-Duck($handles) {
|
|
167
|
+
if ($null -eq $handles) { return }
|
|
168
|
+
try { [CU.AudioDuck]::Restore($handles) } catch { }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# -WavPath mode: duck others (excluding THIS process, which owns the playback), play, restore in finally.
|
|
172
|
+
if ($WavPath) {
|
|
173
|
+
$h = Invoke-Duck $Factor @($PID)
|
|
174
|
+
try {
|
|
175
|
+
$p = New-Object System.Media.SoundPlayer $WavPath
|
|
176
|
+
$p.PlaySync()
|
|
177
|
+
} finally {
|
|
178
|
+
Restore-Duck $h
|
|
179
|
+
}
|
|
180
|
+
}
|
package/scripts/classify.ps1
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# computer-use — classify_activity raw-signal adapter over lib.ps1::Get-AxClassifyActivity.
|
|
2
|
-
# Output = a single JSON blob of raw signals (foreground process/title, notification state, UIA count, fullscreen).
|
|
3
|
-
# The JS side (activity.mjs) derives the activity class from these. Read-only, no images.
|
|
4
|
-
param([int]$UiaCap = 60)
|
|
5
|
-
$ErrorActionPreference = 'Stop'
|
|
6
|
-
. (Join-Path $PSScriptRoot 'lib.ps1')
|
|
7
|
-
Initialize-AxEnv
|
|
8
|
-
Get-AxClassifyActivity -UiaCap $UiaCap | ConvertTo-Json -Depth 4
|
|
1
|
+
# computer-use — classify_activity raw-signal adapter over lib.ps1::Get-AxClassifyActivity.
|
|
2
|
+
# Output = a single JSON blob of raw signals (foreground process/title, notification state, UIA count, fullscreen).
|
|
3
|
+
# The JS side (activity.mjs) derives the activity class from these. Read-only, no images.
|
|
4
|
+
param([int]$UiaCap = 60)
|
|
5
|
+
$ErrorActionPreference = 'Stop'
|
|
6
|
+
. (Join-Path $PSScriptRoot 'lib.ps1')
|
|
7
|
+
Initialize-AxEnv
|
|
8
|
+
Get-AxClassifyActivity -UiaCap $UiaCap | ConvertTo-Json -Depth 4
|
|
@@ -1,65 +1,82 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// computer-use — Supertonic 3 model downloader (one-time, ~380 MB) for the optional neural TTS engine.
|
|
3
|
-
//
|
|
4
|
-
// Downloads Supertone/supertonic-3 (OpenRAIL-M weights — commercial use permitted) into the model cache the
|
|
5
|
-
// speak path expects. Idempotent: existing non-empty files are skipped, so re-running resumes a partial fetch.
|
|
6
|
-
// Usage: node fetch-supertonic.mjs [targetDir] (default: VORTEX_CU_TTS_MODEL_DIR or ~/.vortex/computer-use/supertonic-3)
|
|
7
|
-
//
|
|
8
|
-
// The engine code (speak-supertonic.mjs) is adapted from Supertone's MIT Node example; only the weights are
|
|
9
|
-
// downloaded here, never bundled, keeping the npm package small and the license boundary clean.
|
|
10
|
-
|
|
11
|
-
import { createWriteStream, existsSync, statSync, mkdirSync, renameSync, unlinkSync } from 'node:fs';
|
|
12
|
-
import { Readable } from 'node:stream';
|
|
13
|
-
import { pipeline } from 'node:stream/promises';
|
|
14
|
-
import { join, dirname } from 'node:path';
|
|
15
|
-
import { homedir } from 'node:os';
|
|
16
|
-
|
|
17
|
-
const HF = 'https://huggingface.co/Supertone/supertonic-3/resolve/main';
|
|
18
|
-
const FILES = [
|
|
19
|
-
'onnx/duration_predictor.onnx',
|
|
20
|
-
'onnx/text_encoder.onnx',
|
|
21
|
-
'onnx/vector_estimator.onnx',
|
|
22
|
-
'onnx/vocoder.onnx',
|
|
23
|
-
'onnx/tts.json',
|
|
24
|
-
'onnx/unicode_indexer.json',
|
|
25
|
-
'config.json',
|
|
26
|
-
...['F1', 'F2', 'F3', 'F4', 'F5', 'M1', 'M2', 'M3', 'M4', 'M5'].map((v) => `voice_styles/${v}.json`),
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const targetDir = process.argv[2] || process.env.VORTEX_CU_TTS_MODEL_DIR || join(homedir(), '.vortex', 'computer-use', 'supertonic-3');
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// computer-use — Supertonic 3 model downloader (one-time, ~380 MB) for the optional neural TTS engine.
|
|
3
|
+
//
|
|
4
|
+
// Downloads Supertone/supertonic-3 (OpenRAIL-M weights — commercial use permitted) into the model cache the
|
|
5
|
+
// speak path expects. Idempotent: existing non-empty files are skipped, so re-running resumes a partial fetch.
|
|
6
|
+
// Usage: node fetch-supertonic.mjs [targetDir] (default: VORTEX_CU_TTS_MODEL_DIR or ~/.vortex/computer-use/supertonic-3)
|
|
7
|
+
//
|
|
8
|
+
// The engine code (speak-supertonic.mjs) is adapted from Supertone's MIT Node example; only the weights are
|
|
9
|
+
// downloaded here, never bundled, keeping the npm package small and the license boundary clean.
|
|
10
|
+
|
|
11
|
+
import { createWriteStream, existsSync, statSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { Readable } from 'node:stream';
|
|
13
|
+
import { pipeline } from 'node:stream/promises';
|
|
14
|
+
import { join, dirname } from 'node:path';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
|
|
17
|
+
const HF = 'https://huggingface.co/Supertone/supertonic-3/resolve/main';
|
|
18
|
+
const FILES = [
|
|
19
|
+
'onnx/duration_predictor.onnx',
|
|
20
|
+
'onnx/text_encoder.onnx',
|
|
21
|
+
'onnx/vector_estimator.onnx',
|
|
22
|
+
'onnx/vocoder.onnx',
|
|
23
|
+
'onnx/tts.json',
|
|
24
|
+
'onnx/unicode_indexer.json',
|
|
25
|
+
'config.json',
|
|
26
|
+
...['F1', 'F2', 'F3', 'F4', 'F5', 'M1', 'M2', 'M3', 'M4', 'M5'].map((v) => `voice_styles/${v}.json`),
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const targetDir = process.argv[2] || process.env.VORTEX_CU_TTS_MODEL_DIR || join(homedir(), '.vortex', 'computer-use', 'supertonic-3');
|
|
30
|
+
// Per-process temp suffix so two concurrent fetchers (e.g. two instances on one
|
|
31
|
+
// machine racing the same file) never write the SAME `.part` stream — each
|
|
32
|
+
// renames its own complete download into place, so a present `dest` is always a
|
|
33
|
+
// whole file, never a half-written one.
|
|
34
|
+
const PART_SUFFIX = `.${process.pid}.part`;
|
|
35
|
+
// Clear the session-start auto-download lock (base writes it before spawning us)
|
|
36
|
+
// on exit — success OR failure — so a fast/transient failure never wedges the
|
|
37
|
+
// retry window for hours. Harmless when run manually (no such lock, or already
|
|
38
|
+
// gone). `exit` fires on normal completion and on process.exit(); the unlink is
|
|
39
|
+
// synchronous as required there.
|
|
40
|
+
const DOWNLOAD_LOCK = join(homedir(), '.vortex', 'computer-use', '.supertonic-download.lock');
|
|
41
|
+
process.on('exit', () => { try { unlinkSync(DOWNLOAD_LOCK); } catch {} });
|
|
42
|
+
|
|
43
|
+
async function fetchToFile(url, dest) {
|
|
44
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
45
|
+
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
|
|
46
|
+
const tmp = dest + PART_SUFFIX;
|
|
47
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
48
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(tmp));
|
|
49
|
+
renameSync(tmp, dest);
|
|
50
|
+
return statSync(dest).size;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function main() {
|
|
54
|
+
console.log(`Supertonic 3 model cache: ${targetDir}`);
|
|
55
|
+
let downloaded = 0, skipped = 0, bytes = 0;
|
|
56
|
+
for (const rel of FILES) {
|
|
57
|
+
const dest = join(targetDir, rel);
|
|
58
|
+
if (existsSync(dest) && statSync(dest).size > 0) { skipped++; continue; }
|
|
59
|
+
process.stdout.write(` ↓ ${rel} ... `);
|
|
60
|
+
try {
|
|
61
|
+
const t = Date.now();
|
|
62
|
+
const sz = await fetchToFile(`${HF}/${rel}`, dest);
|
|
63
|
+
bytes += sz;
|
|
64
|
+
downloaded++;
|
|
65
|
+
console.log(`${(sz / 1048576).toFixed(1)} MB (${((Date.now() - t) / 1000).toFixed(1)}s)`);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.log(`FAILED: ${e.message}`);
|
|
68
|
+
try { unlinkSync(dest + PART_SUFFIX); } catch {}
|
|
69
|
+
console.error(`\nDownload failed for ${rel}. Re-run to resume.`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Completion marker — written only after every file has landed, so a consumer
|
|
74
|
+
// (e.g. base session-start's auto-download gate) can check ONE path to know the
|
|
75
|
+
// model is fully present instead of stat'ing the whole file list. A partial or
|
|
76
|
+
// interrupted run exits before this, leaving no marker, so the next run resumes.
|
|
77
|
+
try { writeFileSync(join(targetDir, '.ready'), new Date().toISOString() + '\n'); } catch {}
|
|
78
|
+
console.log(`\nDone — ${downloaded} downloaded (${(bytes / 1048576).toFixed(0)} MB), ${skipped} already present.`);
|
|
79
|
+
console.log('Neural TTS is ready. Set VORTEX_CU_TTS_ENGINE=auto (default) to use it.');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
main().catch((e) => { console.error(e); process.exit(1); });
|