niszetの日記

細かい情報を載せていくブログ

Csoundでエイリアシングの確認

久々にCsound小ネタ

サンプリング定理とエイリアシングについて、Csoundで実際に音を出して確認してみます。
サンプリング定理とエイリアシングについては、TIの記事があったのでとりあえず貼っておきますね。

サンプリング定理/エイリアシング - DSP の基礎・トレーニング - TI

書いてみる。

44.1kHzのサンプリング周波数なので、折り返し前を440Hzと設定した場合、行って帰って0Hzに戻る手前440Hzを引くので、

44100Hz*2-440Hz = 87760Hz で同じ音になるはずです。
以前のサンプルコード中のinstrを少し修正し、パラメータとして周波数を受け取れるようにしています(p4で4番目の引数という意味らしい)

<CsoundSynthesizer>
<CsOptions>
-odac
</CsOptions>
<CsInstruments>
instr 1
aSin      poscil    0dbfs/4, p4
          out       aSin
endin

</CsInstruments>
<CsScore>
i 1 0 1 87760
i 1 2 1 440
</CsScore>
</CsoundSynthesizer>

いかがでしょう?確かに同じ音がしてる気がしますね。しかし本当に87.76kHzが出力されているのでしょうか…?確かめようがないですねぇ。

そして、read_wavで読んでplotしたらread_wavのバグに気づきました…修正修正…

read_smfを作ります。

read_wavも完成していないのに…

まぁ、いつかは完成するさ…

さて、いつものように先人のお知恵を拝借なのです。ありがとうございます。しかし、ちゃんと仕様の原著を読む癖をつけないといけませんね。すぐgoogleで検索しちゃう。。。

maruyama.breadfish.jp

sites.google.com

意外?とwikipediaも情報量が多い スタンダードMIDIファイル - Wikipedia

SMFの初歩

sites.google.com

DTM技術情報 - 2.MIDIコントロールチェンジ一覧 | g200kg Music & Software

MIDI講座

MIDI(SMF)の作り方

現状

骨格だけ作って、あとは時間のあるときに追加できるようにしているので、時間さえあれば出来そうな感触です。SMFは命令の数が多いので、なかなか時間かかりそう。

まぁ、まずは動くものを…というところで。

ggplot2に自作geomを作る方に一旦時間を割く予定です(smf -> 楽譜や周波数-時間プロット向きなもの、スミスチャートなどを作っておきたいのです。geomちょっと難しい…。)

平日はしばらくはあまり時間がとれないので、土日にコツコツやっていくのです。。。

rsoundに機能を追加するときに参照する予定のメモ

rsoundがwavファイルを出力するために、csoundの機能を理解する

ということで、read_wavは細かいところを無視すれば一旦完成ということで、こんどはrsoundにwav出力機能を入れていきたいです。

read_wavはrsoundと一旦分離して、rsound_helper的な感じで、こっちだけで公開できるようにしておこうかなと思ってます。rcppの勉強に時間がかかりそうなので。

rsoundからcsoundを呼ぶ

rcppで書かれたソースを読むと、中ではこんな感じでcsound側を呼んでいるようですね…。

  csound->SetOption((char*) options.get_cstring());

今、自分のPCではCsoundはc:/の直下に置いてあるので

C:/Csound6_x64/include/csound/csound.hpp

を見てみる。
すると、以下のようになっています。

  virtual int SetOption(char *option)
  {
   return csoundSetOption(csound, option);
  }

結局、csound.hpp は csound.hを呼び出しているだけのようなので、調べるときは上記の例ならcsoundSetOptionの方を調べる必要がある。

探す場所は下記である。

Csound API: Main Page

ちなみに、上で上げたcsoundSetOptionは、こちらに書いてある。

Csound API: Attributes

あとは頑張って書いてデバッグして…である。コツコツ行きましょう。

Rでbit演算について

書籍が見つからない

Rの書籍は色々持っているものの、bit演算や複素数型について詳しく説明されているものってないような気がします。

なので、ちょっと調べました。

先人のお知恵を拝借。 takuyaokada.hatenablog.com

R.utilsというパッケージもあるのですね。これも後ほど調べてみる。

RStudioの入力補完でそれらしいものを見つける

