mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-05 18:09:23 +02:00
Compare commits
18 Commits
integrate/
...
dev/mjolle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3924e04578 | ||
|
|
34bdb12cc5 | ||
|
|
734a50ee97 | ||
|
|
c953f84fa3 | ||
|
|
a8d699b145 | ||
|
|
d2cee3a497 | ||
|
|
757f60d719 | ||
|
|
438507cdd4 | ||
|
|
89cf4e3115 | ||
|
|
d7d1e543ae | ||
|
|
272b725ff0 | ||
|
|
7884f4217a | ||
|
|
92014c81b9 | ||
|
|
59bc58afe2 | ||
|
|
e22bbd05a5 | ||
|
|
6b0779a7c5 | ||
|
|
d57096af20 | ||
|
|
f136a4fe04 |
3
.github/actions/spell-check/allow/code.txt
vendored
3
.github/actions/spell-check/allow/code.txt
vendored
@@ -432,3 +432,6 @@ SHELLEXPERIENCEHOST
|
||||
SHELLHOST
|
||||
STARTMENUEXPERIENCEHOST
|
||||
WIDGETBOARD
|
||||
|
||||
# URIs
|
||||
actioncenter
|
||||
|
||||
212
.github/actions/spell-check/allow/zoomit.txt
vendored
212
.github/actions/spell-check/allow/zoomit.txt
vendored
@@ -1,23 +1,51 @@
|
||||
accelscroll
|
||||
acq
|
||||
ADDTO
|
||||
ADDTOOL
|
||||
adr
|
||||
Adr
|
||||
ALWAYSTIP
|
||||
APPLYTOSUBMENUS
|
||||
ARCHMASK
|
||||
archs
|
||||
AUDCLNT
|
||||
autocorr
|
||||
avx
|
||||
axisdefer
|
||||
axisflip
|
||||
axisstart
|
||||
backlight
|
||||
BEOS
|
||||
bfi
|
||||
BFIN
|
||||
bfly
|
||||
BGRX
|
||||
bitmaps
|
||||
bitrev
|
||||
blits
|
||||
Borgerding
|
||||
Borland
|
||||
breakc
|
||||
BREAKSCR
|
||||
BUFFERFLAGS
|
||||
bugzilla
|
||||
Cands
|
||||
capturepath
|
||||
cbs
|
||||
centiseconds
|
||||
cexp
|
||||
cfx
|
||||
cfy
|
||||
cgem
|
||||
cifx
|
||||
cify
|
||||
CLASSW
|
||||
coeffs
|
||||
colblocks
|
||||
constantbuffer
|
||||
coprime
|
||||
cpuid
|
||||
cpx
|
||||
CREATEDIBSECTION
|
||||
CREATESTRUCTW
|
||||
crossfades
|
||||
@@ -28,109 +56,216 @@ CTLCOLORDLG
|
||||
CTLCOLOREDIT
|
||||
CTLCOLORLISTBOX
|
||||
CTrim
|
||||
CVTEPI
|
||||
DBuffer
|
||||
dcl
|
||||
dct
|
||||
ddx
|
||||
ddy
|
||||
Deinterleave
|
||||
denoise
|
||||
denoised
|
||||
DEVSOURCE
|
||||
DFCS
|
||||
DIVSCALAR
|
||||
DJGPP
|
||||
dlg
|
||||
dlu
|
||||
dnn
|
||||
DONTCARE
|
||||
downsample
|
||||
DRAWITEM
|
||||
DRAWITEMSTRUCT
|
||||
droppedband
|
||||
Droppedband
|
||||
DSPs
|
||||
dsum
|
||||
dupburst
|
||||
dupsegments
|
||||
DWLP
|
||||
eband
|
||||
ebx
|
||||
ECX
|
||||
EDITCONTROL
|
||||
EDSP
|
||||
emmintrin
|
||||
EMX
|
||||
ENABLEHOOK
|
||||
endloop
|
||||
ENDOFSTREAM
|
||||
ener
|
||||
enh
|
||||
ettings
|
||||
expectedlock
|
||||
expf
|
||||
fabs
|
||||
fabsf
|
||||
facbuf
|
||||
fastscroll
|
||||
FDE
|
||||
ffast
|
||||
FIXDIV
|
||||
floorf
|
||||
fmadd
|
||||
fout
|
||||
fstride
|
||||
fxc
|
||||
GETCHANNELRECT
|
||||
GETCHECK
|
||||
GETCOUNT
|
||||
GETDISPINFO
|
||||
GETSCREENSAVEACTIVE
|
||||
GETSCREENSAVETIMEOUT
|
||||
GETTHUMBRECT
|
||||
GIFs
|
||||
glu
|
||||
groupshared
|
||||
gru
|
||||
hcfdark
|
||||
hcfwhitespace
|
||||
hlsl
|
||||
Hsieh
|
||||
hstride
|
||||
HTBOTTOMRIGHT
|
||||
HTHEME
|
||||
htol
|
||||
ICONINFORMATION
|
||||
ICONWARNING
|
||||
idct
|
||||
IDIn
|
||||
IDISHWND
|
||||
ifft
|
||||
igc
|
||||
ilog
|
||||
imad
|
||||
imax
|
||||
imin
|
||||
immintrin
|
||||
Inj
|
||||
interp
|
||||
inttypes
|
||||
ishl
|
||||
itof
|
||||
jumprecover
|
||||
kfft
|
||||
kheight
|
||||
kissfft
|
||||
KSDATAFORMAT
|
||||
ksize
|
||||
ktime
|
||||
lastg
|
||||
latestcapture
|
||||
ldx
|
||||
LEFTNOWORDWRAP
|
||||
legitjumps
|
||||
lenmem
|
||||
letterbox
|
||||
lld
|
||||
lldx
|
||||
llu
|
||||
llums
|
||||
logfont
|
||||
lookback
|
||||
lpc
|
||||
lpcnet
|
||||
LPNMHDR
|
||||
LPNMTTDISPINFO
|
||||
lround
|
||||
lte
|
||||
luma
|
||||
Luma
|
||||
maj
|
||||
manualdrop
|
||||
maskcache
|
||||
maxabs
|
||||
maxcorr
|
||||
MAXFACTORS
|
||||
maxperiod
|
||||
maxstep
|
||||
memalign
|
||||
memid
|
||||
memneeded
|
||||
MENUINFO
|
||||
MFSTARTUP
|
||||
mfxhw
|
||||
mic
|
||||
middledrop
|
||||
minperiod
|
||||
MIPSr
|
||||
MJPEG
|
||||
MMRESULT
|
||||
momentumreversal
|
||||
movc
|
||||
mrate
|
||||
mrt
|
||||
MULBYSCALAR
|
||||
MULC
|
||||
MWERKS
|
||||
mycfg
|
||||
narrowstrip
|
||||
nbak
|
||||
nbytes
|
||||
ncapture
|
||||
nchw
|
||||
ncm
|
||||
nduplicates
|
||||
nfft
|
||||
NHWC
|
||||
niterations
|
||||
nmonitor
|
||||
nnet
|
||||
NONCLIENTMETRICS
|
||||
NONOTIFY
|
||||
nonvle
|
||||
normf
|
||||
nredraw
|
||||
nstop
|
||||
nsubpixel
|
||||
ntorn
|
||||
numthreads
|
||||
nvw
|
||||
Octasic
|
||||
osc
|
||||
OSCE
|
||||
ovflw
|
||||
OWNERDRAW
|
||||
PBGRA
|
||||
periodictrap
|
||||
pillarbox
|
||||
pfdc
|
||||
pillarbox
|
||||
playhead
|
||||
pnmh
|
||||
pointerreuse
|
||||
PPW
|
||||
prereq
|
||||
PSHR
|
||||
pstdint
|
||||
PSWA
|
||||
pwfx
|
||||
QCONST
|
||||
qpc
|
||||
Qpc
|
||||
quantums
|
||||
qweight
|
||||
RCSEGMODEL
|
||||
RCZOOMITSCR
|
||||
readback
|
||||
READERF
|
||||
realcapture
|
||||
REFKNOWNFOLDERID
|
||||
relu
|
||||
reposted
|
||||
RETURNCMD
|
||||
rnn
|
||||
rnnoise
|
||||
rotateleft
|
||||
rsqrt
|
||||
rtcd
|
||||
RTEXT
|
||||
RTH
|
||||
rtvs
|
||||
SCALEIN
|
||||
SCALEOUT
|
||||
SCREENSAVE
|
||||
SCRNSAVE
|
||||
SCRNSAVECONFIGURE
|
||||
@@ -138,43 +273,80 @@ scrnsavw
|
||||
Scrnsavw
|
||||
scrollramp
|
||||
SCROLLSIZEGRIP
|
||||
selfie
|
||||
selftest
|
||||
SETBARCOLOR
|
||||
SETBKCOLOR
|
||||
SETDEFID
|
||||
SETRECT
|
||||
SETSCREENSAVETIMEOUT
|
||||
SETTIPSIDE
|
||||
sgem
|
||||
sgemv
|
||||
sgv
|
||||
SHAREMODE
|
||||
SHAREVIOLATION
|
||||
shortlist
|
||||
simde
|
||||
siv
|
||||
slowthenfast
|
||||
smallstart
|
||||
SNIPOCR
|
||||
softmax
|
||||
sqrtf
|
||||
SROUND
|
||||
srvs
|
||||
ssi
|
||||
startuprecovery
|
||||
stdint
|
||||
stf
|
||||
stopafter
|
||||
STREAMFLAGS
|
||||
SUBFROM
|
||||
subias
|
||||
submix
|
||||
sxx
|
||||
sxy
|
||||
symbian
|
||||
synthesising
|
||||
syy
|
||||
tallportal
|
||||
TBTS
|
||||
tci
|
||||
tcsicmp
|
||||
TEXTCALLBACK
|
||||
TEXTMETRIC
|
||||
tgsm
|
||||
THIRDPARTY
|
||||
tinystep
|
||||
tme
|
||||
toolbars
|
||||
TOOLINFO
|
||||
TRACKMOUSEEVENT
|
||||
TRIANGLELIST
|
||||
TTM
|
||||
TTN
|
||||
TWID
|
||||
UADD
|
||||
uav
|
||||
uavs
|
||||
uge
|
||||
Unadvise
|
||||
upscaled
|
||||
upscales
|
||||
USUB
|
||||
utof
|
||||
vad
|
||||
vaddq
|
||||
vaddvq
|
||||
valgrind
|
||||
Valin
|
||||
vandq
|
||||
vblank
|
||||
vcgeq
|
||||
vdup
|
||||
vectorizer
|
||||
VERTID
|
||||
VIDCAP
|
||||
vld
|
||||
vle
|
||||
@@ -184,6 +356,7 @@ vminq
|
||||
vmlal
|
||||
vmull
|
||||
vqaddq
|
||||
VSHR
|
||||
vshrn
|
||||
vsntprintf
|
||||
vsnwprintf
|
||||
@@ -194,7 +367,9 @@ WAVEFORMATEXTENSIBLE
|
||||
webcam
|
||||
Webcam
|
||||
webcams
|
||||
Wextra
|
||||
wfopen
|
||||
WGC
|
||||
wideportal
|
||||
wil
|
||||
WMU
|
||||
@@ -202,11 +377,46 @@ wrapjump
|
||||
wtol
|
||||
WTSSESSION
|
||||
WTSUn
|
||||
wxyz
|
||||
xchg
|
||||
xcorr
|
||||
XEnd
|
||||
Xfl
|
||||
Xiang
|
||||
Xiph
|
||||
xmmintrin
|
||||
xptr
|
||||
xshift
|
||||
XStart
|
||||
XStep
|
||||
xxxy
|
||||
xxyx
|
||||
xxyz
|
||||
xyw
|
||||
xywx
|
||||
xyxx
|
||||
xyxz
|
||||
xyzw
|
||||
xyzx
|
||||
xzwx
|
||||
xzxx
|
||||
Yfl
|
||||
YInternal
|
||||
yshift
|
||||
YUV
|
||||
yyyx
|
||||
yyzw
|
||||
yzw
|
||||
yzwy
|
||||
yzyy
|
||||
Zhou
|
||||
Zhu
|
||||
ZMBS
|
||||
zncc
|
||||
Zncc
|
||||
ZNCC
|
||||
zrh
|
||||
zwzz
|
||||
zyzw
|
||||
zzwz
|
||||
zzzw
|
||||
|
||||
7
.github/actions/spell-check/expect.txt
vendored
7
.github/actions/spell-check/expect.txt
vendored
@@ -113,7 +113,6 @@ azman
|
||||
azureaiinference
|
||||
azureinference
|
||||
azureopenai
|
||||
Backlight
|
||||
backticks
|
||||
Badflags
|
||||
Badmode
|
||||
@@ -472,7 +471,6 @@ DWMWINDOWATTRIBUTE
|
||||
DWMWINDOWMAXIMIZEDCHANGE
|
||||
DWORDLONG
|
||||
dworigin
|
||||
DWRITE
|
||||
dxgi
|
||||
Dxva
|
||||
eab
|
||||
@@ -998,7 +996,6 @@ luid
|
||||
lusrmgr
|
||||
LVDS
|
||||
LWA
|
||||
LWIN
|
||||
LZero
|
||||
MAGTRANSFORM
|
||||
makeappx
|
||||
@@ -1208,7 +1205,6 @@ nonclient
|
||||
NONCLIENTMETRICSW
|
||||
NONELEVATED
|
||||
nonspace
|
||||
nonstd
|
||||
NOOWNERZORDER
|
||||
NOPARENTNOTIFY
|
||||
NOPREFIX
|
||||
@@ -1422,7 +1418,6 @@ Prefixer
|
||||
Premul
|
||||
prependpath
|
||||
prepopulate
|
||||
Prereq
|
||||
prevhost
|
||||
previewer
|
||||
PREVIEWHANDLERFRAMEINFO
|
||||
@@ -2000,7 +1995,6 @@ valuegenerator
|
||||
VARTYPE
|
||||
vbcscompiler
|
||||
vcamp
|
||||
VCENTER
|
||||
vcgtq
|
||||
VCINSTALLDIR
|
||||
vcp
|
||||
@@ -2038,7 +2032,6 @@ vorrq
|
||||
VOS
|
||||
vpaddlq
|
||||
vqsubq
|
||||
VREDRAW
|
||||
vreinterpretq
|
||||
VSC
|
||||
VSCBD
|
||||
|
||||
6
.github/actions/spell-check/patterns.txt
vendored
6
.github/actions/spell-check/patterns.txt
vendored
@@ -312,3 +312,9 @@ ms-windows-store://\S+
|
||||
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
|
||||
|
||||
# Special licenses text from RNNoise (BSD-style disclaimer: ``AS IS'')
|
||||
``AS IS''
|
||||
|
||||
# Old school moniker for macOS from RNNoise
|
||||
MacOS
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -19,6 +19,9 @@
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
!**/rnnoise/
|
||||
!**/rnnoise/x86/
|
||||
!**/rnnoise/x86/**
|
||||
ARM64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 134 KiB |
@@ -73,7 +73,8 @@
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<!-- TODO: _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS: suppress VS 2026 STL hard error for <experimental/coroutine> until the code is ported to <coroutine> -->
|
||||
<PreprocessorDefinitions>_WINRT_DLL;WINRT_LEAN_AND_MEAN;_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>../../..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
</ClCompile>
|
||||
|
||||
@@ -102,19 +102,18 @@ namespace ShortcutGuide.Helpers
|
||||
/// <summary>
|
||||
/// Retrieves all application IDs that should be displayed, based on the foreground window and background processes.
|
||||
/// </summary>
|
||||
/// <param name="foregroundWindowHandle">The window handle captured before Shortcut Guide UI takes focus.</param>
|
||||
/// <returns>
|
||||
/// A dictionary mapping each application ID to the full path of the executable
|
||||
/// that caused the match (used for icon extraction), or <c>null</c> when no
|
||||
/// specific executable is associated (for example, wildcard filters like the
|
||||
/// default shell).
|
||||
/// </returns>
|
||||
public static Dictionary<string, string?> GetAllCurrentApplicationIds()
|
||||
public static Dictionary<string, string?> GetAllCurrentApplicationIds(nint foregroundWindowHandle)
|
||||
{
|
||||
nint handle = NativeMethods.GetForegroundWindow();
|
||||
|
||||
Dictionary<string, string?> applicationIds = new(StringComparer.Ordinal);
|
||||
|
||||
if (NativeMethods.GetWindowThreadProcessId(handle, out uint processId) > 0)
|
||||
if (NativeMethods.GetWindowThreadProcessId(foregroundWindowHandle, out uint processId) > 0)
|
||||
{
|
||||
string? name = null;
|
||||
string? executablePath = null;
|
||||
|
||||
@@ -21,9 +21,12 @@ namespace ShortcutGuide
|
||||
{
|
||||
public static Thread CopyAndIndexGenerationThread { get; private set; } = null!;
|
||||
|
||||
public static nint ForegroundWindowHandle { get; private set; } = nint.Zero;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
ForegroundWindowHandle = NativeMethods.GetForegroundWindow();
|
||||
Logger.InitializeLogger("\\ShortcutGuide\\Logs");
|
||||
|
||||
// The module interface passes: <powertoys_pid> [telemetry]
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace ShortcutGuide
|
||||
_getAppIdsTask = Task.Run(() =>
|
||||
{
|
||||
Program.CopyAndIndexGenerationThread.Join();
|
||||
_currentApplicationIds = ManifestInterpreter.GetAllCurrentApplicationIds();
|
||||
_currentApplicationIds = ManifestInterpreter.GetAllCurrentApplicationIds(Program.ForegroundWindowHandle);
|
||||
return _currentApplicationIds;
|
||||
});
|
||||
|
||||
|
||||
@@ -34,15 +34,17 @@ namespace winrt
|
||||
using namespace Windows::Devices::Enumeration;
|
||||
}
|
||||
|
||||
AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio, bool micMonoMix)
|
||||
AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio, bool mixMicrophoneMono, bool useNoiseCancellation)
|
||||
: m_captureMicrophone(captureMicrophone)
|
||||
, m_captureSystemAudio(captureSystemAudio)
|
||||
, m_micMonoMix(micMonoMix)
|
||||
, m_mixMicrophoneMono(mixMicrophoneMono)
|
||||
, m_useNoiseCancellation(useNoiseCancellation)
|
||||
{
|
||||
OutputDebugStringA(("AudioSampleGenerator created, captureMicrophone=" +
|
||||
std::string(captureMicrophone ? "true" : "false") +
|
||||
", captureSystemAudio=" + std::string(captureSystemAudio ? "true" : "false") +
|
||||
", micMonoMix=" + std::string(micMonoMix ? "true" : "false") + "\n").c_str());
|
||||
", mixMicrophoneMono=" + std::string(mixMicrophoneMono ? "true" : "false") +
|
||||
", useNoiseCancellation=" + std::string(useNoiseCancellation ? "true" : "false") + "\n").c_str());
|
||||
m_audioEvent.create(wil::EventOptions::ManualReset);
|
||||
m_endEvent.create(wil::EventOptions::ManualReset);
|
||||
m_startEvent.create(wil::EventOptions::ManualReset);
|
||||
@@ -158,8 +160,24 @@ winrt::IAsyncAction AudioSampleGenerator::InitializeAsync()
|
||||
throw winrt::hresult_error(E_FAIL, L"Failed to initialize loopback audio capture!");
|
||||
}
|
||||
|
||||
// Initialize noise suppressor for microphone audio if enabled
|
||||
if (m_useNoiseCancellation && m_captureMicrophone)
|
||||
{
|
||||
m_noiseSuppressor = std::make_unique<NoiseSuppressor>();
|
||||
OutputDebugStringA("Noise cancellation enabled for microphone\n");
|
||||
}
|
||||
|
||||
m_audioGraph.QuantumStarted({ this, &AudioSampleGenerator::OnAudioQuantumStarted });
|
||||
|
||||
// Start the AudioGraph now so the microphone device begins warming up
|
||||
// during the remaining recording initialization (transcoder setup, etc.).
|
||||
// OnAudioQuantumStarted returns early while m_started is false, so audio
|
||||
// samples are discarded until Start() is called. The side-effect of
|
||||
// starting the graph early is that the system mic-active icon appears
|
||||
// sooner, which also triggers a desktop-content change that helps
|
||||
// unblock the WGC frame pool wait in OnMediaStreamSourceStarting.
|
||||
m_audioGraph.Start();
|
||||
|
||||
m_asyncInitialized.SetEvent();
|
||||
}
|
||||
}
|
||||
@@ -205,67 +223,65 @@ std::optional<winrt::MediaStreamSample> AudioSampleGenerator::TryGetNextSample()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for audio samples to become available, retrying on spurious wakes
|
||||
// (e.g. when OnAudioQuantumStarted signals m_audioEvent but the quantum
|
||||
// produced an empty buffer so m_samples is still empty).
|
||||
for (;;)
|
||||
{
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (m_samples.empty() && m_endEvent.is_signaled())
|
||||
{
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (m_samples.empty() && m_endEvent.is_signaled())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (!m_samples.empty())
|
||||
{
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
m_audioEvent.ResetEvent();
|
||||
std::vector<HANDLE> events = { m_endEvent.get(), m_audioEvent.get() };
|
||||
auto waitResult = WaitForMultipleObjectsEx(static_cast<DWORD>(events.size()), events.data(), false, INFINITE, false);
|
||||
auto eventIndex = -1;
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
eventIndex = waitResult - WAIT_OBJECT_0;
|
||||
break;
|
||||
}
|
||||
WINRT_VERIFY(eventIndex >= 0);
|
||||
|
||||
auto signaledEvent = events[eventIndex];
|
||||
if (signaledEvent == m_endEvent.get())
|
||||
{
|
||||
// End was signaled, but check for any remaining samples before returning nullopt
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (!m_samples.empty())
|
||||
{
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (!m_samples.empty())
|
||||
{
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
m_audioEvent.ResetEvent();
|
||||
std::vector<HANDLE> events = { m_endEvent.get(), m_audioEvent.get() };
|
||||
auto waitResult = WaitForMultipleObjectsEx(static_cast<DWORD>(events.size()), events.data(), false, INFINITE, false);
|
||||
auto eventIndex = -1;
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
eventIndex = waitResult - WAIT_OBJECT_0;
|
||||
break;
|
||||
}
|
||||
WINRT_VERIFY(eventIndex >= 0);
|
||||
|
||||
auto signaledEvent = events[eventIndex];
|
||||
if (signaledEvent == m_endEvent.get())
|
||||
{
|
||||
// End was signaled, but check for any remaining samples before returning nullopt
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (!m_samples.empty())
|
||||
{
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (m_samples.empty())
|
||||
{
|
||||
// Spurious wake or race - no samples available
|
||||
// If end is signaled, return nullopt
|
||||
return m_endEvent.is_signaled() ? std::nullopt : std::optional<winrt::MediaStreamSample>{};
|
||||
}
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
// m_audioEvent was signaled — loop back to check m_samples again.
|
||||
// If the quantum produced an empty buffer, m_samples will still be
|
||||
// empty and we'll wait for the next quantum.
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSampleGenerator::Start()
|
||||
void AudioSampleGenerator::Start(int64_t videoStartTimestamp)
|
||||
{
|
||||
CheckInitialized();
|
||||
m_videoStartTimestamp = videoStartTimestamp;
|
||||
auto expected = false;
|
||||
if (m_started.compare_exchange_strong(expected, true))
|
||||
{
|
||||
OutputDebugStringW( L"[AudioGen] Start(): m_started set to true, setting m_startEvent\n" );
|
||||
m_endEvent.ResetEvent();
|
||||
m_startEvent.SetEvent();
|
||||
|
||||
@@ -284,7 +300,7 @@ void AudioSampleGenerator::Start()
|
||||
m_loopbackCapture->Start();
|
||||
}
|
||||
|
||||
m_audioGraph.Start();
|
||||
// AudioGraph was already started in InitializeAsync for mic warmup.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,12 +627,21 @@ void AudioSampleGenerator::CombineQueuedSamples()
|
||||
|
||||
void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender, winrt::IInspectable const& args)
|
||||
{
|
||||
// Don't process if we're not actively recording
|
||||
// Don't process if we're not actively recording, but DO drain the
|
||||
// output node so stale audio doesn't accumulate during mic warmup.
|
||||
// Without this, the first GetFrame() after m_started becomes true
|
||||
// would return several seconds of buffered audio, confusing the
|
||||
// transcoder's A/V interleaving.
|
||||
if (!m_started.load())
|
||||
{
|
||||
auto frame = m_audioOutputNode.GetFrame();
|
||||
(void)frame; // discard
|
||||
return;
|
||||
}
|
||||
|
||||
static int s_quantumCount = 0;
|
||||
s_quantumCount++;
|
||||
|
||||
{
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
|
||||
@@ -628,6 +653,14 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
|
||||
auto sampleBuffer = winrt::Buffer::CreateCopyFromMemoryBuffer(audioBuffer);
|
||||
sampleBuffer.Length(audioBuffer.Length());
|
||||
|
||||
if( s_quantumCount <= 5 )
|
||||
{
|
||||
wchar_t dbg[256];
|
||||
swprintf_s( dbg, L"[AudioGen] quantum #%d: audioBuffer.Length=%u sampleBuffer.Length=%u started=%d\n",
|
||||
s_quantumCount, audioBuffer.Length(), sampleBuffer.Length(), m_started.load() ? 1 : 0 );
|
||||
OutputDebugStringW( dbg );
|
||||
}
|
||||
|
||||
// Calculate expected samples per quantum (~10ms at graph sample rate)
|
||||
// AudioGraph uses 10ms quantums by default
|
||||
uint32_t expectedSamplesPerQuantum = (m_graphSampleRate / 100) * m_graphChannels;
|
||||
@@ -636,7 +669,7 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
|
||||
// Apply mono mixing to microphone audio if enabled
|
||||
// This converts stereo mic input (with same signal on both channels) to true mono
|
||||
// by averaging the channels and writing the result to both channels
|
||||
if (m_micMonoMix && m_captureMicrophone && numMicSamples > 0 && m_graphChannels >= 2)
|
||||
if (m_mixMicrophoneMono && m_captureMicrophone && numMicSamples > 0 && m_graphChannels >= 2)
|
||||
{
|
||||
float* micData = reinterpret_cast<float*>(sampleBuffer.data());
|
||||
uint32_t numFrames = numMicSamples / m_graphChannels;
|
||||
@@ -656,6 +689,12 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
|
||||
}
|
||||
}
|
||||
}
|
||||
// Apply noise suppression to microphone audio before mixing with loopback
|
||||
if (m_noiseSuppressor && m_captureMicrophone && numMicSamples > 0)
|
||||
{
|
||||
float* micData = reinterpret_cast<float*>(sampleBuffer.data());
|
||||
m_noiseSuppressor->Process(micData, numMicSamples, m_graphChannels);
|
||||
}
|
||||
|
||||
// Drain loopback samples regardless of whether we have mic audio
|
||||
if (m_loopbackCapture)
|
||||
@@ -733,13 +772,25 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
|
||||
|
||||
if (sampleBuffer.Length() > 0)
|
||||
{
|
||||
auto sample = winrt::MediaStreamSample::CreateFromBuffer(sampleBuffer, timestamp.value());
|
||||
// Rebase audio timestamps to the video's SystemRelativeTime domain.
|
||||
// AudioGraph RelativeTime starts near 0 (or a few hundred ms after
|
||||
// warmup draining), while video uses absolute SRT (~hours since boot).
|
||||
// Without rebasing, the transcoder sees audio far behind video and
|
||||
// starves video while trying to fill the gap with audio.
|
||||
if (!m_hasTimestampOffset && timestamp.has_value())
|
||||
{
|
||||
m_timestampOffset = m_videoStartTimestamp - timestamp.value().count();
|
||||
m_hasTimestampOffset = true;
|
||||
}
|
||||
auto adjustedTs = winrt::TimeSpan{ timestamp.value().count() + m_timestampOffset };
|
||||
|
||||
auto sample = winrt::MediaStreamSample::CreateFromBuffer(sampleBuffer, adjustedTs);
|
||||
m_samples.push_back(sample);
|
||||
|
||||
const uint32_t sampleCount = sampleBuffer.Length() / sizeof(float);
|
||||
const uint32_t frames = (m_graphChannels > 0) ? (sampleCount / m_graphChannels) : 0;
|
||||
const int64_t durationTicks = (m_graphSampleRate > 0) ? (static_cast<int64_t>(frames) * 10000000LL / m_graphSampleRate) : 0;
|
||||
m_lastSampleTimestamp = timestamp.value();
|
||||
m_lastSampleTimestamp = adjustedTs;
|
||||
m_lastSampleDuration = winrt::TimeSpan{ durationTicks };
|
||||
m_hasLastSampleTimestamp = true;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "LoopbackCapture.h"
|
||||
#include "NoiseSuppressor.h"
|
||||
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
|
||||
class AudioSampleGenerator
|
||||
{
|
||||
public:
|
||||
AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true, bool micMonoMix = false);
|
||||
AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true, bool mixMicrophoneMono = false, bool useNoiseCancellation = false);
|
||||
~AudioSampleGenerator();
|
||||
|
||||
winrt::Windows::Foundation::IAsyncAction InitializeAsync();
|
||||
winrt::Windows::Media::MediaProperties::AudioEncodingProperties GetEncodingProperties();
|
||||
|
||||
std::optional<winrt::Windows::Media::Core::MediaStreamSample> TryGetNextSample();
|
||||
void Start();
|
||||
void Start(int64_t videoStartTimestamp = 0);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
@@ -70,5 +75,14 @@ private:
|
||||
std::atomic<bool> m_started = false;
|
||||
bool m_captureMicrophone = true;
|
||||
bool m_captureSystemAudio = true;
|
||||
bool m_micMonoMix = false;
|
||||
};
|
||||
bool m_mixMicrophoneMono = false;
|
||||
bool m_useNoiseCancellation = false;
|
||||
std::unique_ptr<NoiseSuppressor> m_noiseSuppressor;
|
||||
|
||||
// Timestamp rebasing: audio RelativeTime → video SystemRelativeTime domain.
|
||||
// Without this, the transcoder sees audio timestamps (~350ms) far below video
|
||||
// timestamps (~111 billion ticks) and starves video while trying to fill the gap.
|
||||
int64_t m_videoStartTimestamp = 0;
|
||||
int64_t m_timestampOffset = 0;
|
||||
bool m_hasTimestampOffset = false;
|
||||
};
|
||||
|
||||
859
src/modules/ZoomIt/ZoomIt/BackgroundBlur.cpp
Normal file
859
src/modules/ZoomIt/ZoomIt/BackgroundBlur.cpp
Normal file
@@ -0,0 +1,859 @@
|
||||
//==============================================================================
|
||||
//
|
||||
// BackgroundBlur.cpp
|
||||
//
|
||||
// Windows ML-based person segmentation and background blur for the
|
||||
// webcam overlay. Uses the built-in Windows.AI.MachineLearning API
|
||||
// to load an ONNX segmentation model (e.g. MediaPipe SelfieSegmentation)
|
||||
// and produce a per-pixel person mask, then blurs or replaces the
|
||||
// background using an iterated box blur or a user-chosen image.
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
//==============================================================================
|
||||
#include "pch.h"
|
||||
#include "BackgroundBlur.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <wincodec.h>
|
||||
#include <wil/com.h>
|
||||
|
||||
namespace winml = winrt::Windows::AI::MachineLearning;
|
||||
namespace wf = winrt::Windows::Foundation::Collections;
|
||||
|
||||
// Defined in Zoomit.cpp; compiles to nothing in Release builds.
|
||||
void OutputDebug(const TCHAR* format, ...);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::Initialize
|
||||
//
|
||||
// Loads the ONNX segmentation model via Windows ML and inspects its
|
||||
// input/output tensor shapes to auto-configure preprocessing.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::Initialize( const wchar_t* modelPath )
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load the model from file.
|
||||
m_model = winml::LearningModel::LoadFromFilePath( modelPath );
|
||||
|
||||
// Try GPU (DirectML) first for faster inference; fall back to CPU
|
||||
// if no suitable GPU is available.
|
||||
try
|
||||
{
|
||||
winml::LearningModelDevice gpuDevice( winml::LearningModelDeviceKind::DirectXHighPerformance );
|
||||
m_session = winml::LearningModelSession( m_model, gpuDevice );
|
||||
m_usingGpu = true;
|
||||
OutputDebug( L"[BackgroundBlur] Using DirectML (GPU) for inference\n" );
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
winml::LearningModelDevice cpuDevice( winml::LearningModelDeviceKind::Cpu );
|
||||
m_session = winml::LearningModelSession( m_model, cpuDevice );
|
||||
m_usingGpu = false;
|
||||
OutputDebug( L"[BackgroundBlur] GPU unavailable, falling back to CPU\n" );
|
||||
}
|
||||
m_binding = winml::LearningModelBinding( m_session );
|
||||
|
||||
// Get input feature descriptor.
|
||||
auto inputFeatures = m_model.InputFeatures();
|
||||
if( inputFeatures.Size() == 0 )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Model has no input features\n" );
|
||||
return false;
|
||||
}
|
||||
auto inputDesc = inputFeatures.GetAt( 0 );
|
||||
m_inputName = inputDesc.Name();
|
||||
|
||||
// Inspect input tensor shape.
|
||||
auto tensorDesc = inputDesc.as<winml::ITensorFeatureDescriptor>();
|
||||
auto shape = tensorDesc.Shape();
|
||||
if( shape.Size() == 4 )
|
||||
{
|
||||
if( shape.GetAt( 1 ) == 3 || shape.GetAt( 1 ) == 1 )
|
||||
{
|
||||
// NCHW layout.
|
||||
m_inputIsNchw = true;
|
||||
m_modelInputChannels = shape.GetAt( 1 );
|
||||
m_modelInputHeight = shape.GetAt( 2 ) > 0 ? shape.GetAt( 2 ) : 256;
|
||||
m_modelInputWidth = shape.GetAt( 3 ) > 0 ? shape.GetAt( 3 ) : 256;
|
||||
}
|
||||
else
|
||||
{
|
||||
// NHWC layout.
|
||||
m_inputIsNchw = false;
|
||||
m_modelInputHeight = shape.GetAt( 1 ) > 0 ? shape.GetAt( 1 ) : 256;
|
||||
m_modelInputWidth = shape.GetAt( 2 ) > 0 ? shape.GetAt( 2 ) : 256;
|
||||
m_modelInputChannels = shape.GetAt( 3 );
|
||||
}
|
||||
}
|
||||
|
||||
// Get output feature name.
|
||||
auto outputFeatures = m_model.OutputFeatures();
|
||||
if( outputFeatures.Size() == 0 )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Model has no output features\n" );
|
||||
return false;
|
||||
}
|
||||
m_outputName = outputFeatures.GetAt( 0 ).Name();
|
||||
|
||||
OutputDebug( L"[BackgroundBlur] Model loaded: input=%s %lldx%lld (ch=%lld, %s)\n",
|
||||
m_inputName.c_str(), m_modelInputWidth, m_modelInputHeight,
|
||||
m_modelInputChannels, m_inputIsNchw ? L"NCHW" : L"NHWC" );
|
||||
|
||||
// Pre-allocate input tensor buffer.
|
||||
m_inputTensor.resize( static_cast<size_t>( m_modelInputChannels * m_modelInputHeight * m_modelInputWidth ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
catch( winrt::hresult_error const& ex )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Initialize failed: %s (0x%08X)\n", ex.message().c_str(), ex.code().value );
|
||||
m_session = nullptr;
|
||||
m_model = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::RunSegmentation
|
||||
//
|
||||
// Resizes the BGRA frame to the model's expected input size, converts
|
||||
// to float RGB, runs inference via Windows ML, and produces a float mask
|
||||
// in m_mask where 1.0 = person, 0.0 = background.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::RunSegmentation( const uint8_t* bgraPixels, uint32_t width, uint32_t height,
|
||||
bool modelResOnly )
|
||||
{
|
||||
const int64_t mW = m_modelInputWidth;
|
||||
const int64_t mH = m_modelInputHeight;
|
||||
const int64_t mC = m_modelInputChannels;
|
||||
|
||||
// Resize BGRA → model-sized float RGB using nearest-neighbor.
|
||||
for( int64_t y = 0; y < mH; y++ )
|
||||
{
|
||||
uint32_t srcY = static_cast<uint32_t>( y * height / mH );
|
||||
for( int64_t x = 0; x < mW; x++ )
|
||||
{
|
||||
uint32_t srcX = static_cast<uint32_t>( x * width / mW );
|
||||
const uint8_t* px = bgraPixels + ( static_cast<size_t>( srcY ) * width + srcX ) * 4;
|
||||
float b = px[0] / 255.0f;
|
||||
float g = px[1] / 255.0f;
|
||||
float r = px[2] / 255.0f;
|
||||
|
||||
if( m_inputIsNchw )
|
||||
{
|
||||
m_inputTensor[static_cast<size_t>(0ll * mH * mW + y * mW + x)] = r;
|
||||
if( mC > 1 ) m_inputTensor[static_cast<size_t>(1ll * mH * mW + y * mW + x)] = g;
|
||||
if( mC > 2 ) m_inputTensor[static_cast<size_t>(2ll * mH * mW + y * mW + x)] = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t idx = static_cast<size_t>( (y * mW + x) * mC );
|
||||
m_inputTensor[idx + 0] = r;
|
||||
if( mC > 1 ) m_inputTensor[idx + 1] = g;
|
||||
if( mC > 2 ) m_inputTensor[idx + 2] = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create the input tensor shape.
|
||||
std::vector<int64_t> inputShape;
|
||||
if( m_inputIsNchw )
|
||||
inputShape = { 1, mC, mH, mW };
|
||||
else
|
||||
inputShape = { 1, mH, mW, mC };
|
||||
|
||||
// Create a TensorFloat from our data.
|
||||
auto inputTensor = winml::TensorFloat::CreateFromArray(
|
||||
inputShape, winrt::array_view<const float>( m_inputTensor.data(),
|
||||
m_inputTensor.data() + m_inputTensor.size() ) );
|
||||
|
||||
// Bind input and evaluate.
|
||||
m_binding.Clear();
|
||||
m_binding.Bind( m_inputName, inputTensor );
|
||||
|
||||
auto result = m_session.Evaluate( m_binding, L"" );
|
||||
|
||||
// Extract output tensor — bulk-copy to a raw float array so we
|
||||
// avoid per-element WinRT/COM dispatch in the hot loop.
|
||||
auto outputTensor = result.Outputs().Lookup( m_outputName ).as<winml::TensorFloat>();
|
||||
auto outputShape = outputTensor.Shape();
|
||||
auto outputView = outputTensor.GetAsVectorView();
|
||||
const uint32_t outputSize = outputView.Size();
|
||||
m_outputBuf.resize( outputSize );
|
||||
outputView.GetMany( 0, m_outputBuf );
|
||||
const float* outData = m_outputBuf.data();
|
||||
|
||||
// Determine output mask dimensions.
|
||||
int64_t outH = mH, outW = mW;
|
||||
int64_t numClasses = 1;
|
||||
if( outputShape.Size() == 4 )
|
||||
{
|
||||
if( outputShape.GetAt( 1 ) <= 2 && outputShape.GetAt( 2 ) > 2 )
|
||||
{
|
||||
// [1, classes, H, W]
|
||||
numClasses = outputShape.GetAt( 1 );
|
||||
outH = outputShape.GetAt( 2 );
|
||||
outW = outputShape.GetAt( 3 );
|
||||
}
|
||||
else
|
||||
{
|
||||
// [1, H, W, classes]
|
||||
outH = outputShape.GetAt( 1 );
|
||||
outW = outputShape.GetAt( 2 );
|
||||
numClasses = outputShape.GetAt( 3 );
|
||||
}
|
||||
}
|
||||
else if( outputShape.Size() == 3 )
|
||||
{
|
||||
outH = outputShape.GetAt( 1 );
|
||||
outW = outputShape.GetAt( 2 );
|
||||
}
|
||||
|
||||
// Store actual output dimensions for GetModelMaskWidth/Height.
|
||||
m_modelOutputWidth = outW;
|
||||
m_modelOutputHeight = outH;
|
||||
|
||||
// Build model-resolution mask first, apply sigmoid sharpening
|
||||
// at model resolution (e.g. 256×256 = 65K pixels), then upscale
|
||||
// to frame resolution.
|
||||
const size_t modelPixels = static_cast<size_t>( outH * outW );
|
||||
m_erodeBuf.resize( modelPixels );
|
||||
|
||||
// Extract person scores at model resolution from the raw array.
|
||||
// Apply a hard threshold to produce a binary mask. This is much
|
||||
// faster than a sigmoid (no expf) and eliminates the partial-blur
|
||||
// halo that was bleeding onto body/head edges.
|
||||
for( int64_t y = 0; y < outH; y++ )
|
||||
{
|
||||
for( int64_t x = 0; x < outW; x++ )
|
||||
{
|
||||
float personScore;
|
||||
if( numClasses == 1 )
|
||||
{
|
||||
personScore = outData[y * outW + x];
|
||||
}
|
||||
else
|
||||
{
|
||||
float bg = outData[0 * outH * outW + y * outW + x];
|
||||
float fg = outData[1 * outH * outW + y * outW + x];
|
||||
personScore = ( fg > bg ) ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
m_erodeBuf[static_cast<size_t>( y * outW + x )] = ( personScore > 0.5f ) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// ── GPU path: model-resolution post-processing only ────────
|
||||
// When modelResOnly is true, apply feathering and temporal
|
||||
// smoothing at model resolution (e.g. 256×256 = 65K pixels)
|
||||
// and return early. The GPU's hardware bilinear sampler will
|
||||
// handle upscaling to frame resolution for free.
|
||||
if( modelResOnly )
|
||||
{
|
||||
// Small box blur on m_erodeBuf for edge feathering.
|
||||
// Radius 1 at 256×256 provides similar smoothing to
|
||||
// radius 3 at 960×540 after bilinear upscale.
|
||||
const int modelBlurRadius = 1;
|
||||
const int modelDiam = modelBlurRadius * 2 + 1;
|
||||
m_maskBlurBuf.resize( modelPixels );
|
||||
|
||||
// Horizontal pass.
|
||||
for( int64_t y = 0; y < outH; y++ )
|
||||
{
|
||||
const float* srcRow = m_erodeBuf.data() + y * outW;
|
||||
float* dstRow = m_maskBlurBuf.data() + y * outW;
|
||||
float sum = 0.0f;
|
||||
for( int i = -modelBlurRadius; i <= modelBlurRadius; i++ )
|
||||
sum += srcRow[(std::max)( static_cast<int64_t>(0), (std::min)( outW - 1, static_cast<int64_t>( i ) ) )];
|
||||
for( int64_t x = 0; x < outW; x++ )
|
||||
{
|
||||
dstRow[x] = sum / modelDiam;
|
||||
int64_t remX = (std::max)( static_cast<int64_t>(0), x - modelBlurRadius );
|
||||
int64_t addX = (std::min)( outW - 1, x + modelBlurRadius + 1 );
|
||||
sum += srcRow[addX] - srcRow[remX];
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical pass.
|
||||
for( int64_t x = 0; x < outW; x++ )
|
||||
{
|
||||
float sum = 0.0f;
|
||||
for( int i = -modelBlurRadius; i <= modelBlurRadius; i++ )
|
||||
{
|
||||
int64_t iy = (std::max)( static_cast<int64_t>(0), (std::min)( outH - 1, static_cast<int64_t>( i ) ) );
|
||||
sum += m_maskBlurBuf[static_cast<size_t>( iy * outW + x )];
|
||||
}
|
||||
for( int64_t y = 0; y < outH; y++ )
|
||||
{
|
||||
m_erodeBuf[static_cast<size_t>( y * outW + x )] = sum / modelDiam;
|
||||
int64_t remY = (std::max)( static_cast<int64_t>(0), y - modelBlurRadius );
|
||||
int64_t addY = (std::min)( outH - 1, y + modelBlurRadius + 1 );
|
||||
sum += m_maskBlurBuf[static_cast<size_t>( addY * outW + x )] -
|
||||
m_maskBlurBuf[static_cast<size_t>( remY * outW + x )];
|
||||
}
|
||||
}
|
||||
|
||||
// Temporal smoothing at model resolution.
|
||||
if( m_prevModelMask.size() == modelPixels )
|
||||
{
|
||||
constexpr float alpha = 0.6f;
|
||||
constexpr float beta = 0.4f;
|
||||
for( size_t i = 0; i < modelPixels; i++ )
|
||||
m_erodeBuf[i] = alpha * m_erodeBuf[i] + beta * m_prevModelMask[i];
|
||||
}
|
||||
m_prevModelMask = m_erodeBuf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Upscale processed mask to frame dimensions via bilinear interpolation
|
||||
// to produce smooth edges instead of staircase artifacts.
|
||||
const size_t maskPixels = static_cast<size_t>( width ) * height;
|
||||
m_mask.resize( maskPixels );
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
float srcYf = ( y + 0.5f ) * outH / static_cast<float>( height ) - 0.5f;
|
||||
srcYf = (std::max)( 0.0f, (std::min)( srcYf, static_cast<float>( outH - 1 ) ) );
|
||||
int64_t y0 = static_cast<int64_t>( srcYf );
|
||||
int64_t y1 = (std::min)( y0 + 1, outH - 1 );
|
||||
float fy = srcYf - y0;
|
||||
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
float srcXf = ( x + 0.5f ) * outW / static_cast<float>( width ) - 0.5f;
|
||||
srcXf = (std::max)( 0.0f, (std::min)( srcXf, static_cast<float>( outW - 1 ) ) );
|
||||
int64_t x0 = static_cast<int64_t>( srcXf );
|
||||
int64_t x1 = (std::min)( x0 + 1, outW - 1 );
|
||||
float fx = srcXf - x0;
|
||||
|
||||
float v00 = m_erodeBuf[static_cast<size_t>(y0 * outW + x0)];
|
||||
float v01 = m_erodeBuf[static_cast<size_t>(y0 * outW + x1)];
|
||||
float v10 = m_erodeBuf[static_cast<size_t>(y1 * outW + x0)];
|
||||
float v11 = m_erodeBuf[static_cast<size_t>(y1 * outW + x1)];
|
||||
|
||||
m_mask[static_cast<size_t>( y ) * width + x] =
|
||||
v00 * ( 1.0f - fx ) * ( 1.0f - fy ) +
|
||||
v01 * fx * ( 1.0f - fy ) +
|
||||
v10 * ( 1.0f - fx ) * fy +
|
||||
v11 * fx * fy;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply a small box blur to the upscaled mask to feather edges.
|
||||
const int maskBlurRadius = 3;
|
||||
const int maskDiam = maskBlurRadius * 2 + 1;
|
||||
m_maskBlurBuf.resize( maskPixels );
|
||||
|
||||
// Horizontal pass.
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
const float* srcRow = m_mask.data() + static_cast<size_t>( y ) * width;
|
||||
float* dstRow = m_maskBlurBuf.data() + static_cast<size_t>( y ) * width;
|
||||
float sum = 0.0f;
|
||||
|
||||
for( int i = -maskBlurRadius; i <= maskBlurRadius; i++ )
|
||||
sum += srcRow[(std::max)( 0, (std::min)( static_cast<int>( width ) - 1, i ) )];
|
||||
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
dstRow[x] = sum / maskDiam;
|
||||
int remX = (std::max)( 0, static_cast<int>( x ) - maskBlurRadius );
|
||||
int addX = (std::min)( static_cast<int>( width ) - 1, static_cast<int>( x ) + maskBlurRadius + 1 );
|
||||
sum += srcRow[addX] - srcRow[remX];
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical pass.
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
float sum = 0.0f;
|
||||
|
||||
for( int i = -maskBlurRadius; i <= maskBlurRadius; i++ )
|
||||
{
|
||||
int iy = (std::max)( 0, (std::min)( static_cast<int>( height ) - 1, i ) );
|
||||
sum += m_maskBlurBuf[static_cast<size_t>( iy ) * width + x];
|
||||
}
|
||||
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
m_mask[static_cast<size_t>( y ) * width + x] = sum / maskDiam;
|
||||
int remY = (std::max)( 0, static_cast<int>( y ) - maskBlurRadius );
|
||||
int addY = (std::min)( static_cast<int>( height ) - 1, static_cast<int>( y ) + maskBlurRadius + 1 );
|
||||
sum += m_maskBlurBuf[static_cast<size_t>( addY ) * width + x] -
|
||||
m_maskBlurBuf[static_cast<size_t>( remY ) * width + x];
|
||||
}
|
||||
}
|
||||
|
||||
// Temporal smoothing: blend the current mask with the previous
|
||||
// frame's mask to stabilize edges and reduce flicker. A weight
|
||||
// of 0.6 current + 0.4 previous keeps edges responsive while
|
||||
// dampening the frame-to-frame jitter around fine details like
|
||||
// ears, hair, and fingers.
|
||||
{
|
||||
const size_t maskPixelsInner = static_cast<size_t>( width ) * height;
|
||||
if( m_prevMask.size() == maskPixelsInner )
|
||||
{
|
||||
constexpr float alpha = 0.6f; // current frame weight
|
||||
constexpr float beta = 0.4f; // previous frame weight
|
||||
for( size_t i = 0; i < maskPixelsInner; i++ )
|
||||
{
|
||||
m_mask[i] = alpha * m_mask[i] + beta * m_prevMask[i];
|
||||
}
|
||||
}
|
||||
m_prevMask = m_mask;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch( winrt::hresult_error const& ex )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Evaluate failed: %s (0x%08X)\n", ex.message().c_str(), ex.code().value );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// HorizontalBoxBlur / VerticalBoxBlur
|
||||
//
|
||||
// Separable box blur passes used to build an approximate Gaussian.
|
||||
//----------------------------------------------------------------------------
|
||||
static void HorizontalBoxBlur(
|
||||
const uint8_t* src, uint8_t* dst, uint32_t width, uint32_t height, int radius )
|
||||
{
|
||||
const int diameter = radius * 2 + 1;
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
int rSum = 0, gSum = 0, bSum = 0;
|
||||
const uint8_t* row = src + static_cast<size_t>( y ) * width * 4;
|
||||
|
||||
// Initialize window with clamped left edge.
|
||||
for( int i = -radius; i <= radius; i++ )
|
||||
{
|
||||
int ix = (std::max)( 0, (std::min)( static_cast<int>( width ) - 1, i ) );
|
||||
const uint8_t* px = row + ix * 4;
|
||||
bSum += px[0];
|
||||
gSum += px[1];
|
||||
rSum += px[2];
|
||||
}
|
||||
|
||||
uint8_t* dstRow = dst + static_cast<size_t>( y ) * width * 4;
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
dstRow[x * 4 + 0] = static_cast<uint8_t>( bSum / diameter );
|
||||
dstRow[x * 4 + 1] = static_cast<uint8_t>( gSum / diameter );
|
||||
dstRow[x * 4 + 2] = static_cast<uint8_t>( rSum / diameter );
|
||||
dstRow[x * 4 + 3] = 0xFF;
|
||||
|
||||
// Slide window: add right, remove left.
|
||||
int removeX = (std::max)( 0, static_cast<int>( x ) - radius );
|
||||
int addX = (std::min)( static_cast<int>( width ) - 1, static_cast<int>( x ) + radius + 1 );
|
||||
const uint8_t* remPx = row + removeX * 4;
|
||||
const uint8_t* addPx = row + addX * 4;
|
||||
bSum += addPx[0] - remPx[0];
|
||||
gSum += addPx[1] - remPx[1];
|
||||
rSum += addPx[2] - remPx[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void VerticalBoxBlur(
|
||||
const uint8_t* src, uint8_t* dst, uint32_t width, uint32_t height, int radius )
|
||||
{
|
||||
const int diameter = radius * 2 + 1;
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
int rSum = 0, gSum = 0, bSum = 0;
|
||||
|
||||
// Initialize window with clamped top edge.
|
||||
for( int i = -radius; i <= radius; i++ )
|
||||
{
|
||||
int iy = (std::max)( 0, (std::min)( static_cast<int>( height ) - 1, i ) );
|
||||
const uint8_t* px = src + ( static_cast<size_t>( iy ) * width + x ) * 4;
|
||||
bSum += px[0];
|
||||
gSum += px[1];
|
||||
rSum += px[2];
|
||||
}
|
||||
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
uint8_t* dstPx = dst + ( static_cast<size_t>( y ) * width + x ) * 4;
|
||||
dstPx[0] = static_cast<uint8_t>( bSum / diameter );
|
||||
dstPx[1] = static_cast<uint8_t>( gSum / diameter );
|
||||
dstPx[2] = static_cast<uint8_t>( rSum / diameter );
|
||||
dstPx[3] = 0xFF;
|
||||
|
||||
int removeY = (std::max)( 0, static_cast<int>( y ) - radius );
|
||||
int addY = (std::min)( static_cast<int>( height ) - 1, static_cast<int>( y ) + radius + 1 );
|
||||
const uint8_t* remPx = src + ( static_cast<size_t>( removeY ) * width + x ) * 4;
|
||||
const uint8_t* addPx = src + ( static_cast<size_t>( addY ) * width + x ) * 4;
|
||||
bSum += addPx[0] - remPx[0];
|
||||
gSum += addPx[1] - remPx[1];
|
||||
rSum += addPx[2] - remPx[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::ApplyBlurWithMask
|
||||
//
|
||||
// Downscales the frame to a small working size, blurs there, then
|
||||
// performs a single full-resolution pass that blends the original
|
||||
// pixels with the upscaled blurred pixels according to the mask.
|
||||
//----------------------------------------------------------------------------
|
||||
void BackgroundBlur::ApplyBlurWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius )
|
||||
{
|
||||
const size_t frameBytes = static_cast<size_t>( width ) * height * 4;
|
||||
m_blurredFrame.resize( frameBytes );
|
||||
m_tempFrame.resize( frameBytes );
|
||||
|
||||
// The input is already capped at 960×540 by WebcamCapture, so blur
|
||||
// directly — no need for a secondary downscale.
|
||||
int effectiveRadius = (std::max)( 3, blurRadius );
|
||||
|
||||
// 2 iterations of box blur → approximate Gaussian.
|
||||
HorizontalBoxBlur( bgraPixels, m_blurredFrame.data(), width, height, effectiveRadius );
|
||||
VerticalBoxBlur( m_blurredFrame.data(), m_tempFrame.data(), width, height, effectiveRadius );
|
||||
HorizontalBoxBlur( m_tempFrame.data(), m_blurredFrame.data(), width, height, effectiveRadius );
|
||||
VerticalBoxBlur( m_blurredFrame.data(), m_tempFrame.data(), width, height, effectiveRadius );
|
||||
|
||||
// Blend pass with alpha support for smooth mask edges.
|
||||
const uint8_t* blurData = m_tempFrame.data();
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
uint8_t* dstRow = bgraPixels + static_cast<size_t>( y ) * width * 4;
|
||||
const uint8_t* blurRow = blurData + static_cast<size_t>( y ) * width * 4;
|
||||
const float* maskRow = m_mask.data() + static_cast<size_t>( y ) * width;
|
||||
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
float maskVal = maskRow[x];
|
||||
|
||||
// Fast path: fully person → keep original pixel untouched.
|
||||
if( maskVal >= 1.0f )
|
||||
continue;
|
||||
|
||||
uint8_t* dp = dstRow + x * 4;
|
||||
const uint8_t* bp = blurRow + x * 4;
|
||||
|
||||
// Fast path: fully background → copy blurred pixel.
|
||||
if( maskVal <= 0.0f )
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>( dp ) = *reinterpret_cast<const uint32_t*>( bp );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge pixel → alpha blend original and blurred.
|
||||
float inv = 1.0f - maskVal;
|
||||
dp[0] = static_cast<uint8_t>( dp[0] * maskVal + bp[0] * inv + 0.5f );
|
||||
dp[1] = static_cast<uint8_t>( dp[1] * maskVal + bp[1] * inv + 0.5f );
|
||||
dp[2] = static_cast<uint8_t>( dp[2] * maskVal + bp[2] * inv + 0.5f );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::SetBackgroundImage
|
||||
//
|
||||
// Loads an image file via WIC and stores it as a BGRA pixel buffer.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::SetBackgroundImage( const wchar_t* imagePath )
|
||||
{
|
||||
m_bgImage.clear();
|
||||
m_bgImageWidth = 0;
|
||||
m_bgImageHeight = 0;
|
||||
m_scaledBgImage.clear();
|
||||
m_scaledBgW = 0;
|
||||
m_scaledBgH = 0;
|
||||
|
||||
if( !imagePath || !*imagePath )
|
||||
return false;
|
||||
|
||||
auto factory = wil::CoCreateInstance<IWICImagingFactory>( CLSID_WICImagingFactory );
|
||||
if( !factory )
|
||||
return false;
|
||||
|
||||
wil::com_ptr<IWICBitmapDecoder> decoder;
|
||||
HRESULT hr = factory->CreateDecoderFromFilename(
|
||||
imagePath, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder );
|
||||
if( FAILED( hr ) )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Failed to decode image: %s (hr=0x%08X)\n", imagePath, hr );
|
||||
return false;
|
||||
}
|
||||
|
||||
wil::com_ptr<IWICBitmapFrameDecode> frame;
|
||||
hr = decoder->GetFrame( 0, &frame );
|
||||
if( FAILED( hr ) )
|
||||
return false;
|
||||
|
||||
// Convert to BGRA 32bpp.
|
||||
wil::com_ptr<IWICFormatConverter> converter;
|
||||
hr = factory->CreateFormatConverter( &converter );
|
||||
if( FAILED( hr ) )
|
||||
return false;
|
||||
|
||||
hr = converter->Initialize(
|
||||
frame.get(), GUID_WICPixelFormat32bppBGRA,
|
||||
WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom );
|
||||
if( FAILED( hr ) )
|
||||
return false;
|
||||
|
||||
UINT w = 0, h = 0;
|
||||
converter->GetSize( &w, &h );
|
||||
if( w == 0 || h == 0 )
|
||||
return false;
|
||||
|
||||
m_bgImage.resize( static_cast<size_t>( w ) * h * 4 );
|
||||
hr = converter->CopyPixels( nullptr, w * 4, static_cast<UINT>( m_bgImage.size() ), m_bgImage.data() );
|
||||
if( FAILED( hr ) )
|
||||
{
|
||||
m_bgImage.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bgImageWidth = w;
|
||||
m_bgImageHeight = h;
|
||||
|
||||
OutputDebug( L"[BackgroundBlur] Background image loaded: %ux%u from %s\n", w, h, imagePath );
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::EnsureScaledBgImage
|
||||
//
|
||||
// Scales the loaded background image to the specified dimensions using
|
||||
// nearest-neighbor. The result is cached and only recomputed when the
|
||||
// target dimensions change. The image is center-cropped to preserve
|
||||
// aspect ratio (like "cover" scaling).
|
||||
//----------------------------------------------------------------------------
|
||||
void BackgroundBlur::EnsureScaledBgImage( uint32_t width, uint32_t height )
|
||||
{
|
||||
if( m_scaledBgW == width && m_scaledBgH == height && !m_scaledBgImage.empty() )
|
||||
return;
|
||||
|
||||
m_scaledBgImage.resize( static_cast<size_t>( width ) * height * 4 );
|
||||
m_scaledBgW = width;
|
||||
m_scaledBgH = height;
|
||||
|
||||
// Compute center-crop of the source image to match the target aspect ratio.
|
||||
double targetAspect = static_cast<double>( width ) / height;
|
||||
double srcAspect = static_cast<double>( m_bgImageWidth ) / m_bgImageHeight;
|
||||
|
||||
uint32_t cropW, cropH, cropX, cropY;
|
||||
if( srcAspect > targetAspect )
|
||||
{
|
||||
// Source is wider — crop horizontally.
|
||||
cropH = m_bgImageHeight;
|
||||
cropW = static_cast<uint32_t>( m_bgImageHeight * targetAspect + 0.5 );
|
||||
cropX = ( m_bgImageWidth - cropW ) / 2;
|
||||
cropY = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Source is taller — crop vertically.
|
||||
cropW = m_bgImageWidth;
|
||||
cropH = static_cast<uint32_t>( m_bgImageWidth / targetAspect + 0.5 );
|
||||
cropX = 0;
|
||||
cropY = ( m_bgImageHeight - cropH ) / 2;
|
||||
}
|
||||
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
uint32_t srcY = cropY + y * cropH / height;
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
uint32_t srcX = cropX + x * cropW / width;
|
||||
size_t srcIdx = ( static_cast<size_t>( srcY ) * m_bgImageWidth + srcX ) * 4;
|
||||
size_t dstIdx = ( static_cast<size_t>( y ) * width + x ) * 4;
|
||||
m_scaledBgImage[dstIdx + 0] = m_bgImage[srcIdx + 0];
|
||||
m_scaledBgImage[dstIdx + 1] = m_bgImage[srcIdx + 1];
|
||||
m_scaledBgImage[dstIdx + 2] = m_bgImage[srcIdx + 2];
|
||||
m_scaledBgImage[dstIdx + 3] = 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::ApplyImageWithMask
|
||||
//
|
||||
// Replaces background pixels with the loaded background image using the
|
||||
// segmentation mask. Person pixels are preserved, background pixels come
|
||||
// from the scaled image.
|
||||
//----------------------------------------------------------------------------
|
||||
void BackgroundBlur::ApplyImageWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height )
|
||||
{
|
||||
EnsureScaledBgImage( width, height );
|
||||
|
||||
const uint8_t* bgData = m_scaledBgImage.data();
|
||||
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
uint8_t* dstRow = bgraPixels + static_cast<size_t>( y ) * width * 4;
|
||||
const uint8_t* bgRow = bgData + static_cast<size_t>( y ) * width * 4;
|
||||
const float* maskRow = m_mask.data() + static_cast<size_t>( y ) * width;
|
||||
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
float maskVal = maskRow[x];
|
||||
|
||||
// Fully person → keep original pixel.
|
||||
if( maskVal >= 1.0f )
|
||||
continue;
|
||||
|
||||
uint8_t* dp = dstRow + x * 4;
|
||||
const uint8_t* bp = bgRow + x * 4;
|
||||
|
||||
// Fully background → copy background image pixel.
|
||||
if( maskVal <= 0.0f )
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>( dp ) = *reinterpret_cast<const uint32_t*>( bp );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge pixel → alpha blend person and background image.
|
||||
float inv = 1.0f - maskVal;
|
||||
dp[0] = static_cast<uint8_t>( dp[0] * maskVal + bp[0] * inv + 0.5f );
|
||||
dp[1] = static_cast<uint8_t>( dp[1] * maskVal + bp[1] * inv + 0.5f );
|
||||
dp[2] = static_cast<uint8_t>( dp[2] * maskVal + bp[2] * inv + 0.5f );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::ShouldRunInference
|
||||
//
|
||||
// Decides whether segmentation inference should run this frame.
|
||||
// Uses a combination of periodic fallback and motion detection:
|
||||
// motion is estimated by comparing luminance at a sparse grid of
|
||||
// sample points with the previous frame. When the scene changes
|
||||
// quickly (fast head movement), inference runs every frame.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::ShouldRunInference( const uint8_t* bgraPixels, uint32_t width, uint32_t height )
|
||||
{
|
||||
// Always run if no cached mask or dimensions changed.
|
||||
if( !m_hasCachedMask || m_lastMaskWidth != width || m_lastMaskHeight != height )
|
||||
return true;
|
||||
|
||||
// Periodic fallback: run at least every N frames.
|
||||
const uint32_t pixels = width * height;
|
||||
const int inferenceInterval = ( pixels > 500000 ) ? 6 : 3;
|
||||
if( ( m_frameCounter % inferenceInterval ) == 0 )
|
||||
return true;
|
||||
|
||||
// Motion detection: sample luminance at a sparse grid and compare
|
||||
// with the previous frame.
|
||||
constexpr int gridSize = MOTION_GRID_SIZE;
|
||||
constexpr int numSamples = gridSize * gridSize;
|
||||
float curSamples[numSamples];
|
||||
|
||||
for( int gy = 0; gy < gridSize; gy++ )
|
||||
{
|
||||
uint32_t sy = ( gy * 2 + 1 ) * height / ( gridSize * 2 );
|
||||
for( int gx = 0; gx < gridSize; gx++ )
|
||||
{
|
||||
uint32_t sx = ( gx * 2 + 1 ) * width / ( gridSize * 2 );
|
||||
const uint8_t* px = bgraPixels + ( static_cast<size_t>( sy ) * width + sx ) * 4;
|
||||
curSamples[gy * gridSize + gx] = 0.299f * px[2] + 0.587f * px[1] + 0.114f * px[0];
|
||||
}
|
||||
}
|
||||
|
||||
float motionScore = 0.0f;
|
||||
if( m_hasPrevSamples )
|
||||
{
|
||||
for( int i = 0; i < numSamples; i++ )
|
||||
{
|
||||
float diff = curSamples[i] - m_prevSamples[i];
|
||||
motionScore += diff > 0.0f ? diff : -diff;
|
||||
}
|
||||
motionScore /= numSamples;
|
||||
}
|
||||
|
||||
memcpy( m_prevSamples, curSamples, sizeof( curSamples ) );
|
||||
m_hasPrevSamples = true;
|
||||
|
||||
// Average per-sample luminance change > 5/255 indicates significant motion.
|
||||
return motionScore > 5.0f;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::ApplyImageReplacement
|
||||
//
|
||||
// Main entry point for background image replacement mode.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::ApplyImageReplacement( uint8_t* bgraPixels, uint32_t width, uint32_t height )
|
||||
{
|
||||
if( !m_session || !bgraPixels || width == 0 || height == 0 )
|
||||
return false;
|
||||
|
||||
if( m_bgImage.empty() )
|
||||
return false;
|
||||
|
||||
if( ShouldRunInference( bgraPixels, width, height ) )
|
||||
{
|
||||
if( !RunSegmentation( bgraPixels, width, height ) )
|
||||
return false;
|
||||
m_lastMaskWidth = width;
|
||||
m_lastMaskHeight = height;
|
||||
m_hasCachedMask = true;
|
||||
}
|
||||
m_frameCounter++;
|
||||
|
||||
ApplyImageWithMask( bgraPixels, width, height );
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::Apply
|
||||
//
|
||||
// Main entry point: runs segmentation and applies blur to the background.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::Apply( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius )
|
||||
{
|
||||
if( !m_session || !bgraPixels || width == 0 || height == 0 )
|
||||
return false;
|
||||
|
||||
if( ShouldRunInference( bgraPixels, width, height ) )
|
||||
{
|
||||
if( !RunSegmentation( bgraPixels, width, height ) )
|
||||
return false;
|
||||
m_lastMaskWidth = width;
|
||||
m_lastMaskHeight = height;
|
||||
m_hasCachedMask = true;
|
||||
}
|
||||
m_frameCounter++;
|
||||
|
||||
ApplyBlurWithMask( bgraPixels, width, height, blurRadius );
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::RunSegmentationOnly
|
||||
//
|
||||
// Runs the segmentation model and produces the mask, but does NOT blur
|
||||
// or modify the pixel buffer. Used when the GPU compute shader will
|
||||
// perform the box blur instead of the CPU.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::RunSegmentationOnly( const uint8_t* bgraPixels, uint32_t width, uint32_t height )
|
||||
{
|
||||
if( !m_session || !bgraPixels || width == 0 || height == 0 )
|
||||
return false;
|
||||
|
||||
if( ShouldRunInference( bgraPixels, width, height ) )
|
||||
{
|
||||
// Model-resolution only: skip CPU upscale+feather at frame
|
||||
// resolution — the GPU bilinear sampler handles that.
|
||||
if( !RunSegmentation( bgraPixels, width, height, /*modelResOnly=*/ true ) )
|
||||
return false;
|
||||
m_lastMaskWidth = width;
|
||||
m_lastMaskHeight = height;
|
||||
m_hasCachedMask = true;
|
||||
}
|
||||
m_frameCounter++;
|
||||
|
||||
return m_hasCachedMask;
|
||||
}
|
||||
160
src/modules/ZoomIt/ZoomIt/BackgroundBlur.h
Normal file
160
src/modules/ZoomIt/ZoomIt/BackgroundBlur.h
Normal file
@@ -0,0 +1,160 @@
|
||||
//==============================================================================
|
||||
//
|
||||
// BackgroundBlur.h
|
||||
//
|
||||
// Performs person segmentation using Windows ML (Windows.AI.MachineLearning)
|
||||
// and applies either a Gaussian blur or a custom background image to the
|
||||
// background of a BGRA webcam frame. The segmentation model runs on CPU
|
||||
// via the Windows ML default device, keeping the GPU free for recording.
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
//==============================================================================
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <winrt/Windows.AI.MachineLearning.h>
|
||||
|
||||
// Background processing mode for the webcam overlay.
|
||||
enum class WebcamBackgroundMode : uint32_t
|
||||
{
|
||||
None = 0, // No background processing
|
||||
Blur = 1, // Gaussian blur on the background
|
||||
Image = 2, // Replace background with a user-chosen image
|
||||
};
|
||||
|
||||
class BackgroundBlur
|
||||
{
|
||||
public:
|
||||
BackgroundBlur() = default;
|
||||
~BackgroundBlur() = default;
|
||||
|
||||
// Initialize the ONNX model. modelPath must point to a valid .onnx
|
||||
// segmentation model file. Returns true on success.
|
||||
bool Initialize( const wchar_t* modelPath );
|
||||
|
||||
// Load a background replacement image from the given file path.
|
||||
// The image is decoded via WIC and stored as a BGRA buffer.
|
||||
// Returns true on success.
|
||||
bool SetBackgroundImage( const wchar_t* imagePath );
|
||||
|
||||
// Returns true if a background image has been loaded.
|
||||
bool HasBackgroundImage() const { return !m_bgImage.empty(); }
|
||||
|
||||
// Apply background blur to a BGRA pixel buffer in-place.
|
||||
// width/height are the frame dimensions. blurRadius controls
|
||||
// the strength of the Gaussian blur (in pixels).
|
||||
// Returns true if segmentation + blur was applied successfully.
|
||||
bool Apply( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius = 21 );
|
||||
|
||||
// Apply background image replacement to a BGRA pixel buffer in-place.
|
||||
// Uses the previously loaded background image (via SetBackgroundImage).
|
||||
// Returns true if segmentation + image replacement was applied.
|
||||
bool ApplyImageReplacement( uint8_t* bgraPixels, uint32_t width, uint32_t height );
|
||||
|
||||
// Returns true if the model has been loaded successfully.
|
||||
bool IsInitialized() const { return m_session != nullptr; }
|
||||
|
||||
// Access the segmentation mask after Apply()/ApplyImageReplacement().
|
||||
// The mask has one float [0..1] per pixel at the processing resolution
|
||||
// (1.0 = person / foreground, 0.0 = background).
|
||||
const std::vector<float>& GetMask() const { return m_mask; }
|
||||
uint32_t GetMaskWidth() const { return m_lastMaskWidth; }
|
||||
uint32_t GetMaskHeight() const { return m_lastMaskHeight; }
|
||||
bool HasCachedMask() const { return m_hasCachedMask; }
|
||||
|
||||
// Run segmentation only (no CPU blur or mask blend). Use this when
|
||||
// the blur will be performed on the GPU via a compute shader.
|
||||
// Populates the mask (GetMask) but does NOT touch bgraPixels.
|
||||
bool RunSegmentationOnly( const uint8_t* bgraPixels, uint32_t width, uint32_t height );
|
||||
|
||||
// Access the fully-blurred frame after Apply().
|
||||
// Contains all pixels blurred (before mask-based compositing).
|
||||
// Only valid after Apply() — NOT after ApplyImageReplacement().
|
||||
const std::vector<uint8_t>& GetBlurredFrame() const { return m_tempFrame; }
|
||||
|
||||
// Access the model-resolution mask before upscaling (e.g. 256×256).
|
||||
// Useful when the GPU handles upscaling via hardware bilinear filtering.
|
||||
const std::vector<float>& GetModelMask() const { return m_erodeBuf; }
|
||||
int64_t GetModelMaskWidth() const { return m_modelOutputWidth; }
|
||||
int64_t GetModelMaskHeight() const { return m_modelOutputHeight; }
|
||||
|
||||
private:
|
||||
// Run the segmentation model and produce a float mask [0..1] per pixel.
|
||||
// When modelResOnly is true, stops after model-resolution post-processing
|
||||
// (feathering + temporal smoothing at 256×256) and skips the CPU upscale
|
||||
// to frame resolution — the GPU bilinear sampler handles that instead.
|
||||
bool RunSegmentation( const uint8_t* bgraPixels, uint32_t width, uint32_t height,
|
||||
bool modelResOnly = false );
|
||||
|
||||
// Apply box blur (iterated for Gaussian approximation) to bgraPixels
|
||||
// only where the mask indicates background.
|
||||
void ApplyBlurWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius );
|
||||
|
||||
// Replace background pixels with the loaded background image.
|
||||
void ApplyImageWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height );
|
||||
|
||||
// Scale the loaded background image to the given dimensions (cached).
|
||||
void EnsureScaledBgImage( uint32_t width, uint32_t height );
|
||||
|
||||
// Decide whether inference is needed this frame (periodic + motion-adaptive).
|
||||
bool ShouldRunInference( const uint8_t* bgraPixels, uint32_t width, uint32_t height );
|
||||
|
||||
// Windows ML objects.
|
||||
winrt::Windows::AI::MachineLearning::LearningModel m_model{ nullptr };
|
||||
winrt::Windows::AI::MachineLearning::LearningModelSession m_session{ nullptr };
|
||||
winrt::Windows::AI::MachineLearning::LearningModelBinding m_binding{ nullptr };
|
||||
winrt::hstring m_inputName;
|
||||
winrt::hstring m_outputName;
|
||||
|
||||
// Model metadata (detected from the loaded model).
|
||||
int64_t m_modelInputWidth = 256;
|
||||
int64_t m_modelInputHeight = 256;
|
||||
int64_t m_modelInputChannels = 3;
|
||||
bool m_inputIsNchw = true; // true = [1,C,H,W], false = [1,H,W,C]
|
||||
bool m_usingGpu = false; // true if DirectML session is active
|
||||
|
||||
// Actual model output dimensions (may differ from input dimensions).
|
||||
int64_t m_modelOutputWidth = 256;
|
||||
int64_t m_modelOutputHeight = 256;
|
||||
|
||||
// Reusable buffers to avoid per-frame allocations.
|
||||
std::vector<float> m_inputTensor; // RGB float [1,3,H,W] or [1,H,W,3]
|
||||
std::vector<float> m_outputBuf; // Raw copy of output tensor data
|
||||
std::vector<float> m_mask; // Segmentation mask [width*height]
|
||||
std::vector<float> m_erodeBuf; // Model-resolution mask buffer
|
||||
std::vector<float> m_maskBlurBuf; // Temp buffer for mask edge smoothing
|
||||
std::vector<uint8_t> m_blurredFrame; // Temporary blurred copy
|
||||
std::vector<uint8_t> m_tempFrame; // Second temp buffer for blur passes
|
||||
|
||||
// Background image (original resolution, BGRA).
|
||||
std::vector<uint8_t> m_bgImage;
|
||||
uint32_t m_bgImageWidth = 0;
|
||||
uint32_t m_bgImageHeight = 0;
|
||||
|
||||
// Scaled background image (cached at overlay dimensions).
|
||||
std::vector<uint8_t> m_scaledBgImage;
|
||||
uint32_t m_scaledBgW = 0;
|
||||
uint32_t m_scaledBgH = 0;
|
||||
|
||||
// Frame-skipping: reuse the segmentation mask for N frames.
|
||||
int m_frameCounter = 0;
|
||||
uint32_t m_lastMaskWidth = 0;
|
||||
uint32_t m_lastMaskHeight = 0;
|
||||
bool m_hasCachedMask = false;
|
||||
|
||||
// Motion detection: luminance samples from the previous frame.
|
||||
static constexpr int MOTION_GRID_SIZE = 8; // 8×8 = 64 sample points
|
||||
float m_prevSamples[MOTION_GRID_SIZE * MOTION_GRID_SIZE] = {};
|
||||
bool m_hasPrevSamples = false;
|
||||
|
||||
// Temporal smoothing: previous frame's mask blended with current
|
||||
// to stabilize edges and reduce flicker.
|
||||
std::vector<float> m_prevMask;
|
||||
|
||||
// Model-resolution previous mask for GPU path temporal smoothing.
|
||||
std::vector<float> m_prevModelMask;
|
||||
};
|
||||
606
src/modules/ZoomIt/ZoomIt/BoxBlurCS.h
Normal file
606
src/modules/ZoomIt/ZoomIt/BoxBlurCS.h
Normal file
@@ -0,0 +1,606 @@
|
||||
#if 0
|
||||
//
|
||||
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
|
||||
//
|
||||
//
|
||||
// Buffer Definitions:
|
||||
//
|
||||
// cbuffer BlurConstants
|
||||
// {
|
||||
//
|
||||
// uint Direction; // Offset: 0 Size: 4
|
||||
// int Radius; // Offset: 4 Size: 4
|
||||
// uint Width; // Offset: 8 Size: 4
|
||||
// uint Height; // Offset: 12 Size: 4
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
// Resource Bindings:
|
||||
//
|
||||
// Name Type Format Dim HLSL Bind Count
|
||||
// ------------------------------ ---------- ------- ----------- -------------- ------
|
||||
// InputTex texture float4 2d t0 1
|
||||
// OutputTex UAV float4 2d u0 1
|
||||
// BlurConstants cbuffer NA NA cb0 1
|
||||
//
|
||||
//
|
||||
//
|
||||
// Input signature:
|
||||
//
|
||||
// Name Index Mask Register SysValue Format Used
|
||||
// -------------------- ----- ------ -------- -------- ------- ------
|
||||
// no Input
|
||||
//
|
||||
// Output signature:
|
||||
//
|
||||
// Name Index Mask Register SysValue Format Used
|
||||
// -------------------- ----- ------ -------- -------- ------- ------
|
||||
// no Output
|
||||
cs_5_0
|
||||
dcl_globalFlags refactoringAllowed
|
||||
dcl_constantbuffer CB0[1], immediateIndexed
|
||||
dcl_resource_texture2d (float,float,float,float) t0
|
||||
dcl_uav_typed_texture2d (float,float,float,float) u0
|
||||
dcl_input vThreadGroupID.xy
|
||||
dcl_input vThreadIDInGroup.x
|
||||
dcl_temps 4
|
||||
dcl_tgsm_structured g0, 16, 320
|
||||
dcl_thread_group 256, 1, 1
|
||||
imin r0.x, cb0[0].y, l(32)
|
||||
ishl r0.y, r0.x, l(1)
|
||||
iadd r0.z, r0.y, l(256)
|
||||
if_z cb0[0].x
|
||||
uge r0.w, vThreadGroupID.y, cb0[0].w
|
||||
if_nz r0.w
|
||||
ret
|
||||
endif
|
||||
ishl r0.w, vThreadGroupID.x, l(8)
|
||||
iadd r1.x, cb0[0].z, l(-1)
|
||||
mov r2.y, vThreadGroupID.y
|
||||
mov r2.zw, l(0,0,0,0)
|
||||
mov r1.y, vThreadIDInGroup.x
|
||||
loop
|
||||
ige r1.z, r1.y, r0.z
|
||||
breakc_nz r1.z
|
||||
iadd r1.z, r0.w, r1.y
|
||||
iadd r1.z, -r0.x, r1.z
|
||||
imax r1.z, r1.z, l(0)
|
||||
imin r2.x, r1.x, r1.z
|
||||
ld_indexable(texture2d)(float,float,float,float) r3.xyzw, r2.xyzw, t0.xyzw
|
||||
store_structured g0.xyzw, r1.y, l(0), r3.xyzw
|
||||
iadd r1.y, r1.y, l(256)
|
||||
endloop
|
||||
sync_g_t
|
||||
imad r1.x, vThreadGroupID.x, l(256), vThreadIDInGroup.x
|
||||
uge r0.w, r1.x, cb0[0].z
|
||||
if_nz r0.w
|
||||
ret
|
||||
endif
|
||||
mov r2.xyzw, l(0,0,0,0)
|
||||
mov r0.w, l(0)
|
||||
loop
|
||||
ilt r3.x, r0.y, r0.w
|
||||
breakc_nz r3.x
|
||||
iadd r3.x, r0.w, vThreadIDInGroup.x
|
||||
ld_structured r3.xyzw, r3.x, l(0), g0.xyzw
|
||||
add r2.xyzw, r2.xyzw, r3.xyzw
|
||||
iadd r0.w, r0.w, l(1)
|
||||
endloop
|
||||
bfi r0.w, l(31), l(1), r0.x, l(1)
|
||||
itof r0.w, r0.w
|
||||
div r2.xyzw, r2.xyzw, r0.wwww
|
||||
mov r1.yzw, vThreadGroupID.yyyy
|
||||
store_uav_typed u0.xyzw, r1.xyzw, r2.xyzw
|
||||
else
|
||||
uge r0.w, vThreadGroupID.y, cb0[0].z
|
||||
if_nz r0.w
|
||||
ret
|
||||
endif
|
||||
ishl r0.w, vThreadGroupID.x, l(8)
|
||||
iadd r1.x, cb0[0].w, l(-1)
|
||||
mov r2.x, vThreadGroupID.y
|
||||
mov r2.zw, l(0,0,0,0)
|
||||
mov r1.y, vThreadIDInGroup.x
|
||||
loop
|
||||
ige r1.z, r1.y, r0.z
|
||||
breakc_nz r1.z
|
||||
iadd r1.z, r0.w, r1.y
|
||||
iadd r1.z, -r0.x, r1.z
|
||||
imax r1.z, r1.z, l(0)
|
||||
imin r2.y, r1.x, r1.z
|
||||
ld_indexable(texture2d)(float,float,float,float) r3.xyzw, r2.xyzw, t0.xyzw
|
||||
store_structured g0.xyzw, r1.y, l(0), r3.xyzw
|
||||
iadd r1.y, r1.y, l(256)
|
||||
endloop
|
||||
sync_g_t
|
||||
imad r1.yzw, vThreadGroupID.xxxx, l(0, 256, 256, 256), vThreadIDInGroup.xxxx
|
||||
uge r0.z, r1.w, cb0[0].w
|
||||
if_nz r0.z
|
||||
ret
|
||||
endif
|
||||
mov r2.xyzw, l(0,0,0,0)
|
||||
mov r0.z, l(0)
|
||||
loop
|
||||
ilt r0.w, r0.y, r0.z
|
||||
breakc_nz r0.w
|
||||
iadd r0.w, r0.z, vThreadIDInGroup.x
|
||||
ld_structured r3.xyzw, r0.w, l(0), g0.xyzw
|
||||
add r2.xyzw, r2.xyzw, r3.xyzw
|
||||
iadd r0.z, r0.z, l(1)
|
||||
endloop
|
||||
bfi r0.x, l(31), l(1), r0.x, l(1)
|
||||
itof r0.x, r0.x
|
||||
div r0.xyzw, r2.xyzw, r0.xxxx
|
||||
mov r1.x, vThreadGroupID.y
|
||||
store_uav_typed u0.xyzw, r1.xyzw, r0.xyzw
|
||||
endif
|
||||
ret
|
||||
// Approximately 89 instruction slots used
|
||||
#endif
|
||||
|
||||
const BYTE g_BoxBlurCS[] =
|
||||
{
|
||||
68, 88, 66, 67, 109, 156,
|
||||
172, 79, 78, 198, 69, 61,
|
||||
87, 5, 27, 232, 85, 229,
|
||||
69, 181, 1, 0, 0, 0,
|
||||
212, 10, 0, 0, 5, 0,
|
||||
0, 0, 52, 0, 0, 0,
|
||||
80, 2, 0, 0, 96, 2,
|
||||
0, 0, 112, 2, 0, 0,
|
||||
56, 10, 0, 0, 82, 68,
|
||||
69, 70, 20, 2, 0, 0,
|
||||
1, 0, 0, 0, 192, 0,
|
||||
0, 0, 3, 0, 0, 0,
|
||||
60, 0, 0, 0, 0, 5,
|
||||
83, 67, 0, 1, 0, 0,
|
||||
233, 1, 0, 0, 82, 68,
|
||||
49, 49, 60, 0, 0, 0,
|
||||
24, 0, 0, 0, 32, 0,
|
||||
0, 0, 40, 0, 0, 0,
|
||||
36, 0, 0, 0, 12, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
156, 0, 0, 0, 2, 0,
|
||||
0, 0, 5, 0, 0, 0,
|
||||
4, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 13, 0,
|
||||
0, 0, 165, 0, 0, 0,
|
||||
4, 0, 0, 0, 5, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
13, 0, 0, 0, 175, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
73, 110, 112, 117, 116, 84,
|
||||
101, 120, 0, 79, 117, 116,
|
||||
112, 117, 116, 84, 101, 120,
|
||||
0, 66, 108, 117, 114, 67,
|
||||
111, 110, 115, 116, 97, 110,
|
||||
116, 115, 0, 171, 171, 171,
|
||||
175, 0, 0, 0, 4, 0,
|
||||
0, 0, 216, 0, 0, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
120, 1, 0, 0, 0, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
2, 0, 0, 0, 136, 1,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 172, 1,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
4, 0, 0, 0, 2, 0,
|
||||
0, 0, 184, 1, 0, 0,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 220, 1, 0, 0,
|
||||
8, 0, 0, 0, 4, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
136, 1, 0, 0, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
226, 1, 0, 0, 12, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
2, 0, 0, 0, 136, 1,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 68, 105,
|
||||
114, 101, 99, 116, 105, 111,
|
||||
110, 0, 100, 119, 111, 114,
|
||||
100, 0, 0, 0, 19, 0,
|
||||
1, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 130, 1,
|
||||
0, 0, 82, 97, 100, 105,
|
||||
117, 115, 0, 105, 110, 116,
|
||||
0, 171, 0, 0, 2, 0,
|
||||
1, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 179, 1,
|
||||
0, 0, 87, 105, 100, 116,
|
||||
104, 0, 72, 101, 105, 103,
|
||||
104, 116, 0, 77, 105, 99,
|
||||
114, 111, 115, 111, 102, 116,
|
||||
32, 40, 82, 41, 32, 72,
|
||||
76, 83, 76, 32, 83, 104,
|
||||
97, 100, 101, 114, 32, 67,
|
||||
111, 109, 112, 105, 108, 101,
|
||||
114, 32, 49, 48, 46, 49,
|
||||
0, 171, 171, 171, 73, 83,
|
||||
71, 78, 8, 0, 0, 0,
|
||||
0, 0, 0, 0, 8, 0,
|
||||
0, 0, 79, 83, 71, 78,
|
||||
8, 0, 0, 0, 0, 0,
|
||||
0, 0, 8, 0, 0, 0,
|
||||
83, 72, 69, 88, 192, 7,
|
||||
0, 0, 80, 0, 5, 0,
|
||||
240, 1, 0, 0, 106, 8,
|
||||
0, 1, 89, 0, 0, 4,
|
||||
70, 142, 32, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
88, 24, 0, 4, 0, 112,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
85, 85, 0, 0, 156, 24,
|
||||
0, 4, 0, 224, 17, 0,
|
||||
0, 0, 0, 0, 85, 85,
|
||||
0, 0, 95, 0, 0, 2,
|
||||
50, 16, 2, 0, 95, 0,
|
||||
0, 2, 18, 32, 2, 0,
|
||||
104, 0, 0, 2, 4, 0,
|
||||
0, 0, 160, 0, 0, 5,
|
||||
0, 240, 17, 0, 0, 0,
|
||||
0, 0, 16, 0, 0, 0,
|
||||
64, 1, 0, 0, 155, 0,
|
||||
0, 4, 0, 1, 0, 0,
|
||||
1, 0, 0, 0, 1, 0,
|
||||
0, 0, 37, 0, 0, 8,
|
||||
18, 0, 16, 0, 0, 0,
|
||||
0, 0, 26, 128, 32, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
32, 0, 0, 0, 41, 0,
|
||||
0, 7, 34, 0, 16, 0,
|
||||
0, 0, 0, 0, 10, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 1, 0,
|
||||
0, 0, 30, 0, 0, 7,
|
||||
66, 0, 16, 0, 0, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 0, 1, 0, 0,
|
||||
31, 0, 0, 4, 10, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 80, 0,
|
||||
0, 7, 130, 0, 16, 0,
|
||||
0, 0, 0, 0, 26, 16,
|
||||
2, 0, 58, 128, 32, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 31, 0, 4, 3,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 62, 0, 0, 1,
|
||||
21, 0, 0, 1, 41, 0,
|
||||
0, 6, 130, 0, 16, 0,
|
||||
0, 0, 0, 0, 10, 16,
|
||||
2, 0, 1, 64, 0, 0,
|
||||
8, 0, 0, 0, 30, 0,
|
||||
0, 8, 18, 0, 16, 0,
|
||||
1, 0, 0, 0, 42, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
54, 0, 0, 4, 34, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
26, 16, 2, 0, 54, 0,
|
||||
0, 8, 194, 0, 16, 0,
|
||||
2, 0, 0, 0, 2, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
54, 0, 0, 4, 34, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
10, 32, 2, 0, 48, 0,
|
||||
0, 1, 33, 0, 0, 7,
|
||||
66, 0, 16, 0, 1, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
1, 0, 0, 0, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
3, 0, 4, 3, 42, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
30, 0, 0, 7, 66, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
1, 0, 0, 0, 30, 0,
|
||||
0, 8, 66, 0, 16, 0,
|
||||
1, 0, 0, 0, 10, 0,
|
||||
16, 128, 65, 0, 0, 0,
|
||||
0, 0, 0, 0, 42, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
36, 0, 0, 7, 66, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
42, 0, 16, 0, 1, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
0, 0, 0, 0, 37, 0,
|
||||
0, 7, 18, 0, 16, 0,
|
||||
2, 0, 0, 0, 10, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
42, 0, 16, 0, 1, 0,
|
||||
0, 0, 45, 0, 0, 137,
|
||||
194, 0, 0, 128, 67, 85,
|
||||
21, 0, 242, 0, 16, 0,
|
||||
3, 0, 0, 0, 70, 14,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
70, 126, 16, 0, 0, 0,
|
||||
0, 0, 168, 0, 0, 9,
|
||||
242, 240, 17, 0, 0, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
1, 0, 0, 0, 1, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
70, 14, 16, 0, 3, 0,
|
||||
0, 0, 30, 0, 0, 7,
|
||||
34, 0, 16, 0, 1, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
1, 0, 0, 0, 1, 64,
|
||||
0, 0, 0, 1, 0, 0,
|
||||
22, 0, 0, 1, 190, 24,
|
||||
0, 1, 35, 0, 0, 7,
|
||||
18, 0, 16, 0, 1, 0,
|
||||
0, 0, 10, 16, 2, 0,
|
||||
1, 64, 0, 0, 0, 1,
|
||||
0, 0, 10, 32, 2, 0,
|
||||
80, 0, 0, 8, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
10, 0, 16, 0, 1, 0,
|
||||
0, 0, 42, 128, 32, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 31, 0, 4, 3,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 62, 0, 0, 1,
|
||||
21, 0, 0, 1, 54, 0,
|
||||
0, 8, 242, 0, 16, 0,
|
||||
2, 0, 0, 0, 2, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
54, 0, 0, 5, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 0,
|
||||
0, 0, 48, 0, 0, 1,
|
||||
34, 0, 0, 7, 18, 0,
|
||||
16, 0, 3, 0, 0, 0,
|
||||
26, 0, 16, 0, 0, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 3, 0,
|
||||
4, 3, 10, 0, 16, 0,
|
||||
3, 0, 0, 0, 30, 0,
|
||||
0, 6, 18, 0, 16, 0,
|
||||
3, 0, 0, 0, 58, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
10, 32, 2, 0, 167, 0,
|
||||
0, 9, 242, 0, 16, 0,
|
||||
3, 0, 0, 0, 10, 0,
|
||||
16, 0, 3, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 0,
|
||||
0, 0, 70, 254, 17, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 7, 242, 0, 16, 0,
|
||||
2, 0, 0, 0, 70, 14,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
70, 14, 16, 0, 3, 0,
|
||||
0, 0, 30, 0, 0, 7,
|
||||
130, 0, 16, 0, 0, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
22, 0, 0, 1, 140, 0,
|
||||
0, 11, 130, 0, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 31, 0, 0, 0,
|
||||
1, 64, 0, 0, 1, 0,
|
||||
0, 0, 10, 0, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
43, 0, 0, 5, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 14, 0, 0, 7,
|
||||
242, 0, 16, 0, 2, 0,
|
||||
0, 0, 70, 14, 16, 0,
|
||||
2, 0, 0, 0, 246, 15,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
54, 0, 0, 4, 226, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
86, 21, 2, 0, 164, 0,
|
||||
0, 7, 242, 224, 17, 0,
|
||||
0, 0, 0, 0, 70, 14,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
70, 14, 16, 0, 2, 0,
|
||||
0, 0, 18, 0, 0, 1,
|
||||
80, 0, 0, 7, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
26, 16, 2, 0, 42, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 31, 0,
|
||||
4, 3, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 62, 0,
|
||||
0, 1, 21, 0, 0, 1,
|
||||
41, 0, 0, 6, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
10, 16, 2, 0, 1, 64,
|
||||
0, 0, 8, 0, 0, 0,
|
||||
30, 0, 0, 8, 18, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
58, 128, 32, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 255, 255,
|
||||
255, 255, 54, 0, 0, 4,
|
||||
18, 0, 16, 0, 2, 0,
|
||||
0, 0, 26, 16, 2, 0,
|
||||
54, 0, 0, 8, 194, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
2, 64, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 54, 0, 0, 4,
|
||||
34, 0, 16, 0, 1, 0,
|
||||
0, 0, 10, 32, 2, 0,
|
||||
48, 0, 0, 1, 33, 0,
|
||||
0, 7, 66, 0, 16, 0,
|
||||
1, 0, 0, 0, 26, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
42, 0, 16, 0, 0, 0,
|
||||
0, 0, 3, 0, 4, 3,
|
||||
42, 0, 16, 0, 1, 0,
|
||||
0, 0, 30, 0, 0, 7,
|
||||
66, 0, 16, 0, 1, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 26, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
30, 0, 0, 8, 66, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
10, 0, 16, 128, 65, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
42, 0, 16, 0, 1, 0,
|
||||
0, 0, 36, 0, 0, 7,
|
||||
66, 0, 16, 0, 1, 0,
|
||||
0, 0, 42, 0, 16, 0,
|
||||
1, 0, 0, 0, 1, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
37, 0, 0, 7, 34, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
10, 0, 16, 0, 1, 0,
|
||||
0, 0, 42, 0, 16, 0,
|
||||
1, 0, 0, 0, 45, 0,
|
||||
0, 137, 194, 0, 0, 128,
|
||||
67, 85, 21, 0, 242, 0,
|
||||
16, 0, 3, 0, 0, 0,
|
||||
70, 14, 16, 0, 2, 0,
|
||||
0, 0, 70, 126, 16, 0,
|
||||
0, 0, 0, 0, 168, 0,
|
||||
0, 9, 242, 240, 17, 0,
|
||||
0, 0, 0, 0, 26, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 0,
|
||||
0, 0, 70, 14, 16, 0,
|
||||
3, 0, 0, 0, 30, 0,
|
||||
0, 7, 34, 0, 16, 0,
|
||||
1, 0, 0, 0, 26, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 1,
|
||||
0, 0, 22, 0, 0, 1,
|
||||
190, 24, 0, 1, 35, 0,
|
||||
0, 10, 226, 0, 16, 0,
|
||||
1, 0, 0, 0, 6, 16,
|
||||
2, 0, 2, 64, 0, 0,
|
||||
0, 0, 0, 0, 0, 1,
|
||||
0, 0, 0, 1, 0, 0,
|
||||
0, 1, 0, 0, 6, 32,
|
||||
2, 0, 80, 0, 0, 8,
|
||||
66, 0, 16, 0, 0, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
1, 0, 0, 0, 58, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 31, 0,
|
||||
4, 3, 42, 0, 16, 0,
|
||||
0, 0, 0, 0, 62, 0,
|
||||
0, 1, 21, 0, 0, 1,
|
||||
54, 0, 0, 8, 242, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
2, 64, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 54, 0, 0, 5,
|
||||
66, 0, 16, 0, 0, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
0, 0, 0, 0, 48, 0,
|
||||
0, 1, 34, 0, 0, 7,
|
||||
130, 0, 16, 0, 0, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
0, 0, 0, 0, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
3, 0, 4, 3, 58, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
30, 0, 0, 6, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
42, 0, 16, 0, 0, 0,
|
||||
0, 0, 10, 32, 2, 0,
|
||||
167, 0, 0, 9, 242, 0,
|
||||
16, 0, 3, 0, 0, 0,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
0, 0, 0, 0, 70, 254,
|
||||
17, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 7, 242, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
70, 14, 16, 0, 2, 0,
|
||||
0, 0, 70, 14, 16, 0,
|
||||
3, 0, 0, 0, 30, 0,
|
||||
0, 7, 66, 0, 16, 0,
|
||||
0, 0, 0, 0, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 1, 0,
|
||||
0, 0, 22, 0, 0, 1,
|
||||
140, 0, 0, 11, 18, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 31, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
1, 0, 0, 0, 10, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 1, 0,
|
||||
0, 0, 43, 0, 0, 5,
|
||||
18, 0, 16, 0, 0, 0,
|
||||
0, 0, 10, 0, 16, 0,
|
||||
0, 0, 0, 0, 14, 0,
|
||||
0, 7, 242, 0, 16, 0,
|
||||
0, 0, 0, 0, 70, 14,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
6, 0, 16, 0, 0, 0,
|
||||
0, 0, 54, 0, 0, 4,
|
||||
18, 0, 16, 0, 1, 0,
|
||||
0, 0, 26, 16, 2, 0,
|
||||
164, 0, 0, 7, 242, 224,
|
||||
17, 0, 0, 0, 0, 0,
|
||||
70, 14, 16, 0, 1, 0,
|
||||
0, 0, 70, 14, 16, 0,
|
||||
0, 0, 0, 0, 21, 0,
|
||||
0, 1, 62, 0, 0, 1,
|
||||
83, 84, 65, 84, 148, 0,
|
||||
0, 0, 89, 0, 0, 0,
|
||||
4, 0, 0, 0, 0, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
4, 0, 0, 0, 27, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
7, 0, 0, 0, 8, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
12, 0, 0, 0, 0, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
2, 0, 0, 0, 0, 0,
|
||||
0, 0, 2, 0, 0, 0
|
||||
};
|
||||
115
src/modules/ZoomIt/ZoomIt/BoxBlurCS.hlsl
Normal file
115
src/modules/ZoomIt/ZoomIt/BoxBlurCS.hlsl
Normal file
@@ -0,0 +1,115 @@
|
||||
//==============================================================================
|
||||
//
|
||||
// BoxBlurCS.hlsl
|
||||
//
|
||||
// D3D11 compute shader for separable box blur. Each dispatch performs
|
||||
// either a horizontal or a vertical pass controlled by the Direction
|
||||
// constant. Run four dispatches (H→V→H→V) to approximate a Gaussian.
|
||||
//
|
||||
// Uses group-shared memory so each texel is loaded once per thread
|
||||
// group, then reused across the sliding window.
|
||||
//
|
||||
// Entry point:
|
||||
// CSMain (cs_5_0)
|
||||
//
|
||||
// Recompile with:
|
||||
// fxc /T cs_5_0 /E CSMain /Fh BoxBlurCS.h /Vn g_BoxBlurCS BoxBlurCS.hlsl
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
//==============================================================================
|
||||
|
||||
// Input texture (read-only).
|
||||
Texture2D<float4> InputTex : register(t0);
|
||||
|
||||
// Output texture (write-only).
|
||||
RWTexture2D<float4> OutputTex : register(u0);
|
||||
|
||||
cbuffer BlurConstants : register(b0)
|
||||
{
|
||||
uint Direction; // 0 = horizontal, 1 = vertical
|
||||
int Radius; // Box blur radius in pixels
|
||||
uint Width; // Image width
|
||||
uint Height; // Image height
|
||||
};
|
||||
|
||||
// Thread group: 256 threads along the blur axis.
|
||||
#define GROUP_SIZE 256
|
||||
|
||||
// Max radius we support. Shared memory = (GROUP_SIZE + 2*MAX_RADIUS) * 4 floats * 4 bytes
|
||||
// = (256 + 64) * 16 = ~5 KB, well within the 32 KB limit.
|
||||
#define MAX_RADIUS 32
|
||||
|
||||
// Shared memory tile: enough for the group + apron on both sides.
|
||||
groupshared float4 tile[GROUP_SIZE + 2 * MAX_RADIUS];
|
||||
|
||||
[numthreads(GROUP_SIZE, 1, 1)]
|
||||
void CSMain( uint3 groupId : SV_GroupID,
|
||||
uint3 groupTid : SV_GroupThreadID,
|
||||
uint3 dispatchId : SV_DispatchThreadID )
|
||||
{
|
||||
int r = min( Radius, MAX_RADIUS );
|
||||
int tileSize = GROUP_SIZE + 2 * r;
|
||||
int tid = (int)groupTid.x;
|
||||
|
||||
if( Direction == 0 )
|
||||
{
|
||||
// ── Horizontal pass ────────────────────────────────────────
|
||||
int row = (int)groupId.y;
|
||||
if( (uint)row >= Height )
|
||||
return;
|
||||
|
||||
int groupStart = (int)groupId.x * GROUP_SIZE;
|
||||
|
||||
// Load tile: each thread loads its primary texel + apron.
|
||||
for( int i = tid; i < tileSize; i += GROUP_SIZE )
|
||||
{
|
||||
int srcX = clamp( groupStart + i - r, 0, (int)Width - 1 );
|
||||
tile[i] = InputTex[int2( srcX, row )];
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
int outX = groupStart + tid;
|
||||
if( (uint)outX >= Width )
|
||||
return;
|
||||
|
||||
// Sum the window from shared memory.
|
||||
float4 sum = (float4)0;
|
||||
int windowStart = tid; // tile index = tid + r - r
|
||||
for( int k = 0; k <= 2 * r; k++ )
|
||||
{
|
||||
sum += tile[windowStart + k];
|
||||
}
|
||||
OutputTex[int2( outX, row )] = sum / (float)( 2 * r + 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
// ── Vertical pass ──────────────────────────────────────────
|
||||
int col = (int)groupId.y;
|
||||
if( (uint)col >= Width )
|
||||
return;
|
||||
|
||||
int groupStart = (int)groupId.x * GROUP_SIZE;
|
||||
|
||||
// Load tile.
|
||||
for( int i = tid; i < tileSize; i += GROUP_SIZE )
|
||||
{
|
||||
int srcY = clamp( groupStart + i - r, 0, (int)Height - 1 );
|
||||
tile[i] = InputTex[int2( col, srcY )];
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
int outY = groupStart + tid;
|
||||
if( (uint)outY >= Height )
|
||||
return;
|
||||
|
||||
float4 sum = (float4)0;
|
||||
int windowStart = tid;
|
||||
for( int k = 0; k <= 2 * r; k++ )
|
||||
{
|
||||
sum += tile[windowStart + k];
|
||||
}
|
||||
OutputTex[int2( col, outY )] = sum / (float)( 2 * r + 1 );
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,7 @@ std::optional<CaptureFrame> CaptureFrameWait::TryGetNextFrame()
|
||||
if (m_currentFrame != nullptr)
|
||||
{
|
||||
m_currentFrame.Close();
|
||||
m_currentFrame = nullptr; // Prevent double-Close on subsequent calls
|
||||
}
|
||||
m_nextFrameEvent.ResetEvent();
|
||||
|
||||
@@ -107,6 +108,33 @@ std::optional<CaptureFrame> CaptureFrameWait::TryGetNextFrame()
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// CaptureFrameWait::PeekCurrentFrame
|
||||
//
|
||||
// Returns the frame that is currently held (if any) without closing
|
||||
// it and without waiting for a new one. This is useful during
|
||||
// recording startup: the constructor captured a frame when the
|
||||
// session began, and OnMediaStreamSourceStarting can use it
|
||||
// immediately instead of blocking until the next desktop change.
|
||||
// The frame remains alive in the pool until the next
|
||||
// TryGetNextFrame() call closes it.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
std::optional<CaptureFrame> CaptureFrameWait::PeekCurrentFrame() const
|
||||
{
|
||||
if (m_currentFrame != nullptr)
|
||||
{
|
||||
return std::optional<CaptureFrame>(
|
||||
{
|
||||
m_currentFrame.Surface(),
|
||||
m_currentFrame.ContentSize(),
|
||||
m_currentFrame.SystemRelativeTime(),
|
||||
});
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// CaptureFrameWait::TryGetNextFrame (with timeout)
|
||||
@@ -121,6 +149,7 @@ std::optional<CaptureFrame> CaptureFrameWait::TryGetNextFrame( DWORD timeoutMs )
|
||||
if( m_currentFrame != nullptr )
|
||||
{
|
||||
m_currentFrame.Close();
|
||||
m_currentFrame = nullptr; // Prevent double-Close on subsequent calls
|
||||
}
|
||||
m_nextFrameEvent.ResetEvent();
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ public:
|
||||
|
||||
std::optional<CaptureFrame> TryGetNextFrame();
|
||||
std::optional<CaptureFrame> TryGetNextFrame( DWORD timeoutMs );
|
||||
std::optional<CaptureFrame> PeekCurrentFrame() const;
|
||||
void StopCapture();
|
||||
void EnableCursorCapture( bool enable = true )
|
||||
{
|
||||
|
||||
113
src/modules/ZoomIt/ZoomIt/NoiseSuppressor.cpp
Normal file
113
src/modules/ZoomIt/ZoomIt/NoiseSuppressor.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "pch.h"
|
||||
#include "NoiseSuppressor.h"
|
||||
|
||||
extern "C" {
|
||||
#include "rnnoise/rnnoise.h"
|
||||
}
|
||||
|
||||
// RNNoise processes 480 mono samples per frame (10ms at 48kHz)
|
||||
static constexpr uint32_t RNNOISE_FRAME_SIZE = 480;
|
||||
|
||||
// RNNoise expects samples in PCM16 range (-32768 to 32767), not normalized float [-1, 1]
|
||||
static constexpr float PCM16_SCALE = 32768.0f;
|
||||
static constexpr float PCM16_SCALE_INV = 1.0f / 32768.0f;
|
||||
|
||||
NoiseSuppressor::NoiseSuppressor()
|
||||
{
|
||||
}
|
||||
|
||||
NoiseSuppressor::~NoiseSuppressor()
|
||||
{
|
||||
for (auto& channel : m_channels)
|
||||
{
|
||||
if (channel.state)
|
||||
{
|
||||
rnnoise_destroy(channel.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NoiseSuppressor::EnsureChannelCount(uint32_t channels)
|
||||
{
|
||||
if (m_channels.size() == channels)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Channel count changed (or first call): rebuild per-channel RNNoise state.
|
||||
for (auto& channel : m_channels)
|
||||
{
|
||||
if (channel.state)
|
||||
{
|
||||
rnnoise_destroy(channel.state);
|
||||
}
|
||||
}
|
||||
|
||||
m_channels.clear();
|
||||
m_channels.resize(channels);
|
||||
for (auto& channel : m_channels)
|
||||
{
|
||||
channel.state = rnnoise_create(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void NoiseSuppressor::Process(float* samples, uint32_t sampleCount, uint32_t channels)
|
||||
{
|
||||
if (sampleCount == 0 || channels == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureChannelCount(channels);
|
||||
|
||||
uint32_t frameCount = sampleCount / channels;
|
||||
|
||||
// Denoise each channel independently so the original channel layout is
|
||||
// preserved (e.g. a mic wired only to the left channel stays on the left
|
||||
// and silent channels stay silent instead of being filled with the voice).
|
||||
for (uint32_t ch = 0; ch < channels; ch++)
|
||||
{
|
||||
ChannelState& channel = m_channels[ch];
|
||||
if (!channel.state)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t residualCount = static_cast<uint32_t>(channel.residual.size());
|
||||
uint32_t totalSamples = residualCount + frameCount;
|
||||
|
||||
channel.work.resize(totalSamples);
|
||||
|
||||
// Copy residual from previous call
|
||||
if (residualCount > 0)
|
||||
{
|
||||
memcpy(channel.work.data(), channel.residual.data(), residualCount * sizeof(float));
|
||||
}
|
||||
|
||||
// Deinterleave this channel and scale to PCM16 range for RNNoise
|
||||
for (uint32_t i = 0; i < frameCount; i++)
|
||||
{
|
||||
channel.work[residualCount + i] = samples[i * channels + ch] * PCM16_SCALE;
|
||||
}
|
||||
|
||||
// Process complete 480-sample frames through RNNoise
|
||||
uint32_t processedSamples = 0;
|
||||
while (processedSamples + RNNOISE_FRAME_SIZE <= totalSamples)
|
||||
{
|
||||
rnnoise_process_frame(channel.state, &channel.work[processedSamples], &channel.work[processedSamples]);
|
||||
processedSamples += RNNOISE_FRAME_SIZE;
|
||||
}
|
||||
|
||||
// Save unprocessed residual for next call
|
||||
channel.residual.assign(
|
||||
channel.work.begin() + processedSamples,
|
||||
channel.work.end());
|
||||
|
||||
// Write denoised samples back to the interleaved buffer, scaling back to
|
||||
// normalized float. Only this call's input region is written back.
|
||||
for (uint32_t i = 0; i < frameCount; i++)
|
||||
{
|
||||
samples[i * channels + ch] = channel.work[residualCount + i] * PCM16_SCALE_INV;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/modules/ZoomIt/ZoomIt/NoiseSuppressor.h
Normal file
37
src/modules/ZoomIt/ZoomIt/NoiseSuppressor.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
struct DenoiseState;
|
||||
|
||||
class NoiseSuppressor
|
||||
{
|
||||
public:
|
||||
NoiseSuppressor();
|
||||
~NoiseSuppressor();
|
||||
|
||||
NoiseSuppressor(const NoiseSuppressor&) = delete;
|
||||
NoiseSuppressor& operator=(const NoiseSuppressor&) = delete;
|
||||
|
||||
// Process interleaved multi-channel float samples in-place.
|
||||
// Each channel is denoised independently through its own RNNoise state in
|
||||
// 480-sample frames, preserving the original channel layout (e.g. a mic
|
||||
// wired only to the left channel stays on the left and is not duplicated
|
||||
// onto the right).
|
||||
void Process(float* samples, uint32_t sampleCount, uint32_t channels);
|
||||
|
||||
private:
|
||||
// Per-channel RNNoise state and buffers so each channel is denoised
|
||||
// independently and the channel layout is preserved.
|
||||
struct ChannelState
|
||||
{
|
||||
DenoiseState* state = nullptr;
|
||||
std::vector<float> work; // Working buffer for the current quantum
|
||||
std::vector<float> residual; // Leftover samples from previous quantum
|
||||
};
|
||||
|
||||
void EnsureChannelCount(uint32_t channels);
|
||||
|
||||
std::vector<ChannelState> m_channels;
|
||||
};
|
||||
@@ -32,6 +32,9 @@ extern DWORD g_WebcamPosition;
|
||||
extern DWORD g_WebcamSize;
|
||||
extern DWORD g_WebcamShape;
|
||||
extern TCHAR g_WebcamDeviceSymLink[MAX_PATH];
|
||||
extern DWORD g_WebcamBackgroundMode;
|
||||
extern TCHAR g_WebcamBackgroundImage[];
|
||||
extern DWORD g_WebcamBrightness;
|
||||
extern class ClassRegistry reg;
|
||||
extern REG_SETTING RegSettings[];
|
||||
extern HINSTANCE g_hInstance;
|
||||
@@ -106,6 +109,34 @@ static double RecDiagElapsedMs()
|
||||
return static_cast<double>( now.QuadPart - s_origin.QuadPart ) * 1000.0 / s_freq.QuadPart;
|
||||
}
|
||||
|
||||
static FILE* s_recDiagFile = nullptr;
|
||||
|
||||
static void RecDiagOpenFile()
|
||||
{
|
||||
if( !s_recDiagFile )
|
||||
{
|
||||
wchar_t path[MAX_PATH];
|
||||
if( ExpandEnvironmentStringsW( L"%TEMP%\\ZoomIt_RecDiag.log", path, MAX_PATH ) )
|
||||
{
|
||||
_wfopen_s( &s_recDiagFile, path, L"a" );
|
||||
if( s_recDiagFile )
|
||||
{
|
||||
fwprintf( s_recDiagFile, L"\n===== NEW SESSION =====\n" );
|
||||
fflush( s_recDiagFile );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RecDiagCloseFile()
|
||||
{
|
||||
if( s_recDiagFile )
|
||||
{
|
||||
fclose( s_recDiagFile );
|
||||
s_recDiagFile = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void RecDiag( const wchar_t* fmt, ... )
|
||||
{
|
||||
wchar_t buf[512];
|
||||
@@ -123,8 +154,19 @@ static void RecDiag( const wchar_t* fmt, ... )
|
||||
_vsnwprintf_s( buf + offset, _countof( buf ) - offset, _TRUNCATE, fmt, va );
|
||||
va_end( va );
|
||||
OutputDebugStringW( buf );
|
||||
|
||||
RecDiagOpenFile();
|
||||
if( s_recDiagFile )
|
||||
{
|
||||
fwprintf( s_recDiagFile, L"%s", buf );
|
||||
fflush( s_recDiagFile );
|
||||
}
|
||||
}
|
||||
|
||||
static int s_diagVideoCount = 0;
|
||||
static int s_diagAudioCount = 0;
|
||||
static int64_t s_diagStartTs = 0; // SystemRelativeTime from OnStarting
|
||||
|
||||
static bool IsGifPath(const std::wstring& path)
|
||||
{
|
||||
try
|
||||
@@ -930,12 +972,20 @@ VideoRecordingSession::VideoRecordingSession(
|
||||
winrt::GraphicsCaptureItem const& item,
|
||||
RECT const cropRect,
|
||||
uint32_t frameRate,
|
||||
bool captureAudio,
|
||||
bool captureSystemAudio,
|
||||
bool micMonoMix,
|
||||
std::unique_ptr<AudioSampleGenerator> audioGenerator,
|
||||
winrt::IAsyncAction audioInitAction,
|
||||
winrt::Streams::IRandomAccessStream const& stream)
|
||||
{
|
||||
RecDiag( L"Constructor: entry\n" );
|
||||
|
||||
// Take ownership of pre-created audio generator. Its InitializeAsync
|
||||
// was started in StartRecordingAsync so it runs in parallel with all
|
||||
// the D3D, capture-item, and webcam setup below.
|
||||
m_audioGenerator = std::move( audioGenerator );
|
||||
m_audioInitAction = audioInitAction;
|
||||
RecDiag( L"Constructor: audio generator received (init %s)\n",
|
||||
m_audioInitAction ? L"pending" : L"none" );
|
||||
|
||||
m_device = device;
|
||||
m_d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(m_device);
|
||||
m_d3dDevice->GetImmediateContext(m_d3dContext.put());
|
||||
@@ -1038,8 +1088,15 @@ VideoRecordingSession::VideoRecordingSession(
|
||||
probeCapture.Close();
|
||||
return true;
|
||||
}
|
||||
catch( winrt::hresult_error const& ex )
|
||||
{
|
||||
RecDiag( L"Constructor: webcam probe failed hr=0x%08X: %s\n",
|
||||
static_cast<unsigned>( ex.code() ), ex.message().c_str() );
|
||||
return false;
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
RecDiag( L"Constructor: webcam probe failed with unknown exception\n" );
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -1067,7 +1124,10 @@ VideoRecordingSession::VideoRecordingSession(
|
||||
static_cast<WebcamCapture::Position>( g_WebcamPosition ),
|
||||
static_cast<WebcamCapture::Size>( g_WebcamSize ),
|
||||
webcamShape,
|
||||
isFullScreenRecording );
|
||||
isFullScreenRecording,
|
||||
static_cast<WebcamBackgroundMode>( g_WebcamBackgroundMode ),
|
||||
g_WebcamBackgroundImage,
|
||||
static_cast<int>( g_WebcamBrightness ) );
|
||||
m_webcamCapture->Start();
|
||||
RecDiag( L"Constructor: WebcamCapture::Start() returned\n" );
|
||||
}
|
||||
@@ -1089,12 +1149,12 @@ VideoRecordingSession::VideoRecordingSession(
|
||||
// Store frame interval for timeout-based frame production when webcam is active.
|
||||
m_frameIntervalTicks = ( frameRate > 0 ) ? ( 10'000'000LL / frameRate ) : 333'333LL;
|
||||
|
||||
if (captureAudio || captureSystemAudio)
|
||||
if (m_audioGenerator)
|
||||
{
|
||||
// Always set up audio profile for loopback capture (stereo AAC)
|
||||
auto audio = m_encodingProfile.Audio();
|
||||
audio = winrt::AudioEncodingProperties::CreateAac(48000, 2, 192000);
|
||||
m_encodingProfile.Audio(audio);
|
||||
auto audioProps = m_audioGenerator->GetEncodingProperties();
|
||||
m_encodingProfile.Audio(winrt::AudioEncodingProperties::CreateAac(
|
||||
audioProps.SampleRate(), audioProps.ChannelCount(), 192000));
|
||||
}
|
||||
|
||||
// Describe our input: uncompressed BGRA8 buffers
|
||||
@@ -1113,15 +1173,6 @@ VideoRecordingSession::VideoRecordingSession(
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
2);
|
||||
|
||||
if (captureAudio || captureSystemAudio)
|
||||
{
|
||||
m_audioGenerator = std::make_unique<AudioSampleGenerator>(captureAudio, captureSystemAudio, micMonoMix);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_audioGenerator = nullptr;
|
||||
}
|
||||
|
||||
// Wait for the webcam's first frame now that all other setup is done.
|
||||
// The camera was started early, so most of its ~850 ms sensor warmup has
|
||||
// overlapped with the encoding profile, swap chain, and audio generator
|
||||
@@ -1155,6 +1206,7 @@ VideoRecordingSession::VideoRecordingSession(
|
||||
VideoRecordingSession::~VideoRecordingSession()
|
||||
{
|
||||
Close();
|
||||
RecDiagCloseFile();
|
||||
}
|
||||
|
||||
|
||||
@@ -1173,8 +1225,13 @@ winrt::IAsyncAction VideoRecordingSession::StartAsync()
|
||||
// Create our MediaStreamSource
|
||||
if(m_audioGenerator) {
|
||||
|
||||
RecDiag( L"StartAsync: co_await InitializeAsync...\n" );
|
||||
co_await m_audioGenerator->InitializeAsync();
|
||||
RecDiag( L"StartAsync: co_await audio init...\n" );
|
||||
if (m_audioInitAction) {
|
||||
co_await m_audioInitAction; // started in constructor
|
||||
m_audioInitAction = nullptr;
|
||||
} else {
|
||||
co_await m_audioGenerator->InitializeAsync();
|
||||
}
|
||||
RecDiag( L"StartAsync: audio initialized\n" );
|
||||
m_streamSource = winrt::MediaStreamSource(m_videoDescriptor, winrt::AudioStreamDescriptor(m_audioGenerator->GetEncodingProperties()));
|
||||
}
|
||||
@@ -1236,6 +1293,9 @@ winrt::IAsyncAction VideoRecordingSession::StartAsync()
|
||||
//----------------------------------------------------------------------------
|
||||
void VideoRecordingSession::Close()
|
||||
{
|
||||
RecDiag( L"Close: totalVideoFrames=%d totalAudioSamples=%d\n",
|
||||
s_diagVideoCount, s_diagAudioCount );
|
||||
|
||||
// Stop webcam capture before closing the main session.
|
||||
if( m_webcamCapture )
|
||||
{
|
||||
@@ -1285,28 +1345,45 @@ void VideoRecordingSession::OnMediaStreamSourceStarting(
|
||||
winrt::MediaStreamSource const&,
|
||||
winrt::MediaStreamSourceStartingEventArgs const& args)
|
||||
{
|
||||
RecDiag( L"OnStarting: entry, calling TryGetNextFrame...\n" );
|
||||
auto frame = m_frameWait->TryGetNextFrame();
|
||||
// Close the stale frame captured in the constructor (~1-2 seconds
|
||||
// ago) and grab a FRESH frame via TryGetNextFrame. This gives us
|
||||
// both current visual content and a current SystemRelativeTime,
|
||||
// avoiding the frozen-first-frame artefact. WGC delivers a new
|
||||
// frame within one vblank (~16 ms) after the old frame is released
|
||||
// back to the pool, so a 200 ms timeout is very generous.
|
||||
RecDiag( L"OnStarting: calling TryGetNextFrame(200) for fresh frame...\n" );
|
||||
auto frame = m_frameWait->TryGetNextFrame( 200 );
|
||||
|
||||
int64_t startSRT = 0;
|
||||
|
||||
if (frame) {
|
||||
RecDiag( L"OnStarting: got frame, SystemRelativeTime=%lld (%.1fms)\n",
|
||||
frame->SystemRelativeTime.count(),
|
||||
frame->SystemRelativeTime.count() / 10000.0 );
|
||||
args.Request().SetActualStartPosition(frame->SystemRelativeTime);
|
||||
startSRT = frame->SystemRelativeTime.count();
|
||||
RecDiag( L"OnStarting: got fresh frame, SRT=%lld (%.1fms)\n",
|
||||
startSRT, startSRT / 10000.0 );
|
||||
|
||||
// Cache this frame so it can be served as the very first video
|
||||
// sample in OnMediaStreamSourceSampleRequested. Without this,
|
||||
// the frame is discarded and the first encoded sample comes from
|
||||
// the *next* capture, creating a visible timestamp gap.
|
||||
args.Request().SetActualStartPosition( frame->SystemRelativeTime );
|
||||
m_cachedStartingFrame = frame;
|
||||
|
||||
if (m_audioGenerator) {
|
||||
|
||||
RecDiag( L"OnStarting: calling AudioSampleGenerator::Start()\n" );
|
||||
m_audioGenerator->Start();
|
||||
RecDiag( L"OnStarting: audio started\n" );
|
||||
}
|
||||
m_adjustedStartSRT = startSRT;
|
||||
} else {
|
||||
RecDiag( L"OnStarting: TryGetNextFrame returned nullopt!\n" );
|
||||
// Timeout (very unlikely). Fall back to QPC-derived SRT.
|
||||
// Use double for the intermediate product to avoid int64
|
||||
// overflow (naive integer multiply overflows after ~25.6 h).
|
||||
RecDiag( L"OnStarting: TryGetNextFrame timed out, using QPC fallback\n" );
|
||||
LARGE_INTEGER qpcFreq, qpcNow;
|
||||
QueryPerformanceFrequency( &qpcFreq );
|
||||
QueryPerformanceCounter( &qpcNow );
|
||||
startSRT = static_cast<int64_t>(
|
||||
static_cast<double>( qpcNow.QuadPart ) * 10'000'000.0 / qpcFreq.QuadPart );
|
||||
args.Request().SetActualStartPosition( winrt::TimeSpan{ startSRT } );
|
||||
m_adjustedStartSRT = startSRT;
|
||||
}
|
||||
|
||||
// Start audio capture. Pass the video start SRT so audio
|
||||
// timestamps can be rebased to the same domain as video.
|
||||
if (m_audioGenerator) {
|
||||
RecDiag( L"OnStarting: calling AudioSampleGenerator::Start(videoStartSRT=%lld)\n", startSRT );
|
||||
m_audioGenerator->Start( startSRT );
|
||||
RecDiag( L"OnStarting: audio started\n" );
|
||||
}
|
||||
RecDiag( L"OnStarting: exit\n" );
|
||||
}
|
||||
@@ -1321,12 +1398,11 @@ std::shared_ptr<VideoRecordingSession> VideoRecordingSession::Create(
|
||||
winrt::GraphicsCaptureItem const& item,
|
||||
RECT const& crop,
|
||||
uint32_t frameRate,
|
||||
bool captureAudio,
|
||||
bool captureSystemAudio,
|
||||
bool micMonoMix,
|
||||
std::unique_ptr<AudioSampleGenerator> audioGenerator,
|
||||
winrt::IAsyncAction audioInitAction,
|
||||
winrt::Streams::IRandomAccessStream const& stream)
|
||||
{
|
||||
return std::shared_ptr<VideoRecordingSession>(new VideoRecordingSession(device, item, crop, frameRate, captureAudio, captureSystemAudio, micMonoMix, stream));
|
||||
return std::shared_ptr<VideoRecordingSession>(new VideoRecordingSession(device, item, crop, frameRate, std::move(audioGenerator), audioInitAction, stream));
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -1338,10 +1414,6 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
|
||||
winrt::MediaStreamSource const&,
|
||||
winrt::MediaStreamSourceSampleRequestedEventArgs const& args)
|
||||
{
|
||||
static int s_diagVideoCount = 0;
|
||||
static int s_diagAudioCount = 0;
|
||||
static int64_t s_diagStartTs = 0; // SystemRelativeTime from OnStarting
|
||||
|
||||
auto request = args.Request();
|
||||
auto streamDescriptor = request.StreamDescriptor();
|
||||
if (auto videoStreamDescriptor = streamDescriptor.try_as<winrt::VideoStreamDescriptor>())
|
||||
@@ -1412,7 +1484,16 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
|
||||
else
|
||||
{
|
||||
// New desktop frame — crop and copy to back buffer.
|
||||
timeStamp = frame->SystemRelativeTime;
|
||||
// If this is the cached starting frame, use the adjusted
|
||||
// SRT (computed from QPC in OnStarting) instead of the
|
||||
// stale SRT from the constructor. The stale SRT is ~2-3s
|
||||
// behind, creating a massive timestamp gap between frame
|
||||
// #1 and #2 that causes the transcoder to starve video
|
||||
// while filling the gap with audio.
|
||||
if( cachedFrame.has_value() && m_adjustedStartSRT != 0 )
|
||||
timeStamp = winrt::TimeSpan{ m_adjustedStartSRT };
|
||||
else
|
||||
timeStamp = frame->SystemRelativeTime;
|
||||
auto contentSize = frame->ContentSize;
|
||||
auto frameTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame->FrameTexture);
|
||||
D3D11_TEXTURE2D_DESC desc = {};
|
||||
@@ -1448,7 +1529,7 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
|
||||
m_d3dContext->CopyResource( m_cachedDesktopTex.get(), backBuffer.get() );
|
||||
}
|
||||
|
||||
// Log first 10 video frames with timing.
|
||||
// Log first 50 video frames with timing.
|
||||
if( !m_hasVideoSample.load() )
|
||||
{
|
||||
s_diagVideoCount = 0;
|
||||
@@ -1462,13 +1543,14 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
|
||||
m_hasQpcOrigin = true;
|
||||
}
|
||||
s_diagVideoCount++;
|
||||
if( s_diagVideoCount <= 10 )
|
||||
if( s_diagVideoCount <= 50 )
|
||||
{
|
||||
RecDiag( L"SampleReq VIDEO #%d: sysRelTime=%lld deltaFromStart=%.1fms repeat=%d\n",
|
||||
RecDiag( L"SampleReq VIDEO #%d: sysRelTime=%lld deltaFromStart=%.1fms repeat=%d cached=%d\n",
|
||||
s_diagVideoCount,
|
||||
timeStamp.count(),
|
||||
( timeStamp.count() - s_diagStartTs ) / 10000.0,
|
||||
isRepeatFrame ? 1 : 0 );
|
||||
isRepeatFrame ? 1 : 0,
|
||||
( s_diagVideoCount == 1 && cachedFrame.has_value() ) ? 1 : 0 );
|
||||
}
|
||||
|
||||
#if _DEBUG
|
||||
@@ -1492,6 +1574,11 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
|
||||
// Composite webcam overlay onto the back buffer.
|
||||
if( m_webcamCapture )
|
||||
{
|
||||
if( s_diagVideoCount <= 3 )
|
||||
{
|
||||
RecDiag( L"SampleReq VIDEO #%d: compositing LIVE webcam frame\n",
|
||||
s_diagVideoCount );
|
||||
}
|
||||
m_webcamCapture->CompositeOnto( backBuffer.get() );
|
||||
}
|
||||
|
||||
@@ -1541,7 +1628,9 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
|
||||
}
|
||||
catch (winrt::hresult_error const& error)
|
||||
{
|
||||
OutputDebugStringW(error.message().c_str());
|
||||
RecDiag( L"SampleReq VIDEO EXCEPTION on frame #%d: hr=0x%08X %s\n",
|
||||
s_diagVideoCount, static_cast<unsigned>(error.code()),
|
||||
error.message().c_str() );
|
||||
request.Sample(nullptr);
|
||||
CloseInternal();
|
||||
return;
|
||||
@@ -1551,26 +1640,53 @@ void VideoRecordingSession::OnMediaStreamSourceSampleRequested(
|
||||
{
|
||||
try
|
||||
{
|
||||
static int s_audioReqCount = 0;
|
||||
if( !m_hasVideoSample.load() )
|
||||
s_audioReqCount = 0;
|
||||
s_audioReqCount++;
|
||||
|
||||
if( s_audioReqCount <= 5 )
|
||||
{
|
||||
RecDiag( L"SampleReq AUDIO req #%d: calling TryGetNextSample (started=%d)...\n",
|
||||
s_audioReqCount, m_audioGenerator ? 1 : 0 );
|
||||
}
|
||||
|
||||
LARGE_INTEGER tBefore, tAfter, tFreq;
|
||||
QueryPerformanceFrequency( &tFreq );
|
||||
QueryPerformanceCounter( &tBefore );
|
||||
|
||||
if (auto sample = m_audioGenerator ? m_audioGenerator->TryGetNextSample() : std::optional<winrt::MediaStreamSample>{}; sample.has_value())
|
||||
{
|
||||
QueryPerformanceCounter( &tAfter );
|
||||
double waitMs = static_cast<double>( tAfter.QuadPart - tBefore.QuadPart ) * 1000.0 / tFreq.QuadPart;
|
||||
|
||||
s_diagAudioCount++;
|
||||
if( s_diagAudioCount <= 10 )
|
||||
if( s_diagAudioCount <= 50 )
|
||||
{
|
||||
RecDiag( L"SampleReq AUDIO #%d: timestamp=%lld (%.1fms)\n",
|
||||
s_diagAudioCount,
|
||||
sample.value().Timestamp().count(),
|
||||
sample.value().Timestamp().count() / 10000.0 );
|
||||
auto ts = sample.value().Timestamp().count();
|
||||
auto dur = sample.value().Duration().count();
|
||||
RecDiag( L"SampleReq AUDIO #%d (req %d): timestamp=%lld (%.1fms) duration=%lld (%.1fms) waitMs=%.1f\n",
|
||||
s_diagAudioCount, s_audioReqCount,
|
||||
ts, ts / 10000.0,
|
||||
dur, dur / 10000.0,
|
||||
waitMs );
|
||||
}
|
||||
request.Sample(sample.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
QueryPerformanceCounter( &tAfter );
|
||||
double waitMs = static_cast<double>( tAfter.QuadPart - tBefore.QuadPart ) * 1000.0 / tFreq.QuadPart;
|
||||
RecDiag( L"SampleReq AUDIO req #%d: TryGetNextSample returned EMPTY after %.1fms → end-of-audio-stream\n",
|
||||
s_audioReqCount, waitMs );
|
||||
request.Sample(nullptr);
|
||||
}
|
||||
}
|
||||
catch (winrt::hresult_error const& error)
|
||||
{
|
||||
OutputDebugStringW(error.message().c_str());
|
||||
RecDiag( L"SampleReq AUDIO EXCEPTION on sample #%d: hr=0x%08X %s\n",
|
||||
s_diagAudioCount, static_cast<unsigned>(error.code()),
|
||||
error.message().c_str() );
|
||||
request.Sample(nullptr);
|
||||
CloseInternal();
|
||||
return;
|
||||
@@ -1659,7 +1775,7 @@ public:
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return S_OK; }
|
||||
IFACEMETHODIMP OnSelectionChange(IFileDialog*) { return S_OK; }
|
||||
IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) { return S_OK; }
|
||||
@@ -2838,7 +2954,7 @@ static void HandlePlaybackCommand(int controlId, VideoRecordingSession::TrimDial
|
||||
case IDC_TRIM_REWIND:
|
||||
{
|
||||
StopPlayback(hDlg, pData, false);
|
||||
// Use 1 second step for timelines < 20 seconds, 2 seconds
|
||||
// Use 1 second step for timelines < 20 seconds, 2 seconds
|
||||
const int64_t duration = pData->trimEnd.count() - pData->trimStart.count();
|
||||
const int64_t stepTicks = (duration < 200'000'000) ? 10'000'000 : kJogStepTicks;
|
||||
const int64_t newTicks = (std::max)(pData->trimStart.count(), pData->currentPosition.count() - stepTicks);
|
||||
@@ -2854,7 +2970,7 @@ static void HandlePlaybackCommand(int controlId, VideoRecordingSession::TrimDial
|
||||
case IDC_TRIM_FORWARD:
|
||||
{
|
||||
StopPlayback(hDlg, pData, false);
|
||||
// Use 1 second step for timelines < 20 seconds, 2 seconds
|
||||
// Use 1 second step for timelines < 20 seconds, 2 seconds
|
||||
const int64_t duration = pData->trimEnd.count() - pData->trimStart.count();
|
||||
const int64_t stepTicks = (duration < 200'000'000) ? 10'000'000 : kJogStepTicks;
|
||||
const int64_t newTicks = (std::min)(pData->trimEnd.count(), pData->currentPosition.count() + stepTicks);
|
||||
@@ -5517,7 +5633,7 @@ INT_PTR CALLBACK VideoRecordingSession::TrimDialogProc(HWND hDlg, UINT message,
|
||||
const auto relativePos = winrt::TimeSpan{ (std::max)(pData->currentPosition.count() - pData->trimStart.count(), int64_t{ 0 }) };
|
||||
SetTimeText(hDlg, IDC_TRIM_POSITION_LABEL, relativePos, true);
|
||||
}
|
||||
|
||||
|
||||
if (elapsedMs >= frameDurationMs)
|
||||
{
|
||||
// Time to advance to next frame
|
||||
|
||||
@@ -27,9 +27,8 @@ public:
|
||||
winrt::GraphicsCaptureItem const& item,
|
||||
RECT const& cropRect,
|
||||
uint32_t frameRate,
|
||||
bool captureAudio,
|
||||
bool captureSystemAudio,
|
||||
bool micMonoMix,
|
||||
std::unique_ptr<AudioSampleGenerator> audioGenerator,
|
||||
winrt::Windows::Foundation::IAsyncAction audioInitAction,
|
||||
winrt::Streams::IRandomAccessStream const& stream);
|
||||
~VideoRecordingSession();
|
||||
|
||||
@@ -217,9 +216,8 @@ private:
|
||||
winrt::Capture::GraphicsCaptureItem const& item,
|
||||
RECT const cropRect,
|
||||
uint32_t frameRate,
|
||||
bool captureAudio,
|
||||
bool captureSystemAudio,
|
||||
bool micMonoMix,
|
||||
std::unique_ptr<AudioSampleGenerator> audioGenerator,
|
||||
winrt::Windows::Foundation::IAsyncAction audioInitAction,
|
||||
winrt::Streams::IRandomAccessStream const& stream);
|
||||
void CloseInternal();
|
||||
|
||||
@@ -279,5 +277,9 @@ private:
|
||||
LARGE_INTEGER m_qpcFreq{};
|
||||
LARGE_INTEGER m_qpcRecordingStart{}; // QPC at first sample
|
||||
int64_t m_startSystemRelativeTime = 0; // SystemRelativeTime of first frame
|
||||
int64_t m_adjustedStartSRT = 0; // QPC-based current SRT set in OnStarting
|
||||
bool m_hasQpcOrigin = false;
|
||||
|
||||
// Audio initialization started in the constructor, awaited in StartAsync.
|
||||
winrt::Windows::Foundation::IAsyncAction m_audioInitAction{ nullptr };
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,11 +18,37 @@
|
||||
#include <mfreadwrite.h>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include "BackgroundBlur.h"
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <winrt/base.h>
|
||||
|
||||
class BackgroundBlur;
|
||||
|
||||
// Must match CompositeConstants cbuffer layout in WebcamComposite.hlsl.
|
||||
struct GpuCompositeConstants
|
||||
{
|
||||
float CropOffsetX, CropOffsetY; // Camera crop UV offset
|
||||
float CropScaleX, CropScaleY; // Camera crop UV scale
|
||||
float Gamma; // Gamma correction exponent
|
||||
float CornerRadius; // Corner radius in output pixels
|
||||
float OutputW, OutputH; // Output dimensions
|
||||
UINT ShapeType; // 0=Square, 1=RoundedRect, 2=RoundedSquare, 3=Circle
|
||||
UINT HasMask; // 1 if mask texture valid
|
||||
float Pad[2];
|
||||
};
|
||||
|
||||
// Must match BlurConstants cbuffer layout in BoxBlurCS.hlsl.
|
||||
struct GpuBlurConstants
|
||||
{
|
||||
UINT Direction; // 0 = horizontal, 1 = vertical
|
||||
INT Radius; // Box blur radius in pixels
|
||||
UINT Width; // Image width
|
||||
UINT Height; // Image height
|
||||
};
|
||||
|
||||
class WebcamCapture
|
||||
{
|
||||
public:
|
||||
@@ -44,7 +70,10 @@ public:
|
||||
Position position,
|
||||
Size size,
|
||||
Shape shape,
|
||||
bool fullScreenRecording = false );
|
||||
bool fullScreenRecording = false,
|
||||
WebcamBackgroundMode backgroundMode = WebcamBackgroundMode::None,
|
||||
const wchar_t* backgroundImagePath = nullptr,
|
||||
int brightness = 50 );
|
||||
~WebcamCapture();
|
||||
|
||||
// Start/stop the capture thread.
|
||||
@@ -106,6 +135,19 @@ private:
|
||||
bool InitSourceReader();
|
||||
RECT ComputeDestRect() const;
|
||||
void ComputeOverlayDimensions();
|
||||
bool InitGpuComposite();
|
||||
bool GpuComposite( const UINT32* cameraPixels, UINT camW, UINT camH,
|
||||
const UINT32* blurPixels, UINT blurW, UINT blurH,
|
||||
const float* mask, UINT maskW, UINT maskH,
|
||||
UINT outW, UINT outH,
|
||||
UINT srcCropX, UINT srcCropY, UINT srcCropW, UINT srcCropH,
|
||||
float gamma, Shape shape, float cornerRadius,
|
||||
ID3D11ShaderResourceView* preBlurSRV = nullptr );
|
||||
|
||||
// GPU box blur: runs 4 compute-shader dispatches (H→V→H→V) on the
|
||||
// processing-resolution frame. The result stays GPU-resident in
|
||||
// m_blurPingPong[0] for direct use by GpuComposite.
|
||||
bool GpuBoxBlur( const UINT32* pixels, UINT width, UINT height, int radius );
|
||||
|
||||
winrt::com_ptr<ID3D11Device> m_d3dDevice;
|
||||
winrt::com_ptr<ID3D11DeviceContext> m_d3dContext;
|
||||
@@ -135,6 +177,7 @@ private:
|
||||
// Reusable frame buffer for the capture thread (avoids per-frame alloc).
|
||||
std::vector<BYTE> m_framePixels;
|
||||
std::vector<BYTE> m_scaledPixels;
|
||||
std::vector<BYTE> m_upscalePixels;
|
||||
|
||||
UINT m_overlayW = 0;
|
||||
UINT m_overlayH = 0;
|
||||
@@ -142,6 +185,11 @@ private:
|
||||
UINT m_camHeight = 0;
|
||||
RECT m_destRect = {};
|
||||
|
||||
// Brightness correction (user-controlled, fixed gamma LUT).
|
||||
int m_brightness = 50; // 0=dark, 50=neutral, 100=bright
|
||||
std::array<uint8_t, 256> m_gammaLUT = {}; // current LUT
|
||||
double m_lutGamma = 1.0; // gamma used for m_gammaLUT
|
||||
|
||||
// Output dimensions (recording output after crop+scale).
|
||||
UINT m_outputWidth = 0;
|
||||
UINT m_outputHeight = 0;
|
||||
@@ -163,8 +211,57 @@ private:
|
||||
std::condition_variable m_readyCV;
|
||||
bool m_firstFrameCaptured = false;
|
||||
|
||||
// Background processing.
|
||||
WebcamBackgroundMode m_backgroundMode = WebcamBackgroundMode::None;
|
||||
std::wstring m_backgroundImagePath;
|
||||
std::unique_ptr<BackgroundBlur> m_backgroundBlur;
|
||||
|
||||
// Debug counters for CompositeOnto logging.
|
||||
int m_compositeCount = 0;
|
||||
int m_lockFailCount = 0;
|
||||
int m_uploadCount = 0;
|
||||
|
||||
// ── GPU composite pipeline ──────────────────────────────
|
||||
// Separate D3D device for capture thread (avoids contention
|
||||
// with the recording session's device/context).
|
||||
winrt::com_ptr<ID3D11Device> m_gpuDevice;
|
||||
winrt::com_ptr<ID3D11DeviceContext> m_gpuContext;
|
||||
winrt::com_ptr<ID3D11VertexShader> m_compositeVS;
|
||||
winrt::com_ptr<ID3D11PixelShader> m_compositePS;
|
||||
winrt::com_ptr<ID3D11Buffer> m_compositeCB;
|
||||
winrt::com_ptr<ID3D11SamplerState> m_bilinearSampler;
|
||||
winrt::com_ptr<ID3D11RasterizerState> m_gpuRasterState;
|
||||
winrt::com_ptr<ID3D11BlendState> m_gpuBlendState;
|
||||
|
||||
// Input textures + SRVs (recreated when dimensions change).
|
||||
winrt::com_ptr<ID3D11Texture2D> m_gpuCameraTex;
|
||||
winrt::com_ptr<ID3D11ShaderResourceView> m_gpuCameraSRV;
|
||||
UINT m_gpuCameraW = 0, m_gpuCameraH = 0;
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> m_gpuBlurTex;
|
||||
winrt::com_ptr<ID3D11ShaderResourceView> m_gpuBlurSRV;
|
||||
UINT m_gpuBlurW = 0, m_gpuBlurH = 0;
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> m_gpuMaskTex;
|
||||
winrt::com_ptr<ID3D11ShaderResourceView> m_gpuMaskSRV;
|
||||
UINT m_gpuMaskW = 0, m_gpuMaskH = 0;
|
||||
|
||||
// Render target + staging for readback.
|
||||
winrt::com_ptr<ID3D11Texture2D> m_gpuRenderTarget;
|
||||
winrt::com_ptr<ID3D11RenderTargetView> m_gpuRTV;
|
||||
winrt::com_ptr<ID3D11Texture2D> m_gpuStaging;
|
||||
UINT m_gpuRTW = 0, m_gpuRTH = 0;
|
||||
|
||||
bool m_gpuCompositeReady = false;
|
||||
|
||||
// ── GPU box-blur compute pipeline ───────────────────────
|
||||
winrt::com_ptr<ID3D11ComputeShader> m_blurCS;
|
||||
winrt::com_ptr<ID3D11Buffer> m_blurCB;
|
||||
|
||||
// Ping-pong textures with SRV + UAV for blur passes.
|
||||
winrt::com_ptr<ID3D11Texture2D> m_blurPingPong[2];
|
||||
winrt::com_ptr<ID3D11ShaderResourceView> m_blurPingSRV[2];
|
||||
winrt::com_ptr<ID3D11UnorderedAccessView> m_blurPingUAV[2];
|
||||
UINT m_blurPPW = 0, m_blurPPH = 0;
|
||||
bool m_gpuBlurReady = false;
|
||||
};
|
||||
|
||||
143
src/modules/ZoomIt/ZoomIt/WebcamComposite.hlsl
Normal file
143
src/modules/ZoomIt/ZoomIt/WebcamComposite.hlsl
Normal file
@@ -0,0 +1,143 @@
|
||||
//==============================================================================
|
||||
//
|
||||
// WebcamComposite.hlsl
|
||||
//
|
||||
// GPU composite shader for webcam overlay.
|
||||
// Composites sharp foreground from full-resolution camera with
|
||||
// blurred background from processing-resolution blur buffer, using
|
||||
// a segmentation mask to blend between the two sources.
|
||||
//
|
||||
// The GPU's hardware texture sampler provides free bilinear filtering,
|
||||
// making this orders of magnitude faster than the equivalent CPU loop.
|
||||
//
|
||||
// Entry points:
|
||||
// VSMain (vs_5_0) — full-screen triangle, no vertex buffer needed
|
||||
// PSMain (ps_5_0) — composite camera + blur + mask + shape mask
|
||||
//
|
||||
// Recompile with:
|
||||
// fxc /T vs_5_0 /E VSMain /Fh WebcamCompositeVS.h /Vn g_WebcamCompositeVS WebcamComposite.hlsl
|
||||
// fxc /T ps_5_0 /E PSMain /Fh WebcamCompositePS.h /Vn g_WebcamCompositePS WebcamComposite.hlsl
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
//==============================================================================
|
||||
|
||||
// Camera frame at full resolution (e.g. 1920x1080), B8G8R8A8_UNORM.
|
||||
// Shader sees RGBA due to hardware swizzle.
|
||||
Texture2D CameraTex : register(t0);
|
||||
|
||||
// Blurred processing buffer at reduced resolution (e.g. 960x540), B8G8R8A8_UNORM.
|
||||
// Already gamma-corrected from CPU downsample.
|
||||
Texture2D BlurTex : register(t1);
|
||||
|
||||
// Segmentation mask from ONNX model, R32_FLOAT.
|
||||
// 1.0 = foreground (person), 0.0 = background.
|
||||
Texture2D MaskTex : register(t2);
|
||||
|
||||
// Bilinear sampler with clamp addressing — used for all textures.
|
||||
SamplerState BilinearSamp : register(s0);
|
||||
|
||||
cbuffer CompositeConstants : register(b0)
|
||||
{
|
||||
float2 CropOffset; // Camera crop UV offset (srcCropX/camW, srcCropY/camH)
|
||||
float2 CropScale; // Camera crop UV scale (srcCropW/camW, srcCropH/camH)
|
||||
float Gamma; // Gamma correction exponent (< 1 brightens)
|
||||
float CornerRadius; // Corner radius in output pixels
|
||||
float OutputW; // Output width in pixels
|
||||
float OutputH; // Output height in pixels
|
||||
uint ShapeType; // 0=Square, 1=RoundedRect, 2=RoundedSquare, 3=Circle
|
||||
uint HasMask; // 1 if segmentation mask is valid
|
||||
float2 Pad;
|
||||
};
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
float4 Position : SV_POSITION;
|
||||
float2 TexCoord : TEXCOORD0;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Vertex shader: full-screen triangle from SV_VertexID (no vertex buffer).
|
||||
// Draw(3, 0) to invoke. The triangle covers [-1,1] clip space.
|
||||
//----------------------------------------------------------------------------
|
||||
VSOutput VSMain( uint vertexId : SV_VertexID )
|
||||
{
|
||||
VSOutput output;
|
||||
output.TexCoord = float2( (vertexId << 1) & 2, vertexId & 2 );
|
||||
output.Position = float4( output.TexCoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0 );
|
||||
return output;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Pixel shader: composite camera foreground with blurred background.
|
||||
// - Shape masking (circle, rounded rect) produces alpha = 0 outside.
|
||||
// - Segmentation mask blends camera (foreground) with blur (background).
|
||||
// - Gamma correction applied to camera samples only (blur already corrected).
|
||||
// - Hardware bilinear filtering on all texture samples (free).
|
||||
//----------------------------------------------------------------------------
|
||||
float4 PSMain( VSOutput input ) : SV_TARGET
|
||||
{
|
||||
float2 uv = input.TexCoord;
|
||||
float px = uv.x * OutputW;
|
||||
float py = uv.y * OutputH;
|
||||
|
||||
// ── Shape mask ─────────────────────────────────────────────────────
|
||||
if( ShapeType == 3 ) // Circle
|
||||
{
|
||||
float halfW = OutputW * 0.5;
|
||||
float halfH = OutputH * 0.5;
|
||||
float radius = min( halfW, halfH );
|
||||
float dx = ( px - halfW ) / radius;
|
||||
float dy = ( py - halfH ) / radius;
|
||||
if( dx * dx + dy * dy > 1.0 )
|
||||
return float4( 0, 0, 0, 0 );
|
||||
}
|
||||
else if( ShapeType >= 1 ) // RoundedRect or RoundedSquare
|
||||
{
|
||||
float cx = 0, cy = 0;
|
||||
bool inCorner = false;
|
||||
|
||||
if( px < CornerRadius && py < CornerRadius )
|
||||
{ cx = CornerRadius; cy = CornerRadius; inCorner = true; }
|
||||
else if( px > OutputW - CornerRadius && py < CornerRadius )
|
||||
{ cx = OutputW - CornerRadius; cy = CornerRadius; inCorner = true; }
|
||||
else if( px < CornerRadius && py > OutputH - CornerRadius )
|
||||
{ cx = CornerRadius; cy = OutputH - CornerRadius; inCorner = true; }
|
||||
else if( px > OutputW - CornerRadius && py > OutputH - CornerRadius )
|
||||
{ cx = OutputW - CornerRadius; cy = OutputH - CornerRadius; inCorner = true; }
|
||||
|
||||
if( inCorner )
|
||||
{
|
||||
float ddx = px - cx;
|
||||
float ddy = py - cy;
|
||||
if( ddx * ddx + ddy * ddy > CornerRadius * CornerRadius )
|
||||
return float4( 0, 0, 0, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
// ── Composite ──────────────────────────────────────────────────────
|
||||
if( HasMask )
|
||||
{
|
||||
// Segmentation mask (bilinear-filtered for smooth edges).
|
||||
float mask = saturate( MaskTex.Sample( BilinearSamp, uv ).r );
|
||||
|
||||
// Camera: crop-to-fill UV mapping + gamma correction.
|
||||
float2 camUV = CropOffset + uv * CropScale;
|
||||
float4 cam = CameraTex.Sample( BilinearSamp, camUV );
|
||||
cam.rgb = pow( max( cam.rgb, 0.001 ), Gamma );
|
||||
|
||||
// Blur: already gamma-corrected from CPU downsample.
|
||||
float4 blur = BlurTex.Sample( BilinearSamp, uv );
|
||||
|
||||
// Blend: mask=1 → camera (foreground), mask=0 → blur (background).
|
||||
float3 result = lerp( blur.rgb, cam.rgb, mask );
|
||||
return float4( result, 1.0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
// No segmentation mask — just display the processing buffer.
|
||||
float4 blur = BlurTex.Sample( BilinearSamp, uv );
|
||||
return float4( blur.rgb, 1.0 );
|
||||
}
|
||||
}
|
||||
616
src/modules/ZoomIt/ZoomIt/WebcamCompositePS.h
Normal file
616
src/modules/ZoomIt/ZoomIt/WebcamCompositePS.h
Normal file
@@ -0,0 +1,616 @@
|
||||
#if 0
|
||||
//
|
||||
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
|
||||
//
|
||||
//
|
||||
// Buffer Definitions:
|
||||
//
|
||||
// cbuffer CompositeConstants
|
||||
// {
|
||||
//
|
||||
// float2 CropOffset; // Offset: 0 Size: 8
|
||||
// float2 CropScale; // Offset: 8 Size: 8
|
||||
// float Gamma; // Offset: 16 Size: 4
|
||||
// float CornerRadius; // Offset: 20 Size: 4
|
||||
// float OutputW; // Offset: 24 Size: 4
|
||||
// float OutputH; // Offset: 28 Size: 4
|
||||
// uint ShapeType; // Offset: 32 Size: 4
|
||||
// uint HasMask; // Offset: 36 Size: 4
|
||||
// float2 Pad; // Offset: 40 Size: 8 [unused]
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
// Resource Bindings:
|
||||
//
|
||||
// Name Type Format Dim HLSL Bind Count
|
||||
// ------------------------------ ---------- ------- ----------- -------------- ------
|
||||
// BilinearSamp sampler NA NA s0 1
|
||||
// CameraTex texture float4 2d t0 1
|
||||
// BlurTex texture float4 2d t1 1
|
||||
// MaskTex texture float4 2d t2 1
|
||||
// CompositeConstants cbuffer NA NA cb0 1
|
||||
//
|
||||
//
|
||||
//
|
||||
// Input signature:
|
||||
//
|
||||
// Name Index Mask Register SysValue Format Used
|
||||
// -------------------- ----- ------ -------- -------- ------- ------
|
||||
// SV_POSITION 0 xyzw 0 POS float
|
||||
// TEXCOORD 0 xy 1 NONE float xy
|
||||
//
|
||||
//
|
||||
// Output signature:
|
||||
//
|
||||
// Name Index Mask Register SysValue Format Used
|
||||
// -------------------- ----- ------ -------- -------- ------- ------
|
||||
// SV_TARGET 0 xyzw 0 TARGET float xyzw
|
||||
//
|
||||
ps_5_0
|
||||
dcl_globalFlags refactoringAllowed
|
||||
dcl_constantbuffer CB0[3], immediateIndexed
|
||||
dcl_sampler s0, mode_default
|
||||
dcl_resource_texture2d (float,float,float,float) t0
|
||||
dcl_resource_texture2d (float,float,float,float) t1
|
||||
dcl_resource_texture2d (float,float,float,float) t2
|
||||
dcl_input_ps linear v1.xy
|
||||
dcl_output o0.xyzw
|
||||
dcl_temps 4
|
||||
mul r0.xy, v1.xyxx, cb0[1].zwzz
|
||||
ieq r0.z, cb0[2].x, l(3)
|
||||
if_nz r0.z
|
||||
mul r0.zw, cb0[1].zzzw, l(0.000000, 0.000000, 0.500000, 0.500000)
|
||||
min r1.x, r0.w, r0.z
|
||||
mad r0.zw, v1.xxxy, cb0[1].zzzw, -r0.zzzw
|
||||
div r0.zw, r0.zzzw, r1.xxxx
|
||||
mul r0.zw, r0.zzzw, r0.zzzw
|
||||
add r0.z, r0.w, r0.z
|
||||
lt r0.z, l(1.000000), r0.z
|
||||
if_nz r0.z
|
||||
mov o0.xyzw, l(0,0,0,0)
|
||||
ret
|
||||
endif
|
||||
else
|
||||
uge r0.z, cb0[2].x, l(1)
|
||||
if_nz r0.z
|
||||
lt r0.zw, r0.yyyx, cb0[1].yyyy
|
||||
and r1.x, r0.z, r0.w
|
||||
add r2.xy, -cb0[1].yyyy, cb0[1].zwzz
|
||||
lt r0.xy, r2.xyxx, r0.xyxx
|
||||
and r0.zw, r0.zzzw, r0.xxxy
|
||||
and r3.z, r0.y, r0.x
|
||||
and r3.xy, r2.xyxx, r3.zzzz
|
||||
mov r2.z, cb0[1].y
|
||||
mov r2.w, l(-1)
|
||||
movc r0.xyw, r0.wwww, r2.zyzw, r3.xyxz
|
||||
movc r0.xyz, r0.zzzz, r2.xzwx, r0.xywx
|
||||
movc r0.xyz, r1.xxxx, r2.zzwz, r0.xyzx
|
||||
if_nz r0.z
|
||||
mad r0.xy, v1.xyxx, cb0[1].zwzz, -r0.xyxx
|
||||
mul r0.xy, r0.xyxx, r0.xyxx
|
||||
add r0.x, r0.y, r0.x
|
||||
mul r0.y, cb0[1].y, cb0[1].y
|
||||
lt r0.x, r0.y, r0.x
|
||||
if_nz r0.x
|
||||
mov o0.xyzw, l(0,0,0,0)
|
||||
ret
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
if_nz cb0[2].y
|
||||
sample_indexable(texture2d)(float,float,float,float) r0.x, v1.xyxx, t2.xyzw, s0
|
||||
mov_sat r0.x, r0.x
|
||||
mad r0.yz, v1.xxyx, cb0[0].zzwz, cb0[0].xxyx
|
||||
sample_indexable(texture2d)(float,float,float,float) r0.yzw, r0.yzyy, t0.wxyz, s0
|
||||
max r0.yzw, r0.yyzw, l(0.000000, 0.001000, 0.001000, 0.001000)
|
||||
log r0.yzw, r0.yyzw
|
||||
mul r0.yzw, r0.yyzw, cb0[1].xxxx
|
||||
exp r0.yzw, r0.yyzw
|
||||
sample_indexable(texture2d)(float,float,float,float) r1.xyz, v1.xyxx, t1.xyzw, s0
|
||||
add r0.yzw, r0.yyzw, -r1.xxyz
|
||||
mad o0.xyz, r0.xxxx, r0.yzwy, r1.xyzx
|
||||
mov o0.w, l(1.000000)
|
||||
ret
|
||||
else
|
||||
sample_indexable(texture2d)(float,float,float,float) r0.xyz, v1.xyxx, t1.xyzw, s0
|
||||
mov o0.xyz, r0.xyzx
|
||||
mov o0.w, l(1.000000)
|
||||
ret
|
||||
endif
|
||||
ret
|
||||
// Approximately 63 instruction slots used
|
||||
#endif
|
||||
|
||||
const BYTE g_WebcamCompositePS[] =
|
||||
{
|
||||
68, 88, 66, 67, 78, 221,
|
||||
134, 205, 221, 100, 55, 97,
|
||||
108, 36, 219, 137, 244, 189,
|
||||
76, 224, 1, 0, 0, 0,
|
||||
108, 11, 0, 0, 5, 0,
|
||||
0, 0, 52, 0, 0, 0,
|
||||
208, 3, 0, 0, 40, 4,
|
||||
0, 0, 92, 4, 0, 0,
|
||||
208, 10, 0, 0, 82, 68,
|
||||
69, 70, 148, 3, 0, 0,
|
||||
1, 0, 0, 0, 24, 1,
|
||||
0, 0, 5, 0, 0, 0,
|
||||
60, 0, 0, 0, 0, 5,
|
||||
255, 255, 0, 1, 0, 0,
|
||||
108, 3, 0, 0, 82, 68,
|
||||
49, 49, 60, 0, 0, 0,
|
||||
24, 0, 0, 0, 32, 0,
|
||||
0, 0, 40, 0, 0, 0,
|
||||
36, 0, 0, 0, 12, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
220, 0, 0, 0, 3, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 1, 0,
|
||||
0, 0, 233, 0, 0, 0,
|
||||
2, 0, 0, 0, 5, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
13, 0, 0, 0, 243, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
5, 0, 0, 0, 4, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
1, 0, 0, 0, 1, 0,
|
||||
0, 0, 13, 0, 0, 0,
|
||||
251, 0, 0, 0, 2, 0,
|
||||
0, 0, 5, 0, 0, 0,
|
||||
4, 0, 0, 0, 255, 255,
|
||||
255, 255, 2, 0, 0, 0,
|
||||
1, 0, 0, 0, 13, 0,
|
||||
0, 0, 3, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
1, 0, 0, 0, 66, 105,
|
||||
108, 105, 110, 101, 97, 114,
|
||||
83, 97, 109, 112, 0, 67,
|
||||
97, 109, 101, 114, 97, 84,
|
||||
101, 120, 0, 66, 108, 117,
|
||||
114, 84, 101, 120, 0, 77,
|
||||
97, 115, 107, 84, 101, 120,
|
||||
0, 67, 111, 109, 112, 111,
|
||||
115, 105, 116, 101, 67, 111,
|
||||
110, 115, 116, 97, 110, 116,
|
||||
115, 0, 171, 171, 3, 1,
|
||||
0, 0, 9, 0, 0, 0,
|
||||
48, 1, 0, 0, 48, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 152, 2,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
8, 0, 0, 0, 2, 0,
|
||||
0, 0, 172, 2, 0, 0,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 208, 2, 0, 0,
|
||||
8, 0, 0, 0, 8, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
172, 2, 0, 0, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
218, 2, 0, 0, 16, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
2, 0, 0, 0, 232, 2,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 12, 3,
|
||||
0, 0, 20, 0, 0, 0,
|
||||
4, 0, 0, 0, 2, 0,
|
||||
0, 0, 232, 2, 0, 0,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 25, 3, 0, 0,
|
||||
24, 0, 0, 0, 4, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
232, 2, 0, 0, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
33, 3, 0, 0, 28, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
2, 0, 0, 0, 232, 2,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 41, 3,
|
||||
0, 0, 32, 0, 0, 0,
|
||||
4, 0, 0, 0, 2, 0,
|
||||
0, 0, 60, 3, 0, 0,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 96, 3, 0, 0,
|
||||
36, 0, 0, 0, 4, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
60, 3, 0, 0, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
104, 3, 0, 0, 40, 0,
|
||||
0, 0, 8, 0, 0, 0,
|
||||
0, 0, 0, 0, 172, 2,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 67, 114,
|
||||
111, 112, 79, 102, 102, 115,
|
||||
101, 116, 0, 102, 108, 111,
|
||||
97, 116, 50, 0, 171, 171,
|
||||
1, 0, 3, 0, 1, 0,
|
||||
2, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 163, 2, 0, 0,
|
||||
67, 114, 111, 112, 83, 99,
|
||||
97, 108, 101, 0, 71, 97,
|
||||
109, 109, 97, 0, 102, 108,
|
||||
111, 97, 116, 0, 171, 171,
|
||||
0, 0, 3, 0, 1, 0,
|
||||
1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 224, 2, 0, 0,
|
||||
67, 111, 114, 110, 101, 114,
|
||||
82, 97, 100, 105, 117, 115,
|
||||
0, 79, 117, 116, 112, 117,
|
||||
116, 87, 0, 79, 117, 116,
|
||||
112, 117, 116, 72, 0, 83,
|
||||
104, 97, 112, 101, 84, 121,
|
||||
112, 101, 0, 100, 119, 111,
|
||||
114, 100, 0, 171, 171, 171,
|
||||
0, 0, 19, 0, 1, 0,
|
||||
1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 51, 3, 0, 0,
|
||||
72, 97, 115, 77, 97, 115,
|
||||
107, 0, 80, 97, 100, 0,
|
||||
77, 105, 99, 114, 111, 115,
|
||||
111, 102, 116, 32, 40, 82,
|
||||
41, 32, 72, 76, 83, 76,
|
||||
32, 83, 104, 97, 100, 101,
|
||||
114, 32, 67, 111, 109, 112,
|
||||
105, 108, 101, 114, 32, 49,
|
||||
48, 46, 49, 0, 73, 83,
|
||||
71, 78, 80, 0, 0, 0,
|
||||
2, 0, 0, 0, 8, 0,
|
||||
0, 0, 56, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 0,
|
||||
0, 0, 3, 0, 0, 0,
|
||||
0, 0, 0, 0, 15, 0,
|
||||
0, 0, 68, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 3, 0, 0, 0,
|
||||
1, 0, 0, 0, 3, 3,
|
||||
0, 0, 83, 86, 95, 80,
|
||||
79, 83, 73, 84, 73, 79,
|
||||
78, 0, 84, 69, 88, 67,
|
||||
79, 79, 82, 68, 0, 171,
|
||||
171, 171, 79, 83, 71, 78,
|
||||
44, 0, 0, 0, 1, 0,
|
||||
0, 0, 8, 0, 0, 0,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
3, 0, 0, 0, 0, 0,
|
||||
0, 0, 15, 0, 0, 0,
|
||||
83, 86, 95, 84, 65, 82,
|
||||
71, 69, 84, 0, 171, 171,
|
||||
83, 72, 69, 88, 108, 6,
|
||||
0, 0, 80, 0, 0, 0,
|
||||
155, 1, 0, 0, 106, 8,
|
||||
0, 1, 89, 0, 0, 4,
|
||||
70, 142, 32, 0, 0, 0,
|
||||
0, 0, 3, 0, 0, 0,
|
||||
90, 0, 0, 3, 0, 96,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
88, 24, 0, 4, 0, 112,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
85, 85, 0, 0, 88, 24,
|
||||
0, 4, 0, 112, 16, 0,
|
||||
1, 0, 0, 0, 85, 85,
|
||||
0, 0, 88, 24, 0, 4,
|
||||
0, 112, 16, 0, 2, 0,
|
||||
0, 0, 85, 85, 0, 0,
|
||||
98, 16, 0, 3, 50, 16,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
101, 0, 0, 3, 242, 32,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
104, 0, 0, 2, 4, 0,
|
||||
0, 0, 56, 0, 0, 8,
|
||||
50, 0, 16, 0, 0, 0,
|
||||
0, 0, 70, 16, 16, 0,
|
||||
1, 0, 0, 0, 230, 138,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 32, 0,
|
||||
0, 8, 66, 0, 16, 0,
|
||||
0, 0, 0, 0, 10, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
2, 0, 0, 0, 1, 64,
|
||||
0, 0, 3, 0, 0, 0,
|
||||
31, 0, 4, 3, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
56, 0, 0, 11, 194, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
166, 142, 32, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
2, 64, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 63, 0, 0,
|
||||
0, 63, 51, 0, 0, 7,
|
||||
18, 0, 16, 0, 1, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
50, 0, 0, 11, 194, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
6, 20, 16, 0, 1, 0,
|
||||
0, 0, 166, 142, 32, 0,
|
||||
0, 0, 0, 0, 1, 0,
|
||||
0, 0, 166, 14, 16, 128,
|
||||
65, 0, 0, 0, 0, 0,
|
||||
0, 0, 14, 0, 0, 7,
|
||||
194, 0, 16, 0, 0, 0,
|
||||
0, 0, 166, 14, 16, 0,
|
||||
0, 0, 0, 0, 6, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
56, 0, 0, 7, 194, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
166, 14, 16, 0, 0, 0,
|
||||
0, 0, 166, 14, 16, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 7, 66, 0, 16, 0,
|
||||
0, 0, 0, 0, 58, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
42, 0, 16, 0, 0, 0,
|
||||
0, 0, 49, 0, 0, 7,
|
||||
66, 0, 16, 0, 0, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
0, 0, 128, 63, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
31, 0, 4, 3, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
54, 0, 0, 8, 242, 32,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
2, 64, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 62, 0, 0, 1,
|
||||
21, 0, 0, 1, 18, 0,
|
||||
0, 1, 80, 0, 0, 8,
|
||||
66, 0, 16, 0, 0, 0,
|
||||
0, 0, 10, 128, 32, 0,
|
||||
0, 0, 0, 0, 2, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
1, 0, 0, 0, 31, 0,
|
||||
4, 3, 42, 0, 16, 0,
|
||||
0, 0, 0, 0, 49, 0,
|
||||
0, 8, 194, 0, 16, 0,
|
||||
0, 0, 0, 0, 86, 1,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
86, 133, 32, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
1, 0, 0, 7, 18, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
42, 0, 16, 0, 0, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 10, 50, 0, 16, 0,
|
||||
2, 0, 0, 0, 86, 133,
|
||||
32, 128, 65, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 0,
|
||||
0, 0, 230, 138, 32, 0,
|
||||
0, 0, 0, 0, 1, 0,
|
||||
0, 0, 49, 0, 0, 7,
|
||||
50, 0, 16, 0, 0, 0,
|
||||
0, 0, 70, 0, 16, 0,
|
||||
2, 0, 0, 0, 70, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 7, 194, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
166, 14, 16, 0, 0, 0,
|
||||
0, 0, 6, 4, 16, 0,
|
||||
0, 0, 0, 0, 1, 0,
|
||||
0, 7, 66, 0, 16, 0,
|
||||
3, 0, 0, 0, 26, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
10, 0, 16, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 7,
|
||||
50, 0, 16, 0, 3, 0,
|
||||
0, 0, 70, 0, 16, 0,
|
||||
2, 0, 0, 0, 166, 10,
|
||||
16, 0, 3, 0, 0, 0,
|
||||
54, 0, 0, 6, 66, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
26, 128, 32, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
54, 0, 0, 5, 130, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
1, 64, 0, 0, 255, 255,
|
||||
255, 255, 55, 0, 0, 9,
|
||||
178, 0, 16, 0, 0, 0,
|
||||
0, 0, 246, 15, 16, 0,
|
||||
0, 0, 0, 0, 102, 14,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
70, 8, 16, 0, 3, 0,
|
||||
0, 0, 55, 0, 0, 9,
|
||||
114, 0, 16, 0, 0, 0,
|
||||
0, 0, 166, 10, 16, 0,
|
||||
0, 0, 0, 0, 134, 3,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
70, 3, 16, 0, 0, 0,
|
||||
0, 0, 55, 0, 0, 9,
|
||||
114, 0, 16, 0, 0, 0,
|
||||
0, 0, 6, 0, 16, 0,
|
||||
1, 0, 0, 0, 166, 11,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
70, 2, 16, 0, 0, 0,
|
||||
0, 0, 31, 0, 4, 3,
|
||||
42, 0, 16, 0, 0, 0,
|
||||
0, 0, 50, 0, 0, 11,
|
||||
50, 0, 16, 0, 0, 0,
|
||||
0, 0, 70, 16, 16, 0,
|
||||
1, 0, 0, 0, 230, 138,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 70, 0,
|
||||
16, 128, 65, 0, 0, 0,
|
||||
0, 0, 0, 0, 56, 0,
|
||||
0, 7, 50, 0, 16, 0,
|
||||
0, 0, 0, 0, 70, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
70, 0, 16, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 7,
|
||||
18, 0, 16, 0, 0, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
0, 0, 0, 0, 10, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
56, 0, 0, 9, 34, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
26, 128, 32, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
26, 128, 32, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
49, 0, 0, 7, 18, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
26, 0, 16, 0, 0, 0,
|
||||
0, 0, 10, 0, 16, 0,
|
||||
0, 0, 0, 0, 31, 0,
|
||||
4, 3, 10, 0, 16, 0,
|
||||
0, 0, 0, 0, 54, 0,
|
||||
0, 8, 242, 32, 16, 0,
|
||||
0, 0, 0, 0, 2, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
62, 0, 0, 1, 21, 0,
|
||||
0, 1, 21, 0, 0, 1,
|
||||
21, 0, 0, 1, 21, 0,
|
||||
0, 1, 31, 0, 4, 4,
|
||||
26, 128, 32, 0, 0, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
69, 0, 0, 139, 194, 0,
|
||||
0, 128, 67, 85, 21, 0,
|
||||
18, 0, 16, 0, 0, 0,
|
||||
0, 0, 70, 16, 16, 0,
|
||||
1, 0, 0, 0, 70, 126,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
0, 96, 16, 0, 0, 0,
|
||||
0, 0, 54, 32, 0, 5,
|
||||
18, 0, 16, 0, 0, 0,
|
||||
0, 0, 10, 0, 16, 0,
|
||||
0, 0, 0, 0, 50, 0,
|
||||
0, 11, 98, 0, 16, 0,
|
||||
0, 0, 0, 0, 6, 17,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
166, 139, 32, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
6, 129, 32, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
69, 0, 0, 139, 194, 0,
|
||||
0, 128, 67, 85, 21, 0,
|
||||
226, 0, 16, 0, 0, 0,
|
||||
0, 0, 150, 5, 16, 0,
|
||||
0, 0, 0, 0, 54, 121,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
0, 96, 16, 0, 0, 0,
|
||||
0, 0, 52, 0, 0, 10,
|
||||
226, 0, 16, 0, 0, 0,
|
||||
0, 0, 86, 14, 16, 0,
|
||||
0, 0, 0, 0, 2, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
111, 18, 131, 58, 111, 18,
|
||||
131, 58, 111, 18, 131, 58,
|
||||
47, 0, 0, 5, 226, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
86, 14, 16, 0, 0, 0,
|
||||
0, 0, 56, 0, 0, 8,
|
||||
226, 0, 16, 0, 0, 0,
|
||||
0, 0, 86, 14, 16, 0,
|
||||
0, 0, 0, 0, 6, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 25, 0,
|
||||
0, 5, 226, 0, 16, 0,
|
||||
0, 0, 0, 0, 86, 14,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
69, 0, 0, 139, 194, 0,
|
||||
0, 128, 67, 85, 21, 0,
|
||||
114, 0, 16, 0, 1, 0,
|
||||
0, 0, 70, 16, 16, 0,
|
||||
1, 0, 0, 0, 70, 126,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
0, 96, 16, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 8,
|
||||
226, 0, 16, 0, 0, 0,
|
||||
0, 0, 86, 14, 16, 0,
|
||||
0, 0, 0, 0, 6, 9,
|
||||
16, 128, 65, 0, 0, 0,
|
||||
1, 0, 0, 0, 50, 0,
|
||||
0, 9, 114, 32, 16, 0,
|
||||
0, 0, 0, 0, 6, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
150, 7, 16, 0, 0, 0,
|
||||
0, 0, 70, 2, 16, 0,
|
||||
1, 0, 0, 0, 54, 0,
|
||||
0, 5, 130, 32, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 0, 0, 128, 63,
|
||||
62, 0, 0, 1, 18, 0,
|
||||
0, 1, 69, 0, 0, 139,
|
||||
194, 0, 0, 128, 67, 85,
|
||||
21, 0, 114, 0, 16, 0,
|
||||
0, 0, 0, 0, 70, 16,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
70, 126, 16, 0, 1, 0,
|
||||
0, 0, 0, 96, 16, 0,
|
||||
0, 0, 0, 0, 54, 0,
|
||||
0, 5, 114, 32, 16, 0,
|
||||
0, 0, 0, 0, 70, 2,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
54, 0, 0, 5, 130, 32,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 0,
|
||||
128, 63, 62, 0, 0, 1,
|
||||
21, 0, 0, 1, 62, 0,
|
||||
0, 1, 83, 84, 65, 84,
|
||||
148, 0, 0, 0, 63, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
0, 0, 0, 0, 2, 0,
|
||||
0, 0, 23, 0, 0, 0,
|
||||
1, 0, 0, 0, 5, 0,
|
||||
0, 0, 7, 0, 0, 0,
|
||||
5, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
4, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 8, 0, 0, 0,
|
||||
3, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0
|
||||
};
|
||||
162
src/modules/ZoomIt/ZoomIt/WebcamCompositeVS.h
Normal file
162
src/modules/ZoomIt/ZoomIt/WebcamCompositeVS.h
Normal file
@@ -0,0 +1,162 @@
|
||||
#if 0
|
||||
//
|
||||
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
|
||||
//
|
||||
//
|
||||
//
|
||||
// Input signature:
|
||||
//
|
||||
// Name Index Mask Register SysValue Format Used
|
||||
// -------------------- ----- ------ -------- -------- ------- ------
|
||||
// SV_VertexID 0 x 0 VERTID uint x
|
||||
//
|
||||
//
|
||||
// Output signature:
|
||||
//
|
||||
// Name Index Mask Register SysValue Format Used
|
||||
// -------------------- ----- ------ -------- -------- ------- ------
|
||||
// SV_POSITION 0 xyzw 0 POS float xyzw
|
||||
// TEXCOORD 0 xy 1 NONE float xy
|
||||
//
|
||||
vs_5_0
|
||||
dcl_globalFlags refactoringAllowed
|
||||
dcl_input_sgv v0.x, vertex_id
|
||||
dcl_output_siv o0.xyzw, position
|
||||
dcl_output o1.xy
|
||||
dcl_temps 1
|
||||
bfi r0.x, l(1), l(1), v0.x, l(0)
|
||||
and r0.z, v0.x, l(2)
|
||||
utof r0.xy, r0.xzxx
|
||||
mad o0.xy, r0.xyxx, l(2.000000, -2.000000, 0.000000, 0.000000), l(-1.000000, 1.000000, 0.000000, 0.000000)
|
||||
mov o1.xy, r0.xyxx
|
||||
mov o0.zw, l(0,0,0,1.000000)
|
||||
ret
|
||||
// Approximately 7 instruction slots used
|
||||
#endif
|
||||
|
||||
const BYTE g_WebcamCompositeVS[] =
|
||||
{
|
||||
68, 88, 66, 67, 94, 195,
|
||||
253, 40, 165, 172, 45, 84,
|
||||
30, 136, 47, 40, 247, 112,
|
||||
58, 27, 1, 0, 0, 0,
|
||||
224, 2, 0, 0, 5, 0,
|
||||
0, 0, 52, 0, 0, 0,
|
||||
160, 0, 0, 0, 212, 0,
|
||||
0, 0, 44, 1, 0, 0,
|
||||
68, 2, 0, 0, 82, 68,
|
||||
69, 70, 100, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
60, 0, 0, 0, 0, 5,
|
||||
254, 255, 0, 1, 0, 0,
|
||||
60, 0, 0, 0, 82, 68,
|
||||
49, 49, 60, 0, 0, 0,
|
||||
24, 0, 0, 0, 32, 0,
|
||||
0, 0, 40, 0, 0, 0,
|
||||
36, 0, 0, 0, 12, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
77, 105, 99, 114, 111, 115,
|
||||
111, 102, 116, 32, 40, 82,
|
||||
41, 32, 72, 76, 83, 76,
|
||||
32, 83, 104, 97, 100, 101,
|
||||
114, 32, 67, 111, 109, 112,
|
||||
105, 108, 101, 114, 32, 49,
|
||||
48, 46, 49, 0, 73, 83,
|
||||
71, 78, 44, 0, 0, 0,
|
||||
1, 0, 0, 0, 8, 0,
|
||||
0, 0, 32, 0, 0, 0,
|
||||
0, 0, 0, 0, 6, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1,
|
||||
0, 0, 83, 86, 95, 86,
|
||||
101, 114, 116, 101, 120, 73,
|
||||
68, 0, 79, 83, 71, 78,
|
||||
80, 0, 0, 0, 2, 0,
|
||||
0, 0, 8, 0, 0, 0,
|
||||
56, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
3, 0, 0, 0, 0, 0,
|
||||
0, 0, 15, 0, 0, 0,
|
||||
68, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
3, 0, 0, 0, 1, 0,
|
||||
0, 0, 3, 12, 0, 0,
|
||||
83, 86, 95, 80, 79, 83,
|
||||
73, 84, 73, 79, 78, 0,
|
||||
84, 69, 88, 67, 79, 79,
|
||||
82, 68, 0, 171, 171, 171,
|
||||
83, 72, 69, 88, 16, 1,
|
||||
0, 0, 80, 0, 1, 0,
|
||||
68, 0, 0, 0, 106, 8,
|
||||
0, 1, 96, 0, 0, 4,
|
||||
18, 16, 16, 0, 0, 0,
|
||||
0, 0, 6, 0, 0, 0,
|
||||
103, 0, 0, 4, 242, 32,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 101, 0,
|
||||
0, 3, 50, 32, 16, 0,
|
||||
1, 0, 0, 0, 104, 0,
|
||||
0, 2, 1, 0, 0, 0,
|
||||
140, 0, 0, 11, 18, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 1, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
1, 0, 0, 0, 10, 16,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 7,
|
||||
66, 0, 16, 0, 0, 0,
|
||||
0, 0, 10, 16, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
86, 0, 0, 5, 50, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
134, 0, 16, 0, 0, 0,
|
||||
0, 0, 50, 0, 0, 15,
|
||||
50, 32, 16, 0, 0, 0,
|
||||
0, 0, 70, 0, 16, 0,
|
||||
0, 0, 0, 0, 2, 64,
|
||||
0, 0, 0, 0, 0, 64,
|
||||
0, 0, 0, 192, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
2, 64, 0, 0, 0, 0,
|
||||
128, 191, 0, 0, 128, 63,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 54, 0, 0, 5,
|
||||
50, 32, 16, 0, 1, 0,
|
||||
0, 0, 70, 0, 16, 0,
|
||||
0, 0, 0, 0, 54, 0,
|
||||
0, 8, 194, 32, 16, 0,
|
||||
0, 0, 0, 0, 2, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 128, 63,
|
||||
62, 0, 0, 1, 83, 84,
|
||||
65, 84, 148, 0, 0, 0,
|
||||
7, 0, 0, 0, 1, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
3, 0, 0, 0, 1, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 1, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 2, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0
|
||||
};
|
||||
@@ -121,7 +121,7 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
|
||||
BEGIN
|
||||
DEFPUSHBUTTON "OK",IDOK,184,308,50,14
|
||||
PUSHBUTTON "Cancel",IDCANCEL,241,308,50,14
|
||||
LTEXT "ZoomIt v12.0",IDC_VERSION,42,7,73,10
|
||||
LTEXT "ZoomIt v12.1",IDC_VERSION,42,7,73,10
|
||||
LTEXT "Copyright \251 2006-2026 Mark Russinovich",IDC_COPYRIGHT,42,17,251,8
|
||||
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
|
||||
"SysLink",WS_TABSTOP,42,26,150,9
|
||||
@@ -267,33 +267,52 @@ RECORD DIALOGEX 0, 0, 263, 228
|
||||
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
CONTROL "",IDC_RECORD_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,61,71,80,12
|
||||
LTEXT "Record Toggle:",IDC_STATIC,7,73,54,8
|
||||
CONTROL "",IDC_RECORD_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,65,59,80,12
|
||||
RTEXT "Record Toggle:",IDC_STATIC,7,62,54,8
|
||||
LTEXT "Record video of the unzoomed live screen or a static zoomed session by entering the recording hot key and finish the recording by entering it again. ",IDC_STATIC,7,7,248,22
|
||||
LTEXT "Scaling:",IDC_STATIC,30,90,26,8
|
||||
COMBOBOX IDC_RECORD_SCALING,61,89,26,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | WS_VSCROLL | WS_TABSTOP
|
||||
CONTROL "16:9:",IDC_RECORD_ASPECT_RATIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,93,90,30,10
|
||||
LTEXT "Format:",IDC_STATIC,30,108,26,8
|
||||
COMBOBOX IDC_RECORD_FORMAT,61,106,60,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Frame Rate:",IDC_STATIC,135,90,44,8,NOT WS_VISIBLE
|
||||
COMBOBOX IDC_RECORD_FRAME_RATE,182,89,42,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "To crop the portion of the screen that will be recorded, enter the hotkey with the Shift key in the opposite mode. ",IDC_STATIC,7,29,245,18
|
||||
LTEXT "To record a specific window, enter the hotkey with the Alt key in the opposite mode.",IDC_STATIC,7,48,251,19
|
||||
CONTROL "Capture &system audio",IDC_CAPTURE_SYSTEM_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,124,86,10
|
||||
CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,125,124,83,10
|
||||
COMBOBOX IDC_MICROPHONE,81,137,128,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Microphone:",IDC_MICROPHONE_LABEL,34,139,47,8
|
||||
RTEXT "Scaling:",IDC_STATIC,36,79,26,8
|
||||
COMBOBOX IDC_RECORD_SCALING,65,78,26,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | WS_VSCROLL | WS_TABSTOP
|
||||
CONTROL "16:9:",IDC_RECORD_ASPECT_RATIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,94,79,30,10
|
||||
RTEXT "Format:",IDC_STATIC,36,97,26,8
|
||||
COMBOBOX IDC_RECORD_FORMAT,65,96,60,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Frame Rate:",IDC_STATIC,134,79,44,8,NOT WS_VISIBLE
|
||||
COMBOBOX IDC_RECORD_FRAME_RATE,177,78,42,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "To crop the portion of the screen that will be recorded, enter the hotkey with the Shift key in the opposite mode. ",IDC_STATIC,7,7,245,18
|
||||
LTEXT "To record a specific window, enter the hotkey with the Alt key in the opposite mode.",IDC_STATIC,7,38,251,19
|
||||
CONTROL "Capture &system audio:",IDC_CAPTURE_SYSTEM_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,7,115,90,10
|
||||
CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,11,133,86,10
|
||||
CONTROL "Mono:",IDC_MIC_MONO_MIX,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,108,147,48,10
|
||||
COMBOBOX IDC_MICROPHONE,52,162,208,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Microphone:",IDC_MICROPHONE_LABEL,7,163,42,8
|
||||
CONTROL "&Noise cancellation:",IDC_NOISE_CANCELLATION,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | BS_RIGHT | WS_TABSTOP,23,147,74,10
|
||||
CONTROL "Show &webcam overlay (Ctrl+C toggles)",IDC_WEBCAM_OVERLAY,
|
||||
"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,153,148,10
|
||||
LTEXT "Camera:",IDC_WEBCAM_DEVICE_LABEL,46,167,28,8
|
||||
COMBOBOX IDC_WEBCAM_DEVICE,82,165,127,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Position:",IDC_WEBCAM_POSITION_LABEL,33,185,32,8
|
||||
COMBOBOX IDC_WEBCAM_POSITION,64,183,55,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Size:",IDC_WEBCAM_SIZE_LABEL,137,185,20,8
|
||||
COMBOBOX IDC_WEBCAM_SIZE,159,183,50,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Shape:",IDC_WEBCAM_SHAPE_LABEL,33,201,24,8
|
||||
COMBOBOX IDC_WEBCAM_SHAPE,64,199,80,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
PUSHBUTTON "&Trim",IDC_TRIM_FILE,207,207,53,14
|
||||
"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,181,148,10
|
||||
PUSHBUTTON "Webcam S&ettings...",IDC_WEBCAM_SETTINGS,192,180,68,14
|
||||
PUSHBUTTON "&Trim",IDC_TRIM_FILE,207,209,53,14
|
||||
END
|
||||
|
||||
WEBCAM_SETTINGS DIALOGEX 0, 0, 220, 163
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
CAPTION "Webcam Settings"
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
LTEXT "Camera:",IDC_WEBCAM_DEVICE_LABEL,14,10,28,8
|
||||
COMBOBOX IDC_WEBCAM_DEVICE,50,8,158,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Position:",IDC_WEBCAM_POSITION_LABEL,14,28,32,8
|
||||
COMBOBOX IDC_WEBCAM_POSITION,50,26,65,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Size:",IDC_WEBCAM_SIZE_LABEL,125,28,20,8
|
||||
COMBOBOX IDC_WEBCAM_SIZE,148,26,60,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Shape:",IDC_WEBCAM_SHAPE_LABEL,14,46,24,8
|
||||
COMBOBOX IDC_WEBCAM_SHAPE,50,44,80,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Background:",IDC_WEBCAM_BG_LABEL,14,64,44,8
|
||||
COMBOBOX IDC_WEBCAM_BG_MODE,60,62,55,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
EDITTEXT IDC_WEBCAM_BG_IMAGE,117,62,83,12,ES_AUTOHSCROLL | ES_READONLY
|
||||
PUSHBUTTON "...",IDC_WEBCAM_BG_BROWSE,202,62,14,12
|
||||
LTEXT "Brightness:",IDC_WEBCAM_BRIGHTNESS_LABEL,14,82,44,8
|
||||
CONTROL "",IDC_WEBCAM_BRIGHTNESS_SLIDER,"msctls_trackbar32",TBS_AUTOTICKS | TBS_NOTICKS | TBS_TOOLTIPS | WS_TABSTOP,60,80,148,15
|
||||
LTEXT "Uses MediaPipe SelfieSegmentation (Apache 2.0)",IDC_THIRDPARTY_NOTICES,14,102,200,8
|
||||
DEFPUSHBUTTON "OK",IDOK,108,142,50,14
|
||||
PUSHBUTTON "Cancel",IDCANCEL,162,142,50,14
|
||||
END
|
||||
|
||||
SNIP DIALOGEX 0, 0, 260, 80
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(InterPlatformDir)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<RandomizedBaseAddress>true</RandomizedBaseAddress>
|
||||
@@ -112,7 +112,7 @@
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(MSBuildThisFileDirectory)..\ZoomItBreak\$(Platform)\$(Configuration)\</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<RandomizedBaseAddress>true</RandomizedBaseAddress>
|
||||
@@ -135,7 +135,7 @@
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(MSBuildThisFileDirectory)..\ZoomItBreak\$(Platform)\$(Configuration)\</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<FixedBaseAddress>
|
||||
@@ -156,7 +156,7 @@
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(InterPlatformDir)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<RandomizedBaseAddress>false</RandomizedBaseAddress>
|
||||
@@ -177,7 +177,7 @@
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(MSBuildThisFileDirectory)..\ZoomItBreak\$(Platform)\$(Configuration)\</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<UACUIAccess>true</UACUIAccess>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@@ -199,7 +199,7 @@
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(MSBuildThisFileDirectory)..\ZoomItBreak\$(Platform)\$(Configuration)\</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<UACUIAccess>true</UACUIAccess>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@@ -257,6 +257,7 @@
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GifRecordingSession.cpp" />
|
||||
<ClCompile Include="NoiseSuppressor.cpp" />
|
||||
<ClCompile Include="PanoramaCapture.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Use</PrecompiledHeader>
|
||||
@@ -266,6 +267,128 @@
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp" />
|
||||
<ClCompile Include="rnnoise\celt_lpc.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">26451;4100;4091;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">26451;4100;4091;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">26451;4100;4091;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">26451;4100;4091;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26451;4100;4091;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26451;4100;4091;4245</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\denoise.c">
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">26451;4100;4091;4244;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">26451;4100;4091;4244;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">26451;4100;4091;4244;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">26451;4100;4091;4244;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26451;4100;4091;4244;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26451;4100;4091;4244;4245;4305</DisableSpecificWarnings>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\kiss_fft.c">
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\nnet.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\nnet_default.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\parse_lpcnet_weights.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\pitch.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26451;4100;4091;4244;4245</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\rnn.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\rnnoise_data_little.c">
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">26451;4100;4091;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">26451;4100;4091;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">26451;4100;4091;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">26451;4100;4091;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26451;4100;4091;4245;4305</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26451;4100;4091;4245;4305</DisableSpecificWarnings>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\rnnoise_tables.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SelectRectangle.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Use</PrecompiledHeader>
|
||||
@@ -314,6 +437,14 @@
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="BackgroundBlur.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WebcamPreviewWindow.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
||||
@@ -338,16 +469,21 @@
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\Eula\Eula.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\ZoomItModuleInterface\Trace.h" />
|
||||
<ClInclude Include="GifRecordingSession.h" />
|
||||
<ClInclude Include="NoiseSuppressor.h" />
|
||||
<ClInclude Include="PanoramaCapture.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="Registry.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="rnnoise\nnet_arch.h" />
|
||||
<ClInclude Include="rnnoise\rnnoise.h" />
|
||||
<ClInclude Include="rnnoise\vec.h" />
|
||||
<ClInclude Include="SelectRectangle.h" />
|
||||
<ClInclude Include="Utility.h" />
|
||||
<ClInclude Include="DemoType.h" />
|
||||
<ClInclude Include="VersionHelper.h" />
|
||||
<ClInclude Include="VideoRecordingSession.h" />
|
||||
<ClInclude Include="WebcamCapture.h" />
|
||||
<ClInclude Include="BackgroundBlur.h" />
|
||||
<ClInclude Include="WebcamPreviewWindow.h" />
|
||||
<ClInclude Include="ZoomIt.h" />
|
||||
<ClInclude Include="ZoomItSettings.h" />
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
<UniqueIdentifier>{e1fa606f-a2e6-40c8-8779-8ca1813d9f01}</UniqueIdentifier>
|
||||
<Extensions>ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\rnnoise">
|
||||
<UniqueIdentifier>{c1cc2820-c3a4-413d-b5d8-0d2034de2474}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\rnnoise">
|
||||
<UniqueIdentifier>{7795b908-e073-46ed-b209-c07ac0324adb}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Zoomit.cpp">
|
||||
@@ -66,6 +72,48 @@
|
||||
<ClCompile Include="..\ZoomItBreak\BreakTimer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="BackgroundBlur.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WebcamCapture.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WebcamPreviewWindow.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\celt_lpc.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\denoise.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\kiss_fft.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\nnet.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\nnet_default.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\parse_lpcnet_weights.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\pitch.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\rnn.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\rnnoise_data_little.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rnnoise\rnnoise_tables.c">
|
||||
<Filter>Source Files\rnnoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="NoiseSuppressor.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Registry.h">
|
||||
@@ -119,6 +167,27 @@
|
||||
<ClInclude Include="..\ZoomItBreak\BreakTimer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="BackgroundBlur.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WebcamCapture.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WebcamPreviewWindow.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rnnoise\vec.h">
|
||||
<Filter>Header Files\rnnoise</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rnnoise\nnet_arch.h">
|
||||
<Filter>Header Files\rnnoise</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NoiseSuppressor.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rnnoise\rnnoise.h">
|
||||
<Filter>Header Files\rnnoise</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="appicon.ico">
|
||||
@@ -150,5 +219,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natstepfilter" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -56,12 +56,16 @@ RecordingFormat g_RecordingFormat = RecordingFormat::MP4;
|
||||
BOOLEAN g_CaptureSystemAudio = TRUE;
|
||||
BOOLEAN g_CaptureAudio = FALSE;
|
||||
BOOLEAN g_MicMonoMix = FALSE;
|
||||
BOOLEAN g_NoiseCancellation = TRUE;
|
||||
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
|
||||
BOOLEAN g_WebcamOverlay = FALSE;
|
||||
DWORD g_WebcamPosition = 3; // 0=TL, 1=TR, 2=BL, 3=BR
|
||||
DWORD g_WebcamSize = 1; // 0=Small(15%), 1=Medium(25%), 2=Large(33%), 3=XLarge(50%)
|
||||
DWORD g_WebcamShape = 0; // 0=Square, 1=RoundedRect, 2=RoundedSquare, 3=Circle
|
||||
TCHAR g_WebcamDeviceSymLink[MAX_PATH] = {0};
|
||||
DWORD g_WebcamBackgroundMode = 0; // 0=None, 1=Blur, 2=Image
|
||||
TCHAR g_WebcamBackgroundImage[MAX_PATH] = {0};
|
||||
DWORD g_WebcamBrightness = 50; // 0=dark, 50=neutral, 100=bright
|
||||
BOOLEAN g_RecordAspectRatio = FALSE; // Lock region selection to 16:9
|
||||
TCHAR g_RecordingSaveLocationBuffer[MAX_PATH] = {0};
|
||||
TCHAR g_ScreenshotSaveLocationBuffer[MAX_PATH] = {0};
|
||||
@@ -116,11 +120,15 @@ REG_SETTING RegSettings[] = {
|
||||
{ L"CaptureSystemAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureSystemAudio, static_cast<DOUBLE>(g_CaptureSystemAudio) },
|
||||
{ L"MicMonoMix", SETTING_TYPE_BOOLEAN, 0, &g_MicMonoMix, static_cast<DOUBLE>(g_MicMonoMix) },
|
||||
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast<DOUBLE>(0) },
|
||||
{ L"NoiseCancellation", SETTING_TYPE_BOOLEAN, 0, &g_NoiseCancellation, static_cast<DOUBLE>(g_NoiseCancellation) },
|
||||
{ L"WebcamOverlay", SETTING_TYPE_BOOLEAN, 0, &g_WebcamOverlay, static_cast<DOUBLE>(g_WebcamOverlay) },
|
||||
{ L"WebcamPosition", SETTING_TYPE_DWORD, 0, &g_WebcamPosition, static_cast<DOUBLE>(g_WebcamPosition) },
|
||||
{ L"WebcamSize", SETTING_TYPE_DWORD, 0, &g_WebcamSize, static_cast<DOUBLE>(g_WebcamSize) },
|
||||
{ L"WebcamShape", SETTING_TYPE_DWORD, 0, &g_WebcamShape, static_cast<DOUBLE>(g_WebcamShape) },
|
||||
{ L"WebcamDeviceSymLink", SETTING_TYPE_STRING, sizeof(g_WebcamDeviceSymLink), g_WebcamDeviceSymLink, static_cast<DOUBLE>(0) },
|
||||
{ L"WebcamBackgroundMode", SETTING_TYPE_DWORD, 0, &g_WebcamBackgroundMode, static_cast<DOUBLE>(g_WebcamBackgroundMode) },
|
||||
{ L"WebcamBackgroundImage", SETTING_TYPE_STRING, sizeof(g_WebcamBackgroundImage), g_WebcamBackgroundImage, static_cast<DOUBLE>(0) },
|
||||
{ L"WebcamBrightness", SETTING_TYPE_DWORD, 0, &g_WebcamBrightness, static_cast<DOUBLE>(g_WebcamBrightness) },
|
||||
{ L"RecordAspectRatio", SETTING_TYPE_BOOLEAN, 0, &g_RecordAspectRatio, static_cast<DOUBLE>(g_RecordAspectRatio) },
|
||||
{ L"RecordingSaveLocation", SETTING_TYPE_STRING, sizeof(g_RecordingSaveLocationBuffer), g_RecordingSaveLocationBuffer, static_cast<DOUBLE>(0) },
|
||||
{ L"ScreenshotSaveLocation", SETTING_TYPE_STRING, sizeof(g_ScreenshotSaveLocationBuffer), g_ScreenshotSaveLocationBuffer, static_cast<DOUBLE>(0) },
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "PanoramaCapture.h"
|
||||
#include <wtsapi32.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __ZOOMIT_POWERTOYS__
|
||||
@@ -725,7 +724,7 @@ RunningOnWin64(
|
||||
// this executable.
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
BOOLEAN ExtractImageResource( PTCHAR ResourceName, PTCHAR TargetFile )
|
||||
BOOLEAN ExtractImageResource( PCTSTR ResourceName, PTCHAR TargetFile )
|
||||
{
|
||||
HRSRC hResource;
|
||||
HGLOBAL hImageResource;
|
||||
@@ -2835,6 +2834,323 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// WebcamSettingsProc
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
INT_PTR CALLBACK WebcamSettingsProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
|
||||
{
|
||||
static std::vector<std::pair<std::wstring, std::wstring>> webcams;
|
||||
static UINT currentDpi = DPI_BASELINE;
|
||||
static HWND s_hBrightnessTooltip = nullptr;
|
||||
|
||||
switch( message ) {
|
||||
case WM_INITDIALOG:
|
||||
{
|
||||
HICON hIcon = LoadIcon( g_hInstance, L"APPICON" );
|
||||
if( hIcon )
|
||||
{
|
||||
SendMessage( hDlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon) );
|
||||
SendMessage( hDlg, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon) );
|
||||
}
|
||||
|
||||
// Enumerate webcam devices
|
||||
webcams.clear();
|
||||
{
|
||||
MFStartup( MF_VERSION, MFSTARTUP_LITE );
|
||||
IMFAttributes* pAttributes = nullptr;
|
||||
if( SUCCEEDED( MFCreateAttributes( &pAttributes, 1 ) ) )
|
||||
{
|
||||
pAttributes->SetGUID( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID );
|
||||
IMFActivate** ppDevices = nullptr;
|
||||
UINT32 count = 0;
|
||||
if( SUCCEEDED( MFEnumDeviceSources( pAttributes, &ppDevices, &count ) ) )
|
||||
{
|
||||
for( UINT32 i = 0; i < count; i++ )
|
||||
{
|
||||
LPWSTR symLink = nullptr, friendlyName = nullptr;
|
||||
UINT32 nameLen = 0;
|
||||
if( SUCCEEDED( ppDevices[i]->GetAllocatedString( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &symLink, &nameLen ) ) &&
|
||||
SUCCEEDED( ppDevices[i]->GetAllocatedString( MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &nameLen ) ) )
|
||||
{
|
||||
webcams.emplace_back( symLink, friendlyName );
|
||||
}
|
||||
if( symLink ) CoTaskMemFree( symLink );
|
||||
if( friendlyName ) CoTaskMemFree( friendlyName );
|
||||
ppDevices[i]->Release();
|
||||
}
|
||||
CoTaskMemFree( ppDevices );
|
||||
}
|
||||
pAttributes->Release();
|
||||
}
|
||||
MFShutdown();
|
||||
}
|
||||
|
||||
// Populate camera combo
|
||||
SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_DEVICE ), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Default") );
|
||||
{
|
||||
int selection = 0;
|
||||
for( size_t i = 0; i < webcams.size(); i++ )
|
||||
{
|
||||
SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_DEVICE ), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(webcams[i].second.c_str()) );
|
||||
if( selection == 0 && wcscmp( webcams[i].first.c_str(), g_WebcamDeviceSymLink ) == 0 )
|
||||
selection = static_cast<int>(i + 1);
|
||||
}
|
||||
SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_DEVICE ), CB_SETCURSEL, selection, 0 );
|
||||
}
|
||||
|
||||
// Populate position combo
|
||||
{
|
||||
const wchar_t* positions[] = { L"Top-left", L"Top-right", L"Bottom-left", L"Bottom-right" };
|
||||
for( int i = 0; i < 4; i++ )
|
||||
SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_POSITION ), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(positions[i]) );
|
||||
SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_POSITION ), CB_SETCURSEL, g_WebcamPosition, 0 );
|
||||
}
|
||||
|
||||
// Populate size combo
|
||||
{
|
||||
const wchar_t* sizes[] = { L"Small", L"Medium", L"Large", L"X-Large", L"Full screen" };
|
||||
for( int i = 0; i < 5; i++ )
|
||||
SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_SIZE ), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(sizes[i]) );
|
||||
SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_SIZE ), CB_SETCURSEL, g_WebcamSize, 0 );
|
||||
}
|
||||
|
||||
// Populate shape combo
|
||||
{
|
||||
const wchar_t* shapes[] = { L"Rectangle", L"Rounded Rectangle", L"Rounded Square", L"Circle" };
|
||||
for( int i = 0; i < 4; i++ )
|
||||
SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_SHAPE ), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(shapes[i]) );
|
||||
SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_SHAPE ), CB_SETCURSEL, g_WebcamShape, 0 );
|
||||
}
|
||||
|
||||
// Populate background mode combo
|
||||
{
|
||||
HWND hBgMode = GetDlgItem( hDlg, IDC_WEBCAM_BG_MODE );
|
||||
SendMessage( hBgMode, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"None") );
|
||||
SendMessage( hBgMode, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Blur") );
|
||||
SendMessage( hBgMode, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Image") );
|
||||
SendMessage( hBgMode, CB_SETCURSEL, g_WebcamBackgroundMode, 0 );
|
||||
SetDlgItemText( hDlg, IDC_WEBCAM_BG_IMAGE, g_WebcamBackgroundImage );
|
||||
}
|
||||
|
||||
// Initialize brightness slider
|
||||
{
|
||||
HWND hSlider = GetDlgItem( hDlg, IDC_WEBCAM_BRIGHTNESS_SLIDER );
|
||||
SendMessage( hSlider, TBM_SETRANGE, FALSE, MAKELONG( 0, 100 ) );
|
||||
SendMessage( hSlider, TBM_SETPOS, TRUE, g_WebcamBrightness );
|
||||
|
||||
// Place the built-in drag tooltip (TBS_TOOLTIPS) beneath the slider.
|
||||
SendMessage( hSlider, TBM_SETTIPSIDE, TBTS_BOTTOM, 0 );
|
||||
|
||||
// Hover tooltip that displays the current brightness when not dragging.
|
||||
// TTF_SUBCLASS lets the tooltip detect hover itself; LPSTR_TEXTCALLBACK
|
||||
// makes us supply the current value via TTN_GETDISPINFO.
|
||||
s_hBrightnessTooltip = CreateWindowEx(
|
||||
WS_EX_TOPMOST, TOOLTIPS_CLASS, nullptr,
|
||||
WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
hDlg, nullptr, g_hInstance, nullptr );
|
||||
if( s_hBrightnessTooltip )
|
||||
{
|
||||
TOOLINFO ti = { sizeof(TOOLINFO) };
|
||||
ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
|
||||
ti.hwnd = hDlg;
|
||||
ti.uId = reinterpret_cast<UINT_PTR>(hSlider);
|
||||
ti.lpszText = LPSTR_TEXTCALLBACK;
|
||||
SendMessage( s_hBrightnessTooltip, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&ti) );
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial enabled state
|
||||
{
|
||||
bool isFullScreen = g_WebcamSize == 4;
|
||||
EnableWindow( GetDlgItem( hDlg, IDC_WEBCAM_SHAPE_LABEL ), !isFullScreen );
|
||||
EnableWindow( GetDlgItem( hDlg, IDC_WEBCAM_SHAPE ), !isFullScreen );
|
||||
bool isImageMode = g_WebcamBackgroundMode == 2;
|
||||
EnableWindow( GetDlgItem( hDlg, IDC_WEBCAM_BG_IMAGE ), isImageMode );
|
||||
EnableWindow( GetDlgItem( hDlg, IDC_WEBCAM_BG_BROWSE ), isImageMode );
|
||||
}
|
||||
|
||||
// DPI scaling
|
||||
currentDpi = GetDpiForWindowHelper( hDlg );
|
||||
if( currentDpi != DPI_BASELINE )
|
||||
ScaleDialogForDpi( hDlg, currentDpi, DPI_BASELINE );
|
||||
|
||||
ApplyDarkModeToDialog( hDlg );
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
case WM_DPICHANGED:
|
||||
HandleDialogDpiChange( hDlg, wParam, lParam, currentDpi );
|
||||
return TRUE;
|
||||
|
||||
case WM_ERASEBKGND:
|
||||
if( IsDarkModeEnabled() )
|
||||
{
|
||||
HDC hdc = reinterpret_cast<HDC>(wParam);
|
||||
RECT rc;
|
||||
GetClientRect( hDlg, &rc );
|
||||
FillRect( hdc, &rc, GetDarkModeBrush() );
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_CTLCOLORDLG:
|
||||
case WM_CTLCOLORSTATIC:
|
||||
case WM_CTLCOLORBTN:
|
||||
case WM_CTLCOLOREDIT:
|
||||
case WM_CTLCOLORLISTBOX:
|
||||
{
|
||||
HDC hdc = reinterpret_cast<HDC>(wParam);
|
||||
HWND hCtrl = reinterpret_cast<HWND>(lParam);
|
||||
HBRUSH hBrush = HandleDarkModeCtlColor( hdc, hCtrl, message );
|
||||
if( hBrush )
|
||||
return reinterpret_cast<INT_PTR>(hBrush);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_NOTIFY:
|
||||
{
|
||||
LPNMHDR pnmh = reinterpret_cast<LPNMHDR>(lParam);
|
||||
if( s_hBrightnessTooltip && pnmh->hwndFrom == s_hBrightnessTooltip )
|
||||
{
|
||||
if( pnmh->code == TTN_GETDISPINFO )
|
||||
{
|
||||
LPNMTTDISPINFO pdi = reinterpret_cast<LPNMTTDISPINFO>(lParam);
|
||||
static wchar_t buf[16];
|
||||
int pos = static_cast<int>(SendMessage(
|
||||
GetDlgItem( hDlg, IDC_WEBCAM_BRIGHTNESS_SLIDER ), TBM_GETPOS, 0, 0 ));
|
||||
_snwprintf_s( buf, _countof(buf), _TRUNCATE, L"%d", pos );
|
||||
pdi->lpszText = buf;
|
||||
return TRUE;
|
||||
}
|
||||
if( pnmh->code == TTN_SHOW )
|
||||
{
|
||||
// Reposition the tooltip directly beneath the slider, centered on the cursor.
|
||||
HWND hSlider = GetDlgItem( hDlg, IDC_WEBCAM_BRIGHTNESS_SLIDER );
|
||||
RECT rcSlider, rcTip;
|
||||
GetWindowRect( hSlider, &rcSlider );
|
||||
GetWindowRect( s_hBrightnessTooltip, &rcTip );
|
||||
POINT pt;
|
||||
GetCursorPos( &pt );
|
||||
int tipW = rcTip.right - rcTip.left;
|
||||
SetWindowPos( s_hBrightnessTooltip, HWND_TOPMOST,
|
||||
pt.x - tipW / 2, rcSlider.bottom + 4, 0, 0,
|
||||
SWP_NOSIZE | SWP_NOACTIVATE );
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_DESTROY:
|
||||
if( s_hBrightnessTooltip )
|
||||
{
|
||||
DestroyWindow( s_hBrightnessTooltip );
|
||||
s_hBrightnessTooltip = nullptr;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_COMMAND:
|
||||
// Handle size combo change — disable shape when Full screen
|
||||
if( HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_WEBCAM_SIZE )
|
||||
{
|
||||
bool isFullScreen = SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_SIZE ), CB_GETCURSEL, 0, 0 ) == 4;
|
||||
EnableWindow( GetDlgItem( hDlg, IDC_WEBCAM_SHAPE_LABEL ), !isFullScreen );
|
||||
EnableWindow( GetDlgItem( hDlg, IDC_WEBCAM_SHAPE ), !isFullScreen );
|
||||
}
|
||||
// Handle background mode combo change — show/hide browse controls
|
||||
if( HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_WEBCAM_BG_MODE )
|
||||
{
|
||||
bool isImageMode = SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_BG_MODE ), CB_GETCURSEL, 0, 0 ) == 2;
|
||||
EnableWindow( GetDlgItem( hDlg, IDC_WEBCAM_BG_IMAGE ), isImageMode );
|
||||
EnableWindow( GetDlgItem( hDlg, IDC_WEBCAM_BG_BROWSE ), isImageMode );
|
||||
}
|
||||
|
||||
switch( LOWORD(wParam) ) {
|
||||
case IDC_WEBCAM_BG_BROWSE:
|
||||
{
|
||||
auto openDialog = wil::CoCreateInstance<IFileOpenDialog>( CLSID_FileOpenDialog );
|
||||
|
||||
FILEOPENDIALOGOPTIONS options;
|
||||
if( SUCCEEDED( openDialog->GetOptions( &options ) ) )
|
||||
openDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
|
||||
|
||||
COMDLG_FILTERSPEC fileTypes[] = {
|
||||
{ L"Bitmap Files (*.bmp;*.dib)", L"*.bmp;*.dib" },
|
||||
{ L"PNG (*.png)", L"*.png" },
|
||||
{ L"JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)", L"*.jpg;*.jpeg;*.jpe;*.jfif" },
|
||||
{ L"GIF (*.gif)", L"*.gif" },
|
||||
{ L"All Picture Files", L"*.bmp;*.dib;*.png;*.jpg;*.jpeg;*.jpe;*.jfif;*.gif" },
|
||||
{ L"All Files", L"*.*" }
|
||||
};
|
||||
openDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
||||
openDialog->SetFileTypeIndex( 5 );
|
||||
openDialog->SetTitle( L"ZoomIt: Specify Background Image..." );
|
||||
|
||||
TCHAR bgFilePath[MAX_PATH], bgInitDir[MAX_PATH];
|
||||
GetDlgItemText( hDlg, IDC_WEBCAM_BG_IMAGE, bgFilePath, _countof( bgFilePath ) );
|
||||
if( _tcsrchr( bgFilePath, '\\' ) )
|
||||
{
|
||||
_tcscpy( bgInitDir, bgFilePath );
|
||||
*( _tcsrchr( bgInitDir, '\\' ) + 1 ) = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tcscpy( bgFilePath, L"%USERPROFILE%\\Pictures" );
|
||||
ExpandEnvironmentStrings( bgFilePath, bgInitDir, _countof( bgInitDir ) );
|
||||
}
|
||||
wil::com_ptr<IShellItem> folderItem;
|
||||
if( SUCCEEDED( SHCreateItemFromParsingName( bgInitDir, nullptr, IID_PPV_ARGS( &folderItem ) ) ) )
|
||||
openDialog->SetFolder( folderItem.get() );
|
||||
|
||||
OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents(false);
|
||||
DWORD dwCookie = 0;
|
||||
openDialog->Advise( pEvents, &dwCookie );
|
||||
|
||||
if( SUCCEEDED( openDialog->Show( hDlg ) ) )
|
||||
{
|
||||
wil::com_ptr<IShellItem> resultItem;
|
||||
if( SUCCEEDED( openDialog->GetResult( &resultItem ) ) )
|
||||
{
|
||||
wil::unique_cotaskmem_string pathStr;
|
||||
if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
|
||||
SetDlgItemText( hDlg, IDC_WEBCAM_BG_IMAGE, pathStr.get() );
|
||||
}
|
||||
}
|
||||
|
||||
openDialog->Unadvise( dwCookie );
|
||||
pEvents->Release();
|
||||
break;
|
||||
}
|
||||
|
||||
case IDOK:
|
||||
g_WebcamPosition = static_cast<DWORD>(SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_POSITION ), CB_GETCURSEL, 0, 0 ));
|
||||
g_WebcamSize = static_cast<DWORD>(SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_SIZE ), CB_GETCURSEL, 0, 0 ));
|
||||
g_WebcamShape = static_cast<DWORD>(SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_SHAPE ), CB_GETCURSEL, 0, 0 ));
|
||||
g_WebcamBackgroundMode = static_cast<DWORD>(SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_BG_MODE ), CB_GETCURSEL, 0, 0 ));
|
||||
GetDlgItemText( hDlg, IDC_WEBCAM_BG_IMAGE, g_WebcamBackgroundImage, MAX_PATH );
|
||||
g_WebcamBrightness = static_cast<DWORD>(SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_BRIGHTNESS_SLIDER ), TBM_GETPOS, 0, 0 ));
|
||||
{
|
||||
int wcIndex = static_cast<int>(SendMessage( GetDlgItem( hDlg, IDC_WEBCAM_DEVICE ), CB_GETCURSEL, 0, 0 ));
|
||||
_tcscpy( g_WebcamDeviceSymLink, wcIndex == 0 ? L"" : webcams[static_cast<size_t>(wcIndex) - 1].first.c_str() );
|
||||
}
|
||||
reg.WriteRegSettings( RegSettings );
|
||||
EndDialog( hDlg, 0 );
|
||||
break;
|
||||
|
||||
case IDCANCEL:
|
||||
EndDialog( hDlg, 0 );
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// OptionsTabProc
|
||||
@@ -2972,52 +3288,33 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
|
||||
// Enable/disable audio controls based on selection (GIF has no audio)
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_CAPTURE_SYSTEM_AUDIO), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_CAPTURE_AUDIO), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_NOISE_CANCELLATION), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_MIC_MONO_MIX), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE_LABEL), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE), !isGifSelected);
|
||||
|
||||
// Enable/disable webcam controls (webcam overlay is MP4-only).
|
||||
// Also keep everything disabled if no webcam is present.
|
||||
bool hasWebcam = SendMessage(GetDlgItem(hDlg, IDC_WEBCAM_DEVICE), CB_GETCOUNT, 0, 0) > 1;
|
||||
bool webcamEnabled = hasWebcam && !isGifSelected && IsDlgButtonChecked(hDlg, IDC_WEBCAM_OVERLAY) == BST_CHECKED;
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_OVERLAY), hasWebcam && !isGifSelected);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_DEVICE_LABEL), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_DEVICE), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_POSITION_LABEL), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_POSITION), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SIZE_LABEL), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SIZE), webcamEnabled);
|
||||
{
|
||||
bool isFullScreen = webcamEnabled && SendMessage(GetDlgItem(hDlg, IDC_WEBCAM_SIZE), CB_GETCURSEL, 0, 0) == 4;
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SHAPE_LABEL), webcamEnabled && !isFullScreen);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SHAPE), webcamEnabled && !isFullScreen);
|
||||
}
|
||||
// Enable/disable webcam overlay and settings button (webcam overlay is MP4-only).
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_OVERLAY), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SETTINGS), !isGifSelected && IsDlgButtonChecked(hDlg, IDC_WEBCAM_OVERLAY) == BST_CHECKED);
|
||||
}
|
||||
}
|
||||
if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_WEBCAM_OVERLAY) {
|
||||
bool isGif = (g_RecordingFormat == RecordingFormat::GIF);
|
||||
bool webcamEnabled = !isGif && IsDlgButtonChecked(hDlg, IDC_WEBCAM_OVERLAY) == BST_CHECKED;
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_DEVICE_LABEL), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_DEVICE), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_POSITION_LABEL), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_POSITION), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SIZE_LABEL), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SIZE), webcamEnabled);
|
||||
{
|
||||
bool isFullScreen = webcamEnabled && SendMessage(GetDlgItem(hDlg, IDC_WEBCAM_SIZE), CB_GETCURSEL, 0, 0) == 4;
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SHAPE_LABEL), webcamEnabled && !isFullScreen);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SHAPE), webcamEnabled && !isFullScreen);
|
||||
}
|
||||
bool webcamEnabled = IsDlgButtonChecked(hDlg, IDC_WEBCAM_OVERLAY) == BST_CHECKED;
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SETTINGS), webcamEnabled);
|
||||
}
|
||||
// Handle webcam size combo change — disable shape when Full screen selected
|
||||
if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_WEBCAM_SIZE) {
|
||||
bool isGif = (g_RecordingFormat == RecordingFormat::GIF);
|
||||
bool webcamEnabled = !isGif && IsDlgButtonChecked(hDlg, IDC_WEBCAM_OVERLAY) == BST_CHECKED;
|
||||
bool isFullScreen = SendMessage(GetDlgItem(hDlg, IDC_WEBCAM_SIZE), CB_GETCURSEL, 0, 0) == 4;
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SHAPE_LABEL), webcamEnabled && !isFullScreen);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_WEBCAM_SHAPE), webcamEnabled && !isFullScreen);
|
||||
if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_CAPTURE_AUDIO) {
|
||||
bool micEnabled = IsDlgButtonChecked(hDlg, IDC_CAPTURE_AUDIO) == BST_CHECKED;
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_NOISE_CANCELLATION), micEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_MIC_MONO_MIX), micEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE_LABEL), micEnabled);
|
||||
EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE), micEnabled);
|
||||
|
||||
}
|
||||
|
||||
switch ( LOWORD( wParam )) {
|
||||
case IDC_WEBCAM_SETTINGS:
|
||||
DialogBox( g_hInstance, L"WEBCAM_SETTINGS", hDlg, WebcamSettingsProc );
|
||||
break;
|
||||
case IDC_ADVANCED_BREAK:
|
||||
DialogBox( g_hInstance, L"ADVANCED_BREAK", hDlg, AdvancedBreakProc );
|
||||
break;
|
||||
@@ -4577,7 +4874,6 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
||||
DWORD newDrawToggleKey, newDrawToggleMod, newBreakToggleMod, newDemoTypeToggleMod, newRecordToggleMod, newSnipToggleMod, newSnipPanoramaToggleMod, newSnipOcrToggleMod;
|
||||
DWORD newLiveZoomToggleKey, newLiveZoomToggleMod;
|
||||
static std::vector<std::pair<std::wstring, std::wstring>> microphones;
|
||||
static std::vector<std::pair<std::wstring, std::wstring>> webcams;
|
||||
|
||||
auto CleanupFonts = [&]()
|
||||
{
|
||||
@@ -4849,6 +5145,9 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
||||
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MIC_MONO_MIX,
|
||||
g_MicMonoMix ? BST_CHECKED: BST_UNCHECKED );
|
||||
|
||||
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_NOISE_CANCELLATION,
|
||||
g_NoiseCancellation ? BST_CHECKED: BST_UNCHECKED );
|
||||
|
||||
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_ASPECT_RATIO,
|
||||
g_RecordAspectRatio ? BST_CHECKED: BST_UNCHECKED );
|
||||
|
||||
@@ -4924,101 +5223,19 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
||||
bool isGifSelected = (g_RecordingFormat == RecordingFormat::GIF);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_SYSTEM_AUDIO), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE_LABEL), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_NOISE_CANCELLATION), !isGifSelected && g_CaptureAudio);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MIC_MONO_MIX), !isGifSelected && g_CaptureAudio);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE_LABEL), !isGifSelected && g_CaptureAudio);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE), !isGifSelected && g_CaptureAudio);
|
||||
|
||||
// Webcam overlay controls
|
||||
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_OVERLAY,
|
||||
g_WebcamOverlay ? BST_CHECKED : BST_UNCHECKED );
|
||||
|
||||
// Enumerate webcam devices
|
||||
webcams.clear();
|
||||
// Set initial enabled state for webcam overlay and settings button
|
||||
{
|
||||
MFStartup( MF_VERSION, MFSTARTUP_LITE );
|
||||
IMFAttributes* pAttributes = nullptr;
|
||||
if( SUCCEEDED( MFCreateAttributes( &pAttributes, 1 ) ) )
|
||||
{
|
||||
pAttributes->SetGUID( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID );
|
||||
IMFActivate** ppDevices = nullptr;
|
||||
UINT32 count = 0;
|
||||
if( SUCCEEDED( MFEnumDeviceSources( pAttributes, &ppDevices, &count ) ) )
|
||||
{
|
||||
for( UINT32 i = 0; i < count; i++ )
|
||||
{
|
||||
WCHAR* symLink = nullptr;
|
||||
UINT32 symLinkLen = 0;
|
||||
WCHAR* friendlyName = nullptr;
|
||||
UINT32 nameLen = 0;
|
||||
if( SUCCEEDED( ppDevices[i]->GetAllocatedString( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &symLink, &symLinkLen ) ) &&
|
||||
SUCCEEDED( ppDevices[i]->GetAllocatedString( MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &nameLen ) ) )
|
||||
{
|
||||
webcams.emplace_back( symLink, friendlyName );
|
||||
}
|
||||
if( symLink ) CoTaskMemFree( symLink );
|
||||
if( friendlyName ) CoTaskMemFree( friendlyName );
|
||||
ppDevices[i]->Release();
|
||||
}
|
||||
CoTaskMemFree( ppDevices );
|
||||
}
|
||||
pAttributes->Release();
|
||||
}
|
||||
MFShutdown();
|
||||
}
|
||||
|
||||
// Add webcam devices to combo box
|
||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_DEVICE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(L"Default") );
|
||||
selection = 0;
|
||||
for( size_t i = 0; i < webcams.size(); i++ )
|
||||
{
|
||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_DEVICE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(webcams[i].second.c_str()) );
|
||||
if( selection == 0 && wcscmp( webcams[i].first.c_str(), g_WebcamDeviceSymLink ) == 0 )
|
||||
{
|
||||
selection = i + 1;
|
||||
}
|
||||
}
|
||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_DEVICE ), CB_SETCURSEL, static_cast<WPARAM>(selection), static_cast<LPARAM>(0) );
|
||||
|
||||
// Webcam position combo
|
||||
{
|
||||
const wchar_t* positions[] = { L"Top left", L"Top right", L"Bottom left", L"Bottom right" };
|
||||
for( int i = 0; i < 4; i++ )
|
||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_POSITION ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(positions[i]) );
|
||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_POSITION ), CB_SETCURSEL, static_cast<WPARAM>(g_WebcamPosition), static_cast<LPARAM>(0) );
|
||||
}
|
||||
|
||||
// Webcam size combo
|
||||
{
|
||||
const wchar_t* sizes[] = { L"Small", L"Medium", L"Large", L"X-Large", L"Full screen" };
|
||||
for( int i = 0; i < 5; i++ )
|
||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SIZE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(sizes[i]) );
|
||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SIZE ), CB_SETCURSEL, static_cast<WPARAM>(g_WebcamSize), static_cast<LPARAM>(0) );
|
||||
}
|
||||
|
||||
// Webcam shape combo
|
||||
{
|
||||
const wchar_t* shapes[] = { L"Rectangle", L"Rounded rectangle", L"Rounded square", L"Circle" };
|
||||
for( int i = 0; i < 4; i++ )
|
||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SHAPE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(shapes[i]) );
|
||||
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SHAPE ), CB_SETCURSEL, static_cast<WPARAM>(g_WebcamShape), static_cast<LPARAM>(0) );
|
||||
}
|
||||
|
||||
// Set initial enabled state for webcam controls.
|
||||
// Disable everything if no webcam is detected on the system.
|
||||
{
|
||||
bool hasWebcam = !webcams.empty();
|
||||
bool webcamEnabled = hasWebcam && !isGifSelected && g_WebcamOverlay;
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_OVERLAY), hasWebcam && !isGifSelected);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_DEVICE_LABEL), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_DEVICE), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_POSITION_LABEL), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_POSITION), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SIZE_LABEL), webcamEnabled);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SIZE), webcamEnabled);
|
||||
{
|
||||
bool isFullScreen = webcamEnabled && g_WebcamSize == 4;
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SHAPE_LABEL), webcamEnabled && !isFullScreen);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SHAPE), webcamEnabled && !isFullScreen);
|
||||
}
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_OVERLAY), !isGifSelected);
|
||||
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SETTINGS), !isGifSelected && g_WebcamOverlay);
|
||||
}
|
||||
|
||||
if( GetFileAttributes( g_DemoTypeFile ) == -1 )
|
||||
@@ -5372,6 +5589,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
||||
g_CaptureSystemAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_SYSTEM_AUDIO) == BST_CHECKED;
|
||||
g_CaptureAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO) == BST_CHECKED;
|
||||
g_MicMonoMix = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MIC_MONO_MIX) == BST_CHECKED;
|
||||
g_NoiseCancellation = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_NOISE_CANCELLATION) == BST_CHECKED;
|
||||
g_RecordAspectRatio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_ASPECT_RATIO) == BST_CHECKED;
|
||||
GetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text, 3 );
|
||||
text[2] = 0;
|
||||
@@ -5385,15 +5603,8 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
||||
int index = static_cast<int>(SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0) ));
|
||||
_tcscpy( g_MicrophoneDeviceId, index == 0 ? L"" : microphones[static_cast<size_t>(index) - 1].first.c_str() );
|
||||
|
||||
// Get the webcam settings
|
||||
// Get the webcam overlay setting (other webcam settings are saved in WebcamSettingsProc)
|
||||
g_WebcamOverlay = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_OVERLAY) == BST_CHECKED;
|
||||
g_WebcamPosition = static_cast<DWORD>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_POSITION), CB_GETCURSEL, 0, 0));
|
||||
g_WebcamSize = static_cast<DWORD>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SIZE), CB_GETCURSEL, 0, 0));
|
||||
g_WebcamShape = static_cast<DWORD>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_SHAPE), CB_GETCURSEL, 0, 0));
|
||||
{
|
||||
int wcIndex = static_cast<int>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_WEBCAM_DEVICE), CB_GETCURSEL, 0, 0));
|
||||
_tcscpy( g_WebcamDeviceSymLink, wcIndex == 0 ? L"" : webcams[static_cast<size_t>(wcIndex) - 1].first.c_str() );
|
||||
}
|
||||
|
||||
if( newToggleKey && !RegisterHotKey( GetParent( hDlg ), ZOOM_HOTKEY, newToggleMod, newToggleKey & 0xFF )) {
|
||||
|
||||
@@ -5465,18 +5676,15 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
||||
break;
|
||||
|
||||
}
|
||||
else if( newRecordToggleKey ) {
|
||||
UINT cropMod = newRecordToggleMod ^ MOD_SHIFT;
|
||||
UINT windowMod = newRecordToggleMod ^ MOD_ALT;
|
||||
if (!RegisterHotKey(GetParent(hDlg), RECORD_HOTKEY, newRecordToggleMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
|
||||
(cropMod != 0 && !RegisterHotKey(GetParent(hDlg), RECORD_CROP_HOTKEY, cropMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF)) ||
|
||||
(windowMod != 0 && !RegisterHotKey(GetParent(hDlg), RECORD_WINDOW_HOTKEY, windowMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF))) {
|
||||
else if( UINT cropMod = newRecordToggleMod ^ MOD_SHIFT, windowMod = newRecordToggleMod ^ MOD_ALT; newRecordToggleKey &&
|
||||
(!RegisterHotKey(GetParent(hDlg), RECORD_HOTKEY, newRecordToggleMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
|
||||
(cropMod != 0 && !RegisterHotKey(GetParent(hDlg), RECORD_CROP_HOTKEY, cropMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF)) ||
|
||||
(windowMod != 0 && !RegisterHotKey(GetParent(hDlg), RECORD_WINDOW_HOTKEY, windowMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF)))) {
|
||||
|
||||
MessageBox(hDlg, L"The specified record hotkey is already in use.\nSelect a different record hotkey.",
|
||||
APPNAME, MB_ICONERROR);
|
||||
UnregisterAllHotkeys(GetParent(hDlg));
|
||||
break;
|
||||
}
|
||||
MessageBox(hDlg, L"The specified record hotkey is already in use.\nSelect a different record hotkey.",
|
||||
APPNAME, MB_ICONERROR);
|
||||
UnregisterAllHotkeys(GetParent(hDlg));
|
||||
break;
|
||||
} else {
|
||||
|
||||
g_BreakTimeout = newTimeout;
|
||||
@@ -6706,6 +6914,22 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
||||
// Capture the UI thread context so we can resume on it for the save dialog
|
||||
winrt::apartment_context uiThread;
|
||||
|
||||
// Start audio initialization as early as possible. AudioGraph creation
|
||||
// and microphone device opening take ~1400 ms. By starting here, the
|
||||
// init runs in the background during D3D device creation, capture-item
|
||||
// creation, file I/O, and the entire VideoRecordingSession constructor
|
||||
// (webcam probe + warmup), giving it ~1400 ms of overlap — enough to
|
||||
// finish before StartAsync even needs the result.
|
||||
std::unique_ptr<AudioSampleGenerator> audioGenerator;
|
||||
winrt::Windows::Foundation::IAsyncAction audioInitAction{ nullptr };
|
||||
if ((g_RecordingFormat != RecordingFormat::GIF) && (g_CaptureAudio || g_CaptureSystemAudio))
|
||||
{
|
||||
audioGenerator = std::make_unique<AudioSampleGenerator>(
|
||||
g_CaptureAudio, g_CaptureSystemAudio, g_MicMonoMix, g_NoiseCancellation );
|
||||
audioInitAction = audioGenerator->InitializeAsync();
|
||||
_diagLog( L"audio InitializeAsync started (background)" );
|
||||
}
|
||||
|
||||
auto tempFolderPath = std::filesystem::temp_directory_path().wstring();
|
||||
auto tempFolder = co_await winrt::StorageFolder::GetFolderFromPathAsync( tempFolderPath );
|
||||
auto appFolder = co_await tempFolder.CreateFolderAsync( L"ZoomIt", winrt::CreationCollisionOption::OpenIfExists );
|
||||
@@ -6794,9 +7018,8 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
||||
item,
|
||||
*rcCrop,
|
||||
g_RecordFrameRate,
|
||||
g_CaptureAudio,
|
||||
g_CaptureSystemAudio,
|
||||
g_MicMonoMix,
|
||||
std::move(audioGenerator),
|
||||
audioInitAction,
|
||||
stream );
|
||||
_diagLog( L"VideoRecordingSession::Create returned" );
|
||||
|
||||
|
||||
@@ -25,4 +25,7 @@ RCZOOMITSCR BINRES MOVEABLE PURE "ZoomItBreak64.scr"
|
||||
RCZOOMITSCR BINRES MOVEABLE PURE "ZoomItBreak64a.scr"
|
||||
#endif
|
||||
|
||||
// Embed the selfie segmentation ONNX model (platform-independent).
|
||||
RCSEGMODEL BINRES MOVEABLE PURE "selfie_segmentation.onnx"
|
||||
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "ZoomIt.exe.manifest"
|
||||
|
||||
@@ -127,6 +127,16 @@
|
||||
#define IDC_WEBCAM_SHAPE_LABEL 1121
|
||||
#define IDC_TRIM_APPEND 1122
|
||||
#define IDC_RECORD_ASPECT_RATIO 1123
|
||||
#define IDC_WEBCAM_BACKGROUND_BLUR 1124
|
||||
#define IDC_WEBCAM_BG_LABEL 1125
|
||||
#define IDC_WEBCAM_BG_MODE 1126
|
||||
#define IDC_WEBCAM_BG_IMAGE 1127
|
||||
#define IDC_WEBCAM_BG_BROWSE 1128
|
||||
#define IDC_THIRDPARTY_NOTICES 1129
|
||||
#define IDC_WEBCAM_SETTINGS 1130
|
||||
#define IDC_WEBCAM_BRIGHTNESS_LABEL 1131
|
||||
#define IDC_WEBCAM_BRIGHTNESS_SLIDER 1132
|
||||
#define IDC_NOISE_CANCELLATION 1133
|
||||
#define IDC_SAVE 40002
|
||||
#define IDC_COPY 40004
|
||||
#define IDC_RECORD 40006
|
||||
@@ -142,7 +152,7 @@
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 120
|
||||
#define _APS_NEXT_COMMAND_VALUE 40015
|
||||
#define _APS_NEXT_CONTROL_VALUE 1124
|
||||
#define _APS_NEXT_CONTROL_VALUE 1134
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
|
||||
32
src/modules/ZoomIt/ZoomIt/rnnoise/COPYING
Normal file
32
src/modules/ZoomIt/ZoomIt/rnnoise/COPYING
Normal file
@@ -0,0 +1,32 @@
|
||||
Copyright (c) 2007-2017, 2024 Jean-Marc Valin
|
||||
Copyright (c) 2023 Amazon
|
||||
Copyright (c) 2017, Mozilla
|
||||
Copyright (c) 2005-2017, Xiph.Org Foundation
|
||||
Copyright (c) 2003-2004, Mark Borgerding
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
- Neither the name of the Xiph.Org Foundation nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
182
src/modules/ZoomIt/ZoomIt/rnnoise/_kiss_fft_guts.h
Normal file
182
src/modules/ZoomIt/ZoomIt/rnnoise/_kiss_fft_guts.h
Normal file
@@ -0,0 +1,182 @@
|
||||
/*Copyright (c) 2003-2004, Mark Borgerding
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.*/
|
||||
|
||||
#ifndef KISS_FFT_GUTS_H
|
||||
#define KISS_FFT_GUTS_H
|
||||
|
||||
#define MIN(a,b) ((a)<(b) ? (a):(b))
|
||||
#define MAX(a,b) ((a)>(b) ? (a):(b))
|
||||
|
||||
/* kiss_fft.h
|
||||
defines kiss_fft_scalar as either short or a float type
|
||||
and defines
|
||||
typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */
|
||||
#include "kiss_fft.h"
|
||||
|
||||
/*
|
||||
Explanation of macros dealing with complex math:
|
||||
|
||||
C_MUL(m,a,b) : m = a*b
|
||||
C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise
|
||||
C_SUB( res, a,b) : res = a - b
|
||||
C_SUBFROM( res , a) : res -= a
|
||||
C_ADDTO( res , a) : res += a
|
||||
* */
|
||||
#ifdef FIXED_POINT
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
#define SAMP_MAX 2147483647
|
||||
#define TWID_MAX 32767
|
||||
#define TRIG_UPSCALE 1
|
||||
|
||||
#define SAMP_MIN -SAMP_MAX
|
||||
|
||||
|
||||
# define S_MUL(a,b) MULT16_32_Q15(b, a)
|
||||
|
||||
# define C_MUL(m,a,b) \
|
||||
do{ (m).r = SUB32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
|
||||
(m).i = ADD32_ovflw(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0)
|
||||
|
||||
# define C_MULC(m,a,b) \
|
||||
do{ (m).r = ADD32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
|
||||
(m).i = SUB32_ovflw(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0)
|
||||
|
||||
# define C_MULBYSCALAR( c, s ) \
|
||||
do{ (c).r = S_MUL( (c).r , s ) ;\
|
||||
(c).i = S_MUL( (c).i , s ) ; }while(0)
|
||||
|
||||
# define DIVSCALAR(x,k) \
|
||||
(x) = S_MUL( x, (TWID_MAX-((k)>>1))/(k)+1 )
|
||||
|
||||
# define C_FIXDIV(c,div) \
|
||||
do { DIVSCALAR( (c).r , div); \
|
||||
DIVSCALAR( (c).i , div); }while (0)
|
||||
|
||||
#define C_ADD( res, a,b)\
|
||||
do {(res).r=ADD32_ovflw((a).r,(b).r); (res).i=ADD32_ovflw((a).i,(b).i); \
|
||||
}while(0)
|
||||
#define C_SUB( res, a,b)\
|
||||
do {(res).r=SUB32_ovflw((a).r,(b).r); (res).i=SUB32_ovflw((a).i,(b).i); \
|
||||
}while(0)
|
||||
#define C_ADDTO( res , a)\
|
||||
do {(res).r = ADD32_ovflw((res).r, (a).r); (res).i = ADD32_ovflw((res).i,(a).i);\
|
||||
}while(0)
|
||||
|
||||
#define C_SUBFROM( res , a)\
|
||||
do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \
|
||||
}while(0)
|
||||
|
||||
#if defined(OPUS_ARM_INLINE_ASM)
|
||||
#include "arm/kiss_fft_armv4.h"
|
||||
#endif
|
||||
|
||||
#if defined(OPUS_ARM_INLINE_EDSP)
|
||||
#include "arm/kiss_fft_armv5e.h"
|
||||
#endif
|
||||
#if defined(MIPSr1_ASM)
|
||||
#include "mips/kiss_fft_mipsr1.h"
|
||||
#endif
|
||||
|
||||
#else /* not FIXED_POINT*/
|
||||
|
||||
# define S_MUL(a,b) ( (a)*(b) )
|
||||
#define C_MUL(m,a,b) \
|
||||
do{ (m).r = (a).r*(b).r - (a).i*(b).i;\
|
||||
(m).i = (a).r*(b).i + (a).i*(b).r; }while(0)
|
||||
#define C_MULC(m,a,b) \
|
||||
do{ (m).r = (a).r*(b).r + (a).i*(b).i;\
|
||||
(m).i = (a).i*(b).r - (a).r*(b).i; }while(0)
|
||||
|
||||
#define C_MUL4(m,a,b) C_MUL(m,a,b)
|
||||
|
||||
# define C_FIXDIV(c,div) /* NOOP */
|
||||
# define C_MULBYSCALAR( c, s ) \
|
||||
do{ (c).r *= (s);\
|
||||
(c).i *= (s); }while(0)
|
||||
#endif
|
||||
|
||||
#ifndef CHECK_OVERFLOW_OP
|
||||
# define CHECK_OVERFLOW_OP(a,op,b) /* noop */
|
||||
#endif
|
||||
|
||||
#ifndef C_ADD
|
||||
#define C_ADD( res, a,b)\
|
||||
do { \
|
||||
CHECK_OVERFLOW_OP((a).r,+,(b).r)\
|
||||
CHECK_OVERFLOW_OP((a).i,+,(b).i)\
|
||||
(res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \
|
||||
}while(0)
|
||||
#define C_SUB( res, a,b)\
|
||||
do { \
|
||||
CHECK_OVERFLOW_OP((a).r,-,(b).r)\
|
||||
CHECK_OVERFLOW_OP((a).i,-,(b).i)\
|
||||
(res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \
|
||||
}while(0)
|
||||
#define C_ADDTO( res , a)\
|
||||
do { \
|
||||
CHECK_OVERFLOW_OP((res).r,+,(a).r)\
|
||||
CHECK_OVERFLOW_OP((res).i,+,(a).i)\
|
||||
(res).r += (a).r; (res).i += (a).i;\
|
||||
}while(0)
|
||||
|
||||
#define C_SUBFROM( res , a)\
|
||||
do {\
|
||||
CHECK_OVERFLOW_OP((res).r,-,(a).r)\
|
||||
CHECK_OVERFLOW_OP((res).i,-,(a).i)\
|
||||
(res).r -= (a).r; (res).i -= (a).i; \
|
||||
}while(0)
|
||||
#endif /* C_ADD defined */
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
/*# define KISS_FFT_COS(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase))))
|
||||
# define KISS_FFT_SIN(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase))))*/
|
||||
# define KISS_FFT_COS(phase) floor(.5+TWID_MAX*cos (phase))
|
||||
# define KISS_FFT_SIN(phase) floor(.5+TWID_MAX*sin (phase))
|
||||
# define HALF_OF(x) ((x)>>1)
|
||||
#elif defined(USE_SIMD)
|
||||
# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) )
|
||||
# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) )
|
||||
# define HALF_OF(x) ((x)*_mm_set1_ps(.5f))
|
||||
#else
|
||||
# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)
|
||||
# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)
|
||||
# define HALF_OF(x) ((x)*.5f)
|
||||
#endif
|
||||
|
||||
#define kf_cexp(x,phase) \
|
||||
do{ \
|
||||
(x)->r = KISS_FFT_COS(phase);\
|
||||
(x)->i = KISS_FFT_SIN(phase);\
|
||||
}while(0)
|
||||
|
||||
#define kf_cexp2(x,phase) \
|
||||
do{ \
|
||||
(x)->r = TRIG_UPSCALE*celt_cos_norm((phase));\
|
||||
(x)->i = TRIG_UPSCALE*celt_cos_norm((phase)-32768);\
|
||||
}while(0)
|
||||
|
||||
#endif /* KISS_FFT_GUTS_H */
|
||||
261
src/modules/ZoomIt/ZoomIt/rnnoise/arch.h
Normal file
261
src/modules/ZoomIt/ZoomIt/rnnoise/arch.h
Normal file
@@ -0,0 +1,261 @@
|
||||
/* Copyright (c) 2003-2008 Jean-Marc Valin
|
||||
Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */
|
||||
/**
|
||||
@file arch.h
|
||||
@brief Various architecture definitions for CELT
|
||||
*/
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef ARCH_H
|
||||
#define ARCH_H
|
||||
|
||||
#include "opus_types.h"
|
||||
#include "common.h"
|
||||
|
||||
# if !defined(__GNUC_PREREQ)
|
||||
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
|
||||
# define __GNUC_PREREQ(_maj,_min) \
|
||||
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
|
||||
# else
|
||||
# define __GNUC_PREREQ(_maj,_min) 0
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#define CELT_SIG_SCALE 32768.f
|
||||
|
||||
#define celt_fatal(str) _celt_fatal(str, __FILE__, __LINE__);
|
||||
#ifdef ENABLE_ASSERTIONS
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef __GNUC__
|
||||
__attribute__((noreturn))
|
||||
#endif
|
||||
static OPUS_INLINE void _celt_fatal(const char *str, const char *file, int line)
|
||||
{
|
||||
fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str);
|
||||
abort();
|
||||
}
|
||||
#define celt_assert(cond) {if (!(cond)) {celt_fatal("assertion failed: " #cond);}}
|
||||
#define celt_assert2(cond, message) {if (!(cond)) {celt_fatal("assertion failed: " #cond "\n" message);}}
|
||||
#else
|
||||
#define celt_assert(cond)
|
||||
#define celt_assert2(cond, message)
|
||||
#endif
|
||||
|
||||
#define IMUL32(a,b) ((a)*(b))
|
||||
|
||||
#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 16-bit value. */
|
||||
#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */
|
||||
#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 32-bit value. */
|
||||
#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */
|
||||
#define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */
|
||||
#define IMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum int value. */
|
||||
#define UADD32(a,b) ((a)+(b))
|
||||
#define USUB32(a,b) ((a)-(b))
|
||||
|
||||
/* Set this if opus_int64 is a native type of the CPU. */
|
||||
/* Assume that all LP64 architectures have fast 64-bit types; also x86_64
|
||||
(which can be ILP32 for x32) and Win64 (which is LLP64). */
|
||||
#if defined(__x86_64__) || defined(__LP64__) || defined(_WIN64)
|
||||
#define OPUS_FAST_INT64 1
|
||||
#else
|
||||
#define OPUS_FAST_INT64 0
|
||||
#endif
|
||||
|
||||
#define PRINT_MIPS(file)
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
|
||||
typedef opus_int16 opus_val16;
|
||||
typedef opus_int32 opus_val32;
|
||||
typedef opus_int64 opus_val64;
|
||||
|
||||
typedef opus_val32 celt_sig;
|
||||
typedef opus_val16 celt_norm;
|
||||
typedef opus_val32 celt_ener;
|
||||
|
||||
#define Q15ONE 32767
|
||||
|
||||
#define SIG_SHIFT 12
|
||||
/* Safe saturation value for 32-bit signals. Should be less than
|
||||
2^31*(1-0.85) to avoid blowing up on DC at deemphasis.*/
|
||||
#define SIG_SAT (300000000)
|
||||
|
||||
#define NORM_SCALING 16384
|
||||
|
||||
#define DB_SHIFT 10
|
||||
|
||||
#define EPSILON 1
|
||||
#define VERY_SMALL 0
|
||||
#define VERY_LARGE16 ((opus_val16)32767)
|
||||
#define Q15_ONE ((opus_val16)32767)
|
||||
|
||||
#define SCALEIN(a) (a)
|
||||
#define SCALEOUT(a) (a)
|
||||
|
||||
#define ABS16(x) ((x) < 0 ? (-(x)) : (x))
|
||||
#define ABS32(x) ((x) < 0 ? (-(x)) : (x))
|
||||
|
||||
static OPUS_INLINE opus_int16 SAT16(opus_int32 x) {
|
||||
return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x;
|
||||
}
|
||||
|
||||
#ifdef FIXED_DEBUG
|
||||
#include "fixed_debug.h"
|
||||
#else
|
||||
|
||||
#include "fixed_generic.h"
|
||||
|
||||
#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR
|
||||
#include "arm/fixed_arm64.h"
|
||||
#elif OPUS_ARM_INLINE_EDSP
|
||||
#include "arm/fixed_armv5e.h"
|
||||
#elif defined (OPUS_ARM_INLINE_ASM)
|
||||
#include "arm/fixed_armv4.h"
|
||||
#elif defined (BFIN_ASM)
|
||||
#include "fixed_bfin.h"
|
||||
#elif defined (TI_C5X_ASM)
|
||||
#include "fixed_c5x.h"
|
||||
#elif defined (TI_C6X_ASM)
|
||||
#include "fixed_c6x.h"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#else /* FIXED_POINT */
|
||||
|
||||
typedef float opus_val16;
|
||||
typedef float opus_val32;
|
||||
typedef float opus_val64;
|
||||
|
||||
typedef float celt_sig;
|
||||
typedef float celt_norm;
|
||||
typedef float celt_ener;
|
||||
|
||||
#ifdef FLOAT_APPROX
|
||||
/* This code should reliably detect NaN/inf even when -ffast-math is used.
|
||||
Assumes IEEE 754 format. */
|
||||
static OPUS_INLINE int celt_isnan(float x)
|
||||
{
|
||||
union {float f; opus_uint32 i;} in;
|
||||
in.f = x;
|
||||
return ((in.i>>23)&0xFF)==0xFF && (in.i&0x007FFFFF)!=0;
|
||||
}
|
||||
#else
|
||||
#ifdef __FAST_MATH__
|
||||
#error Cannot build libopus with -ffast-math unless FLOAT_APPROX is defined. This could result in crashes on extreme (e.g. NaN) input
|
||||
#endif
|
||||
#define celt_isnan(x) ((x)!=(x))
|
||||
#endif
|
||||
|
||||
#define Q15ONE 1.0f
|
||||
|
||||
#define NORM_SCALING 1.f
|
||||
|
||||
#define EPSILON 1e-15f
|
||||
#define VERY_SMALL 1e-30f
|
||||
#define VERY_LARGE16 1e15f
|
||||
#define Q15_ONE ((opus_val16)1.f)
|
||||
|
||||
/* This appears to be the same speed as C99's fabsf() but it's more portable. */
|
||||
#define ABS16(x) ((float)fabs(x))
|
||||
#define ABS32(x) ((float)fabs(x))
|
||||
|
||||
#define QCONST16(x,bits) (x)
|
||||
#define QCONST32(x,bits) (x)
|
||||
|
||||
#define NEG16(x) (-(x))
|
||||
#define NEG32(x) (-(x))
|
||||
#define NEG32_ovflw(x) (-(x))
|
||||
#define EXTRACT16(x) (x)
|
||||
#define EXTEND32(x) (x)
|
||||
#define SHR16(a,shift) (a)
|
||||
#define SHL16(a,shift) (a)
|
||||
#define SHR32(a,shift) (a)
|
||||
#define SHL32(a,shift) (a)
|
||||
#define PSHR32(a,shift) (a)
|
||||
#define VSHR32(a,shift) (a)
|
||||
|
||||
#define PSHR(a,shift) (a)
|
||||
#define SHR(a,shift) (a)
|
||||
#define SHL(a,shift) (a)
|
||||
#define SATURATE(x,a) (x)
|
||||
#define SATURATE16(x) (x)
|
||||
|
||||
#define ROUND16(a,shift) (a)
|
||||
#define SROUND16(a,shift) (a)
|
||||
#define HALF16(x) (.5f*(x))
|
||||
#define HALF32(x) (.5f*(x))
|
||||
|
||||
#define ADD16(a,b) ((a)+(b))
|
||||
#define SUB16(a,b) ((a)-(b))
|
||||
#define ADD32(a,b) ((a)+(b))
|
||||
#define SUB32(a,b) ((a)-(b))
|
||||
#define ADD32_ovflw(a,b) ((a)+(b))
|
||||
#define SUB32_ovflw(a,b) ((a)-(b))
|
||||
#define MULT16_16_16(a,b) ((a)*(b))
|
||||
#define MULT16_16(a,b) ((opus_val32)(a)*(opus_val32)(b))
|
||||
#define MAC16_16(c,a,b) ((c)+(opus_val32)(a)*(opus_val32)(b))
|
||||
|
||||
#define MULT16_32_Q15(a,b) ((a)*(b))
|
||||
#define MULT16_32_Q16(a,b) ((a)*(b))
|
||||
|
||||
#define MULT32_32_Q31(a,b) ((a)*(b))
|
||||
|
||||
#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b))
|
||||
#define MAC16_32_Q16(c,a,b) ((c)+(a)*(b))
|
||||
|
||||
#define MULT16_16_Q11_32(a,b) ((a)*(b))
|
||||
#define MULT16_16_Q11(a,b) ((a)*(b))
|
||||
#define MULT16_16_Q13(a,b) ((a)*(b))
|
||||
#define MULT16_16_Q14(a,b) ((a)*(b))
|
||||
#define MULT16_16_Q15(a,b) ((a)*(b))
|
||||
#define MULT16_16_P15(a,b) ((a)*(b))
|
||||
#define MULT16_16_P13(a,b) ((a)*(b))
|
||||
#define MULT16_16_P14(a,b) ((a)*(b))
|
||||
#define MULT16_32_P16(a,b) ((a)*(b))
|
||||
|
||||
#define DIV32_16(a,b) (((opus_val32)(a))/(opus_val16)(b))
|
||||
#define DIV32(a,b) (((opus_val32)(a))/(opus_val32)(b))
|
||||
|
||||
#define SCALEIN(a) ((a)*CELT_SIG_SCALE)
|
||||
#define SCALEOUT(a) ((a)*(1/CELT_SIG_SCALE))
|
||||
|
||||
#define SIG2WORD16(x) (x)
|
||||
|
||||
#endif /* !FIXED_POINT */
|
||||
|
||||
#ifndef GLOBAL_STACK_SIZE
|
||||
#ifdef FIXED_POINT
|
||||
#define GLOBAL_STACK_SIZE 120000
|
||||
#else
|
||||
#define GLOBAL_STACK_SIZE 120000
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* ARCH_H */
|
||||
174
src/modules/ZoomIt/ZoomIt/rnnoise/celt_lpc.c
Normal file
174
src/modules/ZoomIt/ZoomIt/rnnoise/celt_lpc.c
Normal file
@@ -0,0 +1,174 @@
|
||||
/* Copyright (c) 2009-2010 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "celt_lpc.h"
|
||||
#include "arch.h"
|
||||
#include "common.h"
|
||||
#include "pitch.h"
|
||||
#include "denoise.h"
|
||||
|
||||
void rnn_lpc(
|
||||
opus_val16 *_lpc, /* out: [0...p-1] LPC coefficients */
|
||||
const opus_val32 *ac, /* in: [0...p] autocorrelation values */
|
||||
int p
|
||||
)
|
||||
{
|
||||
int i, j;
|
||||
opus_val32 r;
|
||||
opus_val32 error = ac[0];
|
||||
#ifdef FIXED_POINT
|
||||
opus_val32 lpc[LPC_ORDER];
|
||||
#else
|
||||
float *lpc = _lpc;
|
||||
#endif
|
||||
|
||||
RNN_CLEAR(lpc, p);
|
||||
if (ac[0] != 0)
|
||||
{
|
||||
for (i = 0; i < p; i++) {
|
||||
/* Sum up this iteration's reflection coefficient */
|
||||
opus_val32 rr = 0;
|
||||
for (j = 0; j < i; j++)
|
||||
rr += MULT32_32_Q31(lpc[j],ac[i - j]);
|
||||
rr += SHR32(ac[i + 1],3);
|
||||
r = -SHL32(rr,3)/error;
|
||||
/* Update LPC coefficients and total error */
|
||||
lpc[i] = SHR32(r,3);
|
||||
for (j = 0; j < (i+1)>>1; j++)
|
||||
{
|
||||
opus_val32 tmp1, tmp2;
|
||||
tmp1 = lpc[j];
|
||||
tmp2 = lpc[i-1-j];
|
||||
lpc[j] = tmp1 + MULT32_32_Q31(r,tmp2);
|
||||
lpc[i-1-j] = tmp2 + MULT32_32_Q31(r,tmp1);
|
||||
}
|
||||
|
||||
error = error - MULT32_32_Q31(MULT32_32_Q31(r,r),error);
|
||||
/* Bail out once we get 30 dB gain */
|
||||
#ifdef FIXED_POINT
|
||||
if (error<SHR32(ac[0],10))
|
||||
break;
|
||||
#else
|
||||
if (error<.001f*ac[0])
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#ifdef FIXED_POINT
|
||||
for (i=0;i<p;i++)
|
||||
_lpc[i] = ROUND16(lpc[i],16);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
int rnn_autocorr(
|
||||
const opus_val16 *x, /* in: [0...n-1] samples x */
|
||||
opus_val32 *ac, /* out: [0...lag-1] ac values */
|
||||
const opus_val16 *window,
|
||||
int overlap,
|
||||
int lag,
|
||||
int n)
|
||||
{
|
||||
opus_val32 d;
|
||||
int i, k;
|
||||
int fastN=n-lag;
|
||||
int shift;
|
||||
const opus_val16 *xptr;
|
||||
opus_val16 xx[PITCH_BUF_SIZE/2];
|
||||
celt_assert(n>0);
|
||||
celt_assert(n<=PITCH_BUF_SIZE/2)
|
||||
celt_assert(overlap>=0);
|
||||
if (overlap == 0)
|
||||
{
|
||||
xptr = x;
|
||||
} else {
|
||||
for (i=0;i<n;i++)
|
||||
xx[i] = x[i];
|
||||
for (i=0;i<overlap;i++)
|
||||
{
|
||||
xx[i] = MULT16_16_Q15(x[i],window[i]);
|
||||
xx[n-i-1] = MULT16_16_Q15(x[n-i-1],window[i]);
|
||||
}
|
||||
xptr = xx;
|
||||
}
|
||||
shift=0;
|
||||
#ifdef FIXED_POINT
|
||||
{
|
||||
opus_val32 ac0;
|
||||
ac0 = 1+(n<<7);
|
||||
if (n&1) ac0 += SHR32(MULT16_16(xptr[0],xptr[0]),9);
|
||||
for(i=(n&1);i<n;i+=2)
|
||||
{
|
||||
ac0 += SHR32(MULT16_16(xptr[i],xptr[i]),9);
|
||||
ac0 += SHR32(MULT16_16(xptr[i+1],xptr[i+1]),9);
|
||||
}
|
||||
|
||||
shift = celt_ilog2(ac0)-30+10;
|
||||
shift = (shift)/2;
|
||||
if (shift>0)
|
||||
{
|
||||
for(i=0;i<n;i++)
|
||||
xx[i] = PSHR32(xptr[i], shift);
|
||||
xptr = xx;
|
||||
} else
|
||||
shift = 0;
|
||||
}
|
||||
#endif
|
||||
rnn_pitch_xcorr(xptr, xptr, ac, fastN, lag+1);
|
||||
for (k=0;k<=lag;k++)
|
||||
{
|
||||
for (i = k+fastN, d = 0; i < n; i++)
|
||||
d = MAC16_16(d, xptr[i], xptr[i-k]);
|
||||
ac[k] += d;
|
||||
}
|
||||
#ifdef FIXED_POINT
|
||||
shift = 2*shift;
|
||||
if (shift<=0)
|
||||
ac[0] += SHL32((opus_int32)1, -shift);
|
||||
if (ac[0] < 268435456)
|
||||
{
|
||||
int shift2 = 29 - EC_ILOG(ac[0]);
|
||||
for (i=0;i<=lag;i++)
|
||||
ac[i] = SHL32(ac[i], shift2);
|
||||
shift -= shift2;
|
||||
} else if (ac[0] >= 536870912)
|
||||
{
|
||||
int shift2=1;
|
||||
if (ac[0] >= 1073741824)
|
||||
shift2++;
|
||||
for (i=0;i<=lag;i++)
|
||||
ac[i] = SHR32(ac[i], shift2);
|
||||
shift += shift2;
|
||||
}
|
||||
#endif
|
||||
|
||||
return shift;
|
||||
}
|
||||
45
src/modules/ZoomIt/ZoomIt/rnnoise/celt_lpc.h
Normal file
45
src/modules/ZoomIt/ZoomIt/rnnoise/celt_lpc.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/* Copyright (c) 2009-2010 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef PLC_H
|
||||
#define PLC_H
|
||||
|
||||
#include "arch.h"
|
||||
#include "common.h"
|
||||
|
||||
#if defined(OPUS_X86_MAY_HAVE_SSE4_1)
|
||||
#include "x86/celt_lpc_sse.h"
|
||||
#endif
|
||||
|
||||
#define LPC_ORDER 24
|
||||
|
||||
void rnn_lpc(opus_val16 *_lpc, const opus_val32 *ac, int p);
|
||||
|
||||
int rnn_autocorr(const opus_val16 *x, opus_val32 *ac,
|
||||
const opus_val16 *window, int overlap, int lag, int n);
|
||||
|
||||
#endif /* PLC_H */
|
||||
56
src/modules/ZoomIt/ZoomIt/rnnoise/common.h
Normal file
56
src/modules/ZoomIt/ZoomIt/rnnoise/common.h
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
#include "stdlib.h"
|
||||
#include "string.h"
|
||||
|
||||
#define RNN_INLINE inline
|
||||
#define OPUS_INLINE inline
|
||||
|
||||
|
||||
/** RNNoise wrapper for malloc(). To do your own dynamic allocation, all you need t
|
||||
o do is replace this function and rnnoise_free */
|
||||
#ifndef OVERRIDE_RNNOISE_ALLOC
|
||||
static RNN_INLINE void *rnnoise_alloc (size_t size)
|
||||
{
|
||||
return malloc(size);
|
||||
}
|
||||
#endif
|
||||
|
||||
/** RNNoise wrapper for free(). To do your own dynamic allocation, all you need to do is replace this function and rnnoise_alloc */
|
||||
#ifndef OVERRIDE_RNNOISE_FREE
|
||||
static RNN_INLINE void rnnoise_free (void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Copy n elements from src to dst. The 0* term provides compile-time type checking */
|
||||
#ifndef OVERRIDE_RNN_COPY
|
||||
#define RNN_COPY(dst, src, n) (memcpy((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) ))
|
||||
#endif
|
||||
|
||||
/** Copy n elements from src to dst, allowing overlapping regions. The 0* term
|
||||
provides compile-time type checking */
|
||||
#ifndef OVERRIDE_RNN_MOVE
|
||||
#define RNN_MOVE(dst, src, n) (memmove((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) ))
|
||||
#endif
|
||||
|
||||
/** Set n elements of dst to zero */
|
||||
#ifndef OVERRIDE_RNN_CLEAR
|
||||
#define RNN_CLEAR(dst, n) (memset((dst), 0, (n)*sizeof(*(dst))))
|
||||
#endif
|
||||
|
||||
# if !defined(OPUS_GNUC_PREREQ)
|
||||
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
|
||||
# define OPUS_GNUC_PREREQ(_maj,_min) \
|
||||
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
|
||||
# else
|
||||
# define OPUS_GNUC_PREREQ(_maj,_min) 0
|
||||
# endif
|
||||
# endif
|
||||
|
||||
|
||||
#endif
|
||||
53
src/modules/ZoomIt/ZoomIt/rnnoise/cpu_support.h
Normal file
53
src/modules/ZoomIt/ZoomIt/rnnoise/cpu_support.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/* Copyright (c) 2010 Xiph.Org Foundation
|
||||
* Copyright (c) 2013 Parrot */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef CPU_SUPPORT_H
|
||||
#define CPU_SUPPORT_H
|
||||
|
||||
#include "opus_types.h"
|
||||
#include "common.h"
|
||||
|
||||
#ifdef RNN_ENABLE_X86_RTCD
|
||||
|
||||
#include "x86/x86cpu.h"
|
||||
/* We currently support 5 x86 variants:
|
||||
* arch[0] -> sse2
|
||||
* arch[1] -> sse4.1
|
||||
* arch[2] -> avx2
|
||||
*/
|
||||
#define OPUS_ARCHMASK 3
|
||||
int rnn_select_arch(void);
|
||||
|
||||
#else
|
||||
#define OPUS_ARCHMASK 0
|
||||
|
||||
static OPUS_INLINE int rnn_select_arch(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
505
src/modules/ZoomIt/ZoomIt/rnnoise/denoise.c
Normal file
505
src/modules/ZoomIt/ZoomIt/rnnoise/denoise.c
Normal file
@@ -0,0 +1,505 @@
|
||||
/* Copyright (c) 2024 Jean-Marc Valin
|
||||
* Copyright (c) 2018 Gregor Richards
|
||||
* Copyright (c) 2017 Mozilla */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "kiss_fft.h"
|
||||
#include "common.h"
|
||||
#include "denoise.h"
|
||||
#include <math.h>
|
||||
#include "rnnoise.h"
|
||||
#include "pitch.h"
|
||||
#include "arch.h"
|
||||
#include "rnn.h"
|
||||
#include "cpu_support.h"
|
||||
|
||||
#define SQUARE(x) ((x)*(x))
|
||||
|
||||
|
||||
#ifndef TRAINING
|
||||
#define TRAINING 0
|
||||
#endif
|
||||
|
||||
|
||||
/* ERB bandwidths going in reverse from 20 kHz and then replacing the 700 and 800
|
||||
with just 750 because having 32 bands is convenient for the DNN.
|
||||
B(1)=400;
|
||||
for k=2:35
|
||||
B(k) = B(k-1) - max(2, round(24.7*(4.37*B(k-1)/20+1)/50));
|
||||
end
|
||||
printf("%d, ", B(end:-1:1));
|
||||
printf("\n")
|
||||
*/
|
||||
const int eband20ms[NB_BANDS+2] = {
|
||||
/*0 100 200 300 400 500 600 750 900 1.1 1.2 1.4 1.6 1.8 2.1 2.4 2.7 3.0 3.4 3.9 4.4 4.9 5.5 6.2 7.0 7.9 8.8 9.9 11.2 12.6 14.1 15.9 17.8 20.0*/
|
||||
0, 2, 4, 6, 8, 10, 12, 15, 18, 21, 24, 28, 32, 36, 41, 47, 53, 60, 68, 77, 87, 98, 110, 124, 140, 157, 176, 198, 223, 251, 282, 317, 356, 400};
|
||||
|
||||
|
||||
struct DenoiseState {
|
||||
RNNoise model;
|
||||
#if !TRAINING
|
||||
int arch;
|
||||
#endif
|
||||
float analysis_mem[FRAME_SIZE];
|
||||
int memid;
|
||||
float synthesis_mem[FRAME_SIZE];
|
||||
float pitch_buf[PITCH_BUF_SIZE];
|
||||
float pitch_enh_buf[PITCH_BUF_SIZE];
|
||||
float last_gain;
|
||||
int last_period;
|
||||
float mem_hp_x[2];
|
||||
float lastg[NB_BANDS];
|
||||
RNNState rnn;
|
||||
kiss_fft_cpx delayed_X[FREQ_SIZE];
|
||||
kiss_fft_cpx delayed_P[FREQ_SIZE];
|
||||
float delayed_Ex[NB_BANDS], delayed_Ep[NB_BANDS];
|
||||
float delayed_Exp[NB_BANDS];
|
||||
|
||||
};
|
||||
|
||||
static void compute_band_energy(float *bandE, const kiss_fft_cpx *X) {
|
||||
int i;
|
||||
float sum[NB_BANDS+2] = {0};
|
||||
for (i=0;i<NB_BANDS+1;i++)
|
||||
{
|
||||
int j;
|
||||
int band_size;
|
||||
band_size = eband20ms[i+1]-eband20ms[i];
|
||||
for (j=0;j<band_size;j++) {
|
||||
float tmp;
|
||||
float frac = (float)j/band_size;
|
||||
tmp = SQUARE(X[eband20ms[i] + j].r);
|
||||
tmp += SQUARE(X[eband20ms[i] + j].i);
|
||||
sum[i] += (1-frac)*tmp;
|
||||
sum[i+1] += frac*tmp;
|
||||
}
|
||||
}
|
||||
sum[1] = (sum[0]+sum[1])*2/3;
|
||||
sum[NB_BANDS] = (sum[NB_BANDS]+sum[NB_BANDS+1])*2/3;
|
||||
for (i=0;i<NB_BANDS;i++)
|
||||
{
|
||||
bandE[i] = sum[i+1];
|
||||
}
|
||||
}
|
||||
|
||||
static void compute_band_corr(float *bandE, const kiss_fft_cpx *X, const kiss_fft_cpx *P) {
|
||||
int i;
|
||||
float sum[NB_BANDS+2] = {0};
|
||||
for (i=0;i<NB_BANDS+1;i++)
|
||||
{
|
||||
int j;
|
||||
int band_size;
|
||||
band_size = eband20ms[i+1]-eband20ms[i];
|
||||
for (j=0;j<band_size;j++) {
|
||||
float tmp;
|
||||
float frac = (float)j/band_size;
|
||||
tmp = X[eband20ms[i] + j].r * P[eband20ms[i] + j].r;
|
||||
tmp += X[eband20ms[i] + j].i * P[eband20ms[i] + j].i;
|
||||
sum[i] += (1-frac)*tmp;
|
||||
sum[i+1] += frac*tmp;
|
||||
}
|
||||
}
|
||||
sum[1] = (sum[0]+sum[1])*2/3;
|
||||
sum[NB_BANDS] = (sum[NB_BANDS]+sum[NB_BANDS+1])*2/3;
|
||||
for (i=0;i<NB_BANDS;i++)
|
||||
{
|
||||
bandE[i] = sum[i+1];
|
||||
}
|
||||
}
|
||||
|
||||
static void interp_band_gain(float *g, const float *bandE) {
|
||||
int i,j;
|
||||
memset(g, 0, FREQ_SIZE);
|
||||
for (i=1;i<NB_BANDS;i++)
|
||||
{
|
||||
int band_size;
|
||||
band_size = eband20ms[i+1]-eband20ms[i];
|
||||
for (j=0;j<band_size;j++) {
|
||||
float frac = (float)j/band_size;
|
||||
g[eband20ms[i] + j] = (1-frac)*bandE[i-1] + frac*bandE[i];
|
||||
}
|
||||
}
|
||||
for (j=0;j<eband20ms[1];j++) g[j] = bandE[0];
|
||||
for (j=eband20ms[NB_BANDS];j<eband20ms[NB_BANDS+1];j++) g[j] = bandE[NB_BANDS-1];
|
||||
}
|
||||
|
||||
extern const float rnn_dct_table[];
|
||||
extern const kiss_fft_state rnn_kfft;
|
||||
extern const float rnn_half_window[];
|
||||
|
||||
static void dct(float *out, const float *in) {
|
||||
int i;
|
||||
for (i=0;i<NB_BANDS;i++) {
|
||||
int j;
|
||||
float sum = 0;
|
||||
for (j=0;j<NB_BANDS;j++) {
|
||||
sum += in[j] * rnn_dct_table[j*NB_BANDS + i];
|
||||
}
|
||||
out[i] = sum*sqrt(2./22);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void idct(float *out, const float *in) {
|
||||
int i;
|
||||
for (i=0;i<NB_BANDS;i++) {
|
||||
int j;
|
||||
float sum = 0;
|
||||
for (j=0;j<NB_BANDS;j++) {
|
||||
sum += in[j] * rnn_dct_table[i*NB_BANDS + j];
|
||||
}
|
||||
out[i] = sum*sqrt(2./22);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void forward_transform(kiss_fft_cpx *out, const float *in) {
|
||||
int i;
|
||||
kiss_fft_cpx x[WINDOW_SIZE];
|
||||
kiss_fft_cpx y[WINDOW_SIZE];
|
||||
for (i=0;i<WINDOW_SIZE;i++) {
|
||||
x[i].r = in[i];
|
||||
x[i].i = 0;
|
||||
}
|
||||
rnn_fft(&rnn_kfft, x, y, 0);
|
||||
for (i=0;i<FREQ_SIZE;i++) {
|
||||
out[i] = y[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void inverse_transform(float *out, const kiss_fft_cpx *in) {
|
||||
int i;
|
||||
kiss_fft_cpx x[WINDOW_SIZE];
|
||||
kiss_fft_cpx y[WINDOW_SIZE];
|
||||
for (i=0;i<FREQ_SIZE;i++) {
|
||||
x[i] = in[i];
|
||||
}
|
||||
for (;i<WINDOW_SIZE;i++) {
|
||||
x[i].r = x[WINDOW_SIZE - i].r;
|
||||
x[i].i = -x[WINDOW_SIZE - i].i;
|
||||
}
|
||||
rnn_fft(&rnn_kfft, x, y, 0);
|
||||
/* output in reverse order for IFFT. */
|
||||
out[0] = WINDOW_SIZE*y[0].r;
|
||||
for (i=1;i<WINDOW_SIZE;i++) {
|
||||
out[i] = WINDOW_SIZE*y[WINDOW_SIZE - i].r;
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_window(float *x) {
|
||||
int i;
|
||||
for (i=0;i<FRAME_SIZE;i++) {
|
||||
x[i] *= rnn_half_window[i];
|
||||
x[WINDOW_SIZE - 1 - i] *= rnn_half_window[i];
|
||||
}
|
||||
}
|
||||
|
||||
struct RNNModel {
|
||||
/* Set either blob or const_blob. */
|
||||
const void *const_blob;
|
||||
void *blob;
|
||||
int blob_len;
|
||||
FILE *file;
|
||||
};
|
||||
|
||||
RNNModel *rnnoise_model_from_buffer(const void *ptr, int len) {
|
||||
RNNModel *model;
|
||||
model = malloc(sizeof(*model));
|
||||
model->blob = NULL;
|
||||
model->const_blob = ptr;
|
||||
model->blob_len = len;
|
||||
return model;
|
||||
}
|
||||
|
||||
RNNModel *rnnoise_model_from_filename(const char *filename) {
|
||||
RNNModel *model;
|
||||
FILE *f = fopen(filename, "rb");
|
||||
model = rnnoise_model_from_file(f);
|
||||
model->file = f;
|
||||
return model;
|
||||
}
|
||||
|
||||
RNNModel *rnnoise_model_from_file(FILE *f) {
|
||||
RNNModel *model;
|
||||
model = malloc(sizeof(*model));
|
||||
model->file = NULL;
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
model->blob_len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
model->const_blob = NULL;
|
||||
model->blob = malloc(model->blob_len);
|
||||
if (fread(model->blob, model->blob_len, 1, f) != 1)
|
||||
{
|
||||
rnnoise_model_free(model);
|
||||
return NULL;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
void rnnoise_model_free(RNNModel *model) {
|
||||
if (model->file != NULL) fclose(model->file);
|
||||
if (model->blob != NULL) free(model->blob);
|
||||
free(model);
|
||||
}
|
||||
|
||||
int rnnoise_get_size(void) {
|
||||
return sizeof(DenoiseState);
|
||||
}
|
||||
|
||||
int rnnoise_get_frame_size(void) {
|
||||
return FRAME_SIZE;
|
||||
}
|
||||
|
||||
int rnnoise_init(DenoiseState *st, RNNModel *model) {
|
||||
memset(st, 0, sizeof(*st));
|
||||
#if !TRAINING
|
||||
if (model != NULL) {
|
||||
WeightArray *list;
|
||||
int ret = 1;
|
||||
parse_weights(&list, model->blob ? model->blob : model->const_blob, model->blob_len);
|
||||
if (list != NULL) {
|
||||
ret = init_rnnoise(&st->model, list);
|
||||
opus_free(list);
|
||||
}
|
||||
if (ret != 0) return -1;
|
||||
}
|
||||
#ifndef USE_WEIGHTS_FILE
|
||||
else {
|
||||
int ret = init_rnnoise(&st->model, rnnoise_arrays);
|
||||
if (ret != 0) return -1;
|
||||
}
|
||||
#endif
|
||||
st->arch = rnn_select_arch();
|
||||
#else
|
||||
(void)model;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
DenoiseState *rnnoise_create(RNNModel *model) {
|
||||
int ret;
|
||||
DenoiseState *st;
|
||||
st = malloc(rnnoise_get_size());
|
||||
ret = rnnoise_init(st, model);
|
||||
if (ret != 0) {
|
||||
free(st);
|
||||
return NULL;
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
void rnnoise_destroy(DenoiseState *st) {
|
||||
free(st);
|
||||
}
|
||||
|
||||
#if TRAINING
|
||||
extern int lowpass;
|
||||
extern int band_lp;
|
||||
#endif
|
||||
|
||||
void rnn_frame_analysis(DenoiseState *st, kiss_fft_cpx *X, float *Ex, const float *in) {
|
||||
int i;
|
||||
float x[WINDOW_SIZE];
|
||||
RNN_COPY(x, st->analysis_mem, FRAME_SIZE);
|
||||
for (i=0;i<FRAME_SIZE;i++) x[FRAME_SIZE + i] = in[i];
|
||||
RNN_COPY(st->analysis_mem, in, FRAME_SIZE);
|
||||
apply_window(x);
|
||||
forward_transform(X, x);
|
||||
#if TRAINING
|
||||
for (i=lowpass;i<FREQ_SIZE;i++)
|
||||
X[i].r = X[i].i = 0;
|
||||
#endif
|
||||
compute_band_energy(Ex, X);
|
||||
}
|
||||
|
||||
int rnn_compute_frame_features(DenoiseState *st, kiss_fft_cpx *X, kiss_fft_cpx *P,
|
||||
float *Ex, float *Ep, float *Exp, float *features, const float *in) {
|
||||
int i;
|
||||
float E = 0;
|
||||
float Ly[NB_BANDS];
|
||||
float p[WINDOW_SIZE];
|
||||
float pitch_buf[PITCH_BUF_SIZE>>1];
|
||||
int pitch_index;
|
||||
float gain;
|
||||
float *(pre[1]);
|
||||
float follow, logMax;
|
||||
rnn_frame_analysis(st, X, Ex, in);
|
||||
RNN_MOVE(st->pitch_buf, &st->pitch_buf[FRAME_SIZE], PITCH_BUF_SIZE-FRAME_SIZE);
|
||||
RNN_COPY(&st->pitch_buf[PITCH_BUF_SIZE-FRAME_SIZE], in, FRAME_SIZE);
|
||||
pre[0] = &st->pitch_buf[0];
|
||||
rnn_pitch_downsample(pre, pitch_buf, PITCH_BUF_SIZE, 1);
|
||||
rnn_pitch_search(pitch_buf+(PITCH_MAX_PERIOD>>1), pitch_buf, PITCH_FRAME_SIZE,
|
||||
PITCH_MAX_PERIOD-3*PITCH_MIN_PERIOD, &pitch_index);
|
||||
pitch_index = PITCH_MAX_PERIOD-pitch_index;
|
||||
|
||||
gain = rnn_remove_doubling(pitch_buf, PITCH_MAX_PERIOD, PITCH_MIN_PERIOD,
|
||||
PITCH_FRAME_SIZE, &pitch_index, st->last_period, st->last_gain);
|
||||
st->last_period = pitch_index;
|
||||
st->last_gain = gain;
|
||||
for (i=0;i<WINDOW_SIZE;i++)
|
||||
p[i] = st->pitch_buf[PITCH_BUF_SIZE-WINDOW_SIZE-pitch_index+i];
|
||||
apply_window(p);
|
||||
forward_transform(P, p);
|
||||
compute_band_energy(Ep, P);
|
||||
compute_band_corr(Exp, X, P);
|
||||
for (i=0;i<NB_BANDS;i++) Exp[i] = Exp[i]/sqrt(.001+Ex[i]*Ep[i]);
|
||||
dct(&features[NB_BANDS], Exp);
|
||||
features[2*NB_BANDS] = .01*(pitch_index-300);
|
||||
logMax = -2;
|
||||
follow = -2;
|
||||
for (i=0;i<NB_BANDS;i++) {
|
||||
Ly[i] = log10(1e-2+Ex[i]);
|
||||
Ly[i] = MAX16(logMax-7, MAX16(follow-1.5, Ly[i]));
|
||||
logMax = MAX16(logMax, Ly[i]);
|
||||
follow = MAX16(follow-1.5, Ly[i]);
|
||||
E += Ex[i];
|
||||
}
|
||||
if (!TRAINING && E < 0.04) {
|
||||
/* If there's no audio, avoid messing up the state. */
|
||||
RNN_CLEAR(features, NB_FEATURES);
|
||||
return 1;
|
||||
}
|
||||
dct(features, Ly);
|
||||
features[0] -= 12;
|
||||
features[1] -= 4;
|
||||
return TRAINING && E < 0.1;
|
||||
}
|
||||
|
||||
static void frame_synthesis(DenoiseState *st, float *out, const kiss_fft_cpx *y) {
|
||||
float x[WINDOW_SIZE];
|
||||
int i;
|
||||
inverse_transform(x, y);
|
||||
apply_window(x);
|
||||
for (i=0;i<FRAME_SIZE;i++) out[i] = x[i] + st->synthesis_mem[i];
|
||||
RNN_COPY(st->synthesis_mem, &x[FRAME_SIZE], FRAME_SIZE);
|
||||
}
|
||||
|
||||
void rnn_biquad(float *y, float mem[2], const float *x, const float *b, const float *a, int N) {
|
||||
int i;
|
||||
for (i=0;i<N;i++) {
|
||||
float xi, yi;
|
||||
xi = x[i];
|
||||
yi = x[i] + mem[0];
|
||||
mem[0] = mem[1] + (b[0]*(double)xi - a[0]*(double)yi);
|
||||
mem[1] = (b[1]*(double)xi - a[1]*(double)yi);
|
||||
y[i] = yi;
|
||||
}
|
||||
}
|
||||
|
||||
void rnn_pitch_filter(kiss_fft_cpx *X, const kiss_fft_cpx *P, const float *Ex, const float *Ep,
|
||||
const float *Exp, const float *g) {
|
||||
int i;
|
||||
float r[NB_BANDS];
|
||||
float rf[FREQ_SIZE] = {0};
|
||||
float newE[NB_BANDS];
|
||||
float norm[NB_BANDS];
|
||||
float normf[FREQ_SIZE]={0};
|
||||
for (i=0;i<NB_BANDS;i++) {
|
||||
#if 0
|
||||
if (Exp[i]>g[i]) r[i] = 1;
|
||||
else r[i] = Exp[i]*(1-g[i])/(.001 + g[i]*(1-Exp[i]));
|
||||
r[i] = MIN16(1, MAX16(0, r[i]));
|
||||
#else
|
||||
if (Exp[i]>g[i]) r[i] = 1;
|
||||
else r[i] = SQUARE(Exp[i])*(1-SQUARE(g[i]))/(.001 + SQUARE(g[i])*(1-SQUARE(Exp[i])));
|
||||
r[i] = sqrt(MIN16(1, MAX16(0, r[i])));
|
||||
#endif
|
||||
r[i] *= sqrt(Ex[i]/(1e-8+Ep[i]));
|
||||
}
|
||||
interp_band_gain(rf, r);
|
||||
for (i=0;i<FREQ_SIZE;i++) {
|
||||
X[i].r += rf[i]*P[i].r;
|
||||
X[i].i += rf[i]*P[i].i;
|
||||
}
|
||||
compute_band_energy(newE, X);
|
||||
for (i=0;i<NB_BANDS;i++) {
|
||||
norm[i] = sqrt(Ex[i]/(1e-8+newE[i]));
|
||||
}
|
||||
interp_band_gain(normf, norm);
|
||||
for (i=0;i<FREQ_SIZE;i++) {
|
||||
X[i].r *= normf[i];
|
||||
X[i].i *= normf[i];
|
||||
}
|
||||
}
|
||||
|
||||
float rnnoise_process_frame(DenoiseState *st, float *out, const float *in) {
|
||||
int i;
|
||||
kiss_fft_cpx X[FREQ_SIZE];
|
||||
kiss_fft_cpx P[FREQ_SIZE];
|
||||
float x[FRAME_SIZE];
|
||||
float Ex[NB_BANDS], Ep[NB_BANDS];
|
||||
float Exp[NB_BANDS];
|
||||
float features[NB_FEATURES];
|
||||
float g[NB_BANDS];
|
||||
float gf[FREQ_SIZE]={1};
|
||||
float vad_prob = 0;
|
||||
int silence;
|
||||
static const float a_hp[2] = {-1.99599, 0.99600};
|
||||
static const float b_hp[2] = {-2, 1};
|
||||
rnn_biquad(x, st->mem_hp_x, in, b_hp, a_hp, FRAME_SIZE);
|
||||
silence = rnn_compute_frame_features(st, X, P, Ex, Ep, Exp, features, x);
|
||||
|
||||
if (!silence) {
|
||||
#if !TRAINING
|
||||
compute_rnn(&st->model, &st->rnn, g, &vad_prob, features, st->arch);
|
||||
#endif
|
||||
rnn_pitch_filter(st->delayed_X, st->delayed_P, st->delayed_Ex, st->delayed_Ep, st->delayed_Exp, g);
|
||||
for (i=0;i<NB_BANDS;i++) {
|
||||
float alpha = .6f;
|
||||
/* Cap the decay at 0.6 per frame, corresponding to an RT60 of 135 ms.
|
||||
That avoids unnaturally quick attenuation. */
|
||||
g[i] = MAX16(g[i], alpha*st->lastg[i]);
|
||||
/* Compensate for energy change across frame when computing the threshold gain.
|
||||
Avoids leaking noise when energy increases (e.g. transient noise). */
|
||||
st->lastg[i] = MIN16(1.f, g[i]*(st->delayed_Ex[i]+1e-3)/(Ex[i]+1e-3));
|
||||
}
|
||||
interp_band_gain(gf, g);
|
||||
#if 1
|
||||
for (i=0;i<FREQ_SIZE;i++) {
|
||||
st->delayed_X[i].r *= gf[i];
|
||||
st->delayed_X[i].i *= gf[i];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
frame_synthesis(st, out, st->delayed_X);
|
||||
|
||||
RNN_COPY(st->delayed_X, X, FREQ_SIZE);
|
||||
RNN_COPY(st->delayed_P, P, FREQ_SIZE);
|
||||
RNN_COPY(st->delayed_Ex, Ex, NB_BANDS);
|
||||
RNN_COPY(st->delayed_Ep, Ep, NB_BANDS);
|
||||
RNN_COPY(st->delayed_Exp, Exp, NB_BANDS);
|
||||
return vad_prob;
|
||||
}
|
||||
|
||||
56
src/modules/ZoomIt/ZoomIt/rnnoise/denoise.h
Normal file
56
src/modules/ZoomIt/ZoomIt/rnnoise/denoise.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/* Copyright (c) 2017 Mozilla */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "rnnoise.h"
|
||||
#include "kiss_fft.h"
|
||||
#include "nnet.h"
|
||||
|
||||
#define FRAME_SIZE 480
|
||||
#define WINDOW_SIZE (2*FRAME_SIZE)
|
||||
#define FREQ_SIZE (FRAME_SIZE + 1)
|
||||
#define NB_BANDS 32
|
||||
#define NB_FEATURES (2*NB_BANDS+1)
|
||||
|
||||
|
||||
#define PITCH_MIN_PERIOD 60
|
||||
#define PITCH_MAX_PERIOD 768
|
||||
#define PITCH_FRAME_SIZE 960
|
||||
#define PITCH_BUF_SIZE (PITCH_MAX_PERIOD+PITCH_FRAME_SIZE)
|
||||
|
||||
extern const WeightArray rnnoise_arrays[];
|
||||
|
||||
extern const int eband20ms[];
|
||||
|
||||
|
||||
void rnn_biquad(float *y, float mem[2], const float *x, const float *b, const float *a, int N);
|
||||
|
||||
void rnn_pitch_filter(kiss_fft_cpx *X, const kiss_fft_cpx *P, const float *Ex, const float *Ep,
|
||||
const float *Exp, const float *g);
|
||||
|
||||
void rnn_frame_analysis(DenoiseState *st, kiss_fft_cpx *X, float *Ex, const float *in);
|
||||
|
||||
int rnn_compute_frame_features(DenoiseState *st, kiss_fft_cpx *X, kiss_fft_cpx *P,
|
||||
float *Ex, float *Ep, float *Exp, float *features, const float *in);
|
||||
601
src/modules/ZoomIt/ZoomIt/rnnoise/kiss_fft.c
Normal file
601
src/modules/ZoomIt/ZoomIt/rnnoise/kiss_fft.c
Normal file
@@ -0,0 +1,601 @@
|
||||
/*Copyright (c) 2003-2004, Mark Borgerding
|
||||
Lots of modifications by Jean-Marc Valin
|
||||
Copyright (c) 2005-2007, Xiph.Org Foundation
|
||||
Copyright (c) 2008, Xiph.Org Foundation, CSIRO
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.*/
|
||||
|
||||
/* This code is originally from Mark Borgerding's KISS-FFT but has been
|
||||
heavily modified to better suit Opus */
|
||||
|
||||
#ifndef SKIP_CONFIG_H
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "_kiss_fft_guts.h"
|
||||
#define CUSTOM_MODES
|
||||
|
||||
/* The guts header contains all the multiplication and addition macros that are defined for
|
||||
complex numbers. It also declares the kf_ internal functions.
|
||||
*/
|
||||
|
||||
static void kf_bfly2(
|
||||
kiss_fft_cpx * Fout,
|
||||
int m,
|
||||
int N
|
||||
)
|
||||
{
|
||||
kiss_fft_cpx * Fout2;
|
||||
int i;
|
||||
(void)m;
|
||||
#ifdef CUSTOM_MODES
|
||||
if (m==1)
|
||||
{
|
||||
celt_assert(m==1);
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
kiss_fft_cpx t;
|
||||
Fout2 = Fout + 1;
|
||||
t = *Fout2;
|
||||
C_SUB( *Fout2 , *Fout , t );
|
||||
C_ADDTO( *Fout , t );
|
||||
Fout += 2;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
opus_val16 tw;
|
||||
tw = QCONST16(0.7071067812f, 15);
|
||||
/* We know that m==4 here because the radix-2 is just after a radix-4 */
|
||||
celt_assert(m==4);
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
kiss_fft_cpx t;
|
||||
Fout2 = Fout + 4;
|
||||
t = Fout2[0];
|
||||
C_SUB( Fout2[0] , Fout[0] , t );
|
||||
C_ADDTO( Fout[0] , t );
|
||||
|
||||
t.r = S_MUL(ADD32_ovflw(Fout2[1].r, Fout2[1].i), tw);
|
||||
t.i = S_MUL(SUB32_ovflw(Fout2[1].i, Fout2[1].r), tw);
|
||||
C_SUB( Fout2[1] , Fout[1] , t );
|
||||
C_ADDTO( Fout[1] , t );
|
||||
|
||||
t.r = Fout2[2].i;
|
||||
t.i = -Fout2[2].r;
|
||||
C_SUB( Fout2[2] , Fout[2] , t );
|
||||
C_ADDTO( Fout[2] , t );
|
||||
|
||||
t.r = S_MUL(SUB32_ovflw(Fout2[3].i, Fout2[3].r), tw);
|
||||
t.i = S_MUL(NEG32_ovflw(ADD32_ovflw(Fout2[3].i, Fout2[3].r)), tw);
|
||||
C_SUB( Fout2[3] , Fout[3] , t );
|
||||
C_ADDTO( Fout[3] , t );
|
||||
Fout += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void kf_bfly4(
|
||||
kiss_fft_cpx * Fout,
|
||||
const size_t fstride,
|
||||
const kiss_fft_state *st,
|
||||
int m,
|
||||
int N,
|
||||
int mm
|
||||
)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (m==1)
|
||||
{
|
||||
/* Degenerate case where all the twiddles are 1. */
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
kiss_fft_cpx scratch0, scratch1;
|
||||
|
||||
C_SUB( scratch0 , *Fout, Fout[2] );
|
||||
C_ADDTO(*Fout, Fout[2]);
|
||||
C_ADD( scratch1 , Fout[1] , Fout[3] );
|
||||
C_SUB( Fout[2], *Fout, scratch1 );
|
||||
C_ADDTO( *Fout , scratch1 );
|
||||
C_SUB( scratch1 , Fout[1] , Fout[3] );
|
||||
|
||||
Fout[1].r = ADD32_ovflw(scratch0.r, scratch1.i);
|
||||
Fout[1].i = SUB32_ovflw(scratch0.i, scratch1.r);
|
||||
Fout[3].r = SUB32_ovflw(scratch0.r, scratch1.i);
|
||||
Fout[3].i = ADD32_ovflw(scratch0.i, scratch1.r);
|
||||
Fout+=4;
|
||||
}
|
||||
} else {
|
||||
int j;
|
||||
kiss_fft_cpx scratch[6];
|
||||
const kiss_twiddle_cpx *tw1,*tw2,*tw3;
|
||||
const int m2=2*m;
|
||||
const int m3=3*m;
|
||||
kiss_fft_cpx * Fout_beg = Fout;
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
Fout = Fout_beg + i*mm;
|
||||
tw3 = tw2 = tw1 = st->twiddles;
|
||||
/* m is guaranteed to be a multiple of 4. */
|
||||
for (j=0;j<m;j++)
|
||||
{
|
||||
C_MUL(scratch[0],Fout[m] , *tw1 );
|
||||
C_MUL(scratch[1],Fout[m2] , *tw2 );
|
||||
C_MUL(scratch[2],Fout[m3] , *tw3 );
|
||||
|
||||
C_SUB( scratch[5] , *Fout, scratch[1] );
|
||||
C_ADDTO(*Fout, scratch[1]);
|
||||
C_ADD( scratch[3] , scratch[0] , scratch[2] );
|
||||
C_SUB( scratch[4] , scratch[0] , scratch[2] );
|
||||
C_SUB( Fout[m2], *Fout, scratch[3] );
|
||||
tw1 += fstride;
|
||||
tw2 += fstride*2;
|
||||
tw3 += fstride*3;
|
||||
C_ADDTO( *Fout , scratch[3] );
|
||||
|
||||
Fout[m].r = ADD32_ovflw(scratch[5].r, scratch[4].i);
|
||||
Fout[m].i = SUB32_ovflw(scratch[5].i, scratch[4].r);
|
||||
Fout[m3].r = SUB32_ovflw(scratch[5].r, scratch[4].i);
|
||||
Fout[m3].i = ADD32_ovflw(scratch[5].i, scratch[4].r);
|
||||
++Fout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifndef RADIX_TWO_ONLY
|
||||
|
||||
static void kf_bfly3(
|
||||
kiss_fft_cpx * Fout,
|
||||
const size_t fstride,
|
||||
const kiss_fft_state *st,
|
||||
int m,
|
||||
int N,
|
||||
int mm
|
||||
)
|
||||
{
|
||||
int i;
|
||||
size_t k;
|
||||
const size_t m2 = 2*m;
|
||||
const kiss_twiddle_cpx *tw1,*tw2;
|
||||
kiss_fft_cpx scratch[5];
|
||||
kiss_twiddle_cpx epi3;
|
||||
|
||||
kiss_fft_cpx * Fout_beg = Fout;
|
||||
#ifdef FIXED_POINT
|
||||
/*epi3.r = -16384;*/ /* Unused */
|
||||
epi3.i = -28378;
|
||||
#else
|
||||
epi3 = st->twiddles[fstride*m];
|
||||
#endif
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
Fout = Fout_beg + i*mm;
|
||||
tw1=tw2=st->twiddles;
|
||||
/* For non-custom modes, m is guaranteed to be a multiple of 4. */
|
||||
k=m;
|
||||
do {
|
||||
|
||||
C_MUL(scratch[1],Fout[m] , *tw1);
|
||||
C_MUL(scratch[2],Fout[m2] , *tw2);
|
||||
|
||||
C_ADD(scratch[3],scratch[1],scratch[2]);
|
||||
C_SUB(scratch[0],scratch[1],scratch[2]);
|
||||
tw1 += fstride;
|
||||
tw2 += fstride*2;
|
||||
|
||||
Fout[m].r = SUB32_ovflw(Fout->r, HALF_OF(scratch[3].r));
|
||||
Fout[m].i = SUB32_ovflw(Fout->i, HALF_OF(scratch[3].i));
|
||||
|
||||
C_MULBYSCALAR( scratch[0] , epi3.i );
|
||||
|
||||
C_ADDTO(*Fout,scratch[3]);
|
||||
|
||||
Fout[m2].r = ADD32_ovflw(Fout[m].r, scratch[0].i);
|
||||
Fout[m2].i = SUB32_ovflw(Fout[m].i, scratch[0].r);
|
||||
|
||||
Fout[m].r = SUB32_ovflw(Fout[m].r, scratch[0].i);
|
||||
Fout[m].i = ADD32_ovflw(Fout[m].i, scratch[0].r);
|
||||
|
||||
++Fout;
|
||||
} while(--k);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifndef OVERRIDE_kf_bfly5
|
||||
static void kf_bfly5(
|
||||
kiss_fft_cpx * Fout,
|
||||
const size_t fstride,
|
||||
const kiss_fft_state *st,
|
||||
int m,
|
||||
int N,
|
||||
int mm
|
||||
)
|
||||
{
|
||||
kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4;
|
||||
int i, u;
|
||||
kiss_fft_cpx scratch[13];
|
||||
const kiss_twiddle_cpx *tw;
|
||||
kiss_twiddle_cpx ya,yb;
|
||||
kiss_fft_cpx * Fout_beg = Fout;
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
ya.r = 10126;
|
||||
ya.i = -31164;
|
||||
yb.r = -26510;
|
||||
yb.i = -19261;
|
||||
#else
|
||||
ya = st->twiddles[fstride*m];
|
||||
yb = st->twiddles[fstride*2*m];
|
||||
#endif
|
||||
tw=st->twiddles;
|
||||
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
Fout = Fout_beg + i*mm;
|
||||
Fout0=Fout;
|
||||
Fout1=Fout0+m;
|
||||
Fout2=Fout0+2*m;
|
||||
Fout3=Fout0+3*m;
|
||||
Fout4=Fout0+4*m;
|
||||
|
||||
/* For non-custom modes, m is guaranteed to be a multiple of 4. */
|
||||
for ( u=0; u<m; ++u ) {
|
||||
scratch[0] = *Fout0;
|
||||
|
||||
C_MUL(scratch[1] ,*Fout1, tw[u*fstride]);
|
||||
C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]);
|
||||
C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]);
|
||||
C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]);
|
||||
|
||||
C_ADD( scratch[7],scratch[1],scratch[4]);
|
||||
C_SUB( scratch[10],scratch[1],scratch[4]);
|
||||
C_ADD( scratch[8],scratch[2],scratch[3]);
|
||||
C_SUB( scratch[9],scratch[2],scratch[3]);
|
||||
|
||||
Fout0->r = ADD32_ovflw(Fout0->r, ADD32_ovflw(scratch[7].r, scratch[8].r));
|
||||
Fout0->i = ADD32_ovflw(Fout0->i, ADD32_ovflw(scratch[7].i, scratch[8].i));
|
||||
|
||||
scratch[5].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,ya.r), S_MUL(scratch[8].r,yb.r)));
|
||||
scratch[5].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,ya.r), S_MUL(scratch[8].i,yb.r)));
|
||||
|
||||
scratch[6].r = ADD32_ovflw(S_MUL(scratch[10].i,ya.i), S_MUL(scratch[9].i,yb.i));
|
||||
scratch[6].i = NEG32_ovflw(ADD32_ovflw(S_MUL(scratch[10].r,ya.i), S_MUL(scratch[9].r,yb.i)));
|
||||
|
||||
C_SUB(*Fout1,scratch[5],scratch[6]);
|
||||
C_ADD(*Fout4,scratch[5],scratch[6]);
|
||||
|
||||
scratch[11].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,yb.r), S_MUL(scratch[8].r,ya.r)));
|
||||
scratch[11].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,yb.r), S_MUL(scratch[8].i,ya.r)));
|
||||
scratch[12].r = SUB32_ovflw(S_MUL(scratch[9].i,ya.i), S_MUL(scratch[10].i,yb.i));
|
||||
scratch[12].i = SUB32_ovflw(S_MUL(scratch[10].r,yb.i), S_MUL(scratch[9].r,ya.i));
|
||||
|
||||
C_ADD(*Fout2,scratch[11],scratch[12]);
|
||||
C_SUB(*Fout3,scratch[11],scratch[12]);
|
||||
|
||||
++Fout0;++Fout1;++Fout2;++Fout3;++Fout4;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* OVERRIDE_kf_bfly5 */
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef CUSTOM_MODES
|
||||
|
||||
static
|
||||
void compute_bitrev_table(
|
||||
int Fout,
|
||||
opus_int32 *f,
|
||||
const size_t fstride,
|
||||
int in_stride,
|
||||
opus_int16 * factors,
|
||||
const kiss_fft_state *st
|
||||
)
|
||||
{
|
||||
const int p=*factors++; /* the radix */
|
||||
const int m=*factors++; /* stage's fft length/p */
|
||||
|
||||
/*printf ("fft %d %d %d %d %d %d\n", p*m, m, p, s2, fstride*in_stride, N);*/
|
||||
if (m==1)
|
||||
{
|
||||
int j;
|
||||
for (j=0;j<p;j++)
|
||||
{
|
||||
*f = Fout+j;
|
||||
f += fstride*in_stride;
|
||||
}
|
||||
} else {
|
||||
int j;
|
||||
for (j=0;j<p;j++)
|
||||
{
|
||||
compute_bitrev_table( Fout , f, fstride*p, in_stride, factors,st);
|
||||
f += fstride*in_stride;
|
||||
Fout += m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* facbuf is populated by p1,m1,p2,m2, ...
|
||||
where
|
||||
p[i] * m[i] = m[i-1]
|
||||
m0 = n */
|
||||
static
|
||||
int kf_factor(int n,opus_int16 * facbuf)
|
||||
{
|
||||
int p=4;
|
||||
int i;
|
||||
int stages=0;
|
||||
int nbak = n;
|
||||
|
||||
/*factor out powers of 4, powers of 2, then any remaining primes */
|
||||
do {
|
||||
while (n % p) {
|
||||
switch (p) {
|
||||
case 4: p = 2; break;
|
||||
case 2: p = 3; break;
|
||||
default: p += 2; break;
|
||||
}
|
||||
if (p>32000 || (opus_int32)p*(opus_int32)p > n)
|
||||
p = n; /* no more factors, skip to end */
|
||||
}
|
||||
n /= p;
|
||||
#ifdef RADIX_TWO_ONLY
|
||||
if (p!=2 && p != 4)
|
||||
#else
|
||||
if (p>5)
|
||||
#endif
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
facbuf[2*stages] = p;
|
||||
if (p==2 && stages > 1)
|
||||
{
|
||||
facbuf[2*stages] = 4;
|
||||
facbuf[2] = 2;
|
||||
}
|
||||
stages++;
|
||||
} while (n > 1);
|
||||
n = nbak;
|
||||
/* Reverse the order to get the radix 4 at the end, so we can use the
|
||||
fast degenerate case. It turns out that reversing the order also
|
||||
improves the noise behaviour. */
|
||||
for (i=0;i<stages/2;i++)
|
||||
{
|
||||
int tmp;
|
||||
tmp = facbuf[2*i];
|
||||
facbuf[2*i] = facbuf[2*(stages-i-1)];
|
||||
facbuf[2*(stages-i-1)] = tmp;
|
||||
}
|
||||
for (i=0;i<stages;i++)
|
||||
{
|
||||
n /= facbuf[2*i];
|
||||
facbuf[2*i+1] = n;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void compute_twiddles(kiss_twiddle_cpx *twiddles, int nfft)
|
||||
{
|
||||
int i;
|
||||
#ifdef FIXED_POINT
|
||||
for (i=0;i<nfft;++i) {
|
||||
opus_val32 phase = -i;
|
||||
kf_cexp2(twiddles+i, DIV32(SHL32(phase,17),nfft));
|
||||
}
|
||||
#else
|
||||
for (i=0;i<nfft;++i) {
|
||||
const double pi=3.14159265358979323846264338327;
|
||||
double phase = ( -2*pi /nfft ) * i;
|
||||
kf_cexp(twiddles+i, phase );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int rnn_fft_alloc_arch_c(kiss_fft_state *st) {
|
||||
(void)st;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Allocates all necessary storage space for the fft and ifft.
|
||||
* The return value is a contiguous block of memory. As such,
|
||||
* It can be freed with free().
|
||||
* */
|
||||
kiss_fft_state *rnn_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem,
|
||||
const kiss_fft_state *base, int arch)
|
||||
{
|
||||
kiss_fft_state *st=NULL;
|
||||
size_t memneeded = sizeof(struct kiss_fft_state); /* twiddle factors*/
|
||||
|
||||
if ( lenmem==NULL ) {
|
||||
st = ( kiss_fft_state*)KISS_FFT_MALLOC( memneeded );
|
||||
}else{
|
||||
if (mem != NULL && *lenmem >= memneeded)
|
||||
st = (kiss_fft_state*)mem;
|
||||
*lenmem = memneeded;
|
||||
}
|
||||
if (st) {
|
||||
opus_int32 *bitrev;
|
||||
kiss_twiddle_cpx *twiddles;
|
||||
|
||||
st->nfft=nfft;
|
||||
#ifdef FIXED_POINT
|
||||
st->scale_shift = celt_ilog2(st->nfft);
|
||||
if (st->nfft == 1<<st->scale_shift)
|
||||
st->scale = Q15ONE;
|
||||
else
|
||||
st->scale = (1073741824+st->nfft/2)/st->nfft>>(15-st->scale_shift);
|
||||
#else
|
||||
st->scale = 1.f/nfft;
|
||||
#endif
|
||||
if (base != NULL)
|
||||
{
|
||||
st->twiddles = base->twiddles;
|
||||
st->shift = 0;
|
||||
while (st->shift < 32 && nfft<<st->shift != base->nfft)
|
||||
st->shift++;
|
||||
if (st->shift>=32)
|
||||
goto fail;
|
||||
} else {
|
||||
st->twiddles = twiddles = (kiss_twiddle_cpx*)KISS_FFT_MALLOC(sizeof(kiss_twiddle_cpx)*nfft);
|
||||
compute_twiddles(twiddles, nfft);
|
||||
st->shift = -1;
|
||||
}
|
||||
if (!kf_factor(nfft,st->factors))
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* bitrev */
|
||||
st->bitrev = bitrev = (opus_int32*)KISS_FFT_MALLOC(sizeof(opus_int32)*nfft);
|
||||
if (st->bitrev==NULL)
|
||||
goto fail;
|
||||
compute_bitrev_table(0, bitrev, 1,1, st->factors,st);
|
||||
|
||||
/* Initialize architecture specific fft parameters */
|
||||
if (rnn_fft_alloc_arch(st, arch))
|
||||
goto fail;
|
||||
}
|
||||
return st;
|
||||
fail:
|
||||
rnn_fft_free(st, arch);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
kiss_fft_state *rnn_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch)
|
||||
{
|
||||
return rnn_fft_alloc_twiddles(nfft, mem, lenmem, NULL, arch);
|
||||
}
|
||||
|
||||
void rnn_fft_free_arch_c(kiss_fft_state *st) {
|
||||
(void)st;
|
||||
}
|
||||
|
||||
void rnn_fft_free(const kiss_fft_state *cfg, int arch)
|
||||
{
|
||||
if (cfg)
|
||||
{
|
||||
rnn_fft_free_arch((kiss_fft_state *)cfg, arch);
|
||||
opus_free((opus_int32*)cfg->bitrev);
|
||||
if (cfg->shift < 0)
|
||||
opus_free((kiss_twiddle_cpx*)cfg->twiddles);
|
||||
opus_free((kiss_fft_state*)cfg);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CUSTOM_MODES */
|
||||
|
||||
void rnn_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout)
|
||||
{
|
||||
int m2, m;
|
||||
int p;
|
||||
int L;
|
||||
int fstride[MAXFACTORS];
|
||||
int i;
|
||||
int shift;
|
||||
|
||||
/* st->shift can be -1 */
|
||||
shift = st->shift>0 ? st->shift : 0;
|
||||
|
||||
fstride[0] = 1;
|
||||
L=0;
|
||||
do {
|
||||
p = st->factors[2*L];
|
||||
m = st->factors[2*L+1];
|
||||
fstride[L+1] = fstride[L]*p;
|
||||
L++;
|
||||
} while(m!=1);
|
||||
m = st->factors[2*L-1];
|
||||
for (i=L-1;i>=0;i--)
|
||||
{
|
||||
if (i!=0)
|
||||
m2 = st->factors[2*i-1];
|
||||
else
|
||||
m2 = 1;
|
||||
switch (st->factors[2*i])
|
||||
{
|
||||
case 2:
|
||||
kf_bfly2(fout, m, fstride[i]);
|
||||
break;
|
||||
case 4:
|
||||
kf_bfly4(fout,fstride[i]<<shift,st,m, fstride[i], m2);
|
||||
break;
|
||||
#ifndef RADIX_TWO_ONLY
|
||||
case 3:
|
||||
kf_bfly3(fout,fstride[i]<<shift,st,m, fstride[i], m2);
|
||||
break;
|
||||
case 5:
|
||||
kf_bfly5(fout,fstride[i]<<shift,st,m, fstride[i], m2);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
m = m2;
|
||||
}
|
||||
}
|
||||
|
||||
void rnn_fft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout)
|
||||
{
|
||||
int i;
|
||||
opus_val16 scale;
|
||||
#ifdef FIXED_POINT
|
||||
/* Allows us to scale with MULT16_32_Q16(), which is faster than
|
||||
MULT16_32_Q15() on ARM. */
|
||||
int scale_shift = st->scale_shift-1;
|
||||
#endif
|
||||
scale = st->scale;
|
||||
|
||||
celt_assert2 (fin != fout, "In-place FFT not supported");
|
||||
/* Bit-reverse the input */
|
||||
for (i=0;i<st->nfft;i++)
|
||||
{
|
||||
kiss_fft_cpx x = fin[i];
|
||||
fout[st->bitrev[i]].r = SHR32(MULT16_32_Q16(scale, x.r), scale_shift);
|
||||
fout[st->bitrev[i]].i = SHR32(MULT16_32_Q16(scale, x.i), scale_shift);
|
||||
}
|
||||
rnn_fft_impl(st, fout);
|
||||
}
|
||||
|
||||
|
||||
void rnn_ifft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout)
|
||||
{
|
||||
int i;
|
||||
celt_assert2 (fin != fout, "In-place FFT not supported");
|
||||
/* Bit-reverse the input */
|
||||
for (i=0;i<st->nfft;i++)
|
||||
fout[st->bitrev[i]] = fin[i];
|
||||
for (i=0;i<st->nfft;i++)
|
||||
fout[i].i = -fout[i].i;
|
||||
rnn_fft_impl(st, fout);
|
||||
for (i=0;i<st->nfft;i++)
|
||||
fout[i].i = -fout[i].i;
|
||||
}
|
||||
203
src/modules/ZoomIt/ZoomIt/rnnoise/kiss_fft.h
Normal file
203
src/modules/ZoomIt/ZoomIt/rnnoise/kiss_fft.h
Normal file
@@ -0,0 +1,203 @@
|
||||
/*Copyright (c) 2003-2004, Mark Borgerding
|
||||
Lots of modifications by Jean-Marc Valin
|
||||
Copyright (c) 2005-2007, Xiph.Org Foundation
|
||||
Copyright (c) 2008, Xiph.Org Foundation, CSIRO
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.*/
|
||||
|
||||
#ifndef KISS_FFT_H
|
||||
#define KISS_FFT_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "arch.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#define opus_alloc(x) malloc(x)
|
||||
#define opus_free(x) free(x)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef USE_SIMD
|
||||
# include <xmmintrin.h>
|
||||
# define kiss_fft_scalar __m128
|
||||
#define KISS_FFT_MALLOC(nbytes) memalign(16,nbytes)
|
||||
#else
|
||||
#define KISS_FFT_MALLOC opus_alloc
|
||||
#endif
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
#include "arch.h"
|
||||
|
||||
# define kiss_fft_scalar opus_int32
|
||||
# define kiss_twiddle_scalar opus_int16
|
||||
|
||||
|
||||
#else
|
||||
# ifndef kiss_fft_scalar
|
||||
/* default is float */
|
||||
# define kiss_fft_scalar float
|
||||
# define kiss_twiddle_scalar float
|
||||
# define KF_SUFFIX _celt_single
|
||||
# endif
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
kiss_fft_scalar r;
|
||||
kiss_fft_scalar i;
|
||||
}kiss_fft_cpx;
|
||||
|
||||
typedef struct {
|
||||
kiss_twiddle_scalar r;
|
||||
kiss_twiddle_scalar i;
|
||||
}kiss_twiddle_cpx;
|
||||
|
||||
#define MAXFACTORS 8
|
||||
/* e.g. an fft of length 128 has 4 factors
|
||||
as far as kissfft is concerned
|
||||
4*4*4*2
|
||||
*/
|
||||
|
||||
typedef struct arch_fft_state{
|
||||
int is_supported;
|
||||
void *priv;
|
||||
} arch_fft_state;
|
||||
|
||||
typedef struct kiss_fft_state{
|
||||
int nfft;
|
||||
opus_val16 scale;
|
||||
#ifdef FIXED_POINT
|
||||
int scale_shift;
|
||||
#endif
|
||||
int shift;
|
||||
opus_int16 factors[2*MAXFACTORS];
|
||||
const opus_int32 *bitrev;
|
||||
const kiss_twiddle_cpx *twiddles;
|
||||
arch_fft_state *arch_fft;
|
||||
} kiss_fft_state;
|
||||
|
||||
#if defined(HAVE_ARM_NE10)
|
||||
#include "arm/fft_arm.h"
|
||||
#endif
|
||||
|
||||
/*typedef struct kiss_fft_state* kiss_fft_cfg;*/
|
||||
|
||||
/**
|
||||
* opus_fft_alloc
|
||||
*
|
||||
* Initialize a FFT (or IFFT) algorithm's cfg/state buffer.
|
||||
*
|
||||
* typical usage: kiss_fft_cfg mycfg=opus_fft_alloc(1024,0,NULL,NULL);
|
||||
*
|
||||
* The return value from fft_alloc is a cfg buffer used internally
|
||||
* by the fft routine or NULL.
|
||||
*
|
||||
* If lenmem is NULL, then opus_fft_alloc will allocate a cfg buffer using malloc.
|
||||
* The returned value should be free()d when done to avoid memory leaks.
|
||||
*
|
||||
* The state can be placed in a user supplied buffer 'mem':
|
||||
* If lenmem is not NULL and mem is not NULL and *lenmem is large enough,
|
||||
* then the function places the cfg in mem and the size used in *lenmem
|
||||
* and returns mem.
|
||||
*
|
||||
* If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough),
|
||||
* then the function returns NULL and places the minimum cfg
|
||||
* buffer size in *lenmem.
|
||||
* */
|
||||
|
||||
kiss_fft_state *rnn_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem, const kiss_fft_state *base, int arch);
|
||||
|
||||
kiss_fft_state *rnn_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch);
|
||||
|
||||
/**
|
||||
* opus_fft(cfg,in_out_buf)
|
||||
*
|
||||
* Perform an FFT on a complex input buffer.
|
||||
* for a forward FFT,
|
||||
* fin should be f[0] , f[1] , ... ,f[nfft-1]
|
||||
* fout will be F[0] , F[1] , ... ,F[nfft-1]
|
||||
* Note that each element is complex and can be accessed like
|
||||
f[k].r and f[k].i
|
||||
* */
|
||||
void rnn_fft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
|
||||
void rnn_ifft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
|
||||
|
||||
void rnn_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout);
|
||||
void rnn_ifft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout);
|
||||
|
||||
void rnn_fft_free(const kiss_fft_state *cfg, int arch);
|
||||
|
||||
|
||||
void rnn_fft_free_arch_c(kiss_fft_state *st);
|
||||
int rnn_fft_alloc_arch_c(kiss_fft_state *st);
|
||||
|
||||
#if !defined(OVERRIDE_OPUS_FFT)
|
||||
/* Is run-time CPU detection enabled on this platform? */
|
||||
#if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10))
|
||||
|
||||
extern int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])(
|
||||
kiss_fft_state *st);
|
||||
|
||||
#define opus_fft_alloc_arch(_st, arch) \
|
||||
((*OPUS_FFT_ALLOC_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st))
|
||||
|
||||
extern void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])(
|
||||
kiss_fft_state *st);
|
||||
#define opus_fft_free_arch(_st, arch) \
|
||||
((*OPUS_FFT_FREE_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st))
|
||||
|
||||
extern void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
|
||||
const kiss_fft_cpx *fin, kiss_fft_cpx *fout);
|
||||
#define opus_fft(_cfg, _fin, _fout, arch) \
|
||||
((*OPUS_FFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout))
|
||||
|
||||
extern void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
|
||||
const kiss_fft_cpx *fin, kiss_fft_cpx *fout);
|
||||
#define opus_ifft(_cfg, _fin, _fout, arch) \
|
||||
((*OPUS_IFFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout))
|
||||
|
||||
#else /* else for if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */
|
||||
|
||||
#define rnn_fft_alloc_arch(_st, arch) \
|
||||
((void)(arch), rnn_fft_alloc_arch_c(_st))
|
||||
|
||||
#define rnn_fft_free_arch(_st, arch) \
|
||||
((void)(arch), rnn_fft_free_arch_c(_st))
|
||||
|
||||
#define rnn_fft(_cfg, _fin, _fout, arch) \
|
||||
((void)(arch), rnn_fft_c(_cfg, _fin, _fout))
|
||||
|
||||
#define rnn_ifft(_cfg, _fin, _fout, arch) \
|
||||
((void)(arch), rnn_ifft_c(_cfg, _fin, _fout))
|
||||
|
||||
#endif /* end if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */
|
||||
#endif /* end if !defined(OVERRIDE_OPUS_FFT) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
123
src/modules/ZoomIt/ZoomIt/rnnoise/nnet.c
Normal file
123
src/modules/ZoomIt/ZoomIt/rnnoise/nnet.c
Normal file
@@ -0,0 +1,123 @@
|
||||
/* Copyright (c) 2018 Mozilla
|
||||
2008-2011 Octasic Inc.
|
||||
2012-2017 Jean-Marc Valin */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "opus_types.h"
|
||||
#include "arch.h"
|
||||
#include "nnet.h"
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
|
||||
#ifdef ENABLE_OSCE
|
||||
#include "osce.h"
|
||||
#endif
|
||||
|
||||
#ifdef NO_OPTIMIZATIONS
|
||||
#if defined(_MSC_VER)
|
||||
#pragma message ("Compiling without any vectorization. This code will be very slow")
|
||||
#else
|
||||
#warning Compiling without any vectorization. This code will be very slow
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#define SOFTMAX_HACK
|
||||
|
||||
|
||||
void compute_generic_dense(const LinearLayer *layer, float *output, const float *input, int activation, int arch)
|
||||
{
|
||||
compute_linear(layer, output, input, arch);
|
||||
compute_activation(output, output, layer->nb_outputs, activation, arch);
|
||||
}
|
||||
|
||||
#define MAX_RNN_NEURONS_ALL 1024
|
||||
|
||||
void compute_generic_gru(const LinearLayer *input_weights, const LinearLayer *recurrent_weights, float *state, const float *in, int arch)
|
||||
{
|
||||
int i;
|
||||
int N;
|
||||
float zrh[3*MAX_RNN_NEURONS_ALL];
|
||||
float recur[3*MAX_RNN_NEURONS_ALL];
|
||||
float *z;
|
||||
float *r;
|
||||
float *h;
|
||||
celt_assert(3*recurrent_weights->nb_inputs == recurrent_weights->nb_outputs);
|
||||
celt_assert(input_weights->nb_outputs == recurrent_weights->nb_outputs);
|
||||
N = recurrent_weights->nb_inputs;
|
||||
z = zrh;
|
||||
r = &zrh[N];
|
||||
h = &zrh[2*N];
|
||||
celt_assert(recurrent_weights->nb_outputs <= 3*MAX_RNN_NEURONS_ALL);
|
||||
celt_assert(in != state);
|
||||
compute_linear(input_weights, zrh, in, arch);
|
||||
compute_linear(recurrent_weights, recur, state, arch);
|
||||
for (i=0;i<2*N;i++)
|
||||
zrh[i] += recur[i];
|
||||
compute_activation(zrh, zrh, 2*N, ACTIVATION_SIGMOID, arch);
|
||||
for (i=0;i<N;i++)
|
||||
h[i] += recur[2*N+i]*r[i];
|
||||
compute_activation(h, h, N, ACTIVATION_TANH, arch);
|
||||
for (i=0;i<N;i++)
|
||||
h[i] = z[i]*state[i] + (1-z[i])*h[i];
|
||||
for (i=0;i<N;i++)
|
||||
state[i] = h[i];
|
||||
}
|
||||
|
||||
void compute_glu(const LinearLayer *layer, float *output, const float *input, int arch)
|
||||
{
|
||||
int i;
|
||||
float act2[MAX_INPUTS];
|
||||
celt_assert(layer->nb_inputs == layer->nb_outputs);
|
||||
compute_linear(layer, act2, input, arch);
|
||||
compute_activation(act2, act2, layer->nb_outputs, ACTIVATION_SIGMOID, arch);
|
||||
if (input == output) {
|
||||
/* Give a vectorization hint to the compiler for the in-place case. */
|
||||
for (i=0;i<layer->nb_outputs;i++) output[i] = output[i]*act2[i];
|
||||
} else {
|
||||
for (i=0;i<layer->nb_outputs;i++) output[i] = input[i]*act2[i];
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_CONV_INPUTS_ALL 1024
|
||||
|
||||
void compute_generic_conv1d(const LinearLayer *layer, float *output, float *mem, const float *input, int input_size, int activation, int arch)
|
||||
{
|
||||
float tmp[MAX_CONV_INPUTS_ALL];
|
||||
celt_assert(input != output);
|
||||
celt_assert(layer->nb_inputs <= MAX_CONV_INPUTS_ALL);
|
||||
if (layer->nb_inputs!=input_size) RNN_COPY(tmp, mem, layer->nb_inputs-input_size);
|
||||
RNN_COPY(&tmp[layer->nb_inputs-input_size], input, input_size);
|
||||
compute_linear(layer, output, tmp, arch);
|
||||
compute_activation(output, output, layer->nb_outputs, activation, arch);
|
||||
if (layer->nb_inputs!=input_size) RNN_COPY(mem, &tmp[input_size], layer->nb_inputs-input_size);
|
||||
}
|
||||
169
src/modules/ZoomIt/ZoomIt/rnnoise/nnet.h
Normal file
169
src/modules/ZoomIt/ZoomIt/rnnoise/nnet.h
Normal file
@@ -0,0 +1,169 @@
|
||||
/* Copyright (c) 2018 Mozilla
|
||||
Copyright (c) 2017 Jean-Marc Valin */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef NNET_H_
|
||||
#define NNET_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include "opus_types.h"
|
||||
|
||||
#define ACTIVATION_LINEAR 0
|
||||
#define ACTIVATION_SIGMOID 1
|
||||
#define ACTIVATION_TANH 2
|
||||
#define ACTIVATION_RELU 3
|
||||
#define ACTIVATION_SOFTMAX 4
|
||||
#define ACTIVATION_SWISH 5
|
||||
|
||||
#define WEIGHT_BLOB_VERSION 0
|
||||
#define WEIGHT_BLOCK_SIZE 64
|
||||
typedef struct {
|
||||
const char *name;
|
||||
int type;
|
||||
int size;
|
||||
const void *data;
|
||||
} WeightArray;
|
||||
|
||||
#define WEIGHT_TYPE_float 0
|
||||
#define WEIGHT_TYPE_int 1
|
||||
#define WEIGHT_TYPE_qweight 2
|
||||
#define WEIGHT_TYPE_int8 3
|
||||
|
||||
typedef struct {
|
||||
char head[4];
|
||||
int version;
|
||||
int type;
|
||||
int size;
|
||||
int block_size;
|
||||
char name[44];
|
||||
} WeightHead;
|
||||
|
||||
/* Generic sparse affine transformation. */
|
||||
typedef struct {
|
||||
const float *bias;
|
||||
const float *subias;
|
||||
const opus_int8 *weights;
|
||||
const float *float_weights;
|
||||
const int *weights_idx;
|
||||
const float *diag;
|
||||
const float *scale;
|
||||
int nb_inputs;
|
||||
int nb_outputs;
|
||||
} LinearLayer;
|
||||
|
||||
/* Generic sparse affine transformation. */
|
||||
typedef struct {
|
||||
const float *bias;
|
||||
const float *float_weights;
|
||||
int in_channels;
|
||||
int out_channels;
|
||||
int ktime;
|
||||
int kheight;
|
||||
} Conv2dLayer;
|
||||
|
||||
|
||||
/* Changes some symbol names to add the rnn_ prefix so we don't get conflicts with Opus. */
|
||||
#define linear_init rnn_linear_init
|
||||
#define conv2d_init rnn_conv2d_init
|
||||
#define compute_generic_dense rnn_compute_generic_dense
|
||||
#define compute_generic_gru rnn_compute_generic_gru
|
||||
#define compute_generic_conv1d rnn_compute_generic_conv1d
|
||||
#define compute_glu rnn_compute_glu
|
||||
|
||||
#define parse_weights rnn_parse_weights
|
||||
|
||||
#define compute_linear_c rnn_compute_linear_c
|
||||
#define compute_activation_c rnn_compute_activation_c
|
||||
#define compute_conv2d_c rnn_compute_conv2d_c
|
||||
#define compute_linear_sse4_1 rnn_compute_linear_sse4_1
|
||||
#define compute_activation_sse4_1 rnn_compute_activation_sse4_1
|
||||
#define compute_conv2d_sse4_1 rnn_compute_conv2d_sse4_1
|
||||
#define compute_linear_avx2 rnn_compute_linear_avx2
|
||||
#define compute_activation_avx2 rnn_compute_activation_avx2
|
||||
#define compute_conv2d_avx2 rnn_compute_conv2d_avx2
|
||||
|
||||
|
||||
void compute_generic_dense(const LinearLayer *layer, float *output, const float *input, int activation, int arch);
|
||||
void compute_generic_gru(const LinearLayer *input_weights, const LinearLayer *recurrent_weights, float *state, const float *in, int arch);
|
||||
void compute_generic_conv1d(const LinearLayer *layer, float *output, float *mem, const float *input, int input_size, int activation, int arch);
|
||||
void compute_glu(const LinearLayer *layer, float *output, const float *input, int arch);
|
||||
|
||||
|
||||
int parse_weights(WeightArray **list, const void *data, int len);
|
||||
|
||||
|
||||
|
||||
int linear_init(LinearLayer *layer, const WeightArray *arrays,
|
||||
const char *bias,
|
||||
const char *subias,
|
||||
const char *weights,
|
||||
const char *float_weights,
|
||||
const char *weights_idx,
|
||||
const char *diag,
|
||||
const char *scale,
|
||||
int nb_inputs,
|
||||
int nb_outputs);
|
||||
|
||||
int conv2d_init(Conv2dLayer *layer, const WeightArray *arrays,
|
||||
const char *bias,
|
||||
const char *float_weights,
|
||||
int in_channels,
|
||||
int out_channels,
|
||||
int ktime,
|
||||
int kheight);
|
||||
|
||||
|
||||
void compute_linear_c(const LinearLayer *linear, float *out, const float *in);
|
||||
void compute_activation_c(float *output, const float *input, int N, int activation);
|
||||
void compute_conv2d_c(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation);
|
||||
|
||||
#ifdef RNN_ENABLE_X86_RTCD
|
||||
#include "x86/dnn_x86.h"
|
||||
#endif
|
||||
|
||||
#ifndef OVERRIDE_COMPUTE_LINEAR
|
||||
#define compute_linear(linear, out, in, arch) ((void)(arch),compute_linear_c(linear, out, in))
|
||||
#endif
|
||||
|
||||
#ifndef OVERRIDE_COMPUTE_ACTIVATION
|
||||
#define compute_activation(output, input, N, activation, arch) ((void)(arch),compute_activation_c(output, input, N, activation))
|
||||
#endif
|
||||
|
||||
#ifndef OVERRIDE_COMPUTE_CONV2D
|
||||
#define compute_conv2d(conv, out, mem, in, height, hstride, activation, arch) ((void)(arch),compute_conv2d_c(conv, out, mem, in, height, hstride, activation))
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) && !defined(RNN_ENABLE_X86_RTCD) && !defined(__AVX2__)
|
||||
#if defined(_MSC_VER)
|
||||
#pragma message ("Only SSE and SSE2 are available. On newer machines, enable SSSE3/AVX/AVX2 to get better performance")
|
||||
#else
|
||||
#warning "Only SSE and SSE2 are available. On newer machines, enable SSSE3/AVX/AVX2 using -march= to get better performance"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#endif /* NNET_H_ */
|
||||
257
src/modules/ZoomIt/ZoomIt/rnnoise/nnet_arch.h
Normal file
257
src/modules/ZoomIt/ZoomIt/rnnoise/nnet_arch.h
Normal file
@@ -0,0 +1,257 @@
|
||||
/* Copyright (c) 2018-2019 Mozilla
|
||||
2023 Amazon */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef NNET_ARCH_H
|
||||
#define NNET_ARCH_H
|
||||
|
||||
#include "nnet.h"
|
||||
#include "arch.h"
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
|
||||
#define CAT_SUFFIX2(a,b) a ## b
|
||||
#define CAT_SUFFIX(a,b) CAT_SUFFIX2(a, b)
|
||||
|
||||
#define RTCD_SUF(name) CAT_SUFFIX(name, RTCD_ARCH)
|
||||
|
||||
# if !defined(OPUS_GNUC_PREREQ)
|
||||
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
|
||||
# define OPUS_GNUC_PREREQ(_maj,_min) \
|
||||
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
|
||||
# else
|
||||
# define OPUS_GNUC_PREREQ(_maj,_min) 0
|
||||
# endif
|
||||
# endif
|
||||
|
||||
|
||||
/* Force vectorization on for DNN code because some of the loops rely on
|
||||
compiler vectorization rather than explicitly using intrinsics. */
|
||||
#if OPUS_GNUC_PREREQ(5,1)
|
||||
#define GCC_POP_OPTIONS
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize("tree-vectorize")
|
||||
#endif
|
||||
|
||||
|
||||
#define MAX_ACTIVATIONS (4096)
|
||||
|
||||
static OPUS_INLINE void vec_swish(float *y, const float *x, int N)
|
||||
{
|
||||
int i;
|
||||
float tmp[MAX_ACTIVATIONS];
|
||||
celt_assert(N <= MAX_ACTIVATIONS);
|
||||
vec_sigmoid(tmp, x, N);
|
||||
for (i=0;i<N;i++)
|
||||
y[i] = x[i]*tmp[i];
|
||||
}
|
||||
|
||||
static OPUS_INLINE float relu(float x)
|
||||
{
|
||||
return x < 0 ? 0 : x;
|
||||
}
|
||||
|
||||
/*#define HIGH_ACCURACY */
|
||||
|
||||
void RTCD_SUF(compute_activation_)(float *output, const float *input, int N, int activation)
|
||||
{
|
||||
int i;
|
||||
if (activation == ACTIVATION_SIGMOID) {
|
||||
#ifdef HIGH_ACCURACY
|
||||
for (int n=0; n<N; n++)
|
||||
{
|
||||
output[n] = 1.f / (1 + exp(-input[n]));
|
||||
}
|
||||
#else
|
||||
vec_sigmoid(output, input, N);
|
||||
#endif
|
||||
} else if (activation == ACTIVATION_TANH) {
|
||||
#ifdef HIGH_ACCURACY
|
||||
for (int n=0; n<N; n++)
|
||||
{
|
||||
output[n] = tanh(input[n]);
|
||||
}
|
||||
#else
|
||||
vec_tanh(output, input, N);
|
||||
#endif
|
||||
} else if (activation == ACTIVATION_SWISH) {
|
||||
vec_swish(output, input, N);
|
||||
} else if (activation == ACTIVATION_RELU) {
|
||||
for (i=0;i<N;i++)
|
||||
output[i] = relu(input[i]);
|
||||
} else if (activation == ACTIVATION_SOFTMAX) {
|
||||
#ifdef SOFTMAX_HACK
|
||||
RNN_COPY(output, input, N);
|
||||
/*for (i=0;i<N;i++)
|
||||
output[i] = input[i];*/
|
||||
#else
|
||||
float sum = 0;
|
||||
softmax(output, input, N);
|
||||
for (i=0;i<N;i++) {
|
||||
sum += output[i];
|
||||
}
|
||||
sum = 1.f/(sum+1e-30);
|
||||
for (i=0;i<N;i++)
|
||||
output[i] = sum*output[i];
|
||||
#endif
|
||||
} else {
|
||||
celt_assert(activation == ACTIVATION_LINEAR);
|
||||
if (input != output) {
|
||||
for (i=0;i<N;i++)
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RTCD_SUF(compute_linear_) (const LinearLayer *linear, float *out, const float *in)
|
||||
{
|
||||
int i, M, N;
|
||||
const float *bias;
|
||||
celt_assert(in != out);
|
||||
bias = linear->bias;
|
||||
M = linear->nb_inputs;
|
||||
N = linear->nb_outputs;
|
||||
if (linear->float_weights != NULL) {
|
||||
if (linear->weights_idx != NULL) sparse_sgemv8x4(out, linear->float_weights, linear->weights_idx, N, in);
|
||||
else sgemv(out, linear->float_weights, N, M, N, in);
|
||||
} else if (linear->weights != NULL) {
|
||||
if (linear->weights_idx != NULL) sparse_cgemv8x4(out, linear->weights, linear->weights_idx, linear->scale, N, M, in);
|
||||
else cgemv8x4(out, linear->weights, linear->scale, N, M, in);
|
||||
/* Only use SU biases on for integer matrices on SU archs. */
|
||||
#ifdef USE_SU_BIAS
|
||||
bias = linear->subias;
|
||||
#endif
|
||||
}
|
||||
else RNN_CLEAR(out, N);
|
||||
if (bias != NULL) {
|
||||
for (i=0;i<N;i++) out[i] += bias[i];
|
||||
}
|
||||
if (linear->diag) {
|
||||
/* Diag is only used for GRU recurrent weights. */
|
||||
celt_assert(3*M == N);
|
||||
for (i=0;i<M;i++) {
|
||||
out[i] += linear->diag[i]*in[i];
|
||||
out[i+M] += linear->diag[i+M]*in[i];
|
||||
out[i+2*M] += linear->diag[i+2*M]*in[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Computes non-padded convolution for input [ ksize1 x in_channels x (len2+ksize2) ],
|
||||
kernel [ out_channels x in_channels x ksize1 x ksize2 ],
|
||||
storing the output as [ out_channels x len2 ].
|
||||
We assume that the output dimension along the ksize1 axis is 1,
|
||||
i.e. processing one frame at a time. */
|
||||
static void conv2d_float(float *out, const float *weights, int in_channels, int out_channels, int ktime, int kheight, const float *in, int height, int hstride)
|
||||
{
|
||||
int i;
|
||||
int in_stride;
|
||||
in_stride = height+kheight-1;
|
||||
for (i=0;i<out_channels;i++) {
|
||||
int m;
|
||||
RNN_CLEAR(&out[i*hstride], height);
|
||||
for (m=0;m<in_channels;m++) {
|
||||
int t;
|
||||
for (t=0;t<ktime;t++) {
|
||||
int h;
|
||||
for (h=0;h<kheight;h++) {
|
||||
int j;
|
||||
for (j=0;j<height;j++) {
|
||||
out[i*hstride + j] += weights[i*in_channels*ktime*kheight + m*ktime*kheight + t*kheight + h] *
|
||||
in[t*in_channels*in_stride + m*in_stride + j + h];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* There's no intrinsics in this function (or the one above) because the gcc (and hopefully other compiler) auto-vectorizer is smart enough to
|
||||
produce the right code by itself based on the compile flags. */
|
||||
static void conv2d_3x3_float(float *out, const float *weights, int in_channels, int out_channels, const float *in, int height, int hstride)
|
||||
{
|
||||
int i;
|
||||
int in_stride;
|
||||
int kheight, ktime;
|
||||
kheight = ktime = 3;
|
||||
in_stride = height+kheight-1;
|
||||
for (i=0;i<out_channels;i++) {
|
||||
int m;
|
||||
RNN_CLEAR(&out[i*hstride], height);
|
||||
for (m=0;m<in_channels;m++) {
|
||||
int j;
|
||||
for (j=0;j<height;j++) {
|
||||
/* Unrolled version of previous function -- compiler will figure out the indexing simplifications. */
|
||||
out[i*hstride + j] += weights[i*in_channels*ktime*kheight + m*ktime*kheight + 0*kheight + 0]*in[0*in_channels*in_stride + m*in_stride + j + 0]
|
||||
+ weights[i*in_channels*ktime*kheight + m*ktime*kheight + 0*kheight + 1]*in[0*in_channels*in_stride + m*in_stride + j + 1]
|
||||
+ weights[i*in_channels*ktime*kheight + m*ktime*kheight + 0*kheight + 2]*in[0*in_channels*in_stride + m*in_stride + j + 2]
|
||||
+ weights[i*in_channels*ktime*kheight + m*ktime*kheight + 1*kheight + 0]*in[1*in_channels*in_stride + m*in_stride + j + 0]
|
||||
+ weights[i*in_channels*ktime*kheight + m*ktime*kheight + 1*kheight + 1]*in[1*in_channels*in_stride + m*in_stride + j + 1]
|
||||
+ weights[i*in_channels*ktime*kheight + m*ktime*kheight + 1*kheight + 2]*in[1*in_channels*in_stride + m*in_stride + j + 2]
|
||||
+ weights[i*in_channels*ktime*kheight + m*ktime*kheight + 2*kheight + 0]*in[2*in_channels*in_stride + m*in_stride + j + 0]
|
||||
+ weights[i*in_channels*ktime*kheight + m*ktime*kheight + 2*kheight + 1]*in[2*in_channels*in_stride + m*in_stride + j + 1]
|
||||
+ weights[i*in_channels*ktime*kheight + m*ktime*kheight + 2*kheight + 2]*in[2*in_channels*in_stride + m*in_stride + j + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_CONV2D_INPUTS 8192
|
||||
|
||||
void RTCD_SUF(compute_conv2d_)(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation)
|
||||
{
|
||||
int i;
|
||||
const float *bias;
|
||||
float in_buf[MAX_CONV2D_INPUTS];
|
||||
int time_stride;
|
||||
celt_assert(in != out);
|
||||
time_stride = conv->in_channels*(height+conv->kheight-1);
|
||||
celt_assert(conv->ktime*time_stride <= MAX_CONV2D_INPUTS);
|
||||
RNN_COPY(in_buf, mem, (conv->ktime-1)*time_stride);
|
||||
RNN_COPY(&in_buf[(conv->ktime-1)*time_stride], in, time_stride);
|
||||
RNN_COPY(mem, &in_buf[time_stride], (conv->ktime-1)*time_stride);
|
||||
bias = conv->bias;
|
||||
if (conv->kheight == 3 && conv->ktime == 3)
|
||||
conv2d_3x3_float(out, conv->float_weights, conv->in_channels, conv->out_channels, in_buf, height, hstride);
|
||||
else
|
||||
conv2d_float(out, conv->float_weights, conv->in_channels, conv->out_channels, conv->ktime, conv->kheight, in_buf, height, hstride);
|
||||
if (bias != NULL) {
|
||||
for (i=0;i<conv->out_channels;i++) {
|
||||
int j;
|
||||
for (j=0;j<height;j++) out[i*hstride+j] += bias[i];
|
||||
}
|
||||
}
|
||||
for (i=0;i<conv->out_channels;i++) {
|
||||
RTCD_SUF(compute_activation_)(&out[i*hstride], &out[i*hstride], height, activation);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef GCC_POP_OPTIONS
|
||||
#pragma GCC pop_options
|
||||
#endif
|
||||
|
||||
#endif
|
||||
35
src/modules/ZoomIt/ZoomIt/rnnoise/nnet_default.c
Normal file
35
src/modules/ZoomIt/ZoomIt/rnnoise/nnet_default.c
Normal file
@@ -0,0 +1,35 @@
|
||||
/* Copyright (c) 2018-2019 Mozilla
|
||||
2023 Amazon */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
|
||||
#define RTCD_ARCH c
|
||||
|
||||
#include "nnet_arch.h"
|
||||
159
src/modules/ZoomIt/ZoomIt/rnnoise/opus_types.h
Normal file
159
src/modules/ZoomIt/ZoomIt/rnnoise/opus_types.h
Normal file
@@ -0,0 +1,159 @@
|
||||
/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */
|
||||
/* Modified by Jean-Marc Valin */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/* opus_types.h based on ogg_types.h from libogg */
|
||||
|
||||
/**
|
||||
@file opus_types.h
|
||||
@brief Opus reference implementation types
|
||||
*/
|
||||
#ifndef OPUS_TYPES_H
|
||||
#define OPUS_TYPES_H
|
||||
|
||||
/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */
|
||||
#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H))
|
||||
#include <stdint.h>
|
||||
|
||||
typedef int16_t opus_int16;
|
||||
typedef uint16_t opus_uint16;
|
||||
typedef int32_t opus_int32;
|
||||
typedef uint32_t opus_uint32;
|
||||
#elif defined(_WIN32)
|
||||
|
||||
# if defined(__CYGWIN__)
|
||||
# include <_G_config.h>
|
||||
typedef _G_int32_t opus_int32;
|
||||
typedef _G_uint32_t opus_uint32;
|
||||
typedef _G_int16 opus_int16;
|
||||
typedef _G_uint16 opus_uint16;
|
||||
# elif defined(__MINGW32__)
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
# elif defined(__MWERKS__)
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
# else
|
||||
/* MSVC/Borland */
|
||||
typedef __int32 opus_int32;
|
||||
typedef unsigned __int32 opus_uint32;
|
||||
typedef __int16 opus_int16;
|
||||
typedef unsigned __int16 opus_uint16;
|
||||
# endif
|
||||
|
||||
#elif defined(__MACOS__)
|
||||
|
||||
# include <sys/types.h>
|
||||
typedef SInt16 opus_int16;
|
||||
typedef UInt16 opus_uint16;
|
||||
typedef SInt32 opus_int32;
|
||||
typedef UInt32 opus_uint32;
|
||||
|
||||
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
|
||||
|
||||
# include <sys/types.h>
|
||||
typedef int16_t opus_int16;
|
||||
typedef u_int16_t opus_uint16;
|
||||
typedef int32_t opus_int32;
|
||||
typedef u_int32_t opus_uint32;
|
||||
|
||||
#elif defined(__BEOS__)
|
||||
|
||||
/* Be */
|
||||
# include <inttypes.h>
|
||||
typedef int16 opus_int16;
|
||||
typedef u_int16 opus_uint16;
|
||||
typedef int32_t opus_int32;
|
||||
typedef u_int32_t opus_uint32;
|
||||
|
||||
#elif defined (__EMX__)
|
||||
|
||||
/* OS/2 GCC */
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#elif defined (DJGPP)
|
||||
|
||||
/* DJGPP */
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#elif defined(R5900)
|
||||
|
||||
/* PS2 EE */
|
||||
typedef int opus_int32;
|
||||
typedef unsigned opus_uint32;
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
|
||||
#elif defined(__SYMBIAN32__)
|
||||
|
||||
/* Symbian GCC */
|
||||
typedef signed short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef signed int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
|
||||
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef long opus_int32;
|
||||
typedef unsigned long opus_uint32;
|
||||
|
||||
#elif defined(CONFIG_TI_C6X)
|
||||
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#else
|
||||
|
||||
/* Give up, take a reasonable guess */
|
||||
typedef short opus_int16;
|
||||
typedef unsigned short opus_uint16;
|
||||
typedef int opus_int32;
|
||||
typedef unsigned int opus_uint32;
|
||||
|
||||
#endif
|
||||
|
||||
#define opus_int int /* used for counters etc; at least 16 bits */
|
||||
#define opus_int64 long long
|
||||
#define opus_int8 signed char
|
||||
|
||||
#define opus_uint unsigned int /* used for counters etc; at least 16 bits */
|
||||
#define opus_uint64 unsigned long long
|
||||
#define opus_uint8 unsigned char
|
||||
|
||||
#endif /* OPUS_TYPES_H */
|
||||
237
src/modules/ZoomIt/ZoomIt/rnnoise/parse_lpcnet_weights.c
Normal file
237
src/modules/ZoomIt/ZoomIt/rnnoise/parse_lpcnet_weights.c
Normal file
@@ -0,0 +1,237 @@
|
||||
/* Copyright (c) 2023 Amazon */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "nnet.h"
|
||||
|
||||
#define SPARSE_BLOCK_SIZE 32
|
||||
|
||||
static int parse_record(const void **data, int *len, WeightArray *array) {
|
||||
WeightHead *h = (WeightHead *)*data;
|
||||
if (*len < WEIGHT_BLOCK_SIZE) return -1;
|
||||
if (h->block_size < h->size) return -1;
|
||||
if (h->block_size > *len-WEIGHT_BLOCK_SIZE) return -1;
|
||||
if (h->name[sizeof(h->name)-1] != 0) return -1;
|
||||
if (h->size < 0) return -1;
|
||||
array->name = h->name;
|
||||
array->type = h->type;
|
||||
array->size = h->size;
|
||||
array->data = (void*)((unsigned char*)(*data)+WEIGHT_BLOCK_SIZE);
|
||||
|
||||
*data = (void*)((unsigned char*)*data + h->block_size+WEIGHT_BLOCK_SIZE);
|
||||
*len -= h->block_size+WEIGHT_BLOCK_SIZE;
|
||||
return array->size;
|
||||
}
|
||||
|
||||
int parse_weights(WeightArray **list, const void *data, int len)
|
||||
{
|
||||
int nb_arrays=0;
|
||||
int capacity=20;
|
||||
*list = calloc(capacity*sizeof(WeightArray), 1);
|
||||
while (len > 0) {
|
||||
int ret;
|
||||
WeightArray array = {NULL, 0, 0, 0};
|
||||
ret = parse_record(&data, &len, &array);
|
||||
if (ret > 0) {
|
||||
if (nb_arrays+1 >= capacity) {
|
||||
/* Make sure there's room for the ending NULL element too. */
|
||||
capacity = capacity*3/2;
|
||||
*list = realloc(*list, capacity*sizeof(WeightArray));
|
||||
}
|
||||
(*list)[nb_arrays++] = array;
|
||||
} else {
|
||||
free(*list);
|
||||
*list = NULL;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
(*list)[nb_arrays].name=NULL;
|
||||
return nb_arrays;
|
||||
}
|
||||
|
||||
static const void *find_array_entry(const WeightArray *arrays, const char *name) {
|
||||
while (arrays->name && strcmp(arrays->name, name) != 0) arrays++;
|
||||
return arrays;
|
||||
}
|
||||
|
||||
static const void *find_array_check(const WeightArray *arrays, const char *name, int size) {
|
||||
const WeightArray *a = find_array_entry(arrays, name);
|
||||
if (a->name && a->size == size) return a->data;
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
static const void *opt_array_check(const WeightArray *arrays, const char *name, int size, int *error) {
|
||||
const WeightArray *a = find_array_entry(arrays, name);
|
||||
*error = (a->name != NULL && a->size != size);
|
||||
if (a->name && a->size == size) return a->data;
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
static const void *find_idx_check(const WeightArray *arrays, const char *name, int nb_in, int nb_out, int *total_blocks) {
|
||||
int remain;
|
||||
const int *idx;
|
||||
const WeightArray *a = find_array_entry(arrays, name);
|
||||
*total_blocks = 0;
|
||||
if (a == NULL) return NULL;
|
||||
idx = a->data;
|
||||
remain = a->size/sizeof(int);
|
||||
while (remain > 0) {
|
||||
int nb_blocks;
|
||||
int i;
|
||||
nb_blocks = *idx++;
|
||||
if (remain < nb_blocks+1) return NULL;
|
||||
for (i=0;i<nb_blocks;i++) {
|
||||
int pos = *idx++;
|
||||
if (pos+3 >= nb_in || (pos&0x3)) return NULL;
|
||||
}
|
||||
nb_out -= 8;
|
||||
remain -= nb_blocks+1;
|
||||
*total_blocks += nb_blocks;
|
||||
}
|
||||
if (nb_out != 0) return NULL;
|
||||
return a->data;
|
||||
}
|
||||
|
||||
int linear_init(LinearLayer *layer, const WeightArray *arrays,
|
||||
const char *bias,
|
||||
const char *subias,
|
||||
const char *weights,
|
||||
const char *float_weights,
|
||||
const char *weights_idx,
|
||||
const char *diag,
|
||||
const char *scale,
|
||||
int nb_inputs,
|
||||
int nb_outputs)
|
||||
{
|
||||
int err;
|
||||
layer->bias = NULL;
|
||||
layer->subias = NULL;
|
||||
layer->weights = NULL;
|
||||
layer->float_weights = NULL;
|
||||
layer->weights_idx = NULL;
|
||||
layer->diag = NULL;
|
||||
layer->scale = NULL;
|
||||
if (bias != NULL) {
|
||||
if ((layer->bias = find_array_check(arrays, bias, nb_outputs*sizeof(layer->bias[0]))) == NULL) return 1;
|
||||
}
|
||||
if (subias != NULL) {
|
||||
if ((layer->subias = find_array_check(arrays, subias, nb_outputs*sizeof(layer->subias[0]))) == NULL) return 1;
|
||||
}
|
||||
if (weights_idx != NULL) {
|
||||
int total_blocks;
|
||||
if ((layer->weights_idx = find_idx_check(arrays, weights_idx, nb_inputs, nb_outputs, &total_blocks)) == NULL) return 1;
|
||||
if (weights != NULL) {
|
||||
if ((layer->weights = find_array_check(arrays, weights, SPARSE_BLOCK_SIZE*total_blocks*sizeof(layer->weights[0]))) == NULL) return 1;
|
||||
}
|
||||
if (float_weights != NULL) {
|
||||
layer->float_weights = opt_array_check(arrays, float_weights, SPARSE_BLOCK_SIZE*total_blocks*sizeof(layer->float_weights[0]), &err);
|
||||
if (err) return 1;
|
||||
}
|
||||
} else {
|
||||
if (weights != NULL) {
|
||||
if ((layer->weights = find_array_check(arrays, weights, nb_inputs*nb_outputs*sizeof(layer->weights[0]))) == NULL) return 1;
|
||||
}
|
||||
if (float_weights != NULL) {
|
||||
layer->float_weights = opt_array_check(arrays, float_weights, nb_inputs*nb_outputs*sizeof(layer->float_weights[0]), &err);
|
||||
if (err) return 1;
|
||||
}
|
||||
}
|
||||
if (diag != NULL) {
|
||||
if ((layer->diag = find_array_check(arrays, diag, nb_outputs*sizeof(layer->diag[0]))) == NULL) return 1;
|
||||
}
|
||||
if (weights != NULL) {
|
||||
if ((layer->scale = find_array_check(arrays, scale, nb_outputs*sizeof(layer->scale[0]))) == NULL) return 1;
|
||||
}
|
||||
layer->nb_inputs = nb_inputs;
|
||||
layer->nb_outputs = nb_outputs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int conv2d_init(Conv2dLayer *layer, const WeightArray *arrays,
|
||||
const char *bias,
|
||||
const char *float_weights,
|
||||
int in_channels,
|
||||
int out_channels,
|
||||
int ktime,
|
||||
int kheight)
|
||||
{
|
||||
int err;
|
||||
layer->bias = NULL;
|
||||
layer->float_weights = NULL;
|
||||
if (bias != NULL) {
|
||||
if ((layer->bias = find_array_check(arrays, bias, out_channels*sizeof(layer->bias[0]))) == NULL) return 1;
|
||||
}
|
||||
if (float_weights != NULL) {
|
||||
layer->float_weights = opt_array_check(arrays, float_weights, in_channels*out_channels*ktime*kheight*sizeof(layer->float_weights[0]), &err);
|
||||
if (err) return 1;
|
||||
}
|
||||
layer->in_channels = in_channels;
|
||||
layer->out_channels = out_channels;
|
||||
layer->ktime = ktime;
|
||||
layer->kheight = kheight;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if 0
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
int fd;
|
||||
void *data;
|
||||
int len;
|
||||
int nb_arrays;
|
||||
int i;
|
||||
WeightArray *list;
|
||||
struct stat st;
|
||||
const char *filename = "weights_blob.bin";
|
||||
stat(filename, &st);
|
||||
len = st.st_size;
|
||||
fd = open(filename, O_RDONLY);
|
||||
data = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
|
||||
printf("size is %d\n", len);
|
||||
nb_arrays = parse_weights(&list, data, len);
|
||||
for (i=0;i<nb_arrays;i++) {
|
||||
printf("found %s: size %d\n", list[i].name, list[i].size);
|
||||
}
|
||||
printf("%p\n", list[i].name);
|
||||
free(list);
|
||||
munmap(data, len);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
528
src/modules/ZoomIt/ZoomIt/rnnoise/pitch.c
Normal file
528
src/modules/ZoomIt/ZoomIt/rnnoise/pitch.c
Normal file
@@ -0,0 +1,528 @@
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */
|
||||
/**
|
||||
@file pitch.c
|
||||
@brief Pitch analysis
|
||||
*/
|
||||
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "pitch.h"
|
||||
#include "common.h"
|
||||
#include "denoise.h"
|
||||
#include "celt_lpc.h"
|
||||
#include "math.h"
|
||||
|
||||
static void find_best_pitch(opus_val32 *xcorr, opus_val16 *y, int len,
|
||||
int max_pitch, int *best_pitch
|
||||
#ifdef FIXED_POINT
|
||||
, int yshift, opus_val32 maxcorr
|
||||
#endif
|
||||
)
|
||||
{
|
||||
int i, j;
|
||||
opus_val32 Syy=1;
|
||||
opus_val16 best_num[2];
|
||||
opus_val32 best_den[2];
|
||||
#ifdef FIXED_POINT
|
||||
int xshift;
|
||||
|
||||
xshift = celt_ilog2(maxcorr)-14;
|
||||
#endif
|
||||
|
||||
best_num[0] = -1;
|
||||
best_num[1] = -1;
|
||||
best_den[0] = 0;
|
||||
best_den[1] = 0;
|
||||
best_pitch[0] = 0;
|
||||
best_pitch[1] = 1;
|
||||
for (j=0;j<len;j++)
|
||||
Syy = ADD32(Syy, SHR32(MULT16_16(y[j],y[j]), yshift));
|
||||
for (i=0;i<max_pitch;i++)
|
||||
{
|
||||
if (xcorr[i]>0)
|
||||
{
|
||||
opus_val16 num;
|
||||
opus_val32 xcorr16;
|
||||
xcorr16 = EXTRACT16(VSHR32(xcorr[i], xshift));
|
||||
#ifndef FIXED_POINT
|
||||
/* Considering the range of xcorr16, this should avoid both underflows
|
||||
and overflows (inf) when squaring xcorr16 */
|
||||
xcorr16 *= 1e-12f;
|
||||
#endif
|
||||
num = MULT16_16_Q15(xcorr16,xcorr16);
|
||||
if (MULT16_32_Q15(num,best_den[1]) > MULT16_32_Q15(best_num[1],Syy))
|
||||
{
|
||||
if (MULT16_32_Q15(num,best_den[0]) > MULT16_32_Q15(best_num[0],Syy))
|
||||
{
|
||||
best_num[1] = best_num[0];
|
||||
best_den[1] = best_den[0];
|
||||
best_pitch[1] = best_pitch[0];
|
||||
best_num[0] = num;
|
||||
best_den[0] = Syy;
|
||||
best_pitch[0] = i;
|
||||
} else {
|
||||
best_num[1] = num;
|
||||
best_den[1] = Syy;
|
||||
best_pitch[1] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
Syy += SHR32(MULT16_16(y[i+len],y[i+len]),yshift) - SHR32(MULT16_16(y[i],y[i]),yshift);
|
||||
Syy = MAX32(1, Syy);
|
||||
}
|
||||
}
|
||||
|
||||
static void celt_fir5(const opus_val16 *x,
|
||||
const opus_val16 *num,
|
||||
opus_val16 *y,
|
||||
int N,
|
||||
opus_val16 *mem)
|
||||
{
|
||||
int i;
|
||||
opus_val16 num0, num1, num2, num3, num4;
|
||||
opus_val32 mem0, mem1, mem2, mem3, mem4;
|
||||
num0=num[0];
|
||||
num1=num[1];
|
||||
num2=num[2];
|
||||
num3=num[3];
|
||||
num4=num[4];
|
||||
mem0=mem[0];
|
||||
mem1=mem[1];
|
||||
mem2=mem[2];
|
||||
mem3=mem[3];
|
||||
mem4=mem[4];
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
opus_val32 sum = SHL32(EXTEND32(x[i]), SIG_SHIFT);
|
||||
sum = MAC16_16(sum,num0,mem0);
|
||||
sum = MAC16_16(sum,num1,mem1);
|
||||
sum = MAC16_16(sum,num2,mem2);
|
||||
sum = MAC16_16(sum,num3,mem3);
|
||||
sum = MAC16_16(sum,num4,mem4);
|
||||
mem4 = mem3;
|
||||
mem3 = mem2;
|
||||
mem2 = mem1;
|
||||
mem1 = mem0;
|
||||
mem0 = x[i];
|
||||
y[i] = ROUND16(sum, SIG_SHIFT);
|
||||
}
|
||||
mem[0]=mem0;
|
||||
mem[1]=mem1;
|
||||
mem[2]=mem2;
|
||||
mem[3]=mem3;
|
||||
mem[4]=mem4;
|
||||
}
|
||||
|
||||
|
||||
void rnn_pitch_downsample(celt_sig *x[], opus_val16 *x_lp,
|
||||
int len, int C)
|
||||
{
|
||||
int i;
|
||||
opus_val32 ac[5];
|
||||
opus_val16 tmp=Q15ONE;
|
||||
opus_val16 lpc[4], mem[5]={0,0,0,0,0};
|
||||
opus_val16 lpc2[5];
|
||||
opus_val16 c1 = QCONST16(.8f,15);
|
||||
#ifdef FIXED_POINT
|
||||
int shift;
|
||||
opus_val32 maxabs = celt_maxabs32(x[0], len);
|
||||
if (C==2)
|
||||
{
|
||||
opus_val32 maxabs_1 = celt_maxabs32(x[1], len);
|
||||
maxabs = MAX32(maxabs, maxabs_1);
|
||||
}
|
||||
if (maxabs<1)
|
||||
maxabs=1;
|
||||
shift = celt_ilog2(maxabs)-10;
|
||||
if (shift<0)
|
||||
shift=0;
|
||||
if (C==2)
|
||||
shift++;
|
||||
#endif
|
||||
for (i=1;i<len>>1;i++)
|
||||
x_lp[i] = SHR32(HALF32(HALF32(x[0][(2*i-1)]+x[0][(2*i+1)])+x[0][2*i]), shift);
|
||||
x_lp[0] = SHR32(HALF32(HALF32(x[0][1])+x[0][0]), shift);
|
||||
if (C==2)
|
||||
{
|
||||
for (i=1;i<len>>1;i++)
|
||||
x_lp[i] += SHR32(HALF32(HALF32(x[1][(2*i-1)]+x[1][(2*i+1)])+x[1][2*i]), shift);
|
||||
x_lp[0] += SHR32(HALF32(HALF32(x[1][1])+x[1][0]), shift);
|
||||
}
|
||||
|
||||
rnn_autocorr(x_lp, ac, NULL, 0,
|
||||
4, len>>1);
|
||||
|
||||
/* Noise floor -40 dB */
|
||||
#ifdef FIXED_POINT
|
||||
ac[0] += SHR32(ac[0],13);
|
||||
#else
|
||||
ac[0] *= 1.0001f;
|
||||
#endif
|
||||
/* Lag windowing */
|
||||
for (i=1;i<=4;i++)
|
||||
{
|
||||
/*ac[i] *= exp(-.5*(2*M_PI*.002*i)*(2*M_PI*.002*i));*/
|
||||
#ifdef FIXED_POINT
|
||||
ac[i] -= MULT16_32_Q15(2*i*i, ac[i]);
|
||||
#else
|
||||
ac[i] -= ac[i]*(.008f*i)*(.008f*i);
|
||||
#endif
|
||||
}
|
||||
|
||||
rnn_lpc(lpc, ac, 4);
|
||||
for (i=0;i<4;i++)
|
||||
{
|
||||
tmp = MULT16_16_Q15(QCONST16(.9f,15), tmp);
|
||||
lpc[i] = MULT16_16_Q15(lpc[i], tmp);
|
||||
}
|
||||
/* Add a zero */
|
||||
lpc2[0] = lpc[0] + QCONST16(.8f,SIG_SHIFT);
|
||||
lpc2[1] = lpc[1] + MULT16_16_Q15(c1,lpc[0]);
|
||||
lpc2[2] = lpc[2] + MULT16_16_Q15(c1,lpc[1]);
|
||||
lpc2[3] = lpc[3] + MULT16_16_Q15(c1,lpc[2]);
|
||||
lpc2[4] = MULT16_16_Q15(c1,lpc[3]);
|
||||
celt_fir5(x_lp, lpc2, x_lp, len>>1, mem);
|
||||
}
|
||||
|
||||
void rnn_pitch_xcorr(const opus_val16 *_x, const opus_val16 *_y,
|
||||
opus_val32 *xcorr, int len, int max_pitch)
|
||||
{
|
||||
|
||||
#if 0 /* This is a simple version of the pitch correlation that should work
|
||||
well on DSPs like Blackfin and TI C5x/C6x */
|
||||
int i, j;
|
||||
#ifdef FIXED_POINT
|
||||
opus_val32 maxcorr=1;
|
||||
#endif
|
||||
for (i=0;i<max_pitch;i++)
|
||||
{
|
||||
opus_val32 sum = 0;
|
||||
for (j=0;j<len;j++)
|
||||
sum = MAC16_16(sum, _x[j], _y[i+j]);
|
||||
xcorr[i] = sum;
|
||||
#ifdef FIXED_POINT
|
||||
maxcorr = MAX32(maxcorr, sum);
|
||||
#endif
|
||||
}
|
||||
#ifdef FIXED_POINT
|
||||
return maxcorr;
|
||||
#endif
|
||||
|
||||
#else /* Unrolled version of the pitch correlation -- runs faster on x86 and ARM */
|
||||
int i;
|
||||
/*The EDSP version requires that max_pitch is at least 1, and that _x is
|
||||
32-bit aligned.
|
||||
Since it's hard to put asserts in assembly, put them here.*/
|
||||
#ifdef FIXED_POINT
|
||||
opus_val32 maxcorr=1;
|
||||
#endif
|
||||
celt_assert(max_pitch>0);
|
||||
celt_assert((((unsigned char *)_x-(unsigned char *)NULL)&3)==0);
|
||||
for (i=0;i<max_pitch-3;i+=4)
|
||||
{
|
||||
opus_val32 sum[4]={0,0,0,0};
|
||||
xcorr_kernel(_x, _y+i, sum, len);
|
||||
xcorr[i]=sum[0];
|
||||
xcorr[i+1]=sum[1];
|
||||
xcorr[i+2]=sum[2];
|
||||
xcorr[i+3]=sum[3];
|
||||
#ifdef FIXED_POINT
|
||||
sum[0] = MAX32(sum[0], sum[1]);
|
||||
sum[2] = MAX32(sum[2], sum[3]);
|
||||
sum[0] = MAX32(sum[0], sum[2]);
|
||||
maxcorr = MAX32(maxcorr, sum[0]);
|
||||
#endif
|
||||
}
|
||||
/* In case max_pitch isn't a multiple of 4, do non-unrolled version. */
|
||||
for (;i<max_pitch;i++)
|
||||
{
|
||||
opus_val32 sum;
|
||||
sum = celt_inner_prod(_x, _y+i, len);
|
||||
xcorr[i] = sum;
|
||||
#ifdef FIXED_POINT
|
||||
maxcorr = MAX32(maxcorr, sum);
|
||||
#endif
|
||||
}
|
||||
#ifdef FIXED_POINT
|
||||
return maxcorr;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void rnn_pitch_search(const opus_val16 *x_lp, opus_val16 *y,
|
||||
int len, int max_pitch, int *pitch)
|
||||
{
|
||||
int i, j;
|
||||
int lag;
|
||||
int best_pitch[2]={0,0};
|
||||
#ifdef FIXED_POINT
|
||||
opus_val32 maxcorr;
|
||||
opus_val32 xmax, ymax;
|
||||
int shift=0;
|
||||
#endif
|
||||
int offset;
|
||||
opus_val16 x_lp4[PITCH_FRAME_SIZE>>2];
|
||||
opus_val16 y_lp4[(PITCH_FRAME_SIZE+PITCH_MAX_PERIOD)>>2];
|
||||
opus_val32 xcorr[PITCH_MAX_PERIOD>>1];
|
||||
|
||||
celt_assert(len <= PITCH_FRAME_SIZE);
|
||||
celt_assert(max_pitch <= PITCH_MAX_PERIOD);
|
||||
celt_assert(len>0);
|
||||
celt_assert(max_pitch>0);
|
||||
lag = len+max_pitch;
|
||||
|
||||
|
||||
/* Downsample by 2 again */
|
||||
for (j=0;j<len>>2;j++)
|
||||
x_lp4[j] = x_lp[2*j];
|
||||
for (j=0;j<lag>>2;j++)
|
||||
y_lp4[j] = y[2*j];
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
xmax = celt_maxabs16(x_lp4, len>>2);
|
||||
ymax = celt_maxabs16(y_lp4, lag>>2);
|
||||
shift = celt_ilog2(MAX32(1, MAX32(xmax, ymax)))-11;
|
||||
if (shift>0)
|
||||
{
|
||||
for (j=0;j<len>>2;j++)
|
||||
x_lp4[j] = SHR16(x_lp4[j], shift);
|
||||
for (j=0;j<lag>>2;j++)
|
||||
y_lp4[j] = SHR16(y_lp4[j], shift);
|
||||
/* Use double the shift for a MAC */
|
||||
shift *= 2;
|
||||
} else {
|
||||
shift = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Coarse search with 4x decimation */
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
maxcorr =
|
||||
#endif
|
||||
rnn_pitch_xcorr(x_lp4, y_lp4, xcorr, len>>2, max_pitch>>2);
|
||||
|
||||
find_best_pitch(xcorr, y_lp4, len>>2, max_pitch>>2, best_pitch
|
||||
#ifdef FIXED_POINT
|
||||
, 0, maxcorr
|
||||
#endif
|
||||
);
|
||||
|
||||
/* Finer search with 2x decimation */
|
||||
#ifdef FIXED_POINT
|
||||
maxcorr=1;
|
||||
#endif
|
||||
for (i=0;i<max_pitch>>1;i++)
|
||||
{
|
||||
opus_val32 sum;
|
||||
xcorr[i] = 0;
|
||||
if (abs(i-2*best_pitch[0])>2 && abs(i-2*best_pitch[1])>2)
|
||||
continue;
|
||||
#ifdef FIXED_POINT
|
||||
sum = 0;
|
||||
for (j=0;j<len>>1;j++)
|
||||
sum += SHR32(MULT16_16(x_lp[j],y[i+j]), shift);
|
||||
#else
|
||||
sum = celt_inner_prod(x_lp, y+i, len>>1);
|
||||
#endif
|
||||
xcorr[i] = MAX32(-1, sum);
|
||||
#ifdef FIXED_POINT
|
||||
maxcorr = MAX32(maxcorr, sum);
|
||||
#endif
|
||||
}
|
||||
find_best_pitch(xcorr, y, len>>1, max_pitch>>1, best_pitch
|
||||
#ifdef FIXED_POINT
|
||||
, shift+1, maxcorr
|
||||
#endif
|
||||
);
|
||||
|
||||
/* Refine by pseudo-interpolation */
|
||||
if (best_pitch[0]>0 && best_pitch[0]<(max_pitch>>1)-1)
|
||||
{
|
||||
opus_val32 a, b, c;
|
||||
a = xcorr[best_pitch[0]-1];
|
||||
b = xcorr[best_pitch[0]];
|
||||
c = xcorr[best_pitch[0]+1];
|
||||
if ((c-a) > MULT16_32_Q15(QCONST16(.7f,15),b-a))
|
||||
offset = 1;
|
||||
else if ((a-c) > MULT16_32_Q15(QCONST16(.7f,15),b-c))
|
||||
offset = -1;
|
||||
else
|
||||
offset = 0;
|
||||
} else {
|
||||
offset = 0;
|
||||
}
|
||||
*pitch = 2*best_pitch[0]-offset;
|
||||
}
|
||||
|
||||
#ifdef FIXED_POINT
|
||||
static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy)
|
||||
{
|
||||
opus_val32 x2y2;
|
||||
int sx, sy, shift;
|
||||
opus_val32 g;
|
||||
opus_val16 den;
|
||||
if (xy == 0 || xx == 0 || yy == 0)
|
||||
return 0;
|
||||
sx = celt_ilog2(xx)-14;
|
||||
sy = celt_ilog2(yy)-14;
|
||||
shift = sx + sy;
|
||||
x2y2 = SHR32(MULT16_16(VSHR32(xx, sx), VSHR32(yy, sy)), 14);
|
||||
if (shift & 1) {
|
||||
if (x2y2 < 32768)
|
||||
{
|
||||
x2y2 <<= 1;
|
||||
shift--;
|
||||
} else {
|
||||
x2y2 >>= 1;
|
||||
shift++;
|
||||
}
|
||||
}
|
||||
den = celt_rsqrt_norm(x2y2);
|
||||
g = MULT16_32_Q15(den, xy);
|
||||
g = VSHR32(g, (shift>>1)-1);
|
||||
return EXTRACT16(MIN32(g, Q15ONE));
|
||||
}
|
||||
#else
|
||||
static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy)
|
||||
{
|
||||
return xy/sqrt(1+xx*yy);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const int second_check[16] = {0, 0, 3, 2, 3, 2, 5, 2, 3, 2, 3, 2, 5, 2, 3, 2};
|
||||
opus_val16 rnn_remove_doubling(opus_val16 *x, int maxperiod, int minperiod,
|
||||
int N, int *T0_, int prev_period, opus_val16 prev_gain)
|
||||
{
|
||||
int k, i, T, T0;
|
||||
opus_val16 g, g0;
|
||||
opus_val16 pg;
|
||||
opus_val32 xy,xx,yy,xy2;
|
||||
opus_val32 xcorr[3];
|
||||
opus_val32 best_xy, best_yy;
|
||||
int offset;
|
||||
int minperiod0;
|
||||
opus_val32 yy_lookup[PITCH_MAX_PERIOD+1];
|
||||
|
||||
celt_assert(maxperiod <= PITCH_MAX_PERIOD);
|
||||
|
||||
minperiod0 = minperiod;
|
||||
maxperiod /= 2;
|
||||
minperiod /= 2;
|
||||
*T0_ /= 2;
|
||||
prev_period /= 2;
|
||||
N /= 2;
|
||||
x += maxperiod;
|
||||
if (*T0_>=maxperiod)
|
||||
*T0_=maxperiod-1;
|
||||
|
||||
T = T0 = *T0_;
|
||||
dual_inner_prod(x, x, x-T0, N, &xx, &xy);
|
||||
yy_lookup[0] = xx;
|
||||
yy=xx;
|
||||
for (i=1;i<=maxperiod;i++)
|
||||
{
|
||||
yy = yy+MULT16_16(x[-i],x[-i])-MULT16_16(x[N-i],x[N-i]);
|
||||
yy_lookup[i] = MAX32(0, yy);
|
||||
}
|
||||
yy = yy_lookup[T0];
|
||||
best_xy = xy;
|
||||
best_yy = yy;
|
||||
g = g0 = compute_pitch_gain(xy, xx, yy);
|
||||
/* Look for any pitch at T/k */
|
||||
for (k=2;k<=15;k++)
|
||||
{
|
||||
int T1, T1b;
|
||||
opus_val16 g1;
|
||||
opus_val16 cont=0;
|
||||
opus_val16 thresh;
|
||||
T1 = (2*T0+k)/(2*k);
|
||||
if (T1 < minperiod)
|
||||
break;
|
||||
/* Look for another strong correlation at T1b */
|
||||
if (k==2)
|
||||
{
|
||||
if (T1+T0>maxperiod)
|
||||
T1b = T0;
|
||||
else
|
||||
T1b = T0+T1;
|
||||
} else
|
||||
{
|
||||
T1b = (2*second_check[k]*T0+k)/(2*k);
|
||||
}
|
||||
dual_inner_prod(x, &x[-T1], &x[-T1b], N, &xy, &xy2);
|
||||
xy = HALF32(xy + xy2);
|
||||
yy = HALF32(yy_lookup[T1] + yy_lookup[T1b]);
|
||||
g1 = compute_pitch_gain(xy, xx, yy);
|
||||
if (abs(T1-prev_period)<=1)
|
||||
cont = prev_gain;
|
||||
else if (abs(T1-prev_period)<=2 && 5*k*k < T0)
|
||||
cont = HALF16(prev_gain);
|
||||
else
|
||||
cont = 0;
|
||||
thresh = MAX16(QCONST16(.3f,15), MULT16_16_Q15(QCONST16(.7f,15),g0)-cont);
|
||||
/* Bias against very high pitch (very short period) to avoid false-positives
|
||||
due to short-term correlation */
|
||||
if (T1<3*minperiod)
|
||||
thresh = MAX16(QCONST16(.4f,15), MULT16_16_Q15(QCONST16(.85f,15),g0)-cont);
|
||||
else if (T1<2*minperiod)
|
||||
thresh = MAX16(QCONST16(.5f,15), MULT16_16_Q15(QCONST16(.9f,15),g0)-cont);
|
||||
if (g1 > thresh)
|
||||
{
|
||||
best_xy = xy;
|
||||
best_yy = yy;
|
||||
T = T1;
|
||||
g = g1;
|
||||
}
|
||||
}
|
||||
best_xy = MAX32(0, best_xy);
|
||||
if (best_yy <= best_xy)
|
||||
pg = Q15ONE;
|
||||
else
|
||||
pg = best_xy/(best_yy+1);
|
||||
|
||||
for (k=0;k<3;k++)
|
||||
xcorr[k] = celt_inner_prod(x, x-(T+k-1), N);
|
||||
if ((xcorr[2]-xcorr[0]) > MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[0]))
|
||||
offset = 1;
|
||||
else if ((xcorr[0]-xcorr[2]) > MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[2]))
|
||||
offset = -1;
|
||||
else
|
||||
offset = 0;
|
||||
if (pg > g)
|
||||
pg = g;
|
||||
*T0_ = 2*T+offset;
|
||||
|
||||
if (*T0_<minperiod0)
|
||||
*T0_=minperiod0;
|
||||
return pg;
|
||||
}
|
||||
147
src/modules/ZoomIt/ZoomIt/rnnoise/pitch.h
Normal file
147
src/modules/ZoomIt/ZoomIt/rnnoise/pitch.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation
|
||||
Written by Jean-Marc Valin */
|
||||
/**
|
||||
@file pitch.h
|
||||
@brief Pitch analysis
|
||||
*/
|
||||
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef PITCH_H
|
||||
#define PITCH_H
|
||||
|
||||
#include "arch.h"
|
||||
|
||||
void rnn_pitch_downsample(celt_sig *x[], opus_val16 *x_lp,
|
||||
int len, int C);
|
||||
|
||||
void rnn_pitch_search(const opus_val16 *x_lp, opus_val16 *y,
|
||||
int len, int max_pitch, int *pitch);
|
||||
|
||||
opus_val16 rnn_remove_doubling(opus_val16 *x, int maxperiod, int minperiod,
|
||||
int N, int *T0, int prev_period, opus_val16 prev_gain);
|
||||
|
||||
|
||||
/* OPT: This is the kernel you really want to optimize. It gets used a lot
|
||||
by the prefilter and by the PLC. */
|
||||
static OPUS_INLINE void xcorr_kernel(const opus_val16 * x, const opus_val16 * y, opus_val32 sum[4], int len)
|
||||
{
|
||||
int j;
|
||||
opus_val16 y_0, y_1, y_2, y_3;
|
||||
celt_assert(len>=3);
|
||||
y_3=0; /* gcc doesn't realize that y_3 can't be used uninitialized */
|
||||
y_0=*y++;
|
||||
y_1=*y++;
|
||||
y_2=*y++;
|
||||
for (j=0;j<len-3;j+=4)
|
||||
{
|
||||
opus_val16 tmp;
|
||||
tmp = *x++;
|
||||
y_3=*y++;
|
||||
sum[0] = MAC16_16(sum[0],tmp,y_0);
|
||||
sum[1] = MAC16_16(sum[1],tmp,y_1);
|
||||
sum[2] = MAC16_16(sum[2],tmp,y_2);
|
||||
sum[3] = MAC16_16(sum[3],tmp,y_3);
|
||||
tmp=*x++;
|
||||
y_0=*y++;
|
||||
sum[0] = MAC16_16(sum[0],tmp,y_1);
|
||||
sum[1] = MAC16_16(sum[1],tmp,y_2);
|
||||
sum[2] = MAC16_16(sum[2],tmp,y_3);
|
||||
sum[3] = MAC16_16(sum[3],tmp,y_0);
|
||||
tmp=*x++;
|
||||
y_1=*y++;
|
||||
sum[0] = MAC16_16(sum[0],tmp,y_2);
|
||||
sum[1] = MAC16_16(sum[1],tmp,y_3);
|
||||
sum[2] = MAC16_16(sum[2],tmp,y_0);
|
||||
sum[3] = MAC16_16(sum[3],tmp,y_1);
|
||||
tmp=*x++;
|
||||
y_2=*y++;
|
||||
sum[0] = MAC16_16(sum[0],tmp,y_3);
|
||||
sum[1] = MAC16_16(sum[1],tmp,y_0);
|
||||
sum[2] = MAC16_16(sum[2],tmp,y_1);
|
||||
sum[3] = MAC16_16(sum[3],tmp,y_2);
|
||||
}
|
||||
if (j++<len)
|
||||
{
|
||||
opus_val16 tmp = *x++;
|
||||
y_3=*y++;
|
||||
sum[0] = MAC16_16(sum[0],tmp,y_0);
|
||||
sum[1] = MAC16_16(sum[1],tmp,y_1);
|
||||
sum[2] = MAC16_16(sum[2],tmp,y_2);
|
||||
sum[3] = MAC16_16(sum[3],tmp,y_3);
|
||||
}
|
||||
if (j++<len)
|
||||
{
|
||||
opus_val16 tmp=*x++;
|
||||
y_0=*y++;
|
||||
sum[0] = MAC16_16(sum[0],tmp,y_1);
|
||||
sum[1] = MAC16_16(sum[1],tmp,y_2);
|
||||
sum[2] = MAC16_16(sum[2],tmp,y_3);
|
||||
sum[3] = MAC16_16(sum[3],tmp,y_0);
|
||||
}
|
||||
if (j<len)
|
||||
{
|
||||
opus_val16 tmp=*x++;
|
||||
y_1=*y++;
|
||||
sum[0] = MAC16_16(sum[0],tmp,y_2);
|
||||
sum[1] = MAC16_16(sum[1],tmp,y_3);
|
||||
sum[2] = MAC16_16(sum[2],tmp,y_0);
|
||||
sum[3] = MAC16_16(sum[3],tmp,y_1);
|
||||
}
|
||||
}
|
||||
|
||||
static OPUS_INLINE void dual_inner_prod(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02,
|
||||
int N, opus_val32 *xy1, opus_val32 *xy2)
|
||||
{
|
||||
int i;
|
||||
opus_val32 xy01=0;
|
||||
opus_val32 xy02=0;
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
xy01 = MAC16_16(xy01, x[i], y01[i]);
|
||||
xy02 = MAC16_16(xy02, x[i], y02[i]);
|
||||
}
|
||||
*xy1 = xy01;
|
||||
*xy2 = xy02;
|
||||
}
|
||||
|
||||
/*We make sure a C version is always available for cases where the overhead of
|
||||
vectorization and passing around an arch flag aren't worth it.*/
|
||||
static OPUS_INLINE opus_val32 celt_inner_prod(const opus_val16 *x,
|
||||
const opus_val16 *y, int N)
|
||||
{
|
||||
int i;
|
||||
opus_val32 xy=0;
|
||||
for (i=0;i<N;i++)
|
||||
xy = MAC16_16(xy, x[i], y[i]);
|
||||
return xy;
|
||||
}
|
||||
|
||||
void rnn_pitch_xcorr(const opus_val16 *_x, const opus_val16 *_y,
|
||||
opus_val32 *xcorr, int len, int max_pitch);
|
||||
|
||||
#endif
|
||||
60
src/modules/ZoomIt/ZoomIt/rnnoise/rnn.c
Normal file
60
src/modules/ZoomIt/ZoomIt/rnnoise/rnn.c
Normal file
@@ -0,0 +1,60 @@
|
||||
/* Copyright (c) 2008-2011 Octasic Inc.
|
||||
2012-2017 Jean-Marc Valin */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
#include "opus_types.h"
|
||||
#include "common.h"
|
||||
#include "arch.h"
|
||||
#include "rnn.h"
|
||||
#include "rnnoise_data.h"
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
#define INPUT_SIZE 42
|
||||
|
||||
|
||||
void compute_rnn(const RNNoise *model, RNNState *rnn, float *gains, float *vad, const float *input, int arch) {
|
||||
float tmp[MAX_NEURONS];
|
||||
float cat[CONV2_OUT_SIZE + GRU1_OUT_SIZE + GRU2_OUT_SIZE + GRU3_OUT_SIZE];
|
||||
/*for (int i=0;i<INPUT_SIZE;i++) printf("%f ", input[i]);printf("\n");*/
|
||||
compute_generic_conv1d(&model->conv1, tmp, rnn->conv1_state, input, CONV1_IN_SIZE, ACTIVATION_TANH, arch);
|
||||
compute_generic_conv1d(&model->conv2, cat, rnn->conv2_state, tmp, CONV2_IN_SIZE, ACTIVATION_TANH, arch);
|
||||
compute_generic_gru(&model->gru1_input, &model->gru1_recurrent, rnn->gru1_state, cat, arch);
|
||||
compute_generic_gru(&model->gru2_input, &model->gru2_recurrent, rnn->gru2_state, rnn->gru1_state, arch);
|
||||
compute_generic_gru(&model->gru3_input, &model->gru3_recurrent, rnn->gru3_state, rnn->gru2_state, arch);
|
||||
RNN_COPY(&cat[CONV2_OUT_SIZE], rnn->gru1_state, GRU1_OUT_SIZE);
|
||||
RNN_COPY(&cat[CONV2_OUT_SIZE+GRU1_OUT_SIZE], rnn->gru2_state, GRU2_OUT_SIZE);
|
||||
RNN_COPY(&cat[CONV2_OUT_SIZE+GRU1_OUT_SIZE+GRU2_OUT_SIZE], rnn->gru3_state, GRU3_OUT_SIZE);
|
||||
compute_generic_dense(&model->dense_out, gains, cat, ACTIVATION_SIGMOID, arch);
|
||||
compute_generic_dense(&model->vad_dense, vad, cat, ACTIVATION_SIGMOID, arch);
|
||||
/*for (int i=0;i<22;i++) printf("%f ", gains[i]);printf("\n");*/
|
||||
/*printf("%f\n", *vad);*/
|
||||
}
|
||||
49
src/modules/ZoomIt/ZoomIt/rnnoise/rnn.h
Normal file
49
src/modules/ZoomIt/ZoomIt/rnnoise/rnn.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* Copyright (c) 2017 Jean-Marc Valin */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef RNN_H_
|
||||
#define RNN_H_
|
||||
|
||||
#include "rnnoise.h"
|
||||
#include "rnnoise_data.h"
|
||||
|
||||
#include "opus_types.h"
|
||||
|
||||
#define WEIGHTS_SCALE (1.f/256)
|
||||
|
||||
#define MAX_NEURONS 1024
|
||||
|
||||
|
||||
typedef struct {
|
||||
float conv1_state[CONV1_STATE_SIZE];
|
||||
float conv2_state[CONV2_STATE_SIZE];
|
||||
float gru1_state[GRU1_STATE_SIZE];
|
||||
float gru2_state[GRU2_STATE_SIZE];
|
||||
float gru3_state[GRU3_STATE_SIZE];
|
||||
} RNNState;
|
||||
void compute_rnn(const RNNoise *model, RNNState *rnn, float *gains, float *vad, const float *input, int arch);
|
||||
|
||||
#endif /* RNN_H_ */
|
||||
131
src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise.h
Normal file
131
src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/* Copyright (c) 2018 Gregor Richards
|
||||
* Copyright (c) 2017 Mozilla */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef RNNOISE_H
|
||||
#define RNNOISE_H 1
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef RNNOISE_EXPORT
|
||||
# if defined(WIN32)
|
||||
# if defined(RNNOISE_BUILD) && defined(DLL_EXPORT)
|
||||
# define RNNOISE_EXPORT __declspec(dllexport)
|
||||
# else
|
||||
# define RNNOISE_EXPORT
|
||||
# endif
|
||||
# elif defined(__GNUC__) && defined(RNNOISE_BUILD)
|
||||
# define RNNOISE_EXPORT __attribute__ ((visibility ("default")))
|
||||
# else
|
||||
# define RNNOISE_EXPORT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
typedef struct DenoiseState DenoiseState;
|
||||
typedef struct RNNModel RNNModel;
|
||||
|
||||
/**
|
||||
* Return the size of DenoiseState
|
||||
*/
|
||||
RNNOISE_EXPORT int rnnoise_get_size(void);
|
||||
|
||||
/**
|
||||
* Return the number of samples processed by rnnoise_process_frame at a time
|
||||
*/
|
||||
RNNOISE_EXPORT int rnnoise_get_frame_size(void);
|
||||
|
||||
/**
|
||||
* Initializes a pre-allocated DenoiseState
|
||||
*
|
||||
* If model is NULL the default model is used.
|
||||
*
|
||||
* See: rnnoise_create() and rnnoise_model_from_file()
|
||||
*/
|
||||
RNNOISE_EXPORT int rnnoise_init(DenoiseState *st, RNNModel *model);
|
||||
|
||||
/**
|
||||
* Allocate and initialize a DenoiseState
|
||||
*
|
||||
* If model is NULL the default model is used.
|
||||
*
|
||||
* The returned pointer MUST be freed with rnnoise_destroy().
|
||||
*/
|
||||
RNNOISE_EXPORT DenoiseState *rnnoise_create(RNNModel *model);
|
||||
|
||||
/**
|
||||
* Free a DenoiseState produced by rnnoise_create.
|
||||
*
|
||||
* The optional custom model must be freed by rnnoise_model_free() after.
|
||||
*/
|
||||
RNNOISE_EXPORT void rnnoise_destroy(DenoiseState *st);
|
||||
|
||||
/**
|
||||
* Denoise a frame of samples
|
||||
*
|
||||
* in and out must be at least rnnoise_get_frame_size() large.
|
||||
*/
|
||||
RNNOISE_EXPORT float rnnoise_process_frame(DenoiseState *st, float *out, const float *in);
|
||||
|
||||
/**
|
||||
* Load a model from a memory buffer
|
||||
*
|
||||
* It must be deallocated with rnnoise_model_free() and the buffer must remain
|
||||
* valid until after the returned object is destroyed.
|
||||
*/
|
||||
RNNOISE_EXPORT RNNModel *rnnoise_model_from_buffer(const void *ptr, int len);
|
||||
|
||||
|
||||
/**
|
||||
* Load a model from a file
|
||||
*
|
||||
* It must be deallocated with rnnoise_model_free() and the file must not be
|
||||
* closed until the returned object is destroyed.
|
||||
*/
|
||||
RNNOISE_EXPORT RNNModel *rnnoise_model_from_file(FILE *f);
|
||||
|
||||
/**
|
||||
* Load a model from a file name
|
||||
*
|
||||
* It must be deallocated with rnnoise_model_free()
|
||||
*/
|
||||
RNNOISE_EXPORT RNNModel *rnnoise_model_from_filename(const char *filename);
|
||||
|
||||
/**
|
||||
* Free a custom model
|
||||
*
|
||||
* It must be called after all the DenoiseStates referring to it are freed.
|
||||
*/
|
||||
RNNOISE_EXPORT void rnnoise_model_free(RNNModel *model);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
55
src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise_data.h
Normal file
55
src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise_data.h
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
#ifndef RNNOISE_DATA_H
|
||||
#define RNNOISE_DATA_H
|
||||
|
||||
#include "nnet.h"
|
||||
|
||||
|
||||
#define CONV1_OUT_SIZE 128
|
||||
|
||||
#define CONV1_IN_SIZE 65
|
||||
|
||||
#define CONV1_STATE_SIZE (65 * (2))
|
||||
|
||||
#define CONV1_DELAY 1
|
||||
|
||||
#define CONV2_OUT_SIZE 384
|
||||
|
||||
#define CONV2_IN_SIZE 128
|
||||
|
||||
#define CONV2_STATE_SIZE (128 * (2))
|
||||
|
||||
#define CONV2_DELAY 1
|
||||
|
||||
#define GRU1_OUT_SIZE 384
|
||||
|
||||
#define GRU1_STATE_SIZE 384
|
||||
|
||||
#define GRU2_OUT_SIZE 384
|
||||
|
||||
#define GRU2_STATE_SIZE 384
|
||||
|
||||
#define GRU3_OUT_SIZE 384
|
||||
|
||||
#define GRU3_STATE_SIZE 384
|
||||
|
||||
#define DENSE_OUT_OUT_SIZE 32
|
||||
|
||||
#define VAD_DENSE_OUT_SIZE 1
|
||||
|
||||
typedef struct {
|
||||
LinearLayer conv1;
|
||||
LinearLayer conv2;
|
||||
LinearLayer gru1_input;
|
||||
LinearLayer gru1_recurrent;
|
||||
LinearLayer gru2_input;
|
||||
LinearLayer gru2_recurrent;
|
||||
LinearLayer gru3_input;
|
||||
LinearLayer gru3_recurrent;
|
||||
LinearLayer dense_out;
|
||||
LinearLayer vad_dense;
|
||||
} RNNoise;
|
||||
|
||||
int init_rnnoise(RNNoise *model, const WeightArray *arrays);
|
||||
|
||||
#endif /* RNNOISE_DATA_H */
|
||||
274950
src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise_data_little.c
Normal file
274950
src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise_data_little.c
Normal file
File diff suppressed because it is too large
Load Diff
874
src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise_tables.c
Normal file
874
src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise_tables.c
Normal file
@@ -0,0 +1,874 @@
|
||||
/* The contents of this file was automatically generated by dump_rnnoise_tables.c*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#include "kiss_fft.h"
|
||||
|
||||
static const arch_fft_state arch_fft = {0, NULL};
|
||||
|
||||
static const opus_int32 fft_bitrev[960] = {
|
||||
0, 192, 384, 576, 768, 64, 256, 448, 640, 832, 128, 320, 512, 704, 896,
|
||||
16, 208, 400, 592, 784, 80, 272, 464, 656, 848, 144, 336, 528, 720, 912,
|
||||
32, 224, 416, 608, 800, 96, 288, 480, 672, 864, 160, 352, 544, 736, 928,
|
||||
48, 240, 432, 624, 816, 112, 304, 496, 688, 880, 176, 368, 560, 752, 944,
|
||||
4, 196, 388, 580, 772, 68, 260, 452, 644, 836, 132, 324, 516, 708, 900,
|
||||
20, 212, 404, 596, 788, 84, 276, 468, 660, 852, 148, 340, 532, 724, 916,
|
||||
36, 228, 420, 612, 804, 100, 292, 484, 676, 868, 164, 356, 548, 740, 932,
|
||||
52, 244, 436, 628, 820, 116, 308, 500, 692, 884, 180, 372, 564, 756, 948,
|
||||
8, 200, 392, 584, 776, 72, 264, 456, 648, 840, 136, 328, 520, 712, 904,
|
||||
24, 216, 408, 600, 792, 88, 280, 472, 664, 856, 152, 344, 536, 728, 920,
|
||||
40, 232, 424, 616, 808, 104, 296, 488, 680, 872, 168, 360, 552, 744, 936,
|
||||
56, 248, 440, 632, 824, 120, 312, 504, 696, 888, 184, 376, 568, 760, 952,
|
||||
12, 204, 396, 588, 780, 76, 268, 460, 652, 844, 140, 332, 524, 716, 908,
|
||||
28, 220, 412, 604, 796, 92, 284, 476, 668, 860, 156, 348, 540, 732, 924,
|
||||
44, 236, 428, 620, 812, 108, 300, 492, 684, 876, 172, 364, 556, 748, 940,
|
||||
60, 252, 444, 636, 828, 124, 316, 508, 700, 892, 188, 380, 572, 764, 956,
|
||||
1, 193, 385, 577, 769, 65, 257, 449, 641, 833, 129, 321, 513, 705, 897,
|
||||
17, 209, 401, 593, 785, 81, 273, 465, 657, 849, 145, 337, 529, 721, 913,
|
||||
33, 225, 417, 609, 801, 97, 289, 481, 673, 865, 161, 353, 545, 737, 929,
|
||||
49, 241, 433, 625, 817, 113, 305, 497, 689, 881, 177, 369, 561, 753, 945,
|
||||
5, 197, 389, 581, 773, 69, 261, 453, 645, 837, 133, 325, 517, 709, 901,
|
||||
21, 213, 405, 597, 789, 85, 277, 469, 661, 853, 149, 341, 533, 725, 917,
|
||||
37, 229, 421, 613, 805, 101, 293, 485, 677, 869, 165, 357, 549, 741, 933,
|
||||
53, 245, 437, 629, 821, 117, 309, 501, 693, 885, 181, 373, 565, 757, 949,
|
||||
9, 201, 393, 585, 777, 73, 265, 457, 649, 841, 137, 329, 521, 713, 905,
|
||||
25, 217, 409, 601, 793, 89, 281, 473, 665, 857, 153, 345, 537, 729, 921,
|
||||
41, 233, 425, 617, 809, 105, 297, 489, 681, 873, 169, 361, 553, 745, 937,
|
||||
57, 249, 441, 633, 825, 121, 313, 505, 697, 889, 185, 377, 569, 761, 953,
|
||||
13, 205, 397, 589, 781, 77, 269, 461, 653, 845, 141, 333, 525, 717, 909,
|
||||
29, 221, 413, 605, 797, 93, 285, 477, 669, 861, 157, 349, 541, 733, 925,
|
||||
45, 237, 429, 621, 813, 109, 301, 493, 685, 877, 173, 365, 557, 749, 941,
|
||||
61, 253, 445, 637, 829, 125, 317, 509, 701, 893, 189, 381, 573, 765, 957,
|
||||
2, 194, 386, 578, 770, 66, 258, 450, 642, 834, 130, 322, 514, 706, 898,
|
||||
18, 210, 402, 594, 786, 82, 274, 466, 658, 850, 146, 338, 530, 722, 914,
|
||||
34, 226, 418, 610, 802, 98, 290, 482, 674, 866, 162, 354, 546, 738, 930,
|
||||
50, 242, 434, 626, 818, 114, 306, 498, 690, 882, 178, 370, 562, 754, 946,
|
||||
6, 198, 390, 582, 774, 70, 262, 454, 646, 838, 134, 326, 518, 710, 902,
|
||||
22, 214, 406, 598, 790, 86, 278, 470, 662, 854, 150, 342, 534, 726, 918,
|
||||
38, 230, 422, 614, 806, 102, 294, 486, 678, 870, 166, 358, 550, 742, 934,
|
||||
54, 246, 438, 630, 822, 118, 310, 502, 694, 886, 182, 374, 566, 758, 950,
|
||||
10, 202, 394, 586, 778, 74, 266, 458, 650, 842, 138, 330, 522, 714, 906,
|
||||
26, 218, 410, 602, 794, 90, 282, 474, 666, 858, 154, 346, 538, 730, 922,
|
||||
42, 234, 426, 618, 810, 106, 298, 490, 682, 874, 170, 362, 554, 746, 938,
|
||||
58, 250, 442, 634, 826, 122, 314, 506, 698, 890, 186, 378, 570, 762, 954,
|
||||
14, 206, 398, 590, 782, 78, 270, 462, 654, 846, 142, 334, 526, 718, 910,
|
||||
30, 222, 414, 606, 798, 94, 286, 478, 670, 862, 158, 350, 542, 734, 926,
|
||||
46, 238, 430, 622, 814, 110, 302, 494, 686, 878, 174, 366, 558, 750, 942,
|
||||
62, 254, 446, 638, 830, 126, 318, 510, 702, 894, 190, 382, 574, 766, 958,
|
||||
3, 195, 387, 579, 771, 67, 259, 451, 643, 835, 131, 323, 515, 707, 899,
|
||||
19, 211, 403, 595, 787, 83, 275, 467, 659, 851, 147, 339, 531, 723, 915,
|
||||
35, 227, 419, 611, 803, 99, 291, 483, 675, 867, 163, 355, 547, 739, 931,
|
||||
51, 243, 435, 627, 819, 115, 307, 499, 691, 883, 179, 371, 563, 755, 947,
|
||||
7, 199, 391, 583, 775, 71, 263, 455, 647, 839, 135, 327, 519, 711, 903,
|
||||
23, 215, 407, 599, 791, 87, 279, 471, 663, 855, 151, 343, 535, 727, 919,
|
||||
39, 231, 423, 615, 807, 103, 295, 487, 679, 871, 167, 359, 551, 743, 935,
|
||||
55, 247, 439, 631, 823, 119, 311, 503, 695, 887, 183, 375, 567, 759, 951,
|
||||
11, 203, 395, 587, 779, 75, 267, 459, 651, 843, 139, 331, 523, 715, 907,
|
||||
27, 219, 411, 603, 795, 91, 283, 475, 667, 859, 155, 347, 539, 731, 923,
|
||||
43, 235, 427, 619, 811, 107, 299, 491, 683, 875, 171, 363, 555, 747, 939,
|
||||
59, 251, 443, 635, 827, 123, 315, 507, 699, 891, 187, 379, 571, 763, 955,
|
||||
15, 207, 399, 591, 783, 79, 271, 463, 655, 847, 143, 335, 527, 719, 911,
|
||||
31, 223, 415, 607, 799, 95, 287, 479, 671, 863, 159, 351, 543, 735, 927,
|
||||
47, 239, 431, 623, 815, 111, 303, 495, 687, 879, 175, 367, 559, 751, 943,
|
||||
63, 255, 447, 639, 831, 127, 319, 511, 703, 895, 191, 383, 575, 767, 959,
|
||||
};
|
||||
|
||||
static const kiss_twiddle_cpx fft_twiddles[960] = {
|
||||
{1.00000000f, -0.00000000f}, {0.999978602f, -0.00654493785f},
|
||||
{0.999914348f, -0.0130895954f}, {0.999807239f, -0.0196336918f},
|
||||
{0.999657333f, -0.0261769481f}, {0.999464571f, -0.0327190831f},
|
||||
{0.999229014f, -0.0392598175f}, {0.998950660f, -0.0457988679f},
|
||||
{0.998629510f, -0.0523359552f}, {0.998265624f, -0.0588708036f},
|
||||
{0.997858942f, -0.0654031262f}, {0.997409463f, -0.0719326511f},
|
||||
{0.996917307f, -0.0784590989f}, {0.996382475f, -0.0849821791f},
|
||||
{0.995804906f, -0.0915016159f}, {0.995184720f, -0.0980171412f},
|
||||
{0.994521916f, -0.104528464f}, {0.993816435f, -0.111035310f},
|
||||
{0.993068457f, -0.117537394f}, {0.992277920f, -0.124034449f},
|
||||
{0.991444886f, -0.130526185f}, {0.990569353f, -0.137012348f},
|
||||
{0.989651382f, -0.143492624f}, {0.988691032f, -0.149966761f},
|
||||
{0.987688363f, -0.156434461f}, {0.986643314f, -0.162895471f},
|
||||
{0.985556066f, -0.169349506f}, {0.984426558f, -0.175796285f},
|
||||
{0.983254910f, -0.182235524f}, {0.982041121f, -0.188666970f},
|
||||
{0.980785251f, -0.195090324f}, {0.979487419f, -0.201505318f},
|
||||
{0.978147626f, -0.207911685f}, {0.976765871f, -0.214309156f},
|
||||
{0.975342333f, -0.220697433f}, {0.973876953f, -0.227076262f},
|
||||
{0.972369909f, -0.233445361f}, {0.970821202f, -0.239804462f},
|
||||
{0.969230890f, -0.246153295f}, {0.967599094f, -0.252491564f},
|
||||
{0.965925813f, -0.258819044f}, {0.964211166f, -0.265135437f},
|
||||
{0.962455213f, -0.271440446f}, {0.960658073f, -0.277733833f},
|
||||
{0.958819747f, -0.284015357f}, {0.956940353f, -0.290284663f},
|
||||
{0.955019951f, -0.296541572f}, {0.953058660f, -0.302785784f},
|
||||
{0.951056540f, -0.309017003f}, {0.949013650f, -0.315234989f},
|
||||
{0.946930110f, -0.321439475f}, {0.944806039f, -0.327630192f},
|
||||
{0.942641497f, -0.333806872f}, {0.940436542f, -0.339969248f},
|
||||
{0.938191354f, -0.346117049f}, {0.935905933f, -0.352250040f},
|
||||
{0.933580399f, -0.358367950f}, {0.931214929f, -0.364470512f},
|
||||
{0.928809524f, -0.370557427f}, {0.926364362f, -0.376628488f},
|
||||
{0.923879504f, -0.382683426f}, {0.921355128f, -0.388721973f},
|
||||
{0.918791234f, -0.394743860f}, {0.916187942f, -0.400748819f},
|
||||
{0.913545430f, -0.406736642f}, {0.910863817f, -0.412707031f},
|
||||
{0.908143163f, -0.418659747f}, {0.905383646f, -0.424594522f},
|
||||
{0.902585268f, -0.430511087f}, {0.899748266f, -0.436409235f},
|
||||
{0.896872759f, -0.442288697f}, {0.893958807f, -0.448149204f},
|
||||
{0.891006529f, -0.453990489f}, {0.888016105f, -0.459812373f},
|
||||
{0.884987652f, -0.465614527f}, {0.881921291f, -0.471396744f},
|
||||
{0.878817141f, -0.477158755f}, {0.875675321f, -0.482900351f},
|
||||
{0.872496009f, -0.488621235f}, {0.869279325f, -0.494321197f},
|
||||
{0.866025388f, -0.500000000f}, {0.862734377f, -0.505657375f},
|
||||
{0.859406412f, -0.511293113f}, {0.856041610f, -0.516906917f},
|
||||
{0.852640152f, -0.522498548f}, {0.849202156f, -0.528067827f},
|
||||
{0.845727801f, -0.533614516f}, {0.842217207f, -0.539138317f},
|
||||
{0.838670552f, -0.544639051f}, {0.835087955f, -0.550116420f},
|
||||
{0.831469595f, -0.555570245f}, {0.827815652f, -0.561000228f},
|
||||
{0.824126184f, -0.566406250f}, {0.820401430f, -0.571787953f},
|
||||
{0.816641569f, -0.577145219f}, {0.812846661f, -0.582477689f},
|
||||
{0.809017003f, -0.587785244f}, {0.805152655f, -0.593067646f},
|
||||
{0.801253796f, -0.598324597f}, {0.797320664f, -0.603555918f},
|
||||
{0.793353319f, -0.608761430f}, {0.789352059f, -0.613940835f},
|
||||
{0.785316944f, -0.619093955f}, {0.781248152f, -0.624220550f},
|
||||
{0.777145982f, -0.629320383f}, {0.773010433f, -0.634393275f},
|
||||
{0.768841803f, -0.639438987f}, {0.764640272f, -0.644457340f},
|
||||
{0.760405958f, -0.649448037f}, {0.756139100f, -0.654410958f},
|
||||
{0.751839817f, -0.659345806f}, {0.747508347f, -0.664252460f},
|
||||
{0.743144810f, -0.669130623f}, {0.738749504f, -0.673980117f},
|
||||
{0.734322488f, -0.678800762f}, {0.729864061f, -0.683592319f},
|
||||
{0.725374401f, -0.688354552f}, {0.720853567f, -0.693087339f},
|
||||
{0.716301918f, -0.697790444f}, {0.711719632f, -0.702463686f},
|
||||
{0.707106769f, -0.707106769f}, {0.702463686f, -0.711719632f},
|
||||
{0.697790444f, -0.716301918f}, {0.693087339f, -0.720853567f},
|
||||
{0.688354552f, -0.725374401f}, {0.683592319f, -0.729864061f},
|
||||
{0.678800762f, -0.734322488f}, {0.673980117f, -0.738749504f},
|
||||
{0.669130623f, -0.743144810f}, {0.664252460f, -0.747508347f},
|
||||
{0.659345806f, -0.751839817f}, {0.654410958f, -0.756139100f},
|
||||
{0.649448037f, -0.760405958f}, {0.644457340f, -0.764640272f},
|
||||
{0.639438987f, -0.768841803f}, {0.634393275f, -0.773010433f},
|
||||
{0.629320383f, -0.777145982f}, {0.624220550f, -0.781248152f},
|
||||
{0.619093955f, -0.785316944f}, {0.613940835f, -0.789352059f},
|
||||
{0.608761430f, -0.793353319f}, {0.603555918f, -0.797320664f},
|
||||
{0.598324597f, -0.801253796f}, {0.593067646f, -0.805152655f},
|
||||
{0.587785244f, -0.809017003f}, {0.582477689f, -0.812846661f},
|
||||
{0.577145219f, -0.816641569f}, {0.571787953f, -0.820401430f},
|
||||
{0.566406250f, -0.824126184f}, {0.561000228f, -0.827815652f},
|
||||
{0.555570245f, -0.831469595f}, {0.550116420f, -0.835087955f},
|
||||
{0.544639051f, -0.838670552f}, {0.539138317f, -0.842217207f},
|
||||
{0.533614516f, -0.845727801f}, {0.528067827f, -0.849202156f},
|
||||
{0.522498548f, -0.852640152f}, {0.516906917f, -0.856041610f},
|
||||
{0.511293113f, -0.859406412f}, {0.505657375f, -0.862734377f},
|
||||
{0.500000000f, -0.866025388f}, {0.494321197f, -0.869279325f},
|
||||
{0.488621235f, -0.872496009f}, {0.482900351f, -0.875675321f},
|
||||
{0.477158755f, -0.878817141f}, {0.471396744f, -0.881921291f},
|
||||
{0.465614527f, -0.884987652f}, {0.459812373f, -0.888016105f},
|
||||
{0.453990489f, -0.891006529f}, {0.448149204f, -0.893958807f},
|
||||
{0.442288697f, -0.896872759f}, {0.436409235f, -0.899748266f},
|
||||
{0.430511087f, -0.902585268f}, {0.424594522f, -0.905383646f},
|
||||
{0.418659747f, -0.908143163f}, {0.412707031f, -0.910863817f},
|
||||
{0.406736642f, -0.913545430f}, {0.400748819f, -0.916187942f},
|
||||
{0.394743860f, -0.918791234f}, {0.388721973f, -0.921355128f},
|
||||
{0.382683426f, -0.923879504f}, {0.376628488f, -0.926364362f},
|
||||
{0.370557427f, -0.928809524f}, {0.364470512f, -0.931214929f},
|
||||
{0.358367950f, -0.933580399f}, {0.352250040f, -0.935905933f},
|
||||
{0.346117049f, -0.938191354f}, {0.339969248f, -0.940436542f},
|
||||
{0.333806872f, -0.942641497f}, {0.327630192f, -0.944806039f},
|
||||
{0.321439475f, -0.946930110f}, {0.315234989f, -0.949013650f},
|
||||
{0.309017003f, -0.951056540f}, {0.302785784f, -0.953058660f},
|
||||
{0.296541572f, -0.955019951f}, {0.290284663f, -0.956940353f},
|
||||
{0.284015357f, -0.958819747f}, {0.277733833f, -0.960658073f},
|
||||
{0.271440446f, -0.962455213f}, {0.265135437f, -0.964211166f},
|
||||
{0.258819044f, -0.965925813f}, {0.252491564f, -0.967599094f},
|
||||
{0.246153295f, -0.969230890f}, {0.239804462f, -0.970821202f},
|
||||
{0.233445361f, -0.972369909f}, {0.227076262f, -0.973876953f},
|
||||
{0.220697433f, -0.975342333f}, {0.214309156f, -0.976765871f},
|
||||
{0.207911685f, -0.978147626f}, {0.201505318f, -0.979487419f},
|
||||
{0.195090324f, -0.980785251f}, {0.188666970f, -0.982041121f},
|
||||
{0.182235524f, -0.983254910f}, {0.175796285f, -0.984426558f},
|
||||
{0.169349506f, -0.985556066f}, {0.162895471f, -0.986643314f},
|
||||
{0.156434461f, -0.987688363f}, {0.149966761f, -0.988691032f},
|
||||
{0.143492624f, -0.989651382f}, {0.137012348f, -0.990569353f},
|
||||
{0.130526185f, -0.991444886f}, {0.124034449f, -0.992277920f},
|
||||
{0.117537394f, -0.993068457f}, {0.111035310f, -0.993816435f},
|
||||
{0.104528464f, -0.994521916f}, {0.0980171412f, -0.995184720f},
|
||||
{0.0915016159f, -0.995804906f}, {0.0849821791f, -0.996382475f},
|
||||
{0.0784590989f, -0.996917307f}, {0.0719326511f, -0.997409463f},
|
||||
{0.0654031262f, -0.997858942f}, {0.0588708036f, -0.998265624f},
|
||||
{0.0523359552f, -0.998629510f}, {0.0457988679f, -0.998950660f},
|
||||
{0.0392598175f, -0.999229014f}, {0.0327190831f, -0.999464571f},
|
||||
{0.0261769481f, -0.999657333f}, {0.0196336918f, -0.999807239f},
|
||||
{0.0130895954f, -0.999914348f}, {0.00654493785f, -0.999978602f},
|
||||
{6.12323426e-17f, -1.00000000f}, {-0.00654493785f, -0.999978602f},
|
||||
{-0.0130895954f, -0.999914348f}, {-0.0196336918f, -0.999807239f},
|
||||
{-0.0261769481f, -0.999657333f}, {-0.0327190831f, -0.999464571f},
|
||||
{-0.0392598175f, -0.999229014f}, {-0.0457988679f, -0.998950660f},
|
||||
{-0.0523359552f, -0.998629510f}, {-0.0588708036f, -0.998265624f},
|
||||
{-0.0654031262f, -0.997858942f}, {-0.0719326511f, -0.997409463f},
|
||||
{-0.0784590989f, -0.996917307f}, {-0.0849821791f, -0.996382475f},
|
||||
{-0.0915016159f, -0.995804906f}, {-0.0980171412f, -0.995184720f},
|
||||
{-0.104528464f, -0.994521916f}, {-0.111035310f, -0.993816435f},
|
||||
{-0.117537394f, -0.993068457f}, {-0.124034449f, -0.992277920f},
|
||||
{-0.130526185f, -0.991444886f}, {-0.137012348f, -0.990569353f},
|
||||
{-0.143492624f, -0.989651382f}, {-0.149966761f, -0.988691032f},
|
||||
{-0.156434461f, -0.987688363f}, {-0.162895471f, -0.986643314f},
|
||||
{-0.169349506f, -0.985556066f}, {-0.175796285f, -0.984426558f},
|
||||
{-0.182235524f, -0.983254910f}, {-0.188666970f, -0.982041121f},
|
||||
{-0.195090324f, -0.980785251f}, {-0.201505318f, -0.979487419f},
|
||||
{-0.207911685f, -0.978147626f}, {-0.214309156f, -0.976765871f},
|
||||
{-0.220697433f, -0.975342333f}, {-0.227076262f, -0.973876953f},
|
||||
{-0.233445361f, -0.972369909f}, {-0.239804462f, -0.970821202f},
|
||||
{-0.246153295f, -0.969230890f}, {-0.252491564f, -0.967599094f},
|
||||
{-0.258819044f, -0.965925813f}, {-0.265135437f, -0.964211166f},
|
||||
{-0.271440446f, -0.962455213f}, {-0.277733833f, -0.960658073f},
|
||||
{-0.284015357f, -0.958819747f}, {-0.290284663f, -0.956940353f},
|
||||
{-0.296541572f, -0.955019951f}, {-0.302785784f, -0.953058660f},
|
||||
{-0.309017003f, -0.951056540f}, {-0.315234989f, -0.949013650f},
|
||||
{-0.321439475f, -0.946930110f}, {-0.327630192f, -0.944806039f},
|
||||
{-0.333806872f, -0.942641497f}, {-0.339969248f, -0.940436542f},
|
||||
{-0.346117049f, -0.938191354f}, {-0.352250040f, -0.935905933f},
|
||||
{-0.358367950f, -0.933580399f}, {-0.364470512f, -0.931214929f},
|
||||
{-0.370557427f, -0.928809524f}, {-0.376628488f, -0.926364362f},
|
||||
{-0.382683426f, -0.923879504f}, {-0.388721973f, -0.921355128f},
|
||||
{-0.394743860f, -0.918791234f}, {-0.400748819f, -0.916187942f},
|
||||
{-0.406736642f, -0.913545430f}, {-0.412707031f, -0.910863817f},
|
||||
{-0.418659747f, -0.908143163f}, {-0.424594522f, -0.905383646f},
|
||||
{-0.430511087f, -0.902585268f}, {-0.436409235f, -0.899748266f},
|
||||
{-0.442288697f, -0.896872759f}, {-0.448149204f, -0.893958807f},
|
||||
{-0.453990489f, -0.891006529f}, {-0.459812373f, -0.888016105f},
|
||||
{-0.465614527f, -0.884987652f}, {-0.471396744f, -0.881921291f},
|
||||
{-0.477158755f, -0.878817141f}, {-0.482900351f, -0.875675321f},
|
||||
{-0.488621235f, -0.872496009f}, {-0.494321197f, -0.869279325f},
|
||||
{-0.500000000f, -0.866025388f}, {-0.505657375f, -0.862734377f},
|
||||
{-0.511293113f, -0.859406412f}, {-0.516906917f, -0.856041610f},
|
||||
{-0.522498548f, -0.852640152f}, {-0.528067827f, -0.849202156f},
|
||||
{-0.533614516f, -0.845727801f}, {-0.539138317f, -0.842217207f},
|
||||
{-0.544639051f, -0.838670552f}, {-0.550116420f, -0.835087955f},
|
||||
{-0.555570245f, -0.831469595f}, {-0.561000228f, -0.827815652f},
|
||||
{-0.566406250f, -0.824126184f}, {-0.571787953f, -0.820401430f},
|
||||
{-0.577145219f, -0.816641569f}, {-0.582477689f, -0.812846661f},
|
||||
{-0.587785244f, -0.809017003f}, {-0.593067646f, -0.805152655f},
|
||||
{-0.598324597f, -0.801253796f}, {-0.603555918f, -0.797320664f},
|
||||
{-0.608761430f, -0.793353319f}, {-0.613940835f, -0.789352059f},
|
||||
{-0.619093955f, -0.785316944f}, {-0.624220550f, -0.781248152f},
|
||||
{-0.629320383f, -0.777145982f}, {-0.634393275f, -0.773010433f},
|
||||
{-0.639438987f, -0.768841803f}, {-0.644457340f, -0.764640272f},
|
||||
{-0.649448037f, -0.760405958f}, {-0.654410958f, -0.756139100f},
|
||||
{-0.659345806f, -0.751839817f}, {-0.664252460f, -0.747508347f},
|
||||
{-0.669130623f, -0.743144810f}, {-0.673980117f, -0.738749504f},
|
||||
{-0.678800762f, -0.734322488f}, {-0.683592319f, -0.729864061f},
|
||||
{-0.688354552f, -0.725374401f}, {-0.693087339f, -0.720853567f},
|
||||
{-0.697790444f, -0.716301918f}, {-0.702463686f, -0.711719632f},
|
||||
{-0.707106769f, -0.707106769f}, {-0.711719632f, -0.702463686f},
|
||||
{-0.716301918f, -0.697790444f}, {-0.720853567f, -0.693087339f},
|
||||
{-0.725374401f, -0.688354552f}, {-0.729864061f, -0.683592319f},
|
||||
{-0.734322488f, -0.678800762f}, {-0.738749504f, -0.673980117f},
|
||||
{-0.743144810f, -0.669130623f}, {-0.747508347f, -0.664252460f},
|
||||
{-0.751839817f, -0.659345806f}, {-0.756139100f, -0.654410958f},
|
||||
{-0.760405958f, -0.649448037f}, {-0.764640272f, -0.644457340f},
|
||||
{-0.768841803f, -0.639438987f}, {-0.773010433f, -0.634393275f},
|
||||
{-0.777145982f, -0.629320383f}, {-0.781248152f, -0.624220550f},
|
||||
{-0.785316944f, -0.619093955f}, {-0.789352059f, -0.613940835f},
|
||||
{-0.793353319f, -0.608761430f}, {-0.797320664f, -0.603555918f},
|
||||
{-0.801253796f, -0.598324597f}, {-0.805152655f, -0.593067646f},
|
||||
{-0.809017003f, -0.587785244f}, {-0.812846661f, -0.582477689f},
|
||||
{-0.816641569f, -0.577145219f}, {-0.820401430f, -0.571787953f},
|
||||
{-0.824126184f, -0.566406250f}, {-0.827815652f, -0.561000228f},
|
||||
{-0.831469595f, -0.555570245f}, {-0.835087955f, -0.550116420f},
|
||||
{-0.838670552f, -0.544639051f}, {-0.842217207f, -0.539138317f},
|
||||
{-0.845727801f, -0.533614516f}, {-0.849202156f, -0.528067827f},
|
||||
{-0.852640152f, -0.522498548f}, {-0.856041610f, -0.516906917f},
|
||||
{-0.859406412f, -0.511293113f}, {-0.862734377f, -0.505657375f},
|
||||
{-0.866025388f, -0.500000000f}, {-0.869279325f, -0.494321197f},
|
||||
{-0.872496009f, -0.488621235f}, {-0.875675321f, -0.482900351f},
|
||||
{-0.878817141f, -0.477158755f}, {-0.881921291f, -0.471396744f},
|
||||
{-0.884987652f, -0.465614527f}, {-0.888016105f, -0.459812373f},
|
||||
{-0.891006529f, -0.453990489f}, {-0.893958807f, -0.448149204f},
|
||||
{-0.896872759f, -0.442288697f}, {-0.899748266f, -0.436409235f},
|
||||
{-0.902585268f, -0.430511087f}, {-0.905383646f, -0.424594522f},
|
||||
{-0.908143163f, -0.418659747f}, {-0.910863817f, -0.412707031f},
|
||||
{-0.913545430f, -0.406736642f}, {-0.916187942f, -0.400748819f},
|
||||
{-0.918791234f, -0.394743860f}, {-0.921355128f, -0.388721973f},
|
||||
{-0.923879504f, -0.382683426f}, {-0.926364362f, -0.376628488f},
|
||||
{-0.928809524f, -0.370557427f}, {-0.931214929f, -0.364470512f},
|
||||
{-0.933580399f, -0.358367950f}, {-0.935905933f, -0.352250040f},
|
||||
{-0.938191354f, -0.346117049f}, {-0.940436542f, -0.339969248f},
|
||||
{-0.942641497f, -0.333806872f}, {-0.944806039f, -0.327630192f},
|
||||
{-0.946930110f, -0.321439475f}, {-0.949013650f, -0.315234989f},
|
||||
{-0.951056540f, -0.309017003f}, {-0.953058660f, -0.302785784f},
|
||||
{-0.955019951f, -0.296541572f}, {-0.956940353f, -0.290284663f},
|
||||
{-0.958819747f, -0.284015357f}, {-0.960658073f, -0.277733833f},
|
||||
{-0.962455213f, -0.271440446f}, {-0.964211166f, -0.265135437f},
|
||||
{-0.965925813f, -0.258819044f}, {-0.967599094f, -0.252491564f},
|
||||
{-0.969230890f, -0.246153295f}, {-0.970821202f, -0.239804462f},
|
||||
{-0.972369909f, -0.233445361f}, {-0.973876953f, -0.227076262f},
|
||||
{-0.975342333f, -0.220697433f}, {-0.976765871f, -0.214309156f},
|
||||
{-0.978147626f, -0.207911685f}, {-0.979487419f, -0.201505318f},
|
||||
{-0.980785251f, -0.195090324f}, {-0.982041121f, -0.188666970f},
|
||||
{-0.983254910f, -0.182235524f}, {-0.984426558f, -0.175796285f},
|
||||
{-0.985556066f, -0.169349506f}, {-0.986643314f, -0.162895471f},
|
||||
{-0.987688363f, -0.156434461f}, {-0.988691032f, -0.149966761f},
|
||||
{-0.989651382f, -0.143492624f}, {-0.990569353f, -0.137012348f},
|
||||
{-0.991444886f, -0.130526185f}, {-0.992277920f, -0.124034449f},
|
||||
{-0.993068457f, -0.117537394f}, {-0.993816435f, -0.111035310f},
|
||||
{-0.994521916f, -0.104528464f}, {-0.995184720f, -0.0980171412f},
|
||||
{-0.995804906f, -0.0915016159f}, {-0.996382475f, -0.0849821791f},
|
||||
{-0.996917307f, -0.0784590989f}, {-0.997409463f, -0.0719326511f},
|
||||
{-0.997858942f, -0.0654031262f}, {-0.998265624f, -0.0588708036f},
|
||||
{-0.998629510f, -0.0523359552f}, {-0.998950660f, -0.0457988679f},
|
||||
{-0.999229014f, -0.0392598175f}, {-0.999464571f, -0.0327190831f},
|
||||
{-0.999657333f, -0.0261769481f}, {-0.999807239f, -0.0196336918f},
|
||||
{-0.999914348f, -0.0130895954f}, {-0.999978602f, -0.00654493785f},
|
||||
{-1.00000000f, -1.22464685e-16f}, {-0.999978602f, 0.00654493785f},
|
||||
{-0.999914348f, 0.0130895954f}, {-0.999807239f, 0.0196336918f},
|
||||
{-0.999657333f, 0.0261769481f}, {-0.999464571f, 0.0327190831f},
|
||||
{-0.999229014f, 0.0392598175f}, {-0.998950660f, 0.0457988679f},
|
||||
{-0.998629510f, 0.0523359552f}, {-0.998265624f, 0.0588708036f},
|
||||
{-0.997858942f, 0.0654031262f}, {-0.997409463f, 0.0719326511f},
|
||||
{-0.996917307f, 0.0784590989f}, {-0.996382475f, 0.0849821791f},
|
||||
{-0.995804906f, 0.0915016159f}, {-0.995184720f, 0.0980171412f},
|
||||
{-0.994521916f, 0.104528464f}, {-0.993816435f, 0.111035310f},
|
||||
{-0.993068457f, 0.117537394f}, {-0.992277920f, 0.124034449f},
|
||||
{-0.991444886f, 0.130526185f}, {-0.990569353f, 0.137012348f},
|
||||
{-0.989651382f, 0.143492624f}, {-0.988691032f, 0.149966761f},
|
||||
{-0.987688363f, 0.156434461f}, {-0.986643314f, 0.162895471f},
|
||||
{-0.985556066f, 0.169349506f}, {-0.984426558f, 0.175796285f},
|
||||
{-0.983254910f, 0.182235524f}, {-0.982041121f, 0.188666970f},
|
||||
{-0.980785251f, 0.195090324f}, {-0.979487419f, 0.201505318f},
|
||||
{-0.978147626f, 0.207911685f}, {-0.976765871f, 0.214309156f},
|
||||
{-0.975342333f, 0.220697433f}, {-0.973876953f, 0.227076262f},
|
||||
{-0.972369909f, 0.233445361f}, {-0.970821202f, 0.239804462f},
|
||||
{-0.969230890f, 0.246153295f}, {-0.967599094f, 0.252491564f},
|
||||
{-0.965925813f, 0.258819044f}, {-0.964211166f, 0.265135437f},
|
||||
{-0.962455213f, 0.271440446f}, {-0.960658073f, 0.277733833f},
|
||||
{-0.958819747f, 0.284015357f}, {-0.956940353f, 0.290284663f},
|
||||
{-0.955019951f, 0.296541572f}, {-0.953058660f, 0.302785784f},
|
||||
{-0.951056540f, 0.309017003f}, {-0.949013650f, 0.315234989f},
|
||||
{-0.946930110f, 0.321439475f}, {-0.944806039f, 0.327630192f},
|
||||
{-0.942641497f, 0.333806872f}, {-0.940436542f, 0.339969248f},
|
||||
{-0.938191354f, 0.346117049f}, {-0.935905933f, 0.352250040f},
|
||||
{-0.933580399f, 0.358367950f}, {-0.931214929f, 0.364470512f},
|
||||
{-0.928809524f, 0.370557427f}, {-0.926364362f, 0.376628488f},
|
||||
{-0.923879504f, 0.382683426f}, {-0.921355128f, 0.388721973f},
|
||||
{-0.918791234f, 0.394743860f}, {-0.916187942f, 0.400748819f},
|
||||
{-0.913545430f, 0.406736642f}, {-0.910863817f, 0.412707031f},
|
||||
{-0.908143163f, 0.418659747f}, {-0.905383646f, 0.424594522f},
|
||||
{-0.902585268f, 0.430511087f}, {-0.899748266f, 0.436409235f},
|
||||
{-0.896872759f, 0.442288697f}, {-0.893958807f, 0.448149204f},
|
||||
{-0.891006529f, 0.453990489f}, {-0.888016105f, 0.459812373f},
|
||||
{-0.884987652f, 0.465614527f}, {-0.881921291f, 0.471396744f},
|
||||
{-0.878817141f, 0.477158755f}, {-0.875675321f, 0.482900351f},
|
||||
{-0.872496009f, 0.488621235f}, {-0.869279325f, 0.494321197f},
|
||||
{-0.866025388f, 0.500000000f}, {-0.862734377f, 0.505657375f},
|
||||
{-0.859406412f, 0.511293113f}, {-0.856041610f, 0.516906917f},
|
||||
{-0.852640152f, 0.522498548f}, {-0.849202156f, 0.528067827f},
|
||||
{-0.845727801f, 0.533614516f}, {-0.842217207f, 0.539138317f},
|
||||
{-0.838670552f, 0.544639051f}, {-0.835087955f, 0.550116420f},
|
||||
{-0.831469595f, 0.555570245f}, {-0.827815652f, 0.561000228f},
|
||||
{-0.824126184f, 0.566406250f}, {-0.820401430f, 0.571787953f},
|
||||
{-0.816641569f, 0.577145219f}, {-0.812846661f, 0.582477689f},
|
||||
{-0.809017003f, 0.587785244f}, {-0.805152655f, 0.593067646f},
|
||||
{-0.801253796f, 0.598324597f}, {-0.797320664f, 0.603555918f},
|
||||
{-0.793353319f, 0.608761430f}, {-0.789352059f, 0.613940835f},
|
||||
{-0.785316944f, 0.619093955f}, {-0.781248152f, 0.624220550f},
|
||||
{-0.777145982f, 0.629320383f}, {-0.773010433f, 0.634393275f},
|
||||
{-0.768841803f, 0.639438987f}, {-0.764640272f, 0.644457340f},
|
||||
{-0.760405958f, 0.649448037f}, {-0.756139100f, 0.654410958f},
|
||||
{-0.751839817f, 0.659345806f}, {-0.747508347f, 0.664252460f},
|
||||
{-0.743144810f, 0.669130623f}, {-0.738749504f, 0.673980117f},
|
||||
{-0.734322488f, 0.678800762f}, {-0.729864061f, 0.683592319f},
|
||||
{-0.725374401f, 0.688354552f}, {-0.720853567f, 0.693087339f},
|
||||
{-0.716301918f, 0.697790444f}, {-0.711719632f, 0.702463686f},
|
||||
{-0.707106769f, 0.707106769f}, {-0.702463686f, 0.711719632f},
|
||||
{-0.697790444f, 0.716301918f}, {-0.693087339f, 0.720853567f},
|
||||
{-0.688354552f, 0.725374401f}, {-0.683592319f, 0.729864061f},
|
||||
{-0.678800762f, 0.734322488f}, {-0.673980117f, 0.738749504f},
|
||||
{-0.669130623f, 0.743144810f}, {-0.664252460f, 0.747508347f},
|
||||
{-0.659345806f, 0.751839817f}, {-0.654410958f, 0.756139100f},
|
||||
{-0.649448037f, 0.760405958f}, {-0.644457340f, 0.764640272f},
|
||||
{-0.639438987f, 0.768841803f}, {-0.634393275f, 0.773010433f},
|
||||
{-0.629320383f, 0.777145982f}, {-0.624220550f, 0.781248152f},
|
||||
{-0.619093955f, 0.785316944f}, {-0.613940835f, 0.789352059f},
|
||||
{-0.608761430f, 0.793353319f}, {-0.603555918f, 0.797320664f},
|
||||
{-0.598324597f, 0.801253796f}, {-0.593067646f, 0.805152655f},
|
||||
{-0.587785244f, 0.809017003f}, {-0.582477689f, 0.812846661f},
|
||||
{-0.577145219f, 0.816641569f}, {-0.571787953f, 0.820401430f},
|
||||
{-0.566406250f, 0.824126184f}, {-0.561000228f, 0.827815652f},
|
||||
{-0.555570245f, 0.831469595f}, {-0.550116420f, 0.835087955f},
|
||||
{-0.544639051f, 0.838670552f}, {-0.539138317f, 0.842217207f},
|
||||
{-0.533614516f, 0.845727801f}, {-0.528067827f, 0.849202156f},
|
||||
{-0.522498548f, 0.852640152f}, {-0.516906917f, 0.856041610f},
|
||||
{-0.511293113f, 0.859406412f}, {-0.505657375f, 0.862734377f},
|
||||
{-0.500000000f, 0.866025388f}, {-0.494321197f, 0.869279325f},
|
||||
{-0.488621235f, 0.872496009f}, {-0.482900351f, 0.875675321f},
|
||||
{-0.477158755f, 0.878817141f}, {-0.471396744f, 0.881921291f},
|
||||
{-0.465614527f, 0.884987652f}, {-0.459812373f, 0.888016105f},
|
||||
{-0.453990489f, 0.891006529f}, {-0.448149204f, 0.893958807f},
|
||||
{-0.442288697f, 0.896872759f}, {-0.436409235f, 0.899748266f},
|
||||
{-0.430511087f, 0.902585268f}, {-0.424594522f, 0.905383646f},
|
||||
{-0.418659747f, 0.908143163f}, {-0.412707031f, 0.910863817f},
|
||||
{-0.406736642f, 0.913545430f}, {-0.400748819f, 0.916187942f},
|
||||
{-0.394743860f, 0.918791234f}, {-0.388721973f, 0.921355128f},
|
||||
{-0.382683426f, 0.923879504f}, {-0.376628488f, 0.926364362f},
|
||||
{-0.370557427f, 0.928809524f}, {-0.364470512f, 0.931214929f},
|
||||
{-0.358367950f, 0.933580399f}, {-0.352250040f, 0.935905933f},
|
||||
{-0.346117049f, 0.938191354f}, {-0.339969248f, 0.940436542f},
|
||||
{-0.333806872f, 0.942641497f}, {-0.327630192f, 0.944806039f},
|
||||
{-0.321439475f, 0.946930110f}, {-0.315234989f, 0.949013650f},
|
||||
{-0.309017003f, 0.951056540f}, {-0.302785784f, 0.953058660f},
|
||||
{-0.296541572f, 0.955019951f}, {-0.290284663f, 0.956940353f},
|
||||
{-0.284015357f, 0.958819747f}, {-0.277733833f, 0.960658073f},
|
||||
{-0.271440446f, 0.962455213f}, {-0.265135437f, 0.964211166f},
|
||||
{-0.258819044f, 0.965925813f}, {-0.252491564f, 0.967599094f},
|
||||
{-0.246153295f, 0.969230890f}, {-0.239804462f, 0.970821202f},
|
||||
{-0.233445361f, 0.972369909f}, {-0.227076262f, 0.973876953f},
|
||||
{-0.220697433f, 0.975342333f}, {-0.214309156f, 0.976765871f},
|
||||
{-0.207911685f, 0.978147626f}, {-0.201505318f, 0.979487419f},
|
||||
{-0.195090324f, 0.980785251f}, {-0.188666970f, 0.982041121f},
|
||||
{-0.182235524f, 0.983254910f}, {-0.175796285f, 0.984426558f},
|
||||
{-0.169349506f, 0.985556066f}, {-0.162895471f, 0.986643314f},
|
||||
{-0.156434461f, 0.987688363f}, {-0.149966761f, 0.988691032f},
|
||||
{-0.143492624f, 0.989651382f}, {-0.137012348f, 0.990569353f},
|
||||
{-0.130526185f, 0.991444886f}, {-0.124034449f, 0.992277920f},
|
||||
{-0.117537394f, 0.993068457f}, {-0.111035310f, 0.993816435f},
|
||||
{-0.104528464f, 0.994521916f}, {-0.0980171412f, 0.995184720f},
|
||||
{-0.0915016159f, 0.995804906f}, {-0.0849821791f, 0.996382475f},
|
||||
{-0.0784590989f, 0.996917307f}, {-0.0719326511f, 0.997409463f},
|
||||
{-0.0654031262f, 0.997858942f}, {-0.0588708036f, 0.998265624f},
|
||||
{-0.0523359552f, 0.998629510f}, {-0.0457988679f, 0.998950660f},
|
||||
{-0.0392598175f, 0.999229014f}, {-0.0327190831f, 0.999464571f},
|
||||
{-0.0261769481f, 0.999657333f}, {-0.0196336918f, 0.999807239f},
|
||||
{-0.0130895954f, 0.999914348f}, {-0.00654493785f, 0.999978602f},
|
||||
{-1.83697015e-16f, 1.00000000f}, {0.00654493785f, 0.999978602f},
|
||||
{0.0130895954f, 0.999914348f}, {0.0196336918f, 0.999807239f},
|
||||
{0.0261769481f, 0.999657333f}, {0.0327190831f, 0.999464571f},
|
||||
{0.0392598175f, 0.999229014f}, {0.0457988679f, 0.998950660f},
|
||||
{0.0523359552f, 0.998629510f}, {0.0588708036f, 0.998265624f},
|
||||
{0.0654031262f, 0.997858942f}, {0.0719326511f, 0.997409463f},
|
||||
{0.0784590989f, 0.996917307f}, {0.0849821791f, 0.996382475f},
|
||||
{0.0915016159f, 0.995804906f}, {0.0980171412f, 0.995184720f},
|
||||
{0.104528464f, 0.994521916f}, {0.111035310f, 0.993816435f},
|
||||
{0.117537394f, 0.993068457f}, {0.124034449f, 0.992277920f},
|
||||
{0.130526185f, 0.991444886f}, {0.137012348f, 0.990569353f},
|
||||
{0.143492624f, 0.989651382f}, {0.149966761f, 0.988691032f},
|
||||
{0.156434461f, 0.987688363f}, {0.162895471f, 0.986643314f},
|
||||
{0.169349506f, 0.985556066f}, {0.175796285f, 0.984426558f},
|
||||
{0.182235524f, 0.983254910f}, {0.188666970f, 0.982041121f},
|
||||
{0.195090324f, 0.980785251f}, {0.201505318f, 0.979487419f},
|
||||
{0.207911685f, 0.978147626f}, {0.214309156f, 0.976765871f},
|
||||
{0.220697433f, 0.975342333f}, {0.227076262f, 0.973876953f},
|
||||
{0.233445361f, 0.972369909f}, {0.239804462f, 0.970821202f},
|
||||
{0.246153295f, 0.969230890f}, {0.252491564f, 0.967599094f},
|
||||
{0.258819044f, 0.965925813f}, {0.265135437f, 0.964211166f},
|
||||
{0.271440446f, 0.962455213f}, {0.277733833f, 0.960658073f},
|
||||
{0.284015357f, 0.958819747f}, {0.290284663f, 0.956940353f},
|
||||
{0.296541572f, 0.955019951f}, {0.302785784f, 0.953058660f},
|
||||
{0.309017003f, 0.951056540f}, {0.315234989f, 0.949013650f},
|
||||
{0.321439475f, 0.946930110f}, {0.327630192f, 0.944806039f},
|
||||
{0.333806872f, 0.942641497f}, {0.339969248f, 0.940436542f},
|
||||
{0.346117049f, 0.938191354f}, {0.352250040f, 0.935905933f},
|
||||
{0.358367950f, 0.933580399f}, {0.364470512f, 0.931214929f},
|
||||
{0.370557427f, 0.928809524f}, {0.376628488f, 0.926364362f},
|
||||
{0.382683426f, 0.923879504f}, {0.388721973f, 0.921355128f},
|
||||
{0.394743860f, 0.918791234f}, {0.400748819f, 0.916187942f},
|
||||
{0.406736642f, 0.913545430f}, {0.412707031f, 0.910863817f},
|
||||
{0.418659747f, 0.908143163f}, {0.424594522f, 0.905383646f},
|
||||
{0.430511087f, 0.902585268f}, {0.436409235f, 0.899748266f},
|
||||
{0.442288697f, 0.896872759f}, {0.448149204f, 0.893958807f},
|
||||
{0.453990489f, 0.891006529f}, {0.459812373f, 0.888016105f},
|
||||
{0.465614527f, 0.884987652f}, {0.471396744f, 0.881921291f},
|
||||
{0.477158755f, 0.878817141f}, {0.482900351f, 0.875675321f},
|
||||
{0.488621235f, 0.872496009f}, {0.494321197f, 0.869279325f},
|
||||
{0.500000000f, 0.866025388f}, {0.505657375f, 0.862734377f},
|
||||
{0.511293113f, 0.859406412f}, {0.516906917f, 0.856041610f},
|
||||
{0.522498548f, 0.852640152f}, {0.528067827f, 0.849202156f},
|
||||
{0.533614516f, 0.845727801f}, {0.539138317f, 0.842217207f},
|
||||
{0.544639051f, 0.838670552f}, {0.550116420f, 0.835087955f},
|
||||
{0.555570245f, 0.831469595f}, {0.561000228f, 0.827815652f},
|
||||
{0.566406250f, 0.824126184f}, {0.571787953f, 0.820401430f},
|
||||
{0.577145219f, 0.816641569f}, {0.582477689f, 0.812846661f},
|
||||
{0.587785244f, 0.809017003f}, {0.593067646f, 0.805152655f},
|
||||
{0.598324597f, 0.801253796f}, {0.603555918f, 0.797320664f},
|
||||
{0.608761430f, 0.793353319f}, {0.613940835f, 0.789352059f},
|
||||
{0.619093955f, 0.785316944f}, {0.624220550f, 0.781248152f},
|
||||
{0.629320383f, 0.777145982f}, {0.634393275f, 0.773010433f},
|
||||
{0.639438987f, 0.768841803f}, {0.644457340f, 0.764640272f},
|
||||
{0.649448037f, 0.760405958f}, {0.654410958f, 0.756139100f},
|
||||
{0.659345806f, 0.751839817f}, {0.664252460f, 0.747508347f},
|
||||
{0.669130623f, 0.743144810f}, {0.673980117f, 0.738749504f},
|
||||
{0.678800762f, 0.734322488f}, {0.683592319f, 0.729864061f},
|
||||
{0.688354552f, 0.725374401f}, {0.693087339f, 0.720853567f},
|
||||
{0.697790444f, 0.716301918f}, {0.702463686f, 0.711719632f},
|
||||
{0.707106769f, 0.707106769f}, {0.711719632f, 0.702463686f},
|
||||
{0.716301918f, 0.697790444f}, {0.720853567f, 0.693087339f},
|
||||
{0.725374401f, 0.688354552f}, {0.729864061f, 0.683592319f},
|
||||
{0.734322488f, 0.678800762f}, {0.738749504f, 0.673980117f},
|
||||
{0.743144810f, 0.669130623f}, {0.747508347f, 0.664252460f},
|
||||
{0.751839817f, 0.659345806f}, {0.756139100f, 0.654410958f},
|
||||
{0.760405958f, 0.649448037f}, {0.764640272f, 0.644457340f},
|
||||
{0.768841803f, 0.639438987f}, {0.773010433f, 0.634393275f},
|
||||
{0.777145982f, 0.629320383f}, {0.781248152f, 0.624220550f},
|
||||
{0.785316944f, 0.619093955f}, {0.789352059f, 0.613940835f},
|
||||
{0.793353319f, 0.608761430f}, {0.797320664f, 0.603555918f},
|
||||
{0.801253796f, 0.598324597f}, {0.805152655f, 0.593067646f},
|
||||
{0.809017003f, 0.587785244f}, {0.812846661f, 0.582477689f},
|
||||
{0.816641569f, 0.577145219f}, {0.820401430f, 0.571787953f},
|
||||
{0.824126184f, 0.566406250f}, {0.827815652f, 0.561000228f},
|
||||
{0.831469595f, 0.555570245f}, {0.835087955f, 0.550116420f},
|
||||
{0.838670552f, 0.544639051f}, {0.842217207f, 0.539138317f},
|
||||
{0.845727801f, 0.533614516f}, {0.849202156f, 0.528067827f},
|
||||
{0.852640152f, 0.522498548f}, {0.856041610f, 0.516906917f},
|
||||
{0.859406412f, 0.511293113f}, {0.862734377f, 0.505657375f},
|
||||
{0.866025388f, 0.500000000f}, {0.869279325f, 0.494321197f},
|
||||
{0.872496009f, 0.488621235f}, {0.875675321f, 0.482900351f},
|
||||
{0.878817141f, 0.477158755f}, {0.881921291f, 0.471396744f},
|
||||
{0.884987652f, 0.465614527f}, {0.888016105f, 0.459812373f},
|
||||
{0.891006529f, 0.453990489f}, {0.893958807f, 0.448149204f},
|
||||
{0.896872759f, 0.442288697f}, {0.899748266f, 0.436409235f},
|
||||
{0.902585268f, 0.430511087f}, {0.905383646f, 0.424594522f},
|
||||
{0.908143163f, 0.418659747f}, {0.910863817f, 0.412707031f},
|
||||
{0.913545430f, 0.406736642f}, {0.916187942f, 0.400748819f},
|
||||
{0.918791234f, 0.394743860f}, {0.921355128f, 0.388721973f},
|
||||
{0.923879504f, 0.382683426f}, {0.926364362f, 0.376628488f},
|
||||
{0.928809524f, 0.370557427f}, {0.931214929f, 0.364470512f},
|
||||
{0.933580399f, 0.358367950f}, {0.935905933f, 0.352250040f},
|
||||
{0.938191354f, 0.346117049f}, {0.940436542f, 0.339969248f},
|
||||
{0.942641497f, 0.333806872f}, {0.944806039f, 0.327630192f},
|
||||
{0.946930110f, 0.321439475f}, {0.949013650f, 0.315234989f},
|
||||
{0.951056540f, 0.309017003f}, {0.953058660f, 0.302785784f},
|
||||
{0.955019951f, 0.296541572f}, {0.956940353f, 0.290284663f},
|
||||
{0.958819747f, 0.284015357f}, {0.960658073f, 0.277733833f},
|
||||
{0.962455213f, 0.271440446f}, {0.964211166f, 0.265135437f},
|
||||
{0.965925813f, 0.258819044f}, {0.967599094f, 0.252491564f},
|
||||
{0.969230890f, 0.246153295f}, {0.970821202f, 0.239804462f},
|
||||
{0.972369909f, 0.233445361f}, {0.973876953f, 0.227076262f},
|
||||
{0.975342333f, 0.220697433f}, {0.976765871f, 0.214309156f},
|
||||
{0.978147626f, 0.207911685f}, {0.979487419f, 0.201505318f},
|
||||
{0.980785251f, 0.195090324f}, {0.982041121f, 0.188666970f},
|
||||
{0.983254910f, 0.182235524f}, {0.984426558f, 0.175796285f},
|
||||
{0.985556066f, 0.169349506f}, {0.986643314f, 0.162895471f},
|
||||
{0.987688363f, 0.156434461f}, {0.988691032f, 0.149966761f},
|
||||
{0.989651382f, 0.143492624f}, {0.990569353f, 0.137012348f},
|
||||
{0.991444886f, 0.130526185f}, {0.992277920f, 0.124034449f},
|
||||
{0.993068457f, 0.117537394f}, {0.993816435f, 0.111035310f},
|
||||
{0.994521916f, 0.104528464f}, {0.995184720f, 0.0980171412f},
|
||||
{0.995804906f, 0.0915016159f}, {0.996382475f, 0.0849821791f},
|
||||
{0.996917307f, 0.0784590989f}, {0.997409463f, 0.0719326511f},
|
||||
{0.997858942f, 0.0654031262f}, {0.998265624f, 0.0588708036f},
|
||||
{0.998629510f, 0.0523359552f}, {0.998950660f, 0.0457988679f},
|
||||
{0.999229014f, 0.0392598175f}, {0.999464571f, 0.0327190831f},
|
||||
{0.999657333f, 0.0261769481f}, {0.999807239f, 0.0196336918f},
|
||||
{0.999914348f, 0.0130895954f}, {0.999978602f, 0.00654493785f},
|
||||
};
|
||||
|
||||
const kiss_fft_state rnn_kfft = {
|
||||
960, /* nfft */
|
||||
0.0010416667f, /* scale */
|
||||
-1, /* shift */
|
||||
{5, 192, 3, 64, 4, 16, 4, 4, 4, 1, 0, 0, 0, 0, 0, 0, }, /* factors */
|
||||
fft_bitrev, /* bitrev*/
|
||||
fft_twiddles, /* twiddles*/
|
||||
(arch_fft_state *)&arch_fft, /* arch_fft*/
|
||||
};
|
||||
|
||||
const float rnn_half_window[] = {
|
||||
4.20549168e-06f, 3.78491532e-05f, 0.000105135041f, 0.000206060256f, 0.000340620492f,
|
||||
0.000508809986f, 0.000710621476f, 0.000946046319f, 0.00121507444f, 0.00151769421f,
|
||||
0.00185389258f, 0.00222365512f, 0.00262696599f, 0.00306380726f, 0.00353416055f,
|
||||
0.00403800514f, 0.00457531959f, 0.00514607970f, 0.00575026125f, 0.00638783723f,
|
||||
0.00705878017f, 0.00776306028f, 0.00850064680f, 0.00927150715f, 0.0100756064f,
|
||||
0.0109129101f, 0.0117833801f, 0.0126869772f, 0.0136236614f, 0.0145933898f,
|
||||
0.0155961197f, 0.0166318044f, 0.0177003983f, 0.0188018531f, 0.0199361145f,
|
||||
0.0211031344f, 0.0223028567f, 0.0235352255f, 0.0248001851f, 0.0260976739f,
|
||||
0.0274276342f, 0.0287899990f, 0.0301847085f, 0.0316116922f, 0.0330708846f,
|
||||
0.0345622115f, 0.0360856056f, 0.0376409888f, 0.0392282903f, 0.0408474281f,
|
||||
0.0424983241f, 0.0441808924f, 0.0458950549f, 0.0476407260f, 0.0494178124f,
|
||||
0.0512262285f, 0.0530658774f, 0.0549366735f, 0.0568385124f, 0.0587713011f,
|
||||
0.0607349351f, 0.0627293140f, 0.0647543296f, 0.0668098852f, 0.0688958541f,
|
||||
0.0710121393f, 0.0731586292f, 0.0753351897f, 0.0775417164f, 0.0797780901f,
|
||||
0.0820441842f, 0.0843398646f, 0.0866650119f, 0.0890194997f, 0.0914031938f,
|
||||
0.0938159525f, 0.0962576419f, 0.0987281203f, 0.101227246f, 0.103754878f,
|
||||
0.106310867f, 0.108895063f, 0.111507311f, 0.114147455f, 0.116815343f,
|
||||
0.119510807f, 0.122233689f, 0.124983832f, 0.127761051f, 0.130565181f,
|
||||
0.133396059f, 0.136253506f, 0.139137328f, 0.142047361f, 0.144983411f,
|
||||
0.147945285f, 0.150932819f, 0.153945804f, 0.156984031f, 0.160047337f,
|
||||
0.163135484f, 0.166248307f, 0.169385567f, 0.172547072f, 0.175732598f,
|
||||
0.178941950f, 0.182174906f, 0.185431242f, 0.188710734f, 0.192013159f,
|
||||
0.195338294f, 0.198685899f, 0.202055752f, 0.205447599f, 0.208861232f,
|
||||
0.212296382f, 0.215752810f, 0.219230279f, 0.222728521f, 0.226247311f,
|
||||
0.229786381f, 0.233345464f, 0.236924306f, 0.240522653f, 0.244140238f,
|
||||
0.247776777f, 0.251432031f, 0.255105674f, 0.258797467f, 0.262507141f,
|
||||
0.266234398f, 0.269978970f, 0.273740560f, 0.277518868f, 0.281313598f,
|
||||
0.285124481f, 0.288951218f, 0.292793512f, 0.296651065f, 0.300523549f,
|
||||
0.304410696f, 0.308312178f, 0.312227666f, 0.316156894f, 0.320099503f,
|
||||
0.324055225f, 0.328023702f, 0.332004637f, 0.335997701f, 0.340002567f,
|
||||
0.344018906f, 0.348046392f, 0.352084726f, 0.356133521f, 0.360192508f,
|
||||
0.364261299f, 0.368339598f, 0.372427016f, 0.376523286f, 0.380627990f,
|
||||
0.384740859f, 0.388861477f, 0.392989576f, 0.397124738f, 0.401266664f,
|
||||
0.405414969f, 0.409569323f, 0.413729399f, 0.417894781f, 0.422065198f,
|
||||
0.426240236f, 0.430419534f, 0.434602767f, 0.438789606f, 0.442979604f,
|
||||
0.447172493f, 0.451367885f, 0.455565393f, 0.459764689f, 0.463965416f,
|
||||
0.468167186f, 0.472369671f, 0.476572484f, 0.480775267f, 0.484977663f,
|
||||
0.489179343f, 0.493379891f, 0.497579008f, 0.501776278f, 0.505971372f,
|
||||
0.510163903f, 0.514353573f, 0.518539906f, 0.522722721f, 0.526901484f,
|
||||
0.531075954f, 0.535245717f, 0.539410412f, 0.543569744f, 0.547723293f,
|
||||
0.551870763f, 0.556011736f, 0.560145974f, 0.564273000f, 0.568392515f,
|
||||
0.572504222f, 0.576607704f, 0.580702662f, 0.584788740f, 0.588865638f,
|
||||
0.592932940f, 0.596990347f, 0.601037502f, 0.605074167f, 0.609099925f,
|
||||
0.613114417f, 0.617117405f, 0.621108532f, 0.625087440f, 0.629053831f,
|
||||
0.633007407f, 0.636947870f, 0.640874863f, 0.644788086f, 0.648687243f,
|
||||
0.652572036f, 0.656442165f, 0.660297334f, 0.664137185f, 0.667961538f,
|
||||
0.671769977f, 0.675562322f, 0.679338276f, 0.683097482f, 0.686839759f,
|
||||
0.690564752f, 0.694272280f, 0.697961986f, 0.701633692f, 0.705287039f,
|
||||
0.708921850f, 0.712537885f, 0.716134787f, 0.719712436f, 0.723270535f,
|
||||
0.726808906f, 0.730327189f, 0.733825266f, 0.737302899f, 0.740759790f,
|
||||
0.744195819f, 0.747610688f, 0.751004279f, 0.754376352f, 0.757726669f,
|
||||
0.761055112f, 0.764361382f, 0.767645359f, 0.770906866f, 0.774145722f,
|
||||
0.777361751f, 0.780554771f, 0.783724606f, 0.786871076f, 0.789994121f,
|
||||
0.793093503f, 0.796169102f, 0.799220800f, 0.802248418f, 0.805251837f,
|
||||
0.808230937f, 0.811185598f, 0.814115703f, 0.817021132f, 0.819901764f,
|
||||
0.822757542f, 0.825588286f, 0.828393936f, 0.831174433f, 0.833929658f,
|
||||
0.836659551f, 0.839363992f, 0.842042983f, 0.844696403f, 0.847324252f,
|
||||
0.849926353f, 0.852502763f, 0.855053425f, 0.857578218f, 0.860077202f,
|
||||
0.862550259f, 0.864997447f, 0.867418647f, 0.869813919f, 0.872183204f,
|
||||
0.874526560f, 0.876843870f, 0.879135191f, 0.881400526f, 0.883639932f,
|
||||
0.885853291f, 0.888040781f, 0.890202343f, 0.892337978f, 0.894447744f,
|
||||
0.896531701f, 0.898589849f, 0.900622249f, 0.902628958f, 0.904610038f,
|
||||
0.906565487f, 0.908495426f, 0.910399914f, 0.912279010f, 0.914132774f,
|
||||
0.915961266f, 0.917764664f, 0.919542909f, 0.921296239f, 0.923024654f,
|
||||
0.924728215f, 0.926407158f, 0.928061485f, 0.929691315f, 0.931296766f,
|
||||
0.932878017f, 0.934435070f, 0.935968161f, 0.937477291f, 0.938962698f,
|
||||
0.940424502f, 0.941862822f, 0.943277776f, 0.944669485f, 0.946038187f,
|
||||
0.947383940f, 0.948706925f, 0.950007319f, 0.951285243f, 0.952540874f,
|
||||
0.953774393f, 0.954985917f, 0.956175685f, 0.957343817f, 0.958490491f,
|
||||
0.959615886f, 0.960720181f, 0.961803555f, 0.962866247f, 0.963908315f,
|
||||
0.964930058f, 0.965931594f, 0.966913164f, 0.967874944f, 0.968817174f,
|
||||
0.969739914f, 0.970643520f, 0.971528113f, 0.972393870f, 0.973241091f,
|
||||
0.974069893f, 0.974880517f, 0.975673139f, 0.976447999f, 0.977205336f,
|
||||
0.977945268f, 0.978668094f, 0.979374051f, 0.980063200f, 0.980735898f,
|
||||
0.981392324f, 0.982032716f, 0.982657254f, 0.983266115f, 0.983859658f,
|
||||
0.984437943f, 0.985001266f, 0.985549867f, 0.986083925f, 0.986603677f,
|
||||
0.987109363f, 0.987601161f, 0.988079309f, 0.988544047f, 0.988995552f,
|
||||
0.989434063f, 0.989859879f, 0.990273118f, 0.990674019f, 0.991062820f,
|
||||
0.991439700f, 0.991804957f, 0.992158771f, 0.992501318f, 0.992832899f,
|
||||
0.993153632f, 0.993463814f, 0.993763626f, 0.994053245f, 0.994332969f,
|
||||
0.994602919f, 0.994863331f, 0.995114446f, 0.995356441f, 0.995589554f,
|
||||
0.995813966f, 0.996029854f, 0.996237516f, 0.996437073f, 0.996628702f,
|
||||
0.996812642f, 0.996989131f, 0.997158289f, 0.997320294f, 0.997475445f,
|
||||
0.997623861f, 0.997765720f, 0.997901261f, 0.998030603f, 0.998153925f,
|
||||
0.998271465f, 0.998383403f, 0.998489857f, 0.998591006f, 0.998687088f,
|
||||
0.998778164f, 0.998864532f, 0.998946249f, 0.999023557f, 0.999096513f,
|
||||
0.999165416f, 0.999230266f, 0.999291301f, 0.999348700f, 0.999402523f,
|
||||
0.999453008f, 0.999500215f, 0.999544322f, 0.999585509f, 0.999623775f,
|
||||
0.999659419f, 0.999692440f, 0.999723017f, 0.999751270f, 0.999777317f,
|
||||
0.999801278f, 0.999823213f, 0.999843359f, 0.999861658f, 0.999878347f,
|
||||
0.999893486f, 0.999907196f, 0.999919534f, 0.999930561f, 0.999940455f,
|
||||
0.999949217f, 0.999957025f, 0.999963880f, 0.999969840f, 0.999975085f,
|
||||
0.999979615f, 0.999983490f, 0.999986768f, 0.999989510f, 0.999991834f,
|
||||
0.999993742f, 0.999995291f, 0.999996543f, 0.999997556f, 0.999998271f,
|
||||
0.999998868f, 0.999999285f, 0.999999523f, 0.999999762f, 0.999999881f,
|
||||
0.999999940f, 1.00000000f, 1.00000000f, 1.00000000f, 1.00000000f,
|
||||
};
|
||||
|
||||
const float rnn_dct_table[] = {
|
||||
0.707106769f, 0.998795450f, 0.995184720f, 0.989176512f, 0.980785251f,
|
||||
0.970031261f, 0.956940353f, 0.941544056f, 0.923879504f, 0.903989315f,
|
||||
0.881921291f, 0.857728601f, 0.831469595f, 0.803207517f, 0.773010433f,
|
||||
0.740951121f, 0.707106769f, 0.671558976f, 0.634393275f, 0.595699310f,
|
||||
0.555570245f, 0.514102757f, 0.471396744f, 0.427555084f, 0.382683426f,
|
||||
0.336889863f, 0.290284663f, 0.242980182f, 0.195090324f, 0.146730468f,
|
||||
0.0980171412f, 0.0490676761f, 0.707106769f, 0.989176512f, 0.956940353f,
|
||||
0.903989315f, 0.831469595f, 0.740951121f, 0.634393275f, 0.514102757f,
|
||||
0.382683426f, 0.242980182f, 0.0980171412f, -0.0490676761f, -0.195090324f,
|
||||
-0.336889863f, -0.471396744f, -0.595699310f, -0.707106769f, -0.803207517f,
|
||||
-0.881921291f, -0.941544056f, -0.980785251f, -0.998795450f, -0.995184720f,
|
||||
-0.970031261f, -0.923879504f, -0.857728601f, -0.773010433f, -0.671558976f,
|
||||
-0.555570245f, -0.427555084f, -0.290284663f, -0.146730468f, 0.707106769f,
|
||||
0.970031261f, 0.881921291f, 0.740951121f, 0.555570245f, 0.336889863f,
|
||||
0.0980171412f, -0.146730468f, -0.382683426f, -0.595699310f, -0.773010433f,
|
||||
-0.903989315f, -0.980785251f, -0.998795450f, -0.956940353f, -0.857728601f,
|
||||
-0.707106769f, -0.514102757f, -0.290284663f, -0.0490676761f, 0.195090324f,
|
||||
0.427555084f, 0.634393275f, 0.803207517f, 0.923879504f, 0.989176512f,
|
||||
0.995184720f, 0.941544056f, 0.831469595f, 0.671558976f, 0.471396744f,
|
||||
0.242980182f, 0.707106769f, 0.941544056f, 0.773010433f, 0.514102757f,
|
||||
0.195090324f, -0.146730468f, -0.471396744f, -0.740951121f, -0.923879504f,
|
||||
-0.998795450f, -0.956940353f, -0.803207517f, -0.555570245f, -0.242980182f,
|
||||
0.0980171412f, 0.427555084f, 0.707106769f, 0.903989315f, 0.995184720f,
|
||||
0.970031261f, 0.831469595f, 0.595699310f, 0.290284663f, -0.0490676761f,
|
||||
-0.382683426f, -0.671558976f, -0.881921291f, -0.989176512f, -0.980785251f,
|
||||
-0.857728601f, -0.634393275f, -0.336889863f, 0.707106769f, 0.903989315f,
|
||||
0.634393275f, 0.242980182f, -0.195090324f, -0.595699310f, -0.881921291f,
|
||||
-0.998795450f, -0.923879504f, -0.671558976f, -0.290284663f, 0.146730468f,
|
||||
0.555570245f, 0.857728601f, 0.995184720f, 0.941544056f, 0.707106769f,
|
||||
0.336889863f, -0.0980171412f, -0.514102757f, -0.831469595f, -0.989176512f,
|
||||
-0.956940353f, -0.740951121f, -0.382683426f, 0.0490676761f, 0.471396744f,
|
||||
0.803207517f, 0.980785251f, 0.970031261f, 0.773010433f, 0.427555084f,
|
||||
0.707106769f, 0.857728601f, 0.471396744f, -0.0490676761f, -0.555570245f,
|
||||
-0.903989315f, -0.995184720f, -0.803207517f, -0.382683426f, 0.146730468f,
|
||||
0.634393275f, 0.941544056f, 0.980785251f, 0.740951121f, 0.290284663f,
|
||||
-0.242980182f, -0.707106769f, -0.970031261f, -0.956940353f, -0.671558976f,
|
||||
-0.195090324f, 0.336889863f, 0.773010433f, 0.989176512f, 0.923879504f,
|
||||
0.595699310f, 0.0980171412f, -0.427555084f, -0.831469595f, -0.998795450f,
|
||||
-0.881921291f, -0.514102757f, 0.707106769f, 0.803207517f, 0.290284663f,
|
||||
-0.336889863f, -0.831469595f, -0.998795450f, -0.773010433f, -0.242980182f,
|
||||
0.382683426f, 0.857728601f, 0.995184720f, 0.740951121f, 0.195090324f,
|
||||
-0.427555084f, -0.881921291f, -0.989176512f, -0.707106769f, -0.146730468f,
|
||||
0.471396744f, 0.903989315f, 0.980785251f, 0.671558976f, 0.0980171412f,
|
||||
-0.514102757f, -0.923879504f, -0.970031261f, -0.634393275f, -0.0490676761f,
|
||||
0.555570245f, 0.941544056f, 0.956940353f, 0.595699310f, 0.707106769f,
|
||||
0.740951121f, 0.0980171412f, -0.595699310f, -0.980785251f, -0.857728601f,
|
||||
-0.290284663f, 0.427555084f, 0.923879504f, 0.941544056f, 0.471396744f,
|
||||
-0.242980182f, -0.831469595f, -0.989176512f, -0.634393275f, 0.0490676761f,
|
||||
0.707106769f, 0.998795450f, 0.773010433f, 0.146730468f, -0.555570245f,
|
||||
-0.970031261f, -0.881921291f, -0.336889863f, 0.382683426f, 0.903989315f,
|
||||
0.956940353f, 0.514102757f, -0.195090324f, -0.803207517f, -0.995184720f,
|
||||
-0.671558976f, 0.707106769f, 0.671558976f, -0.0980171412f, -0.803207517f,
|
||||
-0.980785251f, -0.514102757f, 0.290284663f, 0.903989315f, 0.923879504f,
|
||||
0.336889863f, -0.471396744f, -0.970031261f, -0.831469595f, -0.146730468f,
|
||||
0.634393275f, 0.998795450f, 0.707106769f, -0.0490676761f, -0.773010433f,
|
||||
-0.989176512f, -0.555570245f, 0.242980182f, 0.881921291f, 0.941544056f,
|
||||
0.382683426f, -0.427555084f, -0.956940353f, -0.857728601f, -0.195090324f,
|
||||
0.595699310f, 0.995184720f, 0.740951121f, 0.707106769f, 0.595699310f,
|
||||
-0.290284663f, -0.941544056f, -0.831469595f, -0.0490676761f, 0.773010433f,
|
||||
0.970031261f, 0.382683426f, -0.514102757f, -0.995184720f, -0.671558976f,
|
||||
0.195090324f, 0.903989315f, 0.881921291f, 0.146730468f, -0.707106769f,
|
||||
-0.989176512f, -0.471396744f, 0.427555084f, 0.980785251f, 0.740951121f,
|
||||
-0.0980171412f, -0.857728601f, -0.923879504f, -0.242980182f, 0.634393275f,
|
||||
0.998795450f, 0.555570245f, -0.336889863f, -0.956940353f, -0.803207517f,
|
||||
0.707106769f, 0.514102757f, -0.471396744f, -0.998795450f, -0.555570245f,
|
||||
0.427555084f, 0.995184720f, 0.595699310f, -0.382683426f, -0.989176512f,
|
||||
-0.634393275f, 0.336889863f, 0.980785251f, 0.671558976f, -0.290284663f,
|
||||
-0.970031261f, -0.707106769f, 0.242980182f, 0.956940353f, 0.740951121f,
|
||||
-0.195090324f, -0.941544056f, -0.773010433f, 0.146730468f, 0.923879504f,
|
||||
0.803207517f, -0.0980171412f, -0.903989315f, -0.831469595f, 0.0490676761f,
|
||||
0.881921291f, 0.857728601f, 0.707106769f, 0.427555084f, -0.634393275f,
|
||||
-0.970031261f, -0.195090324f, 0.803207517f, 0.881921291f, -0.0490676761f,
|
||||
-0.923879504f, -0.740951121f, 0.290284663f, 0.989176512f, 0.555570245f,
|
||||
-0.514102757f, -0.995184720f, -0.336889863f, 0.707106769f, 0.941544056f,
|
||||
0.0980171412f, -0.857728601f, -0.831469595f, 0.146730468f, 0.956940353f,
|
||||
0.671558976f, -0.382683426f, -0.998795450f, -0.471396744f, 0.595699310f,
|
||||
0.980785251f, 0.242980182f, -0.773010433f, -0.903989315f, 0.707106769f,
|
||||
0.336889863f, -0.773010433f, -0.857728601f, 0.195090324f, 0.989176512f,
|
||||
0.471396744f, -0.671558976f, -0.923879504f, 0.0490676761f, 0.956940353f,
|
||||
0.595699310f, -0.555570245f, -0.970031261f, -0.0980171412f, 0.903989315f,
|
||||
0.707106769f, -0.427555084f, -0.995184720f, -0.242980182f, 0.831469595f,
|
||||
0.803207517f, -0.290284663f, -0.998795450f, -0.382683426f, 0.740951121f,
|
||||
0.881921291f, -0.146730468f, -0.980785251f, -0.514102757f, 0.634393275f,
|
||||
0.941544056f, 0.707106769f, 0.242980182f, -0.881921291f, -0.671558976f,
|
||||
0.555570245f, 0.941544056f, -0.0980171412f, -0.989176512f, -0.382683426f,
|
||||
0.803207517f, 0.773010433f, -0.427555084f, -0.980785251f, -0.0490676761f,
|
||||
0.956940353f, 0.514102757f, -0.707106769f, -0.857728601f, 0.290284663f,
|
||||
0.998795450f, 0.195090324f, -0.903989315f, -0.634393275f, 0.595699310f,
|
||||
0.923879504f, -0.146730468f, -0.995184720f, -0.336889863f, 0.831469595f,
|
||||
0.740951121f, -0.471396744f, -0.970031261f, 0.707106769f, 0.146730468f,
|
||||
-0.956940353f, -0.427555084f, 0.831469595f, 0.671558976f, -0.634393275f,
|
||||
-0.857728601f, 0.382683426f, 0.970031261f, -0.0980171412f, -0.998795450f,
|
||||
-0.195090324f, 0.941544056f, 0.471396744f, -0.803207517f, -0.707106769f,
|
||||
0.595699310f, 0.881921291f, -0.336889863f, -0.980785251f, 0.0490676761f,
|
||||
0.995184720f, 0.242980182f, -0.923879504f, -0.514102757f, 0.773010433f,
|
||||
0.740951121f, -0.555570245f, -0.903989315f, 0.290284663f, 0.989176512f,
|
||||
0.707106769f, 0.0490676761f, -0.995184720f, -0.146730468f, 0.980785251f,
|
||||
0.242980182f, -0.956940353f, -0.336889863f, 0.923879504f, 0.427555084f,
|
||||
-0.881921291f, -0.514102757f, 0.831469595f, 0.595699310f, -0.773010433f,
|
||||
-0.671558976f, 0.707106769f, 0.740951121f, -0.634393275f, -0.803207517f,
|
||||
0.555570245f, 0.857728601f, -0.471396744f, -0.903989315f, 0.382683426f,
|
||||
0.941544056f, -0.290284663f, -0.970031261f, 0.195090324f, 0.989176512f,
|
||||
-0.0980171412f, -0.998795450f, 0.707106769f, -0.0490676761f, -0.995184720f,
|
||||
0.146730468f, 0.980785251f, -0.242980182f, -0.956940353f, 0.336889863f,
|
||||
0.923879504f, -0.427555084f, -0.881921291f, 0.514102757f, 0.831469595f,
|
||||
-0.595699310f, -0.773010433f, 0.671558976f, 0.707106769f, -0.740951121f,
|
||||
-0.634393275f, 0.803207517f, 0.555570245f, -0.857728601f, -0.471396744f,
|
||||
0.903989315f, 0.382683426f, -0.941544056f, -0.290284663f, 0.970031261f,
|
||||
0.195090324f, -0.989176512f, -0.0980171412f, 0.998795450f, 0.707106769f,
|
||||
-0.146730468f, -0.956940353f, 0.427555084f, 0.831469595f, -0.671558976f,
|
||||
-0.634393275f, 0.857728601f, 0.382683426f, -0.970031261f, -0.0980171412f,
|
||||
0.998795450f, -0.195090324f, -0.941544056f, 0.471396744f, 0.803207517f,
|
||||
-0.707106769f, -0.595699310f, 0.881921291f, 0.336889863f, -0.980785251f,
|
||||
-0.0490676761f, 0.995184720f, -0.242980182f, -0.923879504f, 0.514102757f,
|
||||
0.773010433f, -0.740951121f, -0.555570245f, 0.903989315f, 0.290284663f,
|
||||
-0.989176512f, 0.707106769f, -0.242980182f, -0.881921291f, 0.671558976f,
|
||||
0.555570245f, -0.941544056f, -0.0980171412f, 0.989176512f, -0.382683426f,
|
||||
-0.803207517f, 0.773010433f, 0.427555084f, -0.980785251f, 0.0490676761f,
|
||||
0.956940353f, -0.514102757f, -0.707106769f, 0.857728601f, 0.290284663f,
|
||||
-0.998795450f, 0.195090324f, 0.903989315f, -0.634393275f, -0.595699310f,
|
||||
0.923879504f, 0.146730468f, -0.995184720f, 0.336889863f, 0.831469595f,
|
||||
-0.740951121f, -0.471396744f, 0.970031261f, 0.707106769f, -0.336889863f,
|
||||
-0.773010433f, 0.857728601f, 0.195090324f, -0.989176512f, 0.471396744f,
|
||||
0.671558976f, -0.923879504f, -0.0490676761f, 0.956940353f, -0.595699310f,
|
||||
-0.555570245f, 0.970031261f, -0.0980171412f, -0.903989315f, 0.707106769f,
|
||||
0.427555084f, -0.995184720f, 0.242980182f, 0.831469595f, -0.803207517f,
|
||||
-0.290284663f, 0.998795450f, -0.382683426f, -0.740951121f, 0.881921291f,
|
||||
0.146730468f, -0.980785251f, 0.514102757f, 0.634393275f, -0.941544056f,
|
||||
0.707106769f, -0.427555084f, -0.634393275f, 0.970031261f, -0.195090324f,
|
||||
-0.803207517f, 0.881921291f, 0.0490676761f, -0.923879504f, 0.740951121f,
|
||||
0.290284663f, -0.989176512f, 0.555570245f, 0.514102757f, -0.995184720f,
|
||||
0.336889863f, 0.707106769f, -0.941544056f, 0.0980171412f, 0.857728601f,
|
||||
-0.831469595f, -0.146730468f, 0.956940353f, -0.671558976f, -0.382683426f,
|
||||
0.998795450f, -0.471396744f, -0.595699310f, 0.980785251f, -0.242980182f,
|
||||
-0.773010433f, 0.903989315f, 0.707106769f, -0.514102757f, -0.471396744f,
|
||||
0.998795450f, -0.555570245f, -0.427555084f, 0.995184720f, -0.595699310f,
|
||||
-0.382683426f, 0.989176512f, -0.634393275f, -0.336889863f, 0.980785251f,
|
||||
-0.671558976f, -0.290284663f, 0.970031261f, -0.707106769f, -0.242980182f,
|
||||
0.956940353f, -0.740951121f, -0.195090324f, 0.941544056f, -0.773010433f,
|
||||
-0.146730468f, 0.923879504f, -0.803207517f, -0.0980171412f, 0.903989315f,
|
||||
-0.831469595f, -0.0490676761f, 0.881921291f, -0.857728601f, 0.707106769f,
|
||||
-0.595699310f, -0.290284663f, 0.941544056f, -0.831469595f, 0.0490676761f,
|
||||
0.773010433f, -0.970031261f, 0.382683426f, 0.514102757f, -0.995184720f,
|
||||
0.671558976f, 0.195090324f, -0.903989315f, 0.881921291f, -0.146730468f,
|
||||
-0.707106769f, 0.989176512f, -0.471396744f, -0.427555084f, 0.980785251f,
|
||||
-0.740951121f, -0.0980171412f, 0.857728601f, -0.923879504f, 0.242980182f,
|
||||
0.634393275f, -0.998795450f, 0.555570245f, 0.336889863f, -0.956940353f,
|
||||
0.803207517f, 0.707106769f, -0.671558976f, -0.0980171412f, 0.803207517f,
|
||||
-0.980785251f, 0.514102757f, 0.290284663f, -0.903989315f, 0.923879504f,
|
||||
-0.336889863f, -0.471396744f, 0.970031261f, -0.831469595f, 0.146730468f,
|
||||
0.634393275f, -0.998795450f, 0.707106769f, 0.0490676761f, -0.773010433f,
|
||||
0.989176512f, -0.555570245f, -0.242980182f, 0.881921291f, -0.941544056f,
|
||||
0.382683426f, 0.427555084f, -0.956940353f, 0.857728601f, -0.195090324f,
|
||||
-0.595699310f, 0.995184720f, -0.740951121f, 0.707106769f, -0.740951121f,
|
||||
0.0980171412f, 0.595699310f, -0.980785251f, 0.857728601f, -0.290284663f,
|
||||
-0.427555084f, 0.923879504f, -0.941544056f, 0.471396744f, 0.242980182f,
|
||||
-0.831469595f, 0.989176512f, -0.634393275f, -0.0490676761f, 0.707106769f,
|
||||
-0.998795450f, 0.773010433f, -0.146730468f, -0.555570245f, 0.970031261f,
|
||||
-0.881921291f, 0.336889863f, 0.382683426f, -0.903989315f, 0.956940353f,
|
||||
-0.514102757f, -0.195090324f, 0.803207517f, -0.995184720f, 0.671558976f,
|
||||
0.707106769f, -0.803207517f, 0.290284663f, 0.336889863f, -0.831469595f,
|
||||
0.998795450f, -0.773010433f, 0.242980182f, 0.382683426f, -0.857728601f,
|
||||
0.995184720f, -0.740951121f, 0.195090324f, 0.427555084f, -0.881921291f,
|
||||
0.989176512f, -0.707106769f, 0.146730468f, 0.471396744f, -0.903989315f,
|
||||
0.980785251f, -0.671558976f, 0.0980171412f, 0.514102757f, -0.923879504f,
|
||||
0.970031261f, -0.634393275f, 0.0490676761f, 0.555570245f, -0.941544056f,
|
||||
0.956940353f, -0.595699310f, 0.707106769f, -0.857728601f, 0.471396744f,
|
||||
0.0490676761f, -0.555570245f, 0.903989315f, -0.995184720f, 0.803207517f,
|
||||
-0.382683426f, -0.146730468f, 0.634393275f, -0.941544056f, 0.980785251f,
|
||||
-0.740951121f, 0.290284663f, 0.242980182f, -0.707106769f, 0.970031261f,
|
||||
-0.956940353f, 0.671558976f, -0.195090324f, -0.336889863f, 0.773010433f,
|
||||
-0.989176512f, 0.923879504f, -0.595699310f, 0.0980171412f, 0.427555084f,
|
||||
-0.831469595f, 0.998795450f, -0.881921291f, 0.514102757f, 0.707106769f,
|
||||
-0.903989315f, 0.634393275f, -0.242980182f, -0.195090324f, 0.595699310f,
|
||||
-0.881921291f, 0.998795450f, -0.923879504f, 0.671558976f, -0.290284663f,
|
||||
-0.146730468f, 0.555570245f, -0.857728601f, 0.995184720f, -0.941544056f,
|
||||
0.707106769f, -0.336889863f, -0.0980171412f, 0.514102757f, -0.831469595f,
|
||||
0.989176512f, -0.956940353f, 0.740951121f, -0.382683426f, -0.0490676761f,
|
||||
0.471396744f, -0.803207517f, 0.980785251f, -0.970031261f, 0.773010433f,
|
||||
-0.427555084f, 0.707106769f, -0.941544056f, 0.773010433f, -0.514102757f,
|
||||
0.195090324f, 0.146730468f, -0.471396744f, 0.740951121f, -0.923879504f,
|
||||
0.998795450f, -0.956940353f, 0.803207517f, -0.555570245f, 0.242980182f,
|
||||
0.0980171412f, -0.427555084f, 0.707106769f, -0.903989315f, 0.995184720f,
|
||||
-0.970031261f, 0.831469595f, -0.595699310f, 0.290284663f, 0.0490676761f,
|
||||
-0.382683426f, 0.671558976f, -0.881921291f, 0.989176512f, -0.980785251f,
|
||||
0.857728601f, -0.634393275f, 0.336889863f, 0.707106769f, -0.970031261f,
|
||||
0.881921291f, -0.740951121f, 0.555570245f, -0.336889863f, 0.0980171412f,
|
||||
0.146730468f, -0.382683426f, 0.595699310f, -0.773010433f, 0.903989315f,
|
||||
-0.980785251f, 0.998795450f, -0.956940353f, 0.857728601f, -0.707106769f,
|
||||
0.514102757f, -0.290284663f, 0.0490676761f, 0.195090324f, -0.427555084f,
|
||||
0.634393275f, -0.803207517f, 0.923879504f, -0.989176512f, 0.995184720f,
|
||||
-0.941544056f, 0.831469595f, -0.671558976f, 0.471396744f, -0.242980182f,
|
||||
0.707106769f, -0.989176512f, 0.956940353f, -0.903989315f, 0.831469595f,
|
||||
-0.740951121f, 0.634393275f, -0.514102757f, 0.382683426f, -0.242980182f,
|
||||
0.0980171412f, 0.0490676761f, -0.195090324f, 0.336889863f, -0.471396744f,
|
||||
0.595699310f, -0.707106769f, 0.803207517f, -0.881921291f, 0.941544056f,
|
||||
-0.980785251f, 0.998795450f, -0.995184720f, 0.970031261f, -0.923879504f,
|
||||
0.857728601f, -0.773010433f, 0.671558976f, -0.555570245f, 0.427555084f,
|
||||
-0.290284663f, 0.146730468f, 0.707106769f, -0.998795450f, 0.995184720f,
|
||||
-0.989176512f, 0.980785251f, -0.970031261f, 0.956940353f, -0.941544056f,
|
||||
0.923879504f, -0.903989315f, 0.881921291f, -0.857728601f, 0.831469595f,
|
||||
-0.803207517f, 0.773010433f, -0.740951121f, 0.707106769f, -0.671558976f,
|
||||
0.634393275f, -0.595699310f, 0.555570245f, -0.514102757f, 0.471396744f,
|
||||
-0.427555084f, 0.382683426f, -0.336889863f, 0.290284663f, -0.242980182f,
|
||||
0.195090324f, -0.146730468f, 0.0980171412f, -0.0490676761f, };
|
||||
388
src/modules/ZoomIt/ZoomIt/rnnoise/vec.h
Normal file
388
src/modules/ZoomIt/ZoomIt/rnnoise/vec.h
Normal file
@@ -0,0 +1,388 @@
|
||||
/* Copyright (c) 2018 Mozilla
|
||||
2008-2011 Octasic Inc.
|
||||
2012-2017 Jean-Marc Valin */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef VEC_H
|
||||
#define VEC_H
|
||||
|
||||
#include "opus_types.h"
|
||||
#include "common.h"
|
||||
#include <math.h>
|
||||
#include "arch.h"
|
||||
#include "x86/x86_arch_macros.h"
|
||||
|
||||
|
||||
#if defined(__AVX__) || defined(__SSE2__)
|
||||
#include "vec_avx.h"
|
||||
#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && !defined(DISABLE_NEON)
|
||||
#include "vec_neon.h"
|
||||
#else
|
||||
|
||||
#define MAX_INPUTS (2048)
|
||||
|
||||
#define NO_OPTIMIZATIONS
|
||||
|
||||
static inline void sgemv16x1(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
|
||||
{
|
||||
int i, j;
|
||||
RNN_CLEAR(out, rows);
|
||||
for (i=0;i<rows;i+=16)
|
||||
{
|
||||
for (j=0;j<cols;j++)
|
||||
{
|
||||
const float * restrict w;
|
||||
float * restrict y;
|
||||
float xj;
|
||||
w = &weights[j*col_stride + i];
|
||||
xj = x[j];
|
||||
y = &out[i];
|
||||
y[0] += w[0]*xj;
|
||||
y[1] += w[1]*xj;
|
||||
y[2] += w[2]*xj;
|
||||
y[3] += w[3]*xj;
|
||||
y[4] += w[4]*xj;
|
||||
y[5] += w[5]*xj;
|
||||
y[6] += w[6]*xj;
|
||||
y[7] += w[7]*xj;
|
||||
y[8] += w[8]*xj;
|
||||
y[9] += w[9]*xj;
|
||||
y[10] += w[10]*xj;
|
||||
y[11] += w[11]*xj;
|
||||
y[12] += w[12]*xj;
|
||||
y[13] += w[13]*xj;
|
||||
y[14] += w[14]*xj;
|
||||
y[15] += w[15]*xj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sgemv8x1(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
|
||||
{
|
||||
int i, j;
|
||||
RNN_CLEAR(out, rows);
|
||||
for (i=0;i<rows;i+=8)
|
||||
{
|
||||
for (j=0;j<cols;j++)
|
||||
{
|
||||
const float * restrict w;
|
||||
float * restrict y;
|
||||
float xj;
|
||||
w = &weights[j*col_stride + i];
|
||||
xj = x[j];
|
||||
y = &out[i];
|
||||
y[0] += w[0]*xj;
|
||||
y[1] += w[1]*xj;
|
||||
y[2] += w[2]*xj;
|
||||
y[3] += w[3]*xj;
|
||||
y[4] += w[4]*xj;
|
||||
y[5] += w[5]*xj;
|
||||
y[6] += w[6]*xj;
|
||||
y[7] += w[7]*xj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sgemv(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
|
||||
{
|
||||
if ((rows&0xf) == 0) sgemv16x1(out, weights, rows, cols, col_stride, x);
|
||||
else if ((rows&0x7) == 0) sgemv8x1(out, weights, rows, cols, col_stride, x);
|
||||
else {
|
||||
int i, j;
|
||||
for (i=0;i<rows;i++)
|
||||
{
|
||||
out[i] = 0;
|
||||
for (j=0;j<cols;j++) out[i] += weights[j*col_stride + i]*x[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sparse_sgemv8x4(float *out, const float *w, const int *idx, int rows, const float *x)
|
||||
{
|
||||
int i, j;
|
||||
RNN_CLEAR(out, rows);
|
||||
for (i=0;i<rows;i+=8)
|
||||
{
|
||||
int cols;
|
||||
cols = *idx++;
|
||||
for (j=0;j<cols;j++)
|
||||
{
|
||||
int pos;
|
||||
float * restrict y;
|
||||
float xj0, xj1, xj2, xj3;
|
||||
pos = (*idx++);
|
||||
xj0 = x[pos+0];
|
||||
xj1 = x[pos+1];
|
||||
xj2 = x[pos+2];
|
||||
xj3 = x[pos+3];
|
||||
y = &out[i];
|
||||
y[0] += w[0]*xj0;
|
||||
y[1] += w[1]*xj0;
|
||||
y[2] += w[2]*xj0;
|
||||
y[3] += w[3]*xj0;
|
||||
y[4] += w[4]*xj0;
|
||||
y[5] += w[5]*xj0;
|
||||
y[6] += w[6]*xj0;
|
||||
y[7] += w[7]*xj0;
|
||||
|
||||
y[0] += w[8]*xj1;
|
||||
y[1] += w[9]*xj1;
|
||||
y[2] += w[10]*xj1;
|
||||
y[3] += w[11]*xj1;
|
||||
y[4] += w[12]*xj1;
|
||||
y[5] += w[13]*xj1;
|
||||
y[6] += w[14]*xj1;
|
||||
y[7] += w[15]*xj1;
|
||||
|
||||
y[0] += w[16]*xj2;
|
||||
y[1] += w[17]*xj2;
|
||||
y[2] += w[18]*xj2;
|
||||
y[3] += w[19]*xj2;
|
||||
y[4] += w[20]*xj2;
|
||||
y[5] += w[21]*xj2;
|
||||
y[6] += w[22]*xj2;
|
||||
y[7] += w[23]*xj2;
|
||||
|
||||
y[0] += w[24]*xj3;
|
||||
y[1] += w[25]*xj3;
|
||||
y[2] += w[26]*xj3;
|
||||
y[3] += w[27]*xj3;
|
||||
y[4] += w[28]*xj3;
|
||||
y[5] += w[29]*xj3;
|
||||
y[6] += w[30]*xj3;
|
||||
y[7] += w[31]*xj3;
|
||||
w += 32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_SU_BIAS
|
||||
static inline void sparse_cgemv8x4(float *out, const opus_int8 *w, const int *idx, const float *scale, int rows, int cols, const float *_x)
|
||||
{
|
||||
int i, j;
|
||||
unsigned char x[MAX_INPUTS];
|
||||
for (i=0;i<rows;i++) out[i] = 0;
|
||||
for (i=0;i<cols;i++) x[i] = 127+floor(.5+127*_x[i]);
|
||||
for (i=0;i<rows;i+=8)
|
||||
{
|
||||
int colblocks;
|
||||
colblocks = *idx++;
|
||||
for (j=0;j<colblocks;j++)
|
||||
{
|
||||
int pos;
|
||||
float * restrict y;
|
||||
int xj0, xj1, xj2, xj3;
|
||||
pos = (*idx++);
|
||||
xj0 = x[pos+0];
|
||||
xj1 = x[pos+1];
|
||||
xj2 = x[pos+2];
|
||||
xj3 = x[pos+3];
|
||||
y = &out[i];
|
||||
y[0] += (w[0]*xj0+w[1]*xj1+w[2]*xj2+w[3]*xj3);
|
||||
y[1] += (w[4]*xj0+w[5]*xj1+w[6]*xj2+w[7]*xj3);
|
||||
y[2] += (w[8]*xj0+w[9]*xj1+w[10]*xj2+w[11]*xj3);
|
||||
y[3] += (w[12]*xj0+w[13]*xj1+w[14]*xj2+w[15]*xj3);
|
||||
y[4] += (w[16]*xj0+w[17]*xj1+w[18]*xj2+w[19]*xj3);
|
||||
y[5] += (w[20]*xj0+w[21]*xj1+w[22]*xj2+w[23]*xj3);
|
||||
y[6] += (w[24]*xj0+w[25]*xj1+w[26]*xj2+w[27]*xj3);
|
||||
y[7] += (w[28]*xj0+w[29]*xj1+w[30]*xj2+w[31]*xj3);
|
||||
w += 32;
|
||||
}
|
||||
}
|
||||
for (i=0;i<rows;i++) out[i] *= scale[i];
|
||||
}
|
||||
static inline void cgemv8x4(float *out, const opus_int8 *w, const float *scale, int rows, int cols, const float *_x)
|
||||
{
|
||||
int i, j;
|
||||
unsigned char x[MAX_INPUTS];
|
||||
for (i=0;i<rows;i++) out[i] = 0;
|
||||
for (i=0;i<cols;i++) x[i] = 127+(int)floor(.5+127*_x[i]);
|
||||
for (i=0;i<rows;i+=8)
|
||||
{
|
||||
for (j=0;j<cols;j+=4)
|
||||
{
|
||||
float *y;
|
||||
float xj0, xj1, xj2, xj3;
|
||||
xj0 = x[j+0];
|
||||
xj1 = x[j+1];
|
||||
xj2 = x[j+2];
|
||||
xj3 = x[j+3];
|
||||
y = &out[i];
|
||||
y[0] += (w[0]*xj0+w[1]*xj1+w[2]*xj2+w[3]*xj3);
|
||||
y[1] += (w[4]*xj0+w[5]*xj1+w[6]*xj2+w[7]*xj3);
|
||||
y[2] += (w[8]*xj0+w[9]*xj1+w[10]*xj2+w[11]*xj3);
|
||||
y[3] += (w[12]*xj0+w[13]*xj1+w[14]*xj2+w[15]*xj3);
|
||||
y[4] += (w[16]*xj0+w[17]*xj1+w[18]*xj2+w[19]*xj3);
|
||||
y[5] += (w[20]*xj0+w[21]*xj1+w[22]*xj2+w[23]*xj3);
|
||||
y[6] += (w[24]*xj0+w[25]*xj1+w[26]*xj2+w[27]*xj3);
|
||||
y[7] += (w[28]*xj0+w[29]*xj1+w[30]*xj2+w[31]*xj3);
|
||||
w += 32;
|
||||
}
|
||||
}
|
||||
for (i=0;i<rows;i++) out[i] *= scale[i];
|
||||
}
|
||||
#else
|
||||
static inline void sparse_cgemv8x4(float *out, const opus_int8 *w, const int *idx, const float *scale, int rows, int cols, const float *_x)
|
||||
{
|
||||
int i, j;
|
||||
opus_int8 x[MAX_INPUTS];
|
||||
for (i=0;i<rows;i++) out[i] = 0;
|
||||
for (i=0;i<cols;i++) x[i] = (int)floor(.5+127*_x[i]);
|
||||
for (i=0;i<rows;i+=8)
|
||||
{
|
||||
int colblocks;
|
||||
colblocks = *idx++;
|
||||
for (j=0;j<colblocks;j++)
|
||||
{
|
||||
int pos;
|
||||
float * restrict y;
|
||||
int xj0, xj1, xj2, xj3;
|
||||
pos = (*idx++);
|
||||
xj0 = x[pos+0];
|
||||
xj1 = x[pos+1];
|
||||
xj2 = x[pos+2];
|
||||
xj3 = x[pos+3];
|
||||
y = &out[i];
|
||||
y[0] += (w[0]*xj0+w[1]*xj1+w[2]*xj2+w[3]*xj3);
|
||||
y[1] += (w[4]*xj0+w[5]*xj1+w[6]*xj2+w[7]*xj3);
|
||||
y[2] += (w[8]*xj0+w[9]*xj1+w[10]*xj2+w[11]*xj3);
|
||||
y[3] += (w[12]*xj0+w[13]*xj1+w[14]*xj2+w[15]*xj3);
|
||||
y[4] += (w[16]*xj0+w[17]*xj1+w[18]*xj2+w[19]*xj3);
|
||||
y[5] += (w[20]*xj0+w[21]*xj1+w[22]*xj2+w[23]*xj3);
|
||||
y[6] += (w[24]*xj0+w[25]*xj1+w[26]*xj2+w[27]*xj3);
|
||||
y[7] += (w[28]*xj0+w[29]*xj1+w[30]*xj2+w[31]*xj3);
|
||||
w += 32;
|
||||
}
|
||||
}
|
||||
for (i=0;i<rows;i++) out[i] *= scale[i];
|
||||
}
|
||||
static inline void cgemv8x4(float *out, const opus_int8 *w, const float *scale, int rows, int cols, const float *_x)
|
||||
{
|
||||
int i, j;
|
||||
opus_int8 x[MAX_INPUTS];
|
||||
for (i=0;i<rows;i++) out[i] = 0;
|
||||
for (i=0;i<cols;i++) x[i] = (int)floor(.5+127*_x[i]);
|
||||
for (i=0;i<rows;i+=8)
|
||||
{
|
||||
for (j=0;j<cols;j+=4)
|
||||
{
|
||||
float *y;
|
||||
float xj0, xj1, xj2, xj3;
|
||||
xj0 = x[j+0];
|
||||
xj1 = x[j+1];
|
||||
xj2 = x[j+2];
|
||||
xj3 = x[j+3];
|
||||
y = &out[i];
|
||||
y[0] += (w[0]*xj0+w[1]*xj1+w[2]*xj2+w[3]*xj3);
|
||||
y[1] += (w[4]*xj0+w[5]*xj1+w[6]*xj2+w[7]*xj3);
|
||||
y[2] += (w[8]*xj0+w[9]*xj1+w[10]*xj2+w[11]*xj3);
|
||||
y[3] += (w[12]*xj0+w[13]*xj1+w[14]*xj2+w[15]*xj3);
|
||||
y[4] += (w[16]*xj0+w[17]*xj1+w[18]*xj2+w[19]*xj3);
|
||||
y[5] += (w[20]*xj0+w[21]*xj1+w[22]*xj2+w[23]*xj3);
|
||||
y[6] += (w[24]*xj0+w[25]*xj1+w[26]*xj2+w[27]*xj3);
|
||||
y[7] += (w[28]*xj0+w[29]*xj1+w[30]*xj2+w[31]*xj3);
|
||||
w += 32;
|
||||
}
|
||||
}
|
||||
for (i=0;i<rows;i++) out[i] *= scale[i];
|
||||
}
|
||||
#endif
|
||||
|
||||
/* No AVX2/FMA support */
|
||||
#ifndef LPCNET_TEST
|
||||
static inline float lpcnet_exp2(float x)
|
||||
{
|
||||
int integer;
|
||||
float frac;
|
||||
union {
|
||||
float f;
|
||||
opus_uint32 i;
|
||||
} res;
|
||||
integer = floor(x);
|
||||
if (integer < -50)
|
||||
return 0;
|
||||
frac = x-integer;
|
||||
/* K0 = 1, K1 = log(2), K2 = 3-4*log(2), K3 = 3*log(2) - 2 */
|
||||
res.f = 0.99992522f + frac * (0.69583354f
|
||||
+ frac * (0.22606716f + 0.078024523f*frac));
|
||||
res.i = (res.i + (integer<<23)) & 0x7fffffff;
|
||||
return res.f;
|
||||
}
|
||||
#define lpcnet_exp(x) lpcnet_exp2((x)*1.44269504f)
|
||||
|
||||
#define fmadd(a, b, c) ((a)*(b)+(c))
|
||||
static OPUS_INLINE float tanh_approx(float x)
|
||||
{
|
||||
const float N0 = 952.52801514f;
|
||||
const float N1 = 96.39235687f;
|
||||
const float N2 = 0.60863042f;
|
||||
const float D0 = 952.72399902f;
|
||||
const float D1 = 413.36801147f;
|
||||
const float D2 = 11.88600922f;
|
||||
float X2, num, den;
|
||||
X2 = x*x;
|
||||
num = fmadd(fmadd(N2, X2, N1), X2, N0);
|
||||
den = fmadd(fmadd(D2, X2, D1), X2, D0);
|
||||
num = num*x/den;
|
||||
return MAX32(-1.f, MIN32(1.f, num));
|
||||
}
|
||||
|
||||
static inline float sigmoid_approx(float x)
|
||||
{
|
||||
return .5f + .5f*tanh_approx(.5f*x);
|
||||
}
|
||||
|
||||
static inline void softmax(float *y, const float *x, int N)
|
||||
{
|
||||
int i;
|
||||
for (i=0;i<N;i++)
|
||||
y[i] = lpcnet_exp(x[i]);
|
||||
}
|
||||
|
||||
static inline void vec_tanh(float *y, const float *x, int N)
|
||||
{
|
||||
int i;
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
y[i] = tanh_approx(x[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void vec_sigmoid(float *y, const float *x, int N)
|
||||
{
|
||||
int i;
|
||||
for (i=0;i<N;i++)
|
||||
{
|
||||
y[i] = sigmoid_approx(x[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#define SCALE (128.f*127.f)
|
||||
#define SCALE_1 (1.f/128.f/127.f)
|
||||
|
||||
#endif /*no optimizations*/
|
||||
#endif /*VEC_H*/
|
||||
85
src/modules/ZoomIt/ZoomIt/rnnoise/x86/dnn_x86.h
Normal file
85
src/modules/ZoomIt/ZoomIt/rnnoise/x86/dnn_x86.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/* Copyright (c) 2011-2019 Mozilla
|
||||
2023 Amazon */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef DNN_X86_H
|
||||
#define DNN_X86_H
|
||||
|
||||
#include "cpu_support.h"
|
||||
#include "opus_types.h"
|
||||
|
||||
void compute_linear_sse4_1(const LinearLayer *linear, float *out, const float *in);
|
||||
void compute_activation_sse4_1(float *output, const float *input, int N, int activation);
|
||||
void compute_conv2d_sse4_1(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation);
|
||||
|
||||
void compute_linear_avx2(const LinearLayer *linear, float *out, const float *in);
|
||||
void compute_activation_avx2(float *output, const float *input, int N, int activation);
|
||||
void compute_conv2d_avx2(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation);
|
||||
|
||||
|
||||
|
||||
#ifdef RNN_ENABLE_X86_RTCD
|
||||
|
||||
extern void (*const RNN_COMPUTE_LINEAR_IMPL[OPUS_ARCHMASK + 1])(
|
||||
const LinearLayer *linear,
|
||||
float *out,
|
||||
const float *in
|
||||
);
|
||||
#define OVERRIDE_COMPUTE_LINEAR
|
||||
#define compute_linear(linear, out, in, arch) \
|
||||
((*RNN_COMPUTE_LINEAR_IMPL[(arch) & OPUS_ARCHMASK])(linear, out, in))
|
||||
|
||||
|
||||
extern void (*const RNN_COMPUTE_ACTIVATION_IMPL[OPUS_ARCHMASK + 1])(
|
||||
float *output,
|
||||
const float *input,
|
||||
int N,
|
||||
int activation
|
||||
);
|
||||
#define OVERRIDE_COMPUTE_ACTIVATION
|
||||
#define compute_activation(output, input, N, activation, arch) \
|
||||
((*RNN_COMPUTE_ACTIVATION_IMPL[(arch) & OPUS_ARCHMASK])(output, input, N, activation))
|
||||
|
||||
|
||||
extern void (*const RNN_COMPUTE_CONV2D_IMPL[OPUS_ARCHMASK + 1])(
|
||||
const Conv2dLayer *conv,
|
||||
float *out,
|
||||
float *mem,
|
||||
const float *in,
|
||||
int height,
|
||||
int hstride,
|
||||
int activation
|
||||
);
|
||||
#define OVERRIDE_COMPUTE_CONV2D
|
||||
#define compute_conv2d(conv, out, mem, in, height, hstride, activation, arch) \
|
||||
((*RNN_COMPUTE_CONV2D_IMPL[(arch) & OPUS_ARCHMASK])(conv, out, mem, in, height, hstride, activation))
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#endif /* DNN_X86_H */
|
||||
40
src/modules/ZoomIt/ZoomIt/rnnoise/x86/nnet_avx2.c
Normal file
40
src/modules/ZoomIt/ZoomIt/rnnoise/x86/nnet_avx2.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/* Copyright (c) 2018-2019 Mozilla
|
||||
2023 Amazon */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "x86/x86_arch_macros.h"
|
||||
|
||||
#ifndef __AVX2__
|
||||
#error nnet_avx2.c is being compiled without AVX2 enabled
|
||||
#endif
|
||||
|
||||
#define RTCD_ARCH avx2
|
||||
|
||||
#include "nnet_arch.h"
|
||||
40
src/modules/ZoomIt/ZoomIt/rnnoise/x86/nnet_sse4_1.c
Normal file
40
src/modules/ZoomIt/ZoomIt/rnnoise/x86/nnet_sse4_1.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/* Copyright (c) 2018-2019 Mozilla
|
||||
2023 Amazon */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "x86/x86_arch_macros.h"
|
||||
|
||||
#ifndef __SSE4_1__
|
||||
#error nnet_sse4_1.c is being compiled without SSE4.1 enabled
|
||||
#endif
|
||||
|
||||
#define RTCD_ARCH sse4_1
|
||||
|
||||
#include "nnet_arch.h"
|
||||
47
src/modules/ZoomIt/ZoomIt/rnnoise/x86/x86_arch_macros.h
Normal file
47
src/modules/ZoomIt/ZoomIt/rnnoise/x86/x86_arch_macros.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/* Copyright (c) 2023 Amazon */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
# ifdef OPUS_X86_MAY_HAVE_SSE
|
||||
# ifndef __SSE__
|
||||
# define __SSE__
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# ifdef OPUS_X86_MAY_HAVE_SSE2
|
||||
# ifndef __SSE2__
|
||||
# define __SSE2__
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# ifdef OPUS_X86_MAY_HAVE_SSE4_1
|
||||
# ifndef __SSE4_1__
|
||||
# define __SSE4_1__
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#endif
|
||||
74
src/modules/ZoomIt/ZoomIt/rnnoise/x86/x86_dnn_map.c
Normal file
74
src/modules/ZoomIt/ZoomIt/rnnoise/x86/x86_dnn_map.c
Normal file
@@ -0,0 +1,74 @@
|
||||
/* Copyright (c) 2018-2019 Mozilla
|
||||
2023 Amazon */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "x86/x86cpu.h"
|
||||
#include "nnet.h"
|
||||
|
||||
#ifdef RNN_ENABLE_X86_RTCD
|
||||
|
||||
|
||||
void (*const RNN_COMPUTE_LINEAR_IMPL[OPUS_ARCHMASK + 1])(
|
||||
const LinearLayer *linear,
|
||||
float *out,
|
||||
const float *in
|
||||
) = {
|
||||
compute_linear_c, /* non-sse */
|
||||
MAY_HAVE_SSE4_1(compute_linear), /* sse4.1 */
|
||||
MAY_HAVE_AVX2(compute_linear) /* avx */
|
||||
};
|
||||
|
||||
void (*const RNN_COMPUTE_ACTIVATION_IMPL[OPUS_ARCHMASK + 1])(
|
||||
float *output,
|
||||
const float *input,
|
||||
int N,
|
||||
int activation
|
||||
) = {
|
||||
compute_activation_c, /* non-sse */
|
||||
MAY_HAVE_SSE4_1(compute_activation), /* sse4.1 */
|
||||
MAY_HAVE_AVX2(compute_activation) /* avx */
|
||||
};
|
||||
|
||||
void (*const RNN_COMPUTE_CONV2D_IMPL[OPUS_ARCHMASK + 1])(
|
||||
const Conv2dLayer *conv,
|
||||
float *out,
|
||||
float *mem,
|
||||
const float *in,
|
||||
int height,
|
||||
int hstride,
|
||||
int activation
|
||||
) = {
|
||||
compute_conv2d_c, /* non-sse */
|
||||
MAY_HAVE_SSE4_1(compute_conv2d), /* sse4.1 */
|
||||
MAY_HAVE_AVX2(compute_conv2d) /* avx */
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
166
src/modules/ZoomIt/ZoomIt/rnnoise/x86/x86cpu.c
Normal file
166
src/modules/ZoomIt/ZoomIt/rnnoise/x86/x86cpu.c
Normal file
@@ -0,0 +1,166 @@
|
||||
/* Copyright (c) 2014, Cisco Systems, INC
|
||||
Written by XiangMingZhu WeiZhou MinPeng YanWang
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "cpu_support.h"
|
||||
#include "pitch.h"
|
||||
#include "x86cpu.h"
|
||||
|
||||
#ifdef RNN_ENABLE_X86_RTCD
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
#include <intrin.h>
|
||||
static _inline void cpuid(unsigned int CPUInfo[4], unsigned int InfoType)
|
||||
{
|
||||
__cpuid((int*)CPUInfo, InfoType);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#if defined(CPU_INFO_BY_C)
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
|
||||
static void cpuid(unsigned int CPUInfo[4], unsigned int InfoType)
|
||||
{
|
||||
#if defined(CPU_INFO_BY_ASM)
|
||||
#if defined(__i386__) && defined(__PIC__)
|
||||
/* %ebx is PIC register in 32-bit, so mustn't clobber it. */
|
||||
__asm__ __volatile__ (
|
||||
"xchg %%ebx, %1\n"
|
||||
"cpuid\n"
|
||||
"xchg %%ebx, %1\n":
|
||||
"=a" (CPUInfo[0]),
|
||||
"=r" (CPUInfo[1]),
|
||||
"=c" (CPUInfo[2]),
|
||||
"=d" (CPUInfo[3]) :
|
||||
/* We clear ECX to avoid a valgrind false-positive prior to v3.17.0. */
|
||||
"0" (InfoType), "2" (0)
|
||||
);
|
||||
#else
|
||||
__asm__ __volatile__ (
|
||||
"cpuid":
|
||||
"=a" (CPUInfo[0]),
|
||||
"=b" (CPUInfo[1]),
|
||||
"=c" (CPUInfo[2]),
|
||||
"=d" (CPUInfo[3]) :
|
||||
/* We clear ECX to avoid a valgrind false-positive prior to v3.17.0. */
|
||||
"0" (InfoType), "2" (0)
|
||||
);
|
||||
#endif
|
||||
#elif defined(CPU_INFO_BY_C)
|
||||
/* We use __get_cpuid_count to clear ECX to avoid a valgrind false-positive
|
||||
prior to v3.17.0.*/
|
||||
if (!__get_cpuid_count(InfoType, 0, &(CPUInfo[0]), &(CPUInfo[1]), &(CPUInfo[2]), &(CPUInfo[3]))) {
|
||||
/* Our function cannot fail, but __get_cpuid{_count} can.
|
||||
Returning all zeroes will effectively disable all SIMD, which is
|
||||
what we want on CPUs that don't support CPUID. */
|
||||
CPUInfo[3] = CPUInfo[2] = CPUInfo[1] = CPUInfo[0] = 0;
|
||||
}
|
||||
#else
|
||||
# error "Configured to use x86 RTCD, but no CPU detection method available. " \
|
||||
"Reconfigure with --disable-rtcd (or send patches)."
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
typedef struct CPU_Feature{
|
||||
/* SIMD: 128-bit */
|
||||
int HW_SSE;
|
||||
int HW_SSE2;
|
||||
int HW_SSE41;
|
||||
/* SIMD: 256-bit */
|
||||
int HW_AVX2;
|
||||
} CPU_Feature;
|
||||
|
||||
static void rnn_cpu_feature_check(CPU_Feature *cpu_feature)
|
||||
{
|
||||
unsigned int info[4];
|
||||
unsigned int nIds = 0;
|
||||
|
||||
cpuid(info, 0);
|
||||
nIds = info[0];
|
||||
|
||||
if (nIds >= 1){
|
||||
cpuid(info, 1);
|
||||
cpu_feature->HW_SSE = (info[3] & (1 << 25)) != 0;
|
||||
cpu_feature->HW_SSE2 = (info[3] & (1 << 26)) != 0;
|
||||
cpu_feature->HW_SSE41 = (info[2] & (1 << 19)) != 0;
|
||||
cpu_feature->HW_AVX2 = (info[2] & (1 << 28)) != 0 && (info[2] & (1 << 12)) != 0;
|
||||
if (cpu_feature->HW_AVX2 && nIds >= 7) {
|
||||
cpuid(info, 7);
|
||||
cpu_feature->HW_AVX2 = cpu_feature->HW_AVX2 && (info[1] & (1 << 5)) != 0;
|
||||
} else {
|
||||
cpu_feature->HW_AVX2 = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cpu_feature->HW_SSE = 0;
|
||||
cpu_feature->HW_SSE2 = 0;
|
||||
cpu_feature->HW_SSE41 = 0;
|
||||
cpu_feature->HW_AVX2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int rnn_select_arch_impl(void)
|
||||
{
|
||||
CPU_Feature cpu_feature;
|
||||
int arch;
|
||||
|
||||
rnn_cpu_feature_check(&cpu_feature);
|
||||
|
||||
arch = 0;
|
||||
if (!cpu_feature.HW_SSE41)
|
||||
{
|
||||
return arch;
|
||||
}
|
||||
arch++;
|
||||
|
||||
if (!cpu_feature.HW_AVX2)
|
||||
{
|
||||
return arch;
|
||||
}
|
||||
arch++;
|
||||
|
||||
return arch;
|
||||
}
|
||||
|
||||
int rnn_select_arch(void) {
|
||||
int arch = rnn_select_arch_impl();
|
||||
#ifdef FUZZING
|
||||
/* Randomly downgrade the architecture. */
|
||||
arch = rand()%(arch+1);
|
||||
#endif
|
||||
return arch;
|
||||
}
|
||||
|
||||
#endif
|
||||
88
src/modules/ZoomIt/ZoomIt/rnnoise/x86/x86cpu.h
Normal file
88
src/modules/ZoomIt/ZoomIt/rnnoise/x86/x86cpu.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/* Copyright (c) 2014, Cisco Systems, INC
|
||||
Written by XiangMingZhu WeiZhou MinPeng YanWang
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#if !defined(X86CPU_H)
|
||||
# define X86CPU_H
|
||||
|
||||
# define MAY_HAVE_SSE4_1(name) name ## _sse4_1
|
||||
|
||||
# define MAY_HAVE_AVX2(name) name ## _avx2
|
||||
|
||||
# ifdef RNN_ENABLE_X86_RTCD
|
||||
int opus_select_arch(void);
|
||||
# endif
|
||||
|
||||
# if defined(__SSE2__)
|
||||
# include "common.h"
|
||||
|
||||
/*MOVD should not impose any alignment restrictions, but the C standard does,
|
||||
and UBSan will report errors if we actually make unaligned accesses.
|
||||
Use this to work around those restrictions (which should hopefully all get
|
||||
optimized to a single MOVD instruction).
|
||||
GCC implemented _mm_loadu_si32() since GCC 11; HOWEVER, there is a bug!
|
||||
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99754
|
||||
LLVM implemented _mm_loadu_si32() since Clang 8.0, however the
|
||||
__clang_major__ version number macro is unreliable, as vendors
|
||||
(specifically, Apple) will use different numbering schemes than upstream.
|
||||
Clang's advice is "use feature detection", but they do not provide feature
|
||||
detection support for specific SIMD functions.
|
||||
We follow the approach from the SIMDe project and instead detect unrelated
|
||||
features that should be available in the version we want (see
|
||||
<https://github.com/simd-everywhere/simde/blob/master/simde/simde-detect-clang.h>).*/
|
||||
# if defined(__clang__)
|
||||
# if __has_warning("-Wextra-semi-stmt") || \
|
||||
__has_builtin(__builtin_rotateleft32)
|
||||
# define OPUS_CLANG_8 (1)
|
||||
# endif
|
||||
# endif
|
||||
# if !defined(_MSC_VER) && !OPUS_GNUC_PREREQ(11,3) && !defined(OPUS_CLANG_8)
|
||||
# include <string.h>
|
||||
# include <emmintrin.h>
|
||||
|
||||
# ifdef _mm_loadu_si32
|
||||
# undef _mm_loadu_si32
|
||||
# endif
|
||||
# define _mm_loadu_si32 WORKAROUND_mm_loadu_si32
|
||||
static inline __m128i WORKAROUND_mm_loadu_si32(void const* mem_addr) {
|
||||
int val;
|
||||
memcpy(&val, mem_addr, sizeof(val));
|
||||
return _mm_cvtsi32_si128(val);
|
||||
}
|
||||
# elif defined(_MSC_VER)
|
||||
/* MSVC needs this for _mm_loadu_si32 */
|
||||
# include <immintrin.h>
|
||||
# endif
|
||||
|
||||
# define OP_CVTEPI8_EPI32_M32(x) \
|
||||
(_mm_cvtepi8_epi32(_mm_loadu_si32(x)))
|
||||
|
||||
# define OP_CVTEPI16_EPI32_M64(x) \
|
||||
(_mm_cvtepi16_epi32(_mm_loadl_epi64((__m128i *)(void*)(x))))
|
||||
|
||||
# endif
|
||||
|
||||
#endif
|
||||
BIN
src/modules/ZoomIt/ZoomIt/selfie_segmentation.onnx
Normal file
BIN
src/modules/ZoomIt/ZoomIt/selfie_segmentation.onnx
Normal file
Binary file not shown.
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Services;
|
||||
|
||||
public interface IExtensionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the currently cached installed Command Palette extensions.
|
||||
/// </summary>
|
||||
/// <param name="includeDisabledExtensions">True to include disabled extensions in the result.</param>
|
||||
/// <returns>A sequence of installed Command Palette extensions from the current in-memory cache.</returns>
|
||||
Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false);
|
||||
|
||||
/// <summary>
|
||||
/// Forces a fresh scan of installed Command Palette extensions and updates the in-memory cache.
|
||||
/// </summary>
|
||||
/// <param name="includeDisabledExtensions">True to include disabled extensions in the result.</param>
|
||||
/// <returns>A sequence of installed Command Palette extensions after the cache has been rebuilt.</returns>
|
||||
Task<IEnumerable<IExtensionWrapper>> RefreshInstalledExtensionsAsync(bool includeDisabledExtensions = false);
|
||||
|
||||
// Task<IEnumerable<string>> GetInstalledHomeWidgetPackageFamilyNamesAsync(bool includeDisabledExtensions = false);
|
||||
/// <summary>
|
||||
/// Gets the installed Command Palette extensions for a specific provider type.
|
||||
/// </summary>
|
||||
/// <param name="providerType">The provider type to match.</param>
|
||||
/// <param name="includeDisabledExtensions">True to include disabled extensions in the result.</param>
|
||||
/// <returns>A sequence of installed Command Palette extensions for the requested provider type.</returns>
|
||||
Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(Microsoft.CommandPalette.Extensions.ProviderType providerType, bool includeDisabledExtensions = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached installed extension by its unique id.
|
||||
/// </summary>
|
||||
/// <param name="extensionUniqueId">The unique id of the extension to look up.</param>
|
||||
/// <returns>The cached extension if found; otherwise, null.</returns>
|
||||
IExtensionWrapper? GetInstalledExtension(string extensionUniqueId);
|
||||
|
||||
/// <summary>
|
||||
/// Signals running extensions to stop.
|
||||
/// </summary>
|
||||
Task SignalStopExtensionsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Raised when one or more extensions are added to the installed set.
|
||||
/// </summary>
|
||||
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when one or more extensions are removed from the installed set.
|
||||
/// </summary>
|
||||
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Enables an installed extension by unique id.
|
||||
/// </summary>
|
||||
/// <param name="extensionUniqueId">The unique id of the extension to enable.</param>
|
||||
void EnableExtension(string extensionUniqueId);
|
||||
|
||||
/// <summary>
|
||||
/// Disables an installed extension by unique id.
|
||||
/// </summary>
|
||||
/// <param name="extensionUniqueId">The unique id of the extension to disable.</param>
|
||||
void DisableExtension(string extensionUniqueId);
|
||||
|
||||
///// <summary>
|
||||
///// Gets a boolean indicating whether the extension was disabled due to the corresponding Windows optional feature
|
||||
///// being absent from the machine or in an unknown state.
|
||||
///// </summary>
|
||||
///// <param name="extension">The out of proc extension object</param>
|
||||
///// <returns>True only if the extension was disabled. False otherwise.</returns>
|
||||
// public Task<bool> DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension);
|
||||
}
|
||||
@@ -125,6 +125,49 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a wrapper for a JavaScript extension where the <see cref="ICommandProvider"/>
|
||||
/// is obtained directly (not through <see cref="IExtensionWrapper.GetExtensionObject"/>).
|
||||
/// </summary>
|
||||
/// <param name="extension">The JS extension wrapper managing the node process.</param>
|
||||
/// <param name="provider">The command provider proxy obtained via JSON-RPC.</param>
|
||||
/// <param name="mainThread">The UI thread scheduler.</param>
|
||||
public CommandProviderWrapper(IExtensionWrapper extension, ICommandProvider provider, TaskScheduler mainThread)
|
||||
{
|
||||
_taskScheduler = mainThread;
|
||||
TopLevelPageContext = new(this, _taskScheduler);
|
||||
|
||||
Extension = extension;
|
||||
ExtensionHost = new CommandPaletteHost(extension);
|
||||
_commandProvider = new(provider);
|
||||
|
||||
try
|
||||
{
|
||||
var model = _commandProvider.Unsafe!;
|
||||
|
||||
model.InitializeWithHost(ExtensionHost);
|
||||
model.ItemsChanged += CommandProvider_ItemsChanged;
|
||||
|
||||
isValid = true;
|
||||
|
||||
Id = provider.Id;
|
||||
DisplayName = provider.DisplayName;
|
||||
Icon = new(provider.Icon);
|
||||
Icon.InitializeProperties();
|
||||
Settings = new(provider.Settings, this, _taskScheduler);
|
||||
|
||||
Logger.LogDebug($"Initialized JS extension command provider {Extension.PackageFamilyName}:{Extension.ExtensionUniqueId}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError("Failed to initialize CommandProvider for JS extension.");
|
||||
Logger.LogError($"Extension was {Extension!.PackageFamilyName}");
|
||||
Logger.LogError(e.ToString());
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
private ProviderSettings GetProviderSettings(SettingsModel settings)
|
||||
{
|
||||
if (!settings.ProviderSettings.TryGetValue(ProviderId, out var ps))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.UI.ViewModels.Commands;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -18,8 +19,7 @@ public sealed partial class BuiltInsCommandProvider : CommandProvider
|
||||
private readonly FallbackReloadItem _fallbackReloadItem = new();
|
||||
private readonly FallbackLogItem _fallbackLogItem = new();
|
||||
private readonly NewExtensionPage _newExtension = new();
|
||||
|
||||
private readonly IRootPageService _rootPageService;
|
||||
private readonly GoHomeDockCommand _goHomeDockCommand = new();
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() =>
|
||||
[
|
||||
@@ -41,22 +41,16 @@ public sealed partial class BuiltInsCommandProvider : CommandProvider
|
||||
_fallbackLogItem,
|
||||
];
|
||||
|
||||
public BuiltInsCommandProvider(IRootPageService rootPageService)
|
||||
public BuiltInsCommandProvider()
|
||||
{
|
||||
Id = "com.microsoft.cmdpal.builtin.core";
|
||||
DisplayName = Properties.Resources.builtin_display_name;
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\Square44x44Logo.altform-unplated_targetsize-256.png");
|
||||
|
||||
_rootPageService = rootPageService;
|
||||
}
|
||||
|
||||
public override ICommandItem[]? GetDockBands()
|
||||
{
|
||||
var rootPage = _rootPageService.GetRootPage();
|
||||
List<ICommandItem> bandItems = new();
|
||||
bandItems.Add(new WrappedDockItem(rootPage, Properties.Resources.builtin_command_palette_title));
|
||||
|
||||
return bandItems.ToArray();
|
||||
return [new WrappedDockItem(_goHomeDockCommand, Properties.Resources.builtin_command_palette_title)];
|
||||
}
|
||||
|
||||
public override void InitializeWithHost(IExtensionHost host) => BuiltinsExtensionHost.Instance.Initialize(host);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// A lightweight command used as a dock band item that navigates the user
|
||||
/// back to the Command Palette home page when invoked.
|
||||
/// </summary>
|
||||
internal sealed partial class GoHomeDockCommand : InvokableCommand
|
||||
{
|
||||
public GoHomeDockCommand()
|
||||
{
|
||||
Name = Properties.Resources.builtin_command_palette_title;
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\Square44x44Logo.altform-unplated_targetsize-256.png");
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke() => CommandResult.GoHome();
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public sealed partial class ExtensionGalleryItemViewModel : ObservableObject
|
||||
|
||||
private const string SourceTypeWinGet = "winget";
|
||||
private const string SourceTypeStore = "msstore";
|
||||
private const string SourceTypeNpm = "npm";
|
||||
private const string SourceTypeUrl = "url";
|
||||
private const string SourceTypeGitHub = "github";
|
||||
private const string SourceTypeWebsite = "website";
|
||||
@@ -129,6 +130,10 @@ public sealed partial class ExtensionGalleryItemViewModel : ObservableObject
|
||||
|
||||
public bool HasStoreSource => HasSource(SourceTypeStore);
|
||||
|
||||
public bool HasNpmSource => HasSource(SourceTypeNpm);
|
||||
|
||||
public string? NpmPackageId => GetSource(SourceTypeNpm)?.Id;
|
||||
|
||||
public bool HasUrlSource => _installSourcesByType.ContainsKey(SourceTypeUrl) && InstallUrl is not null;
|
||||
|
||||
public bool HasHomepage => _homepageHttpUri is not null;
|
||||
@@ -166,7 +171,7 @@ public sealed partial class ExtensionGalleryItemViewModel : ObservableObject
|
||||
|
||||
public bool ShowUnknownSourceIndicator => HasUnknownSource || !HasKnownSourceIndicator;
|
||||
|
||||
public bool HasActionableSourceDetails => HasStoreSource || HasWinGetSource || HasHomepage || HasUrlSource;
|
||||
public bool HasActionableSourceDetails => HasStoreSource || HasWinGetSource || HasNpmSource || HasHomepage || HasUrlSource;
|
||||
|
||||
public bool ShowNoSourceDetails => !HasActionableSourceDetails;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.Common.WinGet.Models;
|
||||
using Microsoft.CmdPal.Common.WinGet.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Properties;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -50,7 +51,7 @@ public sealed partial class ExtensionGalleryViewModel : ObservableObject, IDispo
|
||||
"Failed to check WinGet package status");
|
||||
|
||||
private readonly IExtensionGalleryService _galleryService;
|
||||
private readonly IExtensionService _extensionService;
|
||||
private readonly IEnumerable<IExtensionService> _extensionServices;
|
||||
private readonly ILogger<ExtensionGalleryViewModel> _logger;
|
||||
private readonly ExtensionGalleryItemViewModelFactory _galleryExtensionViewModelFactory;
|
||||
private readonly IWinGetPackageManagerService? _winGetPackageManagerService;
|
||||
@@ -162,7 +163,7 @@ public sealed partial class ExtensionGalleryViewModel : ObservableObject, IDispo
|
||||
|
||||
public ExtensionGalleryViewModel(
|
||||
IExtensionGalleryService galleryService,
|
||||
IExtensionService extensionService,
|
||||
IEnumerable<IExtensionService> extensionServices,
|
||||
ILogger<ExtensionGalleryViewModel> logger,
|
||||
ExtensionGalleryItemViewModelFactory galleryExtensionViewModelFactory,
|
||||
IWinGetPackageManagerService? winGetPackageManagerService = null,
|
||||
@@ -171,7 +172,7 @@ public sealed partial class ExtensionGalleryViewModel : ObservableObject, IDispo
|
||||
TaskScheduler? uiScheduler = null)
|
||||
{
|
||||
_galleryService = galleryService;
|
||||
_extensionService = extensionService;
|
||||
_extensionServices = extensionServices;
|
||||
_logger = logger;
|
||||
_galleryExtensionViewModelFactory = galleryExtensionViewModelFactory;
|
||||
_winGetPackageManagerService = winGetPackageManagerService;
|
||||
@@ -347,17 +348,23 @@ public sealed partial class ExtensionGalleryViewModel : ObservableObject, IDispo
|
||||
List<ExtensionGalleryItemViewModel> snapshot;
|
||||
try
|
||||
{
|
||||
var installedExtensions = refreshInstalledExtensions
|
||||
? await RunInBackgroundAsync(
|
||||
() => _extensionService.RefreshInstalledExtensionsAsync(includeDisabledExtensions: true),
|
||||
cancellationToken)
|
||||
: await RunInBackgroundAsync(
|
||||
() => _extensionService.GetInstalledExtensionsAsync(includeDisabledExtensions: true),
|
||||
cancellationToken);
|
||||
var allInstalledExtensions = new List<IExtensionWrapper>();
|
||||
foreach (var service in _extensionServices)
|
||||
{
|
||||
var extensions = refreshInstalledExtensions
|
||||
? await RunInBackgroundAsync(
|
||||
() => service.RefreshInstalledExtensionsAsync(includeDisabledExtensions: true),
|
||||
cancellationToken)
|
||||
: await RunInBackgroundAsync(
|
||||
() => service.GetInstalledExtensionsAsync(includeDisabledExtensions: true),
|
||||
cancellationToken);
|
||||
allInstalledExtensions.AddRange(extensions);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var installedPfns = new HashSet<string>(
|
||||
installedExtensions
|
||||
allInstalledExtensions
|
||||
.Select(e => e.PackageFamilyName)
|
||||
.Where(pfn => !string.IsNullOrEmpty(pfn)),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
|
||||
public record ProviderEnabledStateChangedMessage(string ProviderId, bool IsEnabled);
|
||||
@@ -0,0 +1,558 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type - closely related adapters grouped together
|
||||
#pragma warning disable SA1649 // File name should match first type name
|
||||
#pragma warning disable SA1300 // Element should begin with upper-case letter (private event backing fields)
|
||||
#pragma warning disable SA1516 // Elements should be separated by blank line
|
||||
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services.JsonRpc;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON command data to ICommand and IInvokableCommand interfaces.
|
||||
/// </summary>
|
||||
internal sealed class JSCommandAdapter : ICommand, IInvokableCommand
|
||||
{
|
||||
private readonly JsonElement _data;
|
||||
private readonly JsonRpcConnection _connection;
|
||||
private readonly Lock _eventLock = new();
|
||||
private event TypedEventHandler<object, IPropChangedEventArgs>? _propChanged;
|
||||
|
||||
public JSCommandAdapter(JsonElement data, JsonRpcConnection connection)
|
||||
{
|
||||
_data = data;
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public string Name => GetStringProperty("displayName") ?? GetStringProperty("name") ?? string.Empty;
|
||||
|
||||
public string Id => GetStringProperty("id") ?? string.Empty;
|
||||
|
||||
public IIconInfo Icon => GetIconInfo();
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_propChanged += value;
|
||||
}
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_propChanged -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RaisePropChanged(string propertyName)
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_propChanged?.Invoke(this, new PropChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
public ICommandResult Invoke(object sender)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _connection.SendRequestAsync(
|
||||
"command/invoke",
|
||||
new JsonObject { ["commandId"] = Id },
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (response.Error != null)
|
||||
{
|
||||
Logger.LogError($"Command invoke error: {response.Error.Message}");
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
return ParseCommandResult(response.Result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to invoke command {Id}: {ex.Message}");
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetStringProperty(string name)
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return prop.GetString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IIconInfo GetIconInfo()
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("icon", out var iconProp))
|
||||
{
|
||||
return JSIconInfoAdapter.FromJson(iconProp);
|
||||
}
|
||||
|
||||
return new IconInfo(string.Empty);
|
||||
}
|
||||
|
||||
private ICommandResult ParseCommandResult(JsonElement? result)
|
||||
{
|
||||
return JSCommandResultAdapter.ParseCommandResult(result, _connection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON command item data to ICommandItem interface.
|
||||
/// </summary>
|
||||
internal sealed class JSCommandItemAdapter : ICommandItem
|
||||
{
|
||||
private readonly JsonElement _data;
|
||||
private readonly JsonRpcConnection _connection;
|
||||
private readonly Lock _eventLock = new();
|
||||
private ICommand? _command;
|
||||
private event TypedEventHandler<object, IPropChangedEventArgs>? _propChanged;
|
||||
|
||||
public JSCommandItemAdapter(JsonElement data, JsonRpcConnection connection)
|
||||
{
|
||||
_data = data;
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public ICommand Command
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_command == null)
|
||||
{
|
||||
var commandData = _data;
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("command", out var commandElement) &&
|
||||
commandElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
commandData = commandElement;
|
||||
}
|
||||
|
||||
_command = JSCommandFactory.CreateCommandFromJson(commandData, _connection);
|
||||
}
|
||||
|
||||
return _command;
|
||||
}
|
||||
}
|
||||
|
||||
public IContextItem[] MoreCommands => [];
|
||||
|
||||
public IIconInfo Icon => GetIconInfo();
|
||||
|
||||
public string Title => GetStringProperty("displayName") ?? GetStringProperty("title") ?? string.Empty;
|
||||
|
||||
public string Subtitle => GetStringProperty("description") ?? GetStringProperty("subtitle") ?? string.Empty;
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_propChanged += value;
|
||||
}
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_propChanged -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RaisePropChanged(string propertyName)
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_propChanged?.Invoke(this, new PropChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetStringProperty(string name)
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return prop.GetString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IIconInfo GetIconInfo()
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("icon", out var iconProp))
|
||||
{
|
||||
return JSIconInfoAdapter.FromJson(iconProp);
|
||||
}
|
||||
|
||||
return new IconInfo(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility for creating the appropriate ICommand implementation from JSON data,
|
||||
/// inspecting the _type discriminator to return page proxies for pages.
|
||||
/// </summary>
|
||||
internal static class JSCommandFactory
|
||||
{
|
||||
internal static ICommand CreateCommandFromJson(JsonElement data, JsonRpcConnection connection)
|
||||
{
|
||||
if (data.ValueKind == JsonValueKind.Object &&
|
||||
data.TryGetProperty("_type", out var typeProp) && typeProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var commandId = string.Empty;
|
||||
if (data.TryGetProperty("id", out var idProp) && idProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
commandId = idProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
var pageType = typeProp.GetString();
|
||||
|
||||
if (pageType == "dynamicListPage")
|
||||
{
|
||||
return new JSDynamicListPageProxy(commandId, connection, data);
|
||||
}
|
||||
|
||||
if (pageType == "listPage")
|
||||
{
|
||||
return new JSListPageProxy(commandId, connection, data);
|
||||
}
|
||||
|
||||
if (pageType == "contentPage")
|
||||
{
|
||||
return new JSContentPageProxy(commandId, data, connection);
|
||||
}
|
||||
}
|
||||
|
||||
return new JSCommandAdapter(data, connection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON fallback command item data to IFallbackCommandItem interface.
|
||||
/// </summary>
|
||||
internal sealed class JSFallbackCommandItemAdapter : IFallbackCommandItem, IFallbackCommandItem2
|
||||
{
|
||||
private readonly JsonElement _data;
|
||||
private readonly JsonRpcConnection _connection;
|
||||
private readonly Lock _eventLock = new();
|
||||
private ICommand? _command;
|
||||
private JSFallbackHandler? _fallbackHandler;
|
||||
private string? _displayTitleOverride;
|
||||
private event TypedEventHandler<object, IPropChangedEventArgs>? _propChanged;
|
||||
|
||||
public JSFallbackCommandItemAdapter(JsonElement data, JsonRpcConnection connection)
|
||||
{
|
||||
_data = data;
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public ICommand Command
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_command == null)
|
||||
{
|
||||
var commandData = _data;
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("command", out var commandElement) &&
|
||||
commandElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
commandData = commandElement;
|
||||
}
|
||||
|
||||
_command = JSCommandFactory.CreateCommandFromJson(commandData, _connection);
|
||||
}
|
||||
|
||||
return _command;
|
||||
}
|
||||
}
|
||||
|
||||
public IContextItem[] MoreCommands => [];
|
||||
|
||||
public IIconInfo Icon => GetIconInfo();
|
||||
|
||||
public string Title => GetStringProperty("displayName") ?? GetStringProperty("title") ?? string.Empty;
|
||||
|
||||
public string Subtitle => GetStringProperty("description") ?? GetStringProperty("subtitle") ?? string.Empty;
|
||||
|
||||
public string DisplayTitle => _displayTitleOverride ?? GetStringProperty("displayTitle") ?? Title;
|
||||
|
||||
public string Id => GetStringProperty("id") ?? string.Empty;
|
||||
|
||||
public void UpdateDisplayTitle(string newTitle)
|
||||
{
|
||||
_displayTitleOverride = newTitle;
|
||||
}
|
||||
|
||||
public IFallbackHandler FallbackHandler
|
||||
{
|
||||
get
|
||||
{
|
||||
_fallbackHandler ??= new JSFallbackHandler(_connection, Id);
|
||||
return _fallbackHandler;
|
||||
}
|
||||
}
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_propChanged += value;
|
||||
}
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_propChanged -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RaisePropChanged(string propertyName)
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_propChanged?.Invoke(this, new PropChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetStringProperty(string name)
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return prop.GetString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IIconInfo GetIconInfo()
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("icon", out var iconProp))
|
||||
{
|
||||
return JSIconInfoAdapter.FromJson(iconProp);
|
||||
}
|
||||
|
||||
return new IconInfo(string.Empty);
|
||||
}
|
||||
|
||||
private sealed class JSFallbackHandler : IFallbackHandler
|
||||
{
|
||||
private readonly JsonRpcConnection _connection;
|
||||
private readonly string _commandId;
|
||||
|
||||
public JSFallbackHandler(JsonRpcConnection connection, string commandId)
|
||||
{
|
||||
_connection = connection;
|
||||
_commandId = commandId;
|
||||
}
|
||||
|
||||
public void UpdateQuery(string query)
|
||||
{
|
||||
try
|
||||
{
|
||||
_connection.SendNotificationAsync(
|
||||
"fallback/updateQuery",
|
||||
new JsonObject { ["commandId"] = _commandId, ["query"] = query },
|
||||
CancellationToken.None).Wait(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Failed to send fallback query update: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON icon data to IIconInfo and IIconData interfaces.
|
||||
/// </summary>
|
||||
internal sealed class JSIconInfoAdapter : IIconInfo
|
||||
{
|
||||
private readonly JSIconDataAdapter? _light;
|
||||
private readonly JSIconDataAdapter? _dark;
|
||||
|
||||
private JSIconInfoAdapter(JSIconDataAdapter? light, JSIconDataAdapter? dark)
|
||||
{
|
||||
_light = light;
|
||||
_dark = dark;
|
||||
}
|
||||
|
||||
public IIconData Light => _light ?? new JSIconDataAdapter(string.Empty, null);
|
||||
|
||||
public IIconData Dark => _dark ?? new JSIconDataAdapter(string.Empty, null);
|
||||
|
||||
public static JSIconInfoAdapter FromJson(JsonElement iconJson)
|
||||
{
|
||||
if (iconJson.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
return new JSIconInfoAdapter(null, null);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1507 // JSON property names, not C# members
|
||||
JSIconDataAdapter? light = null;
|
||||
JSIconDataAdapter? dark = null;
|
||||
|
||||
if (iconJson.TryGetProperty("Light", out var lightProp) || iconJson.TryGetProperty("light", out lightProp))
|
||||
{
|
||||
light = ParseIconData(lightProp);
|
||||
}
|
||||
|
||||
if (iconJson.TryGetProperty("Dark", out var darkProp) || iconJson.TryGetProperty("dark", out darkProp))
|
||||
{
|
||||
dark = ParseIconData(darkProp);
|
||||
}
|
||||
|
||||
// If only "icon"/"Icon" property exists, use it for both light and dark
|
||||
if (light == null && dark == null)
|
||||
{
|
||||
if (iconJson.TryGetProperty("Icon", out var iconProp) || iconJson.TryGetProperty("icon", out iconProp))
|
||||
{
|
||||
var iconData = ParseIconData(iconProp);
|
||||
light = iconData;
|
||||
dark = iconData;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1507
|
||||
|
||||
return new JSIconInfoAdapter(light, dark);
|
||||
}
|
||||
|
||||
private static JSIconDataAdapter? ParseIconData(JsonElement iconDataJson)
|
||||
{
|
||||
#pragma warning disable CA1507 // JSON property names, not C# members
|
||||
string? iconPath = null;
|
||||
string? base64Data = null;
|
||||
|
||||
if (iconDataJson.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
iconPath = iconDataJson.GetString();
|
||||
}
|
||||
else if (iconDataJson.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
if ((iconDataJson.TryGetProperty("Icon", out var iconProp) || iconDataJson.TryGetProperty("icon", out iconProp)) &&
|
||||
iconProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
iconPath = iconProp.GetString();
|
||||
}
|
||||
|
||||
if ((iconDataJson.TryGetProperty("Data", out var dataProp) || iconDataJson.TryGetProperty("data", out dataProp)) &&
|
||||
dataProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
base64Data = dataProp.GetString();
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1507
|
||||
|
||||
if (!string.IsNullOrEmpty(iconPath) || !string.IsNullOrEmpty(base64Data))
|
||||
{
|
||||
return new JSIconDataAdapter(iconPath ?? string.Empty, base64Data);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal sealed class JSIconDataAdapter : IIconData
|
||||
{
|
||||
private readonly string _icon;
|
||||
private readonly string? _base64Data;
|
||||
|
||||
public JSIconDataAdapter(string icon, string? base64Data)
|
||||
{
|
||||
_icon = icon;
|
||||
_base64Data = base64Data;
|
||||
}
|
||||
|
||||
public string Icon => _icon;
|
||||
|
||||
public IRandomAccessStreamReference? Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_base64Data))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
byte[] bytes;
|
||||
if (_base64Data.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
bytes = DecodeDataUri(_base64Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes = Convert.FromBase64String(_base64Data);
|
||||
}
|
||||
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
stream.WriteAsync(bytes.AsBuffer()).GetResults();
|
||||
stream.Seek(0);
|
||||
return RandomAccessStreamReference.CreateFromStream(stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Failed to decode icon data: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] DecodeDataUri(string dataUri)
|
||||
{
|
||||
// data:[<mediatype>][;base64],<data>
|
||||
var commaIndex = dataUri.IndexOf(',', StringComparison.Ordinal);
|
||||
if (commaIndex < 0)
|
||||
{
|
||||
throw new FormatException("Invalid data URI: no comma separator");
|
||||
}
|
||||
|
||||
var header = dataUri[..commaIndex];
|
||||
var data = dataUri[(commaIndex + 1)..];
|
||||
|
||||
if (header.Contains(";base64", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Convert.FromBase64String(data);
|
||||
}
|
||||
|
||||
// URL-encoded data (e.g., SVG)
|
||||
var decoded = Uri.UnescapeDataString(data);
|
||||
return System.Text.Encoding.UTF8.GetBytes(decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,588 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma warning disable SA1300 // Element should begin with upper-case letter (private event backing fields)
|
||||
#pragma warning disable CS4014 // Because this call is not awaited
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services.JsonRpc;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Proxy that implements ICommandProvider by forwarding calls to a Node.js extension via JSON-RPC.
|
||||
/// </summary>
|
||||
public sealed class JSCommandProviderProxy : ICommandProvider, IDisposable
|
||||
{
|
||||
private readonly JsonRpcConnection _rpcConnection;
|
||||
private readonly JSExtensionManifest _manifest;
|
||||
private readonly IconInfo _defaultIcon;
|
||||
private readonly Lock _eventLock = new();
|
||||
private readonly Dictionary<(string Message, int State), StatusMessage> _shownStatusMessages = new();
|
||||
private readonly Dictionary<string, JSFallbackCommandItemAdapter> _fallbackAdapters = new();
|
||||
private IExtensionHost? _host;
|
||||
private bool _isDisposed;
|
||||
private ICommandSettings? _settingsCache;
|
||||
private bool _settingsQueried;
|
||||
|
||||
private event TypedEventHandler<object, IItemsChangedEventArgs>? _itemsChanged;
|
||||
|
||||
public JSCommandProviderProxy(JsonRpcConnection rpcConnection, JSExtensionManifest manifest)
|
||||
{
|
||||
_rpcConnection = rpcConnection ?? throw new ArgumentNullException(nameof(rpcConnection));
|
||||
_manifest = manifest ?? throw new ArgumentNullException(nameof(manifest));
|
||||
_defaultIcon = new IconInfo(string.Empty);
|
||||
|
||||
RegisterNotificationHandlers();
|
||||
}
|
||||
|
||||
public string Id => _manifest.Name ?? "unknown";
|
||||
|
||||
public string DisplayName => _manifest.DisplayName ?? _manifest.Name ?? "Unknown";
|
||||
|
||||
public IIconInfo Icon => _defaultIcon;
|
||||
|
||||
public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_itemsChanged += value;
|
||||
}
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
lock (_eventLock)
|
||||
{
|
||||
_itemsChanged -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICommandItem[] TopLevelCommands()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _rpcConnection.SendRequestAsync(
|
||||
"provider/getTopLevelCommands",
|
||||
null,
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (response.Error != null)
|
||||
{
|
||||
Logger.LogError($"TopLevelCommands error: {response.Error.Message}");
|
||||
return [];
|
||||
}
|
||||
|
||||
return ParseCommandItems(response.Result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to get top-level commands: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public IFallbackCommandItem[]? FallbackCommands()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _rpcConnection.SendRequestAsync(
|
||||
"provider/getFallbackCommands",
|
||||
null,
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (response.Error != null)
|
||||
{
|
||||
Logger.LogWarning($"FallbackCommands error: {response.Error.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return ParseFallbackCommandItems(response.Result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Failed to get fallback commands: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand? GetCommand(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _rpcConnection.SendRequestAsync(
|
||||
"provider/getCommand",
|
||||
new JsonObject { ["commandId"] = id },
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (response.Error != null)
|
||||
{
|
||||
Logger.LogWarning($"GetCommand error for {id}: {response.Error.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.Result.HasValue || response.Result.Value.ValueKind == JsonValueKind.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = response.Result.Value;
|
||||
|
||||
// Check for page type discriminator to return the appropriate proxy
|
||||
if (data.TryGetProperty("_type", out var typeProp) && typeProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var pageType = typeProp.GetString();
|
||||
if (pageType == "dynamicListPage")
|
||||
{
|
||||
return new JSDynamicListPageProxy(id, _rpcConnection);
|
||||
}
|
||||
|
||||
if (pageType == "listPage")
|
||||
{
|
||||
return new JSListPageProxy(id, _rpcConnection);
|
||||
}
|
||||
|
||||
if (pageType == "contentPage")
|
||||
{
|
||||
return new JSContentPageProxy(id, _rpcConnection);
|
||||
}
|
||||
}
|
||||
|
||||
return new JSCommandAdapter(data, _rpcConnection);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Failed to get command {id}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ICommandSettings? Settings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_settingsQueried)
|
||||
{
|
||||
return _settingsCache;
|
||||
}
|
||||
|
||||
_settingsQueried = true;
|
||||
|
||||
try
|
||||
{
|
||||
var response = _rpcConnection.SendRequestAsync(
|
||||
"provider/getSettings",
|
||||
null,
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (response.Error != null)
|
||||
{
|
||||
Logger.LogDebug($"Settings not available for {DisplayName}: {response.Error.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.Result.HasValue || response.Result.Value.ValueKind == JsonValueKind.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = response.Result.Value;
|
||||
var pageId = string.Empty;
|
||||
|
||||
if (data.TryGetProperty("id", out var idProp) && idProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
pageId = idProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(pageId))
|
||||
{
|
||||
_settingsCache = new JSCommandSettingsProxy(pageId, _rpcConnection);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogDebug($"Failed to get settings for {DisplayName}: {ex.Message}");
|
||||
}
|
||||
|
||||
return _settingsCache;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Frozen => true;
|
||||
|
||||
public void InitializeWithHost(IExtensionHost host)
|
||||
{
|
||||
_host = host ?? throw new ArgumentNullException(nameof(host));
|
||||
Logger.LogDebug($"JSCommandProviderProxy initialized with host for {DisplayName}");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
lock (_eventLock)
|
||||
{
|
||||
_itemsChanged = null;
|
||||
}
|
||||
|
||||
_host = null;
|
||||
}
|
||||
|
||||
private void RegisterNotificationHandlers()
|
||||
{
|
||||
_rpcConnection.RegisterNotificationHandler("provider/itemsChanged", HandleItemsChangedNotification);
|
||||
_rpcConnection.RegisterNotificationHandler("command/propChanged", HandleCommandPropChangedNotification);
|
||||
_rpcConnection.RegisterNotificationHandler("page/propChanged", HandlePagePropChangedNotification);
|
||||
_rpcConnection.RegisterNotificationHandler("content/propChanged", HandleContentPropChangedNotification);
|
||||
_rpcConnection.RegisterNotificationHandler("provider/propChanged", HandleProviderPropChangedNotification);
|
||||
_rpcConnection.RegisterNotificationHandler("host/logMessage", HandleLogMessageNotification);
|
||||
_rpcConnection.RegisterNotificationHandler("host/showStatus", HandleShowStatusNotification);
|
||||
_rpcConnection.RegisterNotificationHandler("host/hideStatus", HandleHideStatusNotification);
|
||||
_rpcConnection.RegisterNotificationHandler("host/copyText", HandleCopyTextNotification);
|
||||
}
|
||||
|
||||
private void HandleItemsChangedNotification(JsonElement paramsElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
var totalItems = -1;
|
||||
if (paramsElement.TryGetProperty("totalItems", out var totalItemsProp))
|
||||
{
|
||||
totalItems = totalItemsProp.GetInt32();
|
||||
}
|
||||
|
||||
lock (_eventLock)
|
||||
{
|
||||
_itemsChanged?.Invoke(this, new ItemsChangedEventArgs(totalItems));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error handling itemsChanged notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCommandPropChangedNotification(JsonElement paramsElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
var commandId = string.Empty;
|
||||
if (paramsElement.TryGetProperty("commandId", out var commandIdProp))
|
||||
{
|
||||
commandId = commandIdProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
// Update fallback adapter properties if applicable
|
||||
if (!string.IsNullOrEmpty(commandId) && _fallbackAdapters.TryGetValue(commandId, out var fallbackAdapter))
|
||||
{
|
||||
if (paramsElement.TryGetProperty("properties", out var propsProp) && propsProp.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
if (propsProp.TryGetProperty("displayTitle", out var displayTitleProp) && displayTitleProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
fallbackAdapter.UpdateDisplayTitle(displayTitleProp.GetString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
fallbackAdapter.RaisePropChanged("DisplayTitle");
|
||||
}
|
||||
|
||||
Logger.LogDebug($"[{DisplayName}] command/propChanged: commandId={commandId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error handling command/propChanged notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePagePropChangedNotification(JsonElement paramsElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pageId = string.Empty;
|
||||
if (paramsElement.TryGetProperty("pageId", out var pageIdProp))
|
||||
{
|
||||
pageId = pageIdProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
var propertyName = string.Empty;
|
||||
if (paramsElement.TryGetProperty("propertyName", out var propertyNameProp))
|
||||
{
|
||||
propertyName = propertyNameProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
Logger.LogDebug($"[{DisplayName}] page/propChanged: pageId={pageId}, property={propertyName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error handling page/propChanged notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleContentPropChangedNotification(JsonElement paramsElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
var contentId = string.Empty;
|
||||
if (paramsElement.TryGetProperty("contentId", out var contentIdProp))
|
||||
{
|
||||
contentId = contentIdProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
var propertyName = string.Empty;
|
||||
if (paramsElement.TryGetProperty("propertyName", out var propertyNameProp))
|
||||
{
|
||||
propertyName = propertyNameProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
Logger.LogDebug($"[{DisplayName}] content/propChanged: contentId={contentId}, property={propertyName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error handling content/propChanged notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleProviderPropChangedNotification(JsonElement paramsElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
var propertyName = string.Empty;
|
||||
if (paramsElement.TryGetProperty("propertyName", out var propertyNameProp))
|
||||
{
|
||||
propertyName = propertyNameProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
Logger.LogDebug($"[{DisplayName}] provider/propChanged: property={propertyName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error handling provider/propChanged notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLogMessageNotification(JsonElement paramsElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!paramsElement.TryGetProperty("message", out var messageProp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = messageProp.GetString() ?? string.Empty;
|
||||
var state = 2; // Default to Info
|
||||
|
||||
if (paramsElement.TryGetProperty("state", out var stateProp))
|
||||
{
|
||||
state = stateProp.GetInt32();
|
||||
}
|
||||
|
||||
// Map MessageState enum: 0=Trace, 1=Debug, 2=Info, 3=Warning, 4=Error
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
Logger.LogDebug($"[{DisplayName}] {message}");
|
||||
break;
|
||||
case 2:
|
||||
Logger.LogInfo($"[{DisplayName}] {message}");
|
||||
break;
|
||||
case 3:
|
||||
Logger.LogWarning($"[{DisplayName}] {message}");
|
||||
break;
|
||||
case 4:
|
||||
Logger.LogError($"[{DisplayName}] {message}");
|
||||
break;
|
||||
default:
|
||||
Logger.LogInfo($"[{DisplayName}] {message}");
|
||||
break;
|
||||
}
|
||||
|
||||
// Forward to host if available
|
||||
if (_host != null)
|
||||
{
|
||||
var logMessage = new LogMessage { Message = message, State = (MessageState)state };
|
||||
_host.LogMessage(logMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error handling logMessage notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleShowStatusNotification(JsonElement paramsElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_host == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!paramsElement.TryGetProperty("message", out var messageProp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = string.Empty;
|
||||
var state = 2; // Default to Info
|
||||
|
||||
if (messageProp.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
if (messageProp.TryGetProperty("Message", out var msgText))
|
||||
{
|
||||
message = msgText.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
if (messageProp.TryGetProperty("State", out var msgState))
|
||||
{
|
||||
state = msgState.GetInt32();
|
||||
}
|
||||
}
|
||||
else if (messageProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
message = messageProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
var statusMessage = new StatusMessage
|
||||
{
|
||||
Message = message,
|
||||
State = (MessageState)state,
|
||||
};
|
||||
|
||||
_shownStatusMessages[(message, state)] = statusMessage;
|
||||
|
||||
var context = StatusContext.Extension;
|
||||
if (paramsElement.TryGetProperty("context", out var contextProp))
|
||||
{
|
||||
if (contextProp.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
context = (StatusContext)contextProp.GetInt32();
|
||||
}
|
||||
else if (contextProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var contextStr = contextProp.GetString();
|
||||
if (contextStr == "page")
|
||||
{
|
||||
context = StatusContext.Page;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_host.ShowStatus(statusMessage, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error handling showStatus notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleHideStatusNotification(JsonElement paramsElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_host == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = string.Empty;
|
||||
var state = 2; // Default to Info
|
||||
|
||||
if (paramsElement.TryGetProperty("message", out var messageProp) &&
|
||||
messageProp.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
if (messageProp.TryGetProperty("Message", out var msgText))
|
||||
{
|
||||
message = msgText.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
if (messageProp.TryGetProperty("State", out var msgState))
|
||||
{
|
||||
state = msgState.GetInt32();
|
||||
}
|
||||
}
|
||||
|
||||
var key = (message, state);
|
||||
if (_shownStatusMessages.TryGetValue(key, out var originalMessage))
|
||||
{
|
||||
_shownStatusMessages.Remove(key);
|
||||
_host.HideStatus(originalMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error handling hideStatus notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCopyTextNotification(JsonElement paramsElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (paramsElement.TryGetProperty("text", out var textProp) && textProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var text = textProp.GetString() ?? string.Empty;
|
||||
ClipboardHelper.SetText(text);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error handling copyText notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private ICommandItem[] ParseCommandItems(JsonElement? result)
|
||||
{
|
||||
if (!result.HasValue || result.Value.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var items = new List<ICommandItem>();
|
||||
foreach (var element in result.Value.EnumerateArray())
|
||||
{
|
||||
items.Add(new JSCommandItemAdapter(element, _rpcConnection));
|
||||
}
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
|
||||
private IFallbackCommandItem[]? ParseFallbackCommandItems(JsonElement? result)
|
||||
{
|
||||
if (!result.HasValue || result.Value.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var items = new List<IFallbackCommandItem>();
|
||||
foreach (var element in result.Value.EnumerateArray())
|
||||
{
|
||||
var adapter = new JSFallbackCommandItemAdapter(element, _rpcConnection);
|
||||
items.Add(adapter);
|
||||
|
||||
var id = adapter.Id;
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
{
|
||||
_fallbackAdapters[id] = adapter;
|
||||
}
|
||||
}
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services.JsonRpc;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Proxy that implements ICommandSettings for JavaScript extensions by wrapping
|
||||
/// a JSContentPageProxy for the settings page. The settings page is fetched via
|
||||
/// the <c>provider/getSettings</c> JSON-RPC method and then relies on existing
|
||||
/// <c>contentPage/getContent</c> and <c>form/submit</c> handlers for interaction.
|
||||
/// </summary>
|
||||
internal sealed class JSCommandSettingsProxy : ICommandSettings
|
||||
{
|
||||
private readonly JSContentPageProxy _settingsPage;
|
||||
|
||||
public JSCommandSettingsProxy(string settingsPageId, JsonRpcConnection connection)
|
||||
{
|
||||
_settingsPage = new JSContentPageProxy(settingsPageId, connection);
|
||||
}
|
||||
|
||||
public IContentPage SettingsPage => _settingsPage;
|
||||
}
|
||||
@@ -0,0 +1,793 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type - closely related content adapters grouped together
|
||||
#pragma warning disable SA1649 // File name should match first type name
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services.JsonRpc;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Static helper for parsing IContent arrays and enhanced ICommandResult
|
||||
/// including GoToPage, ShowToast, and Confirm args.
|
||||
/// </summary>
|
||||
internal static class JSCommandResultAdapter
|
||||
{
|
||||
internal static ICommandResult ParseCommandResult(JsonElement? result, JsonRpcConnection? connection = null)
|
||||
{
|
||||
if (!result.HasValue || result.Value.ValueKind == JsonValueKind.Null)
|
||||
{
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
var kindValue = 0;
|
||||
if (result.Value.TryGetProperty("Kind", out var kindProp) ||
|
||||
result.Value.TryGetProperty("kind", out kindProp))
|
||||
{
|
||||
kindValue = kindProp.GetInt32();
|
||||
}
|
||||
|
||||
var kind = (CommandResultKind)kindValue;
|
||||
|
||||
var hasArgs = result.Value.TryGetProperty("Args", out var argsProp) ||
|
||||
result.Value.TryGetProperty("args", out argsProp);
|
||||
if (hasArgs && argsProp.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
if (kind == CommandResultKind.GoToPage)
|
||||
{
|
||||
return ParseGoToPage(argsProp, connection);
|
||||
}
|
||||
|
||||
if (kind == CommandResultKind.ShowToast)
|
||||
{
|
||||
return ParseShowToast(argsProp);
|
||||
}
|
||||
|
||||
if (kind == CommandResultKind.Confirm)
|
||||
{
|
||||
return ParseConfirm(argsProp, connection);
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == CommandResultKind.GoHome)
|
||||
{
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
|
||||
if (kind == CommandResultKind.GoBack)
|
||||
{
|
||||
return CommandResult.GoBack();
|
||||
}
|
||||
|
||||
if (kind == CommandResultKind.Hide)
|
||||
{
|
||||
return CommandResult.Hide();
|
||||
}
|
||||
|
||||
if (kind == CommandResultKind.KeepOpen)
|
||||
{
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
internal static IContent[] ParseContentArray(JsonElement? result, string pageId, JsonRpcConnection connection)
|
||||
{
|
||||
if (!result.HasValue || result.Value.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var count = result.Value.GetArrayLength();
|
||||
var items = new IContent[count];
|
||||
var i = 0;
|
||||
foreach (var item in result.Value.EnumerateArray())
|
||||
{
|
||||
items[i++] = ParseContentItem(item, pageId, connection);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
internal static IContent ParseContentItem(JsonElement element, string pageId, JsonRpcConnection connection)
|
||||
{
|
||||
var type = string.Empty;
|
||||
if (element.TryGetProperty("type", out var typeProp) && typeProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
type = typeProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
if (type == "form")
|
||||
{
|
||||
return new JSFormContentProxy(pageId, element, connection);
|
||||
}
|
||||
|
||||
if (type == "tree")
|
||||
{
|
||||
return new JSTreeContentAdapter(element, pageId, connection);
|
||||
}
|
||||
|
||||
if (type == "plainText")
|
||||
{
|
||||
return new JSPlainTextContentAdapter(element);
|
||||
}
|
||||
|
||||
if (type == "image")
|
||||
{
|
||||
return new JSImageContentAdapter(element);
|
||||
}
|
||||
|
||||
return new JSMarkdownContentAdapter(element);
|
||||
}
|
||||
|
||||
internal static OptionalColor ParseOptionalColor(JsonElement element)
|
||||
{
|
||||
if (element.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (!element.TryGetProperty("hasValue", out var hasValueProp) || hasValueProp.ValueKind != JsonValueKind.True)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (!element.TryGetProperty("color", out var colorProp) || colorProp.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var hex = colorProp.GetString();
|
||||
if (string.IsNullOrEmpty(hex) || hex[0] != '#')
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (hex.Length == 7)
|
||||
{
|
||||
var r = Convert.ToByte(hex.Substring(1, 2), 16);
|
||||
var g = Convert.ToByte(hex.Substring(3, 2), 16);
|
||||
var b = Convert.ToByte(hex.Substring(5, 2), 16);
|
||||
return ColorHelpers.FromRgb(r, g, b);
|
||||
}
|
||||
|
||||
if (hex.Length == 9)
|
||||
{
|
||||
var a = Convert.ToByte(hex.Substring(1, 2), 16);
|
||||
var r = Convert.ToByte(hex.Substring(3, 2), 16);
|
||||
var g = Convert.ToByte(hex.Substring(5, 2), 16);
|
||||
var b = Convert.ToByte(hex.Substring(7, 2), 16);
|
||||
return ColorHelpers.FromArgb(a, r, g, b);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid hex format; fall through to default
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static ICommandResult ParseGoToPage(JsonElement args, JsonRpcConnection? connection)
|
||||
{
|
||||
var pageId = string.Empty;
|
||||
if ((args.TryGetProperty("PageId", out var pageIdProp) || args.TryGetProperty("pageId", out pageIdProp)) &&
|
||||
pageIdProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
pageId = pageIdProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
var navMode = NavigationMode.Push;
|
||||
if (args.TryGetProperty("Mode", out var navModeProp) || args.TryGetProperty("mode", out navModeProp) || args.TryGetProperty("navigationMode", out navModeProp))
|
||||
{
|
||||
if (navModeProp.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
navMode = (NavigationMode)navModeProp.GetInt32();
|
||||
}
|
||||
else if (navModeProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var modeStr = navModeProp.GetString()?.ToLowerInvariant();
|
||||
navMode = modeStr switch
|
||||
{
|
||||
"push" => NavigationMode.Push,
|
||||
"goback" or "goBack" => NavigationMode.GoBack,
|
||||
"gohome" or "goHome" => NavigationMode.GoHome,
|
||||
_ => NavigationMode.Push,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the page via JSONRPC if we have a connection and a pageId
|
||||
IPage? resolvedPage = null;
|
||||
if (connection != null && !string.IsNullOrEmpty(pageId))
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = connection.SendRequestAsync(
|
||||
"provider/getCommand",
|
||||
new JsonObject { ["commandId"] = pageId },
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (response.Error == null && response.Result.HasValue)
|
||||
{
|
||||
var command = JSCommandFactory.CreateCommandFromJson(response.Result.Value, connection);
|
||||
if (command is IPage page)
|
||||
{
|
||||
resolvedPage = page;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to resolve page '{pageId}': {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return new JSGoToPageCommandResult
|
||||
{
|
||||
Kind = CommandResultKind.GoToPage,
|
||||
Args = new GoToPageArgs { PageId = pageId, NavigationMode = navMode },
|
||||
Page = resolvedPage,
|
||||
};
|
||||
}
|
||||
|
||||
private static ICommandResult ParseShowToast(JsonElement args)
|
||||
{
|
||||
string? message = null;
|
||||
if ((args.TryGetProperty("Message", out var messageProp) || args.TryGetProperty("message", out messageProp)) &&
|
||||
messageProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
message = messageProp.GetString();
|
||||
}
|
||||
|
||||
return CommandResult.ShowToast(new ToastArgs { Message = message });
|
||||
}
|
||||
|
||||
private static ICommandResult ParseConfirm(JsonElement args, JsonRpcConnection? connection)
|
||||
{
|
||||
string? title = null;
|
||||
string? description = null;
|
||||
ICommand? primaryCommand = null;
|
||||
var isCritical = false;
|
||||
|
||||
if ((args.TryGetProperty("Title", out var titleProp) || args.TryGetProperty("title", out titleProp)) &&
|
||||
titleProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
title = titleProp.GetString();
|
||||
}
|
||||
|
||||
if ((args.TryGetProperty("Description", out var descProp) || args.TryGetProperty("description", out descProp)) &&
|
||||
descProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
description = descProp.GetString();
|
||||
}
|
||||
|
||||
if (args.TryGetProperty("IsPrimaryCommandCritical", out var criticalProp) ||
|
||||
args.TryGetProperty("isPrimaryCommandCritical", out criticalProp))
|
||||
{
|
||||
isCritical = criticalProp.ValueKind == JsonValueKind.True;
|
||||
}
|
||||
|
||||
if (connection != null &&
|
||||
(args.TryGetProperty("PrimaryCommand", out var cmdProp) || args.TryGetProperty("primaryCommand", out cmdProp)) &&
|
||||
cmdProp.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
primaryCommand = new JSCommandAdapter(cmdProp, connection);
|
||||
}
|
||||
|
||||
return CommandResult.Confirm(new ConfirmationArgs
|
||||
{
|
||||
Title = title,
|
||||
Description = description,
|
||||
PrimaryCommand = primaryCommand,
|
||||
IsPrimaryCommandCritical = isCritical,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom ICommandResult for GoToPage that carries a pre-resolved IPage reference.
|
||||
/// This allows UnsafeHandleCommandResult to navigate directly to the page
|
||||
/// without needing access to the JsonRpcConnection.
|
||||
/// </summary>
|
||||
internal sealed class JSGoToPageCommandResult : ICommandResult
|
||||
{
|
||||
public CommandResultKind Kind { get; init; }
|
||||
|
||||
public ICommandResultArgs? Args { get; init; }
|
||||
|
||||
public IPage? Page { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON content page data to IContentPage interface.
|
||||
/// </summary>
|
||||
internal sealed class JSContentPageProxy : IContentPage
|
||||
{
|
||||
private readonly string _pageId;
|
||||
private readonly JsonElement _data;
|
||||
private readonly JsonRpcConnection _connection;
|
||||
|
||||
public JSContentPageProxy(string pageId, JsonElement data, JsonRpcConnection connection)
|
||||
{
|
||||
_pageId = pageId;
|
||||
_data = data;
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public JSContentPageProxy(string pageId, JsonRpcConnection connection)
|
||||
{
|
||||
_pageId = pageId;
|
||||
_data = default;
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public string Name => GetStringProperty("name") ?? string.Empty;
|
||||
|
||||
public string Id => _pageId;
|
||||
|
||||
public IIconInfo Icon => GetIconInfo();
|
||||
|
||||
public string Title => GetStringProperty("title") ?? string.Empty;
|
||||
|
||||
public bool IsLoading
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("isLoading", out var prop))
|
||||
{
|
||||
return prop.ValueKind == JsonValueKind.True;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public OptionalColor AccentColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("accentColor", out var prop))
|
||||
{
|
||||
return JSCommandResultAdapter.ParseOptionalColor(prop);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public IDetails Details
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("details", out var detailsProp) && detailsProp.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
return new JSDetailsAdapter(detailsProp);
|
||||
}
|
||||
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
|
||||
public IContextItem[] Commands
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("commands", out var commandsProp) && commandsProp.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var count = commandsProp.GetArrayLength();
|
||||
var items = new IContextItem[count];
|
||||
var i = 0;
|
||||
foreach (var item in commandsProp.EnumerateArray())
|
||||
{
|
||||
items[i++] = new JSContextItemAdapter(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public IContent[] GetContent()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _connection.SendRequestAsync(
|
||||
"contentPage/getContent",
|
||||
new JsonObject { ["pageId"] = _pageId },
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (response.Error != null)
|
||||
{
|
||||
Logger.LogWarning($"Content page getContent error: {response.Error.Message}");
|
||||
return [];
|
||||
}
|
||||
|
||||
var resultElement = response.Result;
|
||||
if (resultElement.HasValue &&
|
||||
resultElement.Value.ValueKind == JsonValueKind.Object &&
|
||||
resultElement.Value.TryGetProperty("content", out var contentArray))
|
||||
{
|
||||
resultElement = contentArray;
|
||||
}
|
||||
|
||||
return JSCommandResultAdapter.ParseContentArray(resultElement, _pageId, _connection);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Failed to get content for page {_pageId}: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
private string? GetStringProperty(string name)
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty(name, out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return prop.GetString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IIconInfo GetIconInfo()
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("icon", out var iconProp))
|
||||
{
|
||||
return JSIconInfoAdapter.FromJson(iconProp);
|
||||
}
|
||||
|
||||
return new IconInfo(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON markdown content data to IMarkdownContent interface.
|
||||
/// </summary>
|
||||
internal sealed class JSMarkdownContentAdapter : IMarkdownContent
|
||||
{
|
||||
private readonly JsonElement _data;
|
||||
|
||||
public JSMarkdownContentAdapter(JsonElement data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public string Body
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("body", out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return prop.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON form content data to IFormContent interface.
|
||||
/// </summary>
|
||||
internal sealed class JSFormContentProxy : IFormContent
|
||||
{
|
||||
private readonly string _pageId;
|
||||
private readonly JsonElement _data;
|
||||
private readonly JsonRpcConnection _connection;
|
||||
|
||||
public JSFormContentProxy(string pageId, JsonElement data, JsonRpcConnection connection)
|
||||
{
|
||||
_pageId = pageId;
|
||||
_data = data;
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public string TemplateJson
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("templateJson", out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return prop.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public string DataJson
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("dataJson", out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return prop.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public string StateJson
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("stateJson", out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return prop.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public ICommandResult SubmitForm(string inputs, string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _connection.SendRequestAsync(
|
||||
"form/submit",
|
||||
new JsonObject { ["pageId"] = _pageId, ["inputs"] = inputs, ["data"] = data },
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (response.Error != null)
|
||||
{
|
||||
Logger.LogWarning($"Form submit error: {response.Error.Message}");
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
return JSCommandResultAdapter.ParseCommandResult(response.Result, _connection);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Failed to submit form for page {_pageId}: {ex.Message}");
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON tree content data to ITreeContent interface.
|
||||
/// </summary>
|
||||
internal sealed class JSTreeContentAdapter : ITreeContent
|
||||
{
|
||||
private readonly JsonElement _data;
|
||||
private readonly string _pageId;
|
||||
private readonly JsonRpcConnection _connection;
|
||||
|
||||
public JSTreeContentAdapter(JsonElement data, string pageId, JsonRpcConnection connection)
|
||||
{
|
||||
_data = data;
|
||||
_pageId = pageId;
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public IContent RootContent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("rootContent", out var rootProp) && rootProp.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
return JSCommandResultAdapter.ParseContentItem(rootProp, _pageId, _connection);
|
||||
}
|
||||
|
||||
return new JSMarkdownContentAdapter(default);
|
||||
}
|
||||
}
|
||||
|
||||
public IContent[] GetChildren()
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("children", out var childrenProp) && childrenProp.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var count = childrenProp.GetArrayLength();
|
||||
var items = new IContent[count];
|
||||
var i = 0;
|
||||
foreach (var child in childrenProp.EnumerateArray())
|
||||
{
|
||||
items[i++] = JSCommandResultAdapter.ParseContentItem(child, _pageId, _connection);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON context item data to IContextItem marker interface.
|
||||
/// </summary>
|
||||
internal sealed class JSContextItemAdapter : IContextItem
|
||||
{
|
||||
private readonly JsonElement _data;
|
||||
|
||||
public JSContextItemAdapter(JsonElement data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON plain text content data to IPlainTextContent interface.
|
||||
/// </summary>
|
||||
internal sealed class JSPlainTextContentAdapter : IPlainTextContent
|
||||
{
|
||||
private readonly JsonElement _data;
|
||||
|
||||
public JSPlainTextContentAdapter(JsonElement data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("text", out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return prop.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public FontFamily FontFamily
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("fontFamily", out var prop) && prop.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var value = prop.GetString();
|
||||
return value == "monospace" ? FontFamily.Monospace : FontFamily.UserInterface;
|
||||
}
|
||||
|
||||
return FontFamily.UserInterface;
|
||||
}
|
||||
}
|
||||
|
||||
public bool WrapWords
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("wrapWords", out var prop))
|
||||
{
|
||||
return prop.ValueKind == JsonValueKind.True;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapts JSON image content data to IImageContent interface.
|
||||
/// </summary>
|
||||
internal sealed class JSImageContentAdapter : IImageContent
|
||||
{
|
||||
private readonly JsonElement _data;
|
||||
|
||||
public JSImageContentAdapter(JsonElement data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public IIconInfo Image
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("image", out var imageProp))
|
||||
{
|
||||
return JSIconInfoAdapter.FromJson(imageProp);
|
||||
}
|
||||
|
||||
return new IconInfo(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("maxWidth", out var prop) && prop.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
return prop.GetInt32();
|
||||
}
|
||||
|
||||
return -1; // Unlimited
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.ValueKind == JsonValueKind.Object &&
|
||||
_data.TryGetProperty("maxHeight", out var prop) && prop.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
return prop.GetInt32();
|
||||
}
|
||||
|
||||
return -1; // Unlimited
|
||||
}
|
||||
}
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type - manifest and related types grouped together
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the resolved extension manifest, built from a package.json file
|
||||
/// that contains a "cmdpal" section (similar to VS Code's contributes field).
|
||||
/// </summary>
|
||||
public sealed record JSExtensionManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the internal identifier for the extension (from package.json "name", required).
|
||||
/// </summary>
|
||||
public string? Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name shown to users (from cmdpal.displayName).
|
||||
/// </summary>
|
||||
public string? DisplayName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version string (from package.json "version").
|
||||
/// </summary>
|
||||
public string? Version { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the extension (from package.json "description").
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon glyph or relative path (from cmdpal.icon).
|
||||
/// </summary>
|
||||
public string? Icon { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entry point script path (cmdpal.main overrides package.json "main", required).
|
||||
/// </summary>
|
||||
public string? Main { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the publisher or author name (from cmdpal.publisher).
|
||||
/// </summary>
|
||||
public string? Publisher { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether debug mode is enabled for this extension.
|
||||
/// When true, the Node.js process is started with --inspect to allow debugger attachment.
|
||||
/// </summary>
|
||||
public bool Debug { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port number for the Node.js inspector when debug mode is enabled.
|
||||
/// If not specified, a default port starting at 9229 is assigned automatically.
|
||||
/// </summary>
|
||||
public int? DebugPort { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the engine requirements (from package.json "engines").
|
||||
/// </summary>
|
||||
public JSExtensionEngines? Engines { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the capabilities exposed by the extension (from cmdpal.capabilities).
|
||||
/// </summary>
|
||||
public string[]? Capabilities { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Validates that required fields are present.
|
||||
/// </summary>
|
||||
/// <returns>True if the manifest is valid; otherwise, false.</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(Name) && !string.IsNullOrWhiteSpace(Main);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads an extension manifest from a package.json file that contains a "cmdpal" section.
|
||||
/// </summary>
|
||||
/// <param name="packageJsonPath">The full path to the package.json file.</param>
|
||||
/// <returns>The resolved manifest if successful and valid; otherwise, null.</returns>
|
||||
public static async Task<JSExtensionManifest?> LoadFromFileAsync(string packageJsonPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(packageJsonPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!File.Exists(packageJsonPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead(packageJsonPath);
|
||||
var packageJson = await JsonSerializer.DeserializeAsync(
|
||||
stream,
|
||||
JSExtensionManifestJsonContext.Default.JSPackageJson);
|
||||
|
||||
if (packageJson?.CmdPal is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var cmdpal = packageJson.CmdPal;
|
||||
|
||||
// cmdpal.main overrides top-level main
|
||||
var entryPoint = !string.IsNullOrWhiteSpace(cmdpal.Main)
|
||||
? cmdpal.Main
|
||||
: packageJson.Main;
|
||||
|
||||
var manifest = new JSExtensionManifest
|
||||
{
|
||||
Name = packageJson.Name,
|
||||
DisplayName = cmdpal.DisplayName,
|
||||
Version = packageJson.Version,
|
||||
Description = packageJson.Description,
|
||||
Icon = cmdpal.Icon,
|
||||
Main = entryPoint,
|
||||
Publisher = cmdpal.Publisher,
|
||||
Debug = cmdpal.Debug,
|
||||
DebugPort = cmdpal.DebugPort,
|
||||
Engines = packageJson.Engines,
|
||||
Capabilities = cmdpal.Capabilities,
|
||||
};
|
||||
|
||||
if (!manifest.IsValid())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return manifest;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the top-level package.json structure for extension discovery.
|
||||
/// </summary>
|
||||
public sealed record JSPackageJson
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; init; }
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("main")]
|
||||
public string? Main { get; init; }
|
||||
|
||||
[JsonPropertyName("engines")]
|
||||
public JSExtensionEngines? Engines { get; init; }
|
||||
|
||||
[JsonPropertyName("cmdpal")]
|
||||
public JSCmdPalSection? CmdPal { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the "cmdpal" section within package.json containing CmdPal-specific metadata.
|
||||
/// </summary>
|
||||
public sealed record JSCmdPalSection
|
||||
{
|
||||
[JsonPropertyName("displayName")]
|
||||
public string? DisplayName { get; init; }
|
||||
|
||||
[JsonPropertyName("icon")]
|
||||
public string? Icon { get; init; }
|
||||
|
||||
[JsonPropertyName("main")]
|
||||
public string? Main { get; init; }
|
||||
|
||||
[JsonPropertyName("publisher")]
|
||||
public string? Publisher { get; init; }
|
||||
|
||||
[JsonPropertyName("debug")]
|
||||
public bool Debug { get; init; }
|
||||
|
||||
[JsonPropertyName("debugPort")]
|
||||
public int? DebugPort { get; init; }
|
||||
|
||||
[JsonPropertyName("capabilities")]
|
||||
public string[]? Capabilities { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the engine requirements for a JavaScript/TypeScript extension.
|
||||
/// </summary>
|
||||
public sealed record JSExtensionEngines
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Node.js version requirement (e.g., ">=18").
|
||||
/// </summary>
|
||||
[JsonPropertyName("node")]
|
||||
public string? Node { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON serialization context for extension manifest types.
|
||||
/// </summary>
|
||||
[JsonSerializable(typeof(JSPackageJson))]
|
||||
[JsonSerializable(typeof(JSCmdPalSection))]
|
||||
[JsonSerializable(typeof(JSExtensionManifest))]
|
||||
[JsonSerializable(typeof(JSExtensionEngines))]
|
||||
[JsonSerializable(typeof(string[]))]
|
||||
[JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)]
|
||||
internal sealed partial class JSExtensionManifestJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services.JsonRpc;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Adapter that manages a single Node.js extension process and presents it as an IExtensionWrapper to the CmdPal host.
|
||||
/// </summary>
|
||||
public sealed class JSExtensionWrapper : IExtensionWrapper, IDisposable
|
||||
{
|
||||
private readonly JSExtensionManifest _manifest;
|
||||
private readonly string _manifestDirectory;
|
||||
private readonly Lock _lock = new();
|
||||
private readonly List<ProviderType> _providerTypes = [];
|
||||
|
||||
private static int _nextDebugPort = 9229;
|
||||
|
||||
private Process? _nodeProcess;
|
||||
private JsonRpcConnection? _rpcConnection;
|
||||
private JSCommandProviderProxy? _commandProviderProxy;
|
||||
private bool _isDisposed;
|
||||
private int _consecutiveCrashCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of times this extension has been restarted (due to crashes or hot-reload).
|
||||
/// </summary>
|
||||
public int RestartCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the extension is considered healthy.
|
||||
/// An extension becomes unhealthy after exceeding 3 consecutive crashes.
|
||||
/// </summary>
|
||||
public bool IsHealthy { get; private set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JSExtensionWrapper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="manifest">The parsed extension manifest.</param>
|
||||
/// <param name="manifestDirectory">The directory containing the manifest file.</param>
|
||||
public JSExtensionWrapper(JSExtensionManifest manifest, string manifestDirectory)
|
||||
{
|
||||
_manifest = manifest ?? throw new ArgumentNullException(nameof(manifest));
|
||||
_manifestDirectory = manifestDirectory ?? throw new ArgumentNullException(nameof(manifestDirectory));
|
||||
|
||||
if (!_manifest.IsValid())
|
||||
{
|
||||
throw new ArgumentException("Invalid manifest", nameof(manifest));
|
||||
}
|
||||
|
||||
// Map manifest capabilities to provider types
|
||||
var caps = _manifest.Capabilities;
|
||||
if (caps != null)
|
||||
{
|
||||
foreach (var cap in caps)
|
||||
{
|
||||
if (string.Equals(cap, "commands", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AddProviderType(ProviderType.Commands);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default: assume commands capability if not specified
|
||||
AddProviderType(ProviderType.Commands);
|
||||
}
|
||||
}
|
||||
|
||||
public string PackageDisplayName => _manifest.DisplayName ?? _manifest.Name ?? "Unknown";
|
||||
|
||||
public string ExtensionDisplayName => _manifest.DisplayName ?? _manifest.Name ?? "Unknown";
|
||||
|
||||
public string PackageFullName => $"js!{_manifest.Name}";
|
||||
|
||||
public string PackageFamilyName => $"js!{_manifest.Name}";
|
||||
|
||||
public string Publisher => _manifest.Publisher ?? "Unknown";
|
||||
|
||||
public string ExtensionClassId
|
||||
{
|
||||
get
|
||||
{
|
||||
// Generate a deterministic "GUID-like" identifier from the manifest name
|
||||
if (string.IsNullOrWhiteSpace(_manifest.Name))
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(_manifest.Name));
|
||||
var hashString = Convert.ToHexString(hash);
|
||||
return $"js-{hashString[..32]}";
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset InstalledDate
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
var manifestPath = Path.Combine(_manifestDirectory, "package.json");
|
||||
if (File.Exists(manifestPath))
|
||||
{
|
||||
return File.GetCreationTimeUtc(manifestPath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback if file operations fail
|
||||
}
|
||||
|
||||
return DateTimeOffset.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public PackageVersion Version
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_manifest.Version))
|
||||
{
|
||||
return new PackageVersion { Major = 1, Minor = 0, Build = 0, Revision = 0 };
|
||||
}
|
||||
|
||||
var parts = _manifest.Version.Split('.');
|
||||
return new PackageVersion
|
||||
{
|
||||
Major = parts.Length > 0 && ushort.TryParse(parts[0], out var major) ? major : (ushort)1,
|
||||
Minor = parts.Length > 1 && ushort.TryParse(parts[1], out var minor) ? minor : (ushort)0,
|
||||
Build = parts.Length > 2 && ushort.TryParse(parts[2], out var build) ? build : (ushort)0,
|
||||
Revision = 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string ExtensionUniqueId => $"js!{_manifest.Name}";
|
||||
|
||||
public bool IsRunning()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_nodeProcess == null || _rpcConnection == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return !_nodeProcess.HasExited;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartExtensionAsync()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_isDisposed, this);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_nodeProcess != null && _rpcConnection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_nodeProcess.HasExited)
|
||||
{
|
||||
return; // Already running
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Process handle invalid — fall through to restart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogDebug($"Starting JS extension {_manifest.DisplayName ?? _manifest.Name}");
|
||||
|
||||
try
|
||||
{
|
||||
var entryPoint = Path.Combine(_manifestDirectory, _manifest.Main ?? string.Empty);
|
||||
|
||||
// Retry up to 5 times with 1s backoff — the entry point file may
|
||||
// still be in flight when the directory watcher fires.
|
||||
for (var attempt = 0; attempt < 5; attempt++)
|
||||
{
|
||||
if (File.Exists(entryPoint))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (attempt == 4)
|
||||
{
|
||||
Logger.LogError($"Entry point not found after retries: {entryPoint}");
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "node",
|
||||
Arguments = BuildNodeArguments(entryPoint),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = _manifestDirectory,
|
||||
};
|
||||
|
||||
var nodeProcess = Process.Start(psi);
|
||||
if (nodeProcess == null)
|
||||
{
|
||||
Logger.LogError($"Failed to start Node.js process for {_manifest.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Drain stderr asynchronously to prevent the process from blocking
|
||||
// if the stderr pipe buffer fills up
|
||||
nodeProcess.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
Logger.LogDebug($"[{_manifest.Name} stderr] {e.Data}");
|
||||
}
|
||||
};
|
||||
nodeProcess.BeginErrorReadLine();
|
||||
|
||||
var loggerAdapter = new LoggerAdapter();
|
||||
var rpcConnection = new JsonRpcConnection(nodeProcess, loggerAdapter);
|
||||
rpcConnection.OnError += ex => Logger.LogError($"JSON-RPC error in {_manifest.Name}: {ex.Message}");
|
||||
rpcConnection.OnDisconnected += HandleDisconnection;
|
||||
|
||||
// Store the process and connection BEFORE awaiting initialize so
|
||||
// IsRunning() reflects the actual process state immediately.
|
||||
lock (_lock)
|
||||
{
|
||||
_nodeProcess = nodeProcess;
|
||||
_rpcConnection = rpcConnection;
|
||||
}
|
||||
|
||||
nodeProcess.Exited += (_, _) => HandleDisconnection();
|
||||
nodeProcess.EnableRaisingEvents = true;
|
||||
|
||||
rpcConnection.StartListening();
|
||||
|
||||
// Send initialize request to the extension
|
||||
var initResponse = await rpcConnection.SendRequestAsync(
|
||||
"initialize",
|
||||
new JsonObject { ["extensionId"] = _manifest.Name },
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
if (initResponse.Error != null)
|
||||
{
|
||||
Logger.LogError($"Initialization failed for {_manifest.Name}: {initResponse.Error.Message}");
|
||||
SignalDispose();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extension started and initialized successfully — reset crash tracking
|
||||
ResetCrashCount();
|
||||
|
||||
Logger.LogInfo($"Successfully started JS extension {_manifest.DisplayName ?? _manifest.Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to start JS extension {_manifest.Name}: {ex.Message}");
|
||||
SignalDispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void SignalDispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (_rpcConnection != null && IsRunning())
|
||||
{
|
||||
// Send dispose notification (fire-and-forget)
|
||||
_rpcConnection.SendNotificationAsync("dispose", null, CancellationToken.None).Wait(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error sending dispose notification to {_manifest.Name}: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_rpcConnection?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_nodeProcess != null && !_nodeProcess.HasExited)
|
||||
{
|
||||
_nodeProcess.Kill(entireProcessTree: true);
|
||||
_nodeProcess.WaitForExit(2000);
|
||||
}
|
||||
|
||||
_nodeProcess?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error terminating Node.js process for {_manifest.Name}: {ex.Message}");
|
||||
}
|
||||
|
||||
_nodeProcess = null;
|
||||
_rpcConnection = null;
|
||||
_commandProviderProxy = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SignalDispose();
|
||||
}
|
||||
|
||||
public IExtension? GetExtensionObject()
|
||||
{
|
||||
// JS extensions don't have COM objects - the wrapper itself is the bridge
|
||||
return null;
|
||||
}
|
||||
|
||||
public void AddProviderType(ProviderType providerType)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_providerTypes.Contains(providerType))
|
||||
{
|
||||
_providerTypes.Add(providerType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasProviderType(ProviderType providerType)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _providerTypes.Contains(providerType);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T?> GetProviderAsync<T>()
|
||||
where T : class
|
||||
{
|
||||
if (typeof(T) != typeof(ICommandProvider))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await StartExtensionAsync().ConfigureAwait(false);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!IsRunning() || _rpcConnection == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_commandProviderProxy == null)
|
||||
{
|
||||
_commandProviderProxy = new JSCommandProviderProxy(_rpcConnection, _manifest);
|
||||
}
|
||||
|
||||
return _commandProviderProxy as T;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<T>> GetListOfProvidersAsync<T>()
|
||||
where T : class
|
||||
{
|
||||
var provider = await GetProviderAsync<T>().ConfigureAwait(false);
|
||||
if (provider != null)
|
||||
{
|
||||
return new[] { provider };
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private void HandleDisconnection()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
_consecutiveCrashCount++;
|
||||
Logger.LogWarning($"Node.js process for {_manifest.Name} disconnected unexpectedly (crash #{_consecutiveCrashCount})");
|
||||
|
||||
if (_consecutiveCrashCount > 3)
|
||||
{
|
||||
IsHealthy = false;
|
||||
Logger.LogError($"JS extension {_manifest.Name} disabled after {_consecutiveCrashCount} consecutive crashes");
|
||||
}
|
||||
|
||||
_nodeProcess = null;
|
||||
_rpcConnection = null;
|
||||
_commandProviderProxy = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the consecutive crash counter. Call after a successful operation to indicate the extension is stable.
|
||||
/// </summary>
|
||||
internal void ResetCrashCount()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_consecutiveCrashCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the running Node.js process and starts a fresh one.
|
||||
/// Used by hot-reload to restart the extension after source file changes.
|
||||
/// </summary>
|
||||
internal async Task RestartAsync()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isDisposed || !IsHealthy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Restarting JS extension {_manifest.DisplayName ?? _manifest.Name}");
|
||||
|
||||
// Gracefully shut down the current process
|
||||
lock (_lock)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_rpcConnection != null && IsRunning())
|
||||
{
|
||||
_rpcConnection.SendNotificationAsync("dispose", null, CancellationToken.None).Wait(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error sending dispose during restart of {_manifest.Name}: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_rpcConnection?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_nodeProcess != null && !_nodeProcess.HasExited)
|
||||
{
|
||||
_nodeProcess.Kill(entireProcessTree: true);
|
||||
_nodeProcess.WaitForExit(2000);
|
||||
}
|
||||
|
||||
_nodeProcess?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Error terminating Node.js process during restart of {_manifest.Name}: {ex.Message}");
|
||||
}
|
||||
|
||||
_nodeProcess = null;
|
||||
_rpcConnection = null;
|
||||
_commandProviderProxy = null;
|
||||
_isDisposed = false;
|
||||
}
|
||||
|
||||
RestartCount++;
|
||||
await StartExtensionAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the manifest directory path for this extension.
|
||||
/// </summary>
|
||||
internal string ManifestDirectory => _manifestDirectory;
|
||||
|
||||
private string BuildNodeArguments(string entryPoint)
|
||||
{
|
||||
if (_manifest.Debug)
|
||||
{
|
||||
var port = _manifest.DebugPort ?? Interlocked.Increment(ref _nextDebugPort);
|
||||
var debugUrl = $"chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:{port}";
|
||||
Logger.LogInfo($"Debug mode enabled for {_manifest.Name} on port {port}. Attach debugger at: {debugUrl}");
|
||||
return $"--inspect={port} \"{entryPoint}\"";
|
||||
}
|
||||
|
||||
return $"\"{entryPoint}\"";
|
||||
}
|
||||
|
||||
private sealed class LoggerAdapter : Microsoft.Extensions.Logging.ILogger
|
||||
{
|
||||
public IDisposable? BeginScope<TState>(TState state)
|
||||
where TState : notnull => null;
|
||||
|
||||
public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) => true;
|
||||
|
||||
public void Log<TState>(
|
||||
Microsoft.Extensions.Logging.LogLevel logLevel,
|
||||
Microsoft.Extensions.Logging.EventId eventId,
|
||||
TState state,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
var message = formatter(state, exception);
|
||||
switch (logLevel)
|
||||
{
|
||||
case Microsoft.Extensions.Logging.LogLevel.Error:
|
||||
case Microsoft.Extensions.Logging.LogLevel.Critical:
|
||||
Logger.LogError(message);
|
||||
break;
|
||||
case Microsoft.Extensions.Logging.LogLevel.Warning:
|
||||
Logger.LogWarning(message);
|
||||
break;
|
||||
case Microsoft.Extensions.Logging.LogLevel.Information:
|
||||
Logger.LogInfo(message);
|
||||
break;
|
||||
default:
|
||||
Logger.LogDebug(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -120,7 +120,7 @@ public partial class ProviderSettingsViewModel : ObservableObject
|
||||
ProviderSettings = s.ProviderSettings.SetItem(_provider.ProviderId, newSettings),
|
||||
});
|
||||
_providerSettings = newSettings;
|
||||
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
|
||||
WeakReferenceMessenger.Default.Send<ProviderEnabledStateChangedMessage>(new(_provider.ProviderId, value));
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
OnPropertyChanged(nameof(ExtensionSubtext));
|
||||
OnPropertyChanged(nameof(Icon));
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Extension service that manages in-process built-in command providers
|
||||
/// registered in the DI container as <see cref="ICommandProvider"/>.
|
||||
/// </summary>
|
||||
public sealed class BuiltInExtensionService : IExtensionService
|
||||
{
|
||||
private readonly IEnumerable<ICommandProvider> _commandProviders;
|
||||
private readonly TaskScheduler _taskScheduler;
|
||||
private readonly List<IExtensionWrapper> _wrappers = [];
|
||||
|
||||
#pragma warning disable CS0067 // Events are required by the interface but not raised by this implementation
|
||||
public event TypedEventHandler<IExtensionService, IEnumerable<CommandProviderWrapper>>? OnProviderAdded;
|
||||
|
||||
public event TypedEventHandler<IExtensionService, IEnumerable<CommandProviderWrapper>>? OnProviderRemoved;
|
||||
#pragma warning restore CS0067
|
||||
|
||||
public BuiltInExtensionService(IEnumerable<ICommandProvider> commandProviders, TaskScheduler taskScheduler)
|
||||
{
|
||||
_commandProviders = commandProviders;
|
||||
_taskScheduler = taskScheduler;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<CommandProviderWrapper>> LoadProvidersAsync(CancellationToken ct)
|
||||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<CommandProviderWrapper>>([]);
|
||||
}
|
||||
|
||||
var wrappers = new List<CommandProviderWrapper>();
|
||||
foreach (var provider in _commandProviders)
|
||||
{
|
||||
wrappers.Add(new CommandProviderWrapper(provider, _taskScheduler));
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<CommandProviderWrapper>>(wrappers);
|
||||
}
|
||||
|
||||
public Task SignalStopAsync()
|
||||
{
|
||||
// Built-in providers are in-proc and don't need explicit stop signaling.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<IExtensionWrapper>>(_wrappers);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<IExtensionWrapper>> RefreshInstalledExtensionsAsync(bool includeDisabledExtensions = false)
|
||||
{
|
||||
// Built-in set is fixed at startup; refresh is a no-op.
|
||||
return GetInstalledExtensionsAsync(includeDisabledExtensions);
|
||||
}
|
||||
|
||||
public IExtensionWrapper? GetInstalledExtension(string extensionUniqueId)
|
||||
{
|
||||
return _wrappers.FirstOrDefault(w => w.ExtensionUniqueId == extensionUniqueId);
|
||||
}
|
||||
|
||||
public void EnableExtension(string extensionUniqueId)
|
||||
{
|
||||
// Nothing to do here. We're built-in extensions.
|
||||
}
|
||||
|
||||
public void DisableExtension(string extensionUniqueId)
|
||||
{
|
||||
// Nothing to do here. We're built-in extensions.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the outcome of attempting to start a single WinRT extension.
|
||||
/// </summary>
|
||||
internal sealed class ExtensionStartResult
|
||||
{
|
||||
public IExtensionWrapper Extension { get; }
|
||||
|
||||
public CommandProviderWrapper? Wrapper { get; private init; }
|
||||
|
||||
public Task? PendingStartTask { get; private init; }
|
||||
|
||||
public Stopwatch? Stopwatch { get; private init; }
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, nameof(Wrapper))]
|
||||
public bool IsStarted => Wrapper is not null;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, nameof(PendingStartTask), nameof(Stopwatch))]
|
||||
public bool IsTimedOut => PendingStartTask is not null;
|
||||
|
||||
private ExtensionStartResult(IExtensionWrapper extension)
|
||||
{
|
||||
Extension = extension;
|
||||
}
|
||||
|
||||
public static ExtensionStartResult Started(IExtensionWrapper extension, CommandProviderWrapper wrapper)
|
||||
{
|
||||
return new ExtensionStartResult(extension) { Wrapper = wrapper };
|
||||
}
|
||||
|
||||
public static ExtensionStartResult TimedOut(IExtensionWrapper extension, Task pendingStartTask, Stopwatch sw)
|
||||
{
|
||||
return new ExtensionStartResult(extension) { PendingStartTask = pendingStartTask, Stopwatch = sw };
|
||||
}
|
||||
|
||||
public static ExtensionStartResult Failed(IExtensionWrapper extension)
|
||||
{
|
||||
return new ExtensionStartResult(extension);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
public interface IExtensionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads command providers managed by this service. Returns providers that
|
||||
/// are immediately ready. Slow or late providers arrive via <see cref="OnProviderAdded"/>.
|
||||
/// </summary>
|
||||
/// <param name="ct">Cancellation token owned by the caller to cancel in-flight loading.</param>
|
||||
/// <returns>Command provider wrappers that are started and ready for command loading.</returns>
|
||||
Task<IEnumerable<CommandProviderWrapper>> LoadProvidersAsync(CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Signals running providers managed by this service to stop/dispose.
|
||||
/// </summary>
|
||||
Task SignalStopAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently cached installed extensions managed by this service.
|
||||
/// </summary>
|
||||
/// <param name="includeDisabledExtensions">True to include disabled extensions in the result.</param>
|
||||
/// <returns>A sequence of installed extensions from the current in-memory cache.</returns>
|
||||
Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false);
|
||||
|
||||
/// <summary>
|
||||
/// Forces a fresh scan of installed extensions and updates the in-memory cache.
|
||||
/// </summary>
|
||||
/// <param name="includeDisabledExtensions">True to include disabled extensions in the result.</param>
|
||||
/// <returns>A sequence of installed extensions after the cache has been rebuilt.</returns>
|
||||
Task<IEnumerable<IExtensionWrapper>> RefreshInstalledExtensionsAsync(bool includeDisabledExtensions = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached installed extension by its unique id.
|
||||
/// </summary>
|
||||
/// <param name="extensionUniqueId">The unique id of the extension to look up.</param>
|
||||
/// <returns>The cached extension if found; otherwise, null.</returns>
|
||||
IExtensionWrapper? GetInstalledExtension(string extensionUniqueId);
|
||||
|
||||
/// <summary>
|
||||
/// Enables an installed extension by unique id.
|
||||
/// </summary>
|
||||
/// <param name="extensionUniqueId">The unique id of the extension to enable.</param>
|
||||
void EnableExtension(string extensionUniqueId);
|
||||
|
||||
/// <summary>
|
||||
/// Disables an installed extension by unique id.
|
||||
/// </summary>
|
||||
/// <param name="extensionUniqueId">The unique id of the extension to disable.</param>
|
||||
void DisableExtension(string extensionUniqueId);
|
||||
|
||||
/// <summary>
|
||||
/// Raised when one or more providers become available (late start, new package install, etc.).
|
||||
/// </summary>
|
||||
event TypedEventHandler<IExtensionService, IEnumerable<CommandProviderWrapper>>? OnProviderAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when one or more providers are removed (package uninstall, etc.).
|
||||
/// </summary>
|
||||
event TypedEventHandler<IExtensionService, IEnumerable<CommandProviderWrapper>>? OnProviderRemoved;
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Extension service that manages JavaScript/TypeScript extensions running as individual Node.js processes.
|
||||
/// Each extension gets its own process communicating over JSON-RPC 2.0 via stdio with LSP-style framing.
|
||||
/// Supports hot-reload (via FileSystemWatcher), crash recovery, and debug attachment.
|
||||
/// </summary>
|
||||
public sealed class JavaScriptExtensionService : IExtensionService, IDisposable
|
||||
{
|
||||
private static readonly string ExtensionsPath = GetDefaultExtensionsPath();
|
||||
|
||||
private readonly TaskScheduler _taskScheduler;
|
||||
private readonly Lock _extensionsLock = new();
|
||||
private readonly List<JSExtensionWrapper> _extensions = [];
|
||||
private readonly List<CommandProviderWrapper> _providerWrappers = [];
|
||||
private readonly HashSet<string> _disabledExtensions = [];
|
||||
|
||||
private readonly Lock _sourceWatcherLock = new();
|
||||
private readonly Dictionary<string, FileSystemWatcher> _sourceFileWatchers = [];
|
||||
private readonly Dictionary<string, Timer> _debounceTimers = [];
|
||||
|
||||
private FileSystemWatcher? _directoryWatcher;
|
||||
private bool _disposed;
|
||||
|
||||
#pragma warning disable CS0067 // Events are required by the interface but not raised by this implementation yet
|
||||
public event TypedEventHandler<IExtensionService, IEnumerable<CommandProviderWrapper>>? OnProviderAdded;
|
||||
|
||||
public event TypedEventHandler<IExtensionService, IEnumerable<CommandProviderWrapper>>? OnProviderRemoved;
|
||||
#pragma warning restore CS0067
|
||||
|
||||
public JavaScriptExtensionService(TaskScheduler taskScheduler)
|
||||
{
|
||||
_taskScheduler = taskScheduler;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CommandProviderWrapper>> LoadProvidersAsync(CancellationToken ct)
|
||||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
if (!Directory.Exists(ExtensionsPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(ExtensionsPath);
|
||||
Logger.LogDebug($"Created JS extensions directory: {ExtensionsPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to create JS extensions directory {ExtensionsPath}: {ex.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
var wrappers = await DiscoverAndLoadExtensionsAsync(ExtensionsPath, ct).ConfigureAwait(false);
|
||||
StartDirectoryWatcher();
|
||||
|
||||
sw.Stop();
|
||||
Logger.LogInfo($"JavaScriptExtensionService: Loaded {wrappers.Count} extension(s) in {sw.ElapsedMilliseconds} ms");
|
||||
|
||||
return wrappers;
|
||||
}
|
||||
|
||||
public Task SignalStopAsync()
|
||||
{
|
||||
StopDirectoryWatcher();
|
||||
StopAllSourceFileWatchers();
|
||||
|
||||
lock (_extensionsLock)
|
||||
{
|
||||
foreach (var ext in _extensions)
|
||||
{
|
||||
try
|
||||
{
|
||||
ext.SignalDispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to stop JS extension {ext.ExtensionDisplayName}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
_extensions.Clear();
|
||||
_providerWrappers.Clear();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false)
|
||||
{
|
||||
lock (_extensionsLock)
|
||||
{
|
||||
if (includeDisabledExtensions)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<IExtensionWrapper>>(_extensions.ToList());
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<IExtensionWrapper>>(
|
||||
_extensions.Where(e => !_disabledExtensions.Contains(e.ExtensionUniqueId)).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IExtensionWrapper>> RefreshInstalledExtensionsAsync(bool includeDisabledExtensions = false)
|
||||
{
|
||||
// Re-scan the directory for any new extensions
|
||||
if (Directory.Exists(ExtensionsPath))
|
||||
{
|
||||
var subdirs = Directory.GetDirectories(ExtensionsPath);
|
||||
foreach (var subdir in subdirs)
|
||||
{
|
||||
var manifestPath = Path.Combine(subdir, "package.json");
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if already loaded
|
||||
var dirName = Path.GetFileName(subdir);
|
||||
bool alreadyLoaded;
|
||||
lock (_extensionsLock)
|
||||
{
|
||||
alreadyLoaded = _extensions.Any(e => e.ManifestDirectory == subdir);
|
||||
}
|
||||
|
||||
if (!alreadyLoaded)
|
||||
{
|
||||
await LoadExtensionFromDirectoryAsync(subdir, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await GetInstalledExtensionsAsync(includeDisabledExtensions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public IExtensionWrapper? GetInstalledExtension(string extensionUniqueId)
|
||||
{
|
||||
lock (_extensionsLock)
|
||||
{
|
||||
return _extensions.FirstOrDefault(e => e.ExtensionUniqueId == extensionUniqueId);
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableExtension(string extensionUniqueId)
|
||||
{
|
||||
_disabledExtensions.Remove(extensionUniqueId);
|
||||
}
|
||||
|
||||
public void DisableExtension(string extensionUniqueId)
|
||||
{
|
||||
_disabledExtensions.Add(extensionUniqueId);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
StopDirectoryWatcher();
|
||||
StopAllSourceFileWatchers();
|
||||
|
||||
lock (_extensionsLock)
|
||||
{
|
||||
foreach (var ext in _extensions)
|
||||
{
|
||||
ext.Dispose();
|
||||
}
|
||||
|
||||
_extensions.Clear();
|
||||
_providerWrappers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<CommandProviderWrapper>> DiscoverAndLoadExtensionsAsync(string extensionsPath, CancellationToken ct)
|
||||
{
|
||||
var wrappers = new List<CommandProviderWrapper>();
|
||||
|
||||
if (!Directory.Exists(extensionsPath))
|
||||
{
|
||||
return wrappers;
|
||||
}
|
||||
|
||||
var subdirectories = Directory.GetDirectories(extensionsPath);
|
||||
foreach (var subdir in subdirectories)
|
||||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var wrapper = await LoadExtensionFromDirectoryAsync(subdir, ct).ConfigureAwait(false);
|
||||
if (wrapper != null)
|
||||
{
|
||||
wrappers.Add(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
return wrappers;
|
||||
}
|
||||
|
||||
private async Task<CommandProviderWrapper?> LoadExtensionFromDirectoryAsync(string extensionDirectory, CancellationToken ct)
|
||||
{
|
||||
var manifestPath = Path.Combine(extensionDirectory, "package.json");
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var manifest = await JSExtensionManifest.LoadFromFileAsync(manifestPath).ConfigureAwait(false);
|
||||
if (manifest == null)
|
||||
{
|
||||
Logger.LogWarning($"Invalid manifest at {manifestPath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var extensionWrapper = new JSExtensionWrapper(manifest, extensionDirectory);
|
||||
|
||||
lock (_extensionsLock)
|
||||
{
|
||||
_extensions.Add(extensionWrapper);
|
||||
}
|
||||
|
||||
await extensionWrapper.StartExtensionAsync().ConfigureAwait(false);
|
||||
|
||||
if (!extensionWrapper.IsRunning())
|
||||
{
|
||||
Logger.LogError($"Failed to start JS extension {manifest.DisplayName ?? manifest.Name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var provider = await extensionWrapper.GetProviderAsync<ICommandProvider>().ConfigureAwait(false);
|
||||
if (provider == null)
|
||||
{
|
||||
Logger.LogWarning($"JS extension {manifest.DisplayName ?? manifest.Name} does not provide ICommandProvider");
|
||||
return null;
|
||||
}
|
||||
|
||||
var wrapper = new CommandProviderWrapper(extensionWrapper, provider, _taskScheduler);
|
||||
|
||||
lock (_extensionsLock)
|
||||
{
|
||||
_providerWrappers.Add(wrapper);
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Loaded JS extension: {manifest.DisplayName ?? manifest.Name}");
|
||||
|
||||
// Start source file watcher for hot-reload in dev mode
|
||||
StartSourceFileWatcher(extensionWrapper, extensionDirectory);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to load JS extension from {extensionDirectory}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void StartDirectoryWatcher()
|
||||
{
|
||||
if (!Directory.Exists(ExtensionsPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_directoryWatcher = new FileSystemWatcher(ExtensionsPath)
|
||||
{
|
||||
NotifyFilter = NotifyFilters.DirectoryName,
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
|
||||
_directoryWatcher.Created += OnExtensionDirectoryCreated;
|
||||
_directoryWatcher.Deleted += OnExtensionDirectoryDeleted;
|
||||
|
||||
Logger.LogDebug($"Started directory watcher for {ExtensionsPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to start directory watcher for {ExtensionsPath}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void StopDirectoryWatcher()
|
||||
{
|
||||
if (_directoryWatcher != null)
|
||||
{
|
||||
_directoryWatcher.Created -= OnExtensionDirectoryCreated;
|
||||
_directoryWatcher.Deleted -= OnExtensionDirectoryDeleted;
|
||||
_directoryWatcher.Dispose();
|
||||
_directoryWatcher = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExtensionDirectoryCreated(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
// Small delay to let files finish copying
|
||||
await Task.Delay(500).ConfigureAwait(false);
|
||||
var wrapper = await LoadExtensionFromDirectoryAsync(e.FullPath, CancellationToken.None).ConfigureAwait(false);
|
||||
if (wrapper != null)
|
||||
{
|
||||
OnProviderAdded?.Invoke(this, [wrapper]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnExtensionDirectoryDeleted(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await RemoveExtensionByDirectoryAsync(e.FullPath).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
private Task RemoveExtensionByDirectoryAsync(string directoryPath)
|
||||
{
|
||||
JSExtensionWrapper? extensionToRemove = null;
|
||||
CommandProviderWrapper? wrapperToRemove = null;
|
||||
|
||||
lock (_extensionsLock)
|
||||
{
|
||||
extensionToRemove = _extensions.FirstOrDefault(e => e.ManifestDirectory == directoryPath);
|
||||
if (extensionToRemove != null)
|
||||
{
|
||||
_extensions.Remove(extensionToRemove);
|
||||
wrapperToRemove = _providerWrappers.FirstOrDefault(w => w.Extension == extensionToRemove);
|
||||
if (wrapperToRemove != null)
|
||||
{
|
||||
_providerWrappers.Remove(wrapperToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (extensionToRemove != null)
|
||||
{
|
||||
StopSourceFileWatcher(directoryPath);
|
||||
extensionToRemove.SignalDispose();
|
||||
|
||||
if (wrapperToRemove != null)
|
||||
{
|
||||
OnProviderRemoved?.Invoke(this, [wrapperToRemove]);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void StartSourceFileWatcher(JSExtensionWrapper extensionWrapper, string extensionDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
var watcher = new FileSystemWatcher(extensionDirectory, "*.js")
|
||||
{
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName,
|
||||
IncludeSubdirectories = true,
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
|
||||
watcher.Changed += (sender, e) => OnSourceFileChanged(extensionWrapper, extensionDirectory, e);
|
||||
watcher.Created += (sender, e) => OnSourceFileChanged(extensionWrapper, extensionDirectory, e);
|
||||
|
||||
lock (_sourceWatcherLock)
|
||||
{
|
||||
_sourceFileWatchers[extensionDirectory] = watcher;
|
||||
}
|
||||
|
||||
Logger.LogDebug($"Started source file watcher for {extensionWrapper.ExtensionDisplayName} at {extensionDirectory}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to start source file watcher for {extensionWrapper.ExtensionDisplayName}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void StopSourceFileWatcher(string extensionDirectory)
|
||||
{
|
||||
lock (_sourceWatcherLock)
|
||||
{
|
||||
if (_sourceFileWatchers.TryGetValue(extensionDirectory, out var watcher))
|
||||
{
|
||||
watcher.EnableRaisingEvents = false;
|
||||
watcher.Dispose();
|
||||
_sourceFileWatchers.Remove(extensionDirectory);
|
||||
}
|
||||
|
||||
if (_debounceTimers.TryGetValue(extensionDirectory, out var timer))
|
||||
{
|
||||
timer.Dispose();
|
||||
_debounceTimers.Remove(extensionDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StopAllSourceFileWatchers()
|
||||
{
|
||||
lock (_sourceWatcherLock)
|
||||
{
|
||||
foreach (var kvp in _sourceFileWatchers)
|
||||
{
|
||||
kvp.Value.EnableRaisingEvents = false;
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
_sourceFileWatchers.Clear();
|
||||
|
||||
foreach (var kvp in _debounceTimers)
|
||||
{
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
_debounceTimers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSourceFileChanged(JSExtensionWrapper extensionWrapper, string extensionDirectory, FileSystemEventArgs e)
|
||||
{
|
||||
// Skip node_modules changes
|
||||
if (e.FullPath.Contains("node_modules", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_sourceWatcherLock)
|
||||
{
|
||||
if (_debounceTimers.TryGetValue(extensionDirectory, out var existingTimer))
|
||||
{
|
||||
existingTimer.Dispose();
|
||||
}
|
||||
|
||||
_debounceTimers[extensionDirectory] = new Timer(
|
||||
_ => _ = Task.Run(() => RestartExtensionAsync(extensionWrapper)),
|
||||
null,
|
||||
500,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RestartExtensionAsync(JSExtensionWrapper extensionWrapper)
|
||||
{
|
||||
if (!extensionWrapper.IsHealthy)
|
||||
{
|
||||
Logger.LogWarning($"Skipping hot-reload for unhealthy extension {extensionWrapper.ExtensionDisplayName}");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Hot-reload: restarting {extensionWrapper.ExtensionDisplayName}");
|
||||
|
||||
await extensionWrapper.RestartAsync().ConfigureAwait(false);
|
||||
|
||||
if (!extensionWrapper.IsRunning())
|
||||
{
|
||||
Logger.LogError($"Hot-reload failed: {extensionWrapper.ExtensionDisplayName} did not restart");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Hot-reload completed for {extensionWrapper.ExtensionDisplayName}");
|
||||
}
|
||||
|
||||
private static string GetDefaultExtensionsPath()
|
||||
{
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
return Path.Combine(localAppData, "Microsoft", "PowerToys", "CommandPalette", "JSExtensions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma warning disable CA1848 // Use LoggerMessage delegates - this is a low-level protocol class using ILogger directly
|
||||
#pragma warning disable CA1873 // Logging argument evaluation
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services.JsonRpc;
|
||||
|
||||
/// <summary>
|
||||
/// Manages JSON-RPC 2.0 communication with a Node.js child process over stdin/stdout using LSP-style length-prefixed framing.
|
||||
/// </summary>
|
||||
public sealed class JsonRpcConnection : IDisposable
|
||||
{
|
||||
private readonly Process _process;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ConcurrentDictionary<int, TaskCompletionSource<JsonRpcResponse>> _pendingRequests = new();
|
||||
private readonly ConcurrentDictionary<string, Action<JsonElement>> _notificationHandlers = new();
|
||||
private readonly SemaphoreSlim _writeLock = new(1, 1);
|
||||
private readonly Lock _idLock = new();
|
||||
private readonly CancellationTokenSource _disposalCts = new();
|
||||
private readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
private int _nextRequestId = 1;
|
||||
private Task? _readLoopTask;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an error occurs during reading or processing messages.
|
||||
/// </summary>
|
||||
public event Action<Exception>? OnError;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the connection is disconnected (process exit or stream close).
|
||||
/// </summary>
|
||||
public event Action? OnDisconnected;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonRpcConnection"/> class.
|
||||
/// </summary>
|
||||
/// <param name="process">The Node.js child process to communicate with.</param>
|
||||
/// <param name="logger">Logger for diagnostics.</param>
|
||||
public JsonRpcConnection(Process process, ILogger logger)
|
||||
{
|
||||
_process = process ?? throw new ArgumentNullException(nameof(process));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for messages from the process stdout on a background thread.
|
||||
/// </summary>
|
||||
public void StartListening()
|
||||
{
|
||||
if (_readLoopTask != null)
|
||||
{
|
||||
throw new InvalidOperationException("Already listening");
|
||||
}
|
||||
|
||||
_readLoopTask = Task.Run(ReadLoopAsync, _disposalCts.Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a JSON-RPC request and waits for the response.
|
||||
/// </summary>
|
||||
/// <param name="method">The method name to invoke.</param>
|
||||
/// <param name="params">Optional parameters for the method.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>The JSON-RPC response.</returns>
|
||||
public async Task<JsonRpcResponse> SendRequestAsync(string method, JsonNode? @params, CancellationToken ct)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
int id;
|
||||
lock (_idLock)
|
||||
{
|
||||
id = _nextRequestId++;
|
||||
}
|
||||
|
||||
var request = new JsonRpcRequest
|
||||
{
|
||||
Id = id,
|
||||
Method = method,
|
||||
Params = @params,
|
||||
};
|
||||
|
||||
var tcs = new TaskCompletionSource<JsonRpcResponse>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_pendingRequests[id] = tcs;
|
||||
|
||||
try
|
||||
{
|
||||
await SendMessageAsync(request, ct).ConfigureAwait(false);
|
||||
|
||||
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct, _disposalCts.Token);
|
||||
timeoutCts.CancelAfter(_defaultTimeout);
|
||||
|
||||
using (timeoutCts.Token.Register(() => tcs.TrySetCanceled()))
|
||||
{
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_pendingRequests.TryRemove(id, out _);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a JSON-RPC notification (no response expected).
|
||||
/// </summary>
|
||||
/// <param name="method">The method name.</param>
|
||||
/// <param name="params">Optional parameters.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
public async Task SendNotificationAsync(string method, JsonNode? @params, CancellationToken ct)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var notification = new JsonRpcNotification
|
||||
{
|
||||
Method = method,
|
||||
Params = @params,
|
||||
};
|
||||
|
||||
await SendMessageAsync(notification, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a handler for incoming notifications of a specific method.
|
||||
/// </summary>
|
||||
/// <param name="method">The notification method name.</param>
|
||||
/// <param name="handler">The handler to invoke when the notification is received.</param>
|
||||
public void RegisterNotificationHandler(string method, Action<JsonElement> handler)
|
||||
{
|
||||
_notificationHandlers[method] = handler;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
_disposalCts.Cancel();
|
||||
|
||||
try
|
||||
{
|
||||
_readLoopTask?.Wait(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort
|
||||
}
|
||||
|
||||
foreach (var kvp in _pendingRequests)
|
||||
{
|
||||
kvp.Value.TrySetCanceled();
|
||||
}
|
||||
|
||||
_pendingRequests.Clear();
|
||||
_writeLock.Dispose();
|
||||
_disposalCts.Dispose();
|
||||
}
|
||||
|
||||
private async Task SendMessageAsync(object message, CancellationToken ct)
|
||||
{
|
||||
var json = message switch
|
||||
{
|
||||
JsonRpcRequest req => JsonSerializer.Serialize(req, JsonRpcSerializerContext.Default.JsonRpcRequest),
|
||||
JsonRpcNotification notif => JsonSerializer.Serialize(notif, JsonRpcSerializerContext.Default.JsonRpcNotification),
|
||||
_ => throw new ArgumentException("Invalid message type", nameof(message)),
|
||||
};
|
||||
|
||||
var contentBytes = Encoding.UTF8.GetBytes(json);
|
||||
var header = $"Content-Length: {contentBytes.Length}\r\n\r\n";
|
||||
var headerBytes = Encoding.ASCII.GetBytes(header);
|
||||
|
||||
await _writeLock.WaitAsync(ct).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var stdin = _process.StandardInput.BaseStream;
|
||||
await stdin.WriteAsync(headerBytes, ct).ConfigureAwait(false);
|
||||
await stdin.WriteAsync(contentBytes, ct).ConfigureAwait(false);
|
||||
await stdin.FlushAsync(ct).ConfigureAwait(false);
|
||||
|
||||
_logger.LogDebug("Sent {MessageType}: {Json}", message.GetType().Name, json);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_writeLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReadLoopAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stdout = _process.StandardOutput.BaseStream;
|
||||
var headerBuffer = new byte[1024];
|
||||
var contentBuffer = new byte[65536];
|
||||
|
||||
while (!_disposalCts.Token.IsCancellationRequested && !_process.HasExited)
|
||||
{
|
||||
var contentLength = await ReadContentLengthAsync(stdout, headerBuffer, _disposalCts.Token).ConfigureAwait(false);
|
||||
if (contentLength <= 0)
|
||||
{
|
||||
_logger.LogWarning("Read loop: contentLength={ContentLength}, exiting loop", contentLength);
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure buffer is large enough
|
||||
if (contentLength > contentBuffer.Length)
|
||||
{
|
||||
contentBuffer = new byte[contentLength];
|
||||
}
|
||||
|
||||
// Read exactly contentLength bytes
|
||||
var totalRead = 0;
|
||||
while (totalRead < contentLength)
|
||||
{
|
||||
var read = await stdout.ReadAsync(contentBuffer.AsMemory(totalRead, contentLength - totalRead), _disposalCts.Token).ConfigureAwait(false);
|
||||
if (read == 0)
|
||||
{
|
||||
_logger.LogWarning("Stream closed before reading full message");
|
||||
return;
|
||||
}
|
||||
|
||||
totalRead += read;
|
||||
}
|
||||
|
||||
var json = Encoding.UTF8.GetString(contentBuffer, 0, contentLength);
|
||||
_logger.LogDebug("Received message: {Json}", json);
|
||||
|
||||
ProcessMessage(json);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Expected during disposal
|
||||
_logger.LogInformation("Read loop exiting due to cancellation");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in read loop");
|
||||
OnError?.Invoke(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.LogInformation("Read loop ended");
|
||||
OnDisconnected?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> ReadContentLengthAsync(Stream stream, byte[] buffer, CancellationToken ct)
|
||||
{
|
||||
var position = 0;
|
||||
var headerComplete = false;
|
||||
|
||||
while (!headerComplete && position < buffer.Length)
|
||||
{
|
||||
var read = await stream.ReadAsync(buffer.AsMemory(position, 1), ct).ConfigureAwait(false);
|
||||
if (read == 0)
|
||||
{
|
||||
return -1; // Stream closed
|
||||
}
|
||||
|
||||
position++;
|
||||
|
||||
// Check for \r\n\r\n (end of headers)
|
||||
if (position >= 4 &&
|
||||
buffer[position - 4] == '\r' &&
|
||||
buffer[position - 3] == '\n' &&
|
||||
buffer[position - 2] == '\r' &&
|
||||
buffer[position - 1] == '\n')
|
||||
{
|
||||
headerComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!headerComplete)
|
||||
{
|
||||
_logger.LogError("Header too large or malformed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
var headerText = Encoding.ASCII.GetString(buffer, 0, position);
|
||||
var lines = headerText.Split(["\r\n"], StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var valueStr = line.Substring(15).Trim();
|
||||
if (int.TryParse(valueStr, out var length))
|
||||
{
|
||||
return length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogError("Content-Length header not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void ProcessMessage(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (root.TryGetProperty("id", out var idProp) && idProp.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
// This is a response
|
||||
var id = idProp.GetInt32();
|
||||
var response = JsonSerializer.Deserialize(json, JsonRpcSerializerContext.Default.JsonRpcResponse);
|
||||
|
||||
if (response != null && _pendingRequests.TryRemove(id, out var tcs))
|
||||
{
|
||||
tcs.SetResult(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Received response for unknown request ID: {Id}", id);
|
||||
}
|
||||
}
|
||||
else if (root.TryGetProperty("method", out var methodProp))
|
||||
{
|
||||
// This is a notification
|
||||
var method = methodProp.GetString() ?? string.Empty;
|
||||
|
||||
if (_notificationHandlers.TryGetValue(method, out var handler))
|
||||
{
|
||||
var paramsElement = root.TryGetProperty("params", out var p) ? p : default;
|
||||
handler(paramsElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("No handler registered for notification: {Method}", method);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Received message with neither id nor method");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing message: {Json}", json);
|
||||
OnError?.Invoke(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type - closely related message types grouped together
|
||||
#pragma warning disable SA1649 // File name should match first type name
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services.JsonRpc;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a JSON-RPC 2.0 request message.
|
||||
/// </summary>
|
||||
public sealed class JsonRpcRequest
|
||||
{
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; set; } = "2.0";
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonPropertyName("method")]
|
||||
public string Method { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("params")]
|
||||
public JsonNode? Params { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a JSON-RPC 2.0 response message.
|
||||
/// </summary>
|
||||
public sealed class JsonRpcResponse
|
||||
{
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; set; } = "2.0";
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public JsonElement? Result { get; set; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public JsonRpcError? Error { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a JSON-RPC 2.0 notification message (no id, no response expected).
|
||||
/// </summary>
|
||||
public sealed class JsonRpcNotification
|
||||
{
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; set; } = "2.0";
|
||||
|
||||
[JsonPropertyName("method")]
|
||||
public string Method { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("params")]
|
||||
public JsonNode? Params { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a JSON-RPC 2.0 error object.
|
||||
/// </summary>
|
||||
public sealed class JsonRpcError
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public object? Data { get; set; }
|
||||
|
||||
// Standard JSON-RPC 2.0 error codes
|
||||
public const int ParseError = -32700;
|
||||
public const int InvalidRequest = -32600;
|
||||
public const int MethodNotFound = -32601;
|
||||
public const int InvalidParams = -32602;
|
||||
public const int InternalError = -32603;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON source generation context for AOT-safe serialization.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonSerializable(typeof(JsonRpcRequest))]
|
||||
[JsonSerializable(typeof(JsonRpcResponse))]
|
||||
[JsonSerializable(typeof(JsonRpcNotification))]
|
||||
[JsonSerializable(typeof(JsonRpcError))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSerializable(typeof(JsonNode))]
|
||||
[JsonSerializable(typeof(JsonObject))]
|
||||
internal partial class JsonRpcSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -2,28 +2,34 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.AppExtensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
public partial class ExtensionService : IExtensionService, IDisposable
|
||||
/// <summary>
|
||||
/// Extension service that manages out-of-process WinRT AppExtension-based command providers.
|
||||
/// Handles package catalog monitoring, extension startup with timeouts, and background retries.
|
||||
/// </summary>
|
||||
public partial class WinRTExtensionService : IExtensionService, IDisposable
|
||||
{
|
||||
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
|
||||
|
||||
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
|
||||
private static readonly TimeSpan ExtensionStartTimeout = TimeSpan.FromSeconds(10);
|
||||
private static readonly TimeSpan BackgroundStartTimeout = TimeSpan.FromSeconds(60);
|
||||
|
||||
private static readonly PackageCatalog _catalog = PackageCatalog.OpenForCurrentUser();
|
||||
private static readonly Lock _lock = new();
|
||||
private readonly SemaphoreSlim _getInstalledExtensionsLock = new(1, 1);
|
||||
private readonly SemaphoreSlim _getInstalledWidgetsLock = new(1, 1);
|
||||
private readonly TaskScheduler _taskScheduler;
|
||||
private readonly ICommandProviderCache _commandProviderCache;
|
||||
|
||||
// private readonly ILocalSettingsService _localSettingsService;
|
||||
private bool _disposedValue;
|
||||
|
||||
private const string CreateInstanceProperty = "CreateInstance";
|
||||
@@ -32,17 +38,119 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
private static readonly List<IExtensionWrapper> _installedExtensions = [];
|
||||
private static readonly List<IExtensionWrapper> _enabledExtensions = [];
|
||||
|
||||
public ExtensionService()
|
||||
public event TypedEventHandler<IExtensionService, IEnumerable<CommandProviderWrapper>>? OnProviderAdded;
|
||||
|
||||
public event TypedEventHandler<IExtensionService, IEnumerable<CommandProviderWrapper>>? OnProviderRemoved;
|
||||
|
||||
public WinRTExtensionService(TaskScheduler taskScheduler, ICommandProviderCache commandProviderCache)
|
||||
{
|
||||
_taskScheduler = taskScheduler;
|
||||
_commandProviderCache = commandProviderCache;
|
||||
|
||||
_catalog.PackageInstalling += Catalog_PackageInstalling;
|
||||
_catalog.PackageUninstalling += Catalog_PackageUninstalling;
|
||||
_catalog.PackageUpdating += Catalog_PackageUpdating;
|
||||
}
|
||||
|
||||
//// These two were an investigation into getting updates when a package
|
||||
//// gets redeployed from VS. Neither get raised (nor do the above)
|
||||
//// _catalog.PackageStatusChanged += Catalog_PackageStatusChanged;
|
||||
//// _catalog.PackageStaging += Catalog_PackageStaging;
|
||||
// _localSettingsService = settingsService;
|
||||
public async Task<IEnumerable<CommandProviderWrapper>> LoadProvidersAsync(CancellationToken ct)
|
||||
{
|
||||
var extensions = (await GetInstalledExtensionsAsync().ConfigureAwait(false)).ToImmutableList();
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
// Start all extensions in parallel
|
||||
var startResults = await Task.WhenAll(extensions.Select(ext => TryStartExtensionAsync(ext, ct))).ConfigureAwait(false);
|
||||
|
||||
var startedWrappers = new List<CommandProviderWrapper>();
|
||||
foreach (var r in startResults)
|
||||
{
|
||||
if (r.IsStarted)
|
||||
{
|
||||
startedWrappers.Add(r.Wrapper);
|
||||
}
|
||||
else if (r.IsTimedOut)
|
||||
{
|
||||
_ = StartExtensionWhenReadyAsync(r.Extension, r.PendingStartTask, r.Stopwatch, ct);
|
||||
}
|
||||
}
|
||||
|
||||
timer.Stop();
|
||||
Logger.LogInfo($"WinRTExtensionService: Started {startedWrappers.Count} extension(s) in {timer.ElapsedMilliseconds} ms");
|
||||
|
||||
return startedWrappers;
|
||||
}
|
||||
|
||||
public async Task SignalStopAsync()
|
||||
{
|
||||
var installedExtensions = await GetInstalledExtensionsAsync().ConfigureAwait(false);
|
||||
foreach (var installedExtension in installedExtensions)
|
||||
{
|
||||
Logger.LogDebug($"Signaling dispose to {installedExtension.ExtensionUniqueId}");
|
||||
try
|
||||
{
|
||||
if (installedExtension.IsRunning())
|
||||
{
|
||||
installedExtension.SignalDispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to send dispose signal to extension {installedExtension.ExtensionUniqueId}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ExtensionStartResult> TryStartExtensionAsync(IExtensionWrapper extension, CancellationToken ct)
|
||||
{
|
||||
Logger.LogDebug($"Starting {extension.PackageFullName}");
|
||||
var sw = Stopwatch.StartNew();
|
||||
var startTask = extension.StartExtensionAsync();
|
||||
try
|
||||
{
|
||||
await startTask.WaitAsync(ExtensionStartTimeout, ct).ConfigureAwait(false);
|
||||
Logger.LogInfo($"Started extension {extension.PackageFullName} in {sw.ElapsedMilliseconds} ms");
|
||||
return ExtensionStartResult.Started(extension, new CommandProviderWrapper(extension, _taskScheduler, _commandProviderCache));
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
Logger.LogWarning($"Starting extension {extension.PackageFullName} timed out after {sw.ElapsedMilliseconds} ms, continuing in background");
|
||||
return ExtensionStartResult.TimedOut(extension, startTask, sw);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.LogDebug($"Starting extension {extension.PackageFullName} was cancelled after {sw.ElapsedMilliseconds} ms");
|
||||
return ExtensionStartResult.Failed(extension);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to start extension {extension.PackageFullName} after {sw.ElapsedMilliseconds} ms: {ex}");
|
||||
return ExtensionStartResult.Failed(extension);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartExtensionWhenReadyAsync(
|
||||
IExtensionWrapper extension,
|
||||
Task startTask,
|
||||
Stopwatch sw,
|
||||
CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await startTask.WaitAsync(BackgroundStartTimeout, ct).ConfigureAwait(false);
|
||||
|
||||
var wrapper = new CommandProviderWrapper(extension, _taskScheduler, _commandProviderCache);
|
||||
Logger.LogInfo($"Late-started extension {extension.PackageFullName} in {sw.ElapsedMilliseconds} ms");
|
||||
|
||||
OnProviderAdded?.Invoke(this, [wrapper]);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Reload happened -- discard stale results
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Background start of extension {extension.PackageFullName} failed after {sw.ElapsedMilliseconds} ms: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Catalog_PackageInstalling(PackageCatalog sender, PackageInstallingEventArgs args)
|
||||
@@ -73,10 +181,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// Get any extension providers that we previously had from this app
|
||||
UninstallPackageUnderLock(args.TargetPackage);
|
||||
|
||||
// then add the new ones.
|
||||
InstallPackageUnderLock(args.TargetPackage);
|
||||
}
|
||||
}
|
||||
@@ -103,7 +208,25 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
|
||||
UpdateExtensionsListsFromWrappers(wrappers);
|
||||
|
||||
OnExtensionAdded?.Invoke(this, wrappers);
|
||||
// Start extensions and notify via OnProviderAdded
|
||||
var startedProviders = new List<CommandProviderWrapper>();
|
||||
foreach (var wrapper in wrappers)
|
||||
{
|
||||
try
|
||||
{
|
||||
await wrapper.StartExtensionAsync().ConfigureAwait(false);
|
||||
startedProviders.Add(new CommandProviderWrapper(wrapper, _taskScheduler, _commandProviderCache));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to start newly installed extension {wrapper.ExtensionUniqueId}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
if (startedProviders.Count > 0)
|
||||
{
|
||||
OnProviderAdded?.Invoke(this, startedProviders);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -121,7 +244,6 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
if (extension.PackageFullName == package.Id.FullName)
|
||||
{
|
||||
CommandPaletteHost.Instance.DebugLog($"Uninstalled extension app {extension.PackageDisplayName}");
|
||||
|
||||
removedExtensions.Add(extension);
|
||||
}
|
||||
}
|
||||
@@ -134,7 +256,24 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
_installedExtensions.RemoveAll(i => removedExtensions.Contains(i));
|
||||
_enabledExtensions.RemoveAll(i => removedExtensions.Contains(i));
|
||||
|
||||
OnExtensionRemoved?.Invoke(this, removedExtensions);
|
||||
// Build placeholder wrappers for removal notification.
|
||||
// The TopLevelCommandManager matches by Extension reference, so we need to pass
|
||||
// something that carries the IExtensionWrapper identity.
|
||||
var removedProviders = new List<CommandProviderWrapper>();
|
||||
foreach (var ext in removedExtensions)
|
||||
{
|
||||
try
|
||||
{
|
||||
removedProviders.Add(new CommandProviderWrapper(ext, _taskScheduler, _commandProviderCache));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Extension may not be in a runnable state if it was uninstalled;
|
||||
// we still need to signal removal
|
||||
}
|
||||
}
|
||||
|
||||
OnProviderRemoved?.Invoke(this, removedProviders);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -143,52 +282,6 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<IsExtensionResult> IsValidCmdPalExtension(Package package)
|
||||
{
|
||||
var extensions = await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync();
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
if (package.Id?.FullName == extension.Package?.Id?.FullName)
|
||||
{
|
||||
var (cmdPalProvider, classId) = await GetCmdPalExtensionPropertiesAsync(extension);
|
||||
|
||||
return new(cmdPalProvider is not null && classId.Count != 0, extension);
|
||||
}
|
||||
}
|
||||
|
||||
return new(false, null);
|
||||
}
|
||||
|
||||
private static async Task<(IPropertySet? CmdPalProvider, List<string> ClassIds)> GetCmdPalExtensionPropertiesAsync(AppExtension extension)
|
||||
{
|
||||
var classIds = new List<string>();
|
||||
var properties = await extension.GetExtensionPropertiesAsync();
|
||||
|
||||
if (properties is null)
|
||||
{
|
||||
return (null, classIds);
|
||||
}
|
||||
|
||||
var cmdPalProvider = GetSubPropertySet(properties, "CmdPalProvider");
|
||||
if (cmdPalProvider is null)
|
||||
{
|
||||
return (null, classIds);
|
||||
}
|
||||
|
||||
var activation = GetSubPropertySet(cmdPalProvider, "Activation");
|
||||
if (activation is null)
|
||||
{
|
||||
return (cmdPalProvider, classIds);
|
||||
}
|
||||
|
||||
// Handle case where extension creates multiple instances.
|
||||
classIds.AddRange(GetCreateInstanceList(activation));
|
||||
|
||||
return (cmdPalProvider, classIds);
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<AppExtension>> GetInstalledAppExtensionsAsync() => await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync();
|
||||
|
||||
public async Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false)
|
||||
{
|
||||
await _getInstalledExtensionsLock.WaitAsync();
|
||||
@@ -215,25 +308,22 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateExtensionsListsFromWrappers(List<ExtensionWrapper> wrappers)
|
||||
public IExtensionWrapper? GetInstalledExtension(string extensionUniqueId)
|
||||
{
|
||||
foreach (var extensionWrapper in wrappers)
|
||||
{
|
||||
// var localSettingsService = Application.Current.GetService<ILocalSettingsService>();
|
||||
var extensionUniqueId = extensionWrapper.ExtensionUniqueId;
|
||||
var isExtensionDisabled = false; // await localSettingsService.ReadSettingAsync<bool>(extensionUniqueId + "-ExtensionDisabled");
|
||||
var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
|
||||
return extension.FirstOrDefault();
|
||||
}
|
||||
|
||||
_installedExtensions.Add(extensionWrapper);
|
||||
if (!isExtensionDisabled)
|
||||
{
|
||||
_enabledExtensions.Add(extensionWrapper);
|
||||
}
|
||||
public void EnableExtension(string extensionUniqueId)
|
||||
{
|
||||
var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
|
||||
_enabledExtensions.Add(extension.First());
|
||||
}
|
||||
|
||||
// TelemetryFactory.Get<ITelemetry>().Log(
|
||||
// "Extension_ReportInstalled",
|
||||
// LogLevel.Critical,
|
||||
// new ReportInstalledExtensionEvent(extensionUniqueId, isEnabled: !isExtensionDisabled));
|
||||
}
|
||||
public void DisableExtension(string extensionUniqueId)
|
||||
{
|
||||
var extension = _enabledExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
|
||||
_enabledExtensions.Remove(extension.First());
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsyncUnderLock(bool includeDisabledExtensions, bool refresh)
|
||||
@@ -295,6 +385,8 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<AppExtension>> GetInstalledAppExtensionsAsync() => await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync();
|
||||
|
||||
private static async Task<List<ExtensionWrapper>> CreateWrappersForExtension(AppExtension extension)
|
||||
{
|
||||
var (cmdPalProvider, classIds) = await GetCmdPalExtensionPropertiesAsync(extension);
|
||||
@@ -337,7 +429,6 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
// log warning that extension declared unsupported extension interface
|
||||
CommandPaletteHost.Instance.DebugLog($"Extension {extension.DisplayName} declared an unsupported interface: {supportedInterface.Key}");
|
||||
}
|
||||
}
|
||||
@@ -346,77 +437,68 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
return extensionWrapper;
|
||||
}
|
||||
|
||||
public IExtensionWrapper? GetInstalledExtension(string extensionUniqueId)
|
||||
private static void UpdateExtensionsListsFromWrappers(List<ExtensionWrapper> wrappers)
|
||||
{
|
||||
var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
|
||||
return extension.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task SignalStopExtensionsAsync()
|
||||
{
|
||||
var installedExtensions = await GetInstalledExtensionsAsync();
|
||||
foreach (var installedExtension in installedExtensions)
|
||||
foreach (var extensionWrapper in wrappers)
|
||||
{
|
||||
Logger.LogDebug($"Signaling dispose to {installedExtension.ExtensionUniqueId}");
|
||||
try
|
||||
var extensionUniqueId = extensionWrapper.ExtensionUniqueId;
|
||||
var isExtensionDisabled = false;
|
||||
|
||||
_installedExtensions.Add(extensionWrapper);
|
||||
if (!isExtensionDisabled)
|
||||
{
|
||||
if (installedExtension.IsRunning())
|
||||
{
|
||||
installedExtension.SignalDispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to send dispose signal to extension {installedExtension.ExtensionUniqueId}", ex);
|
||||
_enabledExtensions.Add(extensionWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(ProviderType providerType, bool includeDisabledExtensions = false)
|
||||
private static async Task<IsExtensionResult> IsValidCmdPalExtension(Package package)
|
||||
{
|
||||
var installedExtensions = await GetInstalledExtensionsAsync(includeDisabledExtensions);
|
||||
|
||||
List<IExtensionWrapper> filteredExtensions = [];
|
||||
foreach (var installedExtension in installedExtensions)
|
||||
var extensions = await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync();
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
if (installedExtension.HasProviderType(providerType))
|
||||
if (package.Id?.FullName == extension.Package?.Id?.FullName)
|
||||
{
|
||||
filteredExtensions.Add(installedExtension);
|
||||
var (cmdPalProvider, classId) = await GetCmdPalExtensionPropertiesAsync(extension);
|
||||
|
||||
return new(cmdPalProvider is not null && classId.Count != 0, extension);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredExtensions;
|
||||
return new(false, null);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
private static async Task<(IPropertySet? CmdPalProvider, List<string> ClassIds)> GetCmdPalExtensionPropertiesAsync(AppExtension extension)
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
var classIds = new List<string>();
|
||||
var properties = await extension.GetExtensionPropertiesAsync();
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
if (properties is null)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_getInstalledExtensionsLock.Dispose();
|
||||
_getInstalledWidgetsLock.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
return (null, classIds);
|
||||
}
|
||||
|
||||
var cmdPalProvider = GetSubPropertySet(properties, "CmdPalProvider");
|
||||
if (cmdPalProvider is null)
|
||||
{
|
||||
return (null, classIds);
|
||||
}
|
||||
|
||||
var activation = GetSubPropertySet(cmdPalProvider, "Activation");
|
||||
if (activation is null)
|
||||
{
|
||||
return (cmdPalProvider, classIds);
|
||||
}
|
||||
|
||||
classIds.AddRange(GetCreateInstanceList(activation));
|
||||
|
||||
return (cmdPalProvider, classIds);
|
||||
}
|
||||
|
||||
private static IPropertySet? GetSubPropertySet(IPropertySet propSet, string name) => propSet.TryGetValue(name, out var value) ? value as IPropertySet : null;
|
||||
|
||||
private static object[]? GetSubPropertySetArray(IPropertySet propSet, string name) => propSet.TryGetValue(name, out var value) ? value as object[] : null;
|
||||
|
||||
/// <summary>
|
||||
/// There are cases where the extension creates multiple COM instances.
|
||||
/// </summary>
|
||||
/// <param name="activationPropSet">Activation property set object</param>
|
||||
/// <returns>List of ClassId strings associated with the activation property</returns>
|
||||
private static List<string> GetCreateInstanceList(IPropertySet activationPropSet)
|
||||
{
|
||||
var propSetList = new List<string>();
|
||||
@@ -424,8 +506,6 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
if (singlePropertySet is not null)
|
||||
{
|
||||
var classId = GetProperty(singlePropertySet, ClassIdProperty);
|
||||
|
||||
// If the instance has a classId as a single string, then it's only supporting a single instance.
|
||||
if (classId is not null)
|
||||
{
|
||||
propSetList.Add(classId);
|
||||
@@ -457,35 +537,24 @@ public partial class ExtensionService : IExtensionService, IDisposable
|
||||
|
||||
private static string? GetProperty(IPropertySet propSet, string name) => propSet[name] as string;
|
||||
|
||||
public void EnableExtension(string extensionUniqueId)
|
||||
public void Dispose()
|
||||
{
|
||||
var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
|
||||
_enabledExtensions.Add(extension.First());
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void DisableExtension(string extensionUniqueId)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
var extension = _enabledExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
|
||||
_enabledExtensions.Remove(extension.First());
|
||||
}
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_getInstalledExtensionsLock.Dispose();
|
||||
}
|
||||
|
||||
/*
|
||||
///// <inheritdoc cref="IExtensionService.DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper)"/>
|
||||
//public async Task<bool> DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension)
|
||||
//{
|
||||
// // Only attempt to disable feature if its available.
|
||||
// if (IsWindowsOptionalFeatureAvailableForExtension(extension.ExtensionClassId))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// _log.Warning($"Disabling extension: '{extension.ExtensionDisplayName}' because its feature is absent or unknown");
|
||||
// // Remove extension from list of enabled extensions to prevent Dev Home from re-querying for this extension
|
||||
// // for the rest of its process lifetime.
|
||||
// DisableExtension(extension.ExtensionUniqueId);
|
||||
// // Update the local settings so the next time the user launches Dev Home the extension will be disabled.
|
||||
// await _localSettingsService.SaveSettingAsync(extension.ExtensionUniqueId + "-ExtensionDisabled", true);
|
||||
// return true;
|
||||
//} */
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal record struct IsExtensionResult(bool IsExtension, AppExtension? Extension)
|
||||
@@ -489,6 +489,34 @@ public partial class ShellViewModel : ObservableObject,
|
||||
UnsafeHandleCommandResult(a.Result, onBeforeShowConfirmation);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CommandResultKind.GoToPage:
|
||||
{
|
||||
if (result is JSGoToPageCommandResult jsGoToPage && jsGoToPage.Page != null)
|
||||
{
|
||||
// Handle NavigationMode before navigating to the target page
|
||||
if (jsGoToPage.Args is IGoToPageArgs goToPageArgs)
|
||||
{
|
||||
switch (goToPageArgs.NavigationMode)
|
||||
{
|
||||
case NavigationMode.GoBack:
|
||||
GoBack();
|
||||
break;
|
||||
case NavigationMode.GoHome:
|
||||
GoHome(withAnimation: false, focusSearch: false);
|
||||
break;
|
||||
case NavigationMode.Push:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(
|
||||
new(new ExtensionObject<ICommand>(jsGoToPage.Page)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -11,7 +10,6 @@ using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -22,22 +20,20 @@ namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
IRecipient<ReloadCommandsMessage>,
|
||||
IRecipient<ProviderEnabledStateChangedMessage>,
|
||||
IRecipient<PinCommandItemMessage>,
|
||||
IRecipient<UnpinCommandItemMessage>,
|
||||
IRecipient<PinToDockMessage>,
|
||||
IDisposable
|
||||
{
|
||||
private static readonly TimeSpan ExtensionStartTimeout = TimeSpan.FromSeconds(10);
|
||||
private static readonly TimeSpan CommandLoadTimeout = TimeSpan.FromSeconds(10);
|
||||
private static readonly TimeSpan BackgroundStartTimeout = TimeSpan.FromSeconds(60);
|
||||
private static readonly TimeSpan BackgroundCommandLoadTimeout = TimeSpan.FromSeconds(60);
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ICommandProviderCache _commandProviderCache;
|
||||
private readonly IEnumerable<IExtensionService> _extensionServices;
|
||||
private readonly TaskScheduler _taskScheduler;
|
||||
|
||||
private readonly List<CommandProviderWrapper> _builtInCommands = [];
|
||||
private readonly List<CommandProviderWrapper> _extensionCommandProviders = [];
|
||||
private readonly List<CommandProviderWrapper> _commandProviders = [];
|
||||
private readonly Lock _commandProvidersLock = new();
|
||||
|
||||
// watch out: if you add code that locks CommandProviders, be sure to always
|
||||
@@ -50,18 +46,25 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
|
||||
private HashSet<(string ProviderId, string CommandId)> _pinnedCommandSet = [];
|
||||
|
||||
public TopLevelCommandManager(IServiceProvider serviceProvider, ICommandProviderCache commandProviderCache)
|
||||
public TopLevelCommandManager(IServiceProvider serviceProvider, IEnumerable<IExtensionService> extensionServices)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_commandProviderCache = commandProviderCache;
|
||||
_extensionServices = extensionServices;
|
||||
_currentExtensionLoadCancellationToken = _extensionLoadCts.Token;
|
||||
_taskScheduler = _serviceProvider.GetService<TaskScheduler>()!;
|
||||
WeakReferenceMessenger.Default.Register<ReloadCommandsMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ProviderEnabledStateChangedMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<PinCommandItemMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<UnpinCommandItemMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<PinToDockMessage>(this);
|
||||
_reloadCommandsGate = new(ReloadAllCommandsAsyncCore);
|
||||
RebuildPinnedCache();
|
||||
|
||||
foreach (var service in _extensionServices)
|
||||
{
|
||||
service.OnProviderAdded += ExtensionService_OnProviderAdded;
|
||||
service.OnProviderRemoved += ExtensionService_OnProviderRemoved;
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<PinnedCommandSettings> PinnedCommands { get; } = [];
|
||||
@@ -84,7 +87,7 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
{
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
return _builtInCommands.Concat(_extensionCommandProviders).ToList();
|
||||
return _commandProviders.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,58 +104,6 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
ListHelpers.InPlaceUpdateList(PinnedCommands, settings.PinnedCommands);
|
||||
}
|
||||
|
||||
public async Task<bool> LoadBuiltinsAsync()
|
||||
{
|
||||
var s = new Stopwatch();
|
||||
s.Start();
|
||||
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
_builtInCommands.Clear();
|
||||
}
|
||||
|
||||
// Load built-In commands first. These are all in-proc, and
|
||||
// owned by our ServiceProvider.
|
||||
var builtInCommands = _serviceProvider.GetServices<ICommandProvider>();
|
||||
foreach (var provider in builtInCommands)
|
||||
{
|
||||
CommandProviderWrapper wrapper = new(provider, _taskScheduler);
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
_builtInCommands.Add(wrapper);
|
||||
}
|
||||
|
||||
var objects = await LoadTopLevelCommandsFromProvider(wrapper);
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
if (objects.Commands is IEnumerable<TopLevelViewModel> commands)
|
||||
{
|
||||
foreach (var c in commands)
|
||||
{
|
||||
TopLevelCommands.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock (_dockBandsLock)
|
||||
{
|
||||
if (objects.DockBands is IEnumerable<TopLevelViewModel> bands)
|
||||
{
|
||||
foreach (var c in bands)
|
||||
{
|
||||
DockBands.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.Stop();
|
||||
|
||||
Logger.LogDebug($"Loading built-ins took {s.ElapsedMilliseconds}ms");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// May be called from a background thread
|
||||
private async Task<TopLevelObjectSets> LoadTopLevelCommandsFromProvider(CommandProviderWrapper commandProvider)
|
||||
{
|
||||
@@ -288,125 +239,197 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
{
|
||||
// gate ensures that the reload is serialized and if multiple calls
|
||||
// request a reload, only the first and the last one will be executed.
|
||||
// this should be superseded with a cancellable version.
|
||||
await _reloadCommandsGate.ExecuteAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads only built-in (in-process) command providers. This is fast and
|
||||
/// suitable for the initial pre-load phase so the UI appears immediately.
|
||||
/// </summary>
|
||||
public async Task LoadBuiltInProvidersAsync()
|
||||
{
|
||||
var ct = _currentExtensionLoadCancellationToken;
|
||||
foreach (var service in _extensionServices.OfType<BuiltInExtensionService>())
|
||||
{
|
||||
var wrappers = await service.LoadProvidersAsync(ct).ConfigureAwait(false);
|
||||
await RegisterAndLoadCommandsAsync(wrappers, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads external (out-of-process WinRT) command providers. Call this after
|
||||
/// the root page is displayed so the UI is not blocked by extension startup.
|
||||
/// Commands appear progressively via <see cref="IExtensionService.OnProviderAdded"/>.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
public async Task LoadExternalProvidersAsync()
|
||||
{
|
||||
IsLoading = true;
|
||||
try
|
||||
{
|
||||
var ct = _currentExtensionLoadCancellationToken;
|
||||
foreach (var service in _extensionServices.Where(s => s is not BuiltInExtensionService))
|
||||
{
|
||||
var wrappers = await service.LoadProvidersAsync(ct).ConfigureAwait(false);
|
||||
await RegisterAndLoadCommandsAsync(wrappers, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReloadAllCommandsAsyncCore(CancellationToken cancellationToken)
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
// Invalidate any background continuations from the previous load cycle
|
||||
await _extensionLoadCts.CancelAsync().ConfigureAwait(false);
|
||||
_extensionLoadCts.Dispose();
|
||||
_extensionLoadCts = new();
|
||||
_currentExtensionLoadCancellationToken = _extensionLoadCts.Token;
|
||||
|
||||
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
|
||||
await extensionService.SignalStopExtensionsAsync().ConfigureAwait(false);
|
||||
|
||||
lock (TopLevelCommands)
|
||||
try
|
||||
{
|
||||
TopLevelCommands.Clear();
|
||||
}
|
||||
// Invalidate any background continuations from the previous load cycle
|
||||
await _extensionLoadCts.CancelAsync().ConfigureAwait(false);
|
||||
_extensionLoadCts.Dispose();
|
||||
_extensionLoadCts = new();
|
||||
_currentExtensionLoadCancellationToken = _extensionLoadCts.Token;
|
||||
|
||||
lock (_dockBandsLock)
|
||||
{
|
||||
DockBands.Clear();
|
||||
}
|
||||
|
||||
await LoadBuiltinsAsync().ConfigureAwait(false);
|
||||
_ = Task.Run(LoadExtensionsAsync, cancellationToken);
|
||||
}
|
||||
|
||||
// Load commands from our extensions. Called on a background thread.
|
||||
// Currently, this
|
||||
// * queries the package catalog,
|
||||
// * starts all the extensions,
|
||||
// * then fetches the top-level commands from them.
|
||||
// TODO In the future, we'll probably abstract some of this away, to have
|
||||
// separate extension tracking vs stub loading.
|
||||
[RelayCommand]
|
||||
public async Task<bool> LoadExtensionsAsync()
|
||||
{
|
||||
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
|
||||
|
||||
extensionService.OnExtensionAdded -= ExtensionService_OnExtensionAdded;
|
||||
extensionService.OnExtensionRemoved -= ExtensionService_OnExtensionRemoved;
|
||||
|
||||
var ct = _currentExtensionLoadCancellationToken;
|
||||
|
||||
var extensions = (await extensionService.GetInstalledExtensionsAsync().ConfigureAwait(false)).ToImmutableList();
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
_extensionCommandProviders.Clear();
|
||||
}
|
||||
|
||||
await StartExtensionsAndGetCommands(extensions, ct).ConfigureAwait(false);
|
||||
|
||||
extensionService.OnExtensionAdded += ExtensionService_OnExtensionAdded;
|
||||
extensionService.OnExtensionRemoved += ExtensionService_OnExtensionRemoved;
|
||||
|
||||
IsLoading = false;
|
||||
|
||||
// Send on the current thread; receivers should marshal to UI if needed
|
||||
WeakReferenceMessenger.Default.Send<ReloadFinishedMessage>();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ExtensionService_OnExtensionAdded(IExtensionService sender, IEnumerable<IExtensionWrapper> extensions)
|
||||
{
|
||||
var ct = _currentExtensionLoadCancellationToken;
|
||||
|
||||
// When we get an extension install event, hop off to a BG thread
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
// Signal all services to stop their running providers
|
||||
foreach (var service in _extensionServices)
|
||||
{
|
||||
// for each newly installed extension, start it and get commands
|
||||
// from it. One single package might have more than one
|
||||
// IExtensionWrapper in it.
|
||||
await StartExtensionsAndGetCommands(extensions, ct).ConfigureAwait(false);
|
||||
},
|
||||
ct);
|
||||
}
|
||||
|
||||
private async Task StartExtensionsAndGetCommands(IEnumerable<IExtensionWrapper> extensions, CancellationToken ct)
|
||||
{
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
// Start all extensions in parallel
|
||||
var startResults = await Task.WhenAll(extensions.Select(TryStartExtensionAsync)).ConfigureAwait(false);
|
||||
|
||||
var startedWrappers = new List<CommandProviderWrapper>();
|
||||
foreach (var r in startResults)
|
||||
{
|
||||
if (r.IsStarted)
|
||||
{
|
||||
startedWrappers.Add(r.Wrapper);
|
||||
await service.SignalStopAsync().ConfigureAwait(false);
|
||||
}
|
||||
else if (r.IsTimedOut)
|
||||
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
_ = StartExtensionWhenReadyAsync(r.Extension, r.PendingStartTask, r.Stopwatch, ct);
|
||||
TopLevelCommands.Clear();
|
||||
}
|
||||
|
||||
lock (_dockBandsLock)
|
||||
{
|
||||
DockBands.Clear();
|
||||
}
|
||||
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
_commandProviders.Clear();
|
||||
}
|
||||
|
||||
var ct = _currentExtensionLoadCancellationToken;
|
||||
|
||||
// Load providers from each service sequentially (order matters: built-ins first)
|
||||
foreach (var service in _extensionServices)
|
||||
{
|
||||
var wrappers = await service.LoadProvidersAsync(ct).ConfigureAwait(false);
|
||||
await RegisterAndLoadCommandsAsync(wrappers, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Register started extensions and load their commands
|
||||
var loadSummary = await RegisterAndLoadCommandsAsync(startedWrappers, ct).ConfigureAwait(false);
|
||||
|
||||
timer.Stop();
|
||||
Logger.LogInfo($"Loaded {loadSummary.CommandCount} command(s) and {loadSummary.DockBandCount} band(s) from {startedWrappers.Count} extension(s) in {timer.ElapsedMilliseconds} ms");
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
WeakReferenceMessenger.Default.Send<ReloadFinishedMessage>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<RegisterAndLoadSummary> RegisterAndLoadCommandsAsync(ICollection<CommandProviderWrapper> wrappers, CancellationToken ct)
|
||||
private async Task UpdateProviderEnabledStateAsyncCore(string providerId, bool isEnabled)
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
// If disabled, we'll remove that providers commands from top level commands, dock bands, and pinned commands.
|
||||
if (!isEnabled)
|
||||
{
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
var commandsToRemove = TopLevelCommands.Where(c => c.CommandProviderId == providerId).ToList();
|
||||
foreach (var command in commandsToRemove)
|
||||
{
|
||||
TopLevelCommands.Remove(command);
|
||||
}
|
||||
}
|
||||
|
||||
lock (_dockBandsLock)
|
||||
{
|
||||
var dockBandsToRemove = DockBands.Where(b => b.CommandProviderId == providerId).ToList();
|
||||
foreach (var band in dockBandsToRemove)
|
||||
{
|
||||
DockBands.Remove(band);
|
||||
}
|
||||
}
|
||||
|
||||
lock (PinnedCommands)
|
||||
{
|
||||
var pinnedToRemove = PinnedCommands.Where(p => p.ProviderId == providerId).ToList();
|
||||
foreach (var command in pinnedToRemove)
|
||||
{
|
||||
PinnedCommands.Remove(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandProviderWrapper? provider;
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
provider = _commandProviders.FirstOrDefault(p => p.ProviderId == providerId);
|
||||
}
|
||||
|
||||
if (provider != null)
|
||||
{
|
||||
await provider.LoadTopLevelCommands(_serviceProvider);
|
||||
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
foreach (var command in provider.TopLevelItems)
|
||||
{
|
||||
if (!TopLevelCommands.Any(a => a.Id == command.Id))
|
||||
{
|
||||
TopLevelCommands.Add(command);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in provider.FallbackItems)
|
||||
{
|
||||
if (!TopLevelCommands.Any(a => a.Id == item.Id) && item.IsEnabled)
|
||||
{
|
||||
TopLevelCommands.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock (_dockBandsLock)
|
||||
{
|
||||
foreach (var band in provider.DockBandItems)
|
||||
{
|
||||
if (!DockBands.Any(a => a.Id == band.Id))
|
||||
{
|
||||
DockBands.Add(band);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning($"Could not find provider with id '{providerId}' to update enabled state.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<RegisterAndLoadSummary> RegisterAndLoadCommandsAsync(IEnumerable<CommandProviderWrapper> wrappers, CancellationToken ct)
|
||||
{
|
||||
var wrapperList = wrappers.ToList();
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
_extensionCommandProviders.AddRange(wrappers);
|
||||
_commandProviders.AddRange(wrapperList);
|
||||
}
|
||||
|
||||
// Load the commands from the providers in parallel
|
||||
var loadResults = await Task.WhenAll(wrappers.Select(w => TryLoadCommandsAsync(w, ct))).ConfigureAwait(false);
|
||||
var loadResults = await Task.WhenAll(wrapperList.Select(w => TryLoadCommandsAsync(w, ct))).ConfigureAwait(false);
|
||||
|
||||
var totalCommands = 0;
|
||||
var totalDockBands = 0;
|
||||
@@ -463,7 +486,6 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
// Fire background continuations for timed-out loads outside the lock
|
||||
foreach (var r in timedOut)
|
||||
{
|
||||
// It's weird to repeat the condition here, but it allows the compiler to track nullability of other properties
|
||||
if (r.IsTimedOut)
|
||||
{
|
||||
_ = AppendCommandsWhenReadyAsync(r.Wrapper, r.PendingLoadTask, r.Stopwatch, ct);
|
||||
@@ -473,60 +495,6 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
return new RegisterAndLoadSummary(totalCommands, totalDockBands);
|
||||
}
|
||||
|
||||
private async Task<ExtensionStartResult> TryStartExtensionAsync(IExtensionWrapper extension)
|
||||
{
|
||||
Logger.LogDebug($"Starting {extension.PackageFullName}");
|
||||
var sw = Stopwatch.StartNew();
|
||||
var ct = _currentExtensionLoadCancellationToken;
|
||||
var startTask = extension.StartExtensionAsync();
|
||||
try
|
||||
{
|
||||
await startTask.WaitAsync(ExtensionStartTimeout, ct).ConfigureAwait(false);
|
||||
Logger.LogInfo($"Started extension {extension.PackageFullName} in {sw.ElapsedMilliseconds} ms");
|
||||
return ExtensionStartResult.Started(extension, new CommandProviderWrapper(extension, _taskScheduler, _commandProviderCache));
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
Logger.LogWarning($"Starting extension {extension.PackageFullName} timed out after {sw.ElapsedMilliseconds} ms, continuing in background");
|
||||
return ExtensionStartResult.TimedOut(extension, startTask, sw);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.LogDebug($"Starting extension {extension.PackageFullName} was cancelled after {sw.ElapsedMilliseconds} ms");
|
||||
return ExtensionStartResult.Failed(extension);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to start extension {extension.PackageFullName} after {sw.ElapsedMilliseconds} ms: {ex}");
|
||||
return ExtensionStartResult.Failed(extension);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartExtensionWhenReadyAsync(
|
||||
IExtensionWrapper extension,
|
||||
Task startTask,
|
||||
Stopwatch sw,
|
||||
CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await startTask.WaitAsync(BackgroundStartTimeout, ct).ConfigureAwait(false);
|
||||
|
||||
var wrapper = new CommandProviderWrapper(extension, _taskScheduler, _commandProviderCache);
|
||||
Logger.LogInfo($"Late-started extension {extension.PackageFullName} in {sw.ElapsedMilliseconds} ms, loading commands and bands");
|
||||
|
||||
await RegisterAndLoadCommandsAsync([wrapper], ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Reload happened -- discard stale results
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Background start/load of extension {extension.PackageFullName} failed after {sw.ElapsedMilliseconds} ms: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CommandLoadResult> TryLoadCommandsAsync(CommandProviderWrapper wrapper, CancellationToken ct)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
@@ -536,22 +504,22 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
var result = await loadTask.WaitAsync(CommandLoadTimeout, ct).ConfigureAwait(false);
|
||||
var commandCount = result.Commands?.Count ?? 0;
|
||||
var dockBandCount = result.DockBands?.Count ?? 0;
|
||||
Logger.LogInfo($"Loaded {commandCount} command(s) and {dockBandCount} band(s) from {wrapper.ExtensionHost?.Extension?.PackageFullName} in {sw.ElapsedMilliseconds} ms");
|
||||
Logger.LogInfo($"Loaded {commandCount} command(s) and {dockBandCount} band(s) from {wrapper.ExtensionHost?.Extension?.PackageFullName ?? wrapper.DisplayName} in {sw.ElapsedMilliseconds} ms");
|
||||
return CommandLoadResult.Loaded(wrapper, result);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
Logger.LogWarning($"Loading commands and bands from {wrapper.ExtensionHost?.Extension?.PackageFullName} timed out after {sw.ElapsedMilliseconds} ms, continuing in background");
|
||||
Logger.LogWarning($"Loading commands and bands from {wrapper.ExtensionHost?.Extension?.PackageFullName ?? wrapper.DisplayName} timed out after {sw.ElapsedMilliseconds} ms, continuing in background");
|
||||
return CommandLoadResult.TimedOut(wrapper, loadTask, sw);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.LogDebug($"Loading commands and bands from {wrapper.ExtensionHost?.Extension?.PackageFullName} was cancelled after {sw.ElapsedMilliseconds} ms");
|
||||
Logger.LogDebug($"Loading commands and bands from {wrapper.ExtensionHost?.Extension?.PackageFullName ?? wrapper.DisplayName} was cancelled after {sw.ElapsedMilliseconds} ms");
|
||||
return CommandLoadResult.Failed(wrapper);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to load commands and bands for extension {wrapper.ExtensionHost?.Extension?.PackageFullName} after {sw.ElapsedMilliseconds} ms: {ex}");
|
||||
Logger.LogError($"Failed to load commands and bands for {wrapper.ExtensionHost?.Extension?.PackageFullName ?? wrapper.DisplayName} after {sw.ElapsedMilliseconds} ms: {ex}");
|
||||
return CommandLoadResult.Failed(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -590,7 +558,7 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Late-loaded {commands?.Count ?? 0} command(s) and {dockBands?.Count ?? 0} band(s) from {wrapper.ExtensionHost?.Extension?.PackageFullName} in {sw.ElapsedMilliseconds} ms");
|
||||
Logger.LogInfo($"Late-loaded {commands?.Count ?? 0} command(s) and {dockBands?.Count ?? 0} band(s) from {wrapper.ExtensionHost?.Extension?.PackageFullName ?? wrapper.DisplayName} in {sw.ElapsedMilliseconds} ms");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -598,52 +566,63 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Background loading of commands and bands from {wrapper.ExtensionHost?.Extension?.PackageFullName} failed after {sw.ElapsedMilliseconds} ms: {ex}");
|
||||
Logger.LogError($"Background loading of commands and bands from {wrapper.ExtensionHost?.Extension?.PackageFullName ?? wrapper.DisplayName} failed after {sw.ElapsedMilliseconds} ms: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtensionService_OnExtensionRemoved(IExtensionService sender, IEnumerable<IExtensionWrapper> extensions)
|
||||
private void ExtensionService_OnProviderAdded(IExtensionService sender, IEnumerable<CommandProviderWrapper> wrappers)
|
||||
{
|
||||
// When we get an extension uninstall event, hop off to a BG thread
|
||||
var ct = _currentExtensionLoadCancellationToken;
|
||||
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
// Then find all the top-level commands that belonged to that extension
|
||||
await RegisterAndLoadCommandsAsync(wrappers, ct).ConfigureAwait(false);
|
||||
},
|
||||
ct);
|
||||
}
|
||||
|
||||
private void ExtensionService_OnProviderRemoved(IExtensionService sender, IEnumerable<CommandProviderWrapper> removedWrappers)
|
||||
{
|
||||
// When we get a provider removal event, hop off to a BG thread
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
var removedProviderIds = new HashSet<string>(removedWrappers.Select(w => w.ProviderId));
|
||||
|
||||
List<TopLevelViewModel> commandsToRemove = [];
|
||||
List<TopLevelViewModel> bandsToRemove = [];
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
foreach (var command in TopLevelCommands)
|
||||
{
|
||||
var host = command.ExtensionHost;
|
||||
if (host?.Extension == extension)
|
||||
{
|
||||
commandsToRemove.Add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock (_dockBandsLock)
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
foreach (var command in TopLevelCommands)
|
||||
{
|
||||
foreach (var band in DockBands)
|
||||
if (removedProviderIds.Contains(command.CommandProviderId))
|
||||
{
|
||||
var host = band.ExtensionHost;
|
||||
if (host?.Extension == extension)
|
||||
{
|
||||
bandsToRemove.Add(band);
|
||||
}
|
||||
commandsToRemove.Add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then back on the UI thread (remember, TopLevelCommands is
|
||||
// Observable, so you can't touch it on the BG thread)...
|
||||
lock (_dockBandsLock)
|
||||
{
|
||||
foreach (var band in DockBands)
|
||||
{
|
||||
if (removedProviderIds.Contains(band.CommandProviderId))
|
||||
{
|
||||
bandsToRemove.Add(band);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
_commandProviders.RemoveAll(w => removedProviderIds.Contains(w.ProviderId));
|
||||
}
|
||||
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
// ... remove all the deleted commands.
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
if (commandsToRemove.Count != 0)
|
||||
@@ -715,6 +694,9 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
public void Receive(ReloadCommandsMessage message) =>
|
||||
_ = ReloadAllCommandsAsync();
|
||||
|
||||
public void Receive(ProviderEnabledStateChangedMessage message) =>
|
||||
_ = UpdateProviderEnabledStateAsyncCore(message.ProviderId, message.IsEnabled);
|
||||
|
||||
public void Receive(PinCommandItemMessage message)
|
||||
{
|
||||
var wrapper = LookupProvider(message.ProviderId);
|
||||
@@ -752,8 +734,7 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
{
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
return _builtInCommands.FirstOrDefault(w => w.ProviderId == providerId)
|
||||
?? _extensionCommandProviders.FirstOrDefault(w => w.ProviderId == providerId);
|
||||
return _commandProviders.FirstOrDefault(w => w.ProviderId == providerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,8 +742,7 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
{
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
return _builtInCommands.Any(wrapper => wrapper.Id == id && wrapper.IsActive)
|
||||
|| _extensionCommandProviders.Any(wrapper => wrapper.Id == id && wrapper.IsActive);
|
||||
return _commandProviders.Any(wrapper => wrapper.Id == id && wrapper.IsActive);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -815,49 +795,18 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var service in _extensionServices)
|
||||
{
|
||||
service.OnProviderAdded -= ExtensionService_OnProviderAdded;
|
||||
service.OnProviderRemoved -= ExtensionService_OnProviderRemoved;
|
||||
}
|
||||
|
||||
_extensionLoadCts.Cancel();
|
||||
_extensionLoadCts.Dispose();
|
||||
_reloadCommandsGate.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private sealed class ExtensionStartResult
|
||||
{
|
||||
public IExtensionWrapper Extension { get; }
|
||||
|
||||
public CommandProviderWrapper? Wrapper { get; private init; }
|
||||
|
||||
public Task? PendingStartTask { get; private init; }
|
||||
|
||||
public Stopwatch? Stopwatch { get; private init; }
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Wrapper))]
|
||||
public bool IsStarted => Wrapper is not null;
|
||||
|
||||
[MemberNotNullWhen(true, nameof(PendingStartTask), nameof(Stopwatch))]
|
||||
public bool IsTimedOut => PendingStartTask is not null;
|
||||
|
||||
private ExtensionStartResult(IExtensionWrapper extension)
|
||||
{
|
||||
Extension = extension;
|
||||
}
|
||||
|
||||
public static ExtensionStartResult Started(IExtensionWrapper extension, CommandProviderWrapper wrapper)
|
||||
{
|
||||
return new ExtensionStartResult(extension) { Wrapper = wrapper };
|
||||
}
|
||||
|
||||
public static ExtensionStartResult TimedOut(IExtensionWrapper extension, Task pendingStartTask, Stopwatch sw)
|
||||
{
|
||||
return new ExtensionStartResult(extension) { PendingStartTask = pendingStartTask, Stopwatch = sw };
|
||||
}
|
||||
|
||||
public static ExtensionStartResult Failed(IExtensionWrapper extension)
|
||||
{
|
||||
return new ExtensionStartResult(extension);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CommandLoadResult
|
||||
{
|
||||
public TopLevelObjectSets? TopLevelObjectSets { get; private init; }
|
||||
|
||||
@@ -264,7 +264,11 @@ public partial class App : Application, IDisposable
|
||||
// Core services
|
||||
services.AddSingleton(appInfoService);
|
||||
|
||||
services.AddSingleton<IExtensionService, ExtensionService>();
|
||||
// Load IExtensionServices here
|
||||
services.AddSingleton<IExtensionService, BuiltInExtensionService>();
|
||||
services.AddSingleton<IExtensionService, WinRTExtensionService>();
|
||||
services.AddSingleton<IExtensionService, JavaScriptExtensionService>();
|
||||
|
||||
services.AddSingleton<IRunHistoryService, RunHistoryService>();
|
||||
|
||||
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
||||
|
||||
@@ -922,8 +922,11 @@ public sealed partial class MainWindow : WindowEx,
|
||||
}
|
||||
}
|
||||
|
||||
var extensionService = serviceProvider.GetService<IExtensionService>()!;
|
||||
extensionService.SignalStopExtensionsAsync();
|
||||
var extensionServices = serviceProvider.GetServices<IExtensionService>();
|
||||
foreach (var extensionService in extensionServices)
|
||||
{
|
||||
extensionService.SignalStopAsync();
|
||||
}
|
||||
|
||||
App.Current.Services.GetService<TrayIconService>()!.Destroy();
|
||||
|
||||
|
||||
@@ -110,7 +110,6 @@
|
||||
Visibility="{x:Bind IsText, Mode=OneWay}" />
|
||||
<HyperlinkButton
|
||||
Padding="0"
|
||||
Command="{x:Bind NavigateCommand, Mode=OneWay}"
|
||||
NavigateUri="{x:Bind Link, Mode=OneWay}"
|
||||
UseSystemFocusVisuals="{StaticResource UseSystemFocusVisuals}"
|
||||
Visibility="{x:Bind IsLink, Mode=OneWay}">
|
||||
|
||||
@@ -36,7 +36,7 @@ internal sealed class PowerToysRootPageService : IRootPageService
|
||||
|
||||
public async Task PreLoadAsync()
|
||||
{
|
||||
await _tlcManager.LoadBuiltinsAsync();
|
||||
await _tlcManager.LoadBuiltInProvidersAsync();
|
||||
}
|
||||
|
||||
public Microsoft.CommandPalette.Extensions.IPage GetRootPage()
|
||||
@@ -46,11 +46,11 @@ internal sealed class PowerToysRootPageService : IRootPageService
|
||||
|
||||
public async Task PostLoadRootPageAsync()
|
||||
{
|
||||
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
|
||||
_tlcManager.LoadExtensionsCommand.Execute(null);
|
||||
// After loading built-ins, and starting navigation, kick off a thread to load external extensions.
|
||||
_tlcManager.LoadExternalProvidersCommand.Execute(null);
|
||||
|
||||
await _tlcManager.LoadExtensionsCommand.ExecutionTask!;
|
||||
if (_tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
||||
await _tlcManager.LoadExternalProvidersCommand.ExecutionTask!;
|
||||
if (_tlcManager.LoadExternalProvidersCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
// TODO: Handle failure case
|
||||
}
|
||||
|
||||
67
src/modules/cmdpal/TestDeserialize.cs
Normal file
67
src/modules/cmdpal/TestDeserialize.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
// Mimic the JsonRpcResponse
|
||||
public sealed class TestJsonRpcResponse
|
||||
{
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; set; } = "2.0";
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
[JsonPropertyName("result")]
|
||||
public JsonElement? Result { get; set; }
|
||||
[JsonPropertyName("error")]
|
||||
public object? Error { get; set; }
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonSerializable(typeof(TestJsonRpcResponse))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSerializable(typeof(JsonNode))]
|
||||
[JsonSerializable(typeof(JsonObject))]
|
||||
internal partial class TestContext : JsonSerializerContext { }
|
||||
|
||||
var json = """{"jsonrpc":"2.0","id":3,"result":{"items":[{"title":"Say Hello","displayName":"Say Hello","subtitle":"Displays a toast message","icon":null,"section":"Commands","tags":[{"text":"Action"}],"command":{"id":"say-hello","name":"Say Hello","displayName":"Say Hello"}},{"title":"View Readme","displayName":"View Readme","subtitle":"Shows a markdown content page","icon":null,"section":"Pages","tags":[{"text":"Page"}],"command":{"id":"open-markdown","name":"View Readme","displayName":"View Readme"}},{"title":"Static Item","displayName":"Static Item","subtitle":"This item does not have an action","icon":null,"section":"Other","command":{"id":"item-3","name":"Static Item","displayName":"Static Item"}}]}}""";
|
||||
|
||||
Console.WriteLine($"Input JSON length: {json.Length}");
|
||||
|
||||
var response = JsonSerializer.Deserialize(json, TestContext.Default.TestJsonRpcResponse);
|
||||
Console.WriteLine($"Response is null: {response == null}");
|
||||
Console.WriteLine($"Response.Id: {response?.Id}");
|
||||
Console.WriteLine($"Response.Result.HasValue: {response?.Result.HasValue}");
|
||||
Console.WriteLine($"Response.Result.Value.ValueKind: {response?.Result?.ValueKind}");
|
||||
|
||||
if (response?.Result?.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
var result = response.Result.Value;
|
||||
if (result.TryGetProperty("items", out var itemsProp))
|
||||
{
|
||||
Console.WriteLine($"items.ValueKind: {itemsProp.ValueKind}");
|
||||
if (itemsProp.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
Console.WriteLine($"items count: {itemsProp.GetArrayLength()}");
|
||||
foreach (var item in itemsProp.EnumerateArray())
|
||||
{
|
||||
if (item.TryGetProperty("title", out var t))
|
||||
Console.WriteLine($" item title: {t.GetString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("ERROR: 'items' property not found!");
|
||||
Console.WriteLine($"Available properties:");
|
||||
foreach (var prop in result.EnumerateObject())
|
||||
{
|
||||
Console.WriteLine($" {prop.Name}: {prop.Value.ValueKind}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"ERROR: Result is not an object! ValueKind = {response?.Result?.ValueKind}");
|
||||
}
|
||||
@@ -13,6 +13,7 @@ using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.Common.WinGet.Models;
|
||||
using Microsoft.CmdPal.Common.WinGet.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Gallery;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
@@ -61,7 +62,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory());
|
||||
|
||||
@@ -118,7 +119,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory(winGetService.Object, winGetStatusService.Object),
|
||||
winGetService.Object,
|
||||
@@ -202,7 +203,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory(winGetService.Object, winGetStatusService.Object),
|
||||
winGetService.Object,
|
||||
@@ -250,7 +251,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory());
|
||||
|
||||
@@ -292,7 +293,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory());
|
||||
|
||||
@@ -324,7 +325,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory());
|
||||
|
||||
@@ -359,7 +360,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory());
|
||||
|
||||
@@ -389,7 +390,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory());
|
||||
|
||||
@@ -421,7 +422,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory());
|
||||
|
||||
@@ -485,7 +486,7 @@ public class ExtensionGalleryViewModelTests
|
||||
|
||||
using var viewModel = new ExtensionGalleryViewModel(
|
||||
galleryService.Object,
|
||||
extensionService.Object,
|
||||
new[] { extensionService.Object },
|
||||
NullLogger<ExtensionGalleryViewModel>.Instance,
|
||||
CreateGalleryExtensionViewModelFactory(winGetPackageStatusService: winGetStatusService.Object),
|
||||
winGetPackageManagerService: null,
|
||||
|
||||
424
src/modules/cmdpal/doc/js-extensions/extension-authoring-js.md
Normal file
424
src/modules/cmdpal/doc/js-extensions/extension-authoring-js.md
Normal file
@@ -0,0 +1,424 @@
|
||||
# Building Command Palette Extensions with JavaScript/TypeScript
|
||||
|
||||
This guide explains how to create, develop, and publish JavaScript/TypeScript extensions for Command Palette using the JSONRPC extension system.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 22+ (for development; end-users get it automatically)
|
||||
- npm (comes with Node.js)
|
||||
- TypeScript 5.8+ (recommended)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create a new extension project
|
||||
|
||||
```bash
|
||||
mkdir my-cmdpal-extension
|
||||
cd my-cmdpal-extension
|
||||
npm init -y
|
||||
npm install @microsoft/cmdpal-sdk
|
||||
npm install -D typescript @types/node
|
||||
```
|
||||
|
||||
### 2. Configure TypeScript
|
||||
|
||||
Create `tsconfig.json`:
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Configure package.json
|
||||
|
||||
Add the `cmdpal` field to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@yourname/my-extension",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"cmdpal": {
|
||||
"main": "dist/index.js",
|
||||
"displayName": "My Extension",
|
||||
"minVersion": "0.100.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `cmdpal.main` field specifies the entry point for your extension. If omitted, the standard `main` field is used as a fallback.
|
||||
|
||||
### 4. Write your extension
|
||||
|
||||
Create `src/index.ts`:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
CommandProviderBase,
|
||||
ListPageBase,
|
||||
ListItemBase,
|
||||
InvokableCommandBase,
|
||||
ExtensionHost,
|
||||
type ActivationContext,
|
||||
type ICommandItem,
|
||||
type IListItem,
|
||||
type CommandResult,
|
||||
} from '@microsoft/cmdpal-sdk';
|
||||
|
||||
// A command that does something when invoked
|
||||
class GreetCommand extends InvokableCommandBase {
|
||||
id = 'greet';
|
||||
name = 'Greet';
|
||||
|
||||
invoke(): CommandResult {
|
||||
ExtensionHost.log('User invoked greet!');
|
||||
return { kind: 'showToast', args: { message: 'Hello from my extension! 👋' } };
|
||||
}
|
||||
}
|
||||
|
||||
// A list page showing items
|
||||
class MyListPage extends ListPageBase {
|
||||
id = 'my-list';
|
||||
name = 'My Items';
|
||||
title = 'My Extension';
|
||||
placeholderText = 'Search items...';
|
||||
|
||||
getItems(): IListItem[] {
|
||||
return [
|
||||
new ListItemBase({
|
||||
command: new GreetCommand(),
|
||||
title: 'Say Hello',
|
||||
subtitle: 'Shows a greeting toast',
|
||||
tags: [{ text: 'Action' }],
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// The main provider — this is what CmdPal loads
|
||||
class MyProvider extends CommandProviderBase {
|
||||
id = 'my-extension';
|
||||
displayName = 'My Extension';
|
||||
|
||||
private page = new MyListPage();
|
||||
|
||||
topLevelCommands(): ICommandItem[] {
|
||||
return [{
|
||||
command: this.page,
|
||||
title: this.page.title,
|
||||
subtitle: 'My awesome extension',
|
||||
}];
|
||||
}
|
||||
|
||||
getCommand(id: string) {
|
||||
if (id === this.page.id) return this.page as any;
|
||||
if (id === 'greet') return new GreetCommand();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point — CmdPal calls this to activate your extension
|
||||
export function activate(context: ActivationContext) {
|
||||
ExtensionHost.log(`Extension activated in ${context.extensionDirectory}`);
|
||||
return new MyProvider();
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Build and test
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Extension Architecture
|
||||
|
||||
### How it works
|
||||
|
||||
```
|
||||
┌─────────────────────┐ JSONRPC/stdio ┌──────────────────┐
|
||||
│ Command Palette │ ◄───────────────────────► │ Node.js Host │
|
||||
│ (C# / WinUI) │ │ │
|
||||
│ │ │ ┌────────────┐ │
|
||||
│ JSONRPCExtension- │ provider/getCommands ──► │ │ Extension1 │ │
|
||||
│ Service │ ◄── { items: [...] } │ └────────────┘ │
|
||||
│ │ │ ┌────────────┐ │
|
||||
│ │ command/invoke ────────► │ │ Extension2 │ │
|
||||
│ │ ◄── { kind: "toast" } │ └────────────┘ │
|
||||
└─────────────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
1. CmdPal spawns a single Node.js host process at startup
|
||||
2. The host loads all installed JS extensions
|
||||
3. CmdPal communicates with extensions via JSONRPC over stdio
|
||||
4. Your extension responds to requests and can send notifications back
|
||||
|
||||
### Entry Point
|
||||
|
||||
Your extension must export an `activate` function:
|
||||
|
||||
```typescript
|
||||
export function activate(context: ActivationContext): CommandProvider {
|
||||
return new MyProvider();
|
||||
}
|
||||
```
|
||||
|
||||
The `context` provides:
|
||||
- `extensionId` — Your extension's unique identifier
|
||||
- `extensionDirectory` — Absolute path to your extension's install directory
|
||||
|
||||
### CommandProvider
|
||||
|
||||
The `CommandProvider` is the root of your extension. It must implement:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `topLevelCommands()` | Returns items shown in the main palette |
|
||||
| `fallbackCommands()` | (Optional) Returns fallback search handlers |
|
||||
| `getCommand(id)` | (Optional) Returns a command/page by ID |
|
||||
| `initializeWithHost(host)` | (Optional) Called with the extension host API |
|
||||
| `dispose()` | (Optional) Cleanup when extension is unloaded |
|
||||
|
||||
## Page Types
|
||||
|
||||
### ListPage
|
||||
|
||||
Displays a searchable list of items:
|
||||
|
||||
```typescript
|
||||
class MyList extends ListPageBase {
|
||||
id = 'my-list';
|
||||
name = 'My List';
|
||||
title = 'Items';
|
||||
placeholderText = 'Search...';
|
||||
showDetails = true;
|
||||
|
||||
getItems(): IListItem[] {
|
||||
return [/* items */];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DynamicListPage
|
||||
|
||||
A list where your extension handles filtering:
|
||||
|
||||
```typescript
|
||||
class SearchPage extends DynamicListPageBase {
|
||||
id = 'search';
|
||||
name = 'Search';
|
||||
title = 'Search';
|
||||
private query = '';
|
||||
|
||||
setSearchText(text: string) {
|
||||
this.query = text;
|
||||
// Items will be re-fetched automatically
|
||||
}
|
||||
|
||||
async getItems() {
|
||||
return await myApi.search(this.query);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ContentPage
|
||||
|
||||
Displays rich content (markdown, forms, images):
|
||||
|
||||
```typescript
|
||||
class AboutPage extends ContentPageBase {
|
||||
id = 'about';
|
||||
name = 'About';
|
||||
title = 'About My Extension';
|
||||
|
||||
getContent(): Content[] {
|
||||
return [
|
||||
{ type: 'markdown', body: '# Hello\n\nThis is **markdown** content.' },
|
||||
{ type: 'image', image: { light: { icon: 'https://...' } }, maxWidth: 300 },
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### InvokableCommand
|
||||
|
||||
A command that performs an action:
|
||||
|
||||
```typescript
|
||||
class CopyCommand extends InvokableCommandBase {
|
||||
id = 'copy';
|
||||
name = 'Copy to Clipboard';
|
||||
|
||||
async invoke(): Promise<CommandResult> {
|
||||
// Do your action here
|
||||
return { kind: 'showToast', args: { message: 'Copied!' } };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CommandResult
|
||||
|
||||
After invoking, return a result to control navigation:
|
||||
|
||||
| Kind | Description |
|
||||
|------|-------------|
|
||||
| `dismiss` | Close the palette |
|
||||
| `goHome` | Return to the main page, keep palette open |
|
||||
| `goBack` | Go back one page |
|
||||
| `hide` | Hide palette but keep current page |
|
||||
| `keepOpen` | Do nothing (stay on current page) |
|
||||
| `goToPage` | Navigate to another page (`args: { pageId, navigationMode }`) |
|
||||
| `showToast` | Show a toast message (`args: { message }`) |
|
||||
| `confirm` | Show a confirmation dialog |
|
||||
|
||||
## Logging & Status
|
||||
|
||||
Use `ExtensionHost` to communicate with CmdPal:
|
||||
|
||||
```typescript
|
||||
import { ExtensionHost } from '@microsoft/cmdpal-sdk';
|
||||
|
||||
// Logging (visible in CmdPal logs)
|
||||
ExtensionHost.log('Something happened');
|
||||
ExtensionHost.log('Something failed', 'error');
|
||||
|
||||
// Status bar messages
|
||||
ExtensionHost.showStatus('Loading data...', 'info', { isIndeterminate: true });
|
||||
ExtensionHost.hideStatus('loading-id');
|
||||
```
|
||||
|
||||
## Publishing to the Gallery
|
||||
|
||||
### 1. Publish to npm
|
||||
|
||||
```bash
|
||||
npm publish --access public
|
||||
```
|
||||
|
||||
### 2. Submit to the gallery
|
||||
|
||||
Create a PR to [microsoft/CmdPal-Extensions](https://github.com/microsoft/CmdPal-Extensions) adding your extension to the gallery manifest:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "yourname.my-extension",
|
||||
"title": "My Extension",
|
||||
"shortDescription": "A brief description",
|
||||
"description": "Full description of what your extension does...",
|
||||
"author": { "name": "Your Name", "url": "https://github.com/yourname" },
|
||||
"installSources": [
|
||||
{ "type": "npm", "id": "@yourname/my-extension" }
|
||||
],
|
||||
"iconUrl": "https://raw.githubusercontent.com/.../icon.png",
|
||||
"tags": ["productivity"],
|
||||
"categories": ["utilities-and-tools"],
|
||||
"addedAt": "2026-01-01"
|
||||
}
|
||||
```
|
||||
|
||||
### Install source types
|
||||
|
||||
| Type | Description | Required fields |
|
||||
|------|-------------|-----------------|
|
||||
| `npm` | npm package | `id` (package name) |
|
||||
| `winget` | WinGet package | `id` (winget ID) |
|
||||
| `msstore` | Microsoft Store | `id` (store product ID) |
|
||||
|
||||
## Extension Settings
|
||||
|
||||
Extensions can provide a settings page by implementing `settings` on the provider:
|
||||
|
||||
```typescript
|
||||
class SettingsPage extends ContentPageBase {
|
||||
id = 'settings';
|
||||
name = 'Settings';
|
||||
title = 'My Extension Settings';
|
||||
|
||||
getContent(): Content[] {
|
||||
return [{
|
||||
type: 'form',
|
||||
templateJson: JSON.stringify({
|
||||
type: 'AdaptiveCard',
|
||||
body: [
|
||||
{ type: 'Input.Text', id: 'apiKey', label: 'API Key' }
|
||||
],
|
||||
actions: [{ type: 'Action.Submit', title: 'Save' }]
|
||||
}),
|
||||
dataJson: JSON.stringify({ apiKey: '' }),
|
||||
submitForm(inputs: string, data: string) {
|
||||
// Save settings
|
||||
return { kind: 'showToast', args: { message: 'Settings saved!' } };
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
class MyProvider extends CommandProviderBase {
|
||||
// ...
|
||||
settings = { settingsPage: new SettingsPage() };
|
||||
}
|
||||
```
|
||||
|
||||
## Notifications (Push Updates)
|
||||
|
||||
Extensions can notify CmdPal when data changes, triggering a re-fetch:
|
||||
|
||||
```typescript
|
||||
// In DynamicListPageBase subclasses:
|
||||
this.notifyItemsChanged(); // triggers page/getItems re-call
|
||||
|
||||
// Or use the connection directly (advanced):
|
||||
// The SDK wires this up through the JSONRPC bridge
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
A typical extension project:
|
||||
|
||||
```
|
||||
my-extension/
|
||||
├── package.json # npm config + cmdpal field
|
||||
├── tsconfig.json # TypeScript config
|
||||
├── src/
|
||||
│ └── index.ts # Extension entry point
|
||||
├── dist/ # Compiled output (git-ignored)
|
||||
│ └── index.js
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Extension not loading?
|
||||
1. Check that `cmdpal.main` (or `main`) in package.json points to a valid JS file
|
||||
2. Verify your `activate()` function returns a valid `CommandProvider`
|
||||
3. Check CmdPal logs for error messages from your extension
|
||||
|
||||
### Commands not appearing?
|
||||
1. Ensure `topLevelCommands()` returns at least one item
|
||||
2. Verify the extension is enabled in CmdPal settings
|
||||
3. Check that the `getCommand(id)` method returns pages/commands referenced by your items
|
||||
|
||||
### Logs
|
||||
Extension logs are written to:
|
||||
`%LOCALAPPDATA%\Microsoft\PowerToys\CmdPal\logs\`
|
||||
|
||||
## API Reference
|
||||
|
||||
See the [TypeScript SDK README](../ts-sdk/README.md) for the complete API surface.
|
||||
|
||||
See the [JSONRPC Protocol Specification](../docs/jsonrpc-protocol.md) for the wire protocol details.
|
||||
652
src/modules/cmdpal/doc/js-extensions/jsonrpc-protocol.md
Normal file
652
src/modules/cmdpal/doc/js-extensions/jsonrpc-protocol.md
Normal file
@@ -0,0 +1,652 @@
|
||||
# Command Palette JSONRPC Extension Protocol
|
||||
|
||||
This document defines the JSONRPC 2.0 protocol used between the Command Palette host (C#) and the Node.js extension host process.
|
||||
|
||||
## Transport
|
||||
|
||||
- **Transport:** stdio (stdin/stdout)
|
||||
- **Framing:** Header-delimited (Content-Length), matching the LSP/DAP convention
|
||||
- **Direction:** Bidirectional — both host and extensions can send requests and notifications
|
||||
|
||||
## Lifecycle
|
||||
|
||||
1. CmdPal spawns the Node host process
|
||||
2. Host sends `initialize` request with configuration
|
||||
3. Node host responds with capabilities
|
||||
4. Host sends `extensions/load` with list of extensions to activate
|
||||
5. Node host loads each extension, responds with success/failure per extension
|
||||
6. Normal operation: host sends requests, extensions respond; extensions send notifications
|
||||
|
||||
## Conventions
|
||||
|
||||
- All method names use `/` as namespace separator
|
||||
- Request IDs are integers, auto-incremented by the sender
|
||||
- Extension-specific requests include an `extensionId` field to route to the correct extension
|
||||
- All property values use camelCase JSON naming
|
||||
|
||||
---
|
||||
|
||||
## Host → Node Requests
|
||||
|
||||
### `initialize`
|
||||
|
||||
Sent once after process spawn to configure the Node host.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"hostVersion": "0.100.0",
|
||||
"extensionsDirectory": "C:\\Users\\...\\extensions",
|
||||
"logsDirectory": "C:\\Users\\...\\logs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"protocolVersion": "1.0.0",
|
||||
"hostReady": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `extensions/load`
|
||||
|
||||
Instructs the Node host to load a set of extensions.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "extensions/load",
|
||||
"params": {
|
||||
"extensions": [
|
||||
{
|
||||
"id": "cooldev.weather",
|
||||
"packageName": "@cooldev/weather-extension",
|
||||
"entryPoint": "dist/index.js",
|
||||
"directory": "C:\\Users\\...\\extensions\\@cooldev-weather-extension"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"result": {
|
||||
"loaded": [
|
||||
{ "id": "cooldev.weather", "success": true }
|
||||
],
|
||||
"failed": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `extensions/unload`
|
||||
|
||||
Instructs the Node host to unload a specific extension.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"method": "extensions/unload",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `provider/getTopLevelCommands`
|
||||
|
||||
Maps to `ICommandProvider.TopLevelCommands()`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "provider/getTopLevelCommands",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"result": {
|
||||
"items": [
|
||||
{
|
||||
"command": {
|
||||
"id": "weather-now",
|
||||
"name": "Current Weather",
|
||||
"icon": { "light": { "icon": "\uE9CA" }, "dark": { "icon": "\uE9CA" } }
|
||||
},
|
||||
"title": "Current Weather",
|
||||
"subtitle": "Get current weather for your location",
|
||||
"icon": { "light": { "icon": "\uE9CA" }, "dark": { "icon": "\uE9CA" } }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `provider/getFallbackCommands`
|
||||
|
||||
Maps to `ICommandProvider.FallbackCommands()`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 5,
|
||||
"method": "provider/getFallbackCommands",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** Same shape as `provider/getTopLevelCommands` but items include `fallbackHandler` field.
|
||||
|
||||
### `provider/getSettings`
|
||||
|
||||
Maps to `ICommandProvider.Settings`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 6,
|
||||
"method": "provider/getSettings",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 6,
|
||||
"result": {
|
||||
"settingsPage": {
|
||||
"id": "weather-settings",
|
||||
"name": "Weather Settings",
|
||||
"title": "Weather Settings",
|
||||
"content": [...]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `command/invoke`
|
||||
|
||||
Maps to `IInvokableCommand.Invoke()`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 7,
|
||||
"method": "command/invoke",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"commandId": "weather-now"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 7,
|
||||
"result": {
|
||||
"kind": "goToPage",
|
||||
"args": {
|
||||
"pageId": "weather-detail",
|
||||
"navigationMode": "push"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `page/getItems`
|
||||
|
||||
Maps to `IListPage.GetItems()`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 8,
|
||||
"method": "page/getItems",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"pageId": "weather-list"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 8,
|
||||
"result": {
|
||||
"items": [
|
||||
{
|
||||
"command": { "id": "city-nyc", "name": "New York" },
|
||||
"title": "New York",
|
||||
"subtitle": "72°F, Sunny",
|
||||
"tags": [{ "text": "Favorite", "icon": null }],
|
||||
"section": "Favorites"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `page/setSearchText`
|
||||
|
||||
Maps to `IDynamicListPage.SearchText { set }`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 9,
|
||||
"method": "page/setSearchText",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"pageId": "weather-list",
|
||||
"searchText": "new york"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 9,
|
||||
"result": { "accepted": true }
|
||||
}
|
||||
```
|
||||
|
||||
### `page/loadMore`
|
||||
|
||||
Maps to `IListPage.LoadMore()`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 10,
|
||||
"method": "page/loadMore",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"pageId": "weather-list"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `page/getContent`
|
||||
|
||||
Maps to `IContentPage.GetContent()`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 11,
|
||||
"method": "page/getContent",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"pageId": "weather-detail"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 11,
|
||||
"result": {
|
||||
"content": [
|
||||
{ "type": "markdown", "body": "## Current Weather\n\n72°F and sunny" },
|
||||
{ "type": "image", "image": { "light": { "icon": "https://..." } }, "maxWidth": 200, "maxHeight": 200 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `page/getProperties`
|
||||
|
||||
Gets page-level properties (title, isLoading, placeholderText, showDetails, filters, gridProperties, etc.).
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 12,
|
||||
"method": "page/getProperties",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"pageId": "weather-list"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 12,
|
||||
"result": {
|
||||
"title": "Weather",
|
||||
"isLoading": false,
|
||||
"placeholderText": "Search cities...",
|
||||
"showDetails": true,
|
||||
"hasMoreItems": false,
|
||||
"filters": {
|
||||
"currentFilterId": "all",
|
||||
"filters": [
|
||||
{ "id": "all", "name": "All" },
|
||||
{ "id": "favorites", "name": "Favorites" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `form/submit`
|
||||
|
||||
Maps to `IFormContent.SubmitForm()`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 13,
|
||||
"method": "form/submit",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"pageId": "settings-form",
|
||||
"inputs": "{ \"apiKey\": \"abc123\" }",
|
||||
"data": "{ \"location\": \"NYC\" }"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 13,
|
||||
"result": {
|
||||
"kind": "dismiss"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `fallback/updateQuery`
|
||||
|
||||
Maps to `IFallbackHandler.UpdateQuery()`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 14,
|
||||
"method": "fallback/updateQuery",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"commandId": "weather-fallback",
|
||||
"query": "weather in paris"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `provider/getCommand`
|
||||
|
||||
Maps to `ICommandProvider.GetCommand(id)`.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 15,
|
||||
"method": "provider/getCommand",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"commandId": "weather-now"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Node → Host Notifications
|
||||
|
||||
These are fire-and-forget messages from extensions to the host.
|
||||
|
||||
### `notify/itemsChanged`
|
||||
|
||||
Extension signals that its list items have changed. Host should re-fetch.
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notify/itemsChanged",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"pageId": "weather-list",
|
||||
"totalItems": 15
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `notify/propChanged`
|
||||
|
||||
Extension signals that a property has changed on a command/page.
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notify/propChanged",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"objectId": "weather-now",
|
||||
"propertyName": "title"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `notify/commandsChanged`
|
||||
|
||||
Extension signals that its top-level commands have changed. Host should re-fetch.
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notify/commandsChanged",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `host/log`
|
||||
|
||||
Extension sends a log message.
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "host/log",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"state": "info",
|
||||
"message": "Fetched weather data for 5 cities"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`state` is one of: `"info"`, `"success"`, `"warning"`, `"error"`.
|
||||
|
||||
### `host/showStatus`
|
||||
|
||||
Extension requests a status message be shown.
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "host/showStatus",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"context": "page",
|
||||
"message": "Loading weather data...",
|
||||
"state": "info",
|
||||
"progress": { "isIndeterminate": true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `host/hideStatus`
|
||||
|
||||
Extension requests a status message be hidden.
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "host/hideStatus",
|
||||
"params": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"messageId": "loading-weather"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Types
|
||||
|
||||
### IconInfo
|
||||
```json
|
||||
{
|
||||
"light": { "icon": "<glyph or URL>", "data": null },
|
||||
"dark": { "icon": "<glyph or URL>", "data": null }
|
||||
}
|
||||
```
|
||||
|
||||
### CommandResultKind (enum string)
|
||||
`"dismiss"` | `"goHome"` | `"goBack"` | `"hide"` | `"keepOpen"` | `"goToPage"` | `"showToast"` | `"confirm"`
|
||||
|
||||
### NavigationMode (enum string)
|
||||
`"push"` | `"goBack"` | `"goHome"`
|
||||
|
||||
### MessageState (enum string)
|
||||
`"info"` | `"success"` | `"warning"` | `"error"`
|
||||
|
||||
### StatusContext (enum string)
|
||||
`"page"` | `"extension"`
|
||||
|
||||
### ContentType (discriminator for content array items)
|
||||
`"markdown"` | `"form"` | `"tree"` | `"plainText"` | `"image"`
|
||||
|
||||
### Tag
|
||||
```json
|
||||
{
|
||||
"icon": <IconInfo | null>,
|
||||
"text": "string",
|
||||
"foreground": { "hasValue": true, "color": { "r": 255, "g": 0, "b": 0, "a": 255 } },
|
||||
"background": null,
|
||||
"toolTip": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Details
|
||||
```json
|
||||
{
|
||||
"heroImage": <IconInfo | null>,
|
||||
"title": "string",
|
||||
"body": "string",
|
||||
"metadata": [
|
||||
{ "key": "Author", "data": { "type": "tags", "tags": [...] } },
|
||||
{ "key": "Link", "data": { "type": "link", "link": "https://...", "text": "Homepage" } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Filter
|
||||
```json
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"icon": <IconInfo | null>
|
||||
}
|
||||
```
|
||||
|
||||
### GridProperties
|
||||
```json
|
||||
{
|
||||
"type": "small" | "medium" | "gallery",
|
||||
"showTitle": true,
|
||||
"showSubtitle": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
JSONRPC errors use standard error codes:
|
||||
- `-32700`: Parse error
|
||||
- `-32600`: Invalid request
|
||||
- `-32601`: Method not found
|
||||
- `-32602`: Invalid params
|
||||
- `-32603`: Internal error
|
||||
|
||||
Custom error codes (extension-specific):
|
||||
- `-32000`: Extension not found
|
||||
- `-32001`: Extension not loaded
|
||||
- `-32002`: Command not found
|
||||
- `-32003`: Page not found
|
||||
- `-32004`: Extension threw an exception (details in `data` field)
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 7,
|
||||
"error": {
|
||||
"code": -32004,
|
||||
"message": "Extension threw an exception",
|
||||
"data": {
|
||||
"extensionId": "cooldev.weather",
|
||||
"stack": "Error: API key invalid\n at ..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user