2017年5月4日木曜日

C#でMIDI その4

 C#でMIDIの続きです。

 今回は寄り道で、MIDIファイルの読み込みをしてみます。
 MIDIファイルの仕様は、なかなか面倒です。Big EndianとかRunning StatusとかVariable Lengthとかとか。そこら辺を知りたい方は、MIDIの仕様を調べてみてください。

 今回のプログラムのポイントは2つです。
 1つは、3種類のMIDIイベントをオブジェクト指向っぽくクラス化しています。
 もう1つは、個数が分からないMIDIイベントをメモリに格納するため、自動的にメモリを増やしてくれる配列を使っています。C#のCollectionを使うという手もあるのですが、わざわざ自分で書いているのは気分です。

 おまけで、テスト用にSampleMidi.midというのを作っています。

 いろいろ適当ですが、趣味のプログラムなので、ご了承ください。



using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;

class Program
{
[STAThread]

static void Main()
{
Application.Run( new FormMidi());
}
}

/*--------------------------------------------------*/
/* FormMidi Class */
/*--------------------------------------------------*/
class FormMidi : Form
{
private Label lbl;
private Button btn;

/*--------------------------------------------------*/
/* FormMidi */
/*--------------------------------------------------*/
public FormMidi()
{
this.Text = "Midi";
this.ClientSize = new Size( 240, 120);
this.FormBorderStyle = FormBorderStyle.FixedSingle;

this.Load += new EventHandler( this.FormMidi_Load);
this.Closed += new EventHandler( this.FormMidi_Closed);

/*--------------------------------------------------*/
this.AllowDrop = true;
this.DragEnter += new DragEventHandler( this.FormMidi_DragEnter);
this.DragDrop += new DragEventHandler( this.FormMidi_DragDrop);

/*--------------------------------------------------*/
this.lbl = new Label();
this.lbl.SetBounds( 20, 10, 200, 60);
    this.lbl.Font = new Font( "Arial", 10);
this.lbl.Text = "File : " + "\r\n";
this.lbl.Text += "Track : " + "\r\n";
this.lbl.Text += "Event : " + "\r\n";
this.lbl.Text += "TimeDivision : " + "\r\n";
//this.lbl.BorderStyle = BorderStyle.FixedSingle;
this.Controls.Add( this.lbl);

/*--------------------------------------------------*/
this.btn = new Button();
this.btn.SetBounds( 40, 80, 160, 30);
    this.btn.Font = new Font( "Arial", 10);
this.btn.Text = "Open";
this.btn.Click += new EventHandler( this.btn_Click);
this.Controls.Add( this.btn);
}

/*--------------------------------------------------*/
/* FormMidi_Load */
/*--------------------------------------------------*/
private void FormMidi_Load( object sender, EventArgs e)
{
this.SampleMidi();

string[] args = Environment.GetCommandLineArgs();

if( 1 < args.Length)
{
this.OpenFile( args[1]);
}
}

/*--------------------------------------------------*/
/* FormMidi_Closed */
/*--------------------------------------------------*/
private void FormMidi_Closed( object sender, EventArgs e)
{
}

/*--------------------------------------------------*/
/* DragEnter */
/*--------------------------------------------------*/
private void FormMidi_DragEnter( object sender, DragEventArgs e)
{
if( e.Data.GetDataPresent( DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Move;
}
}

/*--------------------------------------------------*/
/* DragDrop */
/*--------------------------------------------------*/
private void FormMidi_DragDrop( object sender, DragEventArgs e)
{
string[] filename = (string[]) e.Data.GetData( DataFormats.FileDrop, false);
this.OpenFile( filename[0]);
}

/*--------------------------------------------------*/
/* btn_Click */
/*--------------------------------------------------*/
private void btn_Click( object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "midi file|*.mid|*|*.*";

if( ofd.ShowDialog() == DialogResult.OK)
{
this.OpenFile( ofd.FileName);
}
}

/*--------------------------------------------------*/
/* OpenFile */
/*--------------------------------------------------*/
private void OpenFile( string filename)
{
if( Path.GetExtension( filename) != ".mid")
{
return;
}

Console.WriteLine( "----- OpenFile -----");

int temp;
MidiEventArray[] Track;
uint TimeDivision;

/*--------------------------------------------------*/
FileStream ifs = new FileStream( filename, FileMode.Open, FileAccess.Read);

byte[] hc = new byte[14];
ifs.Read( hc, 0, hc.Length);

temp = ( hc[10] << 8) + hc[11];
Track = new MidiEventArray[temp];

temp = ( hc[12] << 8) + hc[13];
TimeDivision = (uint) temp;

for( int i = 0; i < Track.Length; i++)
{
byte[] tc = new byte[8];
ifs.Read( tc, 0, tc.Length);

temp = ( tc[4] << 24) + ( tc[5] << 16) + (tc[6] << 8) + tc[7];
byte[] data = new byte[temp];
ifs.Read( data, 0, data.Length);

Track[i] = new MidiEventArray();
Track[i].FromByte( data);
}

ifs.Close();

/*--------------------------------------------------*/
temp = 0;

for( int i = 0; i < Track.Length; i++)
{
temp += Track[i].Count;
}

this.lbl.Text = "File : " + Path.GetFileName( filename) + "\r\n";
this.lbl.Text += "Track : " + Track.Length + "\r\n";
this.lbl.Text += "Event : " + temp + "\r\n";
this.lbl.Text += "TimeDivision : " + TimeDivision + "\r\n";

for( int i = 0; i < Track.Length; i++)
{
for( int j = 0; j < Track[i].Count; j++)
{
Console.Write( i.ToString() + "\t");
Console.Write( j.ToString() + "\t");
Console.Write( Track[i].Event[j].Time.ToString() + "\t");
Console.Write( Track[i].Event[j].Event.ToString() + "\t");
Console.WriteLine();
}
}
}

/*--------------------------------------------------*/
/* SampleMidi */
/*--------------------------------------------------*/
private void SampleMidi()
{
string path = Application.ExecutablePath;
path = Path.GetDirectoryName( path);

FileStream ofs = new FileStream( path + @"\SampleMidi.mid", FileMode.Create, FileAccess.Write);

/*--------------------------------------------------*/
byte[] hc = new byte[14];

hc[0] = (byte) 'M';
hc[1] = (byte) 'T';
hc[2] = (byte) 'h';
hc[3] = (byte) 'd';
hc[4] = (byte) 0;
hc[5] = (byte) 0;
hc[6] = (byte) 0;
hc[7] = (byte) 6;
hc[8] = (byte) 0;
hc[9] = (byte) 0;
hc[10] = (byte) 0;
hc[11] = (byte) 1;
hc[12] = (byte) 0;
hc[13] = (byte) 96;

/*--------------------------------------------------*/
byte[] tc = new byte[8];

tc[0] = (byte) 'M';
tc[1] = (byte) 'T';
tc[2] = (byte) 'r';
tc[3] = (byte) 'k';

byte[] data = new byte[68];
byte[] keys = new byte[8];

keys[0] = (byte) 60;
keys[1] = (byte) 62;
keys[2] = (byte) 64;
keys[3] = (byte) 65;
keys[4] = (byte) 67;
keys[5] = (byte) 69;
keys[6] = (byte) 71;
keys[7] = (byte) 72;

for( int i = 0; i < 8; i++)
{
data[8 * i + 0] = (byte) 0;
data[8 * i + 1] = (byte) 0x90;
data[8 * i + 2] = (byte) keys[i];
data[8 * i + 3] = (byte) 127;

data[8 * i + 4] = (byte) 96;
data[8 * i + 5] = (byte) 0x80;
data[8 * i + 6] = (byte) keys[i];
data[8 * i + 7] = (byte) 127;
}

data[64] = (byte) 0;
data[65] = (byte) 0xff;
data[66] = (byte) 0x2f;
data[67] = (byte) 0;

tc[4] = (byte) ( ( data.Length >> 24) & 0xff);
tc[5] = (byte) ( ( data.Length >> 16) & 0xff);
tc[6] = (byte) ( ( data.Length >> 8) & 0xff);
tc[7] = (byte) ( data.Length & 0xff);

/*--------------------------------------------------*/
ofs.Write( hc, 0, hc.Length);
ofs.Write( tc, 0, tc.Length);
ofs.Write( data, 0, data.Length);
ofs.Close();
}
}



/*--------------------------------------------------*/
/* MidiEvent Class */
/*--------------------------------------------------*/
public class MidiEvent
{
public uint Time; // DeltaTime [tick]
public byte Event; // 0x80 - 0xff
}

/*--------------------------------------------------*/
/* ChannelEvent Class */
/*--------------------------------------------------*/
public class ChannelEvent : MidiEvent
{
public byte Param1; // 0x00 - 0x7f
public byte Param2; // 0x00 - 0x7f

public ChannelEvent( uint tm, byte evt, byte p1, byte p2)
{
this.Time = tm;
this.Event = evt;
this.Param1 = p1;
this.Param2 = p2;
}
}

/*--------------------------------------------------*/
/* SystemEvent Class */
/*--------------------------------------------------*/
public class SystemEvent : MidiEvent
{
public byte[] Data;

public SystemEvent( uint tm, byte evt, byte[] dt)
{
this.Time = tm;
this.Event = evt;
this.Data = dt;
}
}

/*--------------------------------------------------*/
/* MetaEvent Class */
/*--------------------------------------------------*/
public class MetaEvent : MidiEvent
{
public byte Type;
public byte[] Data;

public MetaEvent( uint tm, byte evt, byte tp, byte[] dt)
{
this.Time = tm;
this.Event = evt;
this.Type = tp;
this.Data = dt;
}
}



/*--------------------------------------------------*/
/* MidiEventArray Class */
/*--------------------------------------------------*/
public class MidiEventArray
{
public MidiEvent[] Event;
public int Count;

/*--------------------------------------------------*/
/* MidiEventArray */
/*--------------------------------------------------*/
public MidiEventArray()
{
this.Event = new MidiEvent[256];
this.Count = 0;
}

/*--------------------------------------------------*/
/* Add */
/*--------------------------------------------------*/
public void Add( MidiEvent evt)
{
if( this.Count == this.Event.Length - 1)
{
this.MemoryAllocate();
}

this.Event[this.Count] = evt;
this.Count++;
}

/*--------------------------------------------------*/
/* MemoryAllocate */
/*--------------------------------------------------*/
public void MemoryAllocate()
{
MidiEvent[] temp = new MidiEvent[this.Event.Length * 2];

for( int i = 0; i < this.Event.Length; i++)
{
temp[i] = this.Event[i];
}

this.Event = temp;
}

/*--------------------------------------------------*/
/* ReadVariableLength */
/*--------------------------------------------------*/
private static uint ReadVariableLength( byte[] data, ref int pos)
{
uint ret = (uint) ( data[pos] & 0x7f);
pos++;

while( 0x80 <= data[pos - 1])
{
ret = ( ret << 7) + (uint) ( data[pos] & 0x7f);
pos++;
}

return ret;
}

/*--------------------------------------------------*/
/* FromByte */
/*--------------------------------------------------*/
public void FromByte( byte[] data)
{
uint tm = 0;
byte evt = 0;
byte p1 = 0;
byte p2 = 0;
byte tp = 0;
uint len = 0;
byte[] dt;

int pos = 0;

while( pos < data.Length)
{
/*--------------------------------------------------*/
tm += MidiEventArray.ReadVariableLength( data, ref pos);

/*--------------------------------------------------*/
if( 0x80 <= data[pos])
{
evt = data[pos];
pos++;
}

/*--------------------------------------------------*/
if( evt < 0xf0)
{
if( evt < 0xc0 || 0xe0 <= evt)
{
p1 = data[pos];
p2 = data[pos + 1];
pos += 2;
}
else
{
p1 = data[pos];
p2 = 0;
pos += 1;
}

this.Add( new ChannelEvent( tm, evt, p1, p2));
}
else if( evt == 0xf0 || evt == 0xf7)
{
len = MidiEventArray.ReadVariableLength( data, ref pos);
dt = new byte[len];

for( int i = 0; i < dt.Length; i++)
{
dt[i] = data[pos];
pos++;
}

this.Add( new SystemEvent( tm, evt, dt));
}
else if( evt == 0xff)
{
tp = data[pos];
pos++;

len = MidiEventArray.ReadVariableLength( data, ref pos);
dt = new byte[len];

for( int i = 0; i < dt.Length; i++)
{
dt[i] = data[pos];
pos++;
}

this.Add( new MetaEvent( tm, evt, tp, dt));
}

/*--------------------------------------------------*/
}
}
}

C#でMIDI その3

