1. はじめに
「マシンガンで音を撃ち、弾丸を回収しにいく話」 や 「MATLABで音楽を生成してみた」 を楽しく読ませて頂いて、そういや昔MATLABで鉄砲やピアノの音を発音させるプログラム作ったなぁと思い出したので、忘れてファイルが消えてしまう前に、こちらで披露することにしました。
ファイルのタイムスタンプを見たら2011年になっていたので、10年以上前に作ったファイルのようです。なので詳細は覚えていません。
最近投稿した記事「FPGAオンボードメモリのデバッグ」もよかったらご覧ください。
2. プログラムで実現したこと
Windows MIDI API
Windows OSに内蔵されている音源をMATLABから叩くプログラムを作成しました。
このあたりご存知ない方のために補足すると、Windowsマシンにはソフトウェア的に音を鳴らす音源、つまりPCMシンセサイザーが内蔵されており、APIを叩くことでMIDI音源で発音させることができます。
MIDI (Musical Instrument Digital Interface)
Roland社が作ったデジタル楽器の標準規格で、デジタル楽器の制御方法や音源ファイルのフォーマットが定義されています。
(先日、中2の息子に、数々のシンセやエフェクトの銘機を世に出したRoland社の話をしたら、あのホスト・タレントの!?って言われて悲しい父です。)
MEX関数
Windows APIを叩くためにMATLABからC関数を呼び出すMEX関数を使っています。
3. 構成
MIDI音源を叩くAPIはC言語で提供されています。なので、MATLABとC言語のインターフェイス機能、C-MEX関数を使ってMIDI音源を叩き、パラメータを与えるためのラッパーコードをMATLABで作っています。
- midiout.c: MIDI音源を叩くAPIをMEX関数化したファイル
- soundmidi.m: midiout.cのラッパーファイル
- soundmidi_tb.m: soundmidi.mのテスト用のファイル
4. コード
以下にコードを示します。▶をクリックすると折りたたまれたコード表示を展開できます。
コアとなるCコード: midiout.c
/*=================================================================
* midout.c
* MIDI音源を用いてノート音を発するMEX-ファイル
*=================================================================*/
#include "mex.h"
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
#define MIDIMSG(status,channel,data1,data2) ( (DWORD)((status<<4) | channel | (data1<<8) | (data2<<16)) )
HMIDIOUT hMidiOut;
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
/* 入出力値用変数の宣言 */
int i, m, n;
double *tone, *notenum, *velocty, *lgth, tmp1, tmp2, tmp3, tmp4;
unsigned int midimsg, midimsgoff, tone_i, notenum_i, velocty_i, lgth_i;
/* midimsg = concatenate(midich, notenum, velocty */
/* 入出力引数の数チェック */
if (nrhs != 4) {
mexErrMsgTxt("入力引数の数は4つです。");
}
/* 要素数の取得 */
m = mxGetM(prhs[0]);
n = mxGetN(prhs[0]);
/* 入力ポインタprhs[0]-[3]からデータを取得 */
tone = mxGetPr(prhs[0]);
notenum = mxGetPr(prhs[1]);
velocty = mxGetPr(prhs[2]);
lgth = mxGetPr(prhs[3]);
/* MIDIデバイスオープン */
midiOutOpen(&hMidiOut, MIDIMAPPER, 0, 0, 0);
for (i = 0; i < m; i++) {
/* データの結合 */
// midich_i = *(midich+i);
tmp1 = *(tone + i);
tone_i = (unsigned int)tmp1;
tmp2 = *(notenum + i);
notenum_i = (unsigned int)tmp2;
tmp3 = *(velocty+i);
velocty_i = (unsigned int)tmp3;
tmp4 = *(lgth+i);
lgth_i = (unsigned int)tmp4;
//midimsg = (velocty_i<<16) | (notenum_i<<8) | (midich_i);
//midimsgoff = (0x00<<16) | (notenum_i<<8) | (midich_i);
/* 音色変更 */
midiOutShortMsg(hMidiOut,MIDIMSG(0xC,0,tone_i,0));
/* MIDIチャネル1でノート番号0x3cの音をベロシティ0x40で発音 */
//midiOutShortMsg(hMidiOut, 0x00403d90);
// status 0x9 Note on
midiOutShortMsg(hMidiOut,MIDIMSG(0x9,0x0,notenum_i,velocty_i));
//midiOutShortMsg(hMidiOut,MIDIMSG(0x9,0x0,0x3c,0x40)); //for test
//Sleep(*(lgth+i));
Sleep(lgth_i);
/* MIDIチャネル1でノート番号0x3cの音を消音 */
midiOutShortMsg(hMidiOut,MIDIMSG(0x8,0x0,notenum_i,0));
// status 0x8 Note off
// midiOutShortMsg(hMidiOut,MIDIMSG(0x9,0x0,0x0,0));
// midiOutShortMsg(hMidiOut, 0x00003d90);
}
/* MIDIデバイスクローズ */
midiOutClose(hMidiOut);
}
MATLABのラッパーファイル: soundmidi.m
function [ output_args ] = soundmidi(tone, notenum, velocty, lgth)
% SOUNDMIDI Generate MIDI sound by sending message to MIDI device using
% Windows API
% How to use
% soundmidi(Tone, Note Number, Velocity, Sound Length)
% Tone: Select sound generater number.
% For example(Hex: ToneMap),
% 0x00: Acoustic Grand Piano, 0x02: Electric Grand Piano
% 0x0C: Marinmba , 0x12: Rock Organ
% 0x13: Church Organ , 0x1D: Overdriven Guitar
% 0x38: Trumpet , 0x47: Clarinet
% 0x49: Flute , 0x4D: Shakuhachi
% 0x50: Lead1(square) , 0x5A: Pad3(polysynth)
% 0x68: Sitar , 0x7F: Gunshot
% Tone = 0(piano), Note Number=0x3d, Velocity=40, length=1000
% Range of Note Number: 0 - 127 = (C-2) - (G8)
% Note Number Examples:
% 60(0x3c) = C3
% 13(0x0d) = C#-1
% Examples:
% soundmidi(0, 'C#2', hex2dec('60'), 1000)
% soundmidi('40', 'C3', 90, 2000)
% soundmidi([20 30 40], [40 40 40],[90 98 82], [1000 1000 1000 1000])
%
% tone = ones(8,1)*80;
% note = {'a3'; 'e3'; 'e3' ;'f3';'e3'; 'd3'; 'g#3'; 'a3'};
% velo = [90;90;90;90;90;0;90;90];
% lgth = [400;200;200;400;400;400;400;400];
%
% soundmidi(tone,note,velo,lgth)
% length check
if (size(tone,1)~=size(notenum,1))||(size(notenum,1)~=(size(velocty,1)))||(size(velocty,1)~=(size(lgth,1)))
error('Data length is different')
end
% hex2dec
for n = 1:size(tone,1)
if ischar(tone(n,:))
tonenum(n,1) = hex2dec(tone(n,:));
else
tonenum(n,1) = tone(n);
end
if iscell(notenum(n,:))
if ischar(cell2mat(notenum(n,:)))
num(n,1) = notenum2num(cell2mat(notenum(n,:)));
else
num(n,1) = cell2mat(notenum(n,:));
end
else
if ischar(notenum(n,:))
num(n,1) = notenum2num(notenum(n,:));
else
num(n,1) = notenum(n,:);
end
end
if (velocty(n) > 127)||(velocty(n) < 0)
error('Range of Verocity is from 0 to 127')
end
output_args = sprintf('MIDI Out \n\t Tone No. = 0x%02x\n\t Note No. = 0x%02x\n\t Velocity = %d\n\t Length = %d\n',...
tonenum(n), num(n), velocty(n), lgth(n));
end
midiout(tonenum, num, velocty, lgth);
end
function num = notenum2num(notenum)
% notenum = c-2 ... c3 c#3 d3 e3 f3 g3 a3 b3
% num = 0 ... 60 61 62 ....
% num(hex) = 0 ... 3c 3d 3f ....
numminus = findstr(notenum,'-');
if numminus > 0
num = (str2num(notenum(numminus:numminus+1))*12)+24;
else
num = (str2num(notenum(end))*12)+24;
end
switch lower(notenum(1))
case 'c'
num = num+0;
case 'd'
num = num+2;
case 'e'
num = num+4;
case 'f'
num = num+5;
case 'g'
num = num+7;
case 'a'
num = num+9;
case 'b'
num = num+11;
otherwise
error('Note name is c, d, e, f, g, a, b')
end
numminus = findstr(notenum,'#');
if numminus > 0
num = num+1;
end
end
テスト用のファイル: soundmidi_tb.m
% tone: 音色
% note: 音高
% velo: 音の強さ
% lgth: 音の長さ
%% soundmidi test program
tone = [1;2;19;127];
note = ['c3'; 'c2'; 'f4' ;'g2'];
velo = [90;90;90;90];
lgth = [400;500;500;500];
soundmidi(tone,note,velo,lgth)
%% song
tone = ones(8,1)*19;
note = {'a3'; 'e3'; 'e3' ;'f3';'e3'; 'd3'; 'g#3'; 'a3'};
velo = [90;90;90;90;90;0;90;90];
lgth = [400;200;200;400;400;400;400;600];
%%
soundmidi(tone,note,velo,lgth)
%%
tone = [1 ;1;1;1]*127;
note = ['d3'; 'd3'; 'd3' ;'d3'];
velo = [90;90;90;90];
lgth = [100;100;100;500]*3;
soundmidi(tone,note,velo,lgth)
5. 実行方法
まずCコードをMATLABから実行できるようにコンパイルします。MATLABで次のコマンドを実行します。
>> mex midiout.c
MIDI音源は以下のパラメータで制御できるようになっており、それぞれがsoundmidiの入力引数となっています。
- 音色
- 音高
- 音の強さ
- 音の長さ
以下のように使用します。
tone = [1;2;19;127];
note = ['c3'; 'c2'; 'f4' ;'g2'];
velo = [90;90;90;90];
lgth = [400;500;500;500];
soundmidi(tone,note,velo,lgth)
詳しくはソースコードのコメント欄をご覧ください。
試奏用のサンプルファイル soundmidi_tb.mを実行してみると、ピアノ、パイプオルガン、鉄砲の音などが発音されると思います。
>> soundmidi_tb
ご自由に曲など作ってお楽しみ下さい。
6. 今後
確か自分の記憶によると、モノフォニックでしか再生できないので、ポリフォニックにモディファイしたほうが良いんだろうなぁ・・・と思いつつやっていません。
それから、関数型ではなく、System Objectにしてストリーミングで使えるようにしたほうがプログラムしやすいかもしれませんね。これを作成した当時はSystem Objectがまだなかったような気がします。
どなたか自由に修正してもらえると、いやコラボできると嬉しいです。
そうするとSimulinkのMIDI Controlブロックとかから制御して、MATLABで物理的なコントローラによるシンセサイザーが作れると思います。
終わり