RStudioでbitまで入力して補完を使うと、bitopsというパッケージがある。
baseと別にあるということは、より良いのでは?ということで違いを見てみる。

いくつか入力してみる。

> bitwAnd(15,0)
[1] 0
> bitwAnd(15,7)
[1] 7
> bitops::bitAnd(15,7)
[1] 7
> 15 & 7
[1] TRUE

最後はおまけ。&はlogicalを返しますので、bit演算には使えませぬ。
差異はないように見えますね。

ベクトルでも入力できる。

> bitwAnd(c(15,0),7)
[1] 7 0
> bitops::bitAnd(c(15,0),7)
[1] 7 0

> bitops::bitAnd(c(15,0,1),c(7,1,3))
[1] 7 0 1
> bitwAnd(c(15,0,1),c(7,1,3))
[1] 7 0 1

ここでも差異がみえない。ベクトルを与えられるのは便利ですね。

ヘルプを見るとNAについて記述がある。入力してみる。

> bitops::bitAnd(c(15,0),NA)
[1] NA NA
> bitwAnd(c(15,0),NA)
Error in bitwAnd(c(15, 0), NA) : 'a' and 'b' must have the same type

おっと、差異が出ました。baseの方はNAを入れるとエラー…

( ^ω^)・・・

type…?どうやら、NAがintegerだと思われていないようです。

今度は、NA_integer_で入れてみる。

> bitwAnd(c(15,0),NA_integer_)
[1] NA NA

お、動きますね。
念のため、もう一方も。

> bitops::bitAnd(c(15,0),NA_integer_)
[1] NA NA

こちらも。
bitopsパッケージの方を使っておいた方が、NAの型を合わせてくれるので便利そうです。

ヘルプより、

bitwise {base}   
bitwAnd(a, b)
a, b    integer vectors; numeric vectors are coerced to integer vectors

もう一方

bitAnd {bitops}  
bitAnd(a, b)
a,b numeric vectors of compatible length.

integer… 試しに入力すると…

> bitops::bitAnd(c(15.12501,0,1),c(7,1,3))
[1] 7 0 1
> bitwAnd(c(15.01,0,1),c(0.999,1,3))
[1] 0 0 1
> bitwAnd(c(15.01,0,1),c(1.999,1,3))
[1] 1 0 1

なるほど。使い方を気を付けないといけなさそうです。

Rでバイナリファイルを読む、readBinについて

Rでバイナリファイルを読む

さて、read_wavはwaveファイルを読むのですが、当然?wavファイルはバイナリデータです。
つまり、Rでバイナリファイルを読むための知見が必要なのですが、あまりそういう需要がないためか、情報もあまりないのでした。

readBin

こちらを大変参考にさせていただきました。 blog.recyclebin.jp

最終的にはかなり違うものになりましたが。

Rプログラミングマニュアル[第2版]ならreadBinについて記載があるかなと思ったのですが、残念ながら「ヘルプを読みましょう」と書かれていましたので、ヘルプとにらめっこです。 株式会社サイエンス社 株式会社新世社 株式会社数理工学社

readBinのヘルプ

readBinのヘルプを読みます。 引数で何が与えられるのか、デフォルト値が何で決まっているのか(環境依存とか)、サンプルプログラムとか、色々情報が詰まってますね。
本文は最初は読んでもよくわからないことが多いですが…末尾のプログラムが動かなかったことはなかったはず。

R: Transfer Binary Data To and From Connections

さて、readBinはこんな感じの引数(とデフォルト値)を持っていますね。

readBin(con, what, n = 1L, size = NA_integer_, signed = TRUE,
        endian = .Platform$endian)

conにはコネクションを与えます

  connection <- file(file, "rb")

こんな感じでファイル名fileをバイナリモードのreadモードで開く形です。

whatには型を与えます。wavの場合は"integer"で良いです。"WAVE"などが入っている箇所はreadCharで読めばよいので。

signedは16bitPCMの場合は符号ありなのでTRUE。8bitの場合はFALSEにしないといけませんね。A.I.に追加。

endianはデフォルト値が入っているのですね。"little"を明示的に与えた方が良いのかもしれませんね。A.I.に追加。

