最近,音の認知について少しずつ勉強しています.今回は「錯聴」と呼ばれる音のイリュージョン (錯覚)の話です.
有名な錯聴の一つに「シェパード・トーン」(Shepard tone)と呼ばれる,無限に音程が上昇していくように聞こえる音があります.私も自分でその音を作ってみたいと思い,ちょっと勉強しました.とはいえ,まだ素人ですので,間違いがあれば指摘してください.完成した音をYoutubenにアップしておきました.
いくつかのホームページの説明を読みましたが,仕組みを理解できませんでした.オリジナル論文が,私にはわかりやすかったです.その論文はが,これです.
[Shepard, Roger N. (1964). “Circularity in Judgements of Relative Pitch”. Journal of the Acoustical Society of America 36: 2346–53. doi:10.1121/1.1919362]
音作りのポイントは,以下です.
- オクターブ違いの音を複数並べて,基本周波数を上昇させる.
1オクターブは,周波数で表せば2倍になります.例えば,基本周波数 (一番低い音)が50 Hzの場合は,1オクターブ上が100 Hz,2オクターブ上が200Hzです.
基本周波数を,とすれば,第
倍音 (第
高調波)の周波数は,
となります (下のRスクリプトのcは,1引いた値です).ここで,倍音の最大値をとして,
です.

さらに,それぞれの周波数を時間経過と共に増加させ,時間幅
で1オクターブ (2倍)上昇するために,
とします.
- 低音側と高音側でだんだんと音が小さくなるように決まったエンベロープの音圧バランスにする (ここが,数式を使わずに文章で説明するのが難しいです).下の図は,横軸も縦軸も対数になっていることに注意してください.ぱっと見,グラフは
(
)
にするのですが,実際は,縦軸,横軸とも対数をとった後に,この形のエンベロープにします.しかも,幅も数オクターブの幅にする必要があります.

まず,横軸について考えます.が,
に対応し,
が,
に対応するように,対数関数を作るとすれば,
になります.これを,指数の肩に載せれば,図のようなエンベロープが作れます.音圧レベルをの領域にする場合,周波数
を持つ成分の振幅を,
とします.ここで,
です.
重要なのは,下のgifアニメのように,一番高い音が聞こえなくなるのにあわせて,低い音が聞こえはじめることのようです (青い棒がゆっくり動いてますよ).

- 時間変化する周波数
を持つ信号
は,
で計算できない.ここで,は振幅です.正しいのは,
ではないでしょうか.私は,このことにしばらく気づけませんでした.周波数は,振動の位相の進む早さを特徴付けているので,刻みの離散時刻
に,
だけ時間が経過すると,位相 (角度)は
だけ進みます.注目すべきは時計の針の位置 (アナログ時計の針ですよ)なので,時刻の位相 (角度)は,
で,これは,
です.
ということで,詳しく説明するのが面倒になったので,サンプルのRスクリプトを掲載しておきます.
Rスクリプト
# サンプリング周波数f.s f.s <- 48000 dt <- 1/f.s ################# # 時間依存する周波数を決める関数 freq <- function(time,c,f.min=20,t.max = 60){ return(f.min*2^(c+time/t.max)) } ################# # 生成する長さ T <- 30 # time <- seq(0,T,dt) n.time <- length(time) ################# # 最低周波数 f.min <- 10 # オクターブ(倍音)の数 c.max <- 8 ################# # -4の部分は生成する長さ/t.maxを参考に変更してください c.list <- (-4):c.max f.cut <- f.min*2^(c.max) dB.min <- -120 ########################## for(i in 1:length(c.list)){ f <- freq(time,c.list[i],f.min=10,t.max = 10) A <- 10^((dB.min-dB.min*(1-cos(2*pi/c.max*log(f/f.min)/log(2)))/2)/20) A[f > f.cut | f < f.min] <- 0 ############################# delt <- 2*pi*f/f.s theta <- cumsum(delt)-delt[1] x.sub <- A*sin(theta) ############################# if(i == 1){ x <- x.sub }else{ x <- x + x.sub } } # xが信号データです. # 音として保存すればシェパード・トーンを聴くことができます.