 C#でMIDIの続きです。

 前回はフォームバージョンにしてみました。
 実行してみると分かりますが、音を鳴らしている間は、フォームが動きません。これは、フォームの処理と音を鳴らす処理が片方ずつ処理されているからです。

 というわけで、今回は、マルチスレッド化して、音を鳴らしている間もフォームが動かせるようにしてみます。マルチスレッドについては、適当にインターネットで調べてみてください。

 マルチスレッドのやり方も複数あるみたいですが、今回はスタンダードっぽいやり方をしてみました。本当はもっといいやり方があるかもしれません。そこは趣味のプログラムということで、ご勘弁。


using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;

class Program
{
[STAThread]

static void Main()
{
Application.Run( new FormMidi());
}
}

class FormMidi : Form
{
/*--------------------------------------------------*/
[DllImport( "Winmm.dll")]
extern static uint midiOutGetNumDevs();

[DllImport( "Winmm.dll")]
extern static uint midiOutOpen( ref long lphmo, uint uDeviceID, uint dwCallback, uint dwCallbackInstance, uint dwFlags);

[DllImport( "Winmm.dll")]
extern static uint midiOutClose( long hmo);

[DllImport( "Winmm.dll")]
extern static uint midiOutShortMsg( long hmo, uint dwMsg);

[DllImport( "Winmm.dll")]
extern static uint midiOutReset( long hmo);

private const uint MMSYSERR_NOERROR = 0;

private const uint MMSYSERR_BADDEVICEID = 2;
private const uint MMSYSERR_ALLOCATED = 4;
private const uint MMSYSERR_NOMEM = 7;
private const uint MMSYSERR_INVALPARAM = 11;
private const uint MMSYSERR_NODEVICE = 68;

private const uint MMSYSERR_INVALHANDLE = 5;
private const uint MIDIERR_STILLPLAYING = 65;

private const uint MIDI_MAPPER = 0xffffffff;

/*--------------------------------------------------*/

Thread thread;
long hMidi;

Button btn_play;
Button btn_stop;

public FormMidi()
{
this.ClientSize = new Size( 240, 120);

this.Text = "Midi";
this.Load += new EventHandler( this.FormMidi_Load);
this.Closed += new EventHandler( this.FormMidi_Closed);

this.btn_play = new Button();
this.btn_play.SetBounds( 20, 40, 90, 40);
    this.btn_play.Font = new Font( "Arial", 12);
this.btn_play.Text = "Play";
this.btn_play.Click += new EventHandler( this.btn_play_Click);
this.Controls.Add( this.btn_play);

this.btn_stop = new Button();
this.btn_stop.SetBounds( 130, 40, 90, 40);
    this.btn_stop.Font = new Font( "Arial", 12);
this.btn_stop.Text = "Stop";
this.btn_stop.Click += new EventHandler( this.btn_stop_Click);
this.Controls.Add( this.btn_stop);
}

/*--------------------------------------------------*/
/* Load */
/*--------------------------------------------------*/
private void FormMidi_Load( object sender, EventArgs e)
{
Console.WriteLine( "FormMidi_Load");

ThreadStart ts = new ThreadStart( this.Play);
this.thread = new Thread( ts);
}

/*--------------------------------------------------*/
/* Closed */
/*--------------------------------------------------*/
private void FormMidi_Closed( object sender, EventArgs e)
{
Console.WriteLine( "FormMidi_Closed");

if( this.thread.IsAlive == true)
{
this.PlayStop();
}
}

/*--------------------------------------------------*/
/* Clicked */
/*--------------------------------------------------*/
private void btn_play_Click( object sender, EventArgs e)
{
Console.WriteLine( "btn_play_Click");

if( this.thread.IsAlive == false)
{
this.PlayStart();
}
}

private void btn_stop_Click( object sender, EventArgs e)
{
Console.WriteLine( "btn_stop_Click");

if( this.thread.IsAlive == true)
{
this.PlayStop();
}
}

/*--------------------------------------------------*/
/* PlayStart */
/*--------------------------------------------------*/
private void PlayStart()
{
if( midiOutOpen( ref this.hMidi, MIDI_MAPPER, 0, 0, 0) != MMSYSERR_NOERROR)
{
MessageBox.Show( "midiOutOpen error");
return;
}

ThreadStart ts = new ThreadStart( this.Play);
this.thread = new Thread( ts);
this.thread.IsBackground = true;
this.thread.Start();
}

/*--------------------------------------------------*/
/* Play */
/*--------------------------------------------------*/
private void Play()
{
uint msg;
byte[] keys = new byte[8];

keys[0] = (byte) 60;
keys[1] = (byte) 62;
keys[2] = (byte) 64;
keys[3] = (byte) 65;
keys[4] = (byte) 67;
keys[5] = (byte) 69;
keys[6] = (byte) 71;
keys[7] = (byte) 72;

for( int i = 0; i < keys.Length; i++)
{
Console.WriteLine( "note on " + "\t" + keys[i]);
msg = (uint) ( ( 0x7f << 16) + ( keys[i] << 8) + 0x90);
midiOutShortMsg( this.hMidi, msg);

Thread.Sleep( 500);

Console.WriteLine( "note off" + "\t" + keys[i]);
msg = (uint) ( ( 0x7f << 16) + ( keys[i] << 8) + 0x80);
midiOutShortMsg( this.hMidi, msg);
}

midiOutReset( this.hMidi);
midiOutClose( this.hMidi);
}

/*--------------------------------------------------*/
/* PlayStop */
/*--------------------------------------------------*/
private void PlayStop()
{
this.thread.Abort();

midiOutReset( this.hMidi);
midiOutClose( this.hMidi);
}
}

C#でMIDI その2

