2016年2月21日日曜日

技術の進化と人の退化と

 IOT (Internet Of Things) という言葉があります。さまざまな家電をネットワークにつなごう、という話です。例えば、ドアを開けたら照明がついたり、エアコンがついたり、遠くの誰かに帰宅を知らせたり、天気予報を教えてくれたり、などなどです。

 IOTの社会を実現しようと本気で思っている人々がいます。私はそれに反対する気はありませんが、何でもかんでも機械に世話されるのは、うっとうしく感じると思います。


 自動車が発明されて、人は歩かなくなりました。昔の人に比べて格段に遠くまで行ける変わりに、歩く能力は衰退したと思います。エレベーターを使うのが当たり前な人もいるようですが、昔はみんな歩いていたはずです。

 人は怠惰な生き物だと思います。技術が進化して便利になると、人はきっと楽をしようとして退化します。そして、技術が普及すると、いつのまにか、昔の人が当たり前にできたことが、できなくなっているのではないかという気がします (少なくとも今の私の体力は原始人よりはるかに劣ると思っています)。無意識のうちに退化しているというのは寂しい話です。


 私は、たとえ技術が進化しても、自分を退化させないようにしたいと思っています。「いい技術は採用するけど、それでいて自分を衰退させないように、精進を重ねる」という感じです。怠けないよう、日々、自分を戒めています。・・・それでも怠けちゃうんですが。orz

 というわけで、私は今日も階段を上ります。

料理と有機化学

 私は、あまり料理をしませんが、それでもお米は炊いています。炊飯器は持っていないので、お鍋で炊いています。

 「炊飯器を使わずにお米を炊いている」と言うと驚く人もいるみたいですが、やり方は簡単です。鍋の中でお米を水に浸して1時間くらいしたら、弱火~中火で沸騰するまで10分くらい待ちます。沸騰したら、火を止めて、30分~1時間そのまま蒸らします。蒸らし時間が大切で、この時間を十分にとれば、お米が固いままなんてことはありません。

 玉ねぎを炒めるときなども、ある程度、火を入れたら、後は火を止めて放置すれば柔らかくなります。ガスを使い続けるより、余熱を使って待っている方がガス代の節約になり経済的です。


 お米を構成するでんぷんは高分子です。玉ねぎを構成するセルロースも高分子です。教科書によれば、高分子の化学反応には時間がかかります。余熱を使って放置している時間に、化学反応が進んでいるのでしょう。

 化学の教科書には、「高分子の化学反応には時間がかかる」とは書いてありますが、それが何分なのか、何時間なのかは分かりません。もちろんそれはケースバイケースだからですが、あいまいな表現では科学になりません。

 お米を炊いていると、有機化学の反応にどれくらい時間がかかるかを体感できます。教科書を見ていてお米を炊くことは想像しませんでしたが、お米を炊いていて教科書を思い返してみるのも一興だと思っています。

 科学はとっても身近なものです。











2016年2月14日日曜日

予算制度

 年度末には予算の消化のために道路工事が増えると言います。子供の頃、その話を聞いて、「国の財政は借金まみれなのにけしからん」、なんて思っていました。予算が余ったら次の年に繰り越せるように、予算を2年計画の毎年更新にすべきなのでは?、なんて考えていました。

 正しくお金を使うというのは難しいことですが、「予算が余ると次の年の予算が減額されるから、無理してでも予算を使い切ってしまおう」なんて使い方は、明らかに愚の極みです。


 ところで、普通、買い物をするときは、自分の財布の状況を見て、買うものを決めます。代金を払えないものは買えないのだから、当たり前です。国や企業も同じことができないのでしょうか?

 国や企業は組織が大きいので、財布の中身をチェックするのは大仕事です。だからこその予算計画なのだと思いますが、IT技術が発達した昨今なら、財務状況をリアルタイムに把握することもできる気がします。

 国の財布は借金まみれなので、それを見ているだけでは面白くないですが、例えば、現場で1万円節約したら、それが国の財務状況にリアルタイムに反映されている、とかだったら、面白そうです。

 年1回、議論を重ねて予算を決めたら、突然の災害ですぐに補正予算が必要、なんてことがしょっちゅうです。ならいっそ、年1回の予算ではなく、リアルタイムの財布状況を重視するのは合理的な気がします。

 IT技術はそういった情報処理が大得意です。国や企業においても、その日の財布を見ながら支出を決定する、そんなシステムが登場してもおかしくないのかも?なんてことをふと考えました。

C#でピアノを作ろう

 こう見えて(?)私は音楽好きです。というわけで、暇つぶしに、C#でピアノを作ってみました。

 ボタンを押したら、MIDIの音を鳴らすだけの単純なプログラムです。鍵盤の作り方とかマウスイベントとか、見る人が見ればちょっと面白いかもしれません。

 最近のWindowsはC#のコンパイラを持っているので、誰でも手軽にプログラムができます。

 コンパイル方法は、以下の通り。
(1)以下のソースコードをテキストファイルにコピー、ファイル名を適当に"piano.cs"として保存
(2)コマンドプロンプトで次のコマンドをを実行すると、"piano.exe"ができる
>cd [csc.exeがあるフォルダ]
>csc.exe /target:winexe "[piano.csがあるフォルダ]/piano.cs"

 うまくいかないという方は、周りの詳しそうな人かGoogle先生に聞いてみてください。csc.exeで調べればすぐ分かると思います。

 例によって、保証はありませんが、悪しからず。

 ご自由に遊んでください。


2016/2/27 : キーボードで複数のキーが押せるようにフラグを微修正しました

2016/3/6 : いろいろ微修正

2016/7/24 : すべてキーボードが使えるように修正

2017/5/4 : APIの宣言が32bit OS用だったので、64bit OS用に変更 (気分でキーボードの処理は削除)


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

class Program
{
[STAThread]

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

class FormPiano : Form
{
/*--------------------------------------------------*/
/* 使用するAPIの宣言 */
/*--------------------------------------------------*/
[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);

private const uint MMSYSERR_NOERROR = 0;
private const uint MIDI_MAPPER = 0xffffffff;

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

private long hMidi;

private Label[] lbl;
private ComboBox[] cmb;

private Panel pnl;
private Button[] key;

public FormPiano()
{
this.ClientSize = new Size( 840, 240);
this.Text = "Piano";

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

/*--------------------------------------------------*/
/* コントロールの作成 */
/*--------------------------------------------------*/
this.lbl = new Label[2];

for( int i = 0; i < this.lbl.Length; i++)
{
this.lbl[i] = new Label();
this.lbl[i].SetBounds( 180 * i, 0, 60, 40, BoundsSpecified.All);
this.Controls.Add( this.lbl[i]);
}
this.lbl[0].Text = "channel";
this.lbl[1].Text = "program";

this.cmb = new ComboBox[2];

for( int i = 0; i < this.cmb.Length; i++)
{
this.cmb[i] = new ComboBox();
this.cmb[i].SetBounds( 180 * i + 60, 0, 60, 40, BoundsSpecified.All);
this.cmb[i].DropDownStyle = ComboBoxStyle.DropDownList;
this.Controls.Add( this.cmb[i]);
}

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

this.cmb[0].SelectedIndex = 0;

for( int i = 0; i < 127; i++)
{
this.cmb[1].Items.Add( i);
}

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

/*--------------------------------------------------*/
/* 鍵盤(ボタン)の作成 */
/*--------------------------------------------------*/
this.pnl = new Panel();
this.pnl.SetBounds( 0, 40, 840, 200, BoundsSpecified.All);
this.pnl.AutoScroll = true;
this.pnl.Anchor = ( AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom);
this.Controls.Add( this.pnl);

this.key = new Button[128];

int count = 0;

for( int i = 0; i < this.key.Length; i++)
{
this.key[i] = new Button();
this.key[i].Name = i.ToString();
this.key[i].Text = i.ToString();
    this.key[i].TextAlign = ContentAlignment.BottomCenter;
this.pnl.Controls.Add( this.key[i]);

this.key[i].MouseDown += new MouseEventHandler( this.key_MouseDown);
this.key[i].MouseUp += new MouseEventHandler( this.key_MouseUp);
this.key[i].MouseEnter += new EventHandler( this.key_MouseEnter);
this.key[i].MouseLeave += new EventHandler( this.key_MouseLeave);

if( count % 2 == 0)
{
this.key[i].SetBounds( 20 * count, 0, 40, 160, BoundsSpecified.All);
this.key[i].ForeColor = Color.Black;
this.key[i].BackColor = Color.White; //白
}
else
{
this.key[i].SetBounds( 20 * count + 5, 0, 30, 80, BoundsSpecified.All);
this.key[i].ForeColor = Color.White;
this.key[i].BackColor = Color.Black; //黒
this.key[i].BringToFront();
}

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

/*--------------------------------------------------*/
/* MIDIデバイスのOpenとClose
/*--------------------------------------------------*/

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

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

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

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

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);
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);
midiOutShortMsg( this.hMidi, msg);
}

/*--------------------------------------------------*/
/* プログラム変更 */
/*--------------------------------------------------*/

private void ProgramChange( byte prg)
{
byte ch = (byte) 0;

uint msg;
msg = (uint)( ( prg << 8) + 0xc0 + ch);
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 void key_MouseDown( object sender, MouseEventArgs e)
{
( (Button)sender).Capture = false;

byte key = (byte) int.Parse( ( (Button)sender).Name);
Console.WriteLine( "key_MouseDown " + key.ToString());

this.NoteOn( key);
}

private void key_MouseUp( object sender, MouseEventArgs e)
{
byte key = (byte) int.Parse( ( (Button)sender).Name);
Console.WriteLine( "key_MouseUp " + key.ToString());

this.NoteOff( key);
}

private void key_MouseEnter( object sender, EventArgs e)
{
if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
{
this.key_MouseDown( sender, new MouseEventArgs( MouseButtons.Left, 1, 0, 0, 0));
}
}

private void key_MouseLeave( object sender, EventArgs e)
{
if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
{
this.key_MouseUp( sender, new MouseEventArgs( MouseButtons.Left, 1, 0, 0, 0));
}
}

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

2016年2月6日土曜日

日常の風景

 ときどき、昔の人の日常生活を想像することがあります。

 朝食にパンを食べているとき、「江戸時代なら白いご飯だったんだろうか?」、「お米はふっくらしていたのだろうか?」、「いつ炊いたんだろうか?」、「おかずはあったんだろうか?」、とか考えたりします。

 現代サラリーマンの忙しい生活スタイルに慣れてしまうと、他の生活スタイルがあることを忘れがちです。
 ですが、時代や身分によって様々な生活スタイルがあったはずです。縄文時代の生活、鎌倉時代の生活、庶民の生活、貴族の生活、他にも世界中に様々な生活スタイルがあったはずです。

 江戸時代の庶民の文化についての研究とかを聞くことがありますが、庶民の生活なんて記録に残りにくいものなので、調査は難しそうです。

 庶民の生活が分かったからといって、特に得することはないかも知れませんが、自分の退屈な日常と比べてみたい気持ちになります。

 そんなことを想像していると、今度は、「今から100年、200年後の人類は同じ疑問を持つかしらむ?」、なんて疑問がわいてきます。

 「自分自身の生活のすべてをビデオに録画しておいて、それが100年後の人にとっては重要な歴史的資料になっていたりするだろうか?」、なんて考えがわいてきます。

 日常の風景を見ながら、ふとそんなことを考えていました。



嘘をつく上司の心理

 私は嘘が嫌いです。

 こちらのコラムにもあるように、嘘がはびこる組織は腐敗し、必ず悪い結果をもたらします。耐震偽装マンション、STAP細胞、大手メーカーの不適切会計などなど。枚挙に暇がありません。

 嘘が悪い結果をもたらすのは自明だと思うのですが、それでも世の中には嘘が溢れています。社会に出て何年か働いていますが、幾たびも嘘に遭遇しました。偏見かもしれませんが、役職付きの上司になるほどひどい嘘をついている気がします。

 たいていの上司には、事業計画を立てる、という仕事があります。事業計画には簡単な制約が一つあります。それは「利益を上げる」という制約です。非常に簡単な制約ですが、これが難しいです。

 確実に儲かる話はありません。どんな事業計画も失敗する可能性があります。ましてや不景気の昨今、利益を上げる計画を立てるのは非常に難しいと思います。

 となると、そこに嘘が混じってきます。実現可能性の低いことでもできると言ってしまう。需要を多く予測する。コストを小さく、利益を大きく見積もる。などなど。

 もちろん、未来予測は往々にして外れるので、少し楽観的な予測をしても間違いではないでしょう。加えて、予算をもらえるかどうかは死活問題なので、計画を少し脚色してしまう気持ちは理解できます。
 ですが、計画に嘘が混じると悪い結果につながります。それは歴史が教えてくれている通りです。

 嘘にも程度があります。小さな嘘をつくことに慣れてしまうと、いつか大きな嘘をついて大失敗につながる気がします。

 願わくば、嘘をつかない仕事をしていたいものです。

ExcelでMIDIシーケンサー

 先日、ExcelでMIDIファイルを作るマクロを作ったので、調子に乗って、ExcelでMIDIシーケンサーを作ってみました。 「ドレミの歌」くらいなら、すぐにMIDIファイルを作って遊べます。

 長いですが、ソースコードを以下に載せます。VBAのモジュールにコピペすれば使えるはずです。

Excel VBAで直接MIDIを演奏するには、APIが必要なので面倒です。ですが、MIDIファイルを作るだけなら、バイナリファイルをいじるだけなので簡単です。というわけで演奏機能はありません。

 MIDIファイルの詳細についてはWikipedia等を参照ください。

 興味ある方は、ご自由にご利用ください。


2016/2/6
SMF1に対応させました。ユーザーインターフェースをマルチトラックに対応させるのが面倒だったので、1トラックだけ対応です。


--------------------------------------------------
Option Explicit

'==================================================
'MIDIファイルの構造 (SMF, Standard Midi File)
'<HeaderChunk> + <TrackChunk> + <TrackChunk> ...
'
'MIDIファイルには複数のフォーマットがある (SMF0, SMF1, SMF2)
'SMF0 : TrackChunkは1つ
'SMF1 : TrackChunkは複数 (1つのTrackには1つのChannelが対応, 最初のTrackにはテンポなどの情報だけ入れる(コンダクタトラック))
'SMF2 : 使われていないらしいので省略
'==================================================
'<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(可変長)について
'最上位bitが1なら、次の1byteもデータと見なす
'(1xxx xxxx 1yyy yyyy 0zzz zzzz) は (xxx xxxx yyy yyyy zzz zzzz)を意味する
'最大4bytes (データは最大で28bits)
'
'==================================================
'<ChannelEvent>
'DeltaTime      variable, 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はパーカッションに固定されている) (SMF1なら1つのTrackが1つのChannel、1つの楽器に対応する)
'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, Big Endian, 直前のMidiEventからの時間 (tick単位)
'EventType      1byte, 0xF0 or 0xF7
'DataLength     variable, Dataのサイズ(byte単位)
'Data
'
'Dataが長い場合は、複数のSystem Eventにすることもある(送信エラーも起こりうる)
'Dataの最初なら、System EventはF0で始める
'分割されたDataの途中なら、System EventはF7で始める(variableは分割されたDataのサイズ)
'Dataの最後にはF7を付ける (variableにはF7の1byteも含める)
'==================================================
'<Meta Event>
'DeltaTime          variable, Big Endian, 直前のMidiEventからの時間 (tick単位)
'EventType      1byte, 0xFF
'MetaEventType  1byte, 種類はたくさんある
'DataLength     variable, 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
'==================================================

Private Type HEADER_CHUNK
    ChunkID As String * 4
    ChunkSize As Long
    FormatType As Integer
    NumberOfTracks As Integer
    TimeDivision As Integer
End Type

Private Type TRACK_CHUNK
    ChunkID As String * 4
    ChunkSize As Long
    Data() As Byte
End Type

Private Type MIDI_EVENT
    DeltaTime As Long
    EventType As Byte
    Param1 As Byte
    Param2 As Byte
End Type



Public Sub seq_sample()
    Dim i As Long
    Dim note() As String
    Dim key1 As Byte
    Dim key2 As Byte
    Dim time_step As Long
    Dim time_count As Long
   
    ReDim note(11) As String
    note(0) = "C":  note(1) = "C#": note(2) = "D":   note(3) = "D#"
    note(4) = "E":  note(5) = "F":  note(6) = "F#":  note(7) = "G"
    note(8) = "G#": note(9) = "A":  note(10) = "A#": note(11) = "B"
   
    Sheet1.Cells.ClearContents
   
    '------------------------------
    Sheet1.Cells(1, 1) = "Program No. : "
    Sheet1.Cells(1, 2) = 0
    '------------------------------
    key1 = 48
    key2 = 83
   
    Sheet1.Cells(2, 1) = "Key"
   
    For i = 0 To key2 - key1
        Sheet1.Cells(3 + i, 1) = key2 - i
        Sheet1.Cells(3 + i, 2) = note((key2 - i) Mod 12)
    Next i
    '------------------------------
    time_step = 24
    time_count = 480    '四分音符1個 = 0.5sec = 96tickのときの60secに相当
   
    Sheet1.Cells(1, 3) = "Key"
   
    For i = 0 To time_count - 1
        Sheet1.Cells(2, 3 + i) = i * time_step
    Next i
   
    Sheet1.Range(Sheet1.Cells(1, 1), Sheet1.Cells(1, 2 + time_count)).ColumnWidth = 4
    '------------------------------
    Sheet1.Cells(26, 3) = "on"
    Sheet1.Cells(26, 4) = "off"
    Sheet1.Cells(24, 4) = "on"
    Sheet1.Cells(24, 5) = "off"
    Sheet1.Cells(22, 5) = "on"
    Sheet1.Cells(22, 6) = "off"
    Sheet1.Cells(21, 6) = "on"
    Sheet1.Cells(21, 7) = "off"
    Sheet1.Cells(19, 7) = "on"
    Sheet1.Cells(19, 8) = "off"
    Sheet1.Cells(17, 8) = "on"
    Sheet1.Cells(17, 9) = "off"
    Sheet1.Cells(15, 9) = "on"
    Sheet1.Cells(15, 10) = "off"
    Sheet1.Cells(14, 10) = "on"
    Sheet1.Cells(14, 14) = "off"
    '------------------------------
   
    ActiveWindow.FreezePanes = False
    Sheet1.Cells(3, 3).Select
    ActiveWindow.FreezePanes = True
End Sub

Public Sub seq_write()
    Dim i As Long
    Dim j As Long
    Dim row As Long
    Dim col As Long
    Dim count As Long
    Dim str As String
   
    Dim prg As Byte
    Dim ch As Byte
    Dim time As Long
    Dim time_last As Long
    Dim key As Byte
   
    Dim filename As String
    Dim hChunk As HEADER_CHUNK
    Dim tChunk() As TRACK_CHUNK
    Dim midi() As MIDI_EVENT
   
    '------------------------------
    hChunk.ChunkID = "MThd"
    hChunk.ChunkSize = 6
    hChunk.FormatType = 1
    hChunk.NumberOfTracks = 2
    hChunk.TimeDivision = 96
   
    '------------------------------
    ReDim tChunk(1) As TRACK_CHUNK
   
    '------------------------------
    ReDim midi(0) As MIDI_EVENT
    setMidi midi(0), 0, &HFF, &H2F, &H0
   
    encodeMidi tChunk(0), midi, 1
    Erase midi
    '------------------------------
    'MidiEventイベントの数を調べる
    row = Sheet1.Cells(3, 1).End(xlDown).row - 2
    col = Sheet1.Cells(2, 3).End(xlToRight).column - 2
   
    count = 0
   
    For i = 0 To col - 1
        For j = 0 To row - 1
            str = Sheet1.Cells(3 + j, 3 + i)
           
            If str = "on" Then
                count = count + 1
            ElseIf str = "off" Then
                count = count + 1
            Else
            End If
        Next j
    Next i
    '------------------------------
    ReDim midi(count + 1) As MIDI_EVENT
   
    prg = CByte(Sheet1.Cells(1, 2))
    ch = CByte(1)
    count = 0
    time = 0
    time_last = 0
   
    setMidi midi(count), 0, &HC0, prg, &H0
    count = count + 1
   
    For i = 0 To col - 1
        For j = 0 To row - 1
            str = Sheet1.Cells(3 + j, 3 + i)
            time = Sheet1.Cells(2, 3 + i)
            key = CByte(Sheet1.Cells(3 + j, 1))
           
            If str = "on" Then
                setMidi midi(count), time - time_last, CByte(&H90 + ch), key, &H7F
                count = count + 1
                time_last = time
            ElseIf str = "off" Then
                setMidi midi(count), time - time_last, CByte(&H80 + ch), key, &H7F
                count = count + 1
                time_last = time
            Else
            End If
        Next j
    Next i
       
    setMidi midi(count), 0, &HFF, &H2F, &H0
    count = count + 1
   
    encodeMidi tChunk(1), midi, count
    Erase midi
    '------------------------------
   
    ChDir ThisWorkbook.Path
    filename = "sequence.mid"
    writeMidi filename, hChunk, tChunk
End Sub



Private Sub setMidi(ByRef midi As MIDI_EVENT, dt As Long, et As Byte, p1 As Byte, p2 As Byte)
    midi.DeltaTime = dt
    midi.EventType = et
    midi.Param1 = p1
    midi.Param2 = p2
End Sub

Private Sub encodeMidi(ByRef tChunk As TRACK_CHUNK, ByRef midi() As MIDI_EVENT, ByRef size As Long)
    Dim i As Long
    Dim j As Long
    Dim temp1  As Byte
    Dim temp4 As Long
    Dim buf() As Byte
   
    ReDim buf(size * 8 - 1) As Byte
   
    i = 0
   
    For j = 0 To size - 1
        '------------------------------
        'DeltaTime
        temp4 = 128
       
        Do While temp4 <= midi(j).DeltaTime
            temp4 = temp4 * 128
        Loop
   
        Do While 128 < temp4
            temp4 = temp4 \ 128
            buf(i) = CByte((midi(j).DeltaTime \ temp4) Mod 128 + &H80)
            i = i + 1
        Loop
   
        buf(i) = CByte(midi(j).DeltaTime Mod 128)
        i = i + 1
        '------------------------------
        'EventType
        If 0 < j And midi(j).EventType < &HF0 And midi(j).EventType = temp1 Then
            'running status
        Else
            temp1 = midi(j).EventType
            buf(i) = temp1
            i = i + 1
        End If
        '------------------------------
        'Data
        If temp1 < &HF0 Then
            If &HC0 <= temp1 And temp1 < &HE0 Then
                buf(i) = midi(j).Param1
                i = i + 1
            Else
                buf(i) = midi(j).Param1
                buf(i + 1) = midi(j).Param2
                i = i + 2
            End If
        Else
            'System Event, Meta Eventには未対応
        End If
        '------------------------------
        'Track終端メッセージ
        If midi(j).EventType = &HFF And midi(j).Param1 = &H2F Then
            buf(i) = &H2F
            buf(i + 1) = &H0
            i = i + 2
           
            Exit For
        End If
        '------------------------------
    Next j
   
    tChunk.ChunkID = "MTrk"
    tChunk.ChunkSize = i
   
    ReDim tChunk.Data(tChunk.ChunkSize - 1) As Byte
   
    For j = 0 To i - 1
        tChunk.Data(j) = buf(j)
    Next j
   
    Erase buf
End Sub

Private Sub decodeMidi(ByRef tChunk As TRACK_CHUNK, ByRef midi() As MIDI_EVENT, ByRef size As Long)
    Dim i As Long
    Dim j As Long
    Dim temp1 As Byte
    Dim temp4 As Long
    Dim buf() As MIDI_EVENT
   
    ReDim buf(tChunk.ChunkSize / 2 - 1) As MIDI_EVENT
   
    i = 0
    j = 0
   
    Do While i < tChunk.ChunkSize
        '------------------------------
        'DeltaTime
        temp4 = tChunk.Data(i) And &H7F
        i = i + 1
   
        Do While (tChunk.Data(i - 1) And &H80) = &H80
            temp4 = temp4 * 128 + (tChunk.Data(i) And &H7F)
            i = i + 1
        Loop
       
        buf(j).DeltaTime = temp4
        '------------------------------
        'EventType
        If (tChunk.Data(i) And &H80) = &H80 Then
            temp1 = tChunk.Data(i)
            i = i + 1
        End If
       
        buf(j).EventType = temp1
        '------------------------------
        'Data
        If temp1 < &HF0 Then
            If &HC0 <= temp1 And temp1 < &HE0 Then
                buf(j).Param1 = tChunk.Data(i)
                buf(j).Param2 = 0
                i = i + 1
            Else
                buf(j).Param1 = tChunk.Data(i)
                buf(j).Param2 = tChunk.Data(i + 1)
                i = i + 2
            End If
        Else
            'System Event, Meta Eventには未対応
            If temp1 = &HFF Then
                buf(j).Param1 = tChunk.Data(i)
                i = i + 1
            End If

            temp4 = tChunk.Data(i) And &H7F
            i = i + 1
       
            Do While (tChunk.Data(i - 1) And &H80) = &H80
                temp4 = temp4 * 128 + (tChunk.Data(i) And &H7F)
                i = i + 1
            Loop
           
            i = i + temp4
        End If
        '------------------------------
        j = j + 1
    Loop
   
    size = j
    ReDim midi(size - 1) As MIDI_EVENT
   
    For i = 0 To j - 1
        midi(i).DeltaTime = buf(i).DeltaTime
        midi(i).EventType = buf(i).EventType
        midi(i).Param1 = buf(i).Param1
        midi(i).Param2 = buf(i).Param2
    Next i
   
    Erase buf
End Sub

'ファイルに書き込む
Private Sub writeMidi(filename As String, ByRef hChunk As HEADER_CHUNK, ByRef tChunk() As TRACK_CHUNK)
    Dim i As Long
    Dim buf2(1) As Byte
    Dim buf4(3) As Byte

    Open filename For Binary As 1
        '------------------------------
        Put 1, , hChunk.ChunkID

        buf4(3) = CByte(hChunk.ChunkSize Mod 256)
        buf4(2) = CByte((hChunk.ChunkSize \ 256) Mod 256)
        buf4(1) = CByte(((hChunk.ChunkSize \ 256) \ 256) Mod 256)
        buf4(0) = CByte((((hChunk.ChunkSize \ 256) \ 256) \ 256) Mod 256)
        Put 1, , buf4

        buf2(1) = CByte(hChunk.FormatType)
        buf2(0) = CByte(0)
        Put 1, , buf2

        buf2(1) = CByte(hChunk.NumberOfTracks Mod 256)
        buf2(0) = CByte((hChunk.NumberOfTracks \ 256) Mod 256)
        Put 1, , buf2

        buf2(1) = CByte(hChunk.TimeDivision Mod 256)
        buf2(0) = CByte((hChunk.TimeDivision \ 256) Mod 256)
        Put 1, , buf2
        '------------------------------
        For i = 0 To hChunk.NumberOfTracks - 1
            Put 1, , tChunk(i).ChunkID

            buf4(3) = CByte(tChunk(i).ChunkSize Mod 256)
            buf4(2) = CByte((tChunk(i).ChunkSize \ 256) Mod 256)
            buf4(1) = CByte(((tChunk(i).ChunkSize \ 256) \ 256) Mod 256)
            buf4(0) = CByte((((tChunk(i).ChunkSize \ 256) \ 256) \ 256) Mod 256)

            Put 1, , buf4

            Put 1, , tChunk(i).Data
        Next i
        '------------------------------
    Close 1
End Sub

'ファイルから読み込む
Private Sub readMidi(filename As String, ByRef hChunk As HEADER_CHUNK, ByRef tChunk() As TRACK_CHUNK)

    Dim i As Long
    Dim buf2(1) As Byte
    Dim buf4(3) As Byte

    On Error GoTo Label1

    Open filename For Binary As 1
        '------------------------------
        Get 1, , buf4
        hChunk.ChunkID = Chr(buf4(0)) & Chr(buf4(1)) & Chr(buf4(2)) & Chr(buf4(3))

        Get 1, , buf4
        hChunk.ChunkSize = ((CLng(buf4(0)) * 256 + buf4(1)) * 256 + buf4(2)) * 256 + buf4(3)

        Get 1, , buf2
        hChunk.FormatType = CInt(buf2(0)) * 256 + buf2(1)

        Get 1, , buf2
        hChunk.NumberOfTracks = CInt(buf2(0)) * 256 + buf2(1)

        Get 1, , buf2
        hChunk.TimeDivision = CInt(buf2(0)) * 256 + buf2(1)
       
        ReDim tChunk(hChunk.NumberOfTracks - 1) As TRACK_CHUNK
        '------------------------------
        For i = 0 To hChunk.NumberOfTracks - 1
            Get 1, , buf4
            tChunk(i).ChunkID = Chr(buf4(0)) & Chr(buf4(1)) & Chr(buf4(2)) & Chr(buf4(3))

            Get 1, , buf4
            tChunk(i).ChunkSize = ((CLng(buf4(0)) * 256 + buf4(1)) * 256 + buf4(2)) * 256 + buf4(3)

            ReDim tChunk(i).Data(tChunk(i).ChunkSize - 1) As Byte
            Get 1, , tChunk(i).Data
        Next i
        '------------------------------
    Close 1
   
    Exit Sub

Label1:
    Close 1
    MsgBox "error", vbExclamation
End Sub