ちょうど一年前のアドベントカレンダーで MATLAB で音楽を生成してみた という記事を書きました.
今回はその続編です.
一年前からの進化:
- 関数ベースからオブジェクト指向のプログラムへ(音楽クラス)
- アプリ(GUI)の作成
アプリはこんな感じです.
アプリのコードの中身は乱雑でまあまあ長いのでまたの機会で解説をするとして,今回の記事では音楽クラスの解説をします.
まずは動かしてみよう
Note
クラス
一番基本となるクラスは Note
クラス.つまり音符クラスですね.このクラスは,音程と長さを指定して定義します.
n = Note("C5","WholeNote")
n =
Note のプロパティ:
Pitch: C5
Duration: WholeNote
これでなにができるかというと
methods(n)
クラス Note に対するメソッド:
Note generateSignal play
handle から継承された Note のメソッドです。
generateSignal
は波形を出力します. play
で音を再生します.
play(n)
Part
クラス
ただし, Note
クラスは一つ一つの音符を表すので,実際に音楽を作るには音符を組み合わせる必要がありますね.それに使うのが Part
クラスです. Part
クラスは,音符(音程と長さ)を複数指定することによって定義できます.
p1 = Part("C4","HalfNote","D4","QuarterNote","E4","QuarterNote",...
"F4","QuarterNote","G4","QuarterNote","A4","QuarterNote",...
"B4","QuarterNote","C5","WholeNote")
p1 =
Part のプロパティ:
Name: "20221224T020652"
Notes: [1x8 Note]
再生してみると
play(p1)
もう一つ Part
クラスを作成してみましょう.
p2 = Part("C5","HalfNote","B4","QuarterNote","A4","QuarterNote",...
"G4","QuarterNote","F4","QuarterNote","E4","QuarterNote",...
"D4","QuarterNote","C4","WholeNote")
p2 =
Part のプロパティ:
Name: "20221224T020652"
Notes: [1x8 Note]
Music
クラス
最後に,複数のパートを組み合わせることによって, Music
クラス(音楽クラス)を作成します. Music
クラスはテンポとパートを指定します.
m = Music("Allegro",p1,p2)
m =
Music のプロパティ:
Tempo: Allegro
TimeSignature: "4/4"
Parts: [1x2 Part]
中身をみてみよう
NotePitch
クラス
Note
クラスが必要とする音程は NotePitch
列挙型クラスで定義されています. double
型を継承し,音程の名前に対応する周波数を指定しています.
NotePitch.m
classdef NotePitch < double
enumeration
rest (0)
C0 (16.35)
Csharp0 (17.32)
Dflat0 (17.32)
D0 (18.35)
Dsharp0 (19.45)
Eflat0 (19.45)
E0 (20.6)
F0 (21.83)
Fsharp0 (23.12)
Gflat0 (23.12)
G0 (24.5)
Gsharp0 (25.96)
Aflat0 (25.96)
A0 (27.5)
Asharp0 (29.14)
Bflat0 (29.14)
B0 (30.87)
C1 (32.7)
Csharp1 (34.65)
Dflat1 (34.65)
D1 (36.71)
Dsharp1 (38.89)
Eflat1 (38.89)
E1 (41.2)
F1 (43.65)
Fsharp1 (46.25)
Gflat1 (46.25)
G1 (49)
Gsharp1 (51.91)
Aflat1 (51.91)
A1 (55)
Asharp1 (58.27)
Bflat1 (58.27)
B1 (61.74)
C2 (65.41)
Csharp2 (69.3)
Dflat2 (69.3)
D2 (73.42)
Dsharp2 (77.78)
Eflat2 (77.78)
E2 (82.41)
F2 (87.31)
Fsharp2 (92.5)
Gflat2 (92.5)
G2 (98)
Gsharp2 (103.83)
Aflat2 (103.83)
A2 (110)
Asharp2 (116.54)
Bflat2 (116.54)
B2 (123.47)
C3 (130.81)
Csharp3 (138.59)
Dflat3 (138.59)
D3 (146.83)
Dsharp3 (155.56)
Eflat3 (155.56)
E3 (164.81)
F3 (174.61)
Fsharp3 (185)
Gflat3 (185)
G3 (196)
Gsharp3 (207.65)
Aflat3 (207.65)
A3 (220)
Asharp3 (233.08)
Bflat3 (233.08)
B3 (246.94)
C4 (261.63)
Csharp4 (277.18)
Dflat4 (277.18)
D4 (293.66)
Dsharp4 (311.13)
Eflat4 (311.13)
E4 (329.63)
F4 (349.23)
Fsharp4 (369.99)
Gflat4 (369.99)
G4 (392)
Gsharp4 (415.3)
Aflat4 (415.3)
A4 (440)
Asharp4 (466.16)
Bflat4 (466.16)
B4 (493.88)
C5 (523.25)
Csharp5 (554.37)
Dflat5 (554.37)
D5 (587.33)
Dsharp5 (622.25)
Eflat5 (622.25)
E5 (659.25)
F5 (698.46)
Fsharp5 (739.99)
Gflat5 (739.99)
G5 (783.99)
Gsharp5 (830.61)
Aflat5 (830.61)
A5 (880)
Asharp5 (932.33)
Bflat5 (932.33)
B5 (987.77)
C6 (1046.5)
Csharp6 (1108.73)
Dflat6 (1108.73)
D6 (1174.66)
Dsharp6 (1244.51)
Eflat6 (1244.51)
E6 (1318.51)
F6 (1396.91)
Fsharp6 (1479.98)
Gflat6 (1479.98)
G6 (1567.98)
Gsharp6 (1661.22)
Aflat6 (1661.22)
A6 (1760)
Asharp6 (1864.66)
Bflat6 (1864.66)
B6 (1975.53)
C7 (2093)
Csharp7 (2217.46)
Dflat7 (2217.46)
D7 (2349.32)
Dsharp7 (2489.02)
Eflat7 (2489.02)
E7 (2637.02)
F7 (2793.83)
Fsharp7 (2959.96)
Gflat7 (2959.96)
G7 (3135.96)
Gsharp7 (3322.44)
Aflat7 (3322.44)
A7 (3520)
Asharp7 (3729.31)
Bflat7 (3729.31)
B7 (3951.07)
C8 (4186.01)
Csharp8 (4434.92)
Dflat8 (4434.92)
D8 (4698.63)
Dsharp8 (4978.03)
Eflat8 (4978.03)
E8 (5274.04)
F8 (5587.65)
Fsharp8 (5919.91)
Gflat8 (5919.91)
G8 (6271.93)
Gsharp8 (6644.88)
Aflat8 (6644.88)
A8 (7040)
Asharp8 (7458.62)
Bflat8 (7458.62)
B8 (7902.13)
end
end
NoteDuration
クラス
Note
クラスが必要とするもう一つのパラメーターは音の長さです. NoteDuration
クラスは double
型を継承し,音の長さに対応付けしています.この「長さ」は最終的に音楽のテンポによってスケールされます.
NoteDuration.m
classdef NoteDuration < double
enumeration
WholeNote (1)
WholeNoteDot (1.5)
HalfNote (1/2)
HalfNoteDot (3/4)
QuarterNote (1/4)
QuarterNoteDot (3/8)
EigthNote (1/8)
EigthNoteDot (3/16)
SixteenthNote (1/16)
SixteenthNoteDot (3/32)
ThirtysecondNote (1/32)
ThirtysecondNoteDot (3/64)
SixtyfourthNote (1/64)
SixtyfourthNoteDot (3/128)
end
end
Note
クラス
Note
クラスは基本的には2つのプロパティをもっています: Pitch
と Duration
.また,再生の時に使われるサンプル周波数 Fs
は Constant として定義されています.
classdef Note < handle
properties
Pitch = NotePitch.A4
Duration = NoteDuration.QuarterNote
end
properties (Constant, Hidden)
Fs = 44100
end
end
Note
クラスで重要となるのは音を生成する play
メソッドと generateSignal
メソッドです. generateSignal
メソッドは後で説明する Part
クラスと Music
クラスでも呼ばれます.
function x = generateSignal(obj,bpm)
arguments
obj (1,:) Note
bpm (1,1) double = 240
end
dur = [0, cumsum([obj.Duration])]*(240/bpm);
t = 0:1/Note.Fs:dur(end);
x = zeros(size(t));
for id = 1:length(obj)
ii = t >= dur(id) & t < dur(id+1);
for id2 = 1:length(obj(id).Pitch)
x(ii) = x(ii) + sin(2*pi*obj(id).Pitch(id2)*t(ii));
end
len = nnz(ii);
if mod(len,2) == 0
m = (1e5-[logspace(5,0,len/2),logspace(0,5,len/2)])/1e5;
else
m = (1e5-[logspace(5,0,len/2+1),logspace(0,5,len/2)])/1e5;
end
x(ii) = x(ii) .* m;
end
x = normalize(x,"range",[-1 1]);
end
play
メソッドは generateSignal
メソッドを呼んで,計算された信号を sound
関数で再生します.
function play(obj,bpm)
arguments
obj (1,:) Note
bpm (1,1) double = 240
end
x = generateSignal(obj,bpm);
sound(x,Note.Fs)
end
Part
クラス
Part
クラスは端的に言うと, 複数の Note
を組み合わせたものです.したがって,プロパティの一つは Notes
です.その他, Note
クラスと同様音の再生に活用されるサンプル周波数 Fs
と,パートに付けられる名前 Name
があります.
classdef Part < handle
properties
Name
Notes
end
properties (Constant, Hidden)
Fs = 44100
end
end
コンストラクターは,連続して音程と音の長さを定義できるように改造してます.
function obj = Part(p,d)
arguments (Repeating)
p (1,:) NotePitch
d (1,1) NoteDuration
end
obj.Name = string(datetime,"uuuuMMdd'T'HHmmss");
if isempty(p)
return
end
obj.Notes = Note(p{1},d{1});
for id = 2:length(p)
obj.Notes(id) = Note(p{id},d{id});
end
end
Note
クラスと同様, Part
クラスにも generateSignal
と play
メソッドがありますが,これらは基本的 Note
クラスのメソッドを呼んでいます.
MusicTempo
クラス
最後は Music
クラスですが,それに必要なプロパティの一つが MusicTempo
列挙型クラスです.これは, double
型を継承し,テンポに値するスピード(bpm)に対応付けされています.
MusicTempo.m
classdef MusicTempo < double
enumeration
Larghissimo (25)
Grave (35)
Largo (50)
Lento (52.5)
Larghetto (63)
Adagio (71)
Adagietto (74)
Andante (92)
Andantino (94)
AndanteModerato (102)
Moderato (114)
Allegretto (116)
AllegroModerato (118)
Allegro (144)
Vivace (158)
Allegrissimo (174)
Vivacissimo (174)
Presto (184)
Prestissimo (200)
end
end
Music
クラス
Music
クラスが最終クラスで,テンポと複数のパートを指定して定義します.プロパティーは以下のようになってます.テンポに加えて拍子記号 TimeSignature
も指定できます.
classdef Music < handle
properties
Tempo = MusicTempo.Moderato
TimeSignature = "4/4"
Parts
end
properties (Constant, Hidden)
Fs = 44100
end
end
テンポと複数のパートを定義できるようにコンストラクターを構築してます.
function obj = Music(bpm,p)
arguments
bpm (1,1) MusicTempo = MusicTempo.Moderato
end
arguments (Repeating)
p (1,1) Part
end
ps = [p{:}];
if isempty(ps)
return
end
obj.Parts = ps;
obj.Tempo = bpm;
end
他のクラスと同様, generateSignal
と play
メソッドを実装してますが, Music
クラスではテンポと拍子記号によって音楽のスピードが設定されるため, generateSignal
にそのような細工が加わっています.
function x = generateSignal(obj)
ts = double(string(split(obj.TimeSignature,"/")));
x = generateSignal(obj.Parts(1),obj.Tempo*(4/ts(2)));
for iP = 2:length(obj.Parts)
y = generateSignal(obj.Parts(iP),obj.Tempo*(4/ts(2)));
if numel(y) == numel(x)
x = x+y;
elseif numel(y) < numel(x)
x(1:numel(y)) = x(1:numel(y))+y;
else
y(1:numel(x)) = y(1:numel(x))+x;
x = y;
end
end
x = normalize(x,"range",[-1 1]);
end
すべてのコード
Note.m
コード
classdef Note < handle
properties
Pitch = NotePitch.A4
Duration = NoteDuration.QuarterNote
end
properties (Constant, Hidden)
Fs = 44100
end
methods
function obj = Note(p,d)
arguments
p (1,1) NotePitch
d (1,1) NoteDuration
end
obj.Pitch = p;
obj.Duration = d;
end
function play(obj,bpm)
arguments
obj (1,:) Note
bpm (1,1) double = 240
end
x = generateSignal(obj,bpm);
sound(x,Note.Fs)
end
function x = generateSignal(obj,bpm)
arguments
obj (1,:) Note
bpm (1,1) double = 240
end
dur = [0, cumsum([obj.Duration])]*(240/bpm);
t = 0:1/Note.Fs:dur(end);
x = zeros(size(t));
for id = 1:length(obj)
ii = t >= dur(id) & t < dur(id+1);
for id2 = 1:length(obj(id).Pitch)
x(ii) = x(ii) + sin(2*pi*obj(id).Pitch(id2)*t(ii));
end
len = nnz(ii);
if mod(len,2) == 0
m = (1e5-[logspace(5,0,len/2),logspace(0,5,len/2)])/1e5;
else
m = (1e5-[logspace(5,0,len/2+1),logspace(0,5,len/2)])/1e5;
end
x(ii) = x(ii) .* m;
end
x = normalize(x,"range",[-1 1]);
end
end
end
Part.m
コード
classdef Part < handle
properties
Name
Notes
end
properties (Constant, Hidden)
Fs = 44100
end
methods
function obj = Part(p,d)
arguments (Repeating)
p (1,:) NotePitch
d (1,1) NoteDuration
end
obj.Name = string(datetime,"uuuuMMdd'T'HHmmss");
if isempty(p)
return
end
obj.Notes = Note(p{1},d{1});
for id = 2:length(p)
obj.Notes(id) = Note(p{id},d{id});
end
end
function play(obj,bpm)
arguments
obj (1,:) Part
bpm (1,1) double = 240
end
x = generateSignal(obj,bpm);
sound(x,Part.Fs)
end
function x = generateSignal(obj,bpm)
arguments
obj (1,:) Part
bpm (1,1) double = 240
end
if isempty(obj.Notes)
x = [];
else
x = generateSignal(obj.Notes,bpm);
end
end
end
end
Music.m
コード
classdef Music < handle
properties
Tempo = MusicTempo.Moderato
TimeSignature = "4/4"
Parts
end
properties (Constant, Hidden)
Fs = 44100
end
methods
function obj = Music(bpm,p)
arguments
bpm (1,1) MusicTempo = MusicTempo.Moderato
end
arguments (Repeating)
p (1,1) Part
end
ps = [p{:}];
if isempty(ps)
return
end
obj.Parts = ps;
obj.Tempo = bpm;
end
function play(obj)
x = generateSignal(obj);
sound(x,obj.Fs)
end
function x = generateSignal(obj)
ts = double(string(split(obj.TimeSignature,"/")));
x = generateSignal(obj.Parts(1),obj.Tempo*(4/ts(2)));
for iP = 2:length(obj.Parts)
y = generateSignal(obj.Parts(iP),obj.Tempo*(4/ts(2)));
if numel(y) == numel(x)
x = x+y;
elseif numel(y) < numel(x)
x(1:numel(y)) = x(1:numel(y))+y;
else
y(1:numel(x)) = y(1:numel(x))+x;
x = y;
end
end
x = normalize(x,"range",[-1 1]);
end
end
end
NotePitch.m
コード
classdef NotePitch < double
enumeration
rest (0)
C0 (16.35)
Csharp0 (17.32)
Dflat0 (17.32)
D0 (18.35)
Dsharp0 (19.45)
Eflat0 (19.45)
E0 (20.6)
F0 (21.83)
Fsharp0 (23.12)
Gflat0 (23.12)
G0 (24.5)
Gsharp0 (25.96)
Aflat0 (25.96)
A0 (27.5)
Asharp0 (29.14)
Bflat0 (29.14)
B0 (30.87)
C1 (32.7)
Csharp1 (34.65)
Dflat1 (34.65)
D1 (36.71)
Dsharp1 (38.89)
Eflat1 (38.89)
E1 (41.2)
F1 (43.65)
Fsharp1 (46.25)
Gflat1 (46.25)
G1 (49)
Gsharp1 (51.91)
Aflat1 (51.91)
A1 (55)
Asharp1 (58.27)
Bflat1 (58.27)
B1 (61.74)
C2 (65.41)
Csharp2 (69.3)
Dflat2 (69.3)
D2 (73.42)
Dsharp2 (77.78)
Eflat2 (77.78)
E2 (82.41)
F2 (87.31)
Fsharp2 (92.5)
Gflat2 (92.5)
G2 (98)
Gsharp2 (103.83)
Aflat2 (103.83)
A2 (110)
Asharp2 (116.54)
Bflat2 (116.54)
B2 (123.47)
C3 (130.81)
Csharp3 (138.59)
Dflat3 (138.59)
D3 (146.83)
Dsharp3 (155.56)
Eflat3 (155.56)
E3 (164.81)
F3 (174.61)
Fsharp3 (185)
Gflat3 (185)
G3 (196)
Gsharp3 (207.65)
Aflat3 (207.65)
A3 (220)
Asharp3 (233.08)
Bflat3 (233.08)
B3 (246.94)
C4 (261.63)
Csharp4 (277.18)
Dflat4 (277.18)
D4 (293.66)
Dsharp4 (311.13)
Eflat4 (311.13)
E4 (329.63)
F4 (349.23)
Fsharp4 (369.99)
Gflat4 (369.99)
G4 (392)
Gsharp4 (415.3)
Aflat4 (415.3)
A4 (440)
Asharp4 (466.16)
Bflat4 (466.16)
B4 (493.88)
C5 (523.25)
Csharp5 (554.37)
Dflat5 (554.37)
D5 (587.33)
Dsharp5 (622.25)
Eflat5 (622.25)
E5 (659.25)
F5 (698.46)
Fsharp5 (739.99)
Gflat5 (739.99)
G5 (783.99)
Gsharp5 (830.61)
Aflat5 (830.61)
A5 (880)
Asharp5 (932.33)
Bflat5 (932.33)
B5 (987.77)
C6 (1046.5)
Csharp6 (1108.73)
Dflat6 (1108.73)
D6 (1174.66)
Dsharp6 (1244.51)
Eflat6 (1244.51)
E6 (1318.51)
F6 (1396.91)
Fsharp6 (1479.98)
Gflat6 (1479.98)
G6 (1567.98)
Gsharp6 (1661.22)
Aflat6 (1661.22)
A6 (1760)
Asharp6 (1864.66)
Bflat6 (1864.66)
B6 (1975.53)
C7 (2093)
Csharp7 (2217.46)
Dflat7 (2217.46)
D7 (2349.32)
Dsharp7 (2489.02)
Eflat7 (2489.02)
E7 (2637.02)
F7 (2793.83)
Fsharp7 (2959.96)
Gflat7 (2959.96)
G7 (3135.96)
Gsharp7 (3322.44)
Aflat7 (3322.44)
A7 (3520)
Asharp7 (3729.31)
Bflat7 (3729.31)
B7 (3951.07)
C8 (4186.01)
Csharp8 (4434.92)
Dflat8 (4434.92)
D8 (4698.63)
Dsharp8 (4978.03)
Eflat8 (4978.03)
E8 (5274.04)
F8 (5587.65)
Fsharp8 (5919.91)
Gflat8 (5919.91)
G8 (6271.93)
Gsharp8 (6644.88)
Aflat8 (6644.88)
A8 (7040)
Asharp8 (7458.62)
Bflat8 (7458.62)
B8 (7902.13)
end
end
NoteDuration.m
コード
classdef NoteDuration < double
enumeration
WholeNote (1)
WholeNoteDot (1.5)
HalfNote (1/2)
HalfNoteDot (3/4)
QuarterNote (1/4)
QuarterNoteDot (3/8)
EigthNote (1/8)
EigthNoteDot (3/16)
SixteenthNote (1/16)
SixteenthNoteDot (3/32)
ThirtysecondNote (1/32)
ThirtysecondNoteDot (3/64)
SixtyfourthNote (1/64)
SixtyfourthNoteDot (3/128)
end
end
MusicTempo.m
コード
classdef MusicTempo < double
enumeration
Larghissimo (25)
Grave (35)
Largo (50)
Lento (52.5)
Larghetto (63)
Adagio (71)
Adagietto (74)
Andante (92)
Andantino (94)
AndanteModerato (102)
Moderato (114)
Allegretto (116)
AllegroModerato (118)
Allegro (144)
Vivace (158)
Allegrissimo (174)
Vivacissimo (174)
Presto (184)
Prestissimo (200)
end
end
メヌエットを弾いてみよう
バッハのメヌエット ト長調を弾いてみましょう
p1 = Part("D5","QuarterNote","G4","EigthNote","A4","EigthNote",...
"B4","EigthNote","C5","EigthNote","D5","QuarterNote","G4","QuarterNote",...
"G4","QuarterNote","E5","QuarterNote","C5","EigthNote","D5","EigthNote",...
"E5","EigthNote","Fsharp5","EigthNote","G5","QuarterNote","G4","QuarterNote",...
"G4","QuarterNote","C5","QuarterNote","D5","EigthNote","C5","EigthNote",...
"B4","EigthNote","A4","EigthNote","B4","QuarterNote","C5","EigthNote",...
"B4","EigthNote","A4","EigthNote","G4","EigthNote","Fsharp4","QuarterNote",...
"G4","EigthNote","A4","EigthNote","B4","EigthNote","G4","EigthNote",...
"A4","HalfNoteDot","D5","QuarterNote","G4","EigthNote","A4","EigthNote",...
"B4","EigthNote","C5","EigthNote","D5","QuarterNote","G4","QuarterNote",...
"G4","QuarterNote","E5","QuarterNote","C5","EigthNote","D5","EigthNote",...
"E5","EigthNote","Fsharp5","EigthNote","G5","QuarterNote","G4","QuarterNote",...
"G4","QuarterNote","C5","QuarterNote","D5","EigthNote","C5","EigthNote",...
"B4","EigthNote","A4","EigthNote","B4","QuarterNote","C5","EigthNote",...
"B4","EigthNote","A4","EigthNote","G4","EigthNote","A4","QuarterNote",...
"B4","EigthNote","A4","EigthNote","G4","EigthNote","Fsharp4","EigthNote",...
"G4","HalfNoteDot")
p2 = Part("B3","HalfNote","A3","QuarterNote","B3","HalfNoteDot",...
"C4","HalfNoteDot","B3","HalfNoteDot","A3","HalfNoteDot","G3","HalfNoteDot",...
"D4","QuarterNote","B3","QuarterNote","G3","QuarterNote","D4","QuarterNote",...
"D3","EigthNote","C4","EigthNote","B3","EigthNote","A3","EigthNote",...
"B3","HalfNote","A3","QuarterNote","G3","QuarterNote","B3","QuarterNote",...
"G3","QuarterNote","C4","HalfNoteDot","B3","QuarterNote","C4","EigthNote",...
"B3","EigthNote","A3","EigthNote","G3","EigthNote","A3","HalfNote",...
"Fsharp3","QuarterNote","G3","HalfNote","B3","QuarterNote","C4","QuarterNote",...
"D4","QuarterNote","D3","QuarterNote","G3","HalfNote","G2","QuarterNote")
m = Music("Allegretto",p1,p2);
m.TimeSignature = "3/4"
play(m)