 C#でMIDIの続きです。

 前回は音を鳴らすだけでした。今回はWindowsっぽくフォームにしてみます。

 今回のフォームにはボタンが1つだけです。ユーザーインターフェースは非常に大事ですが、ソフトの機能が決まらないとユーザーインターフェースも決められません。
 実のところ、このプログラムの最終仕様はまだ決まっていません。というわけで、今のところ、ユーザーインターフェースも適当です。ご勘弁。


using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;

class Program
{
[STAThread]

static void Main()
{
Application.Run( new FormMidi());
}
}

class FormMidi : Form
{
/*--------------------------------------------------*/
[DllImport( "Winmm.dll")]
extern static uint midiOutGetNumDevs();

[DllImport( "Winmm.dll")]
extern static uint midiOutOpen( ref long lphmo, uint uDeviceID, uint dwCallback, uint dwCallbackInstance, uint dwFlags);

[DllImport( "Winmm.dll")]
extern static uint midiOutClose( long hmo);

[DllImport( "Winmm.dll")]
extern static uint midiOutShortMsg( long hmo, uint dwMsg);

[DllImport( "Winmm.dll")]
extern static uint midiOutReset( long hmo);

[DllImport( "Kernel32.dll")]
extern static void Sleep( uint dwMilliseconds);

private const uint MMSYSERR_NOERROR = 0;

private const uint MMSYSERR_BADDEVICEID = 2;
private const uint MMSYSERR_ALLOCATED = 4;
private const uint MMSYSERR_NOMEM = 7;
private const uint MMSYSERR_INVALPARAM = 11;
private const uint MMSYSERR_NODEVICE = 68;

private const uint MMSYSERR_INVALHANDLE = 5;
private const uint MIDIERR_STILLPLAYING = 65;

private const uint MIDI_MAPPER = 0xffffffff;

/*--------------------------------------------------*/

long hMidi;

Button btn;

/*--------------------------------------------------*/
/* FormMidi */
/*--------------------------------------------------*/
public FormMidi()
{
this.ClientSize = new Size( 240, 120);

this.Text = "Midi";
this.Load += new EventHandler( this.FormMidi_Load);
this.Closed += new EventHandler( this.FormMidi_Closed);

this.btn = new Button();
this.btn.SetBounds( 40, 40, 160, 40);
    this.btn.Font = new Font( "Arial", 12);
this.btn.Text = "START";
this.btn.Click += new EventHandler( this.btn_Click);
this.Controls.Add( this.btn);
}

/*--------------------------------------------------*/
/* FormMidi_Load */
/*--------------------------------------------------*/
private void FormMidi_Load( object sender, EventArgs e)
{
Console.WriteLine( "FormMidi_Load");
}

/*--------------------------------------------------*/
/* FormMidi_Closed */
/*--------------------------------------------------*/
private void FormMidi_Closed( object sender, EventArgs e)
{
Console.WriteLine( "FormMidi_Closed");
}

/*--------------------------------------------------*/
/* Click */
/*--------------------------------------------------*/
private void btn_Click( object sender, EventArgs e)
{
uint msg;
byte[] keys = new byte[8];

if( midiOutOpen( ref this.hMidi, MIDI_MAPPER, 0, 0, 0) != MMSYSERR_NOERROR)
{
MessageBox.Show( "midiOutOpen error");
return;
}

keys[0] = (byte) 60;
keys[1] = (byte) 62;
keys[2] = (byte) 64;
keys[3] = (byte) 65;
keys[4] = (byte) 67;
keys[5] = (byte) 69;
keys[6] = (byte) 71;
keys[7] = (byte) 72;

for( int i = 0; i < keys.Length; i++)
{
Console.WriteLine( "note on " + "\t" + keys[i]);
msg = (uint) ( ( 0x7f << 16) + ( keys[i] << 8) + 0x90);
midiOutShortMsg( this.hMidi, msg);

Sleep( 500);

Console.WriteLine( "note off" + "\t" + keys[i]);
msg = (uint) ( ( 0x7f << 16) + ( keys[i] << 8) + 0x80);
midiOutShortMsg( this.hMidi, msg);
}

midiOutClose( this.hMidi);
}
}

C#でMIDI その1

 今回はC#でMIDIの音を鳴らそうと思います。

 以下がソースコードです。細かい内容については、インターネットで調べてください。

 本来はソースコードにはコメントを書くべきですが、簡単なプログラムなので省略します。趣味のプログラミングなのでご勘弁。

------------------------------
2017/5/4
APIの宣言に間違いを発見したため修正。
ハンドル(hmo)をuint(4byte)で宣言してました。64bit OSだとハンドルは8byteです。今までのソースコードだと、4byteの領域に8byteのデータを書いてました。
一応、64bit OS向けに修正しました。

------------------------------

using System;
using System.Runtime.InteropServices;

public static class Program
{
/*--------------------------------------------------*/
[DllImport( "Winmm.dll")]
extern static uint midiOutGetNumDevs();

[DllImport( "Winmm.dll")]
extern static uint midiOutOpen( ref long lphmo, uint uDeviceID, uint dwCallback, uint dwCallbackInstance, uint dwFlags);

[DllImport( "Winmm.dll")]
extern static uint midiOutClose( long hmo);

[DllImport( "Winmm.dll")]
extern static uint midiOutShortMsg( long hmo, uint dwMsg);

[DllImport( "Winmm.dll")]
extern static uint midiOutReset( long hmo);

[DllImport( "Kernel32.dll")]
extern static void Sleep( uint dwMilliseconds);

private const uint MMSYSERR_NOERROR = 0;

private const uint MMSYSERR_BADDEVICEID = 2;
private const uint MMSYSERR_ALLOCATED = 4;
private const uint MMSYSERR_NOMEM = 7;
private const uint MMSYSERR_INVALPARAM = 11;
private const uint MMSYSERR_NODEVICE = 68;

private const uint MMSYSERR_INVALHANDLE = 5;
private const uint MIDIERR_STILLPLAYING = 65;

private const uint MIDI_MAPPER = 0xffffffff;

/*--------------------------------------------------*/
public static void Main()
{
uint ret;
long hMidi = 0;

Console.WriteLine( "----- Midi -----");

/*--------------------------------------------------*/
ret = midiOutGetNumDevs();
Console.WriteLine( "Number of MIDI Device : " + ret);

/*--------------------------------------------------*/
ret = midiOutOpen( ref hMidi, MIDI_MAPPER, 0, 0, 0);
Console.WriteLine( "HMIDIOUT : " + hMidi);

ret = midiOutShortMsg( hMidi, 0x007f3c90);
Console.WriteLine( ret);

Sleep( 1000);

ret = midiOutShortMsg( hMidi, 0x007f3c80);
Console.WriteLine( ret);

ret = midiOutClose( hMidi);

/*--------------------------------------------------*/

Console.ReadLine();
}
}

2017年4月30日日曜日

空と君のあいだにあるアレ

 先日、ヨーロッパへ出張してきました。仕事の合間には街を散策して、「日本と違うなぁ」などと思っていました。

 で、何が違うのかを考えていたのですが、「電線がない」ということに気づきました。ヨーロッパにも色々な都市があると思いますが、私が行った街には電柱、電線がありませんでした。おそらく、地中に埋めてあるのでしょう。建物が密集していないうえに、電線がないので、空が広く感じられました。

 日本に戻ってから、よくよく見てみると、街には電線が張り巡らされています。中には10本以上の電線をつないでいる電柱もあります。電気ケーブル、通信ケーブル、などなどでしょう。

 電線はもちろん現代社会に必要な設備です。
 ですが、電線がない街の景色を想像してみてください。きっと、広い空が見えて、晴れ晴れとした気持ちになれると思います。

 すべての電線を地下にするべきとは言いませんが、少し本数を減らして、整理していくのはいいことかもしれません。
 日本にずっといると、電線を当たり前に思ってしまいますが、そういう当たり前の中にも改善の余地はあるものです。

 たくさん張り巡らされた電線が少しくらい減っても、きっとスズメさんは困りません。

2017年4月23日日曜日

とんでもないタクシー

 人生、色々なことがあるものです。先日、出張の帰りにとんでもないタクシーに乗ってしまいました。

 飛行機で帰るために空港まで行くときのことです。日本国内ですが、そこそこの田舎だったため、ほかの交通手段がなく、飛行機に乗り遅れるのは困るので、少し長距離ですがタクシーで行くことにしました。普通なら40~50分くらいだと思います。

 運転手が、高速道路を使った方がいいと言ったので、高速道路を使ってもらうことにしました。当然、料金所を抜けるのですが、ETCが動作しないというトラブルが発生。
 運転手は、「あれー」と言いつつ、車を後退。
 すぐに、料金所のスタッフさんが、「危険ですから後退しないでチケットを取ってください」とアナウンス。
 ところが、運転手はチケットを取る場所が分かっていない様子。
 怪しい雲行きです。
 
 そんなこんなで、何とか高速道路には入りましたが、何か様子がおかしい。

 また運転手が「あれー」と言いだしたと思ったら、どうやら逆方向に入ってしまったとのこと。仕方ないので、次の料金所で折り返すことになりました。

 高速道路に正しく入れたら、後は道なりです。しばらくして空港近くで高速道路を降りました。
 標識にも空港とあったので、私は、そろそろ着くかなくらいに思っていました。カーナビもついていたので、大丈夫だろうと思っていました。
 が、再び何かがおかしい様子。

 運転手は、なぜか標識とは違う道を進もうとします。
 裏道でもあるのかと思っていたら、そのうち、また運転手が「あれー」と言いだします。どうやら単に道が分かっていない様子。
 標識を見ると、明らかに別方向に進んでしまうので、土地勘のない私も、さすがに道が違うと言いました。

 そんなこんなで、道を探しながら進んでいた時です。車線が合流するポイントで、運転手が、突然、車線に逆方向に進入しました。要は逆走です。私が「えっ」と思った時には時すでに遅く、タクシーは逆走しながら進んでいきます。すぐに正面から別のタクシーがこちらに向かって進んできました。正面衝突の寸前です。
 ダメかと思いましたが、運が良かったのか、何とか衝突はしないですみました。
 そこは、両側に壁がある一車線の道でした。運転手は無理やり進行方向を変えようとします。結局、車の前後を壁にぶつけながらなんとか向きを変えました。

 逆走した道を少し戻ると、正しい折り返し地点があったので、運転手はそこで反対車線に進みました。
 でも、やっぱり道が間違っているので、再び迷子状態に。戻るしかないです。左折して、狭い道に入り、もう一度左折して、少し広い道に出ようとした時です。前の乗用車に接触。普通に事故でした。

 前の車の人は、当然、怒ります。なのに、運転手は事の重大さを理解していない様子。

 結局、前の車の人がやってきて、話をするからということになり、私は別のタクシーを探すはめになりました。


 フライトの時間が迫っていたので、私も自分のことで手一杯です。なんとか近くで別のタクシーを見つけて、私は空港へ向かいました。ちなみに、空港への道は、先ほどのタクシーが逆走した道を、普通に進むのが正解でした。

