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.

Sound eXchange (SoX)

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.

brew 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.

Tones and Silence Records

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.

public abstract record Sound(int Duration = 125)
{
    public abstract void Play();
}

public record Silence(int Duration = 125) : Sound(Duration)
{
    public override void Play()
    {
	// TODO: Implement Silence
    }
}

public record Tone(int Frequency, int Duration = 125) : Sound(Duration)
{
    public override void Play()
    {
       // TODO: Call SoX
    }
}

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.

public record Silence(int Duration = 125) : Sound(Duration)
{
    public override void Play()
    {
        Thread.Sleep(Duration);
    }
}

The second option for silence is to use SoX to play a 0 frequency, which has no sound.

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

Let’s now implement the Play method of our Tone record.

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 -c1 synth {length} sine {Frequency}") {
            RedirectStandardOutput = true
        };
        var result = Process.Start(info);
        result?.WaitForExit(Duration);
    }
}

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.

It’s A Me, Mario! C# Code

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;

// Super Mario Bros
// (Ported from http://www.portal42.net/mario.txt)
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(
@"

____▒▒▒▒▒
—-▒▒▒▒▒▒▒▒▒
—–▓▓▓░░▓░
—▓░▓░░░▓░░░
—▓░▓▓░░░▓░░░
—▓▓░░░░▓▓▓▓
——░░░░░░░░
—-▓▓▒▓▓▓▒▓▓
–▓▓▓▒▓▓▓▒▓▓▓
▓▓▓▓▒▒▒▒▒▓▓▓▓
░░▓▒░▒▒▒░▒▓░░
░░░▒▒▒▒▒▒▒░░░
░░▒▒▒▒▒▒▒▒▒░░
—-▒▒▒ ——▒▒▒
–▓▓▓———-▓▓▓
▓▓▓▓———-▓▓▓▓

");


// data structures for sounds
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.