emoemu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +77 -0
- package/.node-version +1 -0
- package/CLAUDE.md +435 -0
- package/README.md +404 -0
- package/TODO.md +655 -0
- package/dist/index.cjs +25108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25085 -0
- package/docs/building-libretro-cores-arm-mac.md +237 -0
- package/docs/config-file-format.md +488 -0
- package/docs/cores-trd.md +425 -0
- package/docs/headless-hardware-rendering-trd.md +676 -0
- package/docs/libretro-cores-trd.md +997 -0
- package/docs/mupen64-software-rendering-trd.md +751 -0
- package/docs/n64-support-trd.md +306 -0
- package/docs/native-rendering-trd.md +540 -0
- package/docs/native-ui-rendering-trd.md +1195 -0
- package/docs/netplay-trd.md +665 -0
- package/docs/retroarch-netplay-docs.md +277 -0
- package/docs/save-state-format.md +666 -0
- package/eslint.config.js +111 -0
- package/icon/icon.png +0 -0
- package/icon/icon.pxd +0 -0
- package/package.json +63 -0
- package/pnpm-workspace.yaml +10 -0
- package/src/Emulator/consts.ts +14 -0
- package/src/Emulator/index.ts +2496 -0
- package/src/Emulator/saveState/index.ts +155 -0
- package/src/Emulator/screenshot/index.ts +160 -0
- package/src/Emulator/terminalDimensions/index.ts +79 -0
- package/src/Emulator/types.ts +83 -0
- package/src/cli/commands/consts.ts +10 -0
- package/src/cli/commands/index.ts +462 -0
- package/src/cli/parseArgs/consts.ts +17 -0
- package/src/cli/parseArgs/index.ts +457 -0
- package/src/cli/parseArgs/types.ts +61 -0
- package/src/cli/runEmulator/index.ts +406 -0
- package/src/cli/runEmulator/types.ts +7 -0
- package/src/consts.ts +19 -0
- package/src/core/button/consts.ts +35 -0
- package/src/core/button/index.ts +123 -0
- package/src/core/core.ts +300 -0
- package/src/core/index.ts +19 -0
- package/src/cores/libretro/api/index.ts +334 -0
- package/src/cores/libretro/api/types.ts +148 -0
- package/src/cores/libretro/callbacks/consts.ts +41 -0
- package/src/cores/libretro/callbacks/index.ts +456 -0
- package/src/cores/libretro/consts.ts +45 -0
- package/src/cores/libretro/coreOptions/consts.ts +36 -0
- package/src/cores/libretro/coreOptions/index.ts +222 -0
- package/src/cores/libretro/environment/consts.ts +118 -0
- package/src/cores/libretro/environment/index.ts +1095 -0
- package/src/cores/libretro/index.ts +937 -0
- package/src/cores/libretro/loader/index.ts +496 -0
- package/src/cores/libretro/pixelFormat/consts.ts +43 -0
- package/src/cores/libretro/pixelFormat/index.ts +397 -0
- package/src/cores/libretro/types.ts +339 -0
- package/src/frontend/AudioManager/index.ts +420 -0
- package/src/frontend/SettingsManager/index.ts +250 -0
- package/src/frontend/config/index.ts +608 -0
- package/src/frontend/config/tests.ts +354 -0
- package/src/frontend/config/types.ts +36 -0
- package/src/frontend/consts.ts +114 -0
- package/src/frontend/coreBuilder/index.ts +644 -0
- package/src/frontend/coreBuilder/types.ts +15 -0
- package/src/frontend/coreDownloader/index.ts +620 -0
- package/src/frontend/coreDownloader/types.ts +17 -0
- package/src/frontend/corePreferences/index.ts +69 -0
- package/src/frontend/coreRegistry/index.ts +276 -0
- package/src/frontend/directoryCache/index.ts +75 -0
- package/src/frontend/index.ts +79 -0
- package/src/frontend/notifications/consts.ts +14 -0
- package/src/frontend/notifications/index.ts +250 -0
- package/src/frontend/playlist/consts.ts +168 -0
- package/src/frontend/playlist/index.ts +899 -0
- package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
- package/src/frontend/playlist/labelFormatter/index.ts +155 -0
- package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
- package/src/frontend/playlist/reader/index.ts +559 -0
- package/src/frontend/playlist/sync/index.ts +511 -0
- package/src/frontend/playlist/systemLookup/index.ts +233 -0
- package/src/frontend/playlist/utils/index.ts +50 -0
- package/src/frontend/romScanner/consts.ts +348 -0
- package/src/frontend/romScanner/index.ts +1957 -0
- package/src/frontend/saveServices/consts.ts +2 -0
- package/src/frontend/saveServices/index.ts +313 -0
- package/src/frontend/serviceProvider/index.ts +108 -0
- package/src/frontend/serviceProvider/types.ts +13 -0
- package/src/index.ts +428 -0
- package/src/input/Controller/consts.ts +50 -0
- package/src/input/Controller/index.ts +81 -0
- package/src/input/GamepadManager/consts.ts +22 -0
- package/src/input/GamepadManager/index.ts +418 -0
- package/src/input/InputManager/consts.ts +86 -0
- package/src/input/InputManager/index.ts +593 -0
- package/src/input/InputMapper/consts.ts +33 -0
- package/src/input/InputMapper/index.ts +436 -0
- package/src/input/consts.ts +410 -0
- package/src/input/gamepadProfiles/index.ts +1100 -0
- package/src/input/index.ts +38 -0
- package/src/input/inputUtils/index.ts +31 -0
- package/src/netplay/FrameBuffer/consts.ts +2 -0
- package/src/netplay/FrameBuffer/index.ts +364 -0
- package/src/netplay/FrameBuffer/tests.ts +286 -0
- package/src/netplay/InputBuffer/consts.ts +7 -0
- package/src/netplay/InputBuffer/index.ts +347 -0
- package/src/netplay/InputBuffer/tests.ts +166 -0
- package/src/netplay/NetplayClient/index.ts +976 -0
- package/src/netplay/NetplayConnection/index.ts +536 -0
- package/src/netplay/NetplayDiscovery/consts.ts +41 -0
- package/src/netplay/NetplayDiscovery/index.ts +525 -0
- package/src/netplay/NetplayServer/index.ts +1407 -0
- package/src/netplay/SyncManager/index.ts +984 -0
- package/src/netplay/SyncManager/tests.ts +419 -0
- package/src/netplay/consts.ts +371 -0
- package/src/netplay/crc32/consts.ts +14 -0
- package/src/netplay/crc32/index.ts +97 -0
- package/src/netplay/crc32/tests.ts +40 -0
- package/src/netplay/index.ts +41 -0
- package/src/netplay/netplayLogger/consts.ts +30 -0
- package/src/netplay/netplayLogger/index.ts +345 -0
- package/src/netplay/protocol/consts.ts +86 -0
- package/src/netplay/protocol/index.ts +1280 -0
- package/src/netplay/protocol/tests.ts +606 -0
- package/src/netplay/protocol/types.ts +20 -0
- package/src/netplay/types.ts +395 -0
- package/src/rendering/KittyRenderer/index.ts +616 -0
- package/src/rendering/NativeRenderer/index.ts +279 -0
- package/src/rendering/NativeRenderer/tests.ts +133 -0
- package/src/rendering/TerminalRenderer/index.ts +770 -0
- package/src/rendering/consts.ts +401 -0
- package/src/rendering/fonts/CozetteVector.ttf +0 -0
- package/src/rendering/index.ts +26 -0
- package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
- package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
- package/src/rendering/nativeUi/consts.ts +6 -0
- package/src/rendering/nativeUi/index.ts +20 -0
- package/src/rendering/postProcessing/consts.ts +38 -0
- package/src/rendering/postProcessing/index.ts +923 -0
- package/src/rendering/shared/ansi/consts.ts +12 -0
- package/src/rendering/shared/ansi/index.ts +104 -0
- package/src/rendering/shared/consts.ts +2 -0
- package/src/rendering/shared/fitToTerminal/index.ts +67 -0
- package/src/ui/AddRomsPrompt/consts.ts +13 -0
- package/src/ui/AddRomsPrompt/index.tsx +781 -0
- package/src/ui/App/consts.ts +2 -0
- package/src/ui/App/index.tsx +456 -0
- package/src/ui/AppCapabilities/index.tsx +67 -0
- package/src/ui/ConfigContext/index.tsx +56 -0
- package/src/ui/CoreManager/consts.ts +11 -0
- package/src/ui/CoreManager/index.tsx +779 -0
- package/src/ui/CoreSelector/consts.ts +2 -0
- package/src/ui/CoreSelector/index.tsx +251 -0
- package/src/ui/DialogContainer/index.tsx +42 -0
- package/src/ui/DialogOptionsList/index.tsx +61 -0
- package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
- package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
- package/src/ui/GamepadContext/consts.ts +15 -0
- package/src/ui/GamepadContext/index.tsx +295 -0
- package/src/ui/NativeDialog/index.tsx +120 -0
- package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
- package/src/ui/NetplayPauseMenu/consts.ts +2 -0
- package/src/ui/NetplayPauseMenu/index.tsx +97 -0
- package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
- package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
- package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
- package/src/ui/RomBrowser/consts.ts +61 -0
- package/src/ui/RomBrowser/index.tsx +1164 -0
- package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
- package/src/ui/RomBrowser/types.ts +67 -0
- package/src/ui/SaveStateDialog/consts.ts +2 -0
- package/src/ui/SaveStateDialog/index.tsx +225 -0
- package/src/ui/WarningDialog/index.tsx +113 -0
- package/src/ui/consts.ts +27 -0
- package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
- package/src/ui/hooks/useClearTerminal/index.ts +37 -0
- package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
- package/src/ui/hooks/useGamepad/consts.ts +21 -0
- package/src/ui/hooks/useGamepad/index.ts +194 -0
- package/src/ui/index.ts +27 -0
- package/src/utils/buffer/consts.ts +17 -0
- package/src/utils/buffer/index.ts +129 -0
- package/src/utils/color/consts.ts +58 -0
- package/src/utils/color/index.ts +183 -0
- package/src/utils/compression/consts.ts +50 -0
- package/src/utils/compression/index.ts +101 -0
- package/src/utils/consts.ts +2 -0
- package/src/utils/crc32/consts.ts +22 -0
- package/src/utils/crc32/index.ts +83 -0
- package/src/utils/ensureDirectory/index.ts +10 -0
- package/src/utils/format/consts.ts +8 -0
- package/src/utils/format/index.ts +53 -0
- package/src/utils/getErrorMessage/index.ts +10 -0
- package/src/utils/index.ts +113 -0
- package/src/utils/ini/index.ts +200 -0
- package/src/utils/kitty/consts.ts +13 -0
- package/src/utils/kitty/index.ts +181 -0
- package/src/utils/logger/consts.ts +35 -0
- package/src/utils/logger/index.ts +217 -0
- package/src/utils/paths/consts.ts +18 -0
- package/src/utils/paths/index.ts +151 -0
- package/src/utils/png/consts.ts +34 -0
- package/src/utils/png/index.ts +131 -0
- package/src/utils/readJsonFile/index.ts +16 -0
- package/src/utils/rotateLogFile/index.ts +44 -0
- package/src/utils/safeClose/index.ts +10 -0
- package/src/utils/terminal/consts.ts +8 -0
- package/src/utils/terminal/index.ts +102 -0
- package/src/utils/thumbnailRenderer/consts.ts +2 -0
- package/src/utils/thumbnailRenderer/index.ts +147 -0
- package/src/utils/typedError/index.ts +26 -0
- package/tsconfig.json +31 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guards for validating partial libretro structs decoded from FFI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isString, isNumber, isBoolean, isPlainObject } from 'remeda';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Type guard that validates a value is a plain object with string keys.
|
|
9
|
+
* Unlike isPlainObject, this narrows to Record<string, unknown> which
|
|
10
|
+
* allows safe property access with string keys.
|
|
11
|
+
*/
|
|
12
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => {
|
|
13
|
+
if (!isPlainObject(value)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
// Verify all keys are strings (not symbols)
|
|
17
|
+
return Object.keys(value).every((key) => typeof key === 'string');
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Partial RetroGameGeometry as decoded from koffi.
|
|
22
|
+
* All properties are optional since FFI decoding may fail.
|
|
23
|
+
*/
|
|
24
|
+
export interface PartialRetroGameGeometry {
|
|
25
|
+
base_width?: number;
|
|
26
|
+
base_height?: number;
|
|
27
|
+
max_width?: number;
|
|
28
|
+
max_height?: number;
|
|
29
|
+
aspect_ratio?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Type guard for partial RetroGameGeometry from FFI decoding.
|
|
34
|
+
*/
|
|
35
|
+
export const isPartialRetroGameGeometry = (value: unknown): value is PartialRetroGameGeometry => {
|
|
36
|
+
if (!isRecord(value)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if ('base_width' in value && !isNumber(value.base_width)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if ('base_height' in value && !isNumber(value.base_height)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if ('max_width' in value && !isNumber(value.max_width)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if ('max_height' in value && !isNumber(value.max_height)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if ('aspect_ratio' in value && !isNumber(value.aspect_ratio)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return true;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Partial RetroSystemTiming as decoded from koffi.
|
|
61
|
+
*/
|
|
62
|
+
export interface PartialRetroSystemTiming {
|
|
63
|
+
fps?: number;
|
|
64
|
+
sample_rate?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Type guard for partial RetroSystemTiming from FFI decoding.
|
|
69
|
+
*/
|
|
70
|
+
export const isPartialRetroSystemTiming = (value: unknown): value is PartialRetroSystemTiming => {
|
|
71
|
+
if (!isRecord(value)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if ('fps' in value && !isNumber(value.fps)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if ('sample_rate' in value && !isNumber(value.sample_rate)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Partial RetroSystemAVInfo as decoded from koffi.
|
|
87
|
+
* Contains nested geometry and timing objects.
|
|
88
|
+
*/
|
|
89
|
+
export interface PartialRetroSystemAVInfo {
|
|
90
|
+
geometry?: PartialRetroGameGeometry;
|
|
91
|
+
timing?: PartialRetroSystemTiming;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Type guard for partial RetroSystemAVInfo from FFI decoding.
|
|
96
|
+
*/
|
|
97
|
+
export const isPartialRetroSystemAVInfo = (value: unknown): value is PartialRetroSystemAVInfo => {
|
|
98
|
+
if (!isRecord(value)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if ('geometry' in value && !isPartialRetroGameGeometry(value.geometry)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if ('timing' in value && !isPartialRetroSystemTiming(value.timing)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return true;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Partial RetroSystemInfo as decoded from koffi.
|
|
114
|
+
*/
|
|
115
|
+
export interface PartialRetroSystemInfo {
|
|
116
|
+
library_name?: string;
|
|
117
|
+
library_version?: string;
|
|
118
|
+
valid_extensions?: string;
|
|
119
|
+
need_fullpath?: boolean;
|
|
120
|
+
block_extract?: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Type guard for partial RetroSystemInfo from FFI decoding.
|
|
125
|
+
*/
|
|
126
|
+
export const isPartialRetroSystemInfo = (value: unknown): value is PartialRetroSystemInfo => {
|
|
127
|
+
if (!isRecord(value)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if ('library_name' in value && !isString(value.library_name)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if ('library_version' in value && !isString(value.library_version)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if ('valid_extensions' in value && !isString(value.valid_extensions)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
if ('need_fullpath' in value && !isBoolean(value.need_fullpath)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
if ('block_extract' in value && !isBoolean(value.block_extract)) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return true;
|
|
148
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Audio Constants
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
/** Initial audio buffer capacity in samples (stereo frames * 2 channels) */
|
|
6
|
+
export const INITIAL_AUDIO_BUFFER_SIZE = 4096;
|
|
7
|
+
|
|
8
|
+
/** Maximum value for signed 16-bit audio samples */
|
|
9
|
+
export const INT16_MAX = 32768;
|
|
10
|
+
|
|
11
|
+
/** Audio buffer growth factor (1.5x is more memory-efficient than 2x) */
|
|
12
|
+
export const AUDIO_BUFFER_GROWTH_FACTOR = 1.5;
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Input Constants
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
/** Libretro RETRO_DEVICE_ID_JOYPAD_MASK value for bitmask input */
|
|
19
|
+
export const JOYPAD_BITMASK_ID = 256;
|
|
20
|
+
|
|
21
|
+
/** Minimum value for signed 16-bit (used for analog clamping) */
|
|
22
|
+
export const INT16_MIN = -32768;
|
|
23
|
+
|
|
24
|
+
/** Maximum positive value for signed 16-bit (used for analog normalization) */
|
|
25
|
+
export const INT16_MAX_POSITIVE = 32767;
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Debug Logging Constants
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/** Number of initial video callbacks to log with detailed info */
|
|
32
|
+
export const DEBUG_VIDEO_CALLBACK_COUNT = 10;
|
|
33
|
+
|
|
34
|
+
/** Number of initial frames to always log video frame info */
|
|
35
|
+
export const DEBUG_INITIAL_FRAMES_TO_LOG = 3;
|
|
36
|
+
|
|
37
|
+
/** Frames between periodic video frame log messages (every N frames) */
|
|
38
|
+
export const DEBUG_VIDEO_FRAME_LOG_INTERVAL = 60;
|
|
39
|
+
|
|
40
|
+
/** Minimum analog value change threshold to trigger debug logging */
|
|
41
|
+
export const DEBUG_ANALOG_CHANGE_THRESHOLD = 1000;
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callback manager for libretro cores
|
|
3
|
+
* Handles video, audio, and input callbacks from native code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { clamp } from 'remeda';
|
|
7
|
+
import koffi from "koffi";
|
|
8
|
+
import type { LibretroAPI, KoffiCallback } from "../api";
|
|
9
|
+
import {
|
|
10
|
+
retro_environment_t,
|
|
11
|
+
retro_video_refresh_t,
|
|
12
|
+
retro_audio_sample_t,
|
|
13
|
+
retro_audio_sample_batch_t,
|
|
14
|
+
retro_input_poll_t,
|
|
15
|
+
retro_input_state_t,
|
|
16
|
+
} from "../api";
|
|
17
|
+
import type { EnvironmentHandler } from "../environment";
|
|
18
|
+
import { RETRO_DEVICE, RETRO_DEVICE_INDEX_ANALOG, RETRO_DEVICE_ID_ANALOG, FRAMEBUFFER_HEADROOM } from "..";
|
|
19
|
+
import { logger } from "@/utils/logger";
|
|
20
|
+
|
|
21
|
+
// Callback-specific constants
|
|
22
|
+
import {
|
|
23
|
+
INITIAL_AUDIO_BUFFER_SIZE,
|
|
24
|
+
JOYPAD_BITMASK_ID,
|
|
25
|
+
INT16_MAX,
|
|
26
|
+
INT16_MIN,
|
|
27
|
+
INT16_MAX_POSITIVE,
|
|
28
|
+
AUDIO_BUFFER_GROWTH_FACTOR,
|
|
29
|
+
DEBUG_VIDEO_CALLBACK_COUNT,
|
|
30
|
+
DEBUG_INITIAL_FRAMES_TO_LOG,
|
|
31
|
+
DEBUG_VIDEO_FRAME_LOG_INTERVAL,
|
|
32
|
+
DEBUG_ANALOG_CHANGE_THRESHOLD,
|
|
33
|
+
} from "./consts";
|
|
34
|
+
|
|
35
|
+
export * from './consts';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* CallbackManager handles all callbacks between the libretro core and the frontend
|
|
39
|
+
*/
|
|
40
|
+
export class CallbackManager {
|
|
41
|
+
// Registered koffi callbacks - must keep references to prevent GC
|
|
42
|
+
private environmentCallback: KoffiCallback | null = null;
|
|
43
|
+
private videoCallback: KoffiCallback | null = null;
|
|
44
|
+
private audioSampleCallback: KoffiCallback | null = null;
|
|
45
|
+
private audioBatchCallback: KoffiCallback | null = null;
|
|
46
|
+
private inputPollCallback: KoffiCallback | null = null;
|
|
47
|
+
private inputStateCallback: KoffiCallback | null = null;
|
|
48
|
+
|
|
49
|
+
// Frame data
|
|
50
|
+
framebuffer: Uint8Array | null = null;
|
|
51
|
+
frameWidth = 0;
|
|
52
|
+
frameHeight = 0;
|
|
53
|
+
framePitch = 0;
|
|
54
|
+
private framebufferCapacity = 0;
|
|
55
|
+
|
|
56
|
+
// Audio buffer (grows as needed)
|
|
57
|
+
private audioBufferCapacity = INITIAL_AUDIO_BUFFER_SIZE;
|
|
58
|
+
audioBuffer: Int16Array = new Int16Array(this.audioBufferCapacity);
|
|
59
|
+
audioSamples = 0;
|
|
60
|
+
|
|
61
|
+
// Reusable Float32Array for drainAudio() output to avoid per-frame allocations
|
|
62
|
+
private audioOutputBuffer: Float32Array | null = null;
|
|
63
|
+
private audioOutputCapacity = 0;
|
|
64
|
+
|
|
65
|
+
// Input state per port - use arrays for O(1) access
|
|
66
|
+
// buttonState[port][buttonId] = pressed (boolean), sparse arrays allow undefined
|
|
67
|
+
private buttonState: Array<Array<boolean | undefined> | undefined> = [];
|
|
68
|
+
// Cached bitmask per port - updated on setButtonState() for O(1) bitmask queries
|
|
69
|
+
private buttonBitmask: number[] = [];
|
|
70
|
+
// Analog state per port - analogState[port][index][axis] = value (-32768 to 32767)
|
|
71
|
+
// index: 0=left stick, 1=right stick; axis: 0=X, 1=Y
|
|
72
|
+
private analogState: Array<Array<Array<number> | undefined> | undefined> = [];
|
|
73
|
+
|
|
74
|
+
constructor(private envHandler: EnvironmentHandler) {}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create and register all callbacks with the libretro API
|
|
78
|
+
*/
|
|
79
|
+
createCallbacks(api: LibretroAPI): void {
|
|
80
|
+
// Environment callback - MUST be set before retro_init() for some cores
|
|
81
|
+
this.environmentCallback = koffi.register(
|
|
82
|
+
(cmd: number, data: Buffer | null): boolean => {
|
|
83
|
+
return this.envHandler.handle(cmd, data);
|
|
84
|
+
},
|
|
85
|
+
koffi.pointer(retro_environment_t)
|
|
86
|
+
);
|
|
87
|
+
api.retro_set_environment(this.environmentCallback);
|
|
88
|
+
|
|
89
|
+
// Video refresh callback
|
|
90
|
+
this.videoCallback = koffi.register(
|
|
91
|
+
(
|
|
92
|
+
data: Buffer | null,
|
|
93
|
+
width: number,
|
|
94
|
+
height: number,
|
|
95
|
+
pitch: number
|
|
96
|
+
): void => {
|
|
97
|
+
// Debug: Log callback invocation (first N calls with detailed info)
|
|
98
|
+
if (this.debugFrameCount < DEBUG_VIDEO_CALLBACK_COUNT) {
|
|
99
|
+
const dataInfo = data ? `present (type=${typeof data}, length=${data.length || 'N/A'})` : 'null';
|
|
100
|
+
logger.debug(`Video callback #${this.debugFrameCount + 1}: data=${dataInfo}, ${width}x${height}, pitch=${pitch}`, 'Video');
|
|
101
|
+
}
|
|
102
|
+
this.handleVideoRefresh(data, width, height, pitch);
|
|
103
|
+
},
|
|
104
|
+
koffi.pointer(retro_video_refresh_t)
|
|
105
|
+
);
|
|
106
|
+
api.retro_set_video_refresh(this.videoCallback);
|
|
107
|
+
logger.debug('Video callback registered', 'Video');
|
|
108
|
+
|
|
109
|
+
// Audio sample callback (single sample, stereo)
|
|
110
|
+
this.audioSampleCallback = koffi.register(
|
|
111
|
+
(left: number, right: number): void => {
|
|
112
|
+
this.handleAudioSample(left, right);
|
|
113
|
+
},
|
|
114
|
+
koffi.pointer(retro_audio_sample_t)
|
|
115
|
+
);
|
|
116
|
+
api.retro_set_audio_sample(this.audioSampleCallback);
|
|
117
|
+
|
|
118
|
+
// Audio sample batch callback (multiple frames at once)
|
|
119
|
+
this.audioBatchCallback = koffi.register(
|
|
120
|
+
(data: Buffer | null, frames: number): number => {
|
|
121
|
+
return this.handleAudioBatch(data, frames);
|
|
122
|
+
},
|
|
123
|
+
koffi.pointer(retro_audio_sample_batch_t)
|
|
124
|
+
);
|
|
125
|
+
api.retro_set_audio_sample_batch(this.audioBatchCallback);
|
|
126
|
+
|
|
127
|
+
// Input poll callback
|
|
128
|
+
this.inputPollCallback = koffi.register((): void => {
|
|
129
|
+
// No-op - we update input state externally
|
|
130
|
+
}, koffi.pointer(retro_input_poll_t));
|
|
131
|
+
api.retro_set_input_poll(this.inputPollCallback);
|
|
132
|
+
|
|
133
|
+
// Input state callback
|
|
134
|
+
this.inputStateCallback = koffi.register(
|
|
135
|
+
(port: number, device: number, index: number, id: number): number => {
|
|
136
|
+
return this.handleInputState(port, device, index, id);
|
|
137
|
+
},
|
|
138
|
+
koffi.pointer(retro_input_state_t)
|
|
139
|
+
);
|
|
140
|
+
api.retro_set_input_state(this.inputStateCallback);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Debug: track frame count for video callback diagnostics
|
|
144
|
+
private debugFrameCount = 0;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handle video refresh callback from core
|
|
148
|
+
*/
|
|
149
|
+
private handleVideoRefresh(
|
|
150
|
+
|
|
151
|
+
data: any,
|
|
152
|
+
width: number,
|
|
153
|
+
height: number,
|
|
154
|
+
pitch: number
|
|
155
|
+
): void {
|
|
156
|
+
// data can be null for duplicate frames when GET_CAN_DUPE is true
|
|
157
|
+
if (!data) {return;}
|
|
158
|
+
|
|
159
|
+
this.frameWidth = width;
|
|
160
|
+
this.frameHeight = height;
|
|
161
|
+
this.framePitch = pitch;
|
|
162
|
+
|
|
163
|
+
// Calculate required buffer size (pitch * height)
|
|
164
|
+
const requiredSize = pitch * height;
|
|
165
|
+
|
|
166
|
+
// Allocate or grow framebuffer if needed
|
|
167
|
+
if (!this.framebuffer || this.framebufferCapacity < requiredSize) {
|
|
168
|
+
// Allocate with some headroom
|
|
169
|
+
this.framebufferCapacity = requiredSize + FRAMEBUFFER_HEADROOM;
|
|
170
|
+
this.framebuffer = new Uint8Array(this.framebufferCapacity);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Use koffi.view() to get direct access to the framebuffer memory without copying
|
|
174
|
+
// This creates an ArrayBuffer view into the native memory
|
|
175
|
+
const arrayBuffer = koffi.view(data, requiredSize);
|
|
176
|
+
const srcData = new Uint8Array(arrayBuffer);
|
|
177
|
+
|
|
178
|
+
// Copy to our internal buffer (we must copy because the source memory
|
|
179
|
+
// is only valid during this callback)
|
|
180
|
+
this.framebuffer.set(srcData);
|
|
181
|
+
|
|
182
|
+
// Debug: log frame info periodically (first few frames and then at regular intervals)
|
|
183
|
+
this.debugFrameCount++;
|
|
184
|
+
if (this.debugFrameCount <= DEBUG_INITIAL_FRAMES_TO_LOG || this.debugFrameCount % DEBUG_VIDEO_FRAME_LOG_INTERVAL === 0) {
|
|
185
|
+
const pixelFormat = this.envHandler.getPixelFormat();
|
|
186
|
+
logger.debug(`Video frame ${this.debugFrameCount}: ${width}x${height}, pitch=${pitch}, format=${pixelFormat}`, 'Video');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Handle single audio sample callback (less common, used by some cores)
|
|
192
|
+
*/
|
|
193
|
+
private handleAudioSample(left: number, right: number): void {
|
|
194
|
+
this.ensureAudioCapacity(2);
|
|
195
|
+
this.audioBuffer[this.audioSamples++] = left;
|
|
196
|
+
this.audioBuffer[this.audioSamples++] = right;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Handle audio batch callback (most common, more efficient)
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
private handleAudioBatch(data: any, frames: number): number {
|
|
204
|
+
if (!data || frames === 0) {return frames;}
|
|
205
|
+
|
|
206
|
+
// Each frame is 2 samples (left, right) as int16 (2 bytes each)
|
|
207
|
+
const samples = frames * 2;
|
|
208
|
+
const byteSize = samples * 2; // 2 bytes per int16 sample
|
|
209
|
+
this.ensureAudioCapacity(samples);
|
|
210
|
+
|
|
211
|
+
// Use koffi.view() to get direct access to the audio memory
|
|
212
|
+
const arrayBuffer = koffi.view(data, byteSize);
|
|
213
|
+
const srcData = new Int16Array(arrayBuffer);
|
|
214
|
+
|
|
215
|
+
// Copy to our internal buffer
|
|
216
|
+
for (let i = 0; i < samples; i++) {
|
|
217
|
+
this.audioBuffer[this.audioSamples++] = srcData[i];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return frames;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Ensure audio buffer has enough capacity.
|
|
225
|
+
* Uses 1.5x growth factor for more memory-efficient expansion.
|
|
226
|
+
*/
|
|
227
|
+
private ensureAudioCapacity(additionalSamples: number): void {
|
|
228
|
+
const required = this.audioSamples + additionalSamples;
|
|
229
|
+
if (required > this.audioBufferCapacity) {
|
|
230
|
+
// Grow by 1.5x until sufficient (more memory-efficient than 2x)
|
|
231
|
+
while (this.audioBufferCapacity < required) {
|
|
232
|
+
this.audioBufferCapacity = Math.ceil(this.audioBufferCapacity * AUDIO_BUFFER_GROWTH_FACTOR);
|
|
233
|
+
}
|
|
234
|
+
const newBuffer = new Int16Array(this.audioBufferCapacity);
|
|
235
|
+
newBuffer.set(this.audioBuffer.subarray(0, this.audioSamples));
|
|
236
|
+
this.audioBuffer = newBuffer;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Handle input state query from core
|
|
242
|
+
* Supports both joypad (digital buttons) and analog stick queries.
|
|
243
|
+
* Uses cached bitmask for O(1) bitmask queries and array lookup for individual buttons.
|
|
244
|
+
*/
|
|
245
|
+
private handleInputState(
|
|
246
|
+
port: number,
|
|
247
|
+
device: number,
|
|
248
|
+
index: number,
|
|
249
|
+
id: number
|
|
250
|
+
): number {
|
|
251
|
+
// Handle analog stick queries
|
|
252
|
+
if (device === RETRO_DEVICE.ANALOG) {
|
|
253
|
+
return this.getAnalogState(port, index, id);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Handle joypad (digital button) queries
|
|
257
|
+
if (device !== RETRO_DEVICE.JOYPAD) {return 0;}
|
|
258
|
+
|
|
259
|
+
// id=JOYPAD_BITMASK_ID (256) is RETRO_DEVICE_ID_JOYPAD_MASK - return cached bitmask
|
|
260
|
+
if (id === JOYPAD_BITMASK_ID) {
|
|
261
|
+
return this.buttonBitmask[port] ?? 0;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Individual button query - O(1) array lookup
|
|
265
|
+
const portState = this.buttonState[port];
|
|
266
|
+
if (!portState) {return 0;}
|
|
267
|
+
return portState[id] ? 1 : 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Track which analog indices have been queried
|
|
271
|
+
private loggedAnalogIndices = new Set<number>();
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get analog axis value for a port/stick/axis
|
|
275
|
+
* @param port - Controller port (0-based)
|
|
276
|
+
* @param index - Analog stick (0=left, 1=right, 2=analog buttons)
|
|
277
|
+
* @param axis - Axis (0=X, 1=Y)
|
|
278
|
+
* @returns Analog value from -32768 to 32767
|
|
279
|
+
*/
|
|
280
|
+
// Track last returned values to avoid spamming logs
|
|
281
|
+
private lastLoggedAnalogValue: Map<string, number> = new Map();
|
|
282
|
+
|
|
283
|
+
private getAnalogState(port: number, index: number, axis: number): number {
|
|
284
|
+
const portState = this.analogState[port];
|
|
285
|
+
if (!portState) {return 0;}
|
|
286
|
+
const stickState = portState[index];
|
|
287
|
+
if (!stickState) {return 0;}
|
|
288
|
+
const value = stickState[axis] ?? 0;
|
|
289
|
+
|
|
290
|
+
// Debug: Log first time core queries each analog index
|
|
291
|
+
if (!this.loggedAnalogIndices.has(index)) {
|
|
292
|
+
logger.debug(`Core queries analog index=${index} (LEFT=0, RIGHT=1)`, 'Input');
|
|
293
|
+
this.loggedAnalogIndices.add(index);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Debug: Log significant return values (changed by more than threshold from last logged)
|
|
297
|
+
const key = `${port}.${index}.${axis}`;
|
|
298
|
+
const lastLogged = this.lastLoggedAnalogValue.get(key) ?? 0;
|
|
299
|
+
if (Math.abs(value - lastLogged) > DEBUG_ANALOG_CHANGE_THRESHOLD) {
|
|
300
|
+
logger.debug(`getAnalogState RETURN: port=${port} index=${index} axis=${axis} → ${value}`, 'Input');
|
|
301
|
+
this.lastLoggedAnalogValue.set(key, value);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Set button state for input handling.
|
|
309
|
+
* Updates both the button array and cached bitmask for O(1) queries.
|
|
310
|
+
*/
|
|
311
|
+
setButtonState(port: number, button: number, pressed: boolean): void {
|
|
312
|
+
// Initialize port state array if needed
|
|
313
|
+
if (!this.buttonState[port]) {
|
|
314
|
+
this.buttonState[port] = [];
|
|
315
|
+
this.buttonBitmask[port] = 0;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Update button state
|
|
319
|
+
this.buttonState[port][button] = pressed;
|
|
320
|
+
|
|
321
|
+
// Update cached bitmask
|
|
322
|
+
if (pressed) {
|
|
323
|
+
this.buttonBitmask[port] |= (1 << button);
|
|
324
|
+
} else {
|
|
325
|
+
this.buttonBitmask[port] &= ~(1 << button);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get all button states for a port.
|
|
331
|
+
* Converts internal array to Map for API compatibility.
|
|
332
|
+
*/
|
|
333
|
+
getButtonState(port: number): Map<number, boolean> {
|
|
334
|
+
const portState = this.buttonState[port];
|
|
335
|
+
if (!portState) {return new Map<number, boolean>();}
|
|
336
|
+
|
|
337
|
+
const result = new Map<number, boolean>();
|
|
338
|
+
for (let i = 0; i < portState.length; i++) {
|
|
339
|
+
const pressed = portState[i];
|
|
340
|
+
if (pressed !== undefined) {
|
|
341
|
+
result.set(i, pressed);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Set analog axis state for a port/stick/axis.
|
|
349
|
+
* @param port - Controller port (0-based)
|
|
350
|
+
* @param index - Analog stick (0=left, 1=right from RETRO_DEVICE_INDEX_ANALOG)
|
|
351
|
+
* @param axis - Axis (0=X, 1=Y from RETRO_DEVICE_ID_ANALOG)
|
|
352
|
+
* @param value - Analog value from -32768 to 32767
|
|
353
|
+
*/
|
|
354
|
+
setAnalogState(port: number, index: number, axis: number, value: number): void {
|
|
355
|
+
// Clamp value to valid range
|
|
356
|
+
const clampedValue = clamp(Math.round(value), { min: INT16_MIN, max: INT16_MAX_POSITIVE });
|
|
357
|
+
|
|
358
|
+
// Debug: Log significant analog values being stored
|
|
359
|
+
if (Math.abs(clampedValue) > DEBUG_ANALOG_CHANGE_THRESHOLD) {
|
|
360
|
+
logger.debug(`setAnalogState: port=${port} index=${index} axis=${axis} value=${clampedValue}`, 'Input');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Initialize port state if needed
|
|
364
|
+
if (!this.analogState[port]) {
|
|
365
|
+
this.analogState[port] = [];
|
|
366
|
+
}
|
|
367
|
+
// Initialize stick state if needed
|
|
368
|
+
if (!this.analogState[port]![index]) {
|
|
369
|
+
this.analogState[port]![index] = [0, 0];
|
|
370
|
+
}
|
|
371
|
+
// Set axis value
|
|
372
|
+
this.analogState[port]![index]![axis] = clampedValue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get all analog states for a port.
|
|
377
|
+
* Returns a map of "index.axis" -> value
|
|
378
|
+
*/
|
|
379
|
+
getAnalogStates(port: number): Map<string, number> {
|
|
380
|
+
const result = new Map<string, number>();
|
|
381
|
+
const portState = this.analogState[port];
|
|
382
|
+
if (!portState) {return result;}
|
|
383
|
+
|
|
384
|
+
// Iterate over both analog sticks (left=0, right=1)
|
|
385
|
+
const analogIndices = [RETRO_DEVICE_INDEX_ANALOG.LEFT, RETRO_DEVICE_INDEX_ANALOG.RIGHT];
|
|
386
|
+
const axisIds = [RETRO_DEVICE_ID_ANALOG.X, RETRO_DEVICE_ID_ANALOG.Y];
|
|
387
|
+
|
|
388
|
+
for (const index of analogIndices) {
|
|
389
|
+
const stickState = portState[index];
|
|
390
|
+
if (!stickState) {continue;}
|
|
391
|
+
for (const axis of axisIds) {
|
|
392
|
+
const value = stickState[axis];
|
|
393
|
+
// Only include non-zero values to reduce map size
|
|
394
|
+
if (value !== 0) {
|
|
395
|
+
result.set(`${index}.${axis}`, value);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Drain the audio buffer and return samples as Float32Array
|
|
404
|
+
* Converts from Int16 [-32768, 32767] to Float32 [-1.0, 1.0]
|
|
405
|
+
* Reuses internal buffer to avoid per-frame allocations.
|
|
406
|
+
*/
|
|
407
|
+
drainAudio(): Float32Array {
|
|
408
|
+
const count = this.audioSamples;
|
|
409
|
+
|
|
410
|
+
// Grow output buffer if needed
|
|
411
|
+
if (!this.audioOutputBuffer || this.audioOutputCapacity < count) {
|
|
412
|
+
this.audioOutputCapacity = Math.max(count, this.audioBufferCapacity);
|
|
413
|
+
this.audioOutputBuffer = new Float32Array(this.audioOutputCapacity);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const output = this.audioOutputBuffer;
|
|
417
|
+
const input = this.audioBuffer;
|
|
418
|
+
|
|
419
|
+
for (let i = 0; i < count; i++) {
|
|
420
|
+
output[i] = input[i] / INT16_MAX;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this.audioSamples = 0;
|
|
424
|
+
return output.subarray(0, count);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Check if there are audio samples available
|
|
429
|
+
*/
|
|
430
|
+
hasAudio(): boolean {
|
|
431
|
+
return this.audioSamples > 0;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Clean up callbacks
|
|
436
|
+
*/
|
|
437
|
+
destroy(): void {
|
|
438
|
+
// Note: koffi registered callbacks are cleaned up when the library is unloaded
|
|
439
|
+
// We just need to clear our references
|
|
440
|
+
this.environmentCallback = null;
|
|
441
|
+
this.videoCallback = null;
|
|
442
|
+
this.audioSampleCallback = null;
|
|
443
|
+
this.audioBatchCallback = null;
|
|
444
|
+
this.inputPollCallback = null;
|
|
445
|
+
this.inputStateCallback = null;
|
|
446
|
+
this.framebuffer = null;
|
|
447
|
+
this.audioBuffer = new Int16Array(0);
|
|
448
|
+
this.audioOutputBuffer = null;
|
|
449
|
+
this.audioOutputCapacity = 0;
|
|
450
|
+
this.audioSamples = 0;
|
|
451
|
+
this.buttonState = [];
|
|
452
|
+
this.buttonBitmask = [];
|
|
453
|
+
this.analogState = [];
|
|
454
|
+
this.lastLoggedAnalogValue.clear();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Audio Constants
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
/** Default audio sample rate in Hz (standard for libretro) */
|
|
6
|
+
export const DEFAULT_SAMPLE_RATE = 44100;
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Input Constants
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
/** Maximum positive value for signed 16-bit analog values */
|
|
13
|
+
export const INT16_MAX_POSITIVE = 32767;
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Video/Buffer Constants
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/** Extra headroom bytes added when allocating framebuffer */
|
|
20
|
+
export const FRAMEBUFFER_HEADROOM = 1024;
|
|
21
|
+
|
|
22
|
+
/** Number of bytes per RGB24 pixel */
|
|
23
|
+
export const RGB24_BYTES_PER_PIXEL = 3;
|
|
24
|
+
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Number Formatting Constants
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
export { HEX_RADIX } from '../../utils';
|
|
30
|
+
|
|
31
|
+
/** Decimal places for aspect ratio formatting */
|
|
32
|
+
export const ASPECT_RATIO_DECIMALS = 3;
|
|
33
|
+
|
|
34
|
+
/** Decimal places for FPS/sample rate formatting */
|
|
35
|
+
export const FPS_DECIMALS = 2;
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Debug Constants
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
/** Number of initial frames to log timing for */
|
|
42
|
+
export const DEBUG_INITIAL_FRAME_LOG_COUNT = 5;
|
|
43
|
+
|
|
44
|
+
/** Maximum normalized analog value threshold (for int16 conversion detection) */
|
|
45
|
+
export const ANALOG_NORMALIZED_THRESHOLD = 1.5;
|