From a1b68942f93bba066c6640aca2d9cc3f46d12215 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Wed, 27 Nov 2024 01:11:01 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=87=AA=E5=AE=9A=E4=B9=89=E9=9F=B3=E6=95=88=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/lib.rs | 17 +++++++++- src/core/SoundService.tsx | 66 +++++++++++++++++++++++++++++++++++++++ src/pages/test.tsx | 2 ++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/core/SoundService.tsx diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3b24e561..f6baed4f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -75,6 +75,20 @@ fn save_base64_to_image(base64_str: &str, file_name: &str) -> Result<(), String> } } +/// 读取 MP3 文件并返回其 Base64 编码字符串 +#[tauri::command] +fn read_mp3_file(path: String) -> Result { + let mut file = File::open(&path).map_err(|e| format!("无法打开文件: {}", e))?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).map_err(|e| format!("读取文件时出错: {}", e))?; + + // 将文件内容编码为 Base64 + let base64_str = general_purpose::STANDARD.encode(&buffer); + Ok(base64_str) +} + + + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { println!("程序运行了!"); @@ -95,7 +109,8 @@ pub fn run() { save_file_by_path, convert_image_to_base64, save_base64_to_image, - check_json_exist + check_json_exist, + read_mp3_file ]) .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_fs::init()) diff --git a/src/core/SoundService.tsx b/src/core/SoundService.tsx new file mode 100644 index 00000000..2c3a630f --- /dev/null +++ b/src/core/SoundService.tsx @@ -0,0 +1,66 @@ + +// 实测发现 不可行: +// @tauri-apps/plugin-fs 只能读取文本文件,不能强行读取流文件并强转为ArrayBuffer +// import { readTextFile } from "@tauri-apps/plugin-fs"; + +import { invoke } from "@tauri-apps/api/core"; +import { StringDict } from "./dataStruct/StringDict"; + +/** + * 播放音效的服务 + */ +export namespace SoundService { + export function testPlay() { + loadAndPlaySound(""); + } + + const audioContext = new window.AudioContext(); + + + async function loadAndPlaySound(filePath: string) { + // 解码音频数据 + const audioBuffer = await getAudioBufferByFilePath(filePath); + const source = audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(audioContext.destination); + source.start(0); + } + + const pathAudioBufferMap = new StringDict(); + + async function getAudioBufferByFilePath(filePath: string) { + + // 先从缓存中获取音频数据 + if (pathAudioBufferMap.hasId(filePath)) { + const result = pathAudioBufferMap.getById(filePath); + if (result) { + return result; + } + } + + // 缓存中没有 + + // 读取文件为字符串 + const base64Data: string = await invoke("read_mp3_file", { + path: filePath, + }); + // 解码 Base64 字符串 + const byteCharacters = atob(base64Data); // 使用 atob 解码 Base64 字符串 + const byteNumbers = new Uint8Array(byteCharacters.length); + + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); // 转换为字节数组 + } + + // 创建 ArrayBuffer + const arrayBuffer = byteNumbers.buffer; + + // 解码音频数据 + const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); + + // 加入缓存 + pathAudioBufferMap.setById(filePath, audioBuffer); + + return audioBuffer; + } +} diff --git a/src/pages/test.tsx b/src/pages/test.tsx index 81804e25..c2e40bbf 100644 --- a/src/pages/test.tsx +++ b/src/pages/test.tsx @@ -10,6 +10,7 @@ import { LastLaunch } from "../core/LastLaunch"; import { StageDumper } from "../core/stage/StageDumper"; import { usePopupDialog } from "../utils/popupDialog"; import { XML } from "../utils/xml"; +import { SoundService } from "../core/SoundService"; export default function TestPage() { const [switchValue, setSwitchValue] = React.useState(false); @@ -50,6 +51,7 @@ export default function TestPage() { + last launch: {LastLaunch.version} ); From ad4de27a4accc0dde2cd24fd972841ccfcf70e23 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Wed, 27 Nov 2024 01:38:02 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E7=A4=BA=E4=BE=8B=E5=A3=B0=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/Settings.tsx | 7 +++- src/core/SoundService.tsx | 37 ++++++++++++++++--- .../controller/concrete/ControllerCutting.tsx | 3 ++ src/main.tsx | 2 + src/pages/settings/_layout.tsx | 1 + src/pages/settings/sounds.tsx | 16 ++++++++ src/pages/test.tsx | 2 +- src/router.ts | 1 + 8 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 src/pages/settings/sounds.tsx diff --git a/src/core/Settings.tsx b/src/core/Settings.tsx index db583a86..98557878 100644 --- a/src/core/Settings.tsx +++ b/src/core/Settings.tsx @@ -38,7 +38,9 @@ export namespace Settings { moveAmplitude: number; moveFriction: number; gamepadDeadzone: number; - + // 音效相关 + soundEnabled: boolean; + cuttingLineStartSoundFile: string; // github 相关 githubToken: string; githubUser: string; @@ -73,6 +75,9 @@ export namespace Settings { moveAmplitude: 2, moveFriction: 0.1, gamepadDeadzone: 0.1, + // 音效相关 + soundEnabled: true, + cuttingLineStartSoundFile: "", // github 相关 githubToken: "", diff --git a/src/core/SoundService.tsx b/src/core/SoundService.tsx index 2c3a630f..e54a41c7 100644 --- a/src/core/SoundService.tsx +++ b/src/core/SoundService.tsx @@ -1,23 +1,51 @@ - // 实测发现 不可行: // @tauri-apps/plugin-fs 只能读取文本文件,不能强行读取流文件并强转为ArrayBuffer // import { readTextFile } from "@tauri-apps/plugin-fs"; import { invoke } from "@tauri-apps/api/core"; import { StringDict } from "./dataStruct/StringDict"; +import { Settings } from "./Settings"; /** * 播放音效的服务 + * 这个音效播放服务是用户自定义的 */ export namespace SoundService { - export function testPlay() { - loadAndPlaySound(""); + + let cuttingLineStartSoundFile = ""; + + export function init() { + Settings.watch("cuttingLineStartSoundFile", (value) => { + cuttingLineStartSoundFile = value; + }); + } + + export namespace play { + // 开始切断 + export function cuttingLineStart() { + loadAndPlaySound(cuttingLineStartSoundFile); + } + + // 开始连接 + // 连接吸附到目标点 + + // 自动保存执行特效 + // 自动备份执行特效 + + // 框选增加物体音效 + + // 切断特效声音 + // 连接成功 } const audioContext = new window.AudioContext(); - async function loadAndPlaySound(filePath: string) { + if (filePath.trim() === "") { + console.log("filePath is empty"); + return; + } + // 解码音频数据 const audioBuffer = await getAudioBufferByFilePath(filePath); const source = audioContext.createBufferSource(); @@ -29,7 +57,6 @@ export namespace SoundService { const pathAudioBufferMap = new StringDict(); async function getAudioBufferByFilePath(filePath: string) { - // 先从缓存中获取音频数据 if (pathAudioBufferMap.hasId(filePath)) { const result = pathAudioBufferMap.getById(filePath); diff --git a/src/core/controller/concrete/ControllerCutting.tsx b/src/core/controller/concrete/ControllerCutting.tsx index f6947da0..f7e83020 100644 --- a/src/core/controller/concrete/ControllerCutting.tsx +++ b/src/core/controller/concrete/ControllerCutting.tsx @@ -6,6 +6,7 @@ import { CircleFlameEffect } from "../../effect/concrete/CircleFlameEffect"; import { LineCuttingEffect } from "../../effect/concrete/LineCuttingEffect"; import { EdgeRenderer } from "../../render/canvas2d/entityRenderer/edge/EdgeRenderer"; import { Renderer } from "../../render/canvas2d/renderer"; +import { SoundService } from "../../SoundService"; import { Stage } from "../../stage/Stage"; import { StageManager } from "../../stage/stageManager/StageManager"; import { Section } from "../../stageObject/entity/Section"; @@ -45,6 +46,8 @@ ControllerCutting.mousedown = (event: MouseEvent) => { cuttingStartLocation, cuttingStartLocation.clone(), ); + // 添加音效提示 + SoundService.play.cuttingLineStart(); } else { Stage.isCutting = false; } diff --git a/src/main.tsx b/src/main.tsx index 5d1340b0..774bf315 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -22,6 +22,7 @@ import { StartFilesManager } from "./core/StartFilesManager"; import "./index.pcss"; import { DialogProvider } from "./utils/dialog"; import { PopupDialogProvider } from "./utils/popupDialog"; +import { SoundService } from "./core/SoundService"; const router = createMemoryRouter(routes); const Routes = () => ; @@ -66,6 +67,7 @@ async function loadSyncModules() { Stage.init(); StageHistoryManager.init(); StageStyleManager.init(); + SoundService.init(); } /** 加载语言文件 */ diff --git a/src/pages/settings/_layout.tsx b/src/pages/settings/_layout.tsx index 8e4ccca8..9c1483f9 100644 --- a/src/pages/settings/_layout.tsx +++ b/src/pages/settings/_layout.tsx @@ -17,6 +17,7 @@ export default function SettingsLayout() { {t("control")} {t("ai")} {t("github")} + sounds
diff --git a/src/pages/settings/sounds.tsx b/src/pages/settings/sounds.tsx new file mode 100644 index 00000000..492467c3 --- /dev/null +++ b/src/pages/settings/sounds.tsx @@ -0,0 +1,16 @@ +import { Folder } from "lucide-react"; +import { SettingField } from "./_field"; + +export default function Sounds() { + return ( + <> + } + settingKey="cuttingLineStartSoundFile" + title={"切割线开始音效"} + details={"右键按下时刚开始创建切割线准备切割东西时播放的音效文件。"} + type="text" + /> + + ); +} diff --git a/src/pages/test.tsx b/src/pages/test.tsx index c2e40bbf..5b7cb99a 100644 --- a/src/pages/test.tsx +++ b/src/pages/test.tsx @@ -51,7 +51,7 @@ export default function TestPage() {
- + last launch: {LastLaunch.version} ); diff --git a/src/router.ts b/src/router.ts index 1818c86e..f1fe4e4d 100644 --- a/src/router.ts +++ b/src/router.ts @@ -13,6 +13,7 @@ export type Path = | `/settings/github` | `/settings/performance` | `/settings/physical` + | `/settings/sounds` | `/settings/visual` | `/test` | `/welcome`; From a4dbf7026838a0f527cb7ac6c2f6ec9e134edc7c Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Wed, 27 Nov 2024 02:01:27 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E6=96=B0=E7=9A=84=E9=9F=B3=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/Settings.tsx | 7 +++++- src/core/SoundService.tsx | 22 +++++++++++++++++++ .../controller/concrete/ControllerCutting.tsx | 1 + .../concrete/ControllerNodeConnection.tsx | 6 ++++- src/pages/settings/sounds.tsx | 21 ++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/core/Settings.tsx b/src/core/Settings.tsx index 98557878..d46299e7 100644 --- a/src/core/Settings.tsx +++ b/src/core/Settings.tsx @@ -41,6 +41,9 @@ export namespace Settings { // 音效相关 soundEnabled: boolean; cuttingLineStartSoundFile: string; + connectLineStartSoundFile: string; + connectFindTargetSoundFile: string; + cuttingLineReleaseSoundFile: string; // github 相关 githubToken: string; githubUser: string; @@ -78,7 +81,9 @@ export namespace Settings { // 音效相关 soundEnabled: true, cuttingLineStartSoundFile: "", - + connectLineStartSoundFile: "", + connectFindTargetSoundFile: "", + cuttingLineReleaseSoundFile: "", // github 相关 githubToken: "", githubUser: "", diff --git a/src/core/SoundService.tsx b/src/core/SoundService.tsx index e54a41c7..c146bcbf 100644 --- a/src/core/SoundService.tsx +++ b/src/core/SoundService.tsx @@ -13,11 +13,23 @@ import { Settings } from "./Settings"; export namespace SoundService { let cuttingLineStartSoundFile = ""; + let connectLineStartSoundFile = ""; + let connectFindTargetSoundFile = ""; + let cuttingLineReleaseSoundFile = ""; export function init() { Settings.watch("cuttingLineStartSoundFile", (value) => { cuttingLineStartSoundFile = value; }); + Settings.watch("connectLineStartSoundFile", (value) => { + connectLineStartSoundFile = value; + }); + Settings.watch("connectFindTargetSoundFile", (value) => { + connectFindTargetSoundFile = value; + }); + Settings.watch("cuttingLineReleaseSoundFile", (value) => { + cuttingLineReleaseSoundFile = value; + }); } export namespace play { @@ -27,7 +39,14 @@ export namespace SoundService { } // 开始连接 + export function connectLineStart() { + loadAndPlaySound(connectLineStartSoundFile); + } + // 连接吸附到目标点 + export function connectFindTarget() { + loadAndPlaySound(connectFindTargetSoundFile); + } // 自动保存执行特效 // 自动备份执行特效 @@ -35,6 +54,9 @@ export namespace SoundService { // 框选增加物体音效 // 切断特效声音 + export function cuttingLineRelease() { + loadAndPlaySound(cuttingLineReleaseSoundFile); + } // 连接成功 } diff --git a/src/core/controller/concrete/ControllerCutting.tsx b/src/core/controller/concrete/ControllerCutting.tsx index f7e83020..b1765342 100644 --- a/src/core/controller/concrete/ControllerCutting.tsx +++ b/src/core/controller/concrete/ControllerCutting.tsx @@ -147,4 +147,5 @@ ControllerCutting.mouseup = (event: MouseEvent) => { cuttingStartLocation.distance(ControllerCutting.lastMoveLocation) / 10, ), ); + SoundService.play.cuttingLineRelease(); }; diff --git a/src/core/controller/concrete/ControllerNodeConnection.tsx b/src/core/controller/concrete/ControllerNodeConnection.tsx index bf7ffc05..12b3ed5c 100644 --- a/src/core/controller/concrete/ControllerNodeConnection.tsx +++ b/src/core/controller/concrete/ControllerNodeConnection.tsx @@ -12,6 +12,7 @@ import { EdgeRenderer } from "../../render/canvas2d/entityRenderer/edge/EdgeRend import { ConnectableEntity } from "../../stageObject/StageObject"; import { ConnectPoint } from "../../stageObject/entity/ConnectPoint"; import { v4 } from "uuid"; +import { SoundService } from "../../SoundService"; /** * 右键连线功能 的控制器 @@ -109,6 +110,8 @@ ControllerNodeConnection.mousedown = (event: MouseEvent) => { ), ); } + // 播放音效 + SoundService.play.connectLineStart(); } }; @@ -126,9 +129,10 @@ ControllerNodeConnection.mousemove = (event: MouseEvent) => { let isFindConnectToNode = false; for (const entity of StageManager.getConnectableEntity()) { if (entity.collisionBox.isPointInCollisionBox(worldLocation)) { + // 找到了连接的节点,吸附上去 Stage.connectToEntity = entity; isFindConnectToNode = true; - + SoundService.play.connectFindTarget(); break; } } diff --git a/src/pages/settings/sounds.tsx b/src/pages/settings/sounds.tsx index 492467c3..c64816be 100644 --- a/src/pages/settings/sounds.tsx +++ b/src/pages/settings/sounds.tsx @@ -11,6 +11,27 @@ export default function Sounds() { details={"右键按下时刚开始创建切割线准备切割东西时播放的音效文件。"} type="text" /> + } + settingKey="connectLineStartSoundFile" + title={"连接线开始音效"} + details={"右键按下时刚开始创建连接时播放的音效文件。"} + type="text" + /> + } + settingKey="connectFindTargetSoundFile" + title={"连接线查找目标音效"} + details={"当"} + type="text" + /> + } + settingKey="cuttingLineReleaseSoundFile" + title={"切断线释放特效"} + details={"纯粹解压用的"} + type="text" + /> ); }