Rで音を生成するには audio パッケージを使用する. このパッケージの関数 play では入力したベクトルデータを音に変換して再生する。

キーボードとノートナンバー

一般的にC4のドの音は261.6256Hzである.それぞれの鍵盤には番号が振られており(ノートナンバー),式\eqref{note2hz} の換算式が利用できる

\begin{equation} f = 440\times2^{\frac{n - 69}{12}} \label{note2hz} \end{equation} ここで $n$ はノートナンバー,$f$は 周波数である.これをまとめたものが表1である.
表1: 音階と周波数の関係
オクターブ音名音名(ドレミ表記)ノートナンバー周波数
4C60261.63
C#ド♯61277.18
D62293.66
D#レ♯63311.13
E64329.63
Fファ65349.23
F#ファ♯66369.99
G67392.00
G#ソ♯68415.30
A69440.00
A#ラ♯70466.16
B71493.88

この換算式とパッケージの関数を組み合わせて打ち込んだ音階を再生する.まず,\fn{note2hz}関数の作成を行う.

note2hz <- function(d){
  freq <- 2^((d - 69)/12) * 440
  return(freq)
}

一般に使用するサンプリング周波数である44.1kHzでは$p$秒のデータは式\eqref{seqmks}で作成可能である.

\begin{equation} \underbrace{\sin\left( 0\right), \cdots, \sin\left(2\pi f p\right)}_{44100\times p\mbox{個}}\label{seqmks} \end{equation}
# 指定した音階をp秒ならす
mks <- function(num, p = 1){
  freq <- note2hz(num)
  playdata <- sin(seq(0, 2*pi*freq*p,
                length = round(44100*p)))
  play(playdata, rate = 44100) # データの再生
}

この mks 関数順に呼び出せば順番に音を鳴らすことができる. play関数は音を非同期で再生するので Sys.sleep を使用している.

for(i in c(60, 62, 64, 65, 67, 69, 71, 72)){
  mks(i, 1)
  Sys.sleep(1)
}
時報の音 時報の音は$\mbox{ラ}×$3回と1オクターブ上のラの音で構成される. したがって以下のようなコードを実行すると時報の音を聞くことができる.
onkai <- c(rep(c(69, 0), 3), 69+12)
nagasa <- c(rep(c(0.25, 0.75), 3), 1)
for(i in 1:length(onkai)){
  mks(onkai[i], nagasa[i])
  Sys.sleep(nagasa[i])
}

なお,0番の音は低周波過ぎて聞こえていないのを口実に与えている.

和音の作成

和音を作成するには,とりあえず和を取ればいいはずである.音量が大きくなりすぎることに注意する. for 文を使用してデータの和を取り,重ねた回数 $k$ で割れば良いので

\begin{equation} \frac{1}{k}\sum^k_{i=1}\left[\underbrace{\sin\left( 0\right), \cdots, \sin\left(2\pi f_i p\right)}_{44100\times p\mbox{個}}\right]\label{seqm} \end{equation}
mkw <- function(num, p = 1, output = F, sound = T){
freq <- note2hz(num)
  data = rep(0, round(44100*p))
  for(i in 1:length(num)){
    data = data + 
             sin(seq(0, 2*pi*freq[i]*p, 
               length = round(44100*p))) / length(num)
  }
  if(sound)play(data, rate=44100)
  if(output)return(data)
}
ドミソの和音は以下で再生できる.
mkw(c(60, 64, 67))

打ち込んだ音楽の再生

音階と長さを list 形式で保存し,再生を行う.オブジェクト noritz というデータを用意する.これはある有名な音楽の一節である.(正確には4小節) 67番の音を0.5秒,次に65番の音を0.5ならす$\cdots$指示を記述したオブジェクトである.

noritz <- list(
  c(67,0.5), c(65, 0.5),
  c(list(c(64,48)),.5),c(list(c(64,48,55,52)),.5),
  c(list(c(67,48,55,52)),.5),c(list(c(72,48,55,52)),.5),
  c(list(c(71,50)),.5),c(list(c(71,50,55,53)),.5),
  c(list(c(67,50,55,53)),.5),c(list(c(74,50,55,53)),.5),
  c(list(c(72,52)),.5),c(list(c(72,52,60,55)),.5),
  c(list(c(76,52,60,55)),.5),c(list(c(76,52,60,55)),.5),
  c(list(c(52)),.5),c(list(c(52,60,55)),.5),
  c(list(c(72,52,60,55)),.5),c(list(c(71,52,60,55)),0.5),
  c(list(c(69,53)),.5),c(list(c(69,53,62,57)),.5),
  c(list(c(77,53,62,57)),.5),c(list(c(74,53,62,57)),.5),
  c(list(c(72,55)),.5),c(list(c(72,55,64,60)),.5),
  c(list(c(71,55)),.5),c(list(c(71,55,65,62)),.5),
  c(list(c(72,60)),.5),c(list(c(72,60,67,64)),.5),
  c(list(c(72,67,64)),.5),c(list(c(72,65,62)),.5),
  c(list(c(64,60)),1)

なお,用いる playnote 関数にはピッチ調整とテンポ調整を実装してある.関数内で先程の mkw 関数を呼び出している.

playnote <- function(data, tempo = 120, key = 0,
                       output=F, sound = T, ...){
  da = numeric()
  for(i in 1:length(data)){
    tmp = mkw(unlist(data[[i]][1])+key,
            unlist(data[[i]][2])/2*(120/tempo),
            output = T, sound = sound, ...)
    da = c(da, tmp)
    if(sound){
      Sys.sleep(unlist(data[[i]][2])/2*(120/tempo))
    }
  }
  if(output)return(da)
}

パートごとに打ち込んで再生

上記の方法だと各パートごとに音符の長さが異なるときは不便である.したがって,各パートを list で入れ子にしたものを準備する.各パートだけで聞きたい方は playnote(kk01[[1]], tempo=120, key=12) などでどうぞ.
# Keikyu Shinagawa Sta.
kk01 = list(
  list(
    c(67,.5),c(74,.5),c(76,.5),
    c(74,.5),c(71,.5),c(69,.5),c(67,1),c(67,1),c(66,1),
    c(66,1),c(67,1),c(67,.5),c(74,.5),c(76,.5),
    c(74,.5),c(71,.5),c(69,.5),c(67,1),c(67,1),c(67,.5),
    c(66,.5),c(67,.5),c(69,.5),c(67,1),c(67,.5),c(74,.5),c(76,.5),
    c(74,.5),c(71,.5),c(69,.5),c(67,1),c(67,1),c(66,1),
    c(66,1),c(67,1),c(67,.5),c(74,.5),c(76,.5),
    c(74,.5),c(71,.5),c(69,.5),c(67,1),c(67,1),c(66,.5),
    c(66,.5),c(67,.5),c(69,.5),c(67,2.5),
    c(55,1/8),c(list(c(55,59)),1/8),c(list(c(55,59,62)),1/8),c(list(c(55,59,62,67)),1/8),c(list(c(55,59,62,67,69)),1/8),c(list(c(55,59,62,67,69,73)),1/8),c(list(c(55,59,62,67,69,73,76)),3+2/8),
    c(55,1/8),c(list(c(55,59)),1/8),c(list(c(55,59,62)),1/8),c(list(c(55,59,62,67)),1/8),c(list(c(55,59,62,67,69)),1/8),c(list(c(55,59,62,67,69,73)),1/8),c(list(c(55,59,62,67,69,73,76)),3+2/8)
  ),
  list(
    c(0,1.5),
    c(62,.5),c(55,.5),c(62,.5),c(64,.5),c(60,.5),c(64,.5),c(64,.5),c(62,1),
    c(62,.5),c(60,.5),c(59,.5),c(59,.5),c(0,1),c(67,.5),
    c(66,.5),c(62,.5),c(54,.5),c(60,1),c(60,1.5),
    c(60,.5),c(57,.5),c(60,.5),c(59,1),c(0,1.5),
    c(67,1),c(64,.5),c(64,.5),c(59,.5),c(64,1),c(62,1),
    c(0,3),c(67,.5),
    c(67,1),c(62,.5),c(60,.5),c(62,1),c(60,.5),c(60,.5),c(60,1.5)
  ),
  list(
    c(0,1.5),
    c(47,1.5),c(48,2.5),
    c(50,1.5),c(52,2.5),
    c(47,1.5),c(45,.5),c(52,1),c(49,2),
    c(50,.5),c(43,1),c(0,1.5),
    c(52,1.5),c(49,1.5),c(49,.5),c(48,.5),
    c(57,.5),c(62,.5),c(60,.5),c(59,.5),c(55,.5),c(47,1.5),
    c(53,1.5),c(52,1),c(48,1),c(50,.5),
    c(38,.5),c(40,.5),c(42,.5),c(43,2.5)
  )
)

先程の playnote 関数をパートごとにデータを出力し,和を取っている.和音と同様に足した回数で割っている.

playnoteList = function(data, key = 0, output = F,
                 sound = T, tempo = 120, ...){
  tmpData = list(); maxlen = 0
  if(length(key) != length(data) && length(key) != 1){
    stop("The length of key is wrong.")
  } else if(length(key) == 1){
    key = rep(key, length(data))
  }
  for(i in 1:length(data)){
    tmpData[[i]] = playnote(data[[i]], tempo = tempo,
                     key = key[i], output=T, sound = F, ...)
    maxlen = max(maxlen, length(tmpData[[i]]))
  }
  for(i in 1:length(data)){
    tmpData[[i]] = 
      c(tmpData[[i]], rep(0, maxlen - length(tmpData[[i]])))
  }
  result = rep(0, maxlen)
  for(i in 1:length(data)){
  	result = result + tmpData[[i]]
  }
  result = result/length(data)
  if(sound)play(result, rate=44100)
  if(output)return(result)
}

波形の変更

一般に生成される音の波形は正弦波(サインカーブ)が多い.(電気店で聞く電話コーナーのような音)今回も例に漏れず使用しているが,このカーブを他の関数に変更する.

[1]正弦波
[1]正弦波
[2]ノコギリ波
[2]ノコギリ波
[3]矩形波
[3]矩形波
[4]合成波
[4]合成波

これらの関数をRで実装するには,下記の関数を定義したうえで,mkw 関数内の sin を置き換えれば良い.また,合成波の関数を式\eqref{composit}にしているが,任意の周期的な関数で作成すれば,まるで R がシンセサイザーかのように音を作ることができる, \begin{equation} f(x) = \sin(-x) - \frac{1}{2}\sin\left(-\frac{x}{3}+\frac{\pi}{2}\right)+0.1\cos(4x) \label{composit} \end{equation}

# ノコギリ波
saw <- function(x){
  return(2 * (x/pi/2 - floor(x/pi/2)) - 1)
}
# 矩形波
rectwave <- function(x){
  return(floor(sin(x) + 1))
}
# 合成波
composite <- function(x){
  return(sin(-x)-0.5*sin(-1/3*x+0.5*pi)+0.1*cos(4*x))
}