sizeはそれぞれの読み込みのサイズをバイト数で与える形です。16bitなら2を与えればよいわけですね。
nは連続して読み込む場合に指定。wavファイルは固定長のデータのため、データ部分は一括で読み込むことが出来ます。

最初はfor文で読んでいましたが、Rのforは遅いので。ファイルサイズが小さい場合は問題にならないと思います。
可変長の場合は考えなくてはならないですが、それは次のread_smfにて考え中。

コネクションは最後に必ず閉じておく必要があります

close(connection)

が、エラーがあるとcloseが実行されなくなってしまうので、on.exit()に入れておく方が良いですかねぇ。これもA.I.…。

on.exit()

実際使ってみるのが、理解するには一番の近道な気がします。バイナリエディタとにらめっこして、思った値が取れているか?を地道にチェックすると良いのです。

read_wavの「完成」はまだまだ先になりそうですね…

前回プロットしたwavファイルの説明

read_wav()の関連知識

さて、条件付きとはいえwavファイルをrで読み込み、data.tabelの形式にしてggplot2で表示させることが出来ました。
前回はCsoundによって生成したwavファイルが、1Hzで非常に見やすいという理由で使っていました。
今回は、Csoundで生成したwavファイルをバイナリエディタで見つつ、read_wav関数内でどのように処理をしているかについて、簡単に説明をしようと思います。

Csoundで生成したwavファイルについて。

前回の記事で使用したwavファイルについては下記をご参照ください。ここで生成した、1Hzのサイン波を説明に使います。

niszet.hatenablog.com

wavのフォーマットについて

前回も紹介した、下記のサイトを見るのが良いと思います。見比べてください。

WAV形式音声ファイル

バイナリエディタでデータを見てみる。

さて、

52 49 46 46 08 00 00 00 57 41 56 45 66 6D 74 20
10 00 00 00 01 00 02 00 44 AC 00 00 10 B1 02 00
04 00 10 00 64 61 74 61 00 00 00 00 00 00 00 00
01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
05 00 00 00 07 00 00 00 08 00 00 00 09 00 00 00

こんな感じです。バイナリなので、52は0x52、10進数で82(=16*5+2)を表しています。これで1byteの単位です。
さて、順に見ていきます。
適宜、先のサイトから説明を引用させていただきます。

52 49 46 46

wavファイルの先頭4byteはRIFFヘッダとなっていて、それぞれR,I,F,Fの文字をasciiコードで表しています。

asciiの文字コードはググれば一発ですがこちらが見やすいかな。
検索するときは52なら0x52、49は0x49で探してみてください。それぞれRとIに対応しています。

参考: ASCIIコード表

08 00 00 00

来ました。Csound第一の謎。 wavのフォーマット的に、次の4byteはファイルサイズ-8のはずです。しかしここでの値は8。

あ、ちなみにここ、リトルエンディアンです。良く調べてないけど。 エンディアン - Wikipedia

別のwavファイルはちゃんとファイルサイズ-8で入っていたので正しかろう…。
ともかく、Csoundで生成したwavには不備があります。そのため、通常のプレーヤで再生できない(WMPfoobar2000で確認)のだと思います。read_wavはこの値を無視し、ファイルサイズ-8を使用するようにしています。今後、csoundオプションで指定するかもしれませんが。

57 41 56 45

それぞれ、'W' ‘A’ ‘V’ ‘E'です。

66 6D 74 20

それぞれ、'f' ’m' ’t' ‘ 'です。

10 00 00 00

引用。

4 byte   バイト数    fmt チャンクのバイト数。リニアPCM ならば 16(10 00 00 00)

ということで、このファイルのfmtチャンクのバイト数は16ということを表しています。

余談ですが、PrintMusic2011Jで生成したwavファイルはこの値が18となっていました。リニアPCMフォーマットなのに…なぜ…

01 00

再び引用。

2 byte   フォーマットID    リニアPCM ならば 1(01 00)

ということで、リニアPCMのようです。

02 00

どんどん行きましょう。

2 byte   チャンネル数  モノラル ならば 1(01 00)、 ステレオ ならば 2(02 00)

ステレオのようですね。これ、Csoundの謎その2です。のちに出てきますが、実際は片方にしか音が入っていません。何のためのステレオ…

44 AC 00 00

44.1KHzでした。

4 byte   サンプリングレート 44.1kHz ならば 44100(44 AC 00 00)

10 B1 02 00

こちらもそのままですね。

4 byte   データ速度 (Byte/sec)  44.1kHz 16bitステレオならば 44100×2×2=176400(10 B1 02 00)

04 00

こちらも。

2 byte   ブロックサイズ (Byte/sample×チャンネル数) 16bit ステレオ ならば 2×2 = 4(04 00)

10 00

こちらも想定通り。

2 byte   サンプルあたりのビット数 (bit/sample)   WAV フォーマットでは 8bit か 16bit。16bit ならば 16(10 00)

64 61 74 61

それぞれ、’d' ‘a’ ’t' ‘a'ですね。

00 00 00 00

来ました、第三の謎。
ここで、残りのデータのサイズを示しているはずなのですが、サイズ0となっています。
read_wavではこの値を無視し、ファイルサイズからヘッダのサイズを引いた値でデータサイズを計算しました。値が0以外の場合は使った方が良いかもしれませんね。

データ部分

さて、データ部分についてはこちらを読むと良いです。
wav ファイルフォーマット

16bitステレオなので、L,R,L,R…の順に16bit= 2byteで並んでいるはずです。つまり、1byte毎に見ればL L R Rのまとまりが1秒間に44.1kだけあるはずです。

実際のデータは…

00 00 00 00
01 00 00 00
02 00 00 00
03 00 00 00
04 00 00 00
05 00 00 00
07 00 00 00
08 00 00 00
09 00 00 00

のように、L側だけデータが入っています。これは設定で変えられるのかもしれませんので謎カウントしません。
1サンプルごとに少しずつ増えていることが見れますね。

16bit ならば符号付き signed (-32768 ~ +32767, 無音は 0)

ということで、負値は0xFFみたいな値になります。実際、末尾は

F3 FF 00 00 F4 FF 00 00 F5 FF 00 00 F6 FF 00 00 
F7 FF 00 00 F8 FF 00 00 FA FF 00 00

のようになっています。
そしてまた謎の、ファイル末尾で-1あたりになっていないという。-6て…。
そして、read_wavで読めばわかりますが、44128行あるのでした。44.1kHzじゃなかったっけ( ^ω^)・・・?

というわけで、細かいことを気にするときりがないのですが、Csoundの生成するwavファイルはなんか奇妙だなというお話でした。
値自体はCsound+バイナリエディタ、あとは気合で見れますので、見てみると良いのではと思いまする。

read_wav 試作版できた。

課題が沢山あります。

  1. とりあえず、読みたいデータを読むだけならば、手元では動くことを確認した、wavを読むRのコードが出来ました。
    read_wav(ファイル名) で対象のwavファイルを読むのですが、
    1. Csoundはヘッダにサイズ情報がない。
    2. PrintMusic 2011で作成したwavはリニアPCMなのにヘッダが18バイトある
      …などなど、もうちょっと考えておく必要があるものがありそうです。
  2. もうひとつ、この関数はwavファイルを一気に読んでしまいます。byte単位をRのintegerに変換するのでディスク上のファイルサイズよりもメモリを食います。
  3. 44.1kでサンプリングしている場合、1秒ごとに44.1k行だけデータフレームの行が増えます。試しに4分超のファイルを読ませてplotさせようとしてみましたがRが固まりました…。よって、どこからどこまで読む、というオプションが必要そうです。
  4. 性善説で、wavじゃないものもwavとして読んでしまうので危ない。ヘッダを見て止める仕組みが必要(これは簡単)
  5. コードが長い…ここに貼れるレベルじゃない…。githubに置くためにgitの勉強しなきゃ…

という状態です。

とはいえ、少しは何か書く。

まぁ、色々課題はあるけどちょっと動くし、いちおう進んでるんですよアピールしておこうかな、と。

先日の記事、

niszet.hatenablog.com

で生成したwavファイルをC直下にsample1.wavという名前でおいてあるとします。
これを、こんな感じで

tmp <- read_wav("C:/sample1.wav")

tmp$data %>%  ggplot(aes(x=time, y=Lval))+geom_line()

こうなります。

f:id:niszet:20170607225526p:plain

とりあえず、動くところまではできたのです…。

そういえば、生成されたwavファイルの説明してなかった…。次回に。