Skip to content

Instantly share code, notes, and snippets.

@praeclarum
Created May 27, 2014 03:53
Show Gist options
  • Save praeclarum/143cb88ce836e476d701 to your computer and use it in GitHub Desktop.
Save praeclarum/143cb88ce836e476d701 to your computer and use it in GitHub Desktop.
Shows how to play MIDI files and dynamically create songs on iOS using C# (Xamarin)
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using MonoTouch;
using MonoTouch.AudioToolbox;
using MonoTouch.AudioUnit;
using MonoTouch.CoreFoundation;
using MonoTouch.CoreMidi;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace PlayMidi
{
/// <summary>
/// A little class showing how to use iOS's AudioToolbox
/// to synthesize audio dynamically and from MIDI files.
/// </summary>
public class PlayerViewController : UIViewController
{
AUGraph graph;
AudioUnit samplerUnit;
public override async void ViewDidLoad ()
{
base.ViewDidLoad ();
CreateAudioGraph ();
LoadInstrument (1);
// PlayMidiSong ();
await PlayDynamicSong ();
}
void CreateAudioGraph ()
{
graph = new AUGraph ();
var samplerNode = graph.AddNode (AudioComponentDescription.CreateMusicDevice (AudioTypeMusicDevice.Sampler));
var ioNode = graph.AddNode (AudioComponentDescription.CreateOutput (AudioTypeOutput.Remote));
graph.Open ();
graph.ConnnectNodeInput (samplerNode, 0, ioNode, 0);
samplerUnit = graph.GetNodeInfo (samplerNode);
graph.Initialize ();
graph.Start ();
}
void LoadInstrument (int preset)
{
var soundFontPath = NSBundle.MainBundle.PathForResource ("ChoriumRevA", "sf2");
var soundFontUrl = CFUrl.FromFile (soundFontPath);
samplerUnit.LoadInstrument (new SamplerInstrumentData (soundFontUrl, InstrumentType.SF2Preset) {
BankLSB = SamplerInstrumentData.DefaultBankLSB,
BankMSB = SamplerInstrumentData.DefaultMelodicBankMSB,
PresetID = (byte)preset,
});
}
async Task PlayDynamicSong ()
{
var dur = 2000;
var t = 5 * 12 - 2;
var progression = new [] {
t,
t,
t,
t,
t + 5,
t + 5,
t,
t,
t + 7,
t + 5,
t,
t,
};
//
// var progression = new [] {
// t,
// t + 5,
// t + 7,
// t + 7,
// t,
// t + 5,
// t + 7,
// t,
// };
for (;;) {
foreach (var i in progression) {
await Task.WhenAll (
PlayMajor7ChordNotesChords (i, dur),
PlayMajor7ChordNotes (i, dur/8));
// await PlayMinor7ChordNotes (i, dur);
// await Task.Delay (dur);
}
}
}
async Task PlayMajor7ChordNotes (int tone, int duration, int velocity = 127)
{
await PlayNote (tone, duration, velocity*0.6);
await PlayNote (tone+4, duration, velocity*0.65);
await PlayNote (tone+7, duration, velocity*0.7);
await PlayNote (tone+11, duration, velocity*0.75);
await PlayNote (tone+12, duration, velocity * 0.8);
await PlayNote (tone+11, duration, velocity*0.85);
await PlayNote (tone+7, duration, velocity*0.9);
await PlayNote (tone+4, duration, velocity*0.7);
}
async Task PlayMajor7DimChordNotes (int tone, int duration, int velocity = 127)
{
await PlayNote (tone, duration, velocity*0.6);
await PlayNote (tone+4, duration, velocity*0.65);
await PlayNote (tone+7, duration, velocity*0.7);
await PlayNote (tone+10, duration, velocity*0.75);
await PlayNote (tone+12, duration, velocity * 0.8);
await PlayNote (tone+10, duration, velocity*0.85);
await PlayNote (tone+7, duration, velocity*0.9);
await PlayNote (tone+4, duration, velocity*0.7);
}
async Task PlayMajor7ChordNotesChords (int tone, int duration, int velocity = 127)
{
await PlayMajorChord (tone, duration/4, velocity*0.8);
await PlayMajor7Chord (tone, duration*3/4, velocity*0.6);
// await PlayMajor7Chord (tone, duration, velocity*0.6);
// await PlayMajor7Chord (tone, duration, velocity*0.6);
}
async Task PlayMinor7ChordNotes (int tone, int duration, int velocity = 127)
{
await PlayNote (tone, duration, velocity*0.6);
await PlayNote (tone+3, duration, velocity*0.7);
await PlayNote (tone+7, duration, velocity*0.8);
await PlayNote (tone+10, duration, velocity*0.9);
await PlayNote (tone+12, duration, velocity);
await PlayNote (tone+10, duration, velocity*0.9);
await PlayNote (tone+7, duration, velocity*0.8);
await PlayNote (tone+3, duration, velocity*0.7);
}
async Task PlayMajorChord (int tone, int duration, double velocity = 127)
{
await PlayNotes (new [] {
tone,
tone + 4,
tone + 7,
}, duration, velocity);
}
async Task PlayMajor7Chord (int tone, int duration, double velocity = 127)
{
await PlayNotes (new [] {
tone,
tone + 4,
tone + 7,
tone + 10,
}, duration, velocity);
}
async Task PlayMinorChord (int tone, int duration, double velocity = 127)
{
await PlayNotes (new [] {
tone,
tone + 3,
tone + 7,
}, duration, velocity);
}
async Task PlayMinor7Chord (int tone, int duration, int velocity = 127)
{
await PlayNotes (new [] {
tone,
tone + 3,
tone + 7,
tone + 10,
}, duration, velocity);
}
async Task PlayMajorScale (int tone, int duration, int velocity = 127)
{
await PlayNotes (new [] {
tone, // I
// tone + 2, // II
tone + 4, // III
// tone + 5, // IV
tone + 7, // V
// tone + 9, // VI
tone + 11, // VII
tone + 12,
}, duration, velocity);
}
async Task PlayMinorScale (int tone, int duration, int velocity = 127)
{
await PlayNotes (new [] {
tone,
// tone + 2,
tone + 3,
// tone + 5,
tone + 7,
// tone + 8,
// tone + 10,
// tone + 12,
}, duration, velocity);
}
async Task PlayNotes (int[] notes, int duration, double velocity = 127)
{
await Task.WhenAll (notes.Select (x => PlayNote (x, duration, velocity)).ToArray ());
}
async Task PlayNote (int note, int duration, double velocity = 127)
{
var channel = 0;
var status = (9 << 4) | channel;
samplerUnit.MusicDeviceMIDIEvent ((byte)status, (byte)note, (byte)velocity);
await Task.Delay (duration);
}
MidiClient midiClient;
MusicPlayer player;
void PlayMidiSong ()
{
var midiPath = NSBundle.MainBundle.PathForResource ("get_lucky", "mid");
midiClient = new MidiClient ("Midi Client");
MidiError stat;
var midiEndpoint = midiClient.CreateVirtualDestination ("VEnd", out stat);
midiEndpoint.MessageReceived += (sender, e) => HandleMidiPackets(e.Packets);
var s = new MusicSequence ();
s.LoadFile (NSUrl.FromFilename (midiPath), MusicSequenceFileTypeID.Midi);
s.SetMidiEndpoint (midiEndpoint);
player = new MusicPlayer ();
player.MusicSequence = s;
player.Start ();
}
unsafe void HandleMidiPackets (MidiPacket[] packets)
{
foreach (var p in packets) {
var bytes = (byte*)p.Bytes;
var status = bytes [0];
var data1 = p.Length > 1 ? bytes [1] : 0u;
var data2 = p.Length > 2 ? bytes [2] : 0u;
var command = status >> 4;
//
if (command != 0x0F) {
// var channel = status & 0x0F;
var note = (byte)(data1 & 0x7F);
var velocity = (byte)(data2 & 0x7F);
samplerUnit.MusicDeviceMIDIEvent (status, note, velocity);
// Console.WriteLine (channel);
} else {
// var eox = bytes [p.Length - 1];
// Console.WriteLine ("st={0:X2}", status);
MusicDeviceSysEx (samplerUnit.Handle, p.Bytes, p.Length);
}
}
}
[DllImport (Constants.AudioToolboxLibrary)]
static extern AudioUnitStatus MusicDeviceSysEx (IntPtr inUnit, IntPtr inData, uint inLength);
}
}
@praeclarum
Copy link
Author

It's hard to find resources on how to play MIDI files in iOS - the Apple docs are non-existent and online examples are of... less than stellar quality.

So here's my example. It can play MIDI files with one instrument, or you can program it to generate music using await. It's fun! It's set to play a terrible Bb blues at the moment.

This program requires a SoundFont file (basically a data file for the synthesizer) to work. It is currently set to use ChoriumRevA.sf2 from openwrld. Just add the SF2 file to your app's resources.

@RomaRudyak
Copy link

@praeclarum thank you for sample.

Can you add value of Constants.AudioToolboxLibrary for correct P/Invoke configuration of MusicDeviceSysEx method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment