Skip to content

Commit

Permalink
Merge pull request #166 from LiRenTech/feat/sound
Browse files Browse the repository at this point in the history
Feat/sound
  • Loading branch information
Littlefean authored Nov 26, 2024
2 parents 2e738ef + a4dbf70 commit 8b83e8c
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 4 deletions.
17 changes: 16 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> {
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!("程序运行了!");
Expand All @@ -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())
Expand Down
14 changes: 12 additions & 2 deletions src/core/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ export namespace Settings {
moveAmplitude: number;
moveFriction: number;
gamepadDeadzone: number;

// 音效相关
soundEnabled: boolean;
cuttingLineStartSoundFile: string;
connectLineStartSoundFile: string;
connectFindTargetSoundFile: string;
cuttingLineReleaseSoundFile: string;
// github 相关
githubToken: string;
githubUser: string;
Expand Down Expand Up @@ -73,7 +78,12 @@ export namespace Settings {
moveAmplitude: 2,
moveFriction: 0.1,
gamepadDeadzone: 0.1,

// 音效相关
soundEnabled: true,
cuttingLineStartSoundFile: "",
connectLineStartSoundFile: "",
connectFindTargetSoundFile: "",
cuttingLineReleaseSoundFile: "",
// github 相关
githubToken: "",
githubUser: "",
Expand Down
115 changes: 115 additions & 0 deletions src/core/SoundService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// 实测发现 不可行:
// @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 {

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 {
// 开始切断
export function cuttingLineStart() {
loadAndPlaySound(cuttingLineStartSoundFile);
}

// 开始连接
export function connectLineStart() {
loadAndPlaySound(connectLineStartSoundFile);
}

// 连接吸附到目标点
export function connectFindTarget() {
loadAndPlaySound(connectFindTargetSoundFile);
}

// 自动保存执行特效
// 自动备份执行特效

// 框选增加物体音效

// 切断特效声音
export function cuttingLineRelease() {
loadAndPlaySound(cuttingLineReleaseSoundFile);
}
// 连接成功
}

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();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start(0);
}

const pathAudioBufferMap = new StringDict<AudioBuffer>();

async function getAudioBufferByFilePath(filePath: string) {
// 先从缓存中获取音频数据
if (pathAudioBufferMap.hasId(filePath)) {
const result = pathAudioBufferMap.getById(filePath);
if (result) {
return result;
}
}

// 缓存中没有

// 读取文件为字符串
const base64Data: string = await invoke<string>("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;
}
}
4 changes: 4 additions & 0 deletions src/core/controller/concrete/ControllerCutting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -45,6 +46,8 @@ ControllerCutting.mousedown = (event: MouseEvent) => {
cuttingStartLocation,
cuttingStartLocation.clone(),
);
// 添加音效提示
SoundService.play.cuttingLineStart();
} else {
Stage.isCutting = false;
}
Expand Down Expand Up @@ -144,4 +147,5 @@ ControllerCutting.mouseup = (event: MouseEvent) => {
cuttingStartLocation.distance(ControllerCutting.lastMoveLocation) / 10,
),
);
SoundService.play.cuttingLineRelease();
};
6 changes: 5 additions & 1 deletion src/core/controller/concrete/ControllerNodeConnection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
* 右键连线功能 的控制器
Expand Down Expand Up @@ -109,6 +110,8 @@ ControllerNodeConnection.mousedown = (event: MouseEvent) => {
),
);
}
// 播放音效
SoundService.play.connectLineStart();
}
};

Expand All @@ -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;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => <RouterProvider router={router} />;
Expand Down Expand Up @@ -66,6 +67,7 @@ async function loadSyncModules() {
Stage.init();
StageHistoryManager.init();
StageStyleManager.init();
SoundService.init();
}

/** 加载语言文件 */
Expand Down
1 change: 1 addition & 0 deletions src/pages/settings/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function SettingsLayout() {
<NavLink to="/settings/control">{t("control")}</NavLink>
<NavLink to="/settings/ai">{t("ai")}</NavLink>
<NavLink to="/settings/github">{t("github")}</NavLink>
<NavLink to="/settings/sounds">sounds</NavLink>
</div>
<div className="container mx-auto flex-1 overflow-auto">
<Outlet />
Expand Down
37 changes: 37 additions & 0 deletions src/pages/settings/sounds.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Folder } from "lucide-react";
import { SettingField } from "./_field";

export default function Sounds() {
return (
<>
<SettingField
icon={<Folder />}
settingKey="cuttingLineStartSoundFile"
title={"切割线开始音效"}
details={"右键按下时刚开始创建切割线准备切割东西时播放的音效文件。"}
type="text"
/>
<SettingField
icon={<Folder />}
settingKey="connectLineStartSoundFile"
title={"连接线开始音效"}
details={"右键按下时刚开始创建连接时播放的音效文件。"}
type="text"
/>
<SettingField
icon={<Folder />}
settingKey="connectFindTargetSoundFile"
title={"连接线查找目标音效"}
details={"当"}
type="text"
/>
<SettingField
icon={<Folder />}
settingKey="cuttingLineReleaseSoundFile"
title={"切断线释放特效"}
details={"纯粹解压用的"}
type="text"
/>
</>
);
}
2 changes: 2 additions & 0 deletions src/pages/test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -50,6 +51,7 @@ export default function TestPage() {
</div>
<Button onClick={handleTestHttp}>test http</Button>
<Button onClick={handleTestImageBase64}>getImageBase64</Button>
<Button onClick={SoundService.play.cuttingLineStart}>test sound</Button>
last launch: {LastLaunch.version}
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type Path =
| `/settings/github`
| `/settings/performance`
| `/settings/physical`
| `/settings/sounds`
| `/settings/visual`
| `/test`
| `/welcome`;
Expand Down

0 comments on commit 8b83e8c

Please sign in to comment.