Files
fur-elise-csharp/Program.cs
T

133 lines
4.8 KiB
C#

// 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;
}
}