mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-18 07:42:08 +08:00
sounds
This commit is contained in:
+28
-1
@@ -101,12 +101,32 @@ class Soundd:
|
||||
self.auto_volume = 0
|
||||
|
||||
self.previous_sound_pack = None
|
||||
self.previous_sound_source_signature = None
|
||||
|
||||
self.error_log = ERROR_LOGS_PATH / "error.txt"
|
||||
self.random_events_directory = RANDOM_EVENTS_PATH / "sounds"
|
||||
|
||||
self.update_starpilot_sounds()
|
||||
|
||||
def get_sound_source_signature(self):
|
||||
try:
|
||||
resolved_path = self.sound_directory.resolve()
|
||||
except OSError:
|
||||
resolved_path = self.sound_directory
|
||||
|
||||
sound_files = ()
|
||||
try:
|
||||
if self.sound_directory.exists():
|
||||
sound_files = tuple(sorted(
|
||||
(entry.name, entry.stat().st_mtime_ns)
|
||||
for entry in self.sound_directory.iterdir()
|
||||
if entry.is_file()
|
||||
))
|
||||
except OSError:
|
||||
sound_files = ()
|
||||
|
||||
return str(resolved_path), sound_files
|
||||
|
||||
def load_sounds(self):
|
||||
self.loaded_sounds: dict[int, np.ndarray] = {}
|
||||
|
||||
@@ -268,6 +288,8 @@ class Soundd:
|
||||
self.starpilot_toggles = starpilot_toggles
|
||||
|
||||
stream = self.update_starpilot_sounds(sd, stream)
|
||||
elif rk.frame % 5 == 0:
|
||||
stream = self.update_starpilot_sounds(sd, stream)
|
||||
|
||||
def update_starpilot_sounds(self, sd=None, stream=None):
|
||||
self.volume_map = {
|
||||
@@ -295,10 +317,15 @@ class Soundd:
|
||||
else:
|
||||
self.sound_directory = Path(BASEDIR) / "selfdrive" / "assets" / "sounds"
|
||||
|
||||
if self.starpilot_toggles.sound_pack != self.previous_sound_pack:
|
||||
sound_source_signature = self.get_sound_source_signature()
|
||||
should_reload = self.starpilot_toggles.sound_pack != self.previous_sound_pack
|
||||
should_reload |= sound_source_signature != self.previous_sound_source_signature
|
||||
|
||||
if should_reload:
|
||||
self.load_sounds()
|
||||
|
||||
self.previous_sound_pack = self.starpilot_toggles.sound_pack
|
||||
self.previous_sound_source_signature = sound_source_signature
|
||||
|
||||
if stream is not None:
|
||||
stream.close()
|
||||
|
||||
@@ -42,6 +42,15 @@ const BUILTIN_THEME_OPTIONS = {
|
||||
turn_signals: { name: "None (Stock)", path: "__stock_none__", type: "stock_none", builtin: true },
|
||||
};
|
||||
|
||||
const PARAM_KEY_BY_ASSET_TYPE = {
|
||||
colors: "ColorScheme",
|
||||
distance_icons: "DistanceIconPack",
|
||||
icons: "IconPack",
|
||||
sounds: "SoundPack",
|
||||
turn_signals: "SignalAnimation",
|
||||
steering_wheel: "WheelIcon",
|
||||
};
|
||||
|
||||
const fileStore = {
|
||||
images: { distanceIcons: {} },
|
||||
sounds: {},
|
||||
@@ -107,6 +116,14 @@ const state = reactive({
|
||||
themeToDelete: null,
|
||||
themes: [],
|
||||
activeTab: "colors",
|
||||
selectedThemeSources: {
|
||||
colors: null,
|
||||
distance_icons: null,
|
||||
icons: null,
|
||||
sounds: null,
|
||||
turn_signals: null,
|
||||
steering_wheel: null,
|
||||
},
|
||||
fetched: false,
|
||||
});
|
||||
|
||||
@@ -190,6 +207,7 @@ function handleDrop(e) {
|
||||
};
|
||||
state.sequentialImages = move(state.sequentialImages, draggedIndex, dropIndex);
|
||||
fileStore.sequentialFiles = move(fileStore.sequentialFiles, draggedIndex, dropIndex);
|
||||
state.selectedThemeSources.turn_signals = null;
|
||||
}
|
||||
|
||||
draggedIndex = -1;
|
||||
@@ -212,6 +230,20 @@ function handleDragEnd(e) {
|
||||
|
||||
const clearAsset = (type, key, subkey = null) => {
|
||||
state.themeSubmitted = false;
|
||||
const assetType = key === "turnSignal" || key === "turnSignalBlindspot"
|
||||
? "turn_signals"
|
||||
: key === "distanceIcons"
|
||||
? "distance_icons"
|
||||
: key === "homeButton" || key === "settingsButton"
|
||||
? "icons"
|
||||
: key === "steeringWheel"
|
||||
? "steering_wheel"
|
||||
: type === "audio"
|
||||
? "sounds"
|
||||
: null;
|
||||
if (assetType) {
|
||||
state.selectedThemeSources[assetType] = null;
|
||||
}
|
||||
if (key === "turnSignal") {
|
||||
if (state.turnSignalType === "Sequential") {
|
||||
fileStore.sequentialFiles = [];
|
||||
@@ -243,6 +275,7 @@ const onClearClick = (e, type, key, subkey = null) => {
|
||||
};
|
||||
|
||||
const clearAssetType = (assetType) => {
|
||||
state.selectedThemeSources[assetType] = null;
|
||||
if (assetType === "colors") {
|
||||
state.colors = { ...defaultColors };
|
||||
}
|
||||
@@ -526,6 +559,20 @@ export function ThemeMaker() {
|
||||
}
|
||||
|
||||
state.themeSubmitted = false;
|
||||
const assetType = key === "turnSignal" || key === "turnSignalBlindspot"
|
||||
? "turn_signals"
|
||||
: key === "distanceIcons"
|
||||
? "distance_icons"
|
||||
: key === "homeButton" || key === "settingsButton"
|
||||
? "icons"
|
||||
: key === "steeringWheel"
|
||||
? "steering_wheel"
|
||||
: type === "audio"
|
||||
? "sounds"
|
||||
: null;
|
||||
if (assetType) {
|
||||
state.selectedThemeSources[assetType] = null;
|
||||
}
|
||||
|
||||
if (key === "turnSignal" && state.turnSignalType === "Sequential") {
|
||||
fileStore.sequentialFiles.push(...files);
|
||||
@@ -555,6 +602,7 @@ export function ThemeMaker() {
|
||||
alpha: 255,
|
||||
};
|
||||
state.themeSubmitted = false;
|
||||
state.selectedThemeSources.colors = null;
|
||||
};
|
||||
|
||||
const validateTurnSignalLength = (e) => {
|
||||
@@ -562,21 +610,30 @@ export function ThemeMaker() {
|
||||
const clampedValue = isNaN(value) ? 25 : Math.max(25, Math.min(1000, value));
|
||||
state.turnSignalLength = clampedValue;
|
||||
e.target.value = clampedValue;
|
||||
state.selectedThemeSources.turn_signals = null;
|
||||
};
|
||||
|
||||
const toggleTurnSignalType = (type) => {
|
||||
state.turnSignalType = type;
|
||||
state.themeSubmitted = false;
|
||||
state.selectedThemeSources.turn_signals = null;
|
||||
state.sequentialImages = [];
|
||||
fileStore.sequentialFiles = [];
|
||||
fileStore.images.turnSignal = undefined;
|
||||
state.imageFileNames.turnSignal = "";
|
||||
};
|
||||
|
||||
const setTurnSignalStyle = (style) => {
|
||||
state.turnSignalStyle = style;
|
||||
state.themeSubmitted = false;
|
||||
state.selectedThemeSources.turn_signals = null;
|
||||
};
|
||||
|
||||
const getFormData = () => {
|
||||
const formData = new FormData();
|
||||
formData.append("themeName", state.themeName);
|
||||
formData.append("saveChecklist", JSON.stringify(state.saveChecklist));
|
||||
formData.append("selectedThemeSources", JSON.stringify(state.selectedThemeSources));
|
||||
|
||||
if (state.discordUsername && state.discordUsername.trim()) {
|
||||
formData.append("discordUsername", state.discordUsername.trim());
|
||||
@@ -646,6 +703,48 @@ export function ThemeMaker() {
|
||||
}
|
||||
};
|
||||
|
||||
const persistThemeSelection = async (theme, assetType) => {
|
||||
const paramKey = PARAM_KEY_BY_ASSET_TYPE[assetType];
|
||||
if (!paramKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let value = theme?.path || "";
|
||||
if (theme?.type === "stock") {
|
||||
value = "stock";
|
||||
} else if (theme?.type === "stock_none") {
|
||||
value = "none";
|
||||
} else if (assetType === "steering_wheel" && value) {
|
||||
value = value.replace(/\.[^.]+$/, "");
|
||||
}
|
||||
|
||||
const customThemesResult = await performApiAction(
|
||||
"/api/params",
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ key: "CustomThemes", value: "true" }),
|
||||
},
|
||||
"",
|
||||
"Failed to enable custom themes."
|
||||
);
|
||||
if (!customThemesResult.ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = await performApiAction(
|
||||
"/api/params",
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ key: paramKey, value }),
|
||||
},
|
||||
"",
|
||||
`Failed to update ${assetType.replace("_", " ")}.`
|
||||
);
|
||||
return result.ok;
|
||||
};
|
||||
|
||||
const applyTheme = async () => {
|
||||
if (!Object.values(state.saveChecklist).some(v => v)) {
|
||||
return showSnackbar("Please select at least one component to apply!", "error");
|
||||
@@ -996,6 +1095,11 @@ export function ThemeMaker() {
|
||||
}
|
||||
}
|
||||
|
||||
state.selectedThemeSources[assetType] = {
|
||||
path: theme.path,
|
||||
type: theme.type,
|
||||
};
|
||||
|
||||
showSnackbar(`Loaded ${assetType.replace("_", " ")} from "${theme.name}"!`);
|
||||
} catch (err) {
|
||||
console.error("Failed to load theme asset:", err);
|
||||
@@ -1013,10 +1117,17 @@ export function ThemeMaker() {
|
||||
state.turnSignalStyle = "Traditional";
|
||||
state.imageFileNames.turnSignal = "None";
|
||||
state.imageFileNames.turnSignalBlindspot = "";
|
||||
state.selectedThemeSources.turn_signals = {
|
||||
path: "__stock_none__",
|
||||
type: "stock_none",
|
||||
};
|
||||
showSnackbar('Loaded stock turn signals.');
|
||||
};
|
||||
|
||||
const loadSelectableThemeAsset = async (theme, assetType) => {
|
||||
const persisted = await persistThemeSelection(theme, assetType);
|
||||
if (!persisted) return;
|
||||
|
||||
if (theme?.type === "stock_none" && assetType === "turn_signals") {
|
||||
loadBuiltinTurnSignals();
|
||||
return;
|
||||
@@ -1225,9 +1336,9 @@ export function ThemeMaker() {
|
||||
</label>
|
||||
<div class="signal-type-toggle">
|
||||
<button class="${() => `toggle-button ${state.turnSignalStyle === "Static" ? "active" : ""}`}"
|
||||
@click="${() => state.turnSignalStyle = "Static"}">Static</button>
|
||||
@click="${() => setTurnSignalStyle("Static")}">Static</button>
|
||||
<button class="${() => `toggle-button ${state.turnSignalStyle === "Traditional" ? "active" : ""}`}"
|
||||
@click="${() => state.turnSignalStyle = "Traditional"}">Traditional</button>
|
||||
@click="${() => setTurnSignalStyle("Traditional")}">Traditional</button>
|
||||
</div>
|
||||
${() => state.showTurnSignalHelp && html`<div class="turn-signal-help-text">
|
||||
<p><strong>Static</strong> - The turn signal animation appears next to the current speed.</p>
|
||||
|
||||
@@ -4778,6 +4778,44 @@ def setup(app):
|
||||
return jsonify({"error": error}), 400
|
||||
|
||||
save_checklist = json.loads(form_data.get("saveChecklist", "{}"))
|
||||
selected_theme_sources = json.loads(form_data.get("selectedThemeSources", "{}"))
|
||||
|
||||
theme_param_map = {
|
||||
"colors": "ColorScheme",
|
||||
"distance_icons": "DistanceIconPack",
|
||||
"icons": "IconPack",
|
||||
"sounds": "SoundPack",
|
||||
"turn_signals": "SignalAnimation",
|
||||
"steering_wheel": "WheelIcon",
|
||||
}
|
||||
|
||||
def get_selected_theme_value(asset_type):
|
||||
source = selected_theme_sources.get(asset_type)
|
||||
if not isinstance(source, dict):
|
||||
return None
|
||||
|
||||
source_type = str(source.get("type") or "").strip().lower()
|
||||
source_path = str(source.get("path") or "").strip()
|
||||
if not source_path:
|
||||
return None
|
||||
|
||||
if source_type == "stock":
|
||||
return "stock"
|
||||
if source_type == "stock_none":
|
||||
return "none"
|
||||
if source_path == "__stock__":
|
||||
return "stock"
|
||||
if source_path == "__stock_none__":
|
||||
return "none"
|
||||
if asset_type == "steering_wheel":
|
||||
return Path(source_path).stem
|
||||
return source_path
|
||||
|
||||
for asset_type, param_key in theme_param_map.items():
|
||||
if save_checklist.get(asset_type):
|
||||
selected_value = get_selected_theme_value(asset_type)
|
||||
if selected_value is not None:
|
||||
params.put(param_key, selected_value)
|
||||
|
||||
if save_checklist.get("colors") and temp_path is not None:
|
||||
asset_location = temp_path / "colors"
|
||||
@@ -4850,6 +4888,13 @@ def setup(app):
|
||||
return stock_asset_path
|
||||
|
||||
stock_fallbacks = {
|
||||
"icons/button_home.png": STOCK_THEME_PATH.parents[2] / "selfdrive" / "assets" / "images" / "button_home.png",
|
||||
"icons/button_settings.png": STOCK_THEME_PATH.parents[2] / "selfdrive" / "assets" / "images" / "button_settings.png",
|
||||
"sounds/disengage.wav": STOCK_THEME_PATH.parents[2] / "selfdrive" / "assets" / "sounds" / "disengage.wav",
|
||||
"sounds/engage.wav": STOCK_THEME_PATH.parents[2] / "selfdrive" / "assets" / "sounds" / "engage.wav",
|
||||
"sounds/prompt.wav": STOCK_THEME_PATH.parents[2] / "selfdrive" / "assets" / "sounds" / "prompt.wav",
|
||||
# Stock openpilot has no dedicated startup clip; runtime falls back to engage.
|
||||
"sounds/startup.wav": STOCK_THEME_PATH.parents[2] / "selfdrive" / "assets" / "sounds" / "engage.wav",
|
||||
"steering_wheel/wheel.png": STOCK_THEME_PATH.parents[2] / "selfdrive" / "assets" / "icons" / "chffr_wheel.png",
|
||||
}
|
||||
|
||||
@@ -5116,6 +5161,13 @@ def setup(app):
|
||||
"path": f"icons/{filename}"
|
||||
}
|
||||
break
|
||||
elif theme_type == "stock" or theme_path == "__stock__":
|
||||
for filename, response_key in [("button_home.png", "homeButton"), ("button_settings.png", "settingsButton")]:
|
||||
if _resolve_stock_theme_asset_path(f"icons/{filename}").exists():
|
||||
response_data["images"][response_key] = {
|
||||
"filename": filename,
|
||||
"path": f"icons/{filename}",
|
||||
}
|
||||
|
||||
distance_dir = theme_dir / "distance_icons"
|
||||
if distance_dir.exists():
|
||||
@@ -5179,6 +5231,13 @@ def setup(app):
|
||||
"filename": f"{name}.wav",
|
||||
"path": f"sounds/{name}.wav"
|
||||
}
|
||||
elif theme_type == "stock" or theme_path == "__stock__":
|
||||
for name in ["engage", "disengage", "startup", "prompt"]:
|
||||
if _resolve_stock_theme_asset_path(f"sounds/{name}.wav").exists():
|
||||
response_data["sounds"][name] = {
|
||||
"filename": f"{name}.wav",
|
||||
"path": f"sounds/{name}.wav"
|
||||
}
|
||||
|
||||
steering_wheel_path = None
|
||||
if theme_type == "stock" or theme_path == "__stock__":
|
||||
|
||||
Reference in New Issue
Block a user