周波数から音名を得る

Common Lispの勉強をしていて、簡単なプログラムを再実装しています。数年前にJuliaで書いた「周波数を与えると音名・セント値が返ってくる関数」をLispに移植してみました。Julia版は以下の記事に書いてあります。

marui.hatenablog.com

やっていることはJulia版と同じですし、あまり難しいことはしていないのでコードだけ張り付けておきます。(アルゴリズムの説明はJulia版の記事を読んでください)

(defun freq-to-midicent (frequency &optional (concert-pitch 440))
  "Convert frequency (Hz) to MIDI cent value (MIDI note number times 100
   for representing partial pitch). `(floor (/ (freq-to-midicent 262) 100))`
   can be used to get MIDI note number and cent value separately."
  (+ 6900 (* 1200 (log (/ frequency concert-pitch) 2))))

(defun freq-to-notename (freq &optional (concert-pitch 440) (flat nil))
  "Convert frequency (Hz) to note name with standard octave number (center C = 4).
   The boundary between octaves lies between the notes B and C."
  (let* ((notenames-flat '("C" "Db" "D" "Eb" "E" "F" "Gb" "G" "Ab" "A" "Bb" "B"))
         (notenames-sharp '("C" "C#" "D" "D#" "E" "F" "F#" "G" "G#" "A" "A#" "B"))
         (mc (freq-to-midicent freq concert-pitch))
         (notenum (round (/ mc 100)))
         (notecent (round (* (- (/ mc 100) notenum) 100)))
         (noteoct (floor (1- (/ notenum 12)))))
    (format nil "~A~A~@D"
            (nth (mod notenum 12) (if flat notenames-flat notenames-sharp))
            noteoct
            notecent)))

下のように使います。ピアノ鍵盤の中央のC音のオクターブ番号を4としています(アメリカ音響学会などのピッチ表記に合わせています)。また、デフォルトのコンサートピッチは440 Hzで、シャープ記号を使った音名表示にしています。

CL-USER> (freq-to-notename 1000)
"B5+21"
;; 1000 Hzの音名はB、オクターブ番号は5、ちょうどピッタリから21セント上

CL-USER> (freq-to-notename 270)
"C#4-45"
;; 270 Hzの音名はC#、オクターブ番号は4、ちょうどピッタリから45セント下

CL-USER> (freq-to-notename 270 442)
"C4+47"
;; コンサートピッチを442 Hzにしたとき

CL-USER> (freq-to-notename 270 440 t)
"Db4-45"
;; コンサートピッチを440 Hzにして、シャープではなくフラット表示にしたとき