├── .gitignore ├── MusicalMoments.sln ├── MusicalMoments ├── AudioConverter.cs ├── BindKeyWindow.Designer.cs ├── BindKeyWindow.cs ├── BindKeyWindow.resx ├── CustomUI.cs ├── HelpWindow.Designer.cs ├── HelpWindow.cs ├── HelpWindow.resx ├── MainWindow.Designer.cs ├── MainWindow.cs ├── MainWindow.resx ├── Misc.cs ├── MusicalMoments - Backup.csproj ├── MusicalMoments.csproj ├── MusicalMoments.csproj.user ├── NeteaseCrypt.cs ├── Plugin │ ├── MMPluginSDKExample │ │ └── MMPluginSDKExample.exe │ └── 按键帮助 │ │ ├── 按键帮助.exe │ │ └── 按键帮助.json ├── PluginSDK.cs ├── Program.cs ├── Properties │ ├── PublishProfiles │ │ ├── FolderProfile.pubxml │ │ └── FolderProfile.pubxml.user │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ ├── Settings.settings │ └── launchSettings.json ├── ResourceFiles │ ├── 切换为音频.wav │ └── 切换为麦克风.wav ├── Updater.exe ├── UserSettings.cs ├── image │ ├── MMLOGO.ico │ ├── MMLOGO.png │ ├── 一般提示.png │ ├── 主页.png │ ├── 关于.png │ ├── 反馈.png │ ├── 发现.png │ ├── 声音.png │ ├── 微信.png │ ├── 插件.png │ ├── 支付宝.png │ ├── 状态.png │ ├── 编辑.png │ ├── 设置.png │ ├── 赞.png │ ├── 赞助.png │ ├── 转换.png │ ├── 音频.png │ └── 首页.png ├── taurusxin.LibNcmDump.dll └── 奶酪陷阱体.ttf └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # =============== Visual Studio / C# 基础配置 =============== 2 | 3 | # 编译输出目录 4 | bin/ 5 | obj/ 6 | 7 | # Visual Studio 用户相关配置文件 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | *.vs/ 13 | 14 | # 依赖包缓存 15 | packages/ 16 | *.nupkg 17 | project.lock.json 18 | 19 | # NuGet 缓存 20 | *.nuget/ 21 | .nuget/ 22 | 23 | # 发布目录 24 | publish/ 25 | out/ 26 | AppPackages/ 27 | 28 | # Rider / Resharper 配置 29 | .idea/ 30 | *.DotSettings.user 31 | 32 | # Windows 相关 33 | Thumbs.db 34 | desktop.ini 35 | $RECYCLE.BIN/ 36 | 37 | # 其他临时文件 38 | *.tmp 39 | *.bak 40 | *.swp 41 | *.sln.cache 42 | 43 | # =============== ASP.NET Core 相关 =============== 44 | appsettings.json 45 | appsettings.Development.json 46 | appsettings.Local.json 47 | 48 | # 证书 49 | *.pfx 50 | *.cer 51 | secrets.json 52 | 53 | # 服务器日志 54 | iisexpress/ 55 | logs/ 56 | 57 | # Docker 相关 58 | docker-compose.override.yml 59 | .env 60 | -------------------------------------------------------------------------------- /MusicalMoments.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34221.43 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MusicalMoments", "MusicalMoments\MusicalMoments.csproj", "{C47FF24F-7C82-4443-ACF8-B67DB4BBF86C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Release|Any CPU = Release|Any CPU 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {C47FF24F-7C82-4443-ACF8-B67DB4BBF86C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {C47FF24F-7C82-4443-ACF8-B67DB4BBF86C}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {C47FF24F-7C82-4443-ACF8-B67DB4BBF86C}.Debug|x64.ActiveCfg = Debug|x64 19 | {C47FF24F-7C82-4443-ACF8-B67DB4BBF86C}.Debug|x64.Build.0 = Debug|x64 20 | {C47FF24F-7C82-4443-ACF8-B67DB4BBF86C}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {C47FF24F-7C82-4443-ACF8-B67DB4BBF86C}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {C47FF24F-7C82-4443-ACF8-B67DB4BBF86C}.Release|x64.ActiveCfg = Release|x64 23 | {C47FF24F-7C82-4443-ACF8-B67DB4BBF86C}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {6285372B-3D9A-48CC-B15D-06B076A719B0} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /MusicalMoments/AudioConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using NAudio.Wave; 4 | 5 | namespace MusicalMoments 6 | { 7 | public class AudioConverter 8 | { 9 | public static bool ConvertTo(string inputFilePath, string outputFilePath, string outputFormat) 10 | { 11 | switch (outputFormat) 12 | { 13 | case "wav": 14 | return ConvertToWAV(inputFilePath, outputFilePath); 15 | case "mp3": 16 | return ConvertToMP3(inputFilePath, outputFilePath); 17 | default: 18 | throw new ArgumentException("不支持的目标格式"); 19 | } 20 | } 21 | 22 | private static bool ConvertToWAV(string inputFilePath, string outputFilePath) 23 | { 24 | try 25 | { 26 | using (var reader = new AudioFileReader(inputFilePath)) 27 | { 28 | var format = new WaveFormat(192000, 16, reader.WaveFormat.Channels); 29 | using (var resampler = new MediaFoundationResampler(reader, format)) 30 | { 31 | WaveFileWriter.CreateWaveFile(outputFilePath, resampler); 32 | } 33 | } 34 | return true; 35 | } 36 | catch (Exception) 37 | { 38 | return false; 39 | } 40 | } 41 | 42 | private static bool ConvertToMP3(string inputFilePath, string outputFilePath) 43 | { 44 | try 45 | { 46 | using (var reader = new AudioFileReader(inputFilePath)) 47 | { 48 | MediaFoundationEncoder.EncodeToMp3(reader, outputFilePath); 49 | } 50 | return true; 51 | } 52 | catch (Exception) 53 | { 54 | return false; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MusicalMoments/BindKeyWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace MusicalMoments 2 | { 3 | partial class BindKeyWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BindKeyWindow)); 32 | BindKey = new TextBox(); 33 | Tip = new Label(); 34 | removeKey = new Button(); 35 | SuspendLayout(); 36 | // 37 | // BindKey 38 | // 39 | BindKey.BorderStyle = BorderStyle.FixedSingle; 40 | BindKey.Font = new Font("Microsoft JhengHei UI", 12F, FontStyle.Bold, GraphicsUnit.Point); 41 | BindKey.ForeColor = Color.FromArgb(90, 90, 90); 42 | BindKey.ImeMode = ImeMode.Disable; 43 | BindKey.Location = new Point(12, 38); 44 | BindKey.Name = "BindKey"; 45 | BindKey.RightToLeft = RightToLeft.No; 46 | BindKey.Size = new Size(192, 28); 47 | BindKey.TabIndex = 9; 48 | BindKey.Text = "Key"; 49 | BindKey.TextAlign = HorizontalAlignment.Center; 50 | BindKey.KeyDown += BindKey_KeyDown; 51 | BindKey.KeyPress += BindKey_KeyPress; 52 | BindKey.Leave += BindKey_Leave; 53 | // 54 | // Tip 55 | // 56 | Tip.AutoSize = true; 57 | Tip.Font = new Font("Microsoft JhengHei UI", 15F, FontStyle.Regular, GraphicsUnit.Point); 58 | Tip.ForeColor = Color.FromArgb(90, 90, 90); 59 | Tip.Location = new Point(12, 9); 60 | Tip.Name = "Tip"; 61 | Tip.Size = new Size(192, 25); 62 | Tip.TabIndex = 10; 63 | Tip.Text = "请按下欲绑定的按键"; 64 | // 65 | // removeKey 66 | // 67 | removeKey.Font = new Font("Microsoft JhengHei UI", 10.25F, FontStyle.Regular, GraphicsUnit.Point); 68 | removeKey.ForeColor = Color.FromArgb(90, 90, 90); 69 | removeKey.Location = new Point(12, 72); 70 | removeKey.Name = "removeKey"; 71 | removeKey.Size = new Size(192, 31); 72 | removeKey.TabIndex = 11; 73 | removeKey.Text = "点我消除已绑定的键"; 74 | removeKey.UseVisualStyleBackColor = true; 75 | removeKey.Click += removeKey_Click; 76 | // 77 | // BindKeyWindow 78 | // 79 | AutoScaleDimensions = new SizeF(7F, 17F); 80 | AutoScaleMode = AutoScaleMode.Font; 81 | ClientSize = new Size(217, 114); 82 | Controls.Add(removeKey); 83 | Controls.Add(Tip); 84 | Controls.Add(BindKey); 85 | FormBorderStyle = FormBorderStyle.FixedSingle; 86 | Icon = (Icon)resources.GetObject("$this.Icon"); 87 | Name = "BindKeyWindow"; 88 | Text = "绑定按键"; 89 | Load += BindKeyWindow_Load; 90 | ResumeLayout(false); 91 | PerformLayout(); 92 | } 93 | 94 | #endregion 95 | 96 | private TextBox BindKey; 97 | private Label Tip; 98 | private Button removeKey; 99 | } 100 | } -------------------------------------------------------------------------------- /MusicalMoments/BindKeyWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace MusicalMoments 5 | { 6 | public partial class BindKeyWindow : Form 7 | { 8 | private Keys originalKey; 9 | private Keys nowKey; 10 | public Keys Key 11 | { 12 | get { return nowKey; } 13 | } 14 | 15 | public BindKeyWindow(Keys key) 16 | { 17 | InitializeComponent(); 18 | originalKey = key; 19 | BindKey.Text = originalKey.ToString(); 20 | } 21 | 22 | private void BindKeyWindow_Load(object sender, EventArgs e) 23 | { 24 | 25 | } 26 | 27 | private void BindKey_KeyDown(object sender, KeyEventArgs e) 28 | { 29 | string displayText = Misc.GetKeyDisplay(keyEventArgs: e); 30 | if (!string.IsNullOrEmpty(displayText)) 31 | { 32 | BindKey.Text = displayText; 33 | nowKey = e.KeyCode; 34 | e.SuppressKeyPress = true; 35 | } 36 | Misc.Delay(500); 37 | MainWindow.nowKey = nowKey; 38 | Close(); 39 | } 40 | 41 | private void BindKey_KeyPress(object sender, KeyPressEventArgs e) 42 | { 43 | string displayText = e.KeyChar.ToString().ToUpper(); 44 | BindKey.Text = displayText; 45 | if (e.KeyChar >= 'A' && e.KeyChar <= 'Z') 46 | { 47 | nowKey = (Keys)e.KeyChar; 48 | } 49 | else if (e.KeyChar >= 'a' && e.KeyChar <= 'z') 50 | { 51 | nowKey = (Keys)(e.KeyChar - 32); 52 | } 53 | e.Handled = true; 54 | } 55 | 56 | private void BindKey_Leave(object sender, EventArgs e) 57 | { 58 | //Close(); 59 | } 60 | 61 | private void removeKey_Click(object sender, EventArgs e) 62 | { 63 | MainWindow.nowKey = Keys.None; 64 | Close(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /MusicalMoments/CustomUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | namespace MusicalMoments 7 | { 8 | internal class CustomUI 9 | { 10 | public class CoverTabControl : TabControl 11 | { 12 | /// 13 | /// 解决系统TabControl多余边距问题 14 | /// 15 | public override Rectangle DisplayRectangle 16 | { 17 | get 18 | { 19 | Rectangle rect = base.DisplayRectangle; 20 | return new Rectangle(rect.Left - 4, rect.Top - 4, rect.Width + 8, rect.Height + 8); 21 | } 22 | } 23 | } 24 | public class BufferedListView : ListView 25 | { 26 | public BufferedListView() 27 | { 28 | // 开启双缓冲技术,减少闪烁 29 | this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); 30 | // 重绘背景,减少闪烁 31 | this.SetStyle(ControlStyles.ResizeRedraw, true); 32 | // 使用自定义的绘制 33 | this.OwnerDraw = true; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MusicalMoments/HelpWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace MusicalMoments 2 | { 3 | partial class HelpWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | label1 = new Label(); 32 | button1 = new Button(); 33 | r_help = new Button(); 34 | SuspendLayout(); 35 | // 36 | // label1 37 | // 38 | label1.AutoSize = true; 39 | label1.Font = new Font("Microsoft JhengHei UI", 12F); 40 | label1.Location = new Point(12, 9); 41 | label1.Name = "label1"; 42 | label1.Size = new Size(541, 20); 43 | label1.TabIndex = 0; 44 | label1.Text = "安装VB后系统声音消失解决方法 - 前往设置将音频设备还原成你的物理设备\r\n"; 45 | // 46 | // button1 47 | // 48 | button1.Font = new Font("Microsoft JhengHei UI", 9F); 49 | button1.Location = new Point(559, 5); 50 | button1.Name = "button1"; 51 | button1.Size = new Size(75, 28); 52 | button1.TabIndex = 1; 53 | button1.Text = "前往设置"; 54 | button1.UseVisualStyleBackColor = true; 55 | button1.Click += button1_Click; 56 | // 57 | // r_help 58 | // 59 | r_help.Location = new Point(597, 336); 60 | r_help.Name = "r_help"; 61 | r_help.Size = new Size(42, 41); 62 | r_help.TabIndex = 2; 63 | r_help.Text = "刷新"; 64 | r_help.UseVisualStyleBackColor = true; 65 | r_help.Click += r_help_Click; 66 | // 67 | // HelpWindow 68 | // 69 | AutoScaleDimensions = new SizeF(7F, 17F); 70 | AutoScaleMode = AutoScaleMode.Font; 71 | ClientSize = new Size(651, 389); 72 | Controls.Add(r_help); 73 | Controls.Add(button1); 74 | Controls.Add(label1); 75 | FormBorderStyle = FormBorderStyle.FixedSingle; 76 | Name = "HelpWindow"; 77 | Text = "帮助"; 78 | Load += HelpWindow_Load; 79 | ResumeLayout(false); 80 | PerformLayout(); 81 | } 82 | 83 | #endregion 84 | 85 | private Label label1; 86 | private Button button1; 87 | private Button r_help; 88 | } 89 | } -------------------------------------------------------------------------------- /MusicalMoments/HelpWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | using System.Threading.Tasks; 11 | using System.Windows.Forms; 12 | 13 | 14 | namespace MusicalMoments 15 | { 16 | public partial class HelpWindow : Form 17 | { 18 | public HelpWindow() 19 | { 20 | InitializeComponent(); 21 | this.Load += HelpWindow_Load; 22 | } 23 | private void button1_Click(object sender, EventArgs e) 24 | { 25 | // 打开系统设置中的声音设置 26 | System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo 27 | { 28 | FileName = "ms-settings:sound", 29 | UseShellExecute = true 30 | }); 31 | } 32 | private async void HelpWindow_Load(object sender, EventArgs e) 33 | { 34 | LoadJsonData(); 35 | } 36 | private List dynamicControls = new List(); 37 | 38 | private async void LoadJsonData(bool removeAddedControls = false) 39 | { 40 | string url = "http://scmd.cc/data.json"; 41 | 42 | try 43 | { 44 | if (removeAddedControls) 45 | { 46 | foreach (var control in dynamicControls) 47 | { 48 | this.Controls.Remove(control); 49 | control.Dispose(); 50 | } 51 | dynamicControls.Clear(); 52 | } 53 | 54 | using HttpClient client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }; 55 | string json = await client.GetStringAsync(url); 56 | 57 | var components = JsonSerializer.Deserialize>(json); 58 | if (components != null) 59 | { 60 | foreach (var component in components) 61 | { 62 | Control control = CreateControlFromJson(component); 63 | if (control != null) 64 | { 65 | this.Controls.Add(control); 66 | dynamicControls.Add(control); // 📌 记录动态添加的控件 67 | } 68 | } 69 | } 70 | } 71 | catch (Exception ex) 72 | { 73 | MessageBox.Show($"加载组件时出错: {ex.Message}"); 74 | } 75 | } 76 | 77 | private Control CreateControlFromJson(Component component) 78 | { 79 | // 1. 根据 Type 创建控件实例 80 | Control control = component.Type switch 81 | { 82 | "Button" => new Button(), 83 | "Label" => new Label(), 84 | "TextBox" => new TextBox(), 85 | "CheckBox" => new CheckBox(), 86 | _ => null 87 | }; 88 | 89 | if (control == null) return null; 90 | 91 | // 2. 提前取出 ClickScript,避免被当成普通属性处理 92 | string clickScript = null; 93 | if (component.RawProperties != null && component.RawProperties.TryGetValue("ClickScript", out var val)) 94 | { 95 | clickScript = val.GetString(); 96 | component.RawProperties.Remove("ClickScript"); 97 | } 98 | 99 | 100 | // 3. 通过反射设置控件的常规属性 101 | foreach (var prop in component.RawProperties) 102 | { 103 | try 104 | { 105 | var property = control.GetType().GetProperty(prop.Key); 106 | if (property != null && property.CanWrite) 107 | { 108 | object value = ConvertToPropertyValue(property.PropertyType, prop.Value); 109 | property.SetValue(control, value); 110 | } 111 | } 112 | catch (Exception ex) 113 | { 114 | MessageBox.Show($"属性 {prop.Key} 赋值失败: {ex.Message}"); 115 | } 116 | } 117 | 118 | // 4. 如果存在 ClickScript 且是 Button,绑定事件 119 | if (!string.IsNullOrEmpty(clickScript) && control is Button btn) 120 | { 121 | btn.Click += (s, e) => ExecutePowerShell(clickScript); 122 | } 123 | 124 | return control; 125 | } 126 | 127 | 128 | 129 | private object ConvertToPropertyValue(Type targetType, JsonElement json) 130 | { 131 | try 132 | { 133 | if (targetType == typeof(string)) 134 | return json.GetString(); 135 | if (targetType == typeof(int)) 136 | return json.GetInt32(); 137 | if (targetType == typeof(bool)) 138 | return json.GetBoolean(); 139 | if (targetType == typeof(Point)) 140 | { 141 | var arr = json.EnumerateArray().ToArray(); 142 | return new Point(arr[0].GetInt32(), arr[1].GetInt32()); 143 | } 144 | if (targetType == typeof(Size)) 145 | { 146 | var arr = json.EnumerateArray().ToArray(); 147 | return new Size(arr[0].GetInt32(), arr[1].GetInt32()); 148 | } 149 | if (targetType == typeof(Color)) 150 | return ColorTranslator.FromHtml(json.GetString()); 151 | 152 | // 支持更多类型请在此扩展 153 | } 154 | catch 155 | { 156 | // 忽略转换失败的字段 157 | } 158 | 159 | return null; 160 | } 161 | private string EscapePowerShell(string script) 162 | { 163 | return script.Replace("\"", "`\""); // 转义双引号 164 | } 165 | 166 | public static void ExecutePowerShell(string script) 167 | { 168 | try 169 | { 170 | using (Process process = new Process()) 171 | { 172 | process.StartInfo.FileName = "powershell"; 173 | process.StartInfo.Arguments = $"-Command \"{script}\""; 174 | process.StartInfo.RedirectStandardOutput = true; 175 | process.StartInfo.RedirectStandardError = true; 176 | process.StartInfo.UseShellExecute = false; 177 | process.StartInfo.CreateNoWindow = true; 178 | 179 | process.Start(); 180 | 181 | string output = process.StandardOutput.ReadToEnd(); 182 | string error = process.StandardError.ReadToEnd(); 183 | 184 | process.WaitForExit(); 185 | } 186 | } 187 | catch (Exception ex) 188 | { 189 | MessageBox.Show($"[动态执行错误]: {ex.Message}"); 190 | } 191 | } 192 | 193 | 194 | 195 | public class Component 196 | { 197 | public string Type { get; set; } 198 | 199 | [JsonExtensionData] 200 | public Dictionary RawProperties { get; set; } 201 | 202 | 203 | } 204 | 205 | private void r_help_Click(object sender, EventArgs e) 206 | { 207 | LoadJsonData(true); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /MusicalMoments/HelpWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /MusicalMoments/MainWindow.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using Microsoft.VisualBasic; 3 | using Newtonsoft.Json; 4 | using System.Diagnostics; 5 | using System.Net; 6 | using NAudio.Wave; 7 | using Gma.System.MouseKeyHook; 8 | using System.Reflection; 9 | using System.Text; 10 | using System.Resources; 11 | using System.Text.RegularExpressions; 12 | using System.Windows.Forms; 13 | using System.Security.Policy; 14 | using NAudio.Gui; 15 | using TagLib.Mpeg; 16 | 17 | using File = System.IO.File; 18 | using static System.Windows.Forms.VisualStyles.VisualStyleElement; 19 | using Microsoft.VisualBasic.Devices; 20 | using System.IO; 21 | using System.Net.Mail; 22 | using Newtonsoft.Json.Linq; 23 | 24 | namespace MusicalMoments 25 | { 26 | public partial class MainWindow : Form 27 | { 28 | public static string nowVer = "v1.4.1-release-x64"; 29 | public static string runningDirectory = AppDomain.CurrentDomain.BaseDirectory; 30 | public static Keys toggleStreamKey; 31 | public static Keys playAudioKey; 32 | public static string selectedAudioPath; 33 | public static string selectedPluginPath; 34 | public static int closeCount = 0; 35 | public static int playedCount = 0; 36 | public static int changedCount = 0; 37 | public static string firstStart = System.DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); 38 | public static bool playAudio = true; 39 | public static IKeyboardMouseEvents m_GlobalHook; 40 | public static bool isPlaying = false; 41 | public static float VBvolume = 1f; 42 | public static float volume = 1f; 43 | public static float tipsvolume = 1f; 44 | 45 | public static int VBInputComboSelect = 0; 46 | public static int VBOutputComboSelect = 0; 47 | public static int InputComboSelect = 0; 48 | public static int OutputComboSelect = 0; 49 | public static bool AudioEquipmentPlayCheck = true; 50 | 51 | public static List audioInfo = new List(); 52 | public MainWindow() 53 | { 54 | InitializeComponent(); 55 | Subscribe(); 56 | SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true); 57 | } 58 | public void Subscribe() 59 | { 60 | // 订阅全局键盘事件 61 | m_GlobalHook = Hook.GlobalEvents(); 62 | m_GlobalHook.KeyDown += GlobalHookKeyDown; 63 | } 64 | 65 | private async void GlobalHookKeyDown(object sender, KeyEventArgs e) 66 | { 67 | if (e.KeyCode == playAudioKey) 68 | { 69 | if (playAudio) 70 | { 71 | if (File.Exists(selectedAudioPath)) 72 | { 73 | if (isPlaying) 74 | { 75 | Misc.PlayAudioToSpecificDevice(selectedAudioPath, Misc.GetOutputDeviceID(comboBox_VBAudioEquipmentOutput.SelectedItem.ToString()), true, VBvolume, audioEquipmentPlay.Checked, selectedAudioPath, Misc.GetOutputDeviceID(comboBox_AudioEquipmentOutput.SelectedItem.ToString()), volume); 76 | isPlaying = true; 77 | } 78 | else 79 | { 80 | Misc.PlayAudioToSpecificDevice(selectedAudioPath, Misc.GetOutputDeviceID(comboBox_VBAudioEquipmentOutput.SelectedItem.ToString()), true, VBvolume, audioEquipmentPlay.Checked, selectedAudioPath, Misc.GetOutputDeviceID(comboBox_AudioEquipmentOutput.SelectedItem.ToString()), volume); 81 | isPlaying = false; 82 | } 83 | } 84 | } 85 | playedCount = playedCount + 1; 86 | } 87 | // 检查是否按下了切换流的按键 88 | if (e.KeyCode == toggleStreamKey) 89 | { 90 | if (switchStreamTips.Checked) 91 | { 92 | playAudio = !playAudio; 93 | string audioFilePath = playAudio 94 | ? Path.Combine(runningDirectory, @"ResourceFiles\切换为音频.wav") 95 | : Path.Combine(runningDirectory, @"ResourceFiles\切换为麦克风.wav"); 96 | PlayAudioex(audioFilePath, Misc.GetOutputDeviceID(comboBox_AudioEquipmentOutput.SelectedItem.ToString()), tipsvolume); 97 | if (!playAudio) 98 | { 99 | if (File.Exists(selectedAudioPath)) 100 | { 101 | if (isPlaying) 102 | { 103 | Misc.PlayAudioToSpecificDevice(selectedAudioPath, Misc.GetOutputDeviceID(comboBox_VBAudioEquipmentOutput.SelectedItem.ToString()), true, VBvolume, audioEquipmentPlay.Checked, selectedAudioPath, Misc.GetOutputDeviceID(comboBox_AudioEquipmentOutput.SelectedItem.ToString()), volume); 104 | } 105 | } 106 | Misc.StartMicrophoneToSpeaker(Misc.GetInputDeviceID(comboBox_AudioEquipmentInput.SelectedItem.ToString()), Misc.GetOutputDeviceID(comboBox_VBAudioEquipmentOutput.SelectedItem.ToString())); 107 | } 108 | else 109 | { 110 | Misc.StopMicrophoneToSpeaker(); 111 | } 112 | } 113 | changedCount = changedCount + 1; 114 | } 115 | 116 | foreach (var audio in audioInfo) 117 | { 118 | if (e.KeyCode == audio.Key) 119 | { 120 | if (playAudio) 121 | { 122 | if (File.Exists(audio.FilePath)) 123 | { 124 | if (isPlaying) 125 | { 126 | Misc.PlayAudioToSpecificDevice(audio.FilePath, Misc.GetOutputDeviceID(comboBox_VBAudioEquipmentOutput.SelectedItem.ToString()), true, VBvolume, audioEquipmentPlay.Checked, audio.FilePath, Misc.GetOutputDeviceID(comboBox_AudioEquipmentOutput.SelectedItem.ToString()), volume); 127 | isPlaying = true; 128 | } 129 | else 130 | { 131 | Misc.PlayAudioToSpecificDevice(audio.FilePath, Misc.GetOutputDeviceID(comboBox_VBAudioEquipmentOutput.SelectedItem.ToString()), true, VBvolume, audioEquipmentPlay.Checked, audio.FilePath, Misc.GetOutputDeviceID(comboBox_AudioEquipmentOutput.SelectedItem.ToString()), volume); 132 | isPlaying = false; 133 | } 134 | } 135 | } 136 | 137 | } 138 | } 139 | } 140 | private void sideLists_DrawItem(object sender, DrawListViewItemEventArgs e) 141 | { 142 | e.DrawBackground(); 143 | // 绘制图片 144 | if (e.Item.ImageIndex >= 0) 145 | { 146 | e.Graphics.DrawImage(sideLists.SmallImageList.Images[e.Item.ImageIndex], e.Bounds.Left, e.Bounds.Top); 147 | } 148 | // 设置文本颜色为灰色 149 | Brush textBrush = new SolidBrush(Color.FromArgb(85, 85, 85)); 150 | // 绘制文本 151 | e.Graphics.DrawString(e.Item.Text, e.Item.Font, textBrush, e.Bounds.Left + 40, e.Bounds.Top); 152 | } 153 | private async void MainWindow_Load(object sender, EventArgs e) 154 | { 155 | Visible = false; 156 | foreach (TabPage tabPage in mainTabControl.TabPages) 157 | { 158 | mainTabControl.SelectTab(tabPage); 159 | //我有强迫症 所以防止切换选项卡的时候有卡顿的加载就在启动前先都切一遍:D 160 | } 161 | /* 162 | 用于构建本地化基文件(感觉用不上这东西 别说海外用户了 能有几个国内用户用我就心满意足了TT) 163 | BuildLocalizationBaseFiles(this.Controls, $"{runningDirectory}Resources.zh-CN.resx"); 164 | */ 165 | Misc.FadeIn(200, this); 166 | 167 | mainTabControl.ItemSize = new System.Drawing.Size(0, 1); 168 | Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AudioData"));//创建存放音频的文件夹 169 | Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugin"));//创建存放插件的文件夹 170 | await Misc.AddAudioFilesToListView(runningDirectory + @"\AudioData\", audioListView); 171 | Misc.AddPluginFilesToListView(runningDirectory + @"\Plugin\", pluginListView); 172 | if (!Misc.IsAdministrator()) { Text += " [当前非管理员运行,可能会出现按下按键无反应]"; } 173 | else { Text += " MusicalMoments"; } 174 | 175 | 176 | label_VBStatus.Text = Misc.checkVB() ? "VB声卡已安装" : "VB声卡未安装"; 177 | comboBoxOutputFormat.SelectedIndex = 0; 178 | 179 | foreach (string device in Misc.GetInputAudioDeviceNames()) 180 | { 181 | comboBox_VBAudioEquipmentInput.Items.Add(device); 182 | comboBox_AudioEquipmentInput.Items.Add(device); 183 | } 184 | foreach (string device in Misc.GetOutputAudioDeviceNames()) 185 | { 186 | comboBox_VBAudioEquipmentOutput.Items.Add(device); 187 | comboBox_AudioEquipmentOutput.Items.Add(device); 188 | } 189 | comboBox_AudioEquipmentInput.SelectedIndex = 0; 190 | comboBox_AudioEquipmentOutput.SelectedIndex = 0; 191 | comboBox_VBAudioEquipmentInput.SelectedIndex = 0; 192 | comboBox_VBAudioEquipmentOutput.SelectedIndex = 0; 193 | VBVolumeTrackBar_Scroll(null, null); 194 | VolumeTrackBar_Scroll(null, null); 195 | TipsVolumeTrackBar_Scroll(null, null); 196 | numberLabel.Text = ""; 197 | 198 | 199 | LoadUserData(); 200 | if (CheckDuplicateKeys()) { MessageBox.Show($"已检测到相同按键 请勿作死将两个或多个音频绑定在同个按键上 该操作可能会导致MM崩溃 此提示会在绑定按键时与软件启动时检测并发出", "温馨提示"); } 201 | Misc.APIStartup(); 202 | //最后再版本验证 以防UI错误 203 | CheckNewVer(); 204 | 205 | } 206 | 207 | private void LoadUserData(bool changeToAudioPage = true) 208 | { 209 | string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "userSettings.json"); 210 | if (File.Exists(configPath)) 211 | { 212 | if (changeToAudioPage) 213 | { 214 | mainTabControl.SelectedIndex = 1; 215 | mainGroupBox.Text = "音频"; 216 | } 217 | 218 | try 219 | { 220 | string json = File.ReadAllText(configPath); 221 | var settings = JsonConvert.DeserializeObject(json); 222 | VBInputComboSelect = comboBox_VBAudioEquipmentInput.SelectedIndex; 223 | VBOutputComboSelect = comboBox_AudioEquipmentInput.SelectedIndex; 224 | InputComboSelect = comboBox_VBAudioEquipmentOutput.SelectedIndex; 225 | OutputComboSelect = comboBox_AudioEquipmentOutput.SelectedIndex; 226 | AudioEquipmentPlayCheck = audioEquipmentPlay.Checked; 227 | 228 | comboBox_VBAudioEquipmentInput.SelectedIndex = settings.VBAudioEquipmentInputIndex >= comboBox_VBAudioEquipmentInput.Items.Count ? 0 : settings.VBAudioEquipmentInputIndex; 229 | comboBox_AudioEquipmentInput.SelectedIndex = settings.AudioEquipmentInputIndex >= comboBox_AudioEquipmentInput.Items.Count ? 0 : settings.AudioEquipmentInputIndex; 230 | comboBox_VBAudioEquipmentOutput.SelectedIndex = settings.VBAudioEquipmentOutputIndex >= comboBox_VBAudioEquipmentOutput.Items.Count ? 0 : settings.VBAudioEquipmentOutputIndex; 231 | comboBox_AudioEquipmentOutput.SelectedIndex = settings.AudioEquipmentOutputIndex >= comboBox_AudioEquipmentOutput.Items.Count ? 0 : settings.AudioEquipmentOutputIndex; 232 | if (!string.IsNullOrEmpty(settings.ToggleStreamKey)) 233 | { 234 | Keys key; 235 | if (Enum.TryParse(settings.ToggleStreamKey, out key)) 236 | { 237 | toggleStreamKey = key; 238 | ToggleStream.Text = key.ToString(); 239 | } 240 | } 241 | if (!string.IsNullOrEmpty(settings.PlayAudioKey)) 242 | { 243 | Keys key; 244 | if (Enum.TryParse(settings.PlayAudioKey, out key)) 245 | { 246 | playAudioKey = key; 247 | PlayAudio.Text = key.ToString(); 248 | } 249 | } 250 | if (settings.CloseCount == 35) 251 | { 252 | MessageBox.Show("看来你已经启动过了35次软件呢!究竟是因为太喜欢放音频还是因为我的代码BUG太多一直闪退捏?!?(ps:算是小彩蛋吧?看到了可以发我哟 毕竟我也想知道是谁这么喜欢放音频 或者是帮我找BUG?后面还有彩蛋哦 多打开几次吧~)", "你好呀"); 253 | } 254 | else if (settings.CloseCount == 50) 255 | { 256 | MessageBox.Show("看来你已经启动过了50次软件呢!这次我敢确定绝对不是我代码BUG多!!因为BUG多一直闪退就代表我的软件不好用!之后用户就会换软件了!!!(ps:当启动次数有80次后会有一个大彩蛋呢~)", "你好呀"); 257 | } 258 | else if (settings.CloseCount == 80) 259 | { 260 | MessageBox.Show("这么快就80次了吗?!?那就告诉你大彩蛋的位置吧~首先呢要去关于页面 然后连续戳20次LOGO图片就有了 那就这样 拜啦 后面也不会有彩蛋了~", "你好呀"); 261 | } 262 | VBVolumeTrackBar.Value = (int)(settings.VBVolume * 100); 263 | VolumeTrackBar.Value = (int)(settings.Volume * 100); 264 | TipsVolumeTrackBar.Value = (int)(settings.TipsVolume * 100); 265 | VBVolumeTrackBar_Scroll(null, null); 266 | VolumeTrackBar_Scroll(null, null); 267 | TipsVolumeTrackBar_Scroll(null, null); 268 | audioEquipmentPlay.Checked = settings.AudioEquipmentPlay; 269 | switchStreamTips.Checked = settings.SwitchStreamTips; 270 | closeCount = settings.CloseCount; 271 | playedCount = settings.PlayedCount; 272 | changedCount = settings.ChangedCount; 273 | firstStart = settings.FirstStart; 274 | } 275 | catch (Exception ex) 276 | { 277 | MessageBox.Show($"读取配置时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 278 | } 279 | } 280 | else 281 | { 282 | mainTabControl.SelectedIndex = 0; 283 | comboBox_AudioEquipmentInput.SelectedIndex = 0; 284 | comboBox_AudioEquipmentOutput.SelectedIndex = 0; 285 | comboBox_VBAudioEquipmentInput.SelectedIndex = 0; 286 | comboBox_VBAudioEquipmentOutput.SelectedIndex = 0; 287 | VBVolumeTrackBar_Scroll(null, null); 288 | VolumeTrackBar_Scroll(null, null); 289 | TipsVolumeTrackBar_Scroll(null, null); 290 | firstStart = System.DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); 291 | } 292 | } 293 | private async void MainWindow_FormClosing(object sender, FormClosingEventArgs e) 294 | { 295 | SaveUserData(true); 296 | 297 | e.Cancel = true; 298 | Misc.FadeOut(200, this); 299 | Misc.APIShutdown(); 300 | } 301 | 302 | private void SaveUserData(bool addCloseCount = false) 303 | { 304 | var settings = new UserSettings 305 | { 306 | VBAudioEquipmentInputIndex = comboBox_VBAudioEquipmentInput.SelectedIndex, 307 | AudioEquipmentInputIndex = comboBox_AudioEquipmentInput.SelectedIndex, 308 | VBAudioEquipmentOutputIndex = comboBox_VBAudioEquipmentOutput.SelectedIndex, 309 | AudioEquipmentOutputIndex = comboBox_AudioEquipmentOutput.SelectedIndex, 310 | ToggleStreamKey = toggleStreamKey.ToString(), 311 | PlayAudioKey = playAudioKey.ToString(), 312 | AudioEquipmentPlay = audioEquipmentPlay.Checked, 313 | SwitchStreamTips = switchStreamTips.Checked, 314 | VBVolume = VBVolumeTrackBar.Value / 100f, 315 | Volume = VolumeTrackBar.Value / 100f, 316 | TipsVolume = TipsVolumeTrackBar.Value / 100f, 317 | 318 | CloseCount = closeCount + (addCloseCount ? 1 : 0), 319 | PlayedCount = playedCount, 320 | ChangedCount = changedCount, 321 | FirstStart = firstStart, 322 | }; 323 | string json = JsonConvert.SerializeObject(settings); 324 | File.WriteAllText(Path.Combine(runningDirectory, "userSettings.json"), json); 325 | } 326 | 327 | 328 | 329 | 330 | private async void CheckNewVer(bool noUpdateShowMessage = false) 331 | { 332 | var newVerTips = await Misc.GetLatestVersionTipsAsync(); 333 | var latestVer = await Misc.GetLatestVersionAsync(); 334 | if (latestVer != nowVer) 335 | { 336 | DialogResult dialogResult = MessageBox.Show($"Musical Moments存在新版本,请尽快更新。当前版本为{nowVer} 最新版本为{latestVer}。\r\n按下是则自动下载最新版本压缩包 按下否则跳转至最新版本页面 按下取消则关闭\r\n\r\n以下是新版本简介:\r\n{newVerTips}", "新版本推送", MessageBoxButtons.YesNoCancel); 337 | 338 | if (dialogResult == DialogResult.Yes) 339 | { 340 | string[] parts = latestVer.Split('-'); 341 | string version = parts[0].TrimStart('v'); // 获取版本号部分 342 | string downloadUrl = $"https://kkgithub.com/TheD0ubleC/MusicalMoments/releases/download/{latestVer}/MM.Release-{version}.zip"; 343 | try 344 | { 345 | using (WebClient wc = new WebClient()) 346 | { 347 | wc.DownloadFile(downloadUrl, $"{runningDirectory}MM.Release-{version}.zip"); 348 | MessageBox.Show($"下载成功 已存放至运行目录 即将开始更新 详情路径:{runningDirectory}MM.Release-{version}.zip", "提示"); 349 | Process currentProcess = Process.GetCurrentProcess(); 350 | int pid = currentProcess.Id; 351 | StartApplication(Path.Combine(runningDirectory, "Updater.exe"), $"{pid} {runningDirectory}MM.Release-{version}.zip {runningDirectory}"); 352 | } 353 | } 354 | catch (Exception ex) 355 | { MessageBox.Show($"下载失败,请至github页面自行下载或在群文件下载 错误详情:{ex.ToString()}", "错误"); } 356 | } 357 | else if (dialogResult == DialogResult.No) 358 | { 359 | Process.Start(new ProcessStartInfo("https://github.com/TheD0ubleC/MusicalMoments/releases/tag/" + latestVer) { UseShellExecute = true }); 360 | } 361 | } 362 | if (noUpdateShowMessage) 363 | { MessageBox.Show($"当前已是最新版本!", "提示"); } 364 | } 365 | static void StartApplication(string applicationPath, string arguments) 366 | { 367 | try 368 | { 369 | if (File.Exists(applicationPath)) 370 | { 371 | ProcessStartInfo startInfo = new ProcessStartInfo 372 | { 373 | FileName = applicationPath, 374 | Arguments = arguments 375 | }; 376 | Process.Start(startInfo); 377 | Console.WriteLine($"Started application: {applicationPath} with arguments: {arguments}"); 378 | } 379 | else 380 | { 381 | Console.WriteLine($"Application not found: {applicationPath}"); 382 | } 383 | } 384 | catch (Exception ex) 385 | { 386 | Console.WriteLine($"Error starting application: {ex.Message}"); 387 | } 388 | } 389 | private async void sideLists_SelectedIndexChanged(object sender, EventArgs e) 390 | { 391 | SaveUserData(); 392 | comboBox_VBAudioEquipmentInput.Items.Clear(); 393 | comboBox_AudioEquipmentInput.Items.Clear(); 394 | foreach (string device in Misc.GetInputAudioDeviceNames()) 395 | { 396 | comboBox_VBAudioEquipmentInput.Items.Add(device); 397 | comboBox_AudioEquipmentInput.Items.Add(device); 398 | } 399 | comboBox_VBAudioEquipmentOutput.Items.Clear(); 400 | comboBox_AudioEquipmentOutput.Items.Clear(); 401 | foreach (string device in Misc.GetOutputAudioDeviceNames()) 402 | { 403 | comboBox_VBAudioEquipmentOutput.Items.Add(device); 404 | comboBox_AudioEquipmentOutput.Items.Add(device); 405 | } 406 | Misc.AddPluginFilesToListView(runningDirectory + @"\Plugin\", pluginListView); 407 | foreach (int index in sideLists.SelectedIndices) 408 | { 409 | mainTabControl.SelectTab(index); 410 | } 411 | foreach (ListViewItem item in sideLists.SelectedItems) 412 | { 413 | mainGroupBox.Text = $"{item.Text}"; 414 | } 415 | AudioListView_fd.Items.Clear(); 416 | if (mainTabControl.SelectedIndex == 1) { reLoadList(); } 417 | LoadUserData(false); 418 | } 419 | private void retestVB_Click(object sender, EventArgs e) 420 | { 421 | //MessageBox.Show(Misc.checkVB() ? "vb已安装" : "vb未安装"); 422 | label_VBStatus.Text = Misc.checkVB() ? "VB声卡已安装" : "VB声卡未安装"; 423 | } 424 | private void audioListView_MouseClick(object sender, MouseEventArgs e) 425 | { 426 | if (e.Button == MouseButtons.Right) 427 | { 428 | var hitTestInfo = audioListView.HitTest(e.Location); 429 | if (hitTestInfo.Item != null) 430 | { 431 | // 显示菜单在鼠标点击的位置 432 | mainContextMenuStrip.Show(audioListView, e.Location); 433 | } 434 | } 435 | } 436 | private void 播放ToolStripMenuItem_Click(object sender, EventArgs e) 437 | { 438 | ListViewItem selectedItem = audioListView.SelectedItems[0]; 439 | string filePath = selectedItem.Tag as string; 440 | try 441 | { 442 | PlayAudioex(filePath, Misc.GetOutputDeviceID(comboBox_AudioEquipmentOutput.SelectedItem.ToString()), volume); 443 | } 444 | catch (Exception ex) 445 | { 446 | MessageBox.Show($"播放音频时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 447 | } 448 | playedCount = playedCount + 1; 449 | } 450 | private void 删除ToolStripMenuItem_Click(object sender, EventArgs e) 451 | { 452 | if (audioListView.SelectedItems.Count > 0) 453 | { 454 | ListViewItem selectedItem = audioListView.SelectedItems[0]; 455 | string filePath = selectedItem.Tag as string; 456 | if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath)) 457 | { 458 | DialogResult dialogResult = MessageBox.Show("确定要删除这个文件吗?", "删除文件", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); 459 | if (dialogResult == DialogResult.Yes) 460 | { 461 | try 462 | { 463 | File.Delete(filePath); // 删除文件 464 | audioListView.Items.Remove(selectedItem); // 如果文件删除成功,从ListView中移除项 465 | } 466 | catch (Exception ex) 467 | { 468 | MessageBox.Show($"删除文件时出错: {ex.Message}"); 469 | } 470 | } 471 | } 472 | } 473 | } 474 | private void 重命名ToolStripMenuItem_Click(object sender, EventArgs e) 475 | { 476 | if (audioListView.SelectedItems.Count > 0) 477 | { 478 | ListViewItem selectedItem = audioListView.SelectedItems[0]; 479 | string originalFilePath = selectedItem.Tag as string; 480 | if (originalFilePath == null) return; 481 | string directoryPath = Path.GetDirectoryName(originalFilePath); 482 | string originalFileName = Path.GetFileName(originalFilePath); 483 | string currentName = selectedItem.Text; 484 | string extension = Path.GetExtension(originalFilePath); 485 | string input = Interaction.InputBox("请输入新的名称:", "重命名", currentName, -1, -1); 486 | if (string.IsNullOrEmpty(input) || input.Equals(currentName, StringComparison.OrdinalIgnoreCase)) 487 | { 488 | return; 489 | } 490 | string newFileName = input + extension; 491 | string newFilePath = Path.Combine(directoryPath, newFileName); 492 | // 检查新命名的文件是否已存在 493 | if (File.Exists(newFilePath)) 494 | { 495 | MessageBox.Show("命名重复", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 496 | } 497 | else 498 | { 499 | try 500 | { 501 | File.Move(originalFilePath, newFilePath); 502 | selectedItem.Text = input; 503 | selectedItem.Tag = newFilePath; 504 | } 505 | catch (Exception ex) 506 | { 507 | MessageBox.Show($"重命名文件时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); 508 | } 509 | } 510 | } 511 | } 512 | private void ToggleStream_KeyDown(object sender, KeyEventArgs e) 513 | { 514 | string displayText = Misc.GetKeyDisplay(keyEventArgs: e); 515 | if (!string.IsNullOrEmpty(displayText)) 516 | { 517 | ToggleStream.Text = displayText; 518 | toggleStreamKey = e.KeyCode; 519 | ToggleStream.Enabled = false; 520 | ToggleStream.Enabled = true; 521 | e.SuppressKeyPress = true; 522 | } 523 | } 524 | private void ToggleStream_KeyPress(object sender, KeyPressEventArgs e) 525 | { 526 | string displayText = e.KeyChar.ToString().ToUpper(); 527 | ToggleStream.Text = displayText; 528 | if (e.KeyChar >= 'A' && e.KeyChar <= 'Z') 529 | { 530 | toggleStreamKey = (Keys)e.KeyChar; 531 | } 532 | else if (e.KeyChar >= 'a' && e.KeyChar <= 'z') 533 | { 534 | toggleStreamKey = (Keys)(e.KeyChar - 32); 535 | } 536 | e.Handled = true; 537 | } 538 | private void PlayAudio_KeyDown(object sender, KeyEventArgs e) 539 | { 540 | string displayText = Misc.GetKeyDisplay(keyEventArgs: e); 541 | if (!string.IsNullOrEmpty(displayText)) 542 | { 543 | PlayAudio.Text = displayText; 544 | playAudioKey = e.KeyCode; 545 | PlayAudio.Enabled = false; 546 | PlayAudio.Enabled = true; 547 | e.SuppressKeyPress = true; 548 | } 549 | } 550 | private void PlayAudio_KeyPress(object sender, KeyPressEventArgs e) 551 | { 552 | string displayText = e.KeyChar.ToString().ToUpper(); 553 | PlayAudio.Text = displayText; 554 | if (e.KeyChar >= 'A' && e.KeyChar <= 'Z') 555 | { 556 | playAudioKey = (Keys)e.KeyChar; 557 | } 558 | else if (e.KeyChar >= 'a' && e.KeyChar <= 'z') 559 | { 560 | playAudioKey = (Keys)(e.KeyChar - 32); 561 | } 562 | e.Handled = true; 563 | 564 | } 565 | private void info_ListBox_SelectedIndexChanged(object sender, EventArgs e) 566 | { 567 | switch (info_ListBox.Text) 568 | { 569 | case "NAudio": 570 | info_Label5.Text = 571 | "MM使用了NAudio音频处理库。\r\n" + 572 | "NAudio遵循Microsoft Public License (Ms-PL)。\r\n" + 573 | "版权所有 (c) [NAudio] \r\n" + 574 | "完整的许可证文本可在以下链接找到:\r\n" + 575 | "https://opensource.org/licenses/MS-PL\r\n" + 576 | "特此向NAudio及其贡献者表示感谢。"; 577 | break; 578 | case "Newtonsoft.Json": 579 | info_Label5.Text = 580 | "MM使用了Newtonsoft.Json库。\r\n" + 581 | "Newtonsoft.Json遵循MIT License。\r\n" + 582 | "版权所有 (c) [Newtonsoft.Json] \r\n" + 583 | "完整的许可证文本可在以下链接找到:\r\n" + 584 | "https://opensource.org/licenses/MIT\r\n" + 585 | "特此向Newtonsoft.Json及其贡献者表示感谢。"; 586 | break; 587 | case "System.Management": 588 | info_Label5.Text = 589 | "MM使用了System.Management库。\r\n" + 590 | "NAudio遵循MIT License。\r\n" + 591 | "版权所有 (c) [.NET Foundation 和贡献者] \r\n" + 592 | "完整的许可证文本可在以下链接找到:\r\n" + 593 | "https://opensource.org/licenses/MS-PL\r\n" + 594 | "特此向.NET社区及其贡献者表示感谢。"; 595 | break; 596 | case "taglib-sharp-netstandard2.0": 597 | info_Label5.Text = 598 | "MM使用了taglib-sharp-netstandard2.0库。\r\n" + 599 | "taglib-sharp-netstandard2.0遵循LGPL-2.1 License\r\n" + 600 | "版权所有 (c) [taglib-sharp] \r\n" + 601 | "完整的许可证文本可在以下链接找到:\r\n" + 602 | "https://opensource.org/licenses/LGPL-2.1\r\n" + 603 | "特此向taglib-sharp及其贡献者表示感谢。"; 604 | break; 605 | case "MouseKeyHook": 606 | info_Label5.Text = 607 | "MM使用了MouseKeyHook库。\r\n" + 608 | "MouseKeyHook遵循MIT License\r\n" + 609 | "版权所有 (c) [George Mamaladze] \r\n" + 610 | "完整的许可证文本可在以下链接找到:\r\n" + 611 | "https://opensource.org/licenses/MS-PL\r\n" + 612 | "特此向George Mamaladze及其贡献者表示感谢。"; 613 | break; 614 | case "MediaToolkit": 615 | info_Label5.Text = 616 | "MM使用了MediaToolkit库。\r\n" + 617 | "MediaToolkit暂无使用中的许可证\r\n" + 618 | "版权所有 (c) [Aydin] \r\n" + 619 | "特此向Aydin表示感谢。"; 620 | break; 621 | case "HtmlAgilityPack": 622 | info_Label5.Text = 623 | "HtmlAgilityPack。\r\n" + 624 | "HtmlAgilityPack遵循 MIT License\r\n" + 625 | "版权所有 (c) [zzzprojects] \r\n" + 626 | "完整的许可证文本可在以下链接找到:\r\n" + 627 | "https://opensource.org/licenses/MS-PL\r\n" + 628 | "特此向zzzprojects及其贡献者表示感谢。"; 629 | break; 630 | } 631 | } 632 | private static WaveOutEvent currentOutputDevice = null; 633 | private static AudioFileReader currentAudioFile = null; 634 | public void PlayAudioex(string audioFilePath, int deviceNumber, float volume) 635 | { 636 | try 637 | { 638 | if (currentOutputDevice != null) 639 | { 640 | currentOutputDevice.Stop(); 641 | currentOutputDevice.Dispose(); 642 | currentAudioFile?.Dispose(); 643 | } 644 | var audioFile = new AudioFileReader(audioFilePath); 645 | var outputDevice = new WaveOutEvent { DeviceNumber = deviceNumber }; 646 | // 应用音量设置 647 | outputDevice.Volume = volume; // 确保 volume 值在 0 到 1 之间 648 | outputDevice.PlaybackStopped += (sender, e) => 649 | { 650 | outputDevice.Dispose(); 651 | audioFile.Dispose(); 652 | }; 653 | outputDevice.Init(audioFile); 654 | outputDevice.Play(); 655 | currentOutputDevice = outputDevice; 656 | currentAudioFile = audioFile; 657 | } 658 | catch (Exception ex) 659 | { 660 | MessageBox.Show($"播放音频时出错: {ex.Message}", "错误"); 661 | } 662 | } 663 | public void StopPlayback() 664 | { 665 | try 666 | { 667 | if (currentOutputDevice != null) 668 | { 669 | currentOutputDevice.Stop(); // 停止播放 670 | currentOutputDevice.Dispose(); // 释放音频输出设备资源 671 | currentOutputDevice = null; // 清除引用 672 | } 673 | if (currentAudioFile != null) 674 | { 675 | currentAudioFile.Dispose(); // 释放音频文件资源 676 | currentAudioFile = null; // 清除引用 677 | } 678 | } 679 | catch (Exception ex) 680 | { 681 | MessageBox.Show($"停止播放时出错: {ex.Message}", "错误"); 682 | } 683 | } 684 | 685 | private void 设为播放项ToolStripMenuItem_Click(object sender, EventArgs e) 686 | { 687 | ListViewItem selectedItem = audioListView.SelectedItems[0]; 688 | string filePath = selectedItem.Tag as string; 689 | selectedAudioPath = filePath; 690 | SelectedAudioLabel.Text = $"已选择:{selectedItem.Text}"; 691 | } 692 | public async Task reLoadList() 693 | { 694 | audioInfo.Clear(); 695 | audioListView.Items.Clear(); 696 | await Misc.AddAudioFilesToListView(runningDirectory + @"\AudioData\", audioListView); 697 | foreach (ListViewItem item in audioListView.Items) 698 | { 699 | string filePath = item.Tag as string; 700 | Keys key = Keys.None; 701 | if (item.SubItems[3].Text != "未绑定") 702 | { key = (Keys)Enum.Parse(typeof(Keys), item.SubItems[3].Text); } 703 | audioInfo.Add(new AudioInfo { Name = item.SubItems[0].Text, FilePath = filePath, Key = key }); 704 | } 705 | 706 | } 707 | private async void reLoadAudioListsView_Click(object sender, EventArgs e) 708 | { 709 | reLoadList(); 710 | } 711 | private void aifadian_Click(object sender, EventArgs e) 712 | { 713 | string url = "https://afdian.net/a/MusicalMoments"; 714 | ProcessStartInfo startInfo = new ProcessStartInfo 715 | { 716 | FileName = url, 717 | UseShellExecute = true 718 | }; 719 | Process.Start(startInfo); 720 | } 721 | private void toVB_Click(object sender, EventArgs e) 722 | { 723 | toVB.Text = "下载中"; 724 | using (WebClient webClient = new WebClient()) 725 | { 726 | string url = "https://download.vb-audio.com/Download_CABLE/VBCABLE_Driver_Pack43.zip"; 727 | string filePath = Path.Combine(runningDirectory, "VB.zip"); 728 | try 729 | { 730 | webClient.DownloadFile(url, filePath); 731 | DialogResult result = MessageBox.Show($"下载完成,已保存在程序的运行目录,是否打开文件?", "打开文件", MessageBoxButtons.YesNo); 732 | if (result == DialogResult.Yes) 733 | { 734 | Process.Start(new ProcessStartInfo(filePath) { UseShellExecute = true }); 735 | } 736 | } 737 | catch (Exception ex) 738 | { 739 | MessageBox.Show("下载文件时发生错误:" + ex.Message); 740 | } 741 | toVB.Text = "点我下载"; 742 | } 743 | } 744 | private void toSettings_Click(object sender, EventArgs e) 745 | { 746 | mainTabControl.SelectedIndex = 2; 747 | } 748 | private void mToAudioData_Click(object sender, EventArgs e) 749 | { 750 | string folderPath = Path.Combine(runningDirectory, "AudioData"); 751 | if (!Directory.Exists(folderPath)) 752 | { 753 | Directory.CreateDirectory(folderPath); 754 | } 755 | Process.Start(new ProcessStartInfo() 756 | { 757 | FileName = folderPath, 758 | UseShellExecute = true 759 | }); 760 | } 761 | private async void 打开文件所在位置ToolStripMenuItem_Click(object sender, EventArgs e) 762 | { 763 | if (audioListView.SelectedItems.Count > 0) 764 | { 765 | ListViewItem selectedItem = audioListView.SelectedItems[0]; 766 | string filePath = selectedItem.Tag as string; 767 | if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath)) 768 | { 769 | string argument = "/select, \"" + filePath + "\""; 770 | Process.Start("explorer.exe", argument); 771 | } 772 | else 773 | { 774 | reLoadList(); 775 | } 776 | } 777 | } 778 | private void VBVolumeTrackBar_Scroll(object sender, EventArgs e) 779 | { 780 | volume_Label1.Text = $"声卡麦({VBVolumeTrackBar.Value}%):"; 781 | VBvolume = VBVolumeTrackBar.Value / 100f; 782 | } 783 | private void VolumeTrackBar_Scroll(object sender, EventArgs e) 784 | { 785 | volume_Label2.Text = $"物理麦({VolumeTrackBar.Value}%):"; 786 | volume = VolumeTrackBar.Value / 100f; 787 | } 788 | private void TipsVolumeTrackBar_Scroll(object sender, EventArgs e) 789 | { 790 | volume_Label3.Text = $"提示音({TipsVolumeTrackBar.Value}%):"; 791 | tipsvolume = TipsVolumeTrackBar.Value / 100f; 792 | } 793 | private static int logoClickCount = 0; 794 | private void LogoImage_Click(object sender, EventArgs e) 795 | { 796 | logoClickCount++; 797 | if (logoClickCount >= 10) 798 | { 799 | MessageBox.Show($"嗨~我是CC,这个软件的开发者。我们第一次见面是在<{firstStart}> 现在是<{System.DateTime.Now.ToString("yyyy年MM月dd日HH时mm分ss秒")}> 这期间你已经播放了<{playedCount}>次音频 还切换了<{changedCount}>次音频流!!(ps:如果数据不太对可能是因为你不小心把运行目录的json删除了吧?)", "恭喜你发现了彩蛋!"); 800 | } 801 | } 802 | 803 | 804 | private void upData_button_Click(object sender, EventArgs e) 805 | { 806 | OpenFileDialog openFileDialog = new OpenFileDialog(); 807 | openFileDialog.Filter = "音视频文件|*.mp3;*.wav;*.ogg;*.acc;*.ncm;*.qmc3;*.mp4;*.avi|全部文件|*.*"; 808 | if (openFileDialog.ShowDialog() == DialogResult.OK) 809 | { 810 | string selectedFile = openFileDialog.FileName; 811 | dataPath_TextBox.Text = selectedFile; 812 | name_TextBox.Text = Path.GetFileNameWithoutExtension(selectedFile); 813 | 814 | // 显示音频属性 815 | conversion_Label4.Text = "源信息:" + Misc.DisplayAudioProperties(selectedFile); 816 | } 817 | } 818 | 819 | private void tabPage6_DragDrop(object sender, DragEventArgs e) 820 | { 821 | string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); 822 | if (files.Length > 0) 823 | { 824 | string selectedFile = files[0]; 825 | dataPath_TextBox.Text = selectedFile; 826 | name_TextBox.Text = Path.GetFileNameWithoutExtension(selectedFile); 827 | 828 | // 显示音频属性 829 | conversion_Label4.Text = "源信息:" + Misc.DisplayAudioProperties(selectedFile); 830 | } 831 | } 832 | 833 | private void tabPage6_DragEnter(object sender, DragEventArgs e) 834 | { 835 | if (e.Data.GetDataPresent(DataFormats.FileDrop)) 836 | e.Effect = DragDropEffects.Copy; 837 | } 838 | private void convert_Button_Click(object sender, EventArgs e) 839 | { 840 | if (!File.Exists(dataPath_TextBox.Text)) 841 | { 842 | MessageBox.Show("选择的文件不存在", "错误"); 843 | } 844 | else 845 | { 846 | string extension = Path.GetExtension(dataPath_TextBox.Text).ToLower(); 847 | if (extension == ".ncm") 848 | { 849 | if (Misc.NCMConvert(dataPath_TextBox.Text, runningDirectory + "AudioData\\" + name_TextBox.Text + ".mp3") == 0) 850 | { 851 | conversion_Label5.Text = "转换后:" + Misc.DisplayAudioProperties(runningDirectory + "AudioData\\" + name_TextBox.Text + ".mp3"); 852 | MessageBox.Show("转换成功 已存储至运行目录下的AudioData文件夹", "提示"); 853 | } 854 | else 855 | { 856 | MessageBox.Show("转换失败", "错误"); 857 | } 858 | } 859 | else if (extension == ".flac" || extension == ".ogg" || extension == ".mp3" || extension == ".wav") 860 | { 861 | string targetFormat = comboBoxOutputFormat.SelectedItem.ToString().ToLower(); 862 | if (AudioConverter.ConvertTo(dataPath_TextBox.Text, runningDirectory + "AudioData\\" + name_TextBox.Text + "." + targetFormat, targetFormat)) 863 | { 864 | conversion_Label5.Text = "转换后:" + Misc.DisplayAudioProperties(runningDirectory + "AudioData\\" + name_TextBox.Text + "." + targetFormat); 865 | MessageBox.Show("转换成功 已存储至运行目录下的AudioData文件夹", "提示"); 866 | } 867 | else 868 | { 869 | MessageBox.Show("转换失败", "错误"); 870 | } 871 | } 872 | else 873 | { 874 | MessageBox.Show("不支持的文件格式", "错误"); 875 | } 876 | } 877 | } 878 | private async void audioListView_DragDrop(object sender, DragEventArgs e) 879 | { 880 | string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); 881 | string audioDataPath = Path.Combine(runningDirectory, "AudioData"); 882 | HashSet supportedExtensions = new HashSet { ".mp3", ".wav" }; 883 | Directory.CreateDirectory(audioDataPath); 884 | foreach (string file in files) 885 | { 886 | string extension = Path.GetExtension(file).ToLower(); 887 | if (supportedExtensions.Contains(extension)) 888 | { 889 | string destFile = Path.Combine(audioDataPath, Path.GetFileName(file)); 890 | if (File.Exists(destFile)) 891 | { 892 | var result = MessageBox.Show($"文件 {Path.GetFileName(file)} 已存在。是否覆盖?", "文件冲突", MessageBoxButtons.YesNo, MessageBoxIcon.Question); 893 | if (result != DialogResult.Yes) 894 | { 895 | continue; 896 | } 897 | } 898 | File.Copy(file, destFile, true); 899 | } 900 | } 901 | reLoadList(); 902 | } 903 | private void audioListView_DragEnter(object sender, DragEventArgs e) 904 | { 905 | if (e.Data.GetDataPresent(DataFormats.FileDrop)) 906 | { 907 | e.Effect = DragDropEffects.Copy; 908 | } 909 | else 910 | { 911 | e.Effect = DragDropEffects.None; 912 | } 913 | } 914 | private void mToAudioData1_Click(object sender, EventArgs e) 915 | { 916 | string folderPath = Path.Combine(runningDirectory, "AudioData"); 917 | if (!Directory.Exists(folderPath)) 918 | { 919 | Directory.CreateDirectory(folderPath); 920 | } 921 | Process.Start(new ProcessStartInfo() 922 | { 923 | FileName = folderPath, 924 | UseShellExecute = true 925 | }); 926 | } 927 | 928 | private void 停止播放ToolStripMenuItem_Click(object sender, EventArgs e) 929 | { 930 | StopPlayback(); 931 | } 932 | 933 | public class AudioItem 934 | { 935 | public string Name { get; set; } 936 | public string DownloadLink { get; set; } 937 | 938 | public AudioItem(string name, string downloadLink) 939 | { 940 | Name = name; 941 | DownloadLink = downloadLink; 942 | } 943 | } 944 | 945 | // 定义一个全局变量来存储原始音频列表和对应链接 946 | List OriginalAudioItems = new List(); 947 | private async void LoadList_Click(object sender, EventArgs e) 948 | { 949 | string jsonFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "download.json"); 950 | string jsonUrl = "https://www.scmd.cc/api/all-audio"; 951 | string hashUrl = "https://www.scmd.cc/api/all-audio-hash"; 952 | 953 | try 954 | { 955 | bool useLocalJson = false; 956 | 957 | if (File.Exists(jsonFilePath)) 958 | { 959 | bool isHashMatching = await Misc.VerifyFileHashAsync(jsonFilePath, hashUrl); 960 | if (isHashMatching) 961 | { 962 | Console.WriteLine("本地 JSON 文件哈希匹配,直接加载数据!"); 963 | useLocalJson = true; 964 | } 965 | else 966 | { 967 | Console.WriteLine("本地 JSON 文件已过期,重新下载!"); 968 | } 969 | } 970 | else 971 | { 972 | Console.WriteLine("本地 JSON 文件不存在,开始下载!"); 973 | } 974 | 975 | if (!useLocalJson) 976 | { 977 | await Misc.DownloadJsonFile(jsonUrl, jsonFilePath); 978 | } 979 | 980 | // 清空 ListView 和 ListBox 981 | AudioListView_fd.Items.Clear(); 982 | DownloadLinkListBox.Items.Clear(); 983 | 984 | // 加载 JSON 数据到 ListView 和 ListBox 985 | await Misc.GetDownloadJsonFromFile(jsonFilePath, AudioListView_fd, DownloadLinkListBox); 986 | 987 | // 获取 total 并更新 numberLabel 988 | int? total = await Misc.GetTotalFromJsonFile(jsonFilePath); 989 | numberLabel.Text = total.HasValue ? $"{total.Value} 个项目" : $"{AudioListView_fd.Items.Count} 个项目"; 990 | OriginalAudioItems.Clear(); 991 | foreach (ListViewItem item in AudioListView_fd.Items) 992 | { 993 | OriginalAudioItems.Add((ListViewItem)item.Clone()); 994 | } 995 | 996 | if (!Debugger.IsAttached) { Misc.ButtonStabilization(5, LoadList); } 997 | } 998 | catch (Exception ex) 999 | { 1000 | MessageBox.Show($"加载列表时发生错误: {ex.Message}"); 1001 | } 1002 | } 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | private void SearchBarTextBox_Enter(object sender, EventArgs e) 1009 | { 1010 | if (SearchBarTextBox.Text == "搜索") 1011 | { 1012 | SearchBarTextBox.Text = ""; 1013 | } 1014 | } 1015 | 1016 | private void SearchBarTextBox_Leave(object sender, EventArgs e) 1017 | { 1018 | if (string.IsNullOrWhiteSpace(SearchBarTextBox.Text)) 1019 | { 1020 | SearchBarTextBox.Text = "搜索"; 1021 | } 1022 | } 1023 | 1024 | private void SearchBarTextBox_TextChanged(object sender, EventArgs e) 1025 | { 1026 | string keyword = SearchBarTextBox.Text.Trim().ToLower(); 1027 | 1028 | // 如果备份列表为空,就直接返回 1029 | if (OriginalAudioItems == null || OriginalAudioItems.Count == 0) 1030 | return; 1031 | 1032 | AudioListView_fd.BeginUpdate(); 1033 | AudioListView_fd.Items.Clear(); 1034 | 1035 | IEnumerable filteredItems; 1036 | 1037 | if (string.IsNullOrWhiteSpace(keyword) || keyword == "搜索") 1038 | { 1039 | filteredItems = OriginalAudioItems; 1040 | } 1041 | else 1042 | { 1043 | filteredItems = OriginalAudioItems.Where(item => 1044 | (item.SubItems.Count > 0 && item.SubItems[0].Text.ToLower().Contains(keyword)) || // 名称列 1045 | (item.SubItems.Count > 1 && item.SubItems[1].Text.ToLower().Contains(keyword)) || // 下载次数列 1046 | (item.SubItems.Count > 2 && item.SubItems[2].Text.ToLower().Contains(keyword)) // 上传者列 1047 | ); 1048 | } 1049 | 1050 | foreach (var item in filteredItems) 1051 | { 1052 | AudioListView_fd.Items.Add((ListViewItem)item.Clone()); 1053 | } 1054 | 1055 | AudioListView_fd.EndUpdate(); 1056 | 1057 | numberLabel.Text = $"{AudioListView_fd.Items.Count} 个项目"; 1058 | } 1059 | 1060 | 1061 | private async void DownloadSelected_Click(object sender, EventArgs e) 1062 | { 1063 | if (AudioListView_fd.SelectedIndices.Count == 0) 1064 | { 1065 | MessageBox.Show("请选择要下载的音频。", "提示"); 1066 | return; 1067 | } 1068 | 1069 | int selectedIndex = AudioListView_fd.SelectedIndices[0]; 1070 | 1071 | // 确保 `ListBox` 也有对应的链接 1072 | if (selectedIndex >= DownloadLinkListBox.Items.Count) 1073 | { 1074 | MessageBox.Show("未找到对应的下载链接。", "错误"); 1075 | return; 1076 | } 1077 | 1078 | // 获取下载链接 1079 | string downloadLink = DownloadLinkListBox.Items[selectedIndex].ToString(); 1080 | 1081 | // 获取原始 URL 文件名 1082 | string rawFileName = Path.GetFileName(new Uri(downloadLink).AbsolutePath); 1083 | 1084 | // ✅ 解码 URL 编码的文件名 1085 | string decodedFileName = Uri.UnescapeDataString(rawFileName); 1086 | 1087 | // 指定下载的保存路径 1088 | string saveDirectory = Path.Combine(Application.StartupPath, "AudioData"); 1089 | string savePath = Path.Combine(saveDirectory, decodedFileName); 1090 | 1091 | try 1092 | { 1093 | if (!Directory.Exists(saveDirectory)) 1094 | { 1095 | Directory.CreateDirectory(saveDirectory); 1096 | } 1097 | 1098 | using (HttpClient client = new HttpClient()) 1099 | { 1100 | byte[] fileBytes = await client.GetByteArrayAsync(downloadLink); 1101 | await File.WriteAllBytesAsync(savePath, fileBytes); 1102 | } 1103 | 1104 | MessageBox.Show($"已成功下载到音频文件夹:{decodedFileName}\n保存路径:{savePath}", "下载完成"); 1105 | } 1106 | catch (Exception ex) 1107 | { 1108 | MessageBox.Show($"下载失败:{ex.Message}", "错误"); 1109 | } 1110 | } 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | bool pluginServer = false; 1117 | private void TogglePluginServer_Click(object sender, EventArgs e) 1118 | { 1119 | if (!pluginServer) 1120 | { 1121 | PluginSDK.PluginServer.StartServer(); 1122 | pluginServer = true; 1123 | TogglePluginServer.Text = "关闭插件服务"; 1124 | PluginStatus.Text = "插件状态:已开启"; 1125 | LoadPlugin.Enabled = true; 1126 | pluginListView.Enabled = true; 1127 | PluginServerAddress.Text = $"{PluginSDK.PluginServer.GetServerAddress()}"; 1128 | } 1129 | else 1130 | { 1131 | PluginSDK.PluginServer.StopServer(); 1132 | pluginServer = false; 1133 | TogglePluginServer.Text = "开启插件服务"; 1134 | PluginStatus.Text = "插件状态:未开启"; 1135 | LoadPlugin.Enabled = false; 1136 | pluginListView.Enabled = false; 1137 | PluginServerAddress.Text = ""; 1138 | } 1139 | } 1140 | 1141 | private void PluginServerAddress_Click(object sender, EventArgs e) 1142 | { 1143 | Clipboard.SetText(PluginServerAddress.Text); 1144 | } 1145 | 1146 | private void mToPluginData_Click(object sender, EventArgs e) 1147 | { 1148 | string folderPath = Path.Combine(runningDirectory, "Plugin"); 1149 | if (!Directory.Exists(folderPath)) 1150 | { 1151 | Directory.CreateDirectory(folderPath); 1152 | } 1153 | Process.Start(new ProcessStartInfo() 1154 | { 1155 | FileName = folderPath, 1156 | UseShellExecute = true 1157 | }); 1158 | } 1159 | private void LoadPlugin_Click(object sender, EventArgs e) 1160 | { 1161 | // 检查是否有选中的项 1162 | if (pluginListView.SelectedItems.Count > 0) 1163 | { 1164 | // 获取选中项 1165 | ListViewItem selectedItem = pluginListView.SelectedItems[0]; 1166 | 1167 | // 从选中项的 Tag 属性中获取插件文件的完整路径 1168 | string pluginFilePath = selectedItem.Tag as string; 1169 | 1170 | // 如果插件文件路径不为空 1171 | if (!string.IsNullOrEmpty(pluginFilePath)) 1172 | { 1173 | // 设置启动信息 1174 | ProcessStartInfo startInfo = new ProcessStartInfo(); 1175 | startInfo.FileName = pluginFilePath; 1176 | startInfo.Arguments = PluginServerAddress.Text; 1177 | 1178 | // 启动选中的插件应用程序 1179 | try 1180 | { Process.Start(startInfo); } 1181 | catch (Exception ex) { MessageBox.Show($"请确认该插件是否为可执行文件 错误详情:\r\n{ex}", "错误"); } 1182 | } 1183 | } 1184 | else 1185 | { 1186 | // 如果没有选中的项,给出提示或者清空显示的内容 1187 | MessageBox.Show("请选择要加载的插件。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); 1188 | } 1189 | } 1190 | 1191 | private void reLoadPluginListsView_Click(object sender, EventArgs e) 1192 | { 1193 | Misc.AddPluginFilesToListView(runningDirectory + @"\Plugin\", pluginListView); 1194 | } 1195 | 1196 | private void toC_Click(object sender, EventArgs e) 1197 | { 1198 | mainTabControl.SelectedIndex = 5; 1199 | } 1200 | 1201 | private void audioTips_Click(object sender, EventArgs e) 1202 | { 1203 | MessageBox.Show("如果您的音频格式非192000hz频率那么在播放时可能会出现电音 这是因为VB声卡通道是192000hz频率 \r\n(我都检查多少遍代码了也没有什么缓冲溢出 这是VB的原因关我什么事 别一直吵吵 还是那句话 别人能用为什么你不能用 有时候该换个方位想想究竟是软件的问题还是文件的问题 我在三台机子上都试过了并未出现电音 且委托朋友帮我测试也都没有问题 人不行别怪路不平 代码都开源的 有问题你找出来我能不改吗?关键是你也找不出问题 就什么事都赖我身上 爱用不用没强迫你用 不行就去用SoundPad 没人拦着你)", "提示"); 1204 | } 1205 | public static Keys nowKey = Keys.None; 1206 | private async void 绑定按键ToolStripMenuItem_Click(object sender, EventArgs e) 1207 | { 1208 | ListViewItem selectedItem = audioListView.SelectedItems[0]; 1209 | Keys key = Keys.None; 1210 | if (selectedItem.SubItems[3].Text != "未绑定") 1211 | { 1212 | key = (Keys)Enum.Parse(typeof(Keys), selectedItem.SubItems[3].Text); 1213 | } 1214 | BindKeyWindow bindKeyWindow = new BindKeyWindow(key); 1215 | bindKeyWindow.ShowDialog(); 1216 | nowKey = bindKeyWindow.Key; 1217 | 1218 | Misc.WriteKeyJsonInfo(Path.ChangeExtension(selectedItem.Tag.ToString(), ".json"), nowKey.ToString()); 1219 | if (CheckDuplicateKeys()) { MessageBox.Show($"已检测到相同按键 请勿作死将两个或多个音频绑定在同个按键上 该操作可能会导致MM崩溃 此提示会在绑定按键时与软件启动时检测并发出", "温馨提示"); } 1220 | 1221 | } 1222 | 1223 | public static bool CheckDuplicateKeys() 1224 | { 1225 | for (int i = 0; i < audioInfo.Count; i++) 1226 | { 1227 | var parentItem = audioInfo[i]; 1228 | if (parentItem.Key == Keys.None) 1229 | continue; 1230 | 1231 | for (int j = i + 1; j < audioInfo.Count; j++) 1232 | { 1233 | var childItem = audioInfo[j]; 1234 | if (childItem.Key == Keys.None) 1235 | continue; 1236 | 1237 | if (parentItem.Key == childItem.Key) 1238 | { 1239 | return true; 1240 | } 1241 | } 1242 | } 1243 | return false; 1244 | } 1245 | 1246 | 1247 | private void FeedbackTipsButton_Click(object sender, EventArgs e) 1248 | { 1249 | MessageBox.Show("请留下您的问题与您的联系方式 如电子邮箱、QQ等 收到反馈后会在72小时内回复您\r\n但请注意 切勿滥用", "提示"); 1250 | } 1251 | 1252 | private void FeedbackButton_Click(object sender, EventArgs e) 1253 | { 1254 | string host = "smtphz.qiye.163.com"; 1255 | int port = 25; 1256 | string from = "feedback@scmd.cc"; 1257 | string to = "feedback@scmd.cc"; 1258 | MailMessage message = new MailMessage(from, to); 1259 | string level = "普通"; 1260 | if (FeedbackAverage.Checked) { level = "普通"; } 1261 | if (FeedbackUrgent.Checked) { level = "紧急"; } 1262 | if (FeedbackDisaster.Checked) { level = "灾难"; } 1263 | 1264 | message.Subject = $"{level} - {FeedbackTitle.Text}"; 1265 | message.IsBodyHtml = true; 1266 | //我有强迫症 看不惯难看的默认样式 然后特地为这个写了个很好看很好看的样式(★w★) 1267 | string htmlBody = $@" 1268 | 1269 | 1270 | 1308 | 1309 | 1310 |
1311 |
1312 |

