2020年9月13日日曜日

生産性の向上を考える

 高齢化が進む日本では、今後、労働人口が減少するから、生産性の向上が急務だ、なんて意見があります。

 そうなのかもしれません。


 もう少し考えてみます。


 歴史的に生産性が向上した事例がいくつかあります。自動車産業の大量生産はその1つでしょう。大規模な工場に労働を集約することで、生産性が大きく向上しました。

 農業や運送業、小売業でも同じやり方が通用すると思います。つまり、労働を集約することが生産性の向上につながるわけです。結果、少数の労働者が集中的に働き、不要な労働者は排除されます。これにより一人当たりの労働生産性は向上します。

 ・・・

 昨今の過重労働や格差社会といった社会問題が連想されます。仕事が見つかれば、給料が得られる代わりに馬車馬のように働き。仕事が見つからなければ、貧困にあえぐ。


 技術革新やITを駆使して無駄をなくし生産性を上げる。余った労働者には新たなスキルを身に着けてもらい、さらに革新を促す。そういう社会を目指すのは正しいことのように思えます。

 ただ、少し間違えると、昨今の社会問題を悪化させるだけになるかもしれないということは、肝に銘じて置いた方がよいのでしょう。



2020年9月6日日曜日

C#で、もう1回、MIDI その8

  今回は、MIDIの演奏のWindowsプログラムです。これまでの内容を組み合わせただけです。

 本当はMIDI編集用のプログラムも作ろうかと思ったのですが、いい感じのユーザーインターフェースが思いつかなかったので。。。

 気が向いたら、またいろいろ追加、更新しようと思います。


 以下が今回のC#のソースコードです。これまでに作った"MidiAPI.cs", "MidiEvent.cs", "MidiFile.cs", "MidiPlayer.cs"も使うので、その5ファイルを合わせてコンパイルしてください。


 ソースコードはご自由にご利用ください。ただし、趣味のプログラムなので保証はありません。コメントとかも適当です。プログラミングを勉強する方のご参考にでもなれば。


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

using System;

using System.Drawing;

using System.Windows.Forms;


class Program

{

[STAThread]


static void Main()

{

Application.Run( new FormMidiView());

}

}


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

class FormMidiView : Form

{

private MenuStrip ms;

private ToolStripMenuItem[][] tsmi;


private ComboBox cbx;

private TextBox tbx;

private ListBox lbx;


MidiPlayer player;


private int TimeDivision;

private MidiEvent[][] Track;

private MidiEvent[][] trk; //Track再生用


private Timer tmr;


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

public FormMidiView()

{

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

/* コントロールの作成 */

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

this.SuspendLayout();


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

this.ClientSize = new Size( 400, 300);

this.Text = "MidiView";


this.Load += new EventHandler( this.Form_Load);

this.FormClosed += new FormClosedEventHandler( this.Form_FormClosed);


this.AllowDrop = true;

this.DragEnter += new DragEventHandler( this.Form_DragEnter);

this.DragDrop += new DragEventHandler( this.Form_DragDrop);


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

this.CreateMenu();


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

this.cbx = new ComboBox();

this.cbx.SetBounds( 0, this.ms.Height, 100, 40);

this.cbx.Anchor = AnchorStyles.Left | AnchorStyles.Top;

this.cbx.SelectedIndexChanged += new EventHandler( this.cbx_SelectedIndexChanged);

this.Controls.Add( this.cbx);


this.tbx = new TextBox();

this.tbx.SetBounds( 0, this.ms.Height + this.cbx.Height, this.ClientSize.Width, 40);

this.tbx.BorderStyle = BorderStyle.None;

this.tbx.ReadOnly = true;

this.tbx.Anchor = AnchorStyles.Left | AnchorStyles.Top;

this.tbx.Text = "Index\tTime\tEvent\tParam1\tParam2\tType\tLength";

this.Controls.Add( this.tbx);


this.lbx = new ListBox();

this.lbx.SetBounds( 0, this.ms.Height + this.cbx.Height + this.tbx.Height, this.ClientSize.Width, this.ClientSize.Height - this.ms.Height - this.cbx.Height - this.tbx.Height);

this.lbx.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;

this.Controls.Add( this.lbx);


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

/* Timer */

/* 表示更新用のタイマー。ちなみに、MIDIを再生しているThreadからListBoxの表示を変更するにはDelegateが必要。面倒くさい。 */

this.tmr = new Timer();

this.tmr.Tick += new EventHandler( this.timer_Tick);

this.tmr.Interval = 100;

this.tmr.Enabled = false;


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

this.ResumeLayout();

}


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

private void CreateMenu()

{

this.ms = new MenuStrip();


this.tsmi = new ToolStripMenuItem[2][];

this.tsmi[0] = new ToolStripMenuItem[4];

this.tsmi[1] = new ToolStripMenuItem[5];


for( int i = 0; i < this.tsmi.Length; i++)

{

this.tsmi[i][0] = new ToolStripMenuItem();

this.ms.Items.Add( this.tsmi[i][0]);


for( int j = 1; j < this.tsmi[i].Length; j++)

{

this.tsmi[i][j] = new ToolStripMenuItem();

this.tsmi[i][j].Click += new EventHandler( this.Menu_Click);

this.tsmi[i][0].DropDownItems.Add( this.tsmi[i][j]);

}

}


this.tsmi[0][0].Text = "File (&F)";

this.tsmi[0][1].Text = "Open (&O)";

this.tsmi[0][2].Text = "Close (&C)";

this.tsmi[0][3].Text = "Exit (&X)";

this.tsmi[1][0].Text = "Midi (&M)";

this.tsmi[1][1].Text = "TimeDivision";

this.tsmi[1][2].Text = "Play";

this.tsmi[1][3].Text = "Play Track";

this.tsmi[1][4].Text = "Stop";


this.Controls.Add( this.ms);

this.MainMenuStrip = ms;

}


private void Menu_Click( object sender, EventArgs e)

{

if( sender == this.tsmi[0][1])

{

OpenFileDialog ofd = new OpenFileDialog();

ofd.Filter = "file|*.mid";

if( ofd.ShowDialog() == DialogResult.OK)

{

this.open( ofd.FileName);

}

}

else if( sender == this.tsmi[0][2])

{

this.player.Stop();

this.tmr.Enabled = false;


this.cbx.Items.Clear();

this.lbx.Items.Clear();


this.TimeDivision = 0;

this.Track = null;

}

else if( sender == this.tsmi[0][3])

{

this.Close();

}

else if( sender == this.tsmi[1][1])

{

MessageBox.Show( "TimeDivision : " + this.TimeDivision.ToString() + " tick");

}

else if( sender == this.tsmi[1][2])

{

if( this.Track == null)

{

return;

}


int i = this.cbx.SelectedIndex;

int j = this.lbx.SelectedIndex;

int tm = this.Track[i][j].Time * 500 / this.TimeDivision;


this.player.Play( this.TimeDivision, this.Track, tm);

this.tmr.Enabled = true;

}

else if( sender == this.tsmi[1][3])

{

if( this.Track == null)

{

return;

}


int i = this.cbx.SelectedIndex;

int j = this.lbx.SelectedIndex;

int tm = this.Track[i][j].Time * 500 / this.TimeDivision;


this.trk = new MidiEvent[1][];

this.trk[0] = new MidiEvent[this.Track[i].Length];


for( int k = 0; k < this.trk[0].Length; k++)

{

this.trk[0][k] = this.Track[i][k];

}


this.player.Play( this.TimeDivision, this.trk, tm);

this.tmr.Enabled = true;

}

else if( sender == this.tsmi[1][4])

{

if( this.Track == null)

{

return;

}


this.player.Stop();

this.tmr.Enabled = false;

}

}


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

private void Form_Load( object sender, EventArgs e)

{

Console.WriteLine( "Form_Load");


this.player = new MidiPlayer();


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

string[] args = Environment.GetCommandLineArgs();


if( 1 < args.Length)

{

this.open( args[1]);

}

}

private void Form_FormClosed( object sender, EventArgs e)

{

Console.WriteLine( "Form_FormClosed");


this.player.Stop();

this.tmr.Enabled = false;

}


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

private void Form_DragEnter( object sender, DragEventArgs e)

{

if( e.Data.GetDataPresent( DataFormats.FileDrop))

{

e.Effect = DragDropEffects.Move;

}

}

private void Form_DragDrop( object sender, DragEventArgs e)

{

Console.WriteLine( "Form_DragDrop");


string[] filename = (string[]) e.Data.GetData( DataFormats.FileDrop, false);


this.open( filename[0]);

}


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

private void cbx_SelectedIndexChanged( object sender, EventArgs e)

{

Console.WriteLine( "cbx_SelectedIndexChanged");


this.lbx.Items.Clear();


int index = this.cbx.SelectedIndex;


for( int i = 0; i < this.Track[index].Length; i++)

{

byte evt = this.Track[index][i].Event;


string str = i.ToString() + "\t";

str += this.Track[index][i].Time.ToString() + "\t";

str += this.Track[index][i].Event.ToString( "x") + "\t";


if( evt < 0xf0)

{

ChannelEvent ce = (ChannelEvent) this.Track[index][i];


str += ce.Param1.ToString() + "\t";

str += ce.Param2.ToString() + "\t";

str += "\t";

str += "\t";

}

else if( evt == 0xf0 || evt == 0xf7)

{

SystemEvent se = (SystemEvent) this.Track[index][i];


str += "\t";

str += "\t";

str += "\t";

str += se.Data.Length.ToString() + "\t";

}

else if( evt == 0xff)

{

MetaEvent me = (MetaEvent) this.Track[index][i];


str += "\t";

str += "\t";

str += me.Type.ToString() + "\t";

str += me.Data.Length.ToString() + "\t";

}


this.lbx.Items.Add( str);

}


this.lbx.SelectedIndex = 0;

}


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

private void timer_Tick( object sender, EventArgs e)

{

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

if( this.player.IsPlaying() == false)

{

this.tmr.Enabled = false;

return;

}


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

long tm = this.player.PlayTime;

int index = this.cbx.SelectedIndex;

int first = 0;

int last = this.Track[index].Length - 1;


while( 1 < last - first)

{

int next = ( first + last) / 2;


if( this.Track[index][next].Time * 500 / this.TimeDivision < tm)

{

first = next;

}

else

{

last = next;

}

}


if( first < this.lbx.Items.Count)

{

// cbxの選択が変更されると、Play中でもlbxが一度Clearされる

// そのため、このif文がないとタイミングによってエラーになる


this.lbx.SelectedIndex = first;

}

}


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

private void open( string filename)

{

this.player.Stop();

this.tmr.Enabled = false;


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

try

{

MidiFileReader.Read( filename, ref this.TimeDivision, ref this.Track);

}

catch( Exception e)

{

MessageBox.Show( "File Read Error");

Console.WriteLine( e.ToString());

}


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

this.cbx.Items.Clear();


for( int i = 0; i < this.Track.Length; i++)

{

this.cbx.Items.Add( "Track " + i.ToString());

}


this.cbx.SelectedIndex = 0;

}

}


C#で、もう1回、MIDI その7

  今回は、MIDIの演奏をしてみます。


 以前、MIDIの演奏プログラムを作ったときは、すべてのMIDI Eventを時系列に並べ直してから演奏しました。今回は各Trackは分けたままで演奏しています。「だから何」って話ですが、前処理がない分、ソースコードは簡単になった気がしてます。大差ないですが。

 今回のプログラムは、別スレッドで無限ループを使って演奏しています。たぶん、本職のプログラマーの方々であれば、別のタイマーの使い方をしそうな気がします。まぁ、趣味なので、動けばいいんです。

 おまけで、少し小細工をして、途中からの再生にも対応させています。


 以下が今回のC#のソースコードです。ソースコードはメイン関数の部分"Main.cs"と、MIDI演奏の"MidiPlayer.cs"に分かれています。"MidiEvent.cs"と"MidiAPI.cs"も使うので、その4つを合わせてコンパイルしてください。


 ソースコードはご自由にご利用ください。ただし、趣味のプログラムなので保証はありません。コメントとかも適当です。プログラミングを勉強する方のご参考にでもなれば。


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

ここから"Main.cs"

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

using System;

using System.IO;

using System.Threading;


public class Program

{

public static void Main()

{

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

int TimeDivision = 960;

MidiEvent[][] Track = new MidiEvent[2][];

byte[] data = new byte[0];


Track[0] = new MidiEvent[1];

Track[0][0] = new MetaEvent( 0, 0xff, 0x2f, data);


Track[1] = new MidiEvent[17];

Track[1][0] = new ChannelEvent( 0, 0x90, 60, 127);

Track[1][1] = new ChannelEvent( TimeDivision * 1, 0x80, 60, 127);

Track[1][2] = new ChannelEvent( TimeDivision * 1, 0x90, 62, 127);

Track[1][3] = new ChannelEvent( TimeDivision * 2, 0x80, 62, 127);

Track[1][4] = new ChannelEvent( TimeDivision * 2, 0x90, 64, 127);

Track[1][5] = new ChannelEvent( TimeDivision * 3, 0x80, 64, 127);

Track[1][6] = new ChannelEvent( TimeDivision * 3, 0x90, 65, 127);

Track[1][7] = new ChannelEvent( TimeDivision * 4, 0x80, 65, 127);

Track[1][8] = new ChannelEvent( TimeDivision * 4, 0x90, 67, 127);

Track[1][9] = new ChannelEvent( TimeDivision * 5, 0x80, 67, 127);

Track[1][10] = new ChannelEvent( TimeDivision * 5, 0x90, 69, 127);

Track[1][11] = new ChannelEvent( TimeDivision * 6, 0x80, 69, 127);

Track[1][12] = new ChannelEvent( TimeDivision * 6, 0x90, 71, 127);

Track[1][13] = new ChannelEvent( TimeDivision * 7, 0x80, 71, 127);

Track[1][14] = new ChannelEvent( TimeDivision * 7, 0x90, 72, 127);

Track[1][15] = new ChannelEvent( TimeDivision * 8, 0x80, 72, 127);

Track[1][16] = new MetaEvent( TimeDivision * 8, 0xff, 0x2f, data);


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

MidiPlayer player = new MidiPlayer();


player.Stop(); //再生していないときの停止無効


player.Play( TimeDivision, Track, 1000);


player.Play( TimeDivision, Track); //再生中の再生は無効


Thread.Sleep( 3000);


player.Stop();


player.Play( TimeDivision, Track, 500); //停止後に再生


Thread.Sleep( 3000);


Console.WriteLine( player.Stop());

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

Console.WriteLine( "Press Enter Key");

Console.ReadLine();

}

}


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

ここから"MidiPlayer.cs"

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

using System;

using System.Threading;


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

/* MidiPlayer Class */

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

public class MidiPlayer

{

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

/* Variables */

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

private IntPtr hMidi;

private Thread PlayThread;


private int TimeDivision;

private MidiEvent[][] Track;


private int Tempo;


private long PlayOffset; // [msec]

public long PlayTime; // [msec]


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

/* MidiPlayer */

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

public MidiPlayer()

{

this.Tempo = 500; // 1 TimeDivision = 500 [msec] のみ対応

}


~MidiPlayer()

{

this.Stop();

}


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

/* Play */

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

public void Play( int tm, MidiEvent[][] trk)

{

this.Play( tm, trk, 0);

}


public void Play( int tm, MidiEvent[][] trk, long offset)

{

if( this.PlayThread != null)

{

return;

}


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


this.TimeDivision = tm;

this.Track = trk;

this.PlayOffset = offset;


ThreadStart ts = new ThreadStart( this.play_func);

this.PlayThread = new Thread( ts);

this.PlayThread.IsBackground = true;

this.PlayThread.Start();

}


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

/* play_func */

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

private void play_func()

{

Console.WriteLine( "----- play_func start -----");


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

// Midi DeviceはThread内でOpenしないといけないみたい

if( MidiAPI.midiOutOpen( ref this.hMidi, MidiAPI.MIDI_MAPPER, 0, 0, 0) != MidiAPI.MMSYSERR_NOERROR )

{

Console.WriteLine( "MidiAPI.midiOutOpen error");

throw new Exception( "MidiAPI.midiOutOpen error");

}


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

int[] event_count = new int[Track.Length];


for( int i = 0; i < Track.Length; i++)

{

event_count[i] = 0;

}


long time_start = DateTime.Now.Ticks / 10000 - this.PlayOffset; // [msec]


this.PlayTime = 0;


bool flag = true;


while( flag)

{

if( this.PlayTime < this.PlayOffset)

{

// offsetまでは、1msec間隔でMidiEventを実行する (本当は音を鳴らす必要はない)

// MidiEventには、Program Changeなどもあるので、途中からの再生でも最初からMidiEventを処理する

this.PlayTime++; // [msec]

}

else

{

this.PlayTime = DateTime.Now.Ticks / 10000 - time_start; // [msec]

}


flag = false;


for( int i = 0; i < Track.Length; i++)

{

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

if( event_count[i] == Track[i].Length)

{

continue;

}


flag = true;


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

MidiEvent me = Track[i][event_count[i]];


if( this.PlayTime < me.Time * this.Tempo / this.TimeDivision)

{

continue;

}


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

if( me.Event < 0xf0)

{

// ChannelEventのみ対応 (本当はMetaEventのTempoとかも対応したほうがいい)

ChannelEvent ce = (ChannelEvent) me;

uint msg = (uint) ( ( ce.Param2 << 16) + ( ce.Param1 << 8) + ce.Event);

MidiAPI.midiOutShortMsg( this.hMidi, msg);

}


event_count[i]++;

}

}


this.PlayTime = 0;


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

Console.WriteLine( "----- play_func stop -----");


this.PlayThread = null;


MidiAPI.midiOutReset( this.hMidi);

MidiAPI.midiOutClose( this.hMidi);

}


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

/* Stop */

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

public long Stop()

{

if( this.PlayThread == null)

{

return 0;

}


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


this.PlayThread.Abort();


this.PlayThread = null;


MidiAPI.midiOutReset( this.hMidi);

MidiAPI.midiOutClose( this.hMidi);


return this.PlayTime;

}


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

public bool IsPlaying()

{

if( this.PlayThread == null)

{

return false;

}


return true;

}

}


C#で、もう1回、MIDI その6

 今回は、MIDIファイルの読み込みと書き出しをしてみます。以前にExcel VBAでも作っているので、内容は大体同じです。


 今回のプログラムで何が面倒かというと、MIDIファイルのフォーマットがBig EndianでVariable Lengthなんてのを使っていることです。MidiEventの個数が分かっていれば、メモリの確保が簡単なのに。。。Excelのときと同様、C#にもCollectionとか便利なクラスはあるんですが。。。リスト構造を自作するという手もあるんですが。。。結局、ただの配列が好きなので、配列を使っています。今回作ったStoreなんたらという関数は、配列のサイズが足りなくなったときに自動でメモリを再確保する関数です。

 

 以下が今回のC#のソースコードです。ソースコードはメイン関数の部分"Main.cs"と、MIDIファイルの読み書きの部分"MidiFile.cs"に分かれています。前回のその5で作った"MidiEvent.cs"も使うので、その3つを合わせてコンパイルしてください。


 ソースコードはご自由にご利用ください。ただし、趣味のプログラムなので保証はありません。コメントとかも適当です。プログラミングを勉強する方のご参考にでもなれば。


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

ここから"Main.cs"

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

using System;

using System.IO;


public class Program

{

public static void Main()

{

string filename;


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

/* 適当なMidi Fileの作成 */

filename = System.Environment.CurrentDirectory + @"\smf0.mid";

sample_smf0( filename);


filename = System.Environment.CurrentDirectory + @"\smf1.mid";

sample_smf1( filename);

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

/* Midi Fileの読み込み */

filename = System.Environment.CurrentDirectory + @"\smf1.mid";


int TimeDivision = 0;

MidiEvent[][] Track = null;


try

{

MidiFileReader.Read( filename, ref TimeDivision, ref Track);

}

catch( Exception e)

{

Console.WriteLine( e);

}


SaveCSV( TimeDivision, Track);

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

Console.WriteLine( "Press Enter Key");

Console.ReadLine();

}


private static void sample_smf0( string filename)

{

int TimeDivision = 960;

MidiEvent[][] Track = new MidiEvent[1][];

byte[] data = new byte[0];


Track[0] = new MidiEvent[17];

Track[0][0] = new ChannelEvent( 0, 0x90, 60, 127);

Track[0][1] = new ChannelEvent( TimeDivision * 1, 0x80, 60, 127);

Track[0][2] = new ChannelEvent( TimeDivision * 1, 0x90, 62, 127);

Track[0][3] = new ChannelEvent( TimeDivision * 2, 0x80, 62, 127);

Track[0][4] = new ChannelEvent( TimeDivision * 2, 0x90, 64, 127);

Track[0][5] = new ChannelEvent( TimeDivision * 3, 0x80, 64, 127);

Track[0][6] = new ChannelEvent( TimeDivision * 3, 0x90, 65, 127);

Track[0][7] = new ChannelEvent( TimeDivision * 4, 0x80, 65, 127);

Track[0][8] = new ChannelEvent( TimeDivision * 4, 0x90, 67, 127);

Track[0][9] = new ChannelEvent( TimeDivision * 5, 0x80, 67, 127);

Track[0][16] = new ChannelEvent( TimeDivision * 5, 0x90, 69, 127);

Track[0][15] = new ChannelEvent( TimeDivision * 6, 0x80, 69, 127);

Track[0][14] = new ChannelEvent( TimeDivision * 6, 0x90, 71, 127);

Track[0][13] = new ChannelEvent( TimeDivision * 7, 0x80, 71, 127);

Track[0][12] = new ChannelEvent( TimeDivision * 7, 0x90, 72, 127);

Track[0][11] = new ChannelEvent( TimeDivision * 8, 0x80, 72, 127);

Track[0][10] = new MetaEvent( TimeDivision * 9, 0xff, 0x2f, data);


/* Sortのテスト */

MidiEvent.Sort( Track[0]);


try

{

MidiFileWriter.Write( filename, TimeDivision, Track);

}

catch( Exception e)

{

Console.WriteLine( e);

}

}


private static void sample_smf1( string filename)

{

int TimeDivision = 960;

MidiEvent[][] Track = new MidiEvent[2][];

byte[] data = new byte[0];


Track[0] = new MidiEvent[1];

Track[0][0] = new MetaEvent( 0, 0xff, 0x2f, data);


Track[1] = new MidiEvent[17];

Track[1][0] = new ChannelEvent( 0, 0x90, 60, 127);

Track[1][1] = new ChannelEvent( TimeDivision * 1, 0x80, 60, 127);

Track[1][2] = new ChannelEvent( TimeDivision * 1, 0x90, 62, 127);

Track[1][3] = new ChannelEvent( TimeDivision * 2, 0x80, 62, 127);

Track[1][4] = new ChannelEvent( TimeDivision * 2, 0x90, 64, 127);

Track[1][5] = new ChannelEvent( TimeDivision * 3, 0x80, 64, 127);

Track[1][6] = new ChannelEvent( TimeDivision * 3, 0x90, 65, 127);

Track[1][7] = new ChannelEvent( TimeDivision * 4, 0x80, 65, 127);

Track[1][8] = new ChannelEvent( TimeDivision * 4, 0x90, 67, 127);

Track[1][9] = new ChannelEvent( TimeDivision * 5, 0x80, 67, 127);

Track[1][10] = new ChannelEvent( TimeDivision * 5, 0x90, 69, 127);

Track[1][11] = new ChannelEvent( TimeDivision * 6, 0x80, 69, 127);

Track[1][12] = new ChannelEvent( TimeDivision * 6, 0x90, 71, 127);

Track[1][13] = new ChannelEvent( TimeDivision * 7, 0x80, 71, 127);

Track[1][14] = new ChannelEvent( TimeDivision * 7, 0x90, 72, 127);

Track[1][15] = new ChannelEvent( TimeDivision * 8, 0x80, 72, 127);

Track[1][16] = new MetaEvent( TimeDivision * 8, 0xff, 0x2f, data);


try

{

MidiFileWriter.Write( filename, TimeDivision, Track);

}

catch( Exception e)

{

Console.WriteLine( e);

}

}


private static void SaveCSV( int TimeDivision, MidiEvent[][] Track)

{

string filename = System.Environment.CurrentDirectory + @"\MidiFile.txt";


try

{

StreamWriter sw = new StreamWriter( filename, false);


sw.WriteLine( "Track, Index, Time, Event, Param1, Param2, Type, Length");


for( int i = 0; i < Track.Length; i++)

{

for( int j = 0; j < Track[i].Length; j++)

{

sw.Write( i.ToString() + ", ");

sw.Write( j.ToString() + ", ");


byte evt = Track[i][j].Event;


if( evt < 0xf0)

{

ChannelEvent ce = (ChannelEvent) Track[i][j];


sw.Write( ce.Time.ToString() + ", ");

sw.Write( ce.Event.ToString( "x") + ", ");

sw.Write( ce.Param1.ToString( "x") + ", ");

sw.Write( ce.Param2.ToString( "x") + ", ");

sw.Write( ", ");

sw.Write( ", ");

sw.WriteLine();

}

else if( evt == 0xf0 || evt == 0xf7)

{

SystemEvent se = (SystemEvent) Track[i][j];


sw.Write( se.Time.ToString() + ", ");

sw.Write( se.Event.ToString( "x") + ", ");

sw.Write( ", ");

sw.Write( ", ");

sw.Write( ", ");

sw.Write( se.Data.Length + ", ");

sw.WriteLine();

}

else if( evt == 0xff)

{

MetaEvent me = (MetaEvent) Track[i][j];


sw.Write( me.Time.ToString() + ", ");

sw.Write( me.Event.ToString( "x") + ", ");

sw.Write( ", ");

sw.Write( ", ");

sw.Write( me.Type.ToString( "x") + ", ");

sw.Write( me.Data.Length.ToString() + ", ");

sw.WriteLine();

}

}

}


sw.Close();

}

catch( Exception e)

{

Console.WriteLine( e);

}

}

}



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

ここから"MidiFile.cs"

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

using System;

using System.IO;


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

/* MidiFileWriter */

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

public class MidiFileWriter

{

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

public static void Write( string filename, int TimeDivision, MidiEvent[][] Track)

{

Console.WriteLine( "----- MidiFileWriter.Write -----");


FileStream fs = new FileStream( filename, FileMode.Create, FileAccess.Write);

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

/* Header Chunk */

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

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;

int NumberOfTracks = Track.Length;


hc[8] = (byte) 0;

hc[9] = (byte) ( NumberOfTracks == 1 ? 0 : 1);


hc[10] = (byte) ( ( NumberOfTracks >> 8) & 0xff);

hc[11] = (byte) ( NumberOfTracks & 0xff);


hc[12] = (byte) ( ( TimeDivision >> 8) & 0xff);

hc[13] = (byte) ( TimeDivision & 0xff);


fs.Write( hc, 0, hc.Length);


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

/* Track Chunk */

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

for( int i = 0; i < Track.Length; i++)

{

byte[] tc = new byte[8];

tc[0] = (byte) 'M';

tc[1] = (byte) 'T';

tc[2] = (byte) 'r';

tc[3] = (byte) 'k';


byte[] data = null;

MidiFileWriter.SetMidiEvent( ref data, Track[i]);


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


fs.Write( tc, 0, tc.Length);


fs.Write( data, 0, data.Length);


Console.WriteLine( "Track " + i.ToString() + " : " + data.Length.ToString() + " bytes");

}

fs.Close();

}

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

private static void SetMidiEvent( ref byte[] data, MidiEvent[] Events)

{

int tm = 0;

byte evt = 0;


int pos = 0;

byte[] buf = new byte[1024];


for( int i = 0; i < Events.Length; i++)

{

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

/* Delta Time */

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

tm = Events[i].Time - ( ( 0 < i) ? Events[i - 1].Time : 0);


MidiFileWriter.SetVariableLength( tm, ref buf, ref pos);


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

/* MIDI Event */

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

if( Events[i].Event < 0xf0)

{

if( Events[i].Event != evt)

{

evt = Events[i].Event;

MidiFileWriter.StoreData( Events[i].Event, ref buf, ref pos);

}


if( evt < 0xc0 || 0xe0 <= evt)

{

MidiFileWriter.StoreData( ( (ChannelEvent) Events[i]).Param1, ref buf, ref pos);

MidiFileWriter.StoreData( ( (ChannelEvent) Events[i]).Param2, ref buf, ref pos);

}

else

{

MidiFileWriter.StoreData( ( (ChannelEvent) Events[i]).Param1, ref buf, ref pos);

}

}

else if( Events[i].Event == 0xf0 || Events[i].Event == 0xf7)

{

evt = Events[i].Event;

MidiFileWriter.StoreData( Events[i].Event, ref buf, ref pos);

MidiFileWriter.SetVariableLength( ( (SystemEvent) Events[i]).Data.Length, ref buf, ref pos);


for( int j = 0; j < ( (SystemEvent) Events[i]).Data.Length; j++)

{

MidiFileWriter.StoreData( ( (SystemEvent) Events[i]).Data[j], ref buf, ref pos);

}

}

else if( Events[i].Event == 0xff)

{

evt = Events[i].Event;

MidiFileWriter.StoreData( Events[i].Event, ref buf, ref pos);

MidiFileWriter.StoreData( ( (MetaEvent) Events[i]).Type, ref buf, ref pos);


MidiFileWriter.SetVariableLength( ( (MetaEvent) Events[i]).Data.Length, ref buf, ref pos);


for( int j = 0; j < ( (MetaEvent) Events[i]).Data.Length; j++)

{

MidiFileWriter.StoreData( ( (MetaEvent) Events[i]).Data[j], ref buf, ref pos);

}

}

}


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

data = new byte[pos];


for( int i = 0; i < data.Length; i++)

{

data[i] = buf[i];

}

}


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

private static void SetVariableLength( int val, ref byte[] data, ref int pos)

{

int temp = 1;


while( temp <= val)

{

temp = temp << 7;

}


temp = temp >> 7;


while( 1 < temp)

{

MidiFileWriter.StoreData( (byte) ( ( ( val / temp) & 0x7f) + 0x80), ref data, ref pos);

temp = temp >> 7;

}


MidiFileWriter.StoreData( (byte) ( val & 0x7f), ref data, ref pos);

}


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

private static void StoreData( byte val, ref byte[] data, ref int pos)

{

if( data.Length < pos + 1)

{

byte[] buf = new byte[2 * data.Length];


for( int i = 0; i < data.Length; i++)

{

buf[i] = data[i];

}


data = buf;

}


data[pos] = val;

pos++;

}

}




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

/* MidiFileReader */

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

public class MidiFileReader

{

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

public static void Read( string filename, ref int TimeDivision, ref MidiEvent[][] Track)

{

Console.WriteLine( "----- MidiFileReader.Read -----");


FileStream fs = new FileStream( filename, FileMode.Open, FileAccess.Read);

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

/* Header Chunk */

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

byte[] hc = new byte[14];

fs.Read( hc, 0, hc.Length);


if( hc[0] != (byte)77 || hc[1] != (byte)84 || hc[2] != (byte)104 || hc[3] != (byte)100)

{

throw new Exception( "It's not a MIDI File.");

}


int NumberOfTracks = ( hc[10] << 8) + hc[11];


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


Track = new MidiEvent[NumberOfTracks][];


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

Console.Write( "ChunkID : ");

Console.Write( (char)hc[0]);

Console.Write( (char)hc[1]);

Console.Write( (char)hc[2]);

Console.Write( (char)hc[3]);

Console.WriteLine();

Console.WriteLine( "ChunkSize : " + ( ( hc[4] << 24) + ( hc[5] << 16) + ( hc[6] << 8) + hc[7]).ToString());

Console.WriteLine( "FormatType : " + ( ( hc[8] << 8) + hc[9]).ToString());

Console.WriteLine( "NumberOfTracks : " + NumberOfTracks.ToString());

Console.WriteLine( "TimeDivision : " + TimeDivision.ToString());

Console.WriteLine();


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

/* Track Chunk */

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

for( int i = 0; i < Track.Length; i++)

{

byte[] tc = new byte[8];

fs.Read( tc, 0, tc.Length);


int temp = ( tc[4] << 24) + ( tc[5] << 16) + (tc[6] << 8) + tc[7];

byte[] data = new byte[temp];


fs.Read( data, 0, data.Length);


MidiFileReader.GetMidiEvent( data, ref Track[i]);

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

Console.Write( (char)tc[0]);

Console.Write( (char)tc[1]);

Console.Write( (char)tc[2]);

Console.Write( (char)tc[3]);

Console.WriteLine( " " + i.ToString() + " : " + data.Length.ToString() + " bytes");

}

fs.Close();

}

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

private static void GetMidiEvent( byte[] data, ref MidiEvent[] Events)

{

int tm = 0;

byte evt = 0;

byte p1 = 0;

byte p2 = 0;

byte tp = 0;

byte[] dt;


int pos = 0;


int count = 0;

MidiEvent[] buf = new MidiEvent[1024];


while( pos < data.Length)

{

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

/* Delta Time */

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

tm = MidiFileReader.GetVariableLength( data, ref pos) + ( ( 0 < count) ? buf[count - 1].Time : 0);


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

/* Event Type */

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

if( 0x80 <= data[pos])

{

evt = data[pos];

pos++;

}


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

/* MIDI Event */

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

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;

}


MidiFileReader.StoreMidiEvent( new ChannelEvent( tm, evt, p1, p2), ref buf, ref count);

}

else if( evt == 0xf0 || evt == 0xf7)

{

dt = new byte[MidiFileReader.GetVariableLength( data, ref pos)];


for( int i = 0; i < dt.Length; i++)

{

dt[i] = data[pos];

pos++;

}


MidiFileReader.StoreMidiEvent( new SystemEvent( tm, evt, dt), ref buf, ref count);

}

else if( evt == 0xff)

{

tp = data[pos];

pos++;

dt = new byte[MidiFileReader.GetVariableLength( data, ref pos)];


for( int i = 0; i < dt.Length; i++)

{

dt[i] = data[pos];

pos++;

}


MidiFileReader.StoreMidiEvent( new MetaEvent( tm, evt, tp, dt), ref buf, ref count);

}

}


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

