feat: C# Fur Elise player using Windows Beep API
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
*.user
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
.vs/
|
||||||
|
.idea/
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AssemblyName>FurElise</AssemblyName>
|
||||||
|
<RootNamespace>FurElise</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net10.0",
|
||||||
|
"framework": {
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "10.0.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+132
@@ -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<string, int> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <n>` | 速度(四分音符 BPM) | 72 |
|
||||||
|
| `--loops <n>` | 重复次数 | 1 |
|
||||||
|
| `--help` | 帮助 | |
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\FurElise.exe --dry-run
|
||||||
|
.\FurElise.exe --bpm 90 --loops 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
|
||||||
|
- 仅 Windows(依赖 `Beep` API);无声卡驱动策略时部分环境可能无声音
|
||||||
|
- 旋律为主题段落的简化编配,适合教学演示
|
||||||
|
- 请勿在文章/仓库中提交站点账号密码
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
Reference in New Issue
Block a user