クーの自由研究

素朴な疑問を実験します。ボクと一緒に螺旋階段を頂上まで登ってみませんか?貴方の影についていきます。

計算機音楽の自由研究(準備:その2.7)~wavファイルのフォーマットを勉強する

はじめに

今回は(機械学習向けの音のデータベースとして使用する予定の)、wavファイルへの波形情報以外の情報の持たせ方と、Pythonでの波形情報以外の取得方法について確認します。準備が進まず、実験にまだまだはいれません。準備ブログについて、亀の歩みで番号を0.1ずつアップさせています。

f:id:np2LKoo:20170514104105j:plain

よく使われるwav/waveフォーマットを実験用の標準データベースフォーマットにする方向で考えているのは前回まで書いたとおりです。
このwavフォーマットについて、実験用の情報をいろいろ持たせるための確認をしますです。wav(の中のRIFF情報)のタグ情報をどう扱うかという、超マイナーな話題です。

Pythonの標準のwaveライブラリなどではタグ情報にアクセスできない*1ことによるお題です。手抜きで英語のリンクを貼っていますが、訳す力がありません。あしからず。(6月末まで反省キャンペーン中です。内容を順次リファインします。)

なんのあしあと?かえるのあしあと!(雪の上ならそれは違いますよ)

 最初に蛇足をかくのもなんですが、助手たちが書いているのは「聖なる館」以下の部分だけで、それ以外の部分はボクが書いてます。

どのような情報を持たせるのか

手書き数字画像学習用のMNISTデータベースがとても優れており、現在も確認用やチュートリアルとしてよくつかわれています。今後も当ブログではよく使うと思います。データベースのフォーマットは扱いやすければなんでもいいと思っていますが、以下の要件が必要かと思っています。


学習用(実験用)データベースの要件

・一度リリースしたら初志貫徹。内容を変えない。

  改善したくてもしない。してしまうと以前の実験との比較が困難になる。

・扱いやすい容量にまとまっている。

  1つ1つの情報の粒度がちいさすぎず、大きすぎない

  全体の情報の「個数」は情報の内容に対して、統計的に考えても十分大きい

・学習用とテスト用の境界がはっきりしている。

  利用する人がまちがいようがないようにする。(ここがまちまちになると比較にならない)

・ダウンロードが容易

  特定コードを書けば無条件で使用できるなど。

  アクセス・使用するためのメソッドが標準化されている。

・一部のみの改変がしにくい

  どうしても応用したものをつくりたくなるが、データ差し替えや書き換えがある程度しにくいとやろうと思わない。


手書きの数字を学習させる場合は、その正解がなんであるかは0~9までの数字を持てばよく、ほかにあまり情報が要りません。
当ブログでの音の実験では、音の種類だけでなく、いろいろな情報を持たせたいと考えています。
バイオリンかピアノかを聞き分けるだけでなく、バイオリンなら1億円超えのストラディバリウスと10万円の練習用を聞き分けたり、ピアノならスタンウェイかヤマハかなどを聞き分けるようなこともやりたいと考えています。(目指せ一流!)
そのため「ピアノの中央ドの音」ではなく、「KeppyがサンプリングしたスタンウェイのサウンドフォントV6.xのMIDIノート60の音」のような情報を持たせたいと考えています。
できれば、wavファイルの中にそれらの情報を持たせたいと考えています。*2
wavファイルのフォーマットを調べて、データをどのように持てるのか、(または持てないのか)を確認します。

おさらい
pythonでwaveファイルを扱う場合はimport wave とすれば簡単に行えます。ところが、pythonのwave機能ではタイトルやアーチスト、コメントの情報を取得できません。

マニュアルを読んでもコメント情報にアクセスできる手段は書いてありません。

flacは大好きですが、今回はあえてPASSしています。*3

機械学習向けの正解や補助情報をwavファイルの「タグ」情報として入れたいと考えていますので、pythonで扱う方法を確認します。

ポイントを列記します
・wavファイルのフォーマットはRIFFというフォーマット規格を基本にしている。

・RCFとかの標準規格ではない。「AS IS」(あるがまま)な規格で悪い意味いい加減。いい意味で柔軟性あり。不運なことに悪い面が目立ってしまっている。

