C# programming isn’t all about enterprise development, databases, and web APIs. Most folks likely got into software development because of video games. We wanted to create our heroes, worlds, and adventures. Being a millennial myself, Super Mario holds a place near and dear in my heart. So I thought I would take some inspiration and create a console application that plays the Super Mario Bros. theme, focusing on being cross-platform friendly and capable.
In this post, we’ll see how to use a cross-platform command-line utility, SoX, to play synthesized sounds from a console application.
SoX is a cross-platform command-line utility that we can use to interact with various formats and audio files. Users can use the utility to apply effects and synthesize audio.
For the sake of this post, SoX is the best utility I could find that would work on all .NET platforms. For folks following along, they should be able to run this code sample regardless of their development environment. Before running the example, install the operating system-specific version of the utility on your development machine.
Since I run primarily on macOS, I used Homebrew to install SoX.
Windows users can download the latest SoX executable from the SourceForge release page. Windows users need to make sure they install SoX into a directory part of PATH
, as we’ll be invoking the utility via Process
calls.
We can break musical tones down into two parts: Frequency and Duration. There is also a particular instance of a musical tone, which we’ll define as silence or note with duration, but no frequency. We’ll use C# 9 records to define a structure for Tone
and Silence
, which will inherit from a Sound
base record.
Let’s start by defining the Play
method of Silence
. We have two options here; the first option is to call Thread.Sleep
for the duration of silence.
The second option for silence is to use SoX to play a 0
frequency, which has no sound.
Let’s now implement the Play
method of our Tone
record.
We can pass the Frequency
in Hertz (Hz)
to our Tone
record’s positional parameter. It just a matter of waiting for the SoX process to complete, and then we can move on to the next tone.
Now that we have the building blocks of music let’s compose our Super Mario theme. Here is the entire example in a top-level statement file.
using System;
using System.Collections.Generic;
using System.Diagnostics;
var music = new List<Sound> {
new Tone(659), new Tone(659), new Silence(), new Tone(659), new Silence(167), new Tone(523),
new Tone(659), new Silence(), new Tone(784), new Silence(375), new Tone(392), new Silence(375),
new Tone(523), new Silence(250), new Tone(392), new Silence(250), new Tone(330), new Silence(250),
new Tone(440), new Silence(), new Tone(494), new Silence(), new Tone(466), new Silence(42),
new Tone(440), new Silence(), new Tone(392), new Silence(), new Tone(659), new Silence(),
new Tone(784), new Silence(), new Tone(880), new Silence(), new Tone(698), new Tone(784),
new Silence(), new Tone(659), new Silence(), new Tone(523), new Silence(), new Tone(587),
new Tone(494), new Silence(), new Tone(523), new Silence(250), new Tone(392), new Silence(250),
new Tone(330), new Silence(250), new Tone(440), new Silence(), new Tone(494), new Silence(),
new Tone(466), new Silence(42), new Tone(440), new Silence(), new Tone(392), new Silence(),
new Tone(659), new Silence(), new Tone(784), new Silence(), new Tone(880), new Silence(),
new Tone(698), new Tone(784), new Silence(), new Tone(659), new Silence(), new Tone(523),
new Silence(), new Tone(587), new Tone(494), new Silence(375), new Tone(784), new Tone(740),
new Tone(698), new Silence(42), new Tone(622), new Silence(), new Tone(659), new Silence(167),
new Tone(415), new Tone(440), new Tone(523), new Silence(), new Tone(440),
new Tone(523), new Tone(587), new Silence(250), new Tone(784), new Tone(740), new Tone(698), new Silence(42),
new Tone(622), new Silence(), new Tone(659), new Silence(167), new Tone(698), new Silence(),
new Tone(698), new Tone(698), new Silence(625), new Tone(784), new Tone(740),
new Tone(698), new Silence(42), new Tone(622), new Silence(), new Tone(659), new Silence(167), new Tone(415),
new Tone(440), new Tone(523), new Silence(), new Tone(440), new Tone(523),
new Tone(587), new Silence(250), new Tone(622), new Silence(250), new Tone(587), new Silence(250), new Tone(523),
new Silence(1125), new Tone(784), new Tone(740), new Tone(698), new Silence(42), new Tone(622),
new Silence(), new Tone(659), new Silence(167), new Tone(415), new Tone(440), new Tone(523),
new Silence(), new Tone(440), new Tone(523), new Tone(587), new Silence(250), new Tone(784),
new Tone(740), new Tone(698), new Silence(42), new Tone(622), new Silence(), new Tone(659),
new Silence(167), new Tone(698), new Silence(), new Tone(698), new Tone(698), new Silence(625),
new Tone(784), new Tone(740), new Tone(698), new Silence(42), new Tone(622), new Silence(),
new Tone(659), new Silence(167), new Tone(415), new Tone(440), new Tone(523),
new Silence(), new Tone(440), new Tone(523), new Tone(587), new Silence(250),
new Tone(622), new Silence(250), new Tone(587), new Silence(250), new Tone(523),
new Silence(625),
};
music.ForEach(s => s.Play());
Console.Write(
@"
____▒▒▒▒▒
—-▒▒▒▒▒▒▒▒▒
—–▓▓▓░░▓░
—▓░▓░░░▓░░░
—▓░▓▓░░░▓░░░
—▓▓░░░░▓▓▓▓
——░░░░░░░░
—-▓▓▒▓▓▓▒▓▓
–▓▓▓▒▓▓▓▒▓▓▓
▓▓▓▓▒▒▒▒▒▓▓▓▓
░░▓▒░▒▒▒░▒▓░░
░░░▒▒▒▒▒▒▒░░░
░░▒▒▒▒▒▒▒▒▒░░
—-▒▒▒ ——▒▒▒
–▓▓▓———-▓▓▓
▓▓▓▓———-▓▓▓▓
");
public abstract record Sound(int Duration = 125)
{
public abstract void Play();
}
public record Silence(int Duration = 125) : Sound(Duration)
{
public override void Play()
{
var length = TimeSpan.FromMilliseconds(Duration).TotalSeconds;
var info = new ProcessStartInfo("play",
$"-n synth {length} sine 0") {RedirectStandardOutput = true};
var result = Process.Start(info);
result?.WaitForExit(Duration);
}
}
public record Tone(int Frequency, int Duration = 125) : Sound(Duration)
{
public override void Play()
{
var length = TimeSpan.FromMilliseconds(Duration).TotalSeconds;
var info = new ProcessStartInfo("play",
$"-n synth {length} sine {Frequency}") {
RedirectStandardOutput = true
};
var result = Process.Start(info);
result?.WaitForExit(Duration);
}
}
Running the console application, we hear the Super Mario Bros. theme song.
To run this code on your development environment, you can clone this project from my GitHub repository.
I hope you enjoyed this blog post, and please share any compositions with me on Twitter by tagging me at @buhakmeh.