 料金については、運転手には後で請求するみたいなことを言われたのですが、タクシー会社に連絡したら、払わなくていいとのこと。ちなみにメーターは1万円を超えていました。


 世の中、何が起こるか分からないものです。自分の意志とは無関係に、突然、事件に巻き込まれることもあるのだろうと思います。
 どうしようもないかもしれませんが、気を付けましょう。

2017年4月15日土曜日

サガな話

 先日、YahooのTOPページに「ロマサガ3リメイクが決定」という記事が載っていました。

 ロマサガ3は、私が好きなゲームの1つです。発売から20年以上経っていますが、その人気が今も健在なのはうれしいことです。

 ロマンシングサガのゲームの内容は、YoutubeとかWikipediaとかに詳しく載っているので、そちらをご覧ください。特に楽曲は秀逸なので、おすすめです。

 ところでですが、サガという名前にひっかけた佐賀県とのPRイベントがあります。このWebサイトには、ゲーム風にアレンジされた佐賀県の紹介があります(http://romasaga2.jp/)。ゲームの方を知っていると、佐賀弁に変換されたセリフが面白くて仕方ないです。

 さらに話は変わりますが、ロマンシングサガは舞台化されて、本日から公演が始まるらしいです(http://saga-stage.com/)。
 最近は、漫画が実写映画化されるなんてことがよくありますが、まさか、スーパーファミコンのゲームが舞台化されるとは思いませんでした。元々、ストーリーがしっかりしたゲームなので脚本は面白くなりそうですが、2次元のゲーム画面が舞台化されてどうなるかは想像できません。ファンの評価がどうなるか、気になるところです。

 自分が好きなものを他の人にも好きになってもらいたい、と思うのは人のサガ。リメイク版をきっかけに、ファンが増えてくれたらうれしい限りです。