・wavファイルのRIFF内タグはRFC 4180を解説してるページの参照としてはあるが、「定義」ではない模様。MS社ソースのaafile.cの内容そのものが仕様定義のようです。
Pythonにはいろいろなwavファイルへのアクセスメソッドやライブラリがあるが、タグアクセスについてはほとんど機能がない。
・タグにアクセス(読むだけ)したいなら通常バイナリファイルとして読み込んで、ジブンで直接アクセスしたほうが手っ取り早い。(Pythonなので、どこかにwavファイルのタグを簡単に扱えるライブラリが「ある」とおもいますが、少し探して見つからないものは探すのがとってもたいへん。)

フォーマットについての説明は初心者につき、他のサイトを参考にさせていただきます。フォーマットの説明は(力が及ばないので)しませんのであしからず。

こちらの説明がわかりやすかったです。どうも有難うございます。

wavファイルの「タグ」情報について

 wavファイルのタグは参照ページのとおり、RIFFの仕様策定はMS社とIBM社の提案によるものですが、厳密な定義をしていない部分やもともと自由度が高いことを想定して策定されているので、かなり混乱したようです。
とくに各タグの意味やフォーマットの例示や定義が薄く、策定時には多言語環境がまとまっていなかったこともあり、多言語及び複数表記(複数情報をデリミタ;で区切るかどうかなど)の不統一により、多くのソフトで非互換部分が発生している模様です。flacのタグなどは後で作成されたのでそのあたりの状況もふまえて設計されており、このあたりの非互換はない模様です。

プログラム

wavフォーマットのページを参考にして、またジブンで作成したwavファイルをバイナリエディタで確認してプログラムしました。仕様にでているフォーマット内容を参考に各情報をそのまま表示しているプログラムです。
ベタベタなコードですが、実験用ということでご容赦ください。(あとでもうすこし汎用的なものに差し替えるかもしれません)


import wave #使ってません
import numpy as np

STRCODE ='utf-8'

class vWave:
iffAll = None
iffID = None
iffSize = 0
iffFormatName = None
iffSubID = None
iffSubSize = 0
iffWavFomat = 0
iffChannel = 0
iffSampling = 0
iffBytePS = 0
iffBlockSize = 0
iffBit = 0
iffEXParaSize = 0
# self.iffEXParam = 0
iffDataID = None
iffDataSize = 0
iffData = None
iffList = None
iffListID = None
iffSubChunkID = []
iffSubChunkSize = []
iffSubChunkData = []

def __init__(self, data):
self.iffAll = np.frombuffer(data, dtype="byte")
self.iffID = (b''.join(self.iffAll[0:4])).decode(STRCODE)
self.iffSize = int.from_bytes(self.iffAll[4:8], 'little')
self.iffFormatName = (b''.join(self.iffAll[8:12])).decode(STRCODE)
self.iffSubID = (b''.join(self.iffAll[12:16])).decode(STRCODE)
self.iffSubSize = int.from_bytes(b''.join(self.iffAll[16:20]), 'little')
self.iffWavFomat = int.from_bytes(self.iffAll[20:22], 'little')
self.iffChannel = int.from_bytes(self.iffAll[22:24], 'little')
self.iffSampling = int.from_bytes(self.iffAll[24:28], 'little')
self.iffBytePS = int.from_bytes(self.iffAll[28:32], 'little')
self.iffBlockSize = int.from_bytes(self.iffAll[32:34], 'little')
self.iffBit = int.from_bytes(self.iffAll[34:36], 'little')
self.iffDataID = (b''.join(self.iffAll[36:40])).decode(STRCODE)
self.iffDataSize = int.from_bytes(self.iffAll[40:44], 'little')
self.iffData = b''.join(self.iffAll[44:44 + self.iffDataSize])
self.iffList = self.iffAll[44 + self.iffDataSize:]
if (self.iffList.size != 0):
self.iffListID = (b''.join(self.iffList[0:4])).decode(STRCODE)
self.iffListSize = int.from_bytes(self.iffList[4:8], 'little')
self.iffInfoID = (b''.join(self.iffList[8:12])).decode(STRCODE)
offset = 12
for i in range(999):
chunkID = (b''.join(self.iffList[offset:offset + 4])).decode(STRCODE)
subChunkSize = int.from_bytes((b''.join(self.iffList[offset + 4:offset + 8])), 'little')
chunkData = (b''.join(self.iffList[offset + 8:offset + 8 + subChunkSize]).decode('SJIS'))
offset += (8 + subChunkSize + (subChunkSize % 2))
self.iffSubChunkID.append(chunkID)
self.iffSubChunkSize.append(subChunkSize)
self.iffSubChunkData.append(chunkData)
if self.iffListSize <= offset + 12:
break

def print(self):
print('Toral size = ' + "{0:d}".format(self.iffAll.size))
print('ID = ' + self.iffID)
print('Chunk Size = ' + "{0:d}".format(self.iffSize))
print('FormatName = ' + self.iffFormatName)
print('Sub ID = ' + self.iffSubID)
print('Sub Size = ' + "{0:d}".format(self.iffSubSize))
print('WavFomat = ' + "{0:d}".format(self.iffWavFomat))
print('Channel = ' + "{0:d}".format(self.iffChannel))
print('Sampling = ' + "{0:d}".format(self.iffSampling))
print('BytePS = ' + "{0:d}".format(self.iffBytePS))
print('BlockSize = ' + "{0:d}".format(self.iffBlockSize))
print('Bit = ' + "{0:d}".format(self.iffBit))
print('DataID = ' + self.iffDataID)
print('DataSize = ' + "{0:d}".format(self.iffDataSize))
print('--------------------------------')
print('List ID = ' + self.iffListID)
print('List Size = ' + "{0:d}".format(self.iffListSize))
print('INFO ID = ' + self.iffInfoID)

def getData(self):
return self.iffData

def getList(self):
return self.iffSubChunkID, self.iffSubChunkSize, self.iffSubChunkData

WAVE_INPUT_FILENAME = "test\Check055B.wav"

f = open(WAVE_INPUT_FILENAME, 'rb')
aData = f.read()
waveData = vWave(aData)
waveData.print()
subChunkID, subChunkSize, subChunkData = waveData.getList()
for i in range(len(subChunkID)):
print(">" + subChunkID[i] + ":{0:4d}:".format(subChunkSize[i]) + subChunkData[i])



 実行結果(例)

例ではモノラル16Bit48KHzサンプル数=32の音声データに対し、フリーソフトmp3infpでタグ情報を付加してその内容を表示したものです。

実行結果


Toral size = 322
ID         = RIFF
Chunk Size = 314
FormatName = WAVE
Sub ID     = fmt
Sub Size   = 16
WavFomat   = 1
Channel    = 1
Sampling   = 48000
BytePS     = 96000
BlockSize  = 2
Bit        = 16
DataID     = data
DataSize   = 64
--------------------------------
List ID    = LIST
List Size  = 206
INFO ID    = INFO
>ISRC:   7:ソース
>ICRD:  11:2017/05/13
>IPRD:   9:アルバム
>IENG:   7:製作者
>ITRK:   5:9999
>INAM:  12:Title Name 
>ICOP:   7:著作権
>IGNR:  11:A Cappella
>ISFT:  13:ソフトウェア
>ICMT:   9:コメント
>IART:  13:アーティスト 

元にしたファイルの情報

f:id:np2LKoo:20170514143304p:plain

f:id:np2LKoo:20170514143103p:plain

まとめ 

勢いでタグ全般を確認しましたが、コメント(ICMT)タグに実験用サウンドの各種情報を入れて登録をすればよいと考えます。(4バイトあるので、4GBまで情報をいれられるはずです。全体でも4バイト容量(4GB)が限界です)機械学習向けコメント情報のフォーマットは別途考えようと思いますが、[JSON形式]でよいと思います。

補足

 Windowsの標準機能では、タグ情報を自由に入れることはできません。専用のソフトを使って入れることができます。フリーでいろいろあります。

ボクはmp3infpというソフトを使っています。

フォーマットがわかれば自作プログラムから書いてもいいと思っています。

何もタグ情報を入れていないwav(上側ファイル)とタグ情報をいれたwav(下側ファイル)でバイナリエディタでの確認。下側ファイルの黒枠部分が上側ファイルに相当します。

f:id:np2LKoo:20170514211645p:plain

Python足 (もしくは Silly Walk)

 (参考)以下の表はMS社のRIFF定義をしたaafile.cの内容からの抜粋です。各種関連情報の紹介とともに訳しておられる方がいらっしゃるのでそちらでどうぞ(苦笑)

