From 7ece38445316ffb246f5a380fb04ef6aa01db022 Mon Sep 17 00:00:00 2001
From: Sherry <1243938742@qq.com>
Date: Mon, 21 Sep 2026 10:58:32 +0800
Subject: [PATCH] feat: C# Fur Elise player using Windows Beep API
---
.gitignore | 8 +++
FurElise.csproj | 10 +++
FurElise.runtimeconfig.json | 9 +++
Program.cs | 132 ++++++++++++++++++++++++++++++++++++
README.md | 62 +++++++++++++++++
5 files changed, 221 insertions(+)
create mode 100644 .gitignore
create mode 100644 FurElise.csproj
create mode 100644 FurElise.runtimeconfig.json
create mode 100644 Program.cs
create mode 100644 README.md
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