commit 7ece38445316ffb246f5a380fb04ef6aa01db022 Author: Sherry <1243938742@qq.com> Date: Mon Sep 21 10:58:32 2026 +0800 feat: C# Fur Elise player using Windows Beep API diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..048601a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +bin/ +obj/ +*.user +*.dll +*.exe +*.pdb +.vs/ +.idea/ diff --git a/FurElise.csproj b/FurElise.csproj new file mode 100644 index 0000000..05ea434 --- /dev/null +++ b/FurElise.csproj @@ -0,0 +1,10 @@ + + + Exe + net10.0 + enable + enable + FurElise + FurElise + + diff --git a/FurElise.runtimeconfig.json b/FurElise.runtimeconfig.json new file mode 100644 index 0000000..25d81f6 --- /dev/null +++ b/FurElise.runtimeconfig.json @@ -0,0 +1,9 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.12" + } + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..243ca11 --- /dev/null +++ b/Program.cs @@ -0,0 +1,132 @@ +// Für Elise (致爱丽丝) — C# console player +// Windows: kernel32 Beep; --dry-run prints score only. +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; + +class FurElisePlayer +{ + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool Beep(uint dwFreq, uint dwDuration); + + static readonly Dictionary Freq = new(StringComparer.OrdinalIgnoreCase) + { + ["C4"] = 262, ["D4"] = 294, ["Ds4"] = 311, ["E4"] = 330, + ["F4"] = 349, ["G4"] = 392, ["Gs4"] = 415, ["A4"] = 440, + ["As4"] = 466, ["B4"] = 494, + ["C5"] = 523, ["D5"] = 587, ["Ds5"] = 622, ["E5"] = 659, + ["F5"] = 698, ["G5"] = 784, ["Gs5"] = 831, ["A5"] = 880, + ["B5"] = 988, ["C6"] = 1047, + ["A3"] = 220, ["B3"] = 247, ["E3"] = 165, + }; + + // note name, duration in 16th-note units (4 = quarter), rest = "R" + static readonly (string Note, int Units)[] Score = + { + // Theme A (famous opening) + ("E5", 2), ("Ds5", 2), ("E5", 2), ("Ds5", 2), ("E5", 2), + ("B4", 2), ("D5", 2), ("C5", 2), + ("A4", 6), ("R", 2), + ("C4", 2), ("E4", 2), ("A4", 2), + ("B4", 6), ("R", 2), + ("E4", 2), ("Gs4", 2), ("B4", 2), + ("C5", 6), ("R", 2), + + ("E4", 2), ("E5", 2), ("Ds5", 2), ("E5", 2), ("Ds5", 2), ("E5", 2), + ("B4", 2), ("D5", 2), ("C5", 2), + ("A4", 6), ("R", 2), + ("C4", 2), ("E4", 2), ("A4", 2), + ("B4", 6), ("R", 2), + ("E4", 2), ("C5", 2), ("B4", 2), + ("A4", 12), + + // Theme B (middle section, simplified) + ("E4", 2), ("C5", 2), ("B4", 2), + ("A4", 4), ("E4", 2), ("F4", 2), + ("E4", 2), ("A4", 2), ("B4", 2), ("C5", 2), + ("B4", 4), ("E4", 2), ("Gs4", 2), + ("A4", 4), ("E4", 2), ("B4", 2), + ("C5", 2), ("A4", 2), ("B4", 2), ("A4", 2), + ("E5", 2), ("Ds5", 2), ("E5", 2), ("Ds5", 2), ("E5", 2), + ("B4", 2), ("D5", 2), ("C5", 2), + ("A4", 6), ("R", 2), + ("C4", 2), ("E4", 2), ("A4", 2), + ("B4", 6), ("R", 2), + ("E4", 2), ("Gs4", 2), ("B4", 2), + ("C5", 6), ("R", 2), + ("E4", 2), ("E5", 2), ("Ds5", 2), ("E5", 2), ("Ds5", 2), ("E5", 2), + ("B4", 2), ("D5", 2), ("C5", 2), + ("A4", 6), ("R", 2), + ("C4", 2), ("E4", 2), ("A4", 2), + ("B4", 6), ("R", 2), + ("E4", 2), ("C5", 2), ("B4", 2), + ("A4", 12), + }; + + static int Main(string[] args) + { + var dryRun = false; + var bpm = 72; // Für Elise ~ 72 eighth-ish feel; we treat unit = 16th + var loops = 1; + + for (var i = 0; i < args.Length; i++) + { + var a = args[i]; + if (a == "--dry-run") dryRun = true; + else if (a == "--bpm" && i + 1 < args.Length) bpm = int.Parse(args[++i]); + else if (a == "--loops" && i + 1 < args.Length) loops = int.Parse(args[++i]); + else if (a == "--help" || a == "-h") + { + Console.WriteLine("FurElisePlayer [--dry-run] [--bpm 72] [--loops 1]"); + Console.WriteLine("Play Beethoven - Für Elise via Windows Beep."); + return 0; + } + } + + if (bpm < 40) bpm = 40; + if (bpm > 200) bpm = 200; + if (loops < 1) loops = 1; + + // 16th-note duration in ms from BPM (quarter note = 60000/bpm) + var unitMs = (int)Math.Round(60000.0 / bpm / 4.0); + if (unitMs < 20) unitMs = 20; + + Console.WriteLine("Beethoven - Für Elise (致爱丽丝)"); + Console.WriteLine($"BPM={bpm}, unit={unitMs}ms, notes={Score.Length}, loops={loops}"); + Console.WriteLine(dryRun ? "Mode: dry-run (no sound)" : "Mode: play"); + Console.WriteLine(new string('-', 48)); + + for (var loop = 1; loop <= loops; loop++) + { + if (loops > 1) Console.WriteLine($"--- loop {loop}/{loops} ---"); + foreach (var (note, units) in Score) + { + var ms = units * unitMs; + // slight articulation: leave a tiny gap + var soundMs = Math.Max(15, ms - 8); + + if (note == "R" || note == "-" || note.Length == 0) + { + if (dryRun) Console.WriteLine($" REST {ms,4} ms"); + else Thread.Sleep(ms); + continue; + } + + if (!Freq.TryGetValue(note, out var hz)) + { + Console.Error.WriteLine($"Unknown note: {note}"); + continue; + } + + if (dryRun) + Console.WriteLine($" {note,-5} {hz,4} Hz {ms,4} ms"); + else if (!Beep((uint)hz, (uint)soundMs)) + Console.Beep(hz, soundMs); + } + } + + Console.WriteLine("Done."); + return 0; + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1756c6 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# FurElise C# + +用 C# 控制台程序在 Windows 上演奏贝多芬《致爱丽丝》(Für Elise)。 + +## 原理 + +- 使用 Windows 内核 API `kernel32!Beep(frequency, duration)` 发声 +- 乐谱数据用 `(音名, 时值)` 数组描述,`R` 表示休止 +- 音名映射到平均律频率(如 `A4=440`、`E5=659`、`Ds5=622`) + +## 运行 + +### 方式 A:已编译程序 + +```powershell +dotnet FurElise.dll +# 或 +.\FurElise.exe +``` + +### 方式 B:源码直接编译(.NET 10 csc) + +```powershell +$refDir = "C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\10.0.12\ref\net10.0" +$refs = (Get-ChildItem "$refDir\*.dll" | ForEach-Object { "-r:`"$($_.FullName)`"" }) -join ' ' +dotnet exec "C:\Program Files\dotnet\sdk\10.0.401\Roslyn\bincore\csc.dll" ` + -nologo -t:exe -out:FurElise.dll $refs Program.cs +# 复制 runtimeconfig.json 后 +dotnet FurElise.dll +``` + +### 方式 C:dotnet run(需可用的 SDK/NuGet) + +```powershell +dotnet run --project FurElise.csproj +``` + +## 参数 + +| 参数 | 说明 | 默认 | +|------|------|------| +| `--dry-run` | 只打印音符与频率,不发声 | 关 | +| `--bpm ` | 速度(四分音符 BPM) | 72 | +| `--loops ` | 重复次数 | 1 | +| `--help` | 帮助 | | + +示例: + +```powershell +.\FurElise.exe --dry-run +.\FurElise.exe --bpm 90 --loops 2 +``` + +## 说明 + +- 仅 Windows(依赖 `Beep` API);无声卡驱动策略时部分环境可能无声音 +- 旋律为主题段落的简化编配,适合教学演示 +- 请勿在文章/仓库中提交站点账号密码 + +## License + +MIT