Skip to content

Instantly share code, notes, and snippets.

@atsushieno
Created May 31, 2009 09:01
Show Gist options
  • Save atsushieno/120822 to your computer and use it in GitHub Desktop.
Save atsushieno/120822 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
#if Moonlight
using MidiOutput = System.IntPtr;
using System.Windows;
#else
using PortMidiSharp;
using Timer = System.Timers.Timer;
#endif
namespace Commons.Music.Midi
{
public abstract class TimerWrapper
{
public abstract void SetNextWait (int milliseconds);
public event EventHandler Tick;
protected void OnTick ()
{
if (Tick != null)
Tick (null, null);
}
}
#if Moonlight
public class MoonlightTimerWrapper : TimerWrapper
{
public MoonlightTimerWrapper ()
{
timer = new DispatcherTimer ();
timer.Tick += delegate {
timer.Stop ();
OnTick ();
};
}
DispatcherTimer timer;
public override void SetNextWait (int milliseconds)
{
timer.Interval = (double) milliseconds;
timer.Start ();
}
}
#else
public class MonoTimerWrapper : TimerWrapper
{
public MonoTimerWrapper ()
{
timer = new Timer () { AutoReset = false };
timer.Elapsed += delegate {
OnTick ();
};
}
Timer timer;
public override void SetNextWait (int milliseconds)
{
timer.Interval = (double) milliseconds;
timer.Start ();
}
}
#endif
public class Driver
{
public static void Main (string [] args)
{
#if Moonlight
var output = IntPtr.Zero;
#else
var output = MidiDeviceManager.OpenOutput (MidiDeviceManager.DefaultOutputDeviceID);
#endif
foreach (var arg in args) {
var parser = new SmfReader (File.OpenRead (arg));
parser.Parse ();
#if true
/* // test reader/writer sanity
using (var outfile = File.Create ("testtest.mid")) {
var data = parser.Music;
var gen = new SmfWriter (outfile);
gen.WriteHeader (data.Format, (short)data.Tracks.Count, data.DeltaTimeSpec);
foreach (var tr in data.Tracks)
gen.WriteTrack (tr);
}
*/
// test merger/splitter
/*
var merged = SmfTrackMerger.Merge (parser.Music);
// var result = merged;
var result = SmfTrackSplitter.Split (merged.Tracks [0].Events, parser.Music.DeltaTimeSpec);
using (var outfile = File.Create ("testtest.mid")) {
var gen = new SmfWriter (outfile);
gen.DisableRunningStatus = true;
gen.WriteHeader (result.Format, (short)result.Tracks.Count, result.DeltaTimeSpec);
foreach (var tr in result.Tracks)
gen.WriteTrack (tr);
}
*/
var player = new PortMidiSyncPlayer (output, parser.Music);
player.PlayerLoop ();
#else
var player = new PortMidiPlayer (output, parser.MusicData);
player.PlayAsync ();
Console.WriteLine ("empty line to quit, P to pause");
while (true) {
string line = Console.ReadLine ();
if (line == "p") {
if (player.State == PlayerState.Playing)
player.PauseAsync ();
else
player.PlayAsync ();
}
else if (line == "") {
player.Dispose ();
break;
}
}
#endif
}
}
}
public enum PlayerState
{
Stopped,
Playing,
Paused
}
// Player implementation. Plays a MIDI song synchronously.
public class PortMidiSyncPlayer : IDisposable
{
public PortMidiSyncPlayer (MidiOutput output, SmfMusic music)
{
if (output == null)
throw new ArgumentNullException ("output");
if (music == null)
throw new ArgumentNullException ("music");
this.output = output;
this.music = music;
events = SmfTrackMerger.Merge (music).Tracks [0].Events;
}
MidiOutput output;
SmfMusic music;
IList<SmfEvent> events;
ManualResetEvent pause_handle = new ManualResetEvent (true);
bool pause, stop;
public int PlayDeltaTime { get; set; }
public void Dispose ()
{
#if Moonlight
#else
output.Close ();
#endif
}
public void Play ()
{
if (pause_handle != null) {
pause_handle.Set ();
pause_handle = null;
}
}
public void Pause ()
{
pause = true;
}
int event_idx = 0;
public void PlayerLoop ()
{
while (true) {
pause_handle.WaitOne ();
if (stop)
break;
if (pause) {
pause_handle.Reset ();
pause = false;
continue;
}
if (event_idx == events.Count)
break;
HandleEvent (events [event_idx++]);
}
}
int current_tempo = 500000; // dummy
int tempo_ratio = 1;
int GetDeltaTimeInMilliseconds (int deltaTime)
{
if (music.DeltaTimeSpec >= 0x80)
throw new NotSupportedException ();
return (int) (current_tempo / 1000 * deltaTime / music.DeltaTimeSpec / tempo_ratio);
}
string ToBinHexString (byte [] bytes)
{
string s = "";
foreach (byte b in bytes)
s += String.Format ("{0:X02} ", b);
return s;
}
public virtual void HandleEvent (SmfEvent e)
{
if (e.DeltaTime != 0) {
var ms = GetDeltaTimeInMilliseconds (e.DeltaTime);
Thread.Sleep (ms);
}
if (e.Message.StatusByte == 0xFF && e.Message.Msb == 0x51)
current_tempo = (e.Message.Data [0] << 16) + (e.Message.Data [1] << 8) + e.Message.Data [2];
OnMessage (e);
PlayDeltaTime += e.DeltaTime;
}
void WriteSysEx (byte status, byte [] sysex)
{
var buf = new byte [sysex.Length + 1];
buf [0] = status;
Array.Copy (sysex, 0, buf, 1, buf.Length - 1);
output.WriteSysEx (0, buf);
}
protected virtual void OnMessage (SmfEvent e)
{
if ((e.Message.Value & 0xFF) == 0xF0)
WriteSysEx (0xF0, e.Message.Data);
else if ((e.Message.Value & 0xFF) == 0xF7)
WriteSysEx (0xF7, e.Message.Data);
else if ((e.Message.Value & 0xFF) == 0xFF)
return; // meta. Nothing to send.
else
#if Moonlight
;
#else
output.Write (0, new MidiMessage (e.Message.StatusByte, e.Message.Msb, e.Message.Lsb));
#endif
}
public void Stop ()
{
if (pause_handle != null)
pause_handle.Set ();
stop = true;
}
}
// Provides asynchronous player control.
public class PortMidiPlayer : IDisposable
{
PortMidiSyncPlayer player;
Thread sync_player_thread;
public PortMidiPlayer (MidiOutput output, SmfMusic music)
{
player = new PortMidiSyncPlayer (output, music);
sync_player_thread = new Thread (new ThreadStart (delegate { player.Play (); }));
State = PlayerState.Stopped;
}
public PlayerState State { get; set; }
public void Dispose ()
{
player.Dispose ();
if (sync_player_thread.ThreadState == ThreadState.Running)
sync_player_thread.Abort ();
}
public void PlayAsync ()
{
switch (State) {
case PlayerState.Playing:
return; // do nothing
case PlayerState.Paused:
player.Play ();
State = PlayerState.Playing;
return;
case PlayerState.Stopped:
player.Play ();
return;
}
}
public void PauseAsync ()
{
switch (State) {
case PlayerState.Playing:
player.Pause ();
State = PlayerState.Paused;
return;
default: // do nothing
return;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment