Click here to Skip to main content
15,882,113 members
Articles / Desktop Programming / Universal Windows Platform
Article

Simple software MIDI keyboard for UWP app

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
28 Mar 2017CPOL12 min read 13.6K   7  
Simple software MIDI keyboard using Microsoft GS Wavetable Synth

Image 1

Introduction

I was last year creating an UWP fun app, Play Your Solo, for Window Store. For that I needed a software keyboard with different instrument in the app. I search the net for a tutorial and code examples but didn't found anything up to date for C#.

As a kind of a beginner to programming in C# and using Visual Studio, finishing the app would have been done faster, if I could have done some copy/paste from a good example. Although it was not very complicated to do and I learned a lot from creating it myself.

For people looking for a simple way to create a software MIDI keyboard as part of an app, or to use as a startup training project into the MIDI world, I created this little example. It does not have a finished touch, as the XAML part is not that interesting.

Background

As a beginner to using MIDI format and using the built in Microsoft GS Wavetable Synth, I found a document on Windows Dev Center, MIDI, that gave me the basic setup of the program. That document is easy read and understood.

For a better understanding of MIDI messages, I found this page by Dominique Vandenneucker from Carnegie Mellon Univerity, School of Computer Science. For the instrument used in the General MIDI (GM), this page was very useful. It is a handsout from a music course at Center for Computer Assisted Research in the Humanities at Stanford University.

Using the code

First of all, start up Visual Studio 2015. The Windows 10 SDK has to been installed. Create a New Project and choose the template "Blank App (Universal Windows)". Call the project MidiKeyBoard.

When the project has been built, you need to reference the SDK extension for the built-in MIDI synth. To do so, right-click on 'References' in the Solution Explorer, in Visual Studio, and choose 'Add Reference'.

A new window will pop-up. In that window, expand the 'Universal Window'-node and click on 'Extensions'.

Depending on your configuration of Visual Studio, a longer list of available extensions will show. Select the extension 'Microsoft General MIDI DLS for Universal Windows Apps'.

If there are more than one extension with that name, choose the version, that matches the target of your app. To check the target version of the app, go to the project properties and select the Application tab.

Image 2

Click 'OK' and the extension is added

Now that the SDK extension for the built-in MIDI synth is added, it is time to do some simple layout of the MIDI keyboard. First of all, lock the app in Landscape mode, as the keyboard will look funny and small in Portrait mode.

To do so, dobble-click the Package.appxmanifest in the Solution Explorer. Select the Application tab and check Landscape only. This will lock the screen in Landscape mode.

Lock app in Landscape mode

In MainPage.xaml, create three rows in the main grid. One for status line, one for the keyboard and one for choosing instruments.

Set the background color of the grid to yellow, to be able to see the black and white key og the keyboard

The status will consist of at TextBlock to show if any MIDI device is connected. This should look like this:

<Page
    x:Class="MidiKeyboard.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MidiKeyboard"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="Yellow">

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock Name="StatusTextBlock"
                   Grid.Row="0"
                   Margin="10" />
    </Grid>
</Page>

In code behind, MainPage.xaml.cs, add the namespaces Windows.Devices.Enumeration and Windows.Devices.Midi, to be able to use enumaration of devices and MIDI devices. Also add the System.Threading.Tasks namespace to be able to do Task in async methods

...
using Windows.Devices.Enumeration;
using Windows.Devices.Midi;
System.Threading.Tasks;
...

To find the Microsoft GS Wavetable Synth to connect to it, enumerate through all the MIDI Output devices connected to the Windows 10 device. This is done by the FindAllAsync method of the DeviceInformation class. Create a private method EnumerateMidiOutputDevices().

In the EnumerateMidiOutputDevices() method, create a string to hold the query information for the FindAllAsync method. The query will hold information to get all MIDI output ports connected. The query string will be used as input for the FindAllAsync method and the search result will be gathered in a DeviceInformationCollection object

If no MIDI output devices is found, return from the method and set the text in the StatusTextBlock to "No MIDI output devices found". If a MIDI output device, hopefully the Microsoft GS Wavetable Synth, the name will be displayed in the StatusTextBlock. In this example it is assumed, that no external MIDI output devices are connected, so the collection of MIDI output devices will only have one item. The one needed, Microsoft GS Wavetable Synth.

As EnumerateMidiOutputDevices() is an async method returning a Task, it should not be called directly from the MainPage(). Create a method, GetMidiOutputDevicesAsync(), to call EnumerateMidiOutputDevices(). GetMidiOutputDevicesAsync() should be called form MainPage(). It will look like this.

MainPage method:

public MainPage()
{
    this.InitializeComponent();

    // Get the MIDI output device
    GetMidiOutputDevicesAsync();
}

GetMidiOutputDevicesAsync:

/// <summary>
/// Method to call EnumarateMidiOutputDevice from the MainPage().
/// EnumerateMidiOutputDevices() has to be called via a await call, and that cannot be done from the constructor.
/// </summary>
private async void GetMidiOutputDevicesAsync()
{
    await EnumerateMidiOutputDevices();
}

EnumerateMidiOutputDevices method:

/// <summary>
/// 
/// </summary>
/// <returns></returns>
private async Task EnumerateMidiOutputDevices()
{
    // Create the query string for finding all MIDI output devices using MidiOutPort.GetDeviceSelector()
    string midiOutportQueryString = MidiOutPort.GetDeviceSelector();

    // Find all MIDI output devices and collect it in a DeviceInformationCollection using FindAllAsync
    DeviceInformationCollection midiOutputDevices = await DeviceInformation.FindAllAsync(midiOutportQueryString);

    // Return if no external devices are connected
    if (midiOutputDevices.Count == 0)
    {
        // Set the StatusTextBlock foreground color to red
        StatusTextBlock.Foreground = new SolidColorBrush(Colors.Red);

        // Set the StatusTextBlock text to "No MIDI output devices found"
        StatusTextBlock.Text = "No MIDI output devices found";
        return;
    }
    else
    {
        // Set the StatusTextBlock foreground color to green
        StatusTextBlock.Foreground = new SolidColorBrush(Colors.Green);

        // Set the StatusTextBlock text to the name of the first item in midiOutputDevices collection
        StatusTextBlock.Text = midiOutputDevices[0].Name;
    }
}

Now that the MIDI output device, hopefully Microsoft GS Wavetable Synth, is found, create an instance of DeviceInformation to hold information of Microsoft GS Wavetable Synth. That will be used to create an object of the interface IMidiOutPort using the Id property of the DeviceInformation. This object will be used to send the MIDI message to be effectuated. Due to that, define the IMidiOutPort object in the top of the code-behind for the page, so it can be used outside the EnumerateMidiOutputDevices method.

Top of the code behind of the page:

...
namespace MidiKeyboard
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        // IMidiOutPort of the output MIDI device
        IMidiOutPort midiOutPort;
 
        public MainPage()
        {
...

In the EnumerateMidiOutputDevices:

...
    else
    {  
       // Set the StatusTextBlock foreground color to green
       StatusTextBlock.Foreground = new SolidColorBrush(Colors.Green);

       // Set the StatusTextBlock text to the name of the first item in midiOutputDevices collection
       StatusTextBlock.Text = midiOutputDevices[0].Name;
    }

    // Create an instance of DeviceInformation and set it to the first midi device in DeviceInformationCollection, midiOutputDevices
    DeviceInformation devInfo = midiOutputDevices[0];

    // Return if DeviceInformation, devInfo, is null
    if (devInfo == null)
    {
        // Set the midi status TextBlock
        StatusTextBlock.Foreground = new SolidColorBrush(Colors.Red);
        StatusTextBlock.Text = "No device information of MIDI output";
        return;
    }


    // Set the IMidiOutPort for the output midi device by calling MidiOutPort.FromIdAsync passing the Id property of the DeviceInformation
    midiOutPort = await MidiOutPort.FromIdAsync(devInfo.Id);

    // Return if midiOutPort is null
    if (midiOutPort == null)
    {
        // Set the midi status TextBlock
        StatusTextBlock.Foreground = new SolidColorBrush(Colors.Red);
        StatusTextBlock.Text = "Unable to create MidiOutPort from output device";
        return;
     }
}

When debugging now, you can find the Id property for the Microsoft GS Wavetable Synth and hardcode the Id in the FromIdAsync method. The enumeration with the FindAllAsync method could then be avoided everytime the applications is loaded. An disavantage of that is, that the application will stop working, if the Id somehow changes from device to device or with an update.

To release resources when the app is suspended, create a methods to be called, when the app is suspended. In this method, call the dispose method for the MidiOutPort and set the MidiOutPort to null

/// <summary>
/// Eventhandler to clean up the MIDI connection, when the app is suspended.
/// The object midiOutPort is disposed and set to null
/// </summary>
/// <param name="sender"><</param>
/// <param name="e"></param>
private void App_Suspending(object sender, SuspendingEventArgs e)
{
    try
    {
        // Dispose the Midi Output port
        midiOutPort.Dispose();

        // Set the midiOutPort to null
        midiOutPort = null;
    }
    catch
    {
        // Do noting. A cleanup has already been made
    }
}

When the application is resumed after suspension, the connection to the MIDI output port should be reestablished. Create a method to call the EnumerateMidiOutputDevices() method to set up the MIDI connection.

/// <summary>
/// Eventhandler to restore connection to the MIDI device when app is resuming after suspension.
/// The method EnumerateMidiOutputDevices() will be called.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void App_Resuming(object sender, object e)
{
    // Call method to set up output midi device
    await EnumerateMidiOutputDevices();
}

As a final step, add the eventhandlers to the event of suspending and resuming in the MainPage().

...
        public MainPage()
        {
            this.InitializeComponent();

            // Get the MIDI output device
            GetMidiOutputDevicesAsync();

            // Reopen the midi connection when resuming after a suspension
            Application.Current.Resuming += new EventHandler<Object>(App_Resuming);

            // Close the midi connection on suspending the app
            Application.Current.Suspending += new SuspendingEventHandler(App_Suspending);
        }
...

Now that the inital setup for using the Microsoft GS Wavetable Synth has been made, the layout has to be set. This layout will be very simple and not very nice looking. A XAML code can easely be very voluminous and not easely read, when creating a good layout. For the same reason, only five keys will be showed, chromatically from C to E.

In the MainPage.xaml, create a grid element inside the main grid, named KeyboardGrid, and set it to grid row 1 of the main grid.

In KeyboardGrid, define five columns to hold the five keys in the keyboard. Make five rectangles, placed in each column. Let the name of each rectangle be the name of the key: Ckey, CSharpKey, DKey, DSharpKey, EKey. Set the fill color to alternate white and black

In each rectangle elements create two events to control the keyboard. Create an event for PointerPressed to call the Key_PointerPressed eventhandler, when a key is pressed. Create an event for PointerReleased to call the Key_PointerReleased eventhandler, when a key is released again.

The grid, KeyboardGrid, should look like this:

...
    <Grid Background="Yellow">

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock Name="StatusTextBlock"
                   Grid.Row="0"
                   Margin="10" />

        <Grid Name="KeyboardGrid" 
              Grid.Row="1"
              Margin="20">
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Rectangle Name="CKey"
                       Grid.Column="0"
                       Fill="White"
                       PointerPressed="Key_PointerPressed"
                       PointerReleased="Key_PointerReleased"/>

            <Rectangle Name="CSharpKey"
                       Grid.Column="1"
                       Fill="Black"
                       PointerPressed="Key_PointerPressed"
                       PointerReleased="Key_PointerReleased"/>

            <Rectangle Name="DKey"
                       Grid.Column="2"
                       Fill="White"
                       PointerPressed="Key_PointerPressed"
                       PointerReleased="Key_PointerReleased"/>

            <Rectangle Name="DSharpKey"
                       Grid.Column="3"
                       Fill="Black"
                       PointerPressed="Key_PointerPressed"
                       PointerReleased="Key_PointerReleased"/>

            <Rectangle Name="EKey"
                       Grid.Column="4"
                       Fill="White"
                       PointerPressed="Key_PointerPressed"
                       PointerReleased="Key_PointerReleased"/>
        </Grid>

    </Grid>
...

Hopefully, the application should look something like this, when it is set to run in Visual Studio 2015:

Image 4

In the eventhandler for Key_PointerPressed, a MIDI message will be send to play a specific note. A way to communicate with the MIDI synthesizer is through the IMidiMessage interface. In this app, we will be using three type of messages in the interface, Note On, Note Off and Program Change. The General Midi (GM) standard specify 16 channels and 128 predefined instrument. As the MIDI keyboard only will play with one instrument at the time, only channel 0 will be used in this example.

The Program Change message is used to select or change the instrument of the MIDI output device in a specific channel. The message is like this: MidiProgramChangeMessage(byte 'Channel Number', byte 'Instrument Number'). In this example, we use channel 0 and set the instrument to Acoustic Grand Piano wich is the first instrument in the list of predefined instrument. As we use the list in a program, we will substract 1 from the number in the list to get the correct instrument. Now define the IMidiMessage in the head of the code-behind of the page and create the MidiProgramChangeMessage in the MainPage().

It should look like this:

...
    public sealed partial class MainPage : Page
    {
        // MidiOutPort of the output MIDI device
        IMidiOutPort midiOutPort;

        // MIDI message to change the instument
        IMidiMessage instrumentChange;

        public MainPage()
        {
            this.InitializeComponent();

            // Set the initial instrument of the keyboard to Acoustic Grand Piano in channel 0
            instrumentChange = new MidiProgramChangeMessage(0, 0);

            // Get the MIDI output device
            GetMidiOutputDevicesAsync();

            // Reopen the midi connection when resuming after a suspension
            Application.Current.Resuming += new EventHandler<Object>(App_Resuming);

            // Close the midi connection on suspending the app
            Application.Current.Suspending += new SuspendingEventHandler(App_Suspending);
        }
...

To set the initial instrument, send the created midi message, instrumentChange, to the MidiOutPort, midiOutPort, in the end of the EnumerateMidiOutputDevices() method:

...
            // Return if midiOutPort is null
            if (midiOutPort == null)
            {
                // Set the midi status TextBlock
                StatusTextBlock.Foreground = new SolidColorBrush(Colors.Red);
                StatusTextBlock.Text = "Unable to create MidiOutPort from output device";
                return;
            }

            // Send the Program Change midi message to port of the output midi device
            // to set the initial instrument to Acoustic Grand Piano.
            midiOutPort.SendMessage(instrumentChange);
        }
...

Now that the instrument of the MIDI output device is set, the keyboard has to be made functional. To start a MIDI sound from the chosen instrument with a chosen frequency (pitch), send the MIDI message Note On. To stop the sound again, send the MIDI message Note OFF.

The range of the pitch goes from 0 to 127. The middle C has the value 60, and the pitch step a half note. That results in D# has the value 61 and D has the value 62 etc.

As with real instrument, some instruments hold the sound (maybe with vibrating or variation) as long as the notes are being played, like the trumpet. Some instruments fade the sound as the notes are being played, like the piano.

The velocity of a MIDI sound define the power in the sound. Higher number has more power. Velocity range from 0 to 127. In this example we use the velocity 127.

A Note On message has the format: MidiNoteOnMessage(byte 'Channel Number', byte 'Note', byte 'Velocity')

Similiar, when we want to stop a specific note in a specific channel, we use the MIDI message Note Off. A Note Off message has the format: MidiNoteOffMessage(byte 'Channel Number', byte 'Note', byte 'Velocity')

As we in this example always use the channel 0 and a velocity of 127, let them be created in the top of the code-behind for the page.

...
    public sealed partial class MainPage : Page
    {
        // MidiOutPort of the output MIDI device
        IMidiOutPort midiOutPort;

        // MIDI message to change the instument
        IMidiMessage instrumentChange;

        // The MIDI channel used for the MIDI output device
        byte channel = 0;

        // The MIDI velocity used for the MIDI output device
        byte velocity = 127;

        public MainPage()
        {
            this.InitializeComponent();
...

In the eventhandler for PointerPressed, Key_PointerPressed, we will extract the key pressed using a switch/case and select the pitch according to that. When the pitch is knowned, the Note On message can be created with the channel and velocity and the message can be send to the MIDI output device. To be able to create a Rectangle element, to cast the pressed key, add the namespace Windows.UI.Xaml.Shapes.

The Key_PointerPressed will look like:

/// <summary>
/// Eventhandler for key pressed at the keyboard.
/// The specific key is extracted and the according pitch is found.
/// A MIDI message Note On is created from the channel field and velocity field.
/// The Note On message is send to the MIDI output device
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Key_PointerPressed(object sender, PointerRoutedEventArgs e)
{
    // Field to hold the pitch of the note
    byte note;

    // Extract the key being pressed
    Rectangle keyPressed = (Rectangle)sender;

    // Get the name of the key and store it in a string, keyPressedName
    string keyPressedName = keyPressed.Name;

    // Switch/Case to set the pitch depending of the key pressed
    switch (keyPressedName)
    {
        case "CKey":
            note = 60;
            break;
        case "CSharpKey":
            note = 61;
            break;
        case "DKey":
            note = 62;
            break;
        case "DSharpKey":
            note = 63;
            break;
        case "EKey":
            note = 64;
            break;
        default:
            note = 60;
            break;
    }

    // Create the Note On message to send to the MIDI output device
    IMidiMessage midiMessageToSend = new MidiNoteOnMessage(channel, note, velocity);

    // Send the Note On MIDI message to the midiOutPort
    midiOutPort.SendMessage(midiMessageToSend);
}

To turn off the specific note again, the same code is used in the eventhandler Key_PointerReleased. Only this time creating a Note Off message to send to the MIDI output device.

/// <summary>
/// Eventhandler for key released at the keyboard.
/// The specific key is extracted and the according pitch is found.
/// A MIDI message Note Off is created from the channel field and velocity field.
/// The Note Off message is send to the MIDI output device
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Key_PointerReleased(object sender, PointerRoutedEventArgs e)
{
    // Field to hold the pitch of the note
    byte note;

    // Extract the key being pressed
    Rectangle keyPressed = (Rectangle)sender;

    // Get the name of the key and store it in a string, keyPressedName
    string keyPressedName = keyPressed.Name;

    // Switch/Case to set the pitch depending of the key pressed
    switch (keyPressedName)
    {
        case "CKey":
            note = 60;
            break;
        case "CSharpKey":
            note = 61;
            break;
        case "DKey":
            note = 62;
            break;
        case "DSharpKey":
            note = 63;
            break;
        case "EKey":
            note = 64;
            break;
        default:
            note = 60;
            break;
    }

    // Create the Note Off message to send to the MIDI output device
    IMidiMessage midiMessageToSend = new MidiNoteOffMessage(channel, note, velocity);

    // Send the Note Off MIDI message to the midiOutPort
    midiOutPort.SendMessage(midiMessageToSend);
}

If the applictation is made to run in Visual Studio 2015, the MIDI keyboard should now be functionally, both for computers using mouse or touchpad and for devices using touch screen.

The MIDI device is able to play more than one note at the time with different pitch. Therefore the keyboard created will be able to play polyphonic sounds. Just press more than one key at the time to create harmonies.

It would be more useful to be able to select different instruments. To do so, in the XAML page, create a StackPanel in the main grid in the third row. The StackPanel should have a horizontal orientation. Inside the StackPanel, create four buttons to be able to select different instruments. Each buttons should call the same eventhandler, when clicked.

The additional code in the XAML page could look like:

...
        <StackPanel Grid.Row="2"
                    Orientation="Horizontal"
                    Margin="20">
            
            <Button Name="Piano"
                    Margin="0,0,10,0"
                    Content="Piano"
                    Click="Selection_Click"/>

            <Button Name="Trombone"
                    Margin="0,0,10,0"
                    Content="Trombone"
                    Click="Selection_Click"/>

            <Button Name="Trumpet"
                    Margin="0,0,10,0"
                    Content="Trumpet"
                    Click="Selection_Click"/>

            <Button Name="Flute"
                    Margin="0,0,10,0"
                    Content="Flute"
                    Click="Selection_Click"/>

        </StackPanel>
...

Before the Selection_Click eventhandler is created, some theory of instruments has to be informed. Most instruments have a natual interval of frequency, where it is useful. The interval depends upon the physical shape of the part of the instrument, that amplifies the sound. It does not sound good to play very high notes on a bass. Likewise, you will never play a low note on a picolo flute. The piano is special, because of the wide range of frequecy, but in this example it is set to be the same as the trumpet.

In the present example the Trombone has a frequency range, one octave lower than the trumpet and piano. The frequency range of the flute is one octave higher than the trumpet and piano.

One octave in a MIDI device is equal to 12 steps in value of the pitch. To contol the octave interval an integer is defined in the top of the page in the code-behind.

...
    public sealed partial class MainPage : Page
    {
        // MidiOutPort of the output MIDI device
        IMidiOutPort midiOutPort;

        // MIDI message to change the instument
        IMidiMessage instrumentChange;

        // Integer to define the frequecy interval of an instrument
        int octaveInterval = 0;

        // The MIDI channel used for the MIDI output device
        byte channel = 0;

        // The MIDI velocity used for the MIDI output device
        byte velocity = 127;

        public MainPage()
        {
...

In the button eventhandler Selection_Click, create a button element to get the name of the selected button. The name of the button will be use to select a new MIDI instrument number and frequency interval using a switch/case. When the instrument number is selected, a new Program Change message is created and sent to the MIDI output device.

The Selection_Click() eventhandler should look like:

/// <summary>
/// Eventhandler for the buttons that select the instrument being played.
/// Each buttons has a different name, that will be used to get a different value of MIDI instrument using a Switch/Case.
/// A Program Change message is created from that and the MIDI channel and the message is sent to the MIDI output device.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Selection_Click(object sender, RoutedEventArgs e)
{
    // Define a byte to hold the MIDI instrument number to be used in the Program Change MIDI message
    byte midiInstrument;

    // Create a Button element to get the pressed instrument button
    Button instrumentNameButton = (Button)sender;

    // Create a string to hold the name of the pressed button to be used in a switch/case
    string instrumentName = instrumentNameButton.Name;

    // Depending on the name of the button pressed, set a new MIDI instrument and frequency interval for the instrument
    switch (instrumentName)
    {
        case "Piano":
            // Set the MIDI instrument number to 0, Piano
            midiInstrument = 0;

            // Set the octaveInterval to 0 to set the frequency interval around the middle C
            octaveInterval = 0;
            break;
        case "Trombone":
            // Set the MIDI instrument number to 57, Trombone (58-1)
            midiInstrument = 57;

            // Set the octaveInterval to -1 to set the frequency interval around one octave lower than the middle C
            octaveInterval = -1;
            break;
        case "Trumpet":
            // Set the MIDI instrument number to 56, Trumpet (57-1)
            midiInstrument = 56;

            // Set the octaveInterval to 0 to set the frequency interval around the middle C
            octaveInterval = 0;
            break;
        case "Flute":
            // Set the MIDI instrument number to 73, Trumpet (74-1)
            midiInstrument = 73;

            // Set the octaveInterval to 1 to set the frequency interval around one octave higher than the middle C
            octaveInterval = 1;
            break;

        // Default value will be equal to piano
        default:
            // Set the MIDI instrument number to 0
            midiInstrument = 0;

            // Set the octaveInterval to 0 to set the frequency interval around the middle C
            octaveInterval = 0;
            break;
    }

    // Create the new Program Change message with the new selected instrument
    instrumentChange = new MidiProgramChangeMessage(channel, midiInstrument);

    // Send the Program Change midi message to port of the output midi device.
    midiOutPort.SendMessage(instrumentChange);
}

The final step is to let the pitch of the played note be depended of the instrument frequency interval set in the eventhandler of the buttons to select instrument. This is done by adding a part to the picth selection in the switch/case in Key_PointerPressed and Key_PointerReleased eventhandler. The part is simply '12 * octaveInterval' to either raise or lower the frequency interval by an octave.

The Key_PointerPressed eventhandler now should look like:

...
            // Extract the key being pressed
            Rectangle keyPressed = (Rectangle)sender;

            // Get the name of the key and store it in a string, keyPressedName
            string keyPressedName = keyPressed.Name;

            // Switch/Case to set the pitch depending of the key pressed
            switch (keyPressedName)
            {
                case "CKey":
                    note = (byte)(60 + (octaveInterval * 12));
                    break;
                case "CSharpKey":
                    note = (byte)(61 + (octaveInterval * 12));
                    break;
                case "DKey":
                    note = (byte)(62 + (octaveInterval * 12));
                    break;
                case "DSharpKey":
                    note = (byte)(63 + (octaveInterval * 12));
                    break;
                case "EKey":
                    note = (byte)(64 + (octaveInterval * 12));
                    break;
                default:
                    note = (byte)(60 + (octaveInterval * 12));
                    break;
            }

            // Create the Note On message to send to the MIDI output device
            IMidiMessage midiMessageToSend = new MidiNoteOnMessage(channel, note, velocity);
...

Likewise the Key_PointerReleased eventhandler should look like:

...
            // Extract the key being pressed
            Rectangle keyPressed = (Rectangle)sender;

            // Get the name of the key and store it in a string, keyPressedName
            string keyPressedName = keyPressed.Name;

            // Switch/Case to set the pitch depending of the key pressed
            switch (keyPressedName)
            {
                case "CKey":
                    note = (byte)(60 + (octaveInterval * 12));
                    break;
                case "CSharpKey":
                    note = (byte)(61 + (octaveInterval * 12));
                    break;
                case "DKey":
                    note = (byte)(62 + (octaveInterval * 12));
                    break;
                case "DSharpKey":
                    note = (byte)(63 + (octaveInterval * 12));
                    break;
                case "EKey":
                    note = (byte)(64 + (octaveInterval * 12));
                    break;
                default:
                    note = (byte)(60 + (octaveInterval * 12));
                    break;
            }

            // Create the Note Off message to send to the MIDI output device
            IMidiMessage midiMessageToSend = new MidiNoteOffMessage(channel, note, velocity);
...

Now the simple MIDI software keyboard example is finished.

Points of Interest

A thing to remember from this project is, how simple and easy it is to use the MIDI format and MIDI devices. This was a bit scary to me, before I started creating a MIDI software keyboard. A way ahead for a new project will be to make an application that can create MIDI melodies with more than one channel in use, and to be able to play then also. This seems a bit more complicated, but hopefully it will turn out to be as easy as creating Note On/Note Off message and send them to a MIDI output device.

History

  • Creation of the initial article and demo source code 20170228

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student Student
Denmark Denmark
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --