はじめに
最近のMathematicaはMIDIのインポートとエキスポートに対応しており、MIDIのインポートの際にSoundという要素でラップされたSoundNoteという要素を含むリストが生成されます。
これを操作することによっても音楽で色々遊べるのですが、
個人的に困る点としては、ピッチが半音刻みでしか変えられないことです。
ピッチベンドを使えばできなくもないかもしれませんが、せっかくなので音声波形にしてしまえば融通が効いて便利だろう、と考えました。
とりあえず自分で実験的に使う用に適当に実装しただけなので、ご不便おかけするかもしれません。
実装
以下がコードです。主にサウンド生成部と記号(C→ド、D→レ など)変換部に分かれています。(最初はピッチを数字だけで考えてましたが、MIDIは記号になってたのでやむを得ず…。)
お使いの際は両方実行してください。
sound[SoundNote[pitch_?NumberQ, time_?NumberQ, tspec_: Null,
style_: Null]] :=
Sound[Play[Sin[440*2^((pitch - 9)/12) 2 Pi t], {t, 0, time},
SampleRate -> 44100]]
sound[SoundNote[pitch_?NumberQ]] := sound[SoundNote[pitch, 1]]
sound[SoundNote[]] := sound[SoundNote[0]]
sound[SoundNote[pitch_?NumberQ, timeLi_List, tspec_: Null,
style_: Null]] :=
Sound[sound[SoundNote[pitch, (#[[2]] - #[[1]]) &@timeLi]], timeLi]
sound[SoundNote[pitch_?NumberQ], timeLi_List] :=
Sound[sound[SoundNote[pitch, (#[[2]] - #[[1]]) &@timeLi]], timeLi]
sound[SoundNote[None, time_: 1]] := Sound[SoundNote[None, time]]
sound[{sn__SoundNote}, time_?NumberQ] := Sound[sound /@ {sn}, time]
sound[{sn__SoundNote}] := Sound[sound /@ {sn}]
sound[SoundNote[{p__}, time_?NumberQ]] :=
Sound[sound[SoundNote[#], {0, time}] & /@ {p}, time]
sound[SoundNote[{p__}]] :=
Sound[sound[SoundNote[#], {0, 1}] & /@ {p}, 1]
noteExpand[str_String] := {
First[StringCases[str, CharacterRange["A", "G"]]],
First[StringCases[str, {"#", "b"}], ""],
First[StringCases[str, DigitCharacter ..], "4"]
}
basePitch[s_String] :=
Piecewise[{{0, s == "C"}, {2, s == "D"}, {4, s == "E"}, {5,
s == "F"}, {7, s == "G"}, {9, s == "A"}, {11, s == "B"}}, 0]
accidentalToShift[s_String] :=
Piecewise[{{1, s == "#"}, {-1, s == "b"}}, 0]
noteObjToNum[{sign_String, accidental_String, octave_String}] :=
12 (ToExpression[octave] - 4) + basePitch[sign] +
ToExpression[accidentalToShift[accidental]]
noteToNum[str_String] := noteObjToNum[noteExpand[str]]
pitch[val_] := If[NumberQ[val], val, noteToNum[val]]
sound[SoundNote[p_String, time_?NumberQ, tspec_: Null,
style_: Null]] := sound[SoundNote[pitch[p], time, tspec, style]]
sound[SoundNote[p_String, timeLi_List, tspec_: Null, style_: Null]] :=
sound[SoundNote[pitch[p], timeLi]]
sound[SoundNote[p_], timeLi_List] :=
sound[SoundNote[pitch[p], timeLi]]
sound[SoundNote[p_String]] := sound[SoundNote[p, 1]]
いろんなオプションに対応する処理を沢山書いているので結構かさんじゃってます。
肝の部分だけ軽く説明します。
sound[SoundNote[pitch_?NumberQ, time_?NumberQ, tspec_: Null,
style_: Null]] :=
Sound[Play[Sin[440*2^((pitch - 9)/12) 2 Pi t], {t, 0, time},
SampleRate -> 44100]]
ここでPlayを使って正弦波を生成してます。440がラの音で、ラとドが半音 9
個の関係にあるので 9
という数字が入っていて、 12
が1オクターブ分の半音です。
noteExpand[str_String] := {
First[StringCases[str, CharacterRange["A", "G"]]],
First[StringCases[str, {"#", "b"}], ""],
First[StringCases[str, DigitCharacter ..], "4"]
}
C
G5
D#5
などの文字列を分解します。
-
A
からG
というのがドレミファソラシドでいう、ラからソに該当します。 - 変化記号は
#
とb
の2つです。 - あとはオクターブの高さを表す数字で、デフォルト(指定がない時)は
4
です。
basePitch[s_String] :=
Piecewise[{{0, s == "C"}, {2, s == "D"}, {4, s == "E"}, {5,
s == "F"}, {7, s == "G"}, {9, s == "A"}, {11, s == "B"}}, 0]
accidentalToShift[s_String] :=
Piecewise[{{1, s == "#"}, {-1, s == "b"}}, 0]
noteObjToNum[{sign_String, accidental_String, octave_String}] :=
12 (ToExpression[octave] - 4) + basePitch[sign] +
ToExpression[accidentalToShift[accidental]]
noteToNum[str_String] := noteObjToNum[noteExpand[str]]
pitch[val_] := If[NumberQ[val], val, noteToNum[val]]
分解した記号を数字に置き換え、ピッチの数値に変換します。
C4
の1音下が B4
ではなく B3
というのが音楽界のややこしい決まりで、そこでハマるかなと思ってましたが意外と素直に処理できました。
鳴らす
クリスマスということで、きよしこの夜を鳴らします。(アドベントカレンダーっぽい!)
音楽は みゅうー さんのフリーBGM を使わせて頂きました。
以下がMIDIを普通に鳴らしたものです。
mid = Import["data/kiyosi.mid"]
Export["kiyoshi-org.mp3", mid]
以下が今回実装したものです。
mid = Import["data/kiyosi--www-ne-jp--asahi--music--myuu.mid"];
snd = sound[First[mid]]
Export["kiyoshi-sine.wav", snd]
おやっ!・・・
問題点
- Mathematica上ではきれいな正弦波に聞こえるのですが、wavやmp3でExportしたときにノイズが入ってしまいました(PlayRangeかSampleRateの問題?Exportのオプションがわからず未解決)
- ドラム未対応なので、ドラムが入っているMIDIは再生できません
- 音割れの問題がなくても、音の鳴り始め・鳴り終わりにプツプツとノイズが入ります
- 次のようなコードは恐らくSampleRateの関係でブレてしまいます
sound[{SoundNote[7], SoundNote[7], SoundNote[7], SoundNote[3, 4]}, 1.5]
以上、瀬戸際で不具合があって残念ですが、実験には有用なのではないでしょうか。