Events = new MidiEvent[count];


for( int i = 0; i < Events.Length; i++)

{

Events[i] = buf[i];

}

}


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

private static int GetVariableLength( byte[] data, ref int pos)

{

int ret = ( data[pos] & 0x7f);

pos++;


while( 0x80 <= data[pos - 1])

{

ret = ( ret << 7) + ( data[pos] & 0x7f);

pos++;

}


return ret;

}


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

private static void StoreMidiEvent( MidiEvent Event, ref MidiEvent[] Events, ref int count)

{

if( Events.Length < count + 1)

{

MidiEvent[] buf = new MidiEvent[2 * Events.Length];


for( int i = 0; i < Events.Length; i++)

{

buf[i] = Events[i];

}


Events = buf;

}


Events[count] = Event;

count++;

}

}


C#で、もう1回、MIDI その5

 今回は、前回その4で調べたMIDIファイルをプログラムで扱うための準備です。MIDI Eventをクラスにしています。短い内容なので、読めば分かるでしょう。なんとなく教科書のオブジェクト指向っぽい感じです。

(ちなみに、Sort関数を作っているのは、後々のため。。。たぶん。)


 作ってみて迷ったのは、Timeの扱いです。

 MIDIファイルには、前のMIDI Eventからの相対時間が記録されています。その仕様で実際にMIDIファイルを演奏させてみると、曲の後半で違和感がありました。おそらく、誤差が累積しているからだと思います(演奏のやり方次第なんでしょうが。。。)。というわけで、クラス内のTimeは曲の中の絶対時間を保存することにしました。変数名がDeltaTimeじゃないのは、そういう理由です。


 以下が今回のC#のソースコード"MidiEvent.cs"です。といっても、Main関数がないので、実行はできません。


 ソースコードはご自由にご利用ください。ただし、趣味のプログラムなので保証はありません。コメントとかも適当です。プログラミングを勉強する方のご参考にでもなれば。


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


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

/* MidiEvent Class */

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

public class MidiEvent

{

public int Time; // Time [tick]

public byte Event; // 0x80 - 0xff



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

/* Sort */

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

public static void Sort( MidiEvent[] trk)

{

MidiEvent temp;


for( int i = 1; i < trk.Length; i++)

{

temp = trk[i];


int j;


for( j = i; 0 < j; j--)

{

if( temp.Time < trk[j - 1].Time)

{

trk[j] = trk[j - 1];

}

else

{

break;

}

}


trk[j] = temp;

}

}

}


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

/* ChannelEvent Class */

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

public class ChannelEvent : MidiEvent

{

public byte Param1; // 0x00 - 0x7f

public byte Param2; // 0x00 - 0x7f


public ChannelEvent( int 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( int 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( int tm, byte evt, byte tp, byte[] dt)

{

this.Time = tm;

this.Event = evt;

this.Type = tp;

this.Data = dt;

}

}


C#で、もう1回、MIDI その4

 今回はMIDIファイルで遊ぶため、そのフォーマットをまとめておきます。

 MIDIファイルのフォーマットの詳細は、WikipediaさんとかGoogle先生とかに聞けばわかります。それでも分からないところは、実際のMIDIファイルをダンプして調べてみませう。


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

 MIDIファイルの構造 (SMF, Standard Midi File)

<HeaderChunk> + <TrackChunk> + <TrackChunk> ...


MIDIファイルには複数のフォーマットがある (SMF0, SMF1, SMF2)

SMF0 : TrackChunkは1つ

SMF1 : TrackChunkは複数

SMF2 : 使われていないらしいので省略


SMF1では、1つのTrackには1つのChannel (1つの楽器) が対応

SMF1では、最初のTrackにはテンポなどの情報だけ入れる(コンダクタトラック)


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

<HeaderChunk>

ChunkID            4bytes, "MThd"

ChunkSize          4bytes, Big Endian, 6

FormatType         2bytes, Big Endian, 0 or 1

NumberOfTracks     2bytes, Big Endian, SMF0なら1

TimeDivision       2bytes, Big Endian


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

TimeDivisionについて

(1) bit15が0の場合(0*** **** **** ****)

4分音符の分割数 (48, 96, 960, etc)

4分音符の実際の時間は、MidiEventのMetaEventで設定可能 (デフォルトは0.5sec)

(2) bit15が1の場合(1aaa aaaa bbbb bbbb)

1aaa aaaaが1秒間のフレーム数 (-24, -25, -29, -30)

bbbb bbbbが1フレームの分解能 (4, 8, 10, 80, 100, etc)


(1)を使うことが多い

なお、MIDIでは最小時間単位をtickという


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

<TrackChunk>

ChunkID            4bytes, "MTrk"

ChunkSize          4bytes, Big Endian

Data               MidiEventの配列


MidiEventには、ChannelEvent , SystemEvent, MetaEventの3種類ある(最初の1byteで区別する)


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

variable length (可変長)について

最上位bitが1なら、次の1byteもデータと見なす

(1xxx xxxx 1yyy yyyy 0zzz zzzz) は (xxx xxxx yyy yyyy zzz zzzz)を意味する

最大4bytes (データは最大で28bits)


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

<ChannelEvent>

DeltaTime      variable length, Big Endian, 直前のMidiEventからの時間 (tick単位)

EventType      1byte, 0x80-0xEF

Param1         1byte

Param2         1byte, EventTypeによっては存在しない


0x 8n kk vv    3bytes, Note Off

0x 9n kk vv    3bytes, Note On

0x An kk vv    3bytes, Note Aftertouch

0x Bn cc dd    3bytes, Controller

0x Cn pp       2bytes, Program Change

0x Dn vv       2bytes, Channel Aftertouch

0x En ll mm    3bytes, Pitch Bend


n      4bits, 0-15,  Channel Number

kk     1byte, 0-127, Note Number

vv     1byte, 0-127, Velocity

cc     1byte, 0-127, Controller Type

dd     1byte, 0-127, Controller Value

pp     1byte, 0-127, Program Number

ll     1byte, 0-127, Pitch Value

mm     1byte, 0-127, Pitch Value


ChannelEventでは、同じChannelEventが連続するときは、2回目以降が省略可能 (Running Status)

1つのChannelには1つの楽器を対応させる (n=9はパーカッションに固定されている)

ControllerについてはWikipedia etcを参照

Program NumberについてはWikipedia etcを参照

llとmmは2つで1つのパラメータ(0-16383、8192のときはPitchの変更なし)

ll=0xxxxxxx, mm=0yyyyyyyは、yyyyyyyxxxxxxxを意味する(Little Endian)


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

<SystemEvent>

DeltaTime      variable length, Big Endian, 直前のMidiEventからの時間 (tick単位)

EventType      1byte, 0xF0 or 0xF7

DataLength     variable length, Dataのサイズ(byte単位)

Data


Dataが長い場合は、複数のSystem Eventにすることもある(送信エラーも起こりうる)

Dataの最初なら、System EventはF0で始める

分割されたDataの途中なら、System EventはF7で始める(variable lengthは分割されたDataのサイズ)

Dataの最後にはF7を付ける (variable lengthにはF7の1byteも含める)


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

<Meta Event>

DeltaTime      variable length, Big Endian, 直前のMidiEventからの時間 (tick単位)

EventType      1byte, 0xFF

MetaEventType  1byte, 種類はたくさんある

DataLength     variable length, Dataのサイズ(byte単位)

Data


0x FF 00    シーケンス番号  (Dataは2bytes)

0x FF 01    テキスト

0x FF 02    著作権

0x FF 03    シーケンス名

0x FF 04    楽器名

0x FF 05    歌詞

0x FF 06    マーカー

0x FF 07    キューポイント

0x FF 20    MIDIチャンネルプリフィックス    (Dataは1byte)

0x FF 21    ポート指定  (Dataは1byte。非標準。)

0x FF 2F    トラック終端    (必須。Dataは0byte)

0x FF 51    テンポ      (Dataは3bytes)  4分音符の秒数(usec単位) デフォルトは500000usec

0x FF 54    オフセット  (Dataは3bytes)  hr mn se fr ff  0-23, 0-59, 0-59, 0-30, 0-99の値をとるらしい

0x FF 58    拍子        (Dataは4bytes)  4/4拍子とか

0x FF 59    調号        (Dataは2bytes)  シャープ、フラット、長調、短調

0x FF 7F    シーケンサー特定メタイベント


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

参考URL

http://ja.wikipedia.org/wiki/General_MIDI

http://www.midi.org/techspecs/midimessages.php


C#で、もう1回、MIDI その3

 今回は、C#でピアノを作ります。

 以前にも同じようなプログラムを作ったのですが、その時は、ボタンコントロールで鍵盤を作りました。今回はPictureBoxに鍵盤の絵を描いています。


 以下が今回のC#のソースコードです。前回の"MidiAPI.cs"と一緒にコンパイルしてください。


 ソースコードはご自由にご利用ください。ただし、趣味のプログラムなので保証はありません。コメントとかも適当です。プログラミングを勉強する方のご参考にでもなれば。


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

using System;

using System.Drawing;

using System.Windows.Forms;


class Program

{

[STAThread]


static void Main()

{

Application.Run( new FormPiano());

}

}


class FormPiano : Form

{

private IntPtr hMidi;


private Label[] lbl;

private ComboBox[] cmb;


private Panel pnl;

private PictureBox pbx;


private byte key;

private int keyWidthS = 30;

private int keyWidthL = 40;

private int keyHeight = 160;


public FormPiano()

{

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

/* コントロールの作成 */

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

this.SuspendLayout();


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

this.ClientSize = new Size( 840, 240);

this.Text = "Piano";


this.Load += new EventHandler( this.FormPiano_Load);

this.FormClosed += new FormClosedEventHandler( this.FormPiano_FormClosed);


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

this.CreateOption();


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

this.pnl = new Panel();

this.pnl.SetBounds( 0, 40, 840, 200);

this.pnl.AutoScroll = true;

this.pnl.Anchor = ( AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom);

this.Controls.Add( this.pnl);


this.pbx = new PictureBox();

this.pbx.SetBounds( 0, 0, this.keyWidthL * 75, this.keyHeight);

this.pbx.Paint += new PaintEventHandler( this.pbx_Paint);

this.pbx.MouseDown += new MouseEventHandler( this.pbx_MouseDown);

this.pbx.MouseMove += new MouseEventHandler( this.pbx_MouseMove);

this.pbx.MouseUp += new MouseEventHandler( this.pbx_MouseUp);

this.pnl.Controls.Add( this.pbx);


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

this.ResumeLayout();

}


private void CreateOption()

{

this.lbl = new Label[2];

this.cmb = new ComboBox[2];


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

this.lbl[0] = new Label();

this.lbl[0].SetBounds( 0, 0, 60, 40);

    this.lbl[0].TextAlign = ContentAlignment.TopRight;

this.lbl[0].Text = "channel";

this.Controls.Add( this.lbl[0]);


this.cmb[0] = new ComboBox();

this.cmb[0].SetBounds( 60, 0, 60, 40);

this.cmb[0].DropDownStyle = ComboBoxStyle.DropDownList;

this.Controls.Add( this.cmb[0]);


this.cmb[0].Items.Add( "0");

this.cmb[0].Items.Add( "9");

this.cmb[0].SelectedIndex = 0;


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

this.lbl[1] = new Label();

this.lbl[1].SetBounds( 120, 0, 60, 40);

    this.lbl[1].TextAlign = ContentAlignment.TopRight;

this.lbl[1].Text = "program";

this.Controls.Add( this.lbl[1]);


this.cmb[1] = new ComboBox();

this.cmb[1].SetBounds( 180, 0, 60, 40);

this.cmb[1].DropDownStyle = ComboBoxStyle.DropDownList;

this.Controls.Add( this.cmb[1]);


for( int i = 0; i < 128; i++)

{

this.cmb[1].Items.Add( i);

}


this.cmb[1].SelectedIndex = 0;

this.cmb[1].SelectedIndexChanged += new EventHandler( this.cmb_SelectedIndexChanged);

}


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

/* 鍵盤の描画 */

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


private void pbx_Paint( object sender, PaintEventArgs e)

{

Graphics g = e.Graphics;


int count;

Rectangle rect;


Font fnt = new Font( "Arial", 8);

StringFormat sf = new StringFormat();

sf.Alignment = StringAlignment.Center;

sf.LineAlignment = StringAlignment.Far;


/* 白 */

count = 0;


for( int i = 0; count < 128; i++)

{

rect = new Rectangle( this.keyWidthL * i, 0, this.keyWidthL - 1, this.keyHeight - 1);

g.FillRectangle( Brushes.White, rect);

g.DrawRectangle( ( ( this.key == count) ? Pens.Red : Pens.Black), rect);

g.DrawString( count.ToString(), fnt, Brushes.Black, rect, sf);


count += ( ( count % 12 == 4 || count % 12 == 11) ? 1 : 2);

}


/* 黒 */

count = 1;


for( int i = 0; count < 128; i++)

{

rect = new Rectangle( this.keyWidthL * ( i + 1) - this.keyWidthS / 2, 0, this.keyWidthS - 1, this.keyHeight / 2 - 1);

g.FillRectangle( Brushes.Black, rect);

g.DrawRectangle( ( ( this.key == count) ? Pens.Red : Pens.Black), rect);

g.DrawString( count.ToString(), fnt, Brushes.White, rect, sf);


i += ( ( count % 12 == 3 || count % 12 == 10) ? 1 : 0);

count += ( ( count % 12 == 3 || count % 12 == 10) ? 3 : 2);

}

}


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

/* MIDIデバイスのOpenとClose

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


private void FormPiano_Load( object sender, EventArgs e)

{

Console.WriteLine( "FormPiano_Load");

if( MidiAPI.midiOutOpen( ref this.hMidi, MidiAPI.MIDI_MAPPER, 0, 0, 0) != MidiAPI.MMSYSERR_NOERROR)

{

MessageBox.Show( "midiOutOpen error");

Application.Exit();

}

this.pnl.AutoScrollPosition = new Point( 1120, 0);

}

private void FormPiano_FormClosed( object sender, EventArgs e)

{

Console.WriteLine( "FormPiano_FormClosed");

MidiAPI.midiOutClose( this.hMidi);

}


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

/* Note On/Off */

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


private void NoteOn( byte key)

{

byte ch = (byte) ( ( this.cmb[0].SelectedIndex == 0) ? 0 : 9);

byte velocity = 0x7f;

uint msg;

msg = (uint)( ( velocity << 16) + ( key << 8) + 0x90 + ch);

MidiAPI.midiOutShortMsg( this.hMidi, msg);

}

private void NoteOff( byte key)

{

byte ch = (byte) ( ( this.cmb[0].SelectedIndex == 0) ? 0 : 9);

byte velocity = 0x7f;

uint msg;

msg = (uint)( ( velocity << 16) + ( key << 8) + 0x80 + ch);

MidiAPI.midiOutShortMsg( this.hMidi, msg);

}


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

/* プログラム変更 */

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


private void ProgramChange( byte prg)

{

byte ch = (byte) 0;


uint msg;

msg = (uint)( ( prg << 8) + 0xc0 + ch);

MidiAPI.midiOutShortMsg( this.hMidi, msg);

}


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

/* コンボボックスの処理 */

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


private void cmb_SelectedIndexChanged(object sender, System.EventArgs e)

{

byte prg = (byte) this.cmb[1].SelectedIndex;


Console.WriteLine( "cmb_SelectedIndexChanged " + prg.ToString());


this.ProgramChange( prg);

}


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

/* マウス処理 */

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


private byte MousePosToKey( object sender, MouseEventArgs e)

{

int q = e.X / ( this.keyWidthL * 7);

int r = e.X % ( this.keyWidthL * 7);


byte k = (byte) ( q * 12);


if( e.Y < this.keyHeight / 2)

{

k += (byte) ( ( ( this.keyWidthL - this.keyWidthS / 2) <= r) ? 1 : 0);

k += (byte) ( ( ( this.keyWidthL + this.keyWidthS / 2) <= r) ? 1 : 0);

k += (byte) ( ( ( this.keyWidthL * 2 - this.keyWidthS / 2) <= r) ? 1 : 0);

k += (byte) ( ( ( this.keyWidthL * 2 + this.keyWidthS / 2) <= r) ? 1 : 0);


k += (byte) ( ( ( this.keyWidthL * 3) <= r) ? 1 : 0);

k += (byte) ( ( ( this.keyWidthL * 4 - this.keyWidthS / 2) <= r) ? 1 : 0);

k += (byte) ( ( ( this.keyWidthL * 4 + this.keyWidthS / 2) <= r) ? 1 : 0);

k += (byte) ( ( ( this.keyWidthL * 5 - this.keyWidthS / 2) <= r) ? 1 : 0);

k += (byte) ( ( ( this.keyWidthL * 5 + this.keyWidthS / 2) <= r) ? 1 : 0);

k += (byte) ( ( ( this.keyWidthL * 6 - this.keyWidthS / 2) <= r) ? 1 : 0);

k += (byte) ( ( ( this.keyWidthL * 6 + this.keyWidthS / 2) <= r) ? 1 : 0);

}

else

{

k += (byte) ( ( r / this.keyWidthL) * 2 + ( ( r < this.keyWidthL * 3) ? 0 : -1));

}


return k;

}


private void pbx_MouseDown( object sender, MouseEventArgs e)

{

this.key = this.MousePosToKey( sender, e);

Console.WriteLine( "pbx_MouseDown " + this.key.ToString());


this.NoteOn( this.key);

this.pbx.Refresh();

}


private void pbx_MouseMove( object sender, MouseEventArgs e)

{

if( e.Button != MouseButtons.Left)

{

return;

}


byte k = this.MousePosToKey( sender, e);


if( this.key == k)

{

return;

}


this.NoteOff( this.key);

this.key = k;

this.NoteOn( this.key);

this.pbx.Refresh();

}


private void pbx_MouseUp( object sender, MouseEventArgs e)

{

this.key = this.MousePosToKey( sender, e);

Console.WriteLine( "pbx_MouseUp " + this.key.ToString());


this.NoteOff( this.key);

this.pbx.Refresh();

}


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

}