{level} - {FeedbackTitle.Text}

1313 |
1314 |
1315 |

{FeedbackContent.Text.Replace("\n", "
")}

1316 |
1317 | 1320 |
1321 | 1322 | "; 1323 | 1324 | message.Body = htmlBody; 1325 | if (string.IsNullOrWhiteSpace(FeedbackTitle.Text) || string.IsNullOrWhiteSpace(FeedbackContent.Text)) 1326 | { 1327 | MessageBox.Show("标题和内容不能为空,请填写后再提交。", "错误"); 1328 | return; 1329 | } 1330 | 1331 | if (!IsValidContent(FeedbackContent.Text)) 1332 | { 1333 | MessageBox.Show("请输入有意义的内容,避免乱码或无意义字符。", "错误"); 1334 | return; 1335 | } 1336 | 1337 | SmtpClient client = new SmtpClient(host, port); 1338 | client.UseDefaultCredentials = false; 1339 | client.Credentials = new NetworkCredential("feedback@scmd.cc", "SCMDfb2023"); 1340 | try 1341 | { 1342 | client.Send(message); 1343 | MessageBox.Show("反馈发送成功!", "谢谢你"); 1344 | } 1345 | catch (Exception ex) 1346 | { 1347 | MessageBox.Show("邮件发送失败:" + ex.Message, "抱歉"); 1348 | } 1349 | } 1350 | private bool IsValidContent(string content) 1351 | { 1352 | int chineseCount = content.Count(c => c >= 0x4E00 && c <= 0x9FA5); 1353 | if (chineseCount < content.Length * 0.3) 1354 | return false; 1355 | if (content.Length < 10) 1356 | return false; 1357 | const int maxRepetitions = 3; 1358 | char lastChar = '\0'; 1359 | int currentRepetition = 1; 1360 | 1361 | foreach (char c in content) 1362 | { 1363 | if (c == lastChar) 1364 | { 1365 | currentRepetition++; 1366 | if (currentRepetition > maxRepetitions) 1367 | return true; 1368 | } 1369 | else 1370 | { 1371 | lastChar = c; 1372 | currentRepetition = 1; 1373 | } 1374 | } 1375 | return true; 1376 | } 1377 | 1378 | private void audioEquipmentPlay_CheckedChanged(object sender, EventArgs e) 1379 | { 1380 | VBInputComboSelect = comboBox_VBAudioEquipmentInput.SelectedIndex; 1381 | VBOutputComboSelect = comboBox_AudioEquipmentInput.SelectedIndex; 1382 | InputComboSelect = comboBox_VBAudioEquipmentOutput.SelectedIndex; 1383 | OutputComboSelect = comboBox_AudioEquipmentOutput.SelectedIndex; 1384 | AudioEquipmentPlayCheck = audioEquipmentPlay.Checked; 1385 | } 1386 | 1387 | private void comboBox_VBAudioEquipmentInput_SelectedIndexChanged(object sender, EventArgs e) 1388 | { 1389 | VBInputComboSelect = comboBox_VBAudioEquipmentInput.SelectedIndex; 1390 | VBOutputComboSelect = comboBox_AudioEquipmentInput.SelectedIndex; 1391 | InputComboSelect = comboBox_VBAudioEquipmentOutput.SelectedIndex; 1392 | OutputComboSelect = comboBox_AudioEquipmentOutput.SelectedIndex; 1393 | AudioEquipmentPlayCheck = audioEquipmentPlay.Checked; 1394 | } 1395 | 1396 | private void comboBox_VBAudioEquipmentOutput_SelectedIndexChanged(object sender, EventArgs e) 1397 | { 1398 | VBInputComboSelect = comboBox_VBAudioEquipmentInput.SelectedIndex; 1399 | VBOutputComboSelect = comboBox_AudioEquipmentInput.SelectedIndex; 1400 | InputComboSelect = comboBox_VBAudioEquipmentOutput.SelectedIndex; 1401 | OutputComboSelect = comboBox_AudioEquipmentOutput.SelectedIndex; 1402 | AudioEquipmentPlayCheck = audioEquipmentPlay.Checked; 1403 | } 1404 | 1405 | private void comboBox_AudioEquipmentInput_SelectedIndexChanged(object sender, EventArgs e) 1406 | { 1407 | VBInputComboSelect = comboBox_VBAudioEquipmentInput.SelectedIndex; 1408 | VBOutputComboSelect = comboBox_AudioEquipmentInput.SelectedIndex; 1409 | InputComboSelect = comboBox_VBAudioEquipmentOutput.SelectedIndex; 1410 | OutputComboSelect = comboBox_AudioEquipmentOutput.SelectedIndex; 1411 | AudioEquipmentPlayCheck = audioEquipmentPlay.Checked; 1412 | } 1413 | 1414 | private void comboBox_AudioEquipmentOutput_SelectedIndexChanged(object sender, EventArgs e) 1415 | { 1416 | VBInputComboSelect = comboBox_VBAudioEquipmentInput.SelectedIndex; 1417 | VBOutputComboSelect = comboBox_AudioEquipmentInput.SelectedIndex; 1418 | InputComboSelect = comboBox_VBAudioEquipmentOutput.SelectedIndex; 1419 | OutputComboSelect = comboBox_AudioEquipmentOutput.SelectedIndex; 1420 | AudioEquipmentPlayCheck = audioEquipmentPlay.Checked; 1421 | } 1422 | 1423 | private void open_help_window_Click(object sender, EventArgs e) 1424 | { 1425 | Form help_window = new HelpWindow(); 1426 | help_window.Show(); 1427 | } 1428 | 1429 | private void open_help_button2_Click(object sender, EventArgs e) 1430 | { 1431 | Form help_window = new HelpWindow(); 1432 | help_window.Show(); 1433 | } 1434 | 1435 | private void check_update_Click(object sender, EventArgs e) 1436 | { 1437 | var ori_text = check_update.Text; 1438 | check_update.Text = "检查中..."; 1439 | CheckNewVer(true); 1440 | check_update.Text = ori_text; 1441 | } 1442 | 1443 | private void open_help_button1_Click(object sender, EventArgs e) 1444 | { 1445 | Form help_window = new HelpWindow(); 1446 | help_window.Show(); 1447 | } 1448 | } 1449 | } -------------------------------------------------------------------------------- /MusicalMoments/Misc.cs: -------------------------------------------------------------------------------- 1 | using HtmlAgilityPack; 2 | using Microsoft.Win32; 3 | using NAudio.Wave; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Reflection; 10 | using System.Runtime.CompilerServices; 11 | using System.Security.Cryptography; 12 | using System.Security.Principal; 13 | using System.Text; 14 | using System.Text.RegularExpressions; 15 | using System.Resources; 16 | using System.Text.Json; 17 | using Newtonsoft.Json; 18 | using System.Globalization; 19 | namespace MusicalMoments 20 | { 21 | public class AudioInfo 22 | { 23 | public string Name { get; set; } 24 | public string FilePath { get; set; } 25 | public Keys Key { get; set; } 26 | } 27 | 28 | 29 | 30 | internal class Misc 31 | { 32 | public static WaveOutEvent currentOutputDevice = null; 33 | public static AudioFileReader currentAudioFile = null; 34 | public static bool checkVB() 35 | { 36 | bool isVBCableInstalled = false; // 初始化VB-Cable安装状态为假(未安装) 37 | // 打开注册表中的卸载信息部分 38 | RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"); 39 | if (key != null) // 确保key不为null 40 | { 41 | foreach (string subkeyName in key.GetSubKeyNames()) // 遍历所有子键 42 | { 43 | RegistryKey subkey = key.OpenSubKey(subkeyName); 44 | string displayName = subkey.GetValue("DisplayName") as string; 45 | if (!string.IsNullOrEmpty(displayName) && displayName.Contains("CABLE")) // 检查显示名称是否包含"CABLE" 46 | { 47 | isVBCableInstalled = true; // 如果找到,更新安装状态为真(已安装) 48 | break; // 退出循环 49 | } 50 | } 51 | } 52 | return isVBCableInstalled; 53 | } 54 | public static string[] GetInputAudioDeviceNames() 55 | { 56 | int waveInDevices = WaveIn.DeviceCount; 57 | string[] inputDeviceNames = new string[waveInDevices]; 58 | for (int i = 0; i < waveInDevices; i++) 59 | { 60 | WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(i); 61 | inputDeviceNames[i] = deviceInfo.ProductName; 62 | } 63 | return inputDeviceNames; 64 | } 65 | public static string[] GetOutputAudioDeviceNames() 66 | { 67 | int waveOutDevices = WaveOut.DeviceCount; 68 | string[] outputDeviceNames = new string[waveOutDevices]; 69 | for (int i = 0; i < waveOutDevices; i++) 70 | { 71 | WaveOutCapabilities deviceInfo = WaveOut.GetCapabilities(i); 72 | outputDeviceNames[i] = deviceInfo.ProductName; 73 | } 74 | return outputDeviceNames; 75 | } 76 | public static int GetOutputDeviceID(string deviceName) 77 | { 78 | for (int deviceId = 0; deviceId < WaveOut.DeviceCount; deviceId++) 79 | { 80 | var capabilities = WaveOut.GetCapabilities(deviceId); 81 | if (capabilities.ProductName.Contains(deviceName, StringComparison.OrdinalIgnoreCase)) 82 | { 83 | return deviceId; 84 | } 85 | } 86 | return -1; // 没有找到匹配的设备 87 | } 88 | public static int GetInputDeviceID(string deviceName) 89 | { 90 | for (int deviceId = 0; deviceId < WaveIn.DeviceCount; deviceId++) 91 | { 92 | var capabilities = WaveIn.GetCapabilities(deviceId); 93 | if (capabilities.ProductName.Contains(deviceName, StringComparison.OrdinalIgnoreCase)) 94 | { 95 | return deviceId; 96 | } 97 | } 98 | return -1; // 没有找到匹配的设备 99 | } 100 | private static bool ignoreNextPlayAttempt = false; 101 | public static void PlayAudioToSpecificDevice(string audioFilePath, int deviceNumber, bool stopCurrent, float volume,bool rplay, string raudioFilePath, int rdeviceNumber, float rvolume) 102 | { 103 | try 104 | { 105 | if (stopCurrent && currentOutputDevice != null) 106 | { 107 | currentOutputDevice.Stop(); 108 | currentOutputDevice.Dispose(); 109 | currentAudioFile.Dispose(); 110 | currentOutputDeviceEX.Stop(); 111 | currentOutputDeviceEX.Dispose(); 112 | currentAudioFileEX?.Dispose(); 113 | currentOutputDevice = null; 114 | currentAudioFile = null; 115 | return; 116 | } 117 | else if (currentOutputDevice == null) 118 | { 119 | var audioFile = new AudioFileReader(audioFilePath); 120 | var outputDevice = new WaveOutEvent { DeviceNumber = deviceNumber, DesiredLatency = 50 }; 121 | outputDevice.Volume = Math.Max(0, Math.Min(1, volume)); 122 | int totalMilliseconds = (int)audioFile.TotalTime.TotalMilliseconds; 123 | if(rplay) 124 | { 125 | PlayAudioex(raudioFilePath,rdeviceNumber,rvolume); 126 | ignoreNextPlayAttempt = false; 127 | } 128 | outputDevice.PlaybackStopped += (sender, e) => 129 | { 130 | outputDevice.Dispose(); 131 | audioFile.Dispose(); 132 | ignoreNextPlayAttempt = false; 133 | currentOutputDevice = null; 134 | currentAudioFile = null; 135 | }; 136 | outputDevice.Init(audioFile); 137 | outputDevice.Play(); 138 | ignoreNextPlayAttempt = false; 139 | currentOutputDevice = outputDevice; 140 | currentAudioFile = audioFile; 141 | } 142 | ignoreNextPlayAttempt = false; 143 | } 144 | catch (Exception ex) 145 | { 146 | MessageBox.Show($"播放音频时出错: {ex.Message}"); 147 | } 148 | ignoreNextPlayAttempt = false; 149 | } 150 | public static void PlayAudioex(string audioFilePath, int deviceNumber, float volume) 151 | { 152 | try 153 | { 154 | if (currentOutputDeviceEX != null) 155 | { 156 | currentOutputDeviceEX.Stop(); 157 | currentOutputDeviceEX.Dispose(); 158 | currentAudioFileEX?.Dispose(); 159 | } 160 | var audioFile = new AudioFileReader(audioFilePath); 161 | var outputDevice = new WaveOutEvent { DeviceNumber = deviceNumber, DesiredLatency = 50 }; 162 | // 应用音量设置 163 | outputDevice.Volume = volume; // 确保 volume 值在 0 到 1 之间 164 | outputDevice.PlaybackStopped += (sender, e) => 165 | { 166 | outputDevice.Dispose(); 167 | audioFile.Dispose(); 168 | }; 169 | outputDevice.Init(audioFile); 170 | outputDevice.Play(); 171 | currentOutputDeviceEX = outputDevice; 172 | currentAudioFileEX = audioFile; 173 | } 174 | catch (Exception ex) 175 | { 176 | MessageBox.Show($"播放音频时出错: {ex.Message}", "错误"); 177 | } 178 | } 179 | public static void Delay(int time) 180 | { 181 | int start = Environment.TickCount; 182 | while (Math.Abs(Environment.TickCount - start) < time) 183 | { 184 | Application.DoEvents(); 185 | } 186 | } 187 | public static async Task AddAudioFilesToListView(string directoryPath, ListView listView) 188 | { 189 | DirectoryInfo dirInfo = new DirectoryInfo(directoryPath); 190 | FileInfo[] audioFiles = dirInfo.GetFiles("*.*", SearchOption.TopDirectoryOnly) 191 | .Where(f => f.Extension.Equals(".mp3", StringComparison.OrdinalIgnoreCase) || 192 | f.Extension.Equals(".wav", StringComparison.OrdinalIgnoreCase) || 193 | f.Extension.Equals(".falc", StringComparison.OrdinalIgnoreCase)) 194 | .ToArray(); 195 | foreach (FileInfo audioFile in audioFiles) 196 | { 197 | // 获取音频文件信息 198 | var fileTag = TagLib.File.Create(audioFile.FullName); 199 | string fileName = Path.GetFileNameWithoutExtension(audioFile.Name); 200 | string track = string.IsNullOrWhiteSpace(fileTag.Tag.Title) ? "无" : fileTag.Tag.Title; 201 | string fileType = audioFile.Extension.TrimStart('.').ToUpper(); 202 | 203 | // 获取 JSON 文件路径并读取信息 204 | string jsonFilePath = Path.ChangeExtension(audioFile.FullName, ".json"); 205 | string jsonInfo = ReadKeyJsonInfo(jsonFilePath); 206 | 207 | // 将文件信息添加到 ListView 中 208 | ListViewItem item = new ListViewItem(new[] { fileName, track, fileType, jsonInfo }); 209 | item.Tag = audioFile.FullName; 210 | listView.Items.Add(item); 211 | } 212 | } 213 | 214 | 215 | private static string ReadKeyJsonInfo(string jsonFilePath) 216 | { 217 | string info = "未绑定"; 218 | try 219 | { 220 | // 检查 JSON 文件是否存在 221 | if (File.Exists(jsonFilePath)) 222 | { 223 | // 读取 JSON 文件内容并尝试解析 224 | string jsonContent = File.ReadAllText(jsonFilePath); 225 | dynamic jsonObject = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonContent); 226 | 227 | // 如果成功解析,则尝试读取 "Key" 值 228 | if (jsonObject != null && jsonObject["Key"] != null) 229 | { 230 | string key = jsonObject["Key"].ToString(); 231 | 232 | // 如果 "Key" 值为空字符串,则将 info 设置为 "未绑定" 233 | if (string.IsNullOrEmpty(key) || key == "None") 234 | { 235 | info = "未绑定"; 236 | } 237 | else 238 | { 239 | info = key; 240 | } 241 | } 242 | else 243 | { 244 | // 如果没有 "Key" 值,则添加一个空的 "Key" 值并返回 "未绑定" 245 | jsonObject = new Newtonsoft.Json.Linq.JObject(); 246 | jsonObject["Key"] = ""; 247 | File.WriteAllText(jsonFilePath, jsonObject.ToString()); 248 | } 249 | } 250 | else 251 | { 252 | // 如果 JSON 文件不存在,则创建一个并添加一个空的 "Key" 值并返回 "未绑定" 253 | File.WriteAllText(jsonFilePath, "{\"Key\":\"\"}"); 254 | } 255 | } 256 | catch{} 257 | return info; 258 | } 259 | public static void WriteKeyJsonInfo(string jsonFilePath, string valueToWrite) 260 | { 261 | try 262 | { 263 | // 检查 JSON 文件是否存在 264 | if (File.Exists(jsonFilePath)) 265 | { 266 | // 读取 JSON 文件内容 267 | string jsonContent = File.ReadAllText(jsonFilePath); 268 | dynamic jsonObject = JsonConvert.DeserializeObject(jsonContent); 269 | 270 | // 更新 "Key" 值 271 | jsonObject["Key"] = valueToWrite; 272 | 273 | // 将更新后的 JSON 重新写入文件 274 | File.WriteAllText(jsonFilePath, JsonConvert.SerializeObject(jsonObject)); 275 | } 276 | else 277 | { 278 | // 创建一个新的 JSON 对象并设置 "Key" 值 279 | var jsonObject = new JObject(); 280 | jsonObject["Key"] = valueToWrite; 281 | 282 | // 将 JSON 对象写入文件 283 | File.WriteAllText(jsonFilePath, jsonObject.ToString()); 284 | } 285 | } 286 | catch {} 287 | } 288 | 289 | 290 | 291 | 292 | public static void AddPluginFilesToListView(string rootDirectoryPath, ListView listView) 293 | { 294 | // 清空列表视图中的项 295 | listView.Items.Clear(); 296 | 297 | // 获取根目录下的所有文件夹 298 | string[] subDirectories = Directory.GetDirectories(rootDirectoryPath); 299 | foreach (string subDirectory in subDirectories) 300 | { 301 | string directoryName = new DirectoryInfo(subDirectory).Name; 302 | string exeFilePath = Path.Combine(subDirectory, directoryName + ".exe"); 303 | string jsonFilePath = Path.Combine(subDirectory, directoryName + ".json"); 304 | if (File.Exists(exeFilePath) && File.Exists(jsonFilePath)) 305 | { 306 | try 307 | { 308 | string jsonContent = File.ReadAllText(jsonFilePath); 309 | PluginSDK.PluginInfo pluginInfo = JsonConvert.DeserializeObject(jsonContent); 310 | if (pluginInfo != null) 311 | { 312 | ListViewItem item = new ListViewItem(new[] { directoryName, pluginInfo.PluginDescription, pluginInfo.PluginVersion }); 313 | item.Tag = exeFilePath; 314 | listView.Items.Add(item); 315 | } 316 | else 317 | { 318 | ListViewItem item = new ListViewItem(new[] { directoryName, "无描述", "无描述" }); 319 | item.Tag = exeFilePath; 320 | listView.Items.Add(item); 321 | } 322 | } 323 | catch (Exception ex) 324 | { 325 | ListViewItem item = new ListViewItem(new[] { directoryName, "无描述", "无描述" }); 326 | item.Tag = exeFilePath; 327 | listView.Items.Add(item); 328 | } 329 | } 330 | else 331 | { 332 | ListViewItem item = new ListViewItem(new[] { directoryName, "无描述", "无描述" }); 333 | item.Tag = exeFilePath; 334 | listView.Items.Add(item); 335 | } 336 | } 337 | } 338 | 339 | 340 | 341 | 342 | public static string GetKeyDisplay(KeyEventArgs keyEventArgs = null, KeyPressEventArgs keyPressEventArgs = null) 343 | { 344 | // 从 KeyEventArgs 提取 keyCode 345 | if (keyEventArgs != null) 346 | { 347 | Keys keyCode = keyEventArgs.KeyCode; 348 | switch (keyCode) 349 | { 350 | case Keys.Space: 351 | return "SPACE"; 352 | case Keys.Back: 353 | return "BACKSPACE"; 354 | case Keys.Delete: 355 | return "DELETE"; 356 | case Keys.Home: 357 | return "HOME"; 358 | case Keys.End: 359 | return "END"; 360 | case Keys.PageUp: 361 | return "PAGE UP"; 362 | case Keys.PageDown: 363 | return "PAGE DOWN"; 364 | case Keys.Escape: 365 | return "None"; 366 | // 处理 F1-F12 367 | case Keys.F1: 368 | case Keys.F2: 369 | case Keys.F3: 370 | case Keys.F4: 371 | case Keys.F5: 372 | case Keys.F6: 373 | case Keys.F7: 374 | case Keys.F8: 375 | case Keys.F9: 376 | case Keys.F10: 377 | case Keys.F11: 378 | case Keys.F12: 379 | return keyCode.ToString(); 380 | // 处理数字键 381 | case Keys.D0: 382 | case Keys.D1: 383 | case Keys.D2: 384 | case Keys.D3: 385 | case Keys.D4: 386 | case Keys.D5: 387 | case Keys.D6: 388 | case Keys.D7: 389 | case Keys.D8: 390 | case Keys.D9: 391 | return keyCode.ToString().Substring(1); 392 | // 特殊的符号? 393 | case Keys.Oemcomma: 394 | return ","; 395 | case Keys.OemPeriod: 396 | return "."; 397 | case Keys.OemSemicolon: 398 | return ";"; 399 | case Keys.OemQuotes: 400 | return "'"; 401 | case Keys.OemOpenBrackets: 402 | return "["; 403 | case Keys.OemCloseBrackets: 404 | return "]"; 405 | case Keys.OemPipe: 406 | return "\\"; 407 | case Keys.OemMinus: 408 | return "-"; 409 | case Keys.Oemplus: 410 | return "="; 411 | case Keys.Oemtilde: 412 | return "`"; 413 | case Keys.OemQuestion: 414 | return "/"; 415 | case Keys.OemBackslash: 416 | return "\\"; 417 | // 添加小键盘按键 418 | case Keys.NumPad0: 419 | return "NUMPAD 0"; 420 | case Keys.NumPad1: 421 | return "NUMPAD 1"; 422 | case Keys.NumPad2: 423 | return "NUMPAD 2"; 424 | case Keys.NumPad3: 425 | return "NUMPAD 3"; 426 | case Keys.NumPad4: 427 | return "NUMPAD 4"; 428 | case Keys.NumPad5: 429 | return "NUMPAD 5"; 430 | case Keys.NumPad6: 431 | return "NUMPAD 6"; 432 | case Keys.NumPad7: 433 | return "NUMPAD 7"; 434 | case Keys.NumPad8: 435 | return "NUMPAD 8"; 436 | case Keys.NumPad9: 437 | return "NUMPAD 9"; 438 | case Keys.Multiply: 439 | return "NUMPAD *"; 440 | case Keys.Add: 441 | return "NUMPAD +"; 442 | case Keys.Subtract: 443 | return "NUMPAD -"; 444 | case Keys.Decimal: 445 | return "NUMPAD ."; 446 | case Keys.Divide: 447 | return "NUMPAD /"; 448 | case Keys.NumLock: 449 | return "NUM LOCK"; 450 | case Keys.Scroll: 451 | return "SCROLL LOCK"; 452 | // 其他不常用按键 453 | case Keys.Pause: 454 | return "PAUSE/BREAK"; 455 | case Keys.Insert: 456 | return "INSERT"; 457 | case Keys.PrintScreen: 458 | return "PRINT SCREEN"; 459 | case Keys.CapsLock: 460 | return "CAPS LOCK"; 461 | case Keys.LWin: 462 | case Keys.RWin: 463 | return "WINDOWS"; 464 | case Keys.Apps: 465 | return "APPLICATION"; 466 | case Keys.Tab: 467 | return "TAB"; 468 | case Keys.Enter: 469 | return "ENTER"; 470 | case Keys.ShiftKey: 471 | case Keys.LShiftKey: 472 | case Keys.RShiftKey: 473 | return "SHIFT"; 474 | case Keys.ControlKey: 475 | case Keys.LControlKey: 476 | case Keys.RControlKey: 477 | return "CONTROL"; 478 | case Keys.Alt: 479 | case Keys.Menu: 480 | return "ALT"; 481 | } 482 | } 483 | if (keyPressEventArgs != null) 484 | { 485 | char keyChar = keyPressEventArgs.KeyChar; 486 | return keyChar.ToString().ToUpper(); 487 | } 488 | return string.Empty; 489 | } 490 | 491 | 492 | private static WaveInEvent waveIn; 493 | private static WaveOutEvent waveOut; 494 | private static BufferedWaveProvider bufferedWaveProvider; 495 | private static WaveOutEvent currentOutputDeviceEX; 496 | private static AudioFileReader currentAudioFileEX; 497 | public static void StartMicrophoneToSpeaker(int inputDeviceIndex, int outputDeviceIndex) 498 | { 499 | StopMicrophoneToSpeaker(); // 停止之前的流 500 | waveIn = new WaveInEvent 501 | { 502 | DeviceNumber = inputDeviceIndex, 503 | WaveFormat = new WaveFormat(44100, 16, 2) // 设置为44100Hz, 16位, 2声道 504 | }; 505 | waveOut = new WaveOutEvent 506 | { 507 | DeviceNumber = outputDeviceIndex 508 | }; 509 | bufferedWaveProvider = new BufferedWaveProvider(waveIn.WaveFormat) 510 | { 511 | DiscardOnBufferOverflow = true 512 | }; 513 | waveOut.Init(bufferedWaveProvider); 514 | waveIn.DataAvailable += (sender, e) => 515 | { 516 | bufferedWaveProvider.AddSamples(e.Buffer, 0, e.BytesRecorded); 517 | }; 518 | waveIn.StartRecording(); 519 | waveOut.Play(); 520 | } 521 | public static void StopMicrophoneToSpeaker() 522 | { 523 | waveIn?.StopRecording(); 524 | waveIn?.Dispose(); 525 | waveOut?.Stop(); 526 | waveOut?.Dispose(); 527 | bufferedWaveProvider = null; 528 | } 529 | public static int NCMConvert(string filepath, string outputpath = "default") 530 | { 531 | NeteaseCrypt neteaseCrypt = new NeteaseCrypt(filepath); 532 | int result = neteaseCrypt.Dump(); 533 | neteaseCrypt.FixMetadata(); 534 | if (outputpath != "default") 535 | { 536 | string fileSuffix; 537 | if (File.Exists(filepath.Replace("ncm", "flac"))) 538 | { 539 | fileSuffix = "flac"; 540 | } 541 | else 542 | { 543 | fileSuffix = "mp3"; 544 | } 545 | string sourceFileName = filepath.Replace("ncm", fileSuffix); 546 | try 547 | { 548 | File.Copy(sourceFileName, AppDomain.CurrentDomain.BaseDirectory +"AudioData\\"+ Path.GetFileNameWithoutExtension(outputpath+ ".mp3"), true); 549 | } 550 | catch (IOException e) 551 | { 552 | MessageBox.Show($"错误:{e.Message}"); 553 | } 554 | } 555 | return result; 556 | } 557 | 558 | public static async Task GetDownloadJsonFromFile(string filePath, ListView listView, ListBox downloadLinkListBox) 559 | { 560 | try 561 | { 562 | if (!File.Exists(filePath)) 563 | { 564 | MessageBox.Show($"文件不存在: {filePath}"); 565 | return; 566 | } 567 | 568 | string jsonResponse = await File.ReadAllTextAsync(filePath); 569 | JObject json = JObject.Parse(jsonResponse); 570 | 571 | if (json["files"] == null || json["files"].Type != JTokenType.Array) 572 | { 573 | MessageBox.Show("本地 JSON 异常!将尝试重新获取"); 574 | await DownloadJsonFile("https://www.scmd.cc/api/all-audio", filePath); 575 | return; 576 | } 577 | 578 | listView.Items.Clear(); // 先清空 ListView 579 | downloadLinkListBox.Items.Clear(); // 先清空 ListBox 580 | 581 | foreach (var file in json["files"]) 582 | { 583 | string link = file["download-link"]?.ToString(); 584 | if (string.IsNullOrEmpty(link)) 585 | { 586 | continue; 587 | } 588 | 589 | if (!link.Contains("/DATA/")) 590 | { 591 | Console.WriteLine($"链接中未找到 '/DATA/':{link}"); 592 | continue; 593 | } 594 | 595 | // 提取 "/DATA/" 后的文件名 596 | string name = link.Substring(link.IndexOf("/DATA/") + 6); 597 | 598 | // 获取其他信息 599 | string uploader = file["uploader"]?.ToString() ?? "未知"; 600 | string downloadCount = file["download-count"]?.ToString() ?? "0"; 601 | 602 | // 添加到 ListView 603 | ListViewItem item = new ListViewItem(name); // 第一列(名称) 604 | item.SubItems.Add(downloadCount); // 第二列(下载次数) 605 | item.SubItems.Add(uploader); // 第三列(上传者) 606 | 607 | listView.Items.Add(item); 608 | 609 | // ⚡ 同时添加到 ListBox 610 | downloadLinkListBox.Items.Add(link); 611 | } 612 | } 613 | catch (Exception ex) 614 | { 615 | MessageBox.Show($"读取 JSON 文件时出错: {ex.Message}"); 616 | } 617 | } 618 | 619 | 620 | public static async Task GetTotalFromJsonFile(string filePath) 621 | { 622 | try 623 | { 624 | // 检查文件是否存在 625 | if (!File.Exists(filePath)) 626 | { 627 | return null; 628 | } 629 | 630 | // 读取 JSON 文件内容 631 | string jsonResponse = await File.ReadAllTextAsync(filePath); 632 | JObject json = JObject.Parse(jsonResponse); 633 | 634 | // 检查 "total" 是否存在并且是一个整数 635 | if (json["total"] == null || !json["total"].Type.Equals(JTokenType.Integer)) 636 | { 637 | MessageBox.Show("JSON 数据中未找到 'total' 字段或格式错误!"); 638 | return null; 639 | } 640 | 641 | int total = json["total"].Value(); // 提取 total 的值 642 | return total; 643 | } 644 | catch (Exception ex) 645 | { 646 | MessageBox.Show($"读取 JSON 文件时出错: {ex.Message}"); 647 | return null; 648 | } 649 | } 650 | 651 | 652 | public static async Task DownloadJsonFile(string url, string filePath) 653 | { 654 | try 655 | { 656 | using (HttpClient client = new HttpClient()) 657 | { 658 | // 发送 HTTP 请求获取 JSON 数据 659 | HttpResponseMessage response = await client.GetAsync(url); 660 | response.EnsureSuccessStatusCode(); 661 | string jsonResponse = await response.Content.ReadAsStringAsync(); 662 | 663 | // 将 JSON 内容写入文件 664 | await File.WriteAllTextAsync(filePath, jsonResponse); 665 | 666 | Console.WriteLine($"✅ JSON 数据已保存至: {filePath}"); 667 | } 668 | } 669 | catch (Exception ex) 670 | { 671 | Console.WriteLine($"❌ 下载 JSON 时出错: {ex.Message}"); 672 | MessageBox.Show($"下载 JSON 时出错: {ex.Message}"); 673 | } 674 | } 675 | 676 | public static async Task VerifyFileHashAsync(string filePath, string hashUrl) 677 | { 678 | try 679 | { 680 | // 检查文件是否存在 681 | if (!File.Exists(filePath)) 682 | { 683 | Console.WriteLine($"文件未找到: {filePath}"); 684 | return false; 685 | } 686 | 687 | // 计算本地文件的 MD5 哈希值 688 | string localHash; 689 | using (var md5 = MD5.Create()) 690 | { 691 | await using (var stream = File.OpenRead(filePath)) 692 | { 693 | var hashBytes = await md5.ComputeHashAsync(stream); 694 | localHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); 695 | } 696 | } 697 | 698 | // 获取云端哈希值 699 | using (var client = new HttpClient()) 700 | { 701 | var response = await client.GetStringAsync(hashUrl); 702 | var json = JObject.Parse(response); 703 | string cloudHash = json["hash"]?.ToString(); 704 | 705 | if (string.IsNullOrEmpty(cloudHash)) 706 | { 707 | Console.WriteLine("未能从云端获取哈希值。"); 708 | return false; 709 | } 710 | 711 | // 比较哈希值是否一致 712 | return string.Equals(localHash, cloudHash, StringComparison.OrdinalIgnoreCase); 713 | } 714 | } 715 | catch (Exception ex) 716 | { 717 | Console.WriteLine($"验证哈希值时出错: {ex.Message}"); 718 | return false; 719 | } 720 | } 721 | private static string GetInnerText(string input, string startTag, string endTag) 722 | { 723 | int startIndex = input.IndexOf(startTag) + startTag.Length; 724 | int endIndex = input.IndexOf(endTag, startIndex); 725 | return input.Substring(startIndex, endIndex - startIndex); 726 | } 727 | 728 | private static string GetAttribute(string input, string pattern, string attributeName) 729 | { 730 | Regex regex = new Regex(pattern); 731 | Match match = regex.Match(input); 732 | return match.Groups[1].Value; 733 | } 734 | public static bool IsAdministrator() 735 | { 736 | WindowsIdentity identity = WindowsIdentity.GetCurrent(); 737 | WindowsPrincipal principal = new WindowsPrincipal(identity); 738 | return principal.IsInRole(WindowsBuiltInRole.Administrator); 739 | } 740 | public static void ButtonStabilization(int time,Button button) 741 | { 742 | var ori = button.Text; 743 | button.Enabled = false; 744 | for (int i = time; i != 0; i--) { button.Text = i.ToString(); Misc.Delay(1000); } 745 | button.Enabled = true; 746 | button.Text = ori; 747 | } 748 | public static async Task FadeIn(int durationMilliseconds, Form form) 749 | { 750 | form.Visible = false; 751 | form.Opacity = 0; 752 | form.Show(); 753 | 754 | for (double opacity = 0; opacity <= 1; opacity += 0.05) 755 | { 756 | form.Opacity = opacity; 757 | await Task.Delay(durationMilliseconds / 20); 758 | } 759 | 760 | form.Opacity = 1; 761 | } 762 | 763 | public static async Task FadeOut(int durationMilliseconds, Form form) 764 | { 765 | for (double opacity = 1; opacity >= 0; opacity -= 0.05) 766 | { 767 | form.Opacity = opacity; 768 | await Task.Delay(durationMilliseconds / 20); 769 | } 770 | form.Visible = false; 771 | form.Dispose(); 772 | } 773 | public void ApplyResourcesToControls(Control.ControlCollection controls, string baseName, Assembly assembly) 774 | { 775 | ResourceManager rm = new ResourceManager(baseName, assembly); 776 | foreach (Control control in controls) 777 | { 778 | if (control.HasChildren) 779 | { 780 | ApplyResourcesToControls(control.Controls, baseName, assembly); 781 | } 782 | string key = $"{control.Name}ResxText"; 783 | string resourceValue = rm.GetString(key); 784 | if (!string.IsNullOrEmpty(resourceValue)) 785 | { 786 | control.Text = resourceValue; 787 | } 788 | } 789 | } 790 | public static void BuildLocalizationBaseFiles(Control.ControlCollection controls, string filePath) 791 | { 792 | //该函数用于生成标准本地化文件 基文件为简体中文 793 | StringBuilder resxContent = new StringBuilder(); 794 | // 添加.resx文件必要的头部 795 | resxContent.AppendLine(""); 796 | resxContent.AppendLine(""); 797 | resxContent.AppendLine(" "); 798 | resxContent.AppendLine(" "); 799 | resxContent.AppendLine(" text/microsoft-resx"); 800 | resxContent.AppendLine(" "); 801 | resxContent.AppendLine(" "); 802 | resxContent.AppendLine(" 2.0"); 803 | resxContent.AppendLine(" "); 804 | resxContent.AppendLine(" "); 805 | resxContent.AppendLine(" System.Resources.ResXResourceReader, System.Windows.Forms, ..."); 806 | resxContent.AppendLine(" "); 807 | resxContent.AppendLine(" "); 808 | resxContent.AppendLine(" System.Resources.ResXResourceWriter, System.Windows.Forms, ..."); 809 | resxContent.AppendLine(" "); 810 | // 为控件生成元素 811 | resxContent.Append(BuildControlToXMLDataValue(controls)); 812 | // 添加文件尾部 813 | resxContent.AppendLine(""); 814 | // 保存到文件 815 | File.WriteAllText(filePath, resxContent.ToString()); 816 | } 817 | private static string BuildControlToXMLDataValue(Control.ControlCollection controls) 818 | { 819 | StringBuilder resxEntries = new StringBuilder(); 820 | foreach (Control control in controls) 821 | { 822 | if (control.HasChildren) 823 | { 824 | resxEntries.Append(BuildControlToXMLDataValue(control.Controls)); 825 | } 826 | if (!string.IsNullOrEmpty(control.Name) && !string.IsNullOrEmpty(control.Text)) 827 | { 828 | string escapedControlText = control.Text 829 | .Replace("<", "<") 830 | .Replace(">", ">"); 831 | resxEntries.AppendLine($" "); 832 | resxEntries.AppendLine($" {escapedControlText}"); 833 | resxEntries.AppendLine($" "); 834 | } 835 | } 836 | return resxEntries.ToString(); 837 | } 838 | 839 | 840 | private const string RepoOwner = "TheD0ubleC"; // GitHub用户名 841 | private const string RepoName = "MusicalMoments"; // GitHub仓库名 842 | 843 | public static async Task GetLatestVersionAsync() 844 | { 845 | HttpResponseMessage response = null; 846 | string latestVersion = string.Empty; 847 | 848 | string releasesUrl = $"https://api.kkgithub.com/repos/{RepoOwner}/{RepoName}/releases/latest"; 849 | 850 | using (HttpClient client = new HttpClient()) 851 | { 852 | client.DefaultRequestHeaders.Add("User-Agent", "C# App"); 853 | try { response = await client.GetAsync(releasesUrl);}catch (Exception ex) { return MainWindow.nowVer; } 854 | 855 | if (response.IsSuccessStatusCode) 856 | { 857 | string json = await response.Content.ReadAsStringAsync(); 858 | latestVersion = ParseLatestVersion(json); 859 | } 860 | } 861 | 862 | return latestVersion; 863 | } 864 | public static async Task GetLatestVersionTipsAsync() 865 | { 866 | HttpResponseMessage response = null; 867 | string latestVersion = string.Empty; 868 | 869 | string releasesUrl = $"https://api.kkgithub.com/repos/{RepoOwner}/{RepoName}/releases/latest"; 870 | 871 | using (HttpClient client = new HttpClient()) 872 | { 873 | client.DefaultRequestHeaders.Add("User-Agent", "C# App"); 874 | try { response = await client.GetAsync(releasesUrl); } catch (Exception ex) { return MainWindow.nowVer; } 875 | 876 | if (response.IsSuccessStatusCode) 877 | { 878 | string json = await response.Content.ReadAsStringAsync(); 879 | latestVersion = ParseLatestVersionTips(json); 880 | } 881 | } 882 | 883 | return latestVersion; 884 | } 885 | 886 | private static string ParseLatestVersion(string json) 887 | { 888 | using (JsonDocument doc = JsonDocument.Parse(json)) 889 | { 890 | JsonElement root = doc.RootElement; 891 | 892 | if (root.TryGetProperty("tag_name", out JsonElement tagElement)) 893 | { 894 | return tagElement.GetString(); 895 | } 896 | } 897 | 898 | return string.Empty; 899 | } 900 | 901 | private static string ParseLatestVersionTips(string json) 902 | { 903 | using (JsonDocument doc = JsonDocument.Parse(json)) 904 | { 905 | JsonElement root = doc.RootElement; 906 | 907 | if (root.TryGetProperty("body", out JsonElement tagElement)) 908 | { 909 | return tagElement.GetString(); 910 | } 911 | } 912 | 913 | return string.Empty; 914 | } 915 | 916 | public static string DisplayAudioProperties(string audioFilePath) 917 | { 918 | try 919 | { 920 | using (var audioFile = new AudioFileReader(audioFilePath)) 921 | { 922 | string frequency = audioFile.WaveFormat.SampleRate.ToString(); 923 | string bits = (audioFile.WaveFormat.BitsPerSample / 8).ToString(); // 位数 924 | string channels = audioFile.WaveFormat.Channels.ToString(); // 声道 925 | string format = Path.GetExtension(audioFilePath).TrimStart('.'); // 格式 926 | 927 | // 将音频属性显示在标签上 928 | return $"格式: {format.ToUpper()}, 频率: {frequency} Hz, 位数: {bits} bit, 声道: {channels}"; 929 | } 930 | } 931 | catch 932 | { 933 | return "无法读取音频属性"; 934 | } 935 | } 936 | 937 | private static readonly HttpClient client = new HttpClient(); 938 | 939 | public static async Task APIStartup() 940 | { 941 | try 942 | { 943 | // 生成一个唯一标识符(GUID) 944 | string clientId = Guid.NewGuid().ToString(); 945 | 946 | // 添加唯一标识符到请求头 947 | client.DefaultRequestHeaders.Add("X-Client-ID", clientId); 948 | 949 | string url = "https://api.scmd.cc/MM.php?action=startup"; 950 | HttpResponseMessage response = await client.GetAsync(url); 951 | response.EnsureSuccessStatusCode(); // 确保响应成功 952 | 953 | string responseBody = await response.Content.ReadAsStringAsync(); 954 | Console.WriteLine(responseBody); // 输出响应内容 955 | } 956 | catch (HttpRequestException e) 957 | { 958 | Console.WriteLine($"HTTP 请求错误: {e.Message}"); 959 | } 960 | } 961 | 962 | public static async Task APIShutdown() 963 | { 964 | try 965 | { 966 | string url = "https://api.scmd.cc/MM.php?action=shutdown"; 967 | HttpResponseMessage response = await client.GetAsync(url); 968 | response.EnsureSuccessStatusCode(); // 确保响应成功 969 | 970 | string responseBody = await response.Content.ReadAsStringAsync(); 971 | Console.WriteLine(responseBody); // 输出响应内容 972 | } 973 | catch (HttpRequestException e) 974 | { 975 | Console.WriteLine($"HTTP 请求错误: {e.Message}"); 976 | } 977 | } 978 | 979 | public static int CalculateDaysBetweenDates(string dateStr1, string dateStr2) 980 | { 981 | DateTime date1, date2; 982 | string[] formats = { 983 | "yyyy年MM月dd日HH时mm分ss秒", 984 | "yyyy年MM月dd日HH时mm分", 985 | "yyyy年MM月dd日HH时", 986 | "yyyy年MM月dd日", 987 | "yyyy年MM月dd", 988 | "yyyy年MM月", 989 | "yyyy年MM", 990 | "yyyy年" 991 | }; 992 | dateStr1 = dateStr1.Replace(" ", ""); 993 | dateStr2 = dateStr2.Replace(" ", ""); 994 | if (!DateTime.TryParseExact(dateStr1, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out date1)) 995 | { 996 | throw new ArgumentException("Invalid date format for the first date."); 997 | } 998 | if (!DateTime.TryParseExact(dateStr2, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out date2)) 999 | { 1000 | throw new ArgumentException("Invalid date format for the second date."); 1001 | } 1002 | return Math.Abs((date2 - date1).Days); 1003 | } 1004 | } 1005 | } 1006 | -------------------------------------------------------------------------------- /MusicalMoments/MusicalMoments - Backup.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net6.0-windows7.0 5 | enable 6 | True 7 | enable 8 | image\MMLOGO.ico 9 | MusicalMoments 10 | 1.1.0 11 | 1.1.0 12 | MusicalMoments.Program 13 | False 14 | 15 | 16 | portable 17 | 18 | 19 | portable 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | True 35 | True 36 | file.resx 37 | 38 | 39 | True 40 | True 41 | Resources.resx 42 | 43 | 44 | True 45 | True 46 | Settings.settings 47 | 48 | 49 | True 50 | True 51 | Resource1.resx 52 | 53 | 54 | 55 | 56 | PublicResXFileCodeGenerator 57 | file.Designer.cs 58 | 59 | 60 | ResXFileCodeGenerator 61 | Resources.Designer.cs 62 | 63 | 64 | ResXFileCodeGenerator 65 | Resource1.Designer.cs 66 | 67 | 68 | 69 | 70 | SettingsSingleFileGenerator 71 | Settings.Designer.cs 72 | 73 | 74 | -------------------------------------------------------------------------------- /MusicalMoments/MusicalMoments.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net8.0-windows10.0.22000.0 5 | enable 6 | True 7 | enable 8 | image\MMLOGO.ico 9 | MusicalMoments 10 | 1.4.1 11 | 1.4.1 12 | MusicalMoments.Program 13 | False 14 | AnyCPU;x64 15 | 16 | 17 | portable 18 | 19 | 20 | portable 21 | 22 | 23 | portable 24 | 25 | 26 | portable 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | True 45 | True 46 | Resources.resx 47 | 48 | 49 | True 50 | True 51 | Settings.settings 52 | 53 | 54 | 55 | 56 | PublicResXFileCodeGenerator 57 | file.Designer.cs 58 | 59 | 60 | ResXFileCodeGenerator 61 | Resources.Designer.cs 62 | 63 | 64 | ResXFileCodeGenerator 65 | Resource1.Designer.cs 66 | 67 | 68 | 69 | 70 | Always 71 | 72 | 73 | Always 74 | 75 | 76 | Always 77 | 78 | 79 | SettingsSingleFileGenerator 80 | Settings.Designer.cs 81 | 82 | 83 | Always 84 | 85 | 86 | Always 87 | 88 | 89 | Always 90 | 91 | 92 | -------------------------------------------------------------------------------- /MusicalMoments/MusicalMoments.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | <_LastSelectedProfileId>K:\Project\C#\MusicalMoments\MusicalMoments\Properties\PublishProfiles\FolderProfile.pubxml 5 | 6 | 7 | 8 | Form 9 | 10 | 11 | Form 12 | 13 | 14 | Form 15 | 16 | 17 | -------------------------------------------------------------------------------- /MusicalMoments/NeteaseCrypt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | namespace MusicalMoments 4 | { 5 | public class NeteaseCrypt : IDisposable 6 | { 7 | const string DLL_PATH = $"taurusxin.LibNcmDump.dll"; 8 | [DllImport(DLL_PATH)] 9 | private static extern IntPtr CreateNeteaseCrypt(string path); 10 | [DllImport(DLL_PATH)] 11 | private static extern int Dump(IntPtr NeteaseCrypt); 12 | [DllImport(DLL_PATH)] 13 | private static extern void FixMetadata(IntPtr NeteaseCrypt); 14 | [DllImport(DLL_PATH)] 15 | private static extern void DestroyNeteaseCrypt(IntPtr NeteaseCrypt); 16 | private IntPtr NeteaseCryptClass = IntPtr.Zero; 17 | /// 18 | /// 创建 NeteaseCrypt 类的实例。 19 | /// 20 | /// 网易云音乐 ncm 加密文件路径 21 | public NeteaseCrypt(string FileName) 22 | { 23 | try{ NeteaseCryptClass = CreateNeteaseCrypt(FileName); } 24 | catch(Exception ex) { MessageBox.Show($"未在运行目录找到\"taurusxin.LibNcmDump.dll\"文件 这是ncm格式转为mp3的重要文件 请从压缩包内完整解压至同一目录 \r\n\r\n详情错误信息:\r\n{ex}","错误"); return; } 25 | } 26 | /// 27 | /// 启动转换过程。 28 | /// 29 | /// 返回一个整数,指示转储过程的结果。如果成功,返回0;如果失败,返回1。 30 | public int Dump() 31 | { 32 | try { return Dump(NeteaseCryptClass); } 33 | catch { return 1; } 34 | } 35 | /// 36 | /// 修复音乐文件元数据。 37 | /// 38 | public void FixMetadata() 39 | { 40 | try { FixMetadata(NeteaseCryptClass); } 41 | catch { return; } 42 | } 43 | // 实现 IDisposable 接口,释放资源 44 | public void Dispose() 45 | { 46 | Dispose(true); 47 | GC.SuppressFinalize(this); 48 | } 49 | protected virtual void Dispose(bool disposing) 50 | { 51 | if (NeteaseCryptClass != IntPtr.Zero) 52 | { 53 | DestroyNeteaseCrypt(NeteaseCryptClass); 54 | NeteaseCryptClass = IntPtr.Zero; 55 | } 56 | } 57 | ~NeteaseCrypt() 58 | { 59 | Dispose(false); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /MusicalMoments/Plugin/MMPluginSDKExample/MMPluginSDKExample.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/Plugin/MMPluginSDKExample/MMPluginSDKExample.exe -------------------------------------------------------------------------------- /MusicalMoments/Plugin/按键帮助/按键帮助.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/Plugin/按键帮助/按键帮助.exe -------------------------------------------------------------------------------- /MusicalMoments/Plugin/按键帮助/按键帮助.json: -------------------------------------------------------------------------------- 1 | { 2 | "PluginDescription": "播放音频时帮助按下游戏开麦键", 3 | "PluginVersion": "1.2" 4 | } -------------------------------------------------------------------------------- /MusicalMoments/PluginSDK.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Xml.Serialization; 8 | 9 | namespace MusicalMoments 10 | { 11 | public class PluginSDK 12 | { 13 | public class PluginInfo 14 | { 15 | public string PluginDescription { get; set; } 16 | public string PluginVersion { get; set; } 17 | } 18 | public class PluginServer 19 | { 20 | private static TcpListener listener; 21 | private static int port; 22 | private static CancellationTokenSource cancellationTokenSource; 23 | private static Task listenerTask; 24 | 25 | public static void StartServer() 26 | { 27 | if (listener == null || !listener.Server.IsBound) 28 | { 29 | port = GetAvailablePort(); 30 | listener = new TcpListener(IPAddress.Any, port); 31 | listener.Start(); 32 | cancellationTokenSource = new CancellationTokenSource(); 33 | listenerTask = Task.Run(() => Listen(cancellationTokenSource.Token)); 34 | } 35 | } 36 | 37 | public static void StopServer() 38 | { 39 | if (listener != null && listener.Server.IsBound) 40 | { 41 | cancellationTokenSource.Cancel(); 42 | listener.Stop(); 43 | listenerTask.Wait(); // 等待任务完成 44 | } 45 | } 46 | 47 | private static void Listen(CancellationToken cancellationToken) 48 | { 49 | while (!cancellationToken.IsCancellationRequested) 50 | { 51 | try 52 | { 53 | TcpClient client = listener.AcceptTcpClient(); 54 | Thread clientThread = new Thread(() => HandleClient(client)); 55 | clientThread.Start(); 56 | } 57 | catch (SocketException ex) 58 | { 59 | // 如果捕获到异常,则检查是否是由于取消操作而引起的 60 | if (ex.SocketErrorCode != SocketError.Interrupted) 61 | { 62 | throw; 63 | } 64 | } 65 | } 66 | } 67 | 68 | private static void HandleClient(TcpClient client) 69 | { 70 | // 获取客户端请求数据 71 | NetworkStream stream = client.GetStream(); 72 | byte[] buffer = new byte[1024]; 73 | int bytesRead = stream.Read(buffer, 0, buffer.Length); 74 | string request = Encoding.ASCII.GetString(buffer, 0, bytesRead); 75 | Console.WriteLine("收到请求:\n" + request); 76 | 77 | // 解析请求 78 | string[] requestLines = request.Split('\n'); 79 | string[] requestParts = requestLines[0].Split(' '); 80 | string method = requestParts[0]; 81 | string endpoint = requestParts[1]; 82 | 83 | // 参数请求 84 | string[] endpointParts = endpoint.Split('?'); 85 | string endpointWithoutParams = endpointParts[0]; 86 | string[] queryParams = null; 87 | if (endpointParts.Length > 1) 88 | { 89 | queryParams = endpointParts[1].Split('&'); 90 | } 91 | 92 | // 处理GET请求 93 | if (method == "GET") 94 | { 95 | // 模拟一些API端点 96 | if (endpoint == "/api/SDKVer") 97 | { 98 | // 返回一些示例数据 99 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + "V1.0.0"; 100 | byte[] responseData = Encoding.ASCII.GetBytes(response); 101 | stream.Write(responseData, 0, responseData.Length); 102 | } 103 | else if (endpoint == "/api/isPlaying") // 是否在播放中 104 | { 105 | // 返回 isPlaying 变量的值 106 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + (Misc.currentOutputDevice != null ? "true" : "false"); 107 | byte[] responseData = Encoding.ASCII.GetBytes(response); 108 | stream.Write(responseData, 0, responseData.Length); 109 | } 110 | else if (endpoint == "/api/playAudio") // 是否正在使用音频源 111 | { 112 | // 返回 playAudio 变量的值 113 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + (MainWindow.playAudio ? "true" : "false"); 114 | byte[] responseData = Encoding.ASCII.GetBytes(response); 115 | stream.Write(responseData, 0, responseData.Length); 116 | } 117 | else if (endpoint == "/api/nowVer") // 当前版本 118 | { 119 | // 返回 nowVer 变量的值 120 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + MainWindow.nowVer; 121 | byte[] responseData = Encoding.ASCII.GetBytes(response); 122 | stream.Write(responseData, 0, responseData.Length); 123 | } 124 | else if (endpoint == "/api/runningDirectory") // 程序运行目录 125 | { 126 | // 返回 runningDirectory 变量的值 127 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + MainWindow.runningDirectory; 128 | byte[] responseData = Encoding.ASCII.GetBytes(response); 129 | stream.Write(responseData, 0, responseData.Length); 130 | } 131 | else if (endpoint == "/api/playAudioKey") // 播放音频按键 132 | { 133 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + MainWindow.playAudioKey.ToString(); 134 | byte[] responseData = Encoding.ASCII.GetBytes(response); 135 | stream.Write(responseData, 0, responseData.Length); 136 | } 137 | else if (endpoint == "/api/toggleStreamKey") // 切换源按键 138 | { 139 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + MainWindow.toggleStreamKey.ToString(); 140 | byte[] responseData = Encoding.ASCII.GetBytes(response); 141 | stream.Write(responseData, 0, responseData.Length); 142 | } 143 | else if (endpoint == "/api/VBvolume") // 切换源按键 144 | { 145 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + (MainWindow.VBvolume * 100f).ToString(); 146 | byte[] responseData = Encoding.ASCII.GetBytes(response); 147 | stream.Write(responseData, 0, responseData.Length); 148 | } 149 | else if (endpoint == "/api/isVBinstalled") 150 | { 151 | // 调用Misc.checkVB()并返回结果 152 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + (Misc.checkVB() ? "true" : "false"); 153 | byte[] responseData = Encoding.ASCII.GetBytes(response); 154 | stream.Write(responseData, 0, responseData.Length); 155 | } 156 | else if (endpoint == "/api/volume") // 切换源按键 157 | { 158 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + (MainWindow.volume * 100f).ToString(); 159 | byte[] responseData = Encoding.ASCII.GetBytes(response); 160 | stream.Write(responseData, 0, responseData.Length); 161 | } 162 | else if (endpoint == "/api/tipsvolume") // 切换源按键 163 | { 164 | string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" + (MainWindow.tipsvolume * 100f).ToString(); 165 | byte[] responseData = Encoding.ASCII.GetBytes(response); 166 | stream.Write(responseData, 0, responseData.Length); 167 | } 168 | else if (endpointWithoutParams == "/api/listViewInfo") 169 | { 170 | string result = ""; 171 | string contentType = "text/plain; charset=utf-8"; 172 | if (queryParams != null) 173 | { 174 | foreach (string param in queryParams) 175 | { 176 | string[] parts = param.Split('='); 177 | if (parts.Length == 2 && parts[0] == "type") 178 | { 179 | switch (parts[1].ToLower()) 180 | { 181 | case "json": 182 | result = JsonConvert.SerializeObject(MainWindow.audioInfo, Formatting.Indented); 183 | contentType = "application/json; charset=utf-8"; 184 | break; 185 | case "xml": 186 | using (var stringWriter = new StringWriter()) 187 | { 188 | var serializer = new XmlSerializer(typeof(List)); 189 | serializer.Serialize(stringWriter, MainWindow.audioInfo); 190 | result = stringWriter.ToString(); 191 | } 192 | contentType = "application/xml; charset=utf-8"; 193 | break; 194 | case "text": 195 | result = string.Join("\n", MainWindow.audioInfo.Select(a => $"Name: {a.Name}, FilePath: {a.FilePath}, Key: {a.Key}")); 196 | contentType = "text/plain; charset=utf-8"; 197 | break; 198 | default: 199 | result = "Unsupported type parameter"; 200 | contentType = "text/plain; charset=utf-8"; 201 | break; 202 | } 203 | } 204 | } 205 | } 206 | 207 | string response = $"HTTP/1.1 200 OK\r\nContent-Type: {contentType}\r\n\r\n" + result; 208 | byte[] responseData = Encoding.UTF8.GetBytes(response); 209 | stream.Write(responseData, 0, responseData.Length); 210 | } 211 | else if (endpointWithoutParams == "/api/playAudio") 212 | { 213 | string result = "false"; // 默认为false,表示未找到或未播放 214 | string contentType = "text/plain; charset=utf-8"; 215 | string audioPath = ""; 216 | 217 | if (queryParams != null) 218 | { 219 | foreach (string param in queryParams) 220 | { 221 | string[] parts = param.Split('='); 222 | if (parts.Length == 2 && parts[0] == "name") 223 | { 224 | // 使用System.Net.WebUtility.UrlDecode来解码参数值 225 | string requestedName = WebUtility.UrlDecode(parts[1]); // 解码音频名称 226 | 227 | // 使用LINQ查找对应的AudioInfo对象 228 | var audioInfo = MainWindow.audioInfo.FirstOrDefault(ai => ai.Name == requestedName); 229 | if (audioInfo != null) 230 | { 231 | audioPath = audioInfo.FilePath; // 获取对应的FilePath 232 | MainWindow.playedCount += 1; 233 | 234 | if (MainWindow.playAudio) 235 | { 236 | if (File.Exists(audioPath)) 237 | { 238 | int outputDeviceID = Misc.GetOutputDeviceID(MainWindow.VBOutputComboSelect.ToString()); 239 | int alternateOutputDeviceID = Misc.GetOutputDeviceID(MainWindow.OutputComboSelect.ToString()); 240 | 241 | // 进行播放 242 | Misc.PlayAudioToSpecificDevice(audioPath, outputDeviceID, true, MainWindow.VBvolume, MainWindow.AudioEquipmentPlayCheck, audioPath, alternateOutputDeviceID, MainWindow.volume); 243 | MainWindow.isPlaying = !MainWindow.isPlaying; // 切换播放状态 244 | result = "true"; // 设置结果为true,表示找到并且播放了 245 | } 246 | } 247 | } 248 | } 249 | } 250 | } 251 | string response = $"HTTP/1.1 200 OK\r\nContent-Type: {contentType}\r\n\r\n" + result; 252 | byte[] responseData = Encoding.UTF8.GetBytes(response); 253 | stream.Write(responseData, 0, responseData.Length); 254 | } 255 | } 256 | client.Close(); 257 | } 258 | private static int GetAvailablePort() 259 | { 260 | TcpListener tempListener = new TcpListener(IPAddress.Any, 0); 261 | tempListener.Start(); 262 | int port = ((IPEndPoint)tempListener.LocalEndpoint).Port; 263 | tempListener.Stop(); 264 | return port; 265 | } 266 | 267 | public static string GetServerAddress() 268 | { 269 | return $"http://localhost:{port}/"; 270 | } 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /MusicalMoments/Program.cs: -------------------------------------------------------------------------------- 1 | namespace MusicalMoments 2 | { 3 | internal static class Program 4 | { 5 | static bool isAppRunning = false; 6 | /// 7 | /// The main entry point for the application. 8 | /// 9 | [STAThread] 10 | 11 | static void Main() 12 | { 13 | Mutex mutex = new Mutex(true, System.Diagnostics.Process.GetCurrentProcess().ProcessName, out isAppRunning); 14 | if (!isAppRunning) 15 | { 16 | Environment.Exit(1); 17 | } 18 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; 19 | Application.ThreadException += Application_ThreadException; 20 | Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); 21 | ApplicationConfiguration.Initialize(); 22 | Application.Run(new MainWindow()); 23 | 24 | 25 | } 26 | private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 27 | { 28 | LogException((Exception)e.ExceptionObject); 29 | } 30 | private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) 31 | { 32 | LogException(e.Exception); 33 | } 34 | private static void LogException(Exception ex) 35 | { 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /MusicalMoments/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net6.0\publish\win-x64\ 10 | FileSystem 11 | <_TargetId>Folder 12 | net6.0-windows7.0 13 | win-x64 14 | false 15 | true 16 | false 17 | 18 | -------------------------------------------------------------------------------- /MusicalMoments/Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | True|2025-01-29T06:47:42.2257391Z||;True|2024-08-02T02:29:32.0677355+08:00||;True|2024-08-02T02:26:40.1336609+08:00||;False|2024-04-17T19:59:19.6784669+08:00||;True|2024-04-17T00:04:38.8191752+08:00||;True|2024-04-17T00:03:33.6952457+08:00||;True|2024-04-16T00:10:08.7699212+08:00||;True|2024-04-14T20:21:37.7415611+08:00||;True|2024-04-14T00:27:29.9264453+08:00||;False|2024-04-14T00:27:23.1081231+08:00||;True|2024-04-14T00:26:51.4126012+08:00||;True|2024-04-11T19:10:26.7102690+08:00||;True|2024-04-11T19:07:16.0634912+08:00||;True|2024-03-31T17:14:34.5942107+08:00||;True|2024-03-31T17:14:09.8221859+08:00||;True|2024-03-31T16:59:02.9472317+08:00||;True|2024-03-27T15:46:03.4719140+08:00||;True|2024-03-26T23:00:27.8181528+08:00||;True|2024-03-26T22:58:16.4516739+08:00||;True|2024-03-24T02:25:36.9130954+08:00||;True|2024-03-22T22:44:58.0478434+08:00||;True|2024-03-21T23:21:17.7438531+08:00||;True|2024-03-21T23:19:07.4537139+08:00||;True|2024-03-21T23:19:04.1686757+08:00||;True|2024-03-21T20:18:40.3733591+08:00||;True|2024-03-21T00:09:20.8830395+08:00||;True|2024-03-21T00:08:28.7287884+08:00||;True|2024-03-21T00:08:04.5836531+08:00||;True|2024-03-20T23:55:38.2704330+08:00||;True|2024-03-09T20:22:40.3467568+08:00||;True|2024-03-02T17:03:12.6279752+08:00||;True|2024-02-28T18:59:51.1748043+08:00||;True|2024-02-28T18:55:29.6362956+08:00||;True|2024-02-28T18:14:49.6337208+08:00||;False|2024-02-28T18:14:36.9677424+08:00||;True|2024-02-28T18:13:47.7425932+08:00||; 8 | 9 | 10 | -------------------------------------------------------------------------------- /MusicalMoments/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | namespace MusicalMoments.Properties { 11 | using System; 12 | /// 13 | /// 一个强类型的资源类,用于查找本地化的字符串等。 14 | /// 15 | // 此类是由 StronglyTypedResourceBuilder 16 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 17 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen 18 | // (以 /str 作为命令选项),或重新生成 VS 项目。 19 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 20 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 21 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 22 | internal class Resources { 23 | private static global::System.Resources.ResourceManager resourceMan; 24 | private static global::System.Globalization.CultureInfo resourceCulture; 25 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 26 | internal Resources() { 27 | } 28 | /// 29 | /// 返回此类使用的缓存的 ResourceManager 实例。 30 | /// 31 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 32 | internal static global::System.Resources.ResourceManager ResourceManager { 33 | get { 34 | if (object.ReferenceEquals(resourceMan, null)) { 35 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MusicalMoments.Properties.Resources", typeof(Resources).Assembly); 36 | resourceMan = temp; 37 | } 38 | return resourceMan; 39 | } 40 | } 41 | /// 42 | /// 重写当前线程的 CurrentUICulture 属性,对 43 | /// 使用此强类型资源类的所有资源查找执行重写。 44 | /// 45 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 46 | internal static global::System.Globalization.CultureInfo Culture { 47 | get { 48 | return resourceCulture; 49 | } 50 | set { 51 | resourceCulture = value; 52 | } 53 | } 54 | /// 55 | /// 查找 System.Drawing.Bitmap 类型的本地化资源。 56 | /// 57 | internal static System.Drawing.Bitmap MMLOGO { 58 | get { 59 | object obj = ResourceManager.GetObject("MMLOGO", resourceCulture); 60 | return ((System.Drawing.Bitmap)(obj)); 61 | } 62 | } 63 | /// 64 | /// 查找 System.Drawing.Bitmap 类型的本地化资源。 65 | /// 66 | internal static System.Drawing.Bitmap 微信 { 67 | get { 68 | object obj = ResourceManager.GetObject("微信", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | /// 73 | /// 查找 System.Drawing.Bitmap 类型的本地化资源。 74 | /// 75 | internal static System.Drawing.Bitmap 支付宝 { 76 | get { 77 | object obj = ResourceManager.GetObject("支付宝", resourceCulture); 78 | return ((System.Drawing.Bitmap)(obj)); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /MusicalMoments/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | text/microsoft-resx 99 | 100 | 101 | 2.0 102 | 103 | 104 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 105 | 106 | 107 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 108 | 109 | 110 | 111 | ..\image\微信.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 112 | 113 | 114 | ..\image\支付宝.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 115 | 116 | 117 | ..\image\MMLOGO.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 118 | 119 | -------------------------------------------------------------------------------- /MusicalMoments/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | namespace MusicalMoments.Properties { 11 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 12 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] 13 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 14 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 15 | public static Settings Default { 16 | get { 17 | return defaultInstance; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MusicalMoments/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /MusicalMoments/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "MusicalMoments": { 4 | "commandName": "Project", 5 | "workingDirectory": "K:\\Project\\C#\\MusicalMoments\\MusicalMoments\\bin\\Debug", 6 | "remoteDebugEnabled": false, 7 | "remoteDebugMachine": "192.168.5.2:4026" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /MusicalMoments/ResourceFiles/切换为音频.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/ResourceFiles/切换为音频.wav -------------------------------------------------------------------------------- /MusicalMoments/ResourceFiles/切换为麦克风.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/ResourceFiles/切换为麦克风.wav -------------------------------------------------------------------------------- /MusicalMoments/Updater.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/Updater.exe -------------------------------------------------------------------------------- /MusicalMoments/UserSettings.cs: -------------------------------------------------------------------------------- 1 | public class UserSettings 2 | { 3 | public int VBAudioEquipmentInputIndex { get; set; } 4 | public int AudioEquipmentInputIndex { get; set; } 5 | public int VBAudioEquipmentOutputIndex { get; set; } 6 | public int AudioEquipmentOutputIndex { get; set; } 7 | public string ToggleStreamKey { get; set; } 8 | public string PlayAudioKey { get; set; } 9 | public bool AudioEquipmentPlay { get; set; } 10 | public bool SwitchStreamTips { get; set; } 11 | public float VBVolume { get; set; } 12 | public float Volume { get; set; } 13 | public float TipsVolume { get; set; } 14 | public int CloseCount { get; set; } 15 | public int PlayedCount { get; set; } 16 | public int ChangedCount { get; set; } 17 | public string FirstStart { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /MusicalMoments/image/MMLOGO.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/MMLOGO.ico -------------------------------------------------------------------------------- /MusicalMoments/image/MMLOGO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/MMLOGO.png -------------------------------------------------------------------------------- /MusicalMoments/image/一般提示.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/一般提示.png -------------------------------------------------------------------------------- /MusicalMoments/image/主页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/主页.png -------------------------------------------------------------------------------- /MusicalMoments/image/关于.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/关于.png -------------------------------------------------------------------------------- /MusicalMoments/image/反馈.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/反馈.png -------------------------------------------------------------------------------- /MusicalMoments/image/发现.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/发现.png -------------------------------------------------------------------------------- /MusicalMoments/image/声音.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/声音.png -------------------------------------------------------------------------------- /MusicalMoments/image/微信.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/微信.png -------------------------------------------------------------------------------- /MusicalMoments/image/插件.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/插件.png -------------------------------------------------------------------------------- /MusicalMoments/image/支付宝.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/支付宝.png -------------------------------------------------------------------------------- /MusicalMoments/image/状态.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/状态.png -------------------------------------------------------------------------------- /MusicalMoments/image/编辑.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/编辑.png -------------------------------------------------------------------------------- /MusicalMoments/image/设置.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/设置.png -------------------------------------------------------------------------------- /MusicalMoments/image/赞.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/赞.png -------------------------------------------------------------------------------- /MusicalMoments/image/赞助.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/赞助.png -------------------------------------------------------------------------------- /MusicalMoments/image/转换.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/转换.png -------------------------------------------------------------------------------- /MusicalMoments/image/音频.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/音频.png -------------------------------------------------------------------------------- /MusicalMoments/image/首页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/image/首页.png -------------------------------------------------------------------------------- /MusicalMoments/taurusxin.LibNcmDump.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/taurusxin.LibNcmDump.dll -------------------------------------------------------------------------------- /MusicalMoments/奶酪陷阱体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheD0ubleC/MusicalMoments/ec6c66460bc53d683e92d6efbf3ee56463ce574c/MusicalMoments/奶酪陷阱体.ttf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Musical Moments - 音乐时刻

4 | 5 | 6 | 7 | 8 | 9 | ## 省流 10 | Musical Moments (MM) 的主要目的是提供一个全面的实时音频解决方案,旨在对标如SoundPad等在游戏、直播和语音领域广泛使用的音效/音频播放工具。 11 | 12 | ### [视频版教程点我](https://www.bilibili.com/video/BV1qx4y127ue) 13 | 14 | 15 | 16 | 17 | MM的优点: 18 | 多功能生态:MM不仅支持音频格式转换,还提供一个音频发现平台,用户可以在此发现、分享心仪音频。 19 | 20 | 插件系统:允许用户和开发者通过插件扩展MM的功能,使其更加灵活和强大。 21 | 22 | **功能亮点:** 23 | 音频热键绑定:用户可以为不同音频文件设定热键,快速播放所需音效,提高效率。 24 | 25 | 单独音量调节:每个音频频道都可独立调节音量,满足个性化需求。 26 | 27 | 音频与麦克风切换:MM支持用户在音频播放与麦克风使用间随时切换,无需反复调整设置,省时省力。 28 | 29 | MM旨在为用户提供一个便捷、高效的音频播放和管理平台,不论是在游戏中、直播时或在其他需要使用音效的场合,MM都能大幅提升用户的操作便捷性和体验。 30 | 31 | **未来发展:** 32 | 随着《Musical Moments》(MM)项目的不断发展,我们计划引入更多实用且有趣的功能,以进一步增强用户体验: 33 | 34 | 文本转语音(TTS)功能:允许用户将文本直接转换为语音文件,方便快捷。 35 | 36 | 音频编辑器:提供基础的音频剪辑工具,让用户能够直接在MM中编辑和调整音频文件。 37 | 38 | MM互联:一个新的平台功能,用于增强用户间的互动与音频共享。 39 | 40 | MM移动端操作器:开发移动应用,让用户能够在移动设备上操作电脑端来进行各种操作,如选择音频音效、直接播放音频音效、远程上传等等。 41 | 42 | 多语言本地化:添加英语等多种语言支持,让MM能够服务于全球用户。 43 | 44 | 开设社区:创建一个专门的社区平台,用户可以在此分享自创的插件和音频内容。 45 | 46 | 为了实现这些激动人心的新功能,我们需要您的支持。如果您期待这些更新,敬请在GitHub上为我们的项目点个小星星并帮助宣传。您的每一次分享和支持都是我们前进的动力。感谢您加入我们的旅程,一同创造更美好的音频体验! 47 | 48 | ## 介绍 49 | **Musical Moments (MM)** 是一款完全使用.NET6.0开发的实时音频工具 不像传统的实时音频播放工具一样闭源、收费和功能少(说的就是你SoundPad) 50 | 51 | 因为发现市面上还没有一款免费开源好用的实时音频工具 所以我决定开发一款如同我描述的工具(sh*t [SLAM](https://slam.flankers.net/) 你们知道我有多难吗 这家伙[七年](https://github.com/SilentSys/SLAM)没更新了?!? 还是我硬给他[汉化](https://www.bilibili.com/video/BV1tK411i7S3)一下和搭建[音频整合站](slam.scmd.cc)才勉强在国内续命 关于他的视频都发了3个了 然后群里巴拉巴拉吵着说CS2怎么还不能用 再加上我早就加了CS2支持 但懒狗valve控制台坏了都不知道修 然后就真没办法咯 硬肝出来这个 还因为哔哩哔哩被封了30天视频都发不了没热度呜呜呜呜) 52 | 53 | ## 快速跳转 54 | 55 | - 如果你已经有了.NET环境请跳至[第二步](https://github.com/TheD0ubleC/MusicalMoments/tree/main?tab=readme-ov-file#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E9%85%8D%E7%BD%AEmmmusical-moments) 56 | - 如果你已经有了.NET环境和VB声卡请跳至[绑定音频设备与按键](https://github.com/TheD0ubleC/MusicalMoments/tree/main?tab=readme-ov-file#%E7%BB%91%E5%AE%9A%E9%9F%B3%E9%A2%91%E8%AE%BE%E5%A4%87%E4%B8%8E%E6%8C%89%E9%94%AE)或[游戏/语音工具内设置](https://github.com/TheD0ubleC/MusicalMoments/tree/main?tab=readme-ov-file#%E6%B8%B8%E6%88%8F%E8%AF%AD%E9%9F%B3%E5%B7%A5%E5%85%B7%E5%86%85%E9%85%8D%E7%BD%AE) 57 | ## 第一步:安装.NET运行时 58 | ## [点击此处下载.NET运行时](https://download.visualstudio.microsoft.com/download/pr/e030e884-446c-4530-b37b-9cda7ee93e4a/403c115daa64ad3fcf6d8a8b170f86b8/dotnet-sdk-6.0.127-win-x64.exe) 59 | 60 | - 下载完成后,双击打开安装程序并按照指示完成安装。 61 | 62 | ![image](https://github.com/TheD0ubleC/MusicalMoments/assets/143760576/667f76a7-776b-4e09-afab-e72aada0c4c0) 63 | 64 | 安装完成后会显示 65 | 66 | ![image](https://github.com/TheD0ubleC/MusicalMoments/assets/143760576/78943de2-2812-48b5-98a7-42dba6ea5c38) 67 | 68 | ## 第二步: 配置MM(Musical Moments) 69 | 70 | ### [下载最新版本](https://github.com/TheD0ubleC/MusicalMoments/releases) 71 | 72 | - 下载并解压到任意位置。 73 | 74 | ### 首次启动 75 | 76 | - 首次启动MM时,你将进入引导页。请按照引导完成初次设置。 77 | 78 | ![image](https://github.com/TheD0ubleC/MusicalMoments/assets/143760576/402b2b7e-7a79-49e9-bfe9-9171cd731be4) 79 | 80 | ### 虚拟声卡 81 | 82 | - 我推荐使用VB虚拟声卡来获取最佳体验。你可以使用压缩包中的VB安装程序,或通过MM下载最新版本的VB。 83 | 84 | **注意**:过新的版本可能导致兼容性问题,请选择适宜版本。但一般不可能会出现,因为虚拟声卡的性质都是相同的。 85 | 86 | - 安装成功后,在MM中点击“重新检测”验证安装是否成功。 87 | 88 | 如果在安装过程中出现需要管理员权限的提示,请以管理员身份运行安装程序。 89 | 90 | ![image](https://github.com/TheD0ubleC/MusicalMoments/assets/143760576/abd85c55-806b-4db4-af9c-78a6315adbe9) 91 | 92 | - 如果安装VB时弹出该消息框请使用管理员运行安装程序 93 | 94 | ### 绑定音频设备与按键 95 | 96 | 请按下图进行操作 97 | 98 | ![image](https://github.com/TheD0ubleC/MusicalMoments/assets/143760576/c8a90cd7-9604-4f67-b16a-6fe8e0220298) 99 | 100 | - 导入音频可在“音频”页直接拖入音频文件或手动放入运行目录下的“AudioData”文件夹。 101 | 102 | - 右键点击导入的音频,并选择“设为播放项”。 103 | 104 | ![image](https://github.com/TheD0ubleC/MusicalMoments/assets/143760576/38277b5e-34dd-4a24-b15d-604802b6f70e) 105 | 106 | ### 游戏/语音工具内配置 107 | 108 | - 我将以CS2和KOOK为例展示如何在游戏或语音工具中配置MM。 109 | 110 | **KOOK示例配置** 111 | 112 | ![70717d71a498315b8ebca1ce68d52525](https://github.com/TheD0ubleC/MusicalMoments/assets/143760576/f447ef78-7a27-46f2-92f9-7a152d01f4ea) 113 | 114 | **CS2示例配置** 115 | 116 | ![image](https://github.com/TheD0ubleC/MusicalMoments/assets/143760576/461615f4-e0b6-4147-b3f2-f205200c4d60) 117 | 118 | - 其他语音工具或游戏(如Discord、Valorant、OW2等)的配置方法类似。 119 | 120 | ## 开始使用 121 | 122 | - 完成以上步骤后,按下你绑定的播放音频快捷键即可播放音频。如果需要使用麦克风,按下绑定的切换源快捷键进行切换。切换成功后,会有语音提示(如:“切换为麦克风”或“切换为音频”)。注意,切换为麦克风后通话可能会有大约200ms的延迟,请注意闭麦实际。 123 | 124 | **注意**:如果按下快捷键没有反应,请尝试以管理员权限运行MM。 125 | --------------------------------------------------------------------------------