周波数から音名を得る (Julia Advent Calendar 2020)

このエントリはJulia Advent Calendar 2020の24日目のものです。Juliaを音に関する作業によく使っているので、その中から一つ紹介したいと思います。

音の高さをあらわす方法にはいくつかあります。周波数を使うと「1秒あたりの周期の数」として物理的にも明確な値が得られます。

音楽の分野ではドレミファソラシドやCDEFGABCといったものが使われます(音名・階名表記 - Wikipedia)。国際式では、ピアノ中央のド(約262 Hz)をC4として、ピッチクラスのC~Bとオクターブ番号を組み合わせています。さらに細かい音高をあらわすときには、十二平均律に限りますがセント値(セント (音楽) - Wikipedia)を使うこともあります。セント値は半音の1/100となり、1200セントがちょうど1オクターブになります。

さらにMIDI規格ではC4をノート番号60とし、半音ごとにノート番号を1ずつ増減させます。チューニングの基準とすることが多いA4(440 Hz)は、ノート番号69番です。セント値まで含めたいという目的で、IRCAMで開発されたOpenMusicでは、MIDIセント値が採用されています。これはMIDIノート番号を100倍して、下2桁にセント値を含めようというものです。たとえばA4の35セント上は6935になります。

これら音高をあらわす単位の変換はときどき使うことがあるので、そのために作ったJuliaのプログラムを紹介します。

MIDIセント値→周波数

MIDIセント値はMIDIノート番号の上位互換なので、MIDIセント値から周波数への変換式さえあればノート番号から周波数を得ることができます。

1オクターブ(1200セント)は周波数2倍なので、1セントぶんの周波数比は{2^{1/1200}}となります。比なので、どこかに基準が必要になりますから、一般的なA4(MIDIセント6900)からのセント値の差を周波数比に変換することにします。A4の周波数は国際規格では440 Hzですが、442 Hzなども使われていますので、適宜指定できるようにconcert_pitch変数も用意しました。

function midicent_to_freq(midicent, concert_pitch=440)
  concert_pitch * 2^((midicent - 6900) / 1200);
end

ピアノ中央の鍵盤(C4)のMIDIセント値は6000です。周波数はどのくらいでしょうか、というのが下の使用例です。基準ピッチが440 Hzと442 Hzの違いで、約1.2 Hzの差がありますね。

julia> midicent_to_freq(6000)
261.6255653005986

julia> midicent_to_freq(6000, 442)
262.81477241560134

周波数→MIDIセント値

逆に周波数からMIDIセント値に変換するときには、上記の式を逆に使えばOKです。所望の周波数と基準ピッチとの比を底を2とした対数にして(1オクターブが周波数2倍なので)、MIDIセント値になるようにスケールさせています。

function freq_to_midicent(freq, concert_pitch = 440)
  log2(freq / concert_pitch) * 1200 + 6900;
end

次のように使うことができます。基準ピッチを442 Hzとしたときには、1000 HzはだいたいMIDIノート番号83の13.458セント上、ということが分かります。

julia> freq_to_midicent(1000)
8321.309485364913

julia> freq_to_midicent(1000, 442)
8313.458070324787

周波数→音名

MIDIセント値が得られても、そこからドレミファ~という音名がすぐに分かるわけではありません。人にとっての可読性の高い表示になるとうれしいです。そこで、以下のようなプログラムを組んでみました。

using Printf

function freq_to_notename(freq, concert_pitch=440; flat=false)
  if flat
    notenames = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"];
  else
    notenames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
  end
  mc = freq_to_midicent(freq, concert_pitch);
  notenum = round(Int, mc / 100);
  notecent = round((mc/100 - notenum) * 100);
  noteoct = floor(Int, notenum / 12) - 1;
  notename = @sprintf("%s%d%+2d", notenames[notenum % 12 + 1], noteoct, notecent);
  return(notename);
end

以下のように実行することで、周波数から音名とセント値が得られます。1000 HzはB5の21セント上、270 HzはC#4の45セント下、ということが分かります。セント値は整数になるように四捨五入しています。

julia> freq_to_notename(1000)
B5+21

julia> freq_to_notename(270)
C#4-45

C♯とD♭などの異名同音にも対応できるようなフラグもつけてありますので、flatをtrueにすれば♭付きの音名になります。(ASCIIコードの範囲で出力できるように、♯(シャープ)ではなく#(ハッシュ)、♭(フラット)ではなくb(ビー)を使っています)

julia> freq_to_notename(270, flat=true)
Db4-45