ID 内容 内容(英語) 説明(英語)
IARL アーカイブされた所在 Archival Location Indicates where the subject of the file is archived.
IART 参加アーチスト(複数は;でデリミト:しないソフトもある) Artist Lists the artist of the original subject of the file. For example, \"Michaelangelo.\"
ICMS 著作権代理人 Commissioned Lists the name of the person or organization that commissioned the subject of the file. For example, \"Pope Julian II.\"
ICMT コメント Comments Provides general comments about the file or the subject of the file. If the comment is several sentences long, end each sentence with a period. Do not include newline characters.
ICOP 著作権情報(複数は;でデリミト:しないソフトもある模様) Copyright Records the copyright information for the file. For example, \"Copyright Encyclopedia International 1991.\" If there are multiple copyrights, separate them by a semicolon followed by a space.
ICRD 作成日→年のみ表示するソフトもある Creation date Specifies the date the subject of the file was created. List dates in year-month-day format, padding one-digit months and days with a zero on the left. For example, \"1553-05-03\" for May 3, 1553.*4
ICRP

(AVI)

一部切り取り

Cropped Describes whether an image has been cropped and, if so, how it was cropped. For example, \"lower right corner.\"
IDIM

(AVI)縦横サイズ

Dimensions Specifies the size of the original subject of the file. For example, \"8.5 in h, 11 in w.\"
IDPI (AVI)解像度 Dots Per Inch Stores dots per inch setting of the digitizer used to produce the file, such as \"300.\"
IENG (録音/録画)エンジニア Engineer Stores the name of the engineer who worked on the file. If there are multiple engineers, separate the names by a semicolon and a blank. For example, \"Smith, John; Adams, Joe.\"
IGNR ジャンル Genre Describes the original work, such as, \"landscape,\" \"portrait,\" \"still life,\" etc.
IKEY キーワード Keywords Provides a list of keywords that refer to the file or subject of the file. Separate multiple keywords with a semicolon and a blank. For example, \"Seattle; aerial view; scenery.\"
ILGT

(AVI)

明度

Lightness Describes the changes in lightness settings on the digitizer required to produce the file. Note that the format of this information depends on hardware used.
IMED メディア形式 Medium Describes the original subject of the file, such as, \"computer image,\" \"drawing,\" \"lithograph,\" and so forth.
INAM タイトル Name Stores the title of the subject of the file, such as, \"Seattle From Above.\"
IPLT (AVI)色数 Palette Setting Specifies the number of colors requested when digitizing an image, such as \"256.\"
IPRD アルバム Product Specifies the name of the title the file was originally intended for, such as \"Encyclopedia of Pacific Northwest Geography.\"
ISBJ サブジェクト/サブタイトル/内容説明 ※ソフトにより扱いがまちまち Subject Describes the contents of the file, such as \"Aerial view of Seattle.\"
ISFT ソフトウェア Software Identifies the name of the software package used to create the file, such as \"Microsoft WaveEdit.\"
ISHP (AVI)鮮明度 Sharpness Identifies the changes in sharpness for the digitizer required to produce the file (the format depends on the hardware used).
ISRC ソース Source Identifies the name of the person or organization who supplied the original subject of the file. For example, \"Trey Research.\"
ISRF ソースの形態 Source Form Identifies the original form of the material that was digitized, such as \"slide,\" \"paper,\" \"map,\" and so forth. This is not necessarily the same as IMED.
ITCH (デジタル編集加工した)技術者 Technician Identifies the technician who digitized the subject file. For example, \"Smith, John.\"
邪の道 にねそべるカエル(ボク)

wavのアクセスを確認する過程で、Python for .NET というのを使ってみました。(リンクは英語です。ごめんなさい)すんなり動き、何でもできて「にんまり」と笑いました。ボクはピュアなPythonには別にこだわっておらず、(実験なので動けばいいので、)「これもあり」だと思いました。Pythonから.NETのライブラリや.NET経由でシステムCALLが読み放題です。


*1:chunkライブラリではできますが、AIFFなどを対象にしているので、wavファイルに対して使うには逆に手間がかかる感じがします。wavのLISTタグは入れ子のchunkをもつなどがありますが、wavフォーマットそのものには対応していないため、どう使えばいいかがわかりません。。。

*2:MNISTの手書き文字データベースは、「数少ない(集約された)ファイルから構成されており、手軽に改変できない」ことが重要だと思っています。wavファイルの中に各種情報を持たせることにより、改変しにくく、ファイルも少ない状態となると考えます。

*3:各種の情報をいろいろな再生方法で確実に共有したい場合はflacなどを使うべきだ。との見解がほとんどでした。flacは大好きなのですが、実験用の再加工することもあるデータとしては、wavファイルのほうが扱いやすいと考えています。

*4:アスカニオ・コンディヴィの「ミケランジェロの生涯」の著作をイメージした日付かも