読者です 読者をやめる 読者になる 読者になる

クーの自由研究

かえるのクーが素朴な疑問を実験して報告します。

計算機音楽の自由研究(準備:その2.9.2)~機械学習用サウンドデータベースをつくる(実践編)

はじめに

機械学習用のサウンドデータベースについては、「音の機械学習」に興味のある方に使ってもらえるレベルのもの(汎用性がある。統計的にもそこそこ多い要素数といえる。)を目指そうと思っていました。考え方を改め、「汎用性のあるサウンドデータベース」をつくることをやめます

f:id:np2LKoo:20170525203856p:plain

「汎用性はなくていいから(ボク専用でいいから)とにかく手持ちのサウンドでてっとり早くデータベースをつくり、実験を開始する」ことにシフトします。(泣く泣く「格付けチェック遊び」はあきらめます)音質もあきらめます。(涙)

とはいえ、60000音で11時間ぶん以上の音を扱うことになるので、整理してまとめるだけでもかなり時間がかかると思われます。

(6月末まで反省キャンペーン中です。内容を順次リファインします。準備:その2.6から、準備:その2.9.2までのブログを順次内容更新します。その間内容的には「カオス」な感じになるとおもいますが、ご容赦ください。)

目標設定します(妥協は友達!)

6月末まで、60000音程度の「音のデータベース」を作成することに集中する。

7月からそのデータベースを使って「音の機械学習」の自由研究実験をはじめる。「音楽の学習」を目標にしているが、いきなりはむりポなので、まずは「音」についての自由研究をする。

8月くらいに自由研究での扱いやすさ、扱いにくさをみてデータベースを調整し「そこそこのレベル」にまでする。(ただし手持ちの音なのでしょぼいです)

9月くらいにデータベースを固め、それ以降不満があっても変えない。どうしても不満があるときは別のものを1から作る。データベースの(しょぼいけど)完成版とアクセス用のPythonクラス等をどこかに公開する。(ソースはGitHUBですが、データベースはどこかのフリーのネットストレージサービスにすると思います。)

9月以降はデータベースのことは忘れ(使うだけに集中し)今年の「音の自由研究」に専念をする。(そしてここが本当のスタートラインです)

・そしてまたきっとが訪れる。

このページはすこし特殊になります。

ボクはブログは日記形式にではなく、情報ごとにまとめる感覚です。とはいえ、最近は経過も含めてこまかめに書いています。「(追記)」をかいたり、1つの話題を細切れの時間を使って2~3日程度で編集することもよくあります。

このページは「実践編」として、データベースが完成する予定の6月末まで順次更新していきます。(他に話題があれば書きますが、サウンドデータベースに関する作成はこのページに報告していきます。(どちらかといえば、公開するというより、ジブンにハッパをかける意図です)データベースの仕様についても微調整していきますが、データベースの仕様については、(準備:2.X)の内容を更新していきます。追記ではなく一部書き換えます。)

ブログの読者の皆様には誠に申し訳ございませんが、6月末までは確実に諸々停滞することになります。ご容赦ください。

そもそもできるの?

・1日あたり(単音で)2000音の整備を目標にします。(30日で60,000音の計算!)4オクターブの楽器なら40種類を目途に毎日録音です。

・1200種類くらいの音が必要ですが、ピアノなどは音量(タッチ)によって音質がかなり違いますので、(サンプリング音源のレイヤー構造にもよりますが)1種類の楽器選択で4(pp, mp, mf, ff)種類くらい採取するように考えていますので、充分まかなえると思います。

SampleTank 2 XLだけで 1500音色以上あるので、数的問題はありません

XV-5080なんていう骨董品もあります。XV-5080はファクトリーパッチで1200種類以上あります。Roland CL32-L/Pという化石も発掘するかもです。(悲しいことにkontaktは持っていない。だれか持っていないかなぁ。)もちろん?Roland SC-55系もあります。

ほかにも、古いデジタルシンセ(実機:メーカ保守にだして25年モノの現役)などいろいろあります。。。KORGレガシーコレクションもあります。どの音を使うか決めるだけて一苦労。ソフトも含めてどれも10年以上前の古い「遺産・遺跡」です。どれも「溶け込む」タイプの主張が弱い音ばかりで、単音で聞くと本当に「しょぼい音」です。問題は「音の品質」と、まとめる「時間」(というよりは精神力)ということになります。(機材等は助手の協力にて)

5/25 機械学習用情報(タグにいれるやつ)のフォーマット調整

・汎用性を少なくする代わりに、よりシンプルにします。

データベース(仮)の音をPythonを使って鳴らしてみました。ノイズがあることと、クリップ(飽和)している感じがあるので、まだ調整中です。*1この調整が完了したら、怒涛の「音の録音」をしていきます。 

 5/26 反省キャンペーン始動

6月末までサウンドデータベース作成期間とし、不都合な点、考え直した点をフィードバックします。その場合サウンドデータベースの仕様定義も変更となるので、準備:2.6~2.9.2までの内容を見直します。(わかりにくくなるので、追記、変更の記載をせずに書き換えます。この間のブログ日付はあまり意味がなくなります)完成版の仕様をあらためてかけばいいのですが、断片の時間にボク自身、このブログをみて思い出しながら修正していきますので、(ブログ的には禁じ手な感じがしますが)遡り修正します。

5/27 録音の音圧をそろえます

録音時に波形のピークで音量を調整するのをやめて、平均音圧を目標値にするよう音量調整します。短い音なので、音の立ち上がりなどのフレーム分析はせず、全体(32768sample/0.68Sec)の平均音圧を求めます。RMS(実行値計算)という音圧計算方法で求めます。

powerAvarage = 10.0 * np.log10((regFrame ** 2).mean())

regFrame は-1.0から1.0までの値に正規化された値を入れます。(平均音圧を出したい部分の全サンプルです) 最初の数字が20.0ではなく、10.0であるところがポイントです。(Powerの場合は10です)単位はdB(デシベル)です。0を超えることのない、マイナスの数字となります。元にするサンプル音には、ff(フォルテシモ)、pp(ピアニシモ)などのサンプリングがあり、ff(フォルテシモ)、の場合は調整目標音圧を-17.0dBにするなどの調整をします。但しピークが範囲(16bit最大)を超えるような調整は行いません。

5/27 その2 ノイズが乗りやすいので、単音差し替え可能に

効率を考え、一連の音の連結は一度にやっていましたが、後で単音を再連結できる(=ノイズのある単音を差し替えて結合)ようにします。

(1)単音で録音する。(1音32768サンプル)

(2)単音の連結をして楽器スケール音にまとめる。(たとえばピアノだと88音)

(3)楽器スケール音を連結してサウンドデータベースにまとめる。

雑記

波形情報をそのまま持つと容量がすごいことになりますが、あえてそうしています。学習だけを考えるとフーリエ変換mfcc(メル周波数ケプストラム係数)変換の前処理をしたものを素材にする考えもありますが、その方法を特定したくない(いろいろやりたい)ので、wave波形そのものを学習素材にしようとしています。

音声認識もおもしろそう。。。

 

(以降順次記載予定) 

 

 

*1:とても元の音がsteinway(のサンプリング)だとはおもえません。

計算機音楽の自由研究(準備:その2.9.1)~機械学習用サウンドデータベースをつくる(特別編)

 

はじめに

連投はやっぱり無理です。ごめんなさい。

(6月末まで反省キャンペーン中です。内容を順次リファインします。)

サウンドデータベーステストサンプルです

準備:その2.9で結合した機械学習用の音です。聞くと、結合されただけでありがたみはありません。

SoundCloudはそういえばダウンロードできないんでした。

フリーのネットワークストレージを探します。

ちなみにタグはこんなふうについています。

f:id:np2LKoo:20170522002900p:plain

本当は「ソース」に楽器名などをのせるつもりでしたが、説明に「元データを提供した人または組織を書く」と例示で明記してありましたので、無難な「ソフトウェア」欄に入れました。

コメントには各音の情報をリスト形式で格納しました。

視聴頂いた方どうも有難うございます

 おかげ様でcarpet of the sunの視聴回数が16HITになりました。目標まであと「4」です!!

 

(ここから追記)

内容的にこまぎれになるので(連投もあきらめたことですし)こちらに追記します。

サウンドデータサンプルについてのお題のままです。

そもそも10万音無理でした

 10万音ぶんのデータ:50音×2000ファイル(内容は重複)を仮準備してテストとして結合してみました。待つこと1時間弱。ようやく出力!というところでエラーとなりました。数字がおおきすぎて、4バイトリトルエンディアンに変換できないエラー(エラーメッセージはひかえませんでしたが、もう一度やる元気はないです)が発生しました!

通常waveファイルフォーマットは4GBが最大なのに6GBくらいになるデータをつくってサイズをいれるとき、4バイトに収まらない数となりました。

機械学習用の情報も入れて1ファイルにするので、だいたい今のサンプル数(1音32,678サンプル(0.68秒)では6万音までです。0.34秒は少し短い(というよりあとで聞いてて楽しくない)ので倍にしましたが、0.34秒から0.68秒の間に調整する「かも」しれません。

f:id:np2LKoo:20170523214530p:plain

5000音で320MBくらい、6万音で3.8GBくらいです。6万音だと全部聞くのに11時間22分40秒かかります!音はちゃんとMediaPlayerなどで聞けることを確認しました。問題Pythonで、6万音ファイルを読み込んで配列にいれたとたん、メモリ不足で落ちることです。320MBのファイルでさえ、配列にいれる処理をPythonが内部でしているときに一時的にメモリを大量消費し、(いろいろ調整していないとはいえ)そのあたりが限界ぎりぎりセーフな感じです。

読み込んで、変数に格納する過程で大量に(一時的に)メモリを消費

 320MBのファイルを読み込んでメモリ消費量を確認しました。

f:id:np2LKoo:20170523222108p:plain

なんと、バイト配列からバイト列への結合(47行目の b''.join(バイト配列)でバイト配列(これは320MB)の約90倍もの作業領域を使って処理しています。内部的な作業領域が命令が完了するまで開放されずにいる感じです。この処理を3.8GBのファイルで行うと、すぐにPycharm(開発IDE:開発環境)ごと落ちます。*1これはよろしくありません。もう少し容量をおさえるとともに、安定してデータを扱う方法を確認(もしくはしくみを提供)する必要がありそうです。大量データを扱い、マシンの性能限界(レッドゾーン)近くで動く、それっぽい「いい感じ」の状況になってきました。(ちなみにボクのマシンはメモリ32GB MAXです)

(さらに追記)リソースを大量に消費する処理に気を付けてコーディングすれば、問題なく4GB近くのwavファイルを自由に操作できることがわかりました。ファイルをメモリ上に読み込み、各種情報へアクセスできるようにするまで、2秒程度でできるので、合格かと思います。

Pythonで番号指定で特定のサンプル音を再生できるようにしました。また、情報の持ち方についてもう少しシンプルにするよう調整中です。ちゃんと調整できたらGitHUBにアップすると思います。(さらに追記ここまで)

「音あつめ」もたいへんかも

今は、320MBでいっぱいいっぱいですが、アクセス方法も含めて容量など各種パラメータを調整していきます。枠組みがきまればあとはひたすら「仮面ライダー音あつめ」です。スタンウェイは比較的集めやすいと思いますが、ベーゼンドルファーとベヒシュタインの「いい音」集めはなかなか厳しそうです。全国スタジオ、ホールめぐりする余裕はとてもありません。。。

こんなサービス「ベーゼンドルファー東京レンタルスタジオに簡単・高音質な録音サービス「即レコ」を導入しました « ベーゼンドルファー・ジャパン

汐留ベヒシュタイン・サロン | ユーロピアノ株式会社

ピアノレンタルスタジオなどはいろいろある感じですね。。。)

みたいなのを地道に探すのもいいかもしれません。1時間2~3千円くらいだと、現実的かも。録音セッティング、収録、撤収作業含めても3時間あればOKだと思います。ピアノの音あて遊び(もちろん機械学習で!)をするにはちゃんとした「音」を集めないとおもしろくないですものね。。。

バイオリン等は奏者込じゃないと無理ですよねぇ。ストラディバリウスや名だたる銘器(しかも各種)は、どうやったら演奏してもらえる人(持っている人)を探せるのかわかりません。探せたとしても(スタジオ、できれば小さくてもそれなりの響きのあるホールで)演奏してもらうのはいくらかかるんでしょう。。。現実味がないけど、考えるのは楽しいです。

今年は「これ計算機音楽」にかけているので、中途半端なことはしたくないのですが、短期で作成するとなると、出来合いの(しかも手元にある)音源を集めるしかないのかもしれません。

(さらに追記)高級なピアノのあるスタジオにいってパッと録音できるよう、ICレコーダが無性に欲しくなってきています。これはいけません。そんな凝り始めたらいつまでたってもおわりません。。。最高のピアノの音を集めたら次は最高のストリング、次は最高のギター。。。際限なくなることは間違いないです。中途半端に留める勇気も必要だと感じました。ああ、手元のサンプリング音がもうすこしちゃんとサンプリング(全音フルサンプリング)してあれば、生音を録りたいとはそんなにおもわないはずなのですが。。。(手元の音は聞けばきくほど貧弱です。)市販のサンプルとくらべてもKeppyさんのSteinwayピアノのサンプリングはとてもよいことがわかります。。。。う~ん演奏から音の断片を切り取って(当然単音ではなく和音や旋律の経過音の断片かと)学習素材にすることも考えよーっと。それなら、Googleライブラリが使えるし~(さらに追記ここまで)

助手・元祖?現役?〇〇疑惑

以前、助手からSF好きなら「萩尾望都」先生を読むべきと勧められました。元祖○○なら「竹宮恵子」先生の「風と木の詩」です。最近リバイバルとの話題がどこかに載っていました。最近の傾向なのですが、身近で話題になったことが1~2ヶ月くらいあとでネットで「最近流行なんだって!」ときくことが本当によくあります。まわりじゅう、流行とは無縁の面々ばかりですが、案外自分たちが世間に近づけた(もしくはミーハー傾向な)のかもしれません。

f:id:np2LKoo:20170524001020p:plain

助手さん!早く「萩尾望都」先生の「銀の三角」貸してください。(どこかにあるはず。と言ってそれっきりですよ!)

キャンペーン効果!!

 毎度有難うございます。目標20Hitまであと「3」となりました!!

*1:落ちたのは最初はバイト列→バイト配列だと予想していたのですが、まさかのバイト配列→バイト列の処理でした。大容量のb''.join操作は要注意です!

計算機音楽の自由研究(準備:その2.9)~機械学習用サウンドデータベースをつくる(発動編)

はじめに

ファイルの結合ができたのでタグ情報を付加してみます。前回「結合編」と付けましたが、今回も続きのためXX編にしようと思いました。いいのが思いつかないので助手に意見を求めたところ「発動編がいいのでは?」「最初のは接触編にしたほうがよかったね。」と意味不明なことを言われました。他に思いつかないので、発動編にしました。内容と合っていない気がしますが、何かを発動します。

f:id:np2LKoo:20170521090240p:plain

(助手はコレジャナイとさらに意味不明。 井出の力って何?)

f:id:np2LKoo:20170521200508p:plain

これでもなさそうだし。

 

f:id:np2LKoo:20170521233059j:plain

見つけました!これですね。これ発動していいやつ?

なお、助手の井戸中を井出上に改名する案は却下します。

(6月末まで反省キャンペーン中です。内容を順次リファインします。)

 機械学習用に付加する情報について

各音の情報についてJSONにしようと思っていましたが、いざ書いてみると(1行表示なので)逆にとても読みにくかったです。JSONはあっさりヤメます。フツーの配列(リスト)表記にします。
以下はまだ案で、試行テストで不都合があれば変ます。(個人的な要件のメモなので読み飛ばしてください)
大きくわけて


(1)楽器の音の情報:ソース情報
(2)(1)の楽器の音の音階などの情報:各サンプル情報


のように2段階の情報にします。

(1)ソース情報は以下の案です。


[カテゴリ番号,ソース番号, [メーカー,地域,...],音源説明,補足],...


カテゴリ番号:

準備その1で決めた分類を使います。

当面99以下しか使わない前提で「今は」2桁にしてみます。

ソース番号:

カテゴリ番号での単なる順番です。

基本的に1から始めますが、分類上番号が連続していなくても可とします。

[メーカー,地域,...]:

別途きめます。

(ない場合は不明な場合は[]だけです。[ ]内の要素数は不定です。

(ex.[Steinway & Son's, Germany,Model A-188,NHK Hall]))

音源説明: 任意の文字(ただし全角、半角カナは不可)
補足: 任意の文字(〃)

 

(2)各サンプル情報は以下の案です。


[カテゴリ番号, ソース番号, 音階, 識別, [拡張情報]],...


とします。

カテゴリ番号:

準備その1で決めた分類を使います。(1)と同じ

当面99以下しか使わない前提で「今は」2桁にしてみます。

ソース番号:

カテゴリ番号での単なる順番です。(1)と同じ

基本的に1から始めますが、分類上番号が連続していなくても可とします。

音階:

1~127はMIDIノート番号,平均律(12クロマ)におしこめられるもののみ

0は音階なし(もしくは不明)

識別:

※音階、識別のどちらか一方は必須

音階でない音の識別1桁目が識別で2桁以降が識別詳細データ。

純正率などを調ごとに厳密にやりたい場合はこちらも使うことになると思います。(複数の調の音を識別する?)民族音楽系もおおくはこちらでしょうか。。。(サンプリング済のものは音階でよいと思います)

(識別の補足) 0は識別なし。
 言語音声の場合: VX(Xには発音記号半角アルファベットがはいるボカロ発音記号準拠。桁数任意)
 代表周波数の場合: Fnnnnn(nnnnには代表周波数(Hz)がはいります)
 そのた諸々 必要の都度、識別記号と識別内容を発案してくわえます(笑い声とか鳴き声とかetc)
[拡張情報]:

今はリザーブ、案として

サブカテゴリ:DBごとに任意
感情(その音で呼び出される感情の種類:主観でよい)
状態(機械動作音であれば、故障中(故障パターン識別)とか故障直前とか、初期起動音とか、エージング終了後の動作音とかの種類)
いずれも1桁目は分類のIDがはいり、2桁目以降は内容を記載します。
拡張情報は任意に複数持てる。ない場合は空の [ ] を書きます。

(1),(2)共通で以下のようにします。

なお、

■情報はすべてアルファベットと記号、数字とする。(漢字、カナは不可)
■文字にセミコロンは使用しない。
■, [ ] は配列の構造に使用するので、データとしては使用しない
■文字列は"や'で囲まない
情報の保存場所とファイル名
■データ作成の効率を考え、該当サウンドDB作成に仕様するフォルダをXXYYYX,Yは数字)の5桁とする。XXはカテゴリ番号、YYYはソース番号とする。
■情報とwaveファイルはは各フォルダ(0000199999)の中にフォルダ名と同じファイル名 (02001.txt,02001.wavなど)で保持する。

XX,YYYのペアは作成するサウンドDB内でのみ一意とする。(他のサウンドDBではまた別の音を入れてももちろんよい)

■登録用のファイルは、作成がしやすいように若干フォーマット調整する。
■ソース情報、サンプル情報は登録用はまとめて1ファイルのテキストにする。
■■1行目は(1)ソース情報とする。(カテゴリ番号,ソース番号, [メーカー,地域,...],音源説明, 補足)
■■2行目以降は(2)各サンプル情報とする。各行に(サンプル番号(1から連番),音階, 識別, [拡張情報])をかく
■■これら情報はwave登録用のフォーマットに整形のうえ、wavファイルの所定のタグに登録する。

----------------------------------------------
02002.txt
----------------------------------------------
2,2, [ ] ,Keppy Steinway Piano V6.x,SoundFont
1,21,0, [ ]
2,22,0, [ ]
3,23,0, [ ]
...
88,108,0, [ ]
----------------------------------------------
のようにします。サンプル番号を書くのは、登録ミスチェック用です。音階を全部書くのは、特定12半音がでない(近似該当がない)楽器があるためです。

プログラムソース

 すこし長いですが、切り出してもわかりにくいので、そのまま貼ります。

import numpy as np
import os.path
import re

STRCODE = 'utf-8'
'''
=====================================================================================
機械学習用waveファイル結合プログラム
(複数のファイルを結合するとともに、機械学習用の付加情報をwaveファイルに取り込みます)
=====================================================================================
'''

class VWave:
# 読み込んだwaveファイルの情報を格納するクラスです。
SAMPLING_COUNT = 32768
INSTR_TAG = 'ISFT'
NOTE_TAG = 'ICMT'
# 地道にwavフォーマット情報を定義していきます。
#
IFF_ALL = 0
IFF_ID, IFF_SIZE, IFF_FMT, IFF_SUBID, IFF_SUBSIZE = 1, 2, 3, 4, 5
IFF_WAVFOMT, IFF_CHANNEL, IFF_SAMPLING, IFF_BYTEPS, IFF_BLKSIZE = 6, 7, 8, 9, 10
IFF_BIT, IFF_DATAID, IFF_DATASIZE, IFF_DATA, IFF_LIST = 11, 12, 13, 14, 15
IFF_LISTID, IFF_LISTSIZE, IFF_INFOID = 16, 17, 18
TYPE_BYTE = "byte"
TYPE_STR = "string"
TYPE_LITTLE = "little"
DEF_METAID, DEF_TITLE, DEF_TYPE, DEF_START, DEF_BYTE, DEF_ENC = 0, 1, 2, 3, 4, 5
IFF_DEF = [
[IFF_ALL, "ALL DATA ", TYPE_BYTE, None, None, None ],
[IFF_ID, "ID ", TYPE_STR, 0, 4, STRCODE],
[IFF_SIZE, "Chunk Size", TYPE_LITTLE, 4, 4, None],
[IFF_FMT, "FormatName", TYPE_STR, 8, 4, STRCODE],
[IFF_SUBID, "Sub ID ", TYPE_STR, 12, 4, STRCODE],
[IFF_SUBSIZE, "Sub Size ", TYPE_LITTLE, 16, 4, None],
[IFF_WAVFOMT, "WavFomat ", TYPE_LITTLE, 20, 2, None],
[IFF_CHANNEL, "Channel ", TYPE_LITTLE, 22, 2, None],
[IFF_SAMPLING,"Sampling ", TYPE_LITTLE, 24, 4, None],
[IFF_BYTEPS, "BytePS ", TYPE_LITTLE, 28, 4, None],
[IFF_BLKSIZE, "BlockSize ", TYPE_LITTLE, 32, 2, None],
[IFF_BIT, "Bit ", TYPE_LITTLE, 34, 2, None],
[IFF_DATAID, "DataID ", TYPE_STR, 36, 4, STRCODE],
[IFF_DATASIZE,"DataSize ", TYPE_LITTLE, 40, 4, None],
[IFF_DATA, "IFF Data ", TYPE_BYTE, 44, None, None],
[IFF_LIST, "List ", TYPE_BYTE, 0, None, None ],
[IFF_LISTID, "List ID ", TYPE_STR, 0, 4, STRCODE ],
[IFF_LISTSIZE,"List Size ", TYPE_LITTLE, 4, 4, None],
[IFF_INFOID, "INFO ID ", TYPE_STR, 8, 4, STRCODE]
]
#読み込んだ情報を格納する配列です。最初はクラス変数を使っていましたが、多すぎてわかりにくくなったため、
#すべて配列で持つことにしました。
iff = []
iffSubChunkID = []
iffSubChunkSize = []
iffSubChunkData = []

def __init__(self, data):
self.iff =[b'', "", 0, "", "", 0, 0, 0, 0, 0, 0, 0, "", 0, b'', b'', "", 0, ""]
siff = self.iff
sALL = self.IFF_ALL
dMetaID, dTitle, dType, dStart, dByte, dEnc = self.DEF_METAID, self.DEF_TITLE, self.DEF_TYPE, self.DEF_START, self.DEF_BYTE, self.DEF_ENC
tByte, tStr, tLittle = self.TYPE_BYTE, self.TYPE_STR, self.TYPE_LITTLE
# 以降全体から特定部分へアクセスするためにバイト配列にして格納します。
siff[sALL] = np.frombuffer(data, dtype=self.IFF_DEF[sALL][dType])
#順次定義の内容に基づき、data内容を解釈して配列に格納します。
for i in range(14)[1:]:
iDEF = self.IFF_DEF[i]
if iDEF[dType] == tStr:
siff[i] = (b''.join(siff[sALL][iDEF[dStart]:iDEF[dStart] + iDEF[dByte]])).decode(iDEF[dEnc])
elif iDEF[dType] == tLittle:
siff[i] = int.from_bytes(siff[sALL][iDEF[dStart]:iDEF[dStart] + iDEF[dByte]], tLittle)

#Waveデータ部分を格納します。この部分が可変長なんです。
iDEF = self.IFF_DEF[self.IFF_DATA]
siff[self.IFF_DATA] = b''.join(siff[sALL][iDEF[dStart]:iDEF[dStart] + siff[self.IFF_DATASIZE]])
#タグの部分はIFF_LISTで示す配列に格納します。
siff[self.IFF_LIST] = siff[sALL][44 + siff[self.IFF_DATASIZE]:]

#タグの部分の範囲を解釈して配列「XXXiffSubChunk」に格納します。
if (siff[self.IFF_LIST].size != 0):
sList = siff[self.IFF_LIST]
iDEF = self.IFF_DEF[self.IFF_LISTID]
siff[self.IFF_LISTID] = (b''.join(sList[iDEF[dStart]:iDEF[dStart] + iDEF[dByte]])).decode(iDEF[dEnc])
iDEF = self.IFF_DEF[self.IFF_LISTSIZE]
siff[self.IFF_LISTSIZE] = int.from_bytes(sList[iDEF[dStart]:iDEF[dStart] + iDEF[dByte]], tLittle)
iDEF = self.IFF_DEF[self.IFF_INFOID]
siff[self.IFF_INFOID] = (b''.join(sList[iDEF[dStart]:iDEF[dStart] + iDEF[dByte]])).decode(iDEF[dEnc])
offset = 12
for i in range(999):
chunkID = (b''.join(sList[offset:offset + 4])).decode(STRCODE)
subChunkSize = int.from_bytes((b''.join(sList[offset + 4:offset + 8])), tLittle)
chunkData = (b''.join(sList[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 siff[self.IFF_LISTSIZE] <= offset + 12:
break

def print(self):
#読み込んだwavファイルの内容をレポートします。
siff = self.iff
sALL = self.IFF_ALL
dMetaID, dTitle, dType, dStart, dByte, dEnc = self.DEF_METAID, self.DEF_TITLE, self.DEF_TYPE, self.DEF_START, self.DEF_BYTE, self.DEF_ENC
tByte, tStr, tLittle = self.TYPE_BYTE, self.TYPE_STR, self.TYPE_LITTLE
print('Toral size = ' + "{0:,d}".format(siff[sALL].size))
for i in range(19)[1:]:
if (i == 14):
if (siff[self.IFF_LIST].size != 0):
print("-------------------------------------------")
continue
else:
#このブロックは調整中です
break
iDEF = self.IFF_DEF[i]
if iDEF[dType] == tStr:
print(iDEF[dTitle] + '=' + siff[i])
elif iDEF[dType] == tLittle:
print(iDEF[dTitle] + '=' + "{0:,d}".format(siff[i]))
print("<<This wave file contains [ " + "{0:,d}".format(siff[self.IFF_DATASIZE] // (self.SAMPLING_COUNT * siff[self.IFF_BLKSIZE])) + "] sound>>")
print("-------------------------------------------")

def printSize(self):
siff = self.iff
print("Chunk Size=" + siff[self.IFF_SIZE])

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

def cat(self, data):
#結合用のメソッドです。元のソースとチャンネル数、サンプリングレート、ビット数が異なればエラーにします。
#エラーがあれば結合しません。
if (self.iff[self.IFF_CHANNEL] != data.iff[data.IFF_CHANNEL]):
print("Channel is different base=" + "0:d".format(self.iff[self.IFF_CHANNEL]) +
"source=" + "0:d".format(data.iff[self.IFF_CHANNEL]))
return -1
if (self.iff[self.IFF_SAMPLING] != data.iff[data.IFF_SAMPLING]):
print("Samplint is different base=" + "0:d".format(self.iff[self.IFF_SAMPLING]) +
":source=" + "0:d".format(data.iff[self.IFF_SAMPLING]))
return -1
if (self.iff[self.IFF_BIT] != data.iff[data.IFF_BIT]):
print("Bit is different base=" + "0:d".format(self.iff[self.IFF_BIT]) +
":source=" + "0:d".format(data.iff[self.IFF_BIT]))
return -1
#結合するサンプリング数がSAMPLING_COUNTの整数倍でないと、エラーとします。
if (data.iff[data.IFF_DATASIZE] % self.SAMPLING_COUNT != 0):
print("Sampling count is not base on :" + "0:d".format(self.SAMPLING_COUNT) )

#結合は単純に足しているだけです。
self.iff[self.IFF_DATA] += data.iff[data.IFF_DATA]
self.iff[self.IFF_DATASIZE] += data.iff[data.IFF_DATASIZE]
self.iff[self.IFF_SIZE] += data.iff[data.IFF_DATASIZE]

def catInstrumentInfo(self, info):
#ファイルから読み込んだデータについて、楽器情報を結合します。
tagInstr = self.INSTR_TAG
if tagInstr in self.iffSubChunkID:
itagNo = self.iffSubChunkID.index(tagInstr)
else:
self.iffSubChunkID.append(tagInstr)
self.iffSubChunkData.append("")
self.iffSubChunkSize.append(0)
itagNo = self.iffSubChunkID.index(tagInstr)
sInfo = info.split('\n')
self.iffSubChunkData[itagNo] += '[' + sInfo[0] + "],"
self.iffSubChunkSize[itagNo] = len(self.iffSubChunkData[itagNo])

def catSoundInfo(self, info):
# ファイルから読み込んだデータについて、各音の情報を結合します。
tagNote = self.NOTE_TAG
if tagNote in self.iffSubChunkID:
itagNo = self.iffSubChunkID.index(tagNote)
else:
self.iffSubChunkID.append(tagNote)
self.iffSubChunkData.append("")
self.iffSubChunkSize.append(0)
itagNo = self.iffSubChunkID.index(tagNote)
sInfo = info.split('\n')
sInstr = sInfo[0]
sInstrInfo = sInstr.split(',')

for i in range(len(sInfo))[1:]:
sNoteInfo = sInfo[i].split(',')
if len(sInfo[i].strip(' ')) == 0:
#空の行があればそこで終了
break
self.iffSubChunkData[itagNo] += '[' + sInstrInfo[0] + ',' +\
sInstrInfo[1] + ',' +\
sNoteInfo[1] + ',' +\
sNoteInfo[2] + ',' +\
sNoteInfo[3] + "],"
self.iffSubChunkSize[itagNo] = len(self.iffSubChunkData[itagNo])

def getWave(self):
#Waveデータのフォーマットでバイナリで取り出すメソッドです。
dMetaID, dTitle, dType, dStart, dByte, dEnc = self.DEF_METAID, self.DEF_TITLE, self.DEF_TYPE, self.DEF_START, self.DEF_BYTE, self.DEF_ENC
work = b''
workInfo = b'INFO'
for i in range(len(self.iffSubChunkID)):
workInfo += self.iffSubChunkID[i].encode(STRCODE)
workInfo += (self.iffSubChunkSize[i]+1).to_bytes(4, 'little')
workInfo += self.iffSubChunkData[i].encode(STRCODE) + b'\x00'
if ((len(self.iffSubChunkData[i].encode(STRCODE)) + 1) % 2 == 1):
workInfo += b'\x00'
workList = b'LIST' + (len(workInfo)).to_bytes(4, 'little') + workInfo
self.iff[self.IFF_SIZE] = self.iff[self.IFF_SUBSIZE] + self.iff[self.IFF_DATASIZE] + len(workInfo) + 28
for i in range(14):
siffData = self.iff[i]
iDEF = self.IFF_DEF[i]
if (iDEF[dType] == self.TYPE_STR):
work = work + siffData.encode(iDEF[dEnc])
elif (iDEF[dType] == self.TYPE_LITTLE and iDEF[dByte] == 2 ):
work = work + siffData.to_bytes(2, 'little')
elif (iDEF[dType] == self.TYPE_LITTLE and iDEF[dByte] == 4):
work = work + siffData.to_bytes(4, 'little')
work = work + self.iff[self.IFF_DATA]
work += workList
return work

#出力するDBの名前です。
CAT_WAVE_FILE='.\\KooSoundDB.wav'
sCount = 0
#カレントディレクトリで、"00001"から"99999"(5桁)のフォルダがあれば名前を取得してソートします
output_path = "."
files = os.listdir(output_path)
list_dir = [f for f in files if os.path.isdir(os.path.join(output_path, f))]
sound_dir = [i for i in list_dir if re.search(r'[0-9][0-9][0-9][0-9][0-9]', i)]
sound_dir.sort()
print(sound_dir)
#フォルダ名と同じ番号のwavファイルがあるか調べます。
for dir_name in sound_dir:
fileName = dir_name + "\\" + dir_name + ".wav"
infoFileName = dir_name + "\\" + dir_name + ".txt"
if os.path.isfile(fileName):
if not os.path.isfile(infoFileName):
print("Not exist:" + infoFileName)
continue
sCount += 1
print ("exist:" + fileName)
f = open(fileName, 'rb')
fData = f.read()
t = open(infoFileName, 'r')
tData = t.read()
waveData = VWave(fData)
if (sCount == 1):
# 1個目は元データそのものです。
catWave = waveData
else:
# 2個目以降は結合していきます。
catWave.cat(waveData)
waveData.print()
waveData.catInstrumentInfo(tData)
waveData.catSoundInfo(tData)
#タグリストはget/setするので確認用です。setはまだ作ってません。
subChunkID, subChunkSize, subChunkData = waveData.getList()
for i in range(len(subChunkID)):
print(">" + subChunkID[i] + ":{0:4d}:".format(subChunkSize[i]) + subChunkData[i])
f.close()
t.close()
print("===========================================")
print("Output file name:" + CAT_WAVE_FILE)
print("===========================================")
catWave.print()
#出力用のファイルをオープンし、結合演算済の内容をwavファイルに出力します。
fw = open(CAT_WAVE_FILE, 'wb')
fw.write(catWave.getWave())
fw.close()

 前のプログラムからタグ関連の編集を追加しました。差分だけのせてもわかりにくいので、全部はりました。

 まとめ 

簡単なテストではOKでした。

このプログラムでファイルを2000個以上、容量で6GB(10万音でそれくらい)まで結合できるかテストが必要です。一度全部メモリに乗せる単純なつくりなので、問題があればPGを改変します。(テストはたいへんなわりにおもしろくないので、結果だけいずれ報告します。)最終的なファイルは1つあたり、アップロードの扱いやすさの感覚的な上限の1GB以下がいいと感じていますが、10万音だと6GBくらいになります。圧縮して1GB以下になればよしとしますが、それでも超えるようだとどこかを調整して「圧縮して1GB以下にします」(1ファイル2GBまでならOKなフリーのネットストレージサービスもあるようなので、2GBを上限にするかもしれません。。。)

他のタグの情報も若干追加してプログラムの完成となる予定です。 

祈りをいま君のもとへ。

もしもボクニューロンの1細胞ならばのです

1人称研究というのがあります。人類のいままでの科学や実験では「客観性」や「再現性」を重視してきました。ある特定の視点を欠落させている反省から、「1人称主観」による情報処理結果、判断を重要視する考え方がでてきたようです。(内容はあまり理解していないので説明できません。すみません)
1人称研究の本は読んだことがないのですが、是非読んでみたいです。*1それとは別ですが、ボクのブログで推奨している(といっても助手が推奨していて、ボクが同感しているだけですが)「自在主観」があります。*2自己符号化器の自由研究にあたっては「ジブンがもし1つのニューロン細胞であったら、どう成長し、何を日々の糧とし、何を喜びとし、何を目的に生き、どう死ぬのか」を考え続けています。

1人称といえば

ブログにつかう1人称を何にするかも話題です。
このブログは、複数によって運営されています。(人工知能はもちろん含まれていませんのでご安心ください。ただし開発できたらこっそりメンバーが増えるかもしれません。)このブログにはすこしだけ規定(レギュレーション)があり、「1人称については固有のものを使う」ことを決め事にしています。
性別や年齢、リアルの立場、(現在の)プライベートに関することは公開してはいけないことにしています。(そのほうがおもしろそうなので、という単純にして最強の理由です。)それ以外は各自の判断で自由です。このブログでは1人称はそれぞれ各者固有としています。*3


このおはなしの登場人物 / いきもの


ボク:かえるのクー (井戸中 空)/ Koo Wells
わたくし:井戸中 聖(イトナカ アキラ)
吾(あ):井戸中 芽守(イトナカ メモル)
未発言:井戸中 瑠璃(イトナカ ルリ)(るぅりぃ)
未発言:井戸中 明日良(イトナカ アスラ)(アッシュ、あーくん)


「君の名は」でジブンの1人称に関する(外国の人には絶対伝わらないと思う)「ギャグ」があり、気に入っています。1人称がこれだけ多岐にわたり、発言する人の社会的地位、性別、シュチュエーションでバリエーションがある言語は、日本語がトップクラスであるとききました。ということで、1人称で遊んでいます。なおこのブログでは「わたし」は大好きな「人類は衰退しました」の「わたし」ちゃんの固有名詞である扱いなのでだれも使えません。(ああ、Voltaよりもこのえんばんがほしい。ねたばれ、みるです?*4) 

*1:一般的に考えると統計的に十分な数の「1人称主観」の判断を、主観者についてグループ化すれば、そのグループごとの客観視点(おおまかな総意)とバリエーション(個々の主観のブレ)があきらかになります。もっと大数でサンプリングすれば、人類の思考パターン(の流行)が明確になります。そのうち「人工知能」にこれらの情報がinputされると考えています。

*2:助手の造語と思われます。「執筆依頼があれば書くよ」と助手たちの言動は本当に訳がわかりません。自在主観とは対象物そのものに「本当になったつもり」になって思考する発想テクニックです。(本気度がないと単なる思考の遊びにおわります)ジブンが孫〇義や、イー論マ〇クになったつもりで思考し、そのビジネス思考パタンをなぞる練習にも使えます。絶対絶命の状態や社内的窮地を想像できると、臨場感が増し効果があると考えます。ヒトではなく「モノ」になりきる主観のほうが、実験や研究では違った発想を思いつけることがあるので有用だと力説してました。

*3:1人称の決定権は皆には不評ですがボクが持っています。

*4:妖精さんのちからでもいいから、このブログ「せんきゃくばんらいですからー」と言ってみたい。妖精さん密度100fくらいになれば、きっと人工知能なんて1晩で完成するんでしょうが。

計算機音楽の自由研究(準備:その2.8)~機械学習用サウンドデータベースをつくる(結合編)

はじめに

今年は春のけだるさ病*1にはかからなかったようです。熱中できることを持つことは素晴らしいと感じました。

(準備:その2.6)で、一連の音の作成(たとえばあるVSTi音源のMIDIノート21~108の音)までは、設定して実行すれば簡単に行えるようになりました。

f:id:np2LKoo:20170519231630p:plain

パソコンの処理能力が低いので音を出すと(録音すると)よくノイズが乗ることがあります。外部ノイズではなく、明らかにCPU側能力不足のノイズです。そのため、もっと多数の音を一度に作成することはぜず、一旦作成単位のwavファイル(30音~100音くらい)ごとに、ノイズが乗っていないかチェックします。そのあとで、まとめて1つのDBに結合することを考えます。(6月末まで反省キャンペーン中です。内容を順次リファインします。)

機械学習用ファイル作成プログラムの要件

・複数の所定のフォルダに格納されたwavファイルを結合する。
・同じ条件(サンプリングレートやビット数など)の録音でないとチェックしてエラーとする。
所定のフォルダは数字5桁(00001,00002...)などに決め、その中にフォルダと同名のwavファイル(名前は任意、拡張子は.wav)を置く。(わざわざ00001などのフォルダをつくるのは、その中に複数の候補や作成補助用の(DBには直接関係しない)ファイルをもたせたいからです。)

以下の実装はまた今度しますが、定義だけしておきます。
・準備:その2.7で確認したタグのフォーマットを参考にコメント(ICMT)欄に音(0.68秒のサンプル単位)に関する基本情報を書く。(内容は別途きめる)
・ソース(ISRC)欄に音に関する情報を書く(音源の単位)
・DBに関するサマリ情報をプロダクト(アルバム)欄(IPRD)に書く
・いろいろな情報を乗せても、wavファイル単独で音が聞けるようにする。
・データベースが大きくなると、音の頭出しが難しくなるので、情報を指定すると特定の音を簡単に再生できるしくみを考える。(これは結合用プログラムとは別に考える)

結合用のプログラムを作成してみます。

準備:その2.7のプログラムを元にもうすこし汎用化して結合機能をもたせてみます。

汎用化すると逆に「読みにくい」ソースとなってしまいました。*2小出しで本当に申し訳ないですが、今回は結合する部分だけです。作成中ながら貼っちゃいます。結合する部分と出力する部分だけならあわせて10~20行程度なのですが、内容確認用の情報表示を汎用的にできるように作成したので長くなってしまいました。Python標準的な命名規約には全く従っていません。わかりやすいソースが書けるように勉強中です。

waveファイルのフォーマットについては前にも貼りましたが、こちらを参照してください。実験用でテストも十分行っていないので、間違いがあっらこっそり張り替えます。


import numpy as np
import os.path
import re

STRCODE = 'utf-8'
'''
=====================================================================================
Waveファイルに関するフォーマット情報を地道に定義してみます。
とりあえず、今回はwavファイルを結合するところまでやります。(クラスは未完成です)
=====================================================================================
'''

class VWave:
# 読み込んだwaveファイルの情報を格納するクラスです。
SAMPLING_COUNT = 32768
# 地道にwavフォーマット情報を定義していきます。
IFF_ALL = 0
IFF_ID, IFF_SIZE, IFF_FMT, IFF_SUBID, IFF_SUBSIZE = 1, 2, 3, 4, 5
IFF_WAVFOMT, IFF_CHANNEL, IFF_SAMPLING, IFF_BYTEPS, IFF_BLKSIZE = 6, 7, 8, 9, 10
IFF_BIT, IFF_DATAID, IFF_DATASIZE, IFF_DATA, IFF_LIST = 11, 12, 13, 14, 15
IFF_LISTID, IFF_LISTSIZE, IFF_INFOID = 16, 17, 18
TYPE_BYTE = "byte"
TYPE_STR = "string"
TYPE_LITTLE = "little"
DEF_METAID, DEF_TITLE, DEF_TYPE, DEF_START, DEF_BYTE, DEF_ENC = 0, 1, 2, 3, 4, 5
IFF_DEF = [
[IFF_ALL, "ALL DATA ", TYPE_BYTE, None, None, None ],
[IFF_ID, "ID ", TYPE_STR, 0, 4, STRCODE],
[IFF_SIZE, "Chunk Size", TYPE_LITTLE, 4, 4, None],
[IFF_FMT, "FormatName", TYPE_STR, 8, 4, STRCODE],
[IFF_SUBID, "Sub ID ", TYPE_STR, 12, 4, STRCODE],
[IFF_SUBSIZE, "Sub Size ", TYPE_LITTLE, 16, 4, None],
[IFF_WAVFOMT, "WavFomat ", TYPE_LITTLE, 20, 2, None],
[IFF_CHANNEL, "Channel ", TYPE_LITTLE, 22, 2, None],
[IFF_SAMPLING,"Sampling ", TYPE_LITTLE, 24, 4, None],
[IFF_BYTEPS, "BytePS ", TYPE_LITTLE, 28, 4, None],
[IFF_BLKSIZE, "BlockSize ", TYPE_LITTLE, 32, 2, None],
[IFF_BIT, "Bit ", TYPE_LITTLE, 34, 2, None],
[IFF_DATAID, "DataID ", TYPE_STR, 36, 4, STRCODE],
[IFF_DATASIZE,"DataSize ", TYPE_LITTLE, 40, 4, None],
[IFF_DATA, "IFF Data ", TYPE_BYTE, 44, None, None],
[IFF_LIST, "List ", TYPE_BYTE, 0, None, None ],
[IFF_LISTID, "List ID ", TYPE_STR, 0, 4, STRCODE ],
[IFF_LISTSIZE,"List Size ", TYPE_LITTLE, 4, 4, None],
[IFF_INFOID, "INFO ID ", TYPE_STR, 8, 4, STRCODE]
]
#読み込んだ情報を格納する配列です。最初はクラス変数を使っていましたが、多すぎてわかりにくくなったため、
#すべて配列で持つことにしました。
iff = []
iffSubChunkID = []
iffSubChunkSize = []
iffSubChunkData = []

def __init__(self, data):
self.iff =[b'', "", 0, "", "", 0, 0, 0, 0, 0, 0, 0, "", 0, b'', b'', "", 0, ""]
siff = self.iff
sALL = self.IFF_ALL
dMetaID, dTitle, dType, dStart, dByte, dEnc = self.DEF_METAID, self.DEF_TITLE, self.DEF_TYPE, self.DEF_START, self.DEF_BYTE, self.DEF_ENC
tByte, tStr, tLittle = self.TYPE_BYTE, self.TYPE_STR, self.TYPE_LITTLE
# 以降全体から特定部分へアクセスするためにバイト配列にして格納します。
siff[sALL] = np.frombuffer(data, dtype=self.IFF_DEF[sALL][dType])
#順次定義の内容に基づき、data内容を解釈して配列に格納します。
for i in range(14)[1:]:
iDEF = self.IFF_DEF[i]
if iDEF[dType] == tStr:
siff[i] = (b''.join(siff[sALL][iDEF[dStart]:iDEF[dStart] + iDEF[dByte]])).decode(iDEF[dEnc])
elif iDEF[dType] == tLittle:
siff[i] = int.from_bytes(siff[sALL][iDEF[dStart]:iDEF[dStart] + iDEF[dByte]], tLittle)

#Waveデータ部分を格納します。この部分が可変長なんです。
iDEF = self.IFF_DEF[self.IFF_DATA]
siff[self.IFF_DATA] = b''.join(siff[sALL][iDEF[dStart]:iDEF[dStart] + siff[self.IFF_DATASIZE]])
#タグの部分はIFF_LISTで示す配列に格納します。
siff[self.IFF_LIST] = siff[sALL][44 + siff[self.IFF_DATASIZE]:]

#タグの部分の範囲を解釈して配列「XXXiffSubChunk」に格納します。
if (siff[self.IFF_LIST].size != 0):
sList = siff[self.IFF_LIST]
iDEF = self.IFF_DEF[self.IFF_LISTID]
siff[self.IFF_LISTID] = (b''.join(sList[iDEF[dStart]:iDEF[dStart] + iDEF[dByte]])).decode(iDEF[dEnc])
iDEF = self.IFF_DEF[self.IFF_LISTSIZE]
siff[self.IFF_LISTSIZE] = int.from_bytes(sList[iDEF[dStart]:iDEF[dStart] + iDEF[dByte]], tLittle)
iDEF = self.IFF_DEF[self.IFF_INFOID]
siff[self.IFF_INFOID] = (b''.join(sList[iDEF[dStart]:iDEF[dStart] + iDEF[dByte]])).decode(iDEF[dEnc])
offset = 12
for i in range(999):
chunkID = (b''.join(sList[offset:offset + 4])).decode(STRCODE)
subChunkSize = int.from_bytes((b''.join(sList[offset + 4:offset + 8])), tLittle)
chunkData = (b''.join(sList[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 siff[self.IFF_LISTSIZE] <= offset + 12:
break

def print(self):
#読み込んだwavファイルの内容をレポートします。
siff = self.iff
sALL = self.IFF_ALL
dMetaID, dTitle, dType, dStart, dByte, dEnc = self.DEF_METAID, self.DEF_TITLE, self.DEF_TYPE, self.DEF_START, self.DEF_BYTE, self.DEF_ENC
tByte, tStr, tLittle = self.TYPE_BYTE, self.TYPE_STR, self.TYPE_LITTLE
print('Toral size = ' + "{0:,d}".format(siff[sALL].size))
for i in range(19)[1:]:
if (i == 14):
if (siff[self.IFF_LIST].size != 0):
print("-------------------------------------------")
continue
else:
#このブロックは調整中です
break
iDEF = self.IFF_DEF[i]
if iDEF[dType] == tStr:
print(iDEF[dTitle] + '=' + siff[i])
elif iDEF[dType] == tLittle:
print(iDEF[dTitle] + '=' + "{0:,d}".format(siff[i]))
print("<<This wave file contains [ " + "{0:,d}".format(siff[self.IFF_DATASIZE] // (self.SAMPLING_COUNT * siff[self.IFF_BLKSIZE])) + "] sound>>")
print("-------------------------------------------")

def printSize(self):
siff = self.iff
print("Chunk Size=" + siff[self.IFF_SIZE])

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

def cat(self, data):
#結合用のメソッドです。元のソースとチャンネル数、サンプリングレート、ビット数が異なればエラーにします。
#エラーがあれば結合しません。
if (self.iff[self.IFF_CHANNEL] != data.iff[data.IFF_CHANNEL]):
print("Channel is different base=" + "0:d".format(self.iff[self.IFF_CHANNEL]) +
"source=" + "0:d".format(data.iff[self.IFF_CHANNEL]))
return -1
if (self.iff[self.IFF_SAMPLING] != data.iff[data.IFF_SAMPLING]):
print("Samplint is different base=" + "0:d".format(self.iff[self.IFF_SAMPLING]) +
":source=" + "0:d".format(data.iff[self.IFF_SAMPLING]))
return -1
if (self.iff[self.IFF_BIT] != data.iff[data.IFF_BIT]):
print("Bit is different base=" + "0:d".format(self.iff[self.IFF_BIT]) +
":source=" + "0:d".format(data.iff[self.IFF_BIT]))
return -1
#結合するサンプリング数がSAMPLING_COUNTの整数倍でないと、エラーとします。
if (data.iff[data.IFF_DATASIZE] % self.SAMPLING_COUNT != 0):
print("Sampling count is not base on :" + self.SAMPLING_COUNT )

#結合は単純に足しているだけです。
self.iff[self.IFF_DATA] += data.iff[data.IFF_DATA]
self.iff[self.IFF_DATASIZE] += data.iff[data.IFF_DATASIZE]
self.iff[self.IFF_SIZE] += data.iff[data.IFF_DATASIZE]


def getWave(self):
#Waveデータのフォーマットでバイナリで取り出すメソッドです。
dMetaID, dTitle, dType, dStart, dByte, dEnc = self.DEF_METAID, self.DEF_TITLE, self.DEF_TYPE, self.DEF_START, self.DEF_BYTE, self.DEF_ENC
work = b''
for i in range(14):
siffData = self.iff[i]
iDEF = self.IFF_DEF[i]
if (iDEF[dType] == self.TYPE_STR):
work = work + siffData.encode(iDEF[dEnc])
elif (iDEF[dType] == self.TYPE_LITTLE and iDEF[dByte] == 2 ):
work = work + siffData.to_bytes(2, 'little')
elif (iDEF[dType] == self.TYPE_LITTLE and iDEF[dByte] == 4):
work = work + siffData.to_bytes(4, 'little')
work = work + self.iff[self.IFF_DATA]
return work

#出力するDBの名前です。
CAT_WAVE_FILE='KooSoundDB.wav'
sCount = 0
#カレントディレクトリで、"00001"から"99999"(5桁)のフォルダがあれば名前を取得してソートします
output_path = "."
files = os.listdir(output_path)
list_dir = [f for f in files if os.path.isdir(os.path.join(output_path, f))]
sound_dir = [i for i in list_dir if re.search(r'[0-9][0-9][0-9][0-9][0-9]', i)]
sound_dir.sort()
print(sound_dir)
#フォルダ名と同じ番号のwavファイルがあるか調べます。
for dir_name in sound_dir:
fileName = dir_name + "\\" + dir_name + ".wav"
if os.path.isfile(fileName):
sCount += 1
print ("exist:" + fileName)
f = open(fileName, 'rb')
aData = f.read()
waveData = VWave(aData)
if (sCount == 1):
# 1個目は元データそのものです。
catWave = waveData
else:
# 2個目以降は結合していきます。
catWave.cat(waveData)
waveData.print()
#タグリストはget/setするので確認用です。setはまだ作ってません。
subChunkID, subChunkSize, subChunkData = waveData.getList()
for i in range(len(subChunkID)):
print(">" + subChunkID[i] + ":{0:4d}:".format(subChunkSize[i]) + subChunkData[i])
f.close()
print("===========================================")
print("Output file name:" + CAT_WAVE_FILE)
print("===========================================")
catWave.print()
#出力用のファイルをオープンし、結合演算済の内容をwavファイルに出力します。
fw = open(CAT_WAVE_FILE, 'wb')
fw.write(catWave.getWave())
fw.close()

wavフォーマットの定義を配列(リスト)で行いました。本当はPythonstruct(バイナリ構造の定義、解釈)を使いたかったのですが、可変長のbyteデータの扱いがうまくできなかったので、このようになりました。
RIFF形式のファイルであれば少し改造するだけでなんでも読めるようになるはずです。(作ったボクでもわかりにくいソースになっており反省です)

実行結果例です。(テスト用)

['02001', '02002', '04001', '07001']
exist:02001\02001.wav
Toral size = 5,767,212
ID        =RIFF
Chunk Size=5,767,204
FormatName=WAVE
Sub ID    =fmt 
Sub Size  =16
WavFomat  =1
Channel   =1
Sampling  =48,000
BytePS    =96,000
BlockSize =2
Bit       =16
DataID    =data
DataSize  =5,767,168
<<This wave file contains [ 88] sound>>
-------------------------------------------
exist:02002\02002.wav
...
===========================================
Output file name:KooSoundDB.wav
===========================================
Toral size = 5,767,212
ID        =RIFF
Chunk Size=14,614,564
FormatName=WAVE
Sub ID    =fmt 
Sub Size  =16
WavFomat  =1
Channel   =1
Sampling  =48,000
BytePS    =96,000
BlockSize =2
Bit       =16
DataID    =data
DataSize  =14,614,528
<<This wave file contains [ 223] sound>>
-------------------------------------------

あとは、機械学習用のタグ情報を付加させる機能をつければ、機械学習用サウンドDB作成プログラムはほぼ完成です。

お元気で(2.7)!こんにちは(3.6)!!

 余談ですが、世の中の流れに従って完全にPython 3.xにしています。昨年はじめたときは2.xでしか動かない太古のソースをたくさん参照していたので、おのずと2.xを多用していたのですが、今年は完全に3.xベースにします。今年作成のソースは2.xでは動作しません*3のであしからず。

f:id:np2LKoo:20170520124131p:plain

2.7おくえんのパガーニ ウアイラロードスター

f:id:np2LKoo:20170520123120p:plain

おいしい3.6牛乳

次回は、この「機械学習用サウンドDB」(だけどwavファイルそのもの)に、機械学習に必要なタグ情報を付加する方法を考えます。

プログラムが完成すれば、あとは「ねこ音あつめ」です!めざせ!10万音!!

 

プロモーションキャンペーン終了のお知らせ

プロモーション空しく全く再生されません。2週間くらいカウントアップなしです。15HITからいっこうにアップしません!そこで、プロモーションは今月末までの期間限定となりました。(ジョーンズ調査員より:地球人は「限定」に弱い。)

月末までなら再生すると「小さな幸せ音ずれ訪れる」という特典付きです。(秘密結社「柔らか銀行」マーケット報告書より:日本人は「特典」に弱い。)
よろしくお願いいたします!めざせ20HIT!!!

*1:蛙界に存在する、生きることの意義に疑問を感じる青春期/思春期特有の病気。「蛙はなぜ生きるのか」をつきつめ、崇高な自己実現目的意識と自己潜在能力とのギャップに失望し、なにもヤルキが起こらなくなる、モチベーション低下スパイラルパターン。失望が大きいと食欲もなくなり死に至る病でもある。:おたまじゃくしから蛙になった年と翌年(感受性の強い蛙は毎年春期に)かかりやすいといわれている。

*2:余談ですが、クラスを使ったので「self.」がやまほどでてきました。あまりに多いところは、一旦別の変数におきかえることをしています。「self.」を1文字であらわす略記方法を採用してくれないかなぁ。。。1行の半分がself.である行が何行も続くと誰でもうんざりすると思います。慣れれば気にならないのかもしれませんが、初心者には辛いです。

*3:「今時、『機械学習するならPython2.xだよ!』なんて言っていると小学生に笑われる。」と、どなたかが書いていましたが、ドキッとしました

計算機音楽の自由研究(準備:その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:アスカニオ・コンディヴィの「ミケランジェロの生涯」の著作をイメージした日付かも

古き良き時代を思うようなお年頃になりました(助手)

あのころはよかった

Kooはがんばってプログラムをしているようですが、いっこうに進まず記事がかけないので、「なんか書いて!」と「命令」がありました。

Kooの助手の「井戸中 聖」(いとなか あきら)でございます。前回書いた記事が裏目にでて書くことになりました。ほんとにへそがないのに(以下略)。

目新しいことが何もないので、古き良き青春時代のことを書きます。精査すると年齢がばれるので、流し読みするか読み飛ばしてください。余談ですが、クーは実験自体にあまり他人を介在させないので、わたくしは「助手」というよりは「執事」といったほうが合っているかもしれません。

わが青春のBASIC

マイコン*1はTK-80の時代から興味がありますが、初めて触ったのはTK-80(+TK-80BS)にプラスチックの赤い筐体をまとわせた!懐かしのNEC COMPO BS/80でした。まだパソコン雑誌のないくらいの時期です。(トラ技ではいろいろやってたけど)

f:id:np2LKoo:20170512230914j:plain

パソコンといえば「PC-8001」や「MZ-80C/K」が主流になっていきますが、COMPO BS/80はそれよりも前の主流になれなかったマイコンです。*2

高価で買えないので、近くにできたパイロット店舗的ないわゆる「マイコンショップ」に足しげく通いました。当時は普及/啓蒙目的で自由に2時間くらいなら「占有して」使わせてもらいました。(もちろん無料、買えないのが分かっている学生にも優しかった。)カセットテープ片手に毎日のように通いました。

BASICプログラムでゲーム的なものを作って遊びました

電子回路は真空管が基本

いちおう理系の大学に行きましたが、予算の関係で実験機材は「骨董品」でした。

電子回路の実験は「真空管」でした。幼少のころでこそ、テレビは真空管があたりまえでしたが、大学にいくころには真空管を見るのもめずらしくなっていました。時代は「トランジスタ!」です。パッケージ化されたオペアンプなども手軽に入手できるようになっていたと記憶しています。そんなころだったので、「最先端を勉強するつもりがこれかぁ」とがっかりしたものです。ところが、「真空管」の素直な特性を計測するにつれ、終盤にはすっかり「真空管」のとりこになっていました。

残念ながら秋葉原の「ラジオ会館」はいまやフィギュアとカードショップばかりで、真空管やわけのわからないワクワクするジャンクを扱うお店がなくなりました。他の場所ではまだ古き良き電子骨董品を扱うお店がありますので、ぜひ頑張って存続してほしいです。話がそれました。

デバッグはハサミとのり(テープ)で!

学科実験実習の極め付けは「HITAC 10」(はいたっく てん)という日立製作所製の国産初のミニコンを使ったプログラミング実習でした。当時ですら「XX博物館に展示してあるやつと同じだよ。」と噂されていました。

f:id:np2LKoo:20170513000745j:plain

f:id:np2LKoo:20170513004234p:plain

f:id:np2LKoo:20170513004333j:plain

演習プログラムでこれくらだったかな。
ちなみにFORTRANコンパイラは30~40mくらいだったと思います。

記憶装置(主メモリ)に磁気コアを採用し、最大32K ワード、入出力はテレタイプ(そう!紙テープを打つタイプライタ)。言語はFORTRANでの実習でした。

COMPO BS/80を触ったことのあるわたくしにとっては、逆カルチャーショックでした。

まず紙テープのFORTRANコンパイラをロードするところからですが、IPL(Initial Program Loader)を手入力で入力するところからはじまります。30から40ワード入力したように覚えています。(1ワード=16ビット)

プログラムは念入りに紙に書いて「机上デバッグ」してから書いたとおりにまちがわずに入力しなければなりません。

1文字入力すると、1文字分(8ビット/文字としては7ビット、のこり1ビットはパリティ)の穴をあけていきます。間違って入力すると大変です。修正用のシールと単独穿孔の道具で紙に穴をあけたり、ふさいだりしなければなりません。まとめて修正する場合は紙テープの切り貼りです。正しい部分だけ入力して紙テープを作り、ハサミと紙テープ接続用のシールを使って「デバッグ」することになります。出力はプリンタだったと思いますが、ドットで文字をつくるものではなく、タイプライターそのものだった記憶があります。

初速度、初期角度と重さ、空気抵抗(係数)、気圧、到達最高予定高度、重力加速度などを適切に計算すれば弾道計算がかなりの精度でできます!!なお、慣れてくると「紙テープ」が読めるようになるのは「本当のこと」です。(いちいち対応表をみていたのではデバッグになりません)初代ウルトラマンで「キャップ!東京湾に怪獣出現です!!」といっていたおねえさんは、紙テープを読んでいましたが、「ほんとうかなぁ。」と思っていました。大学生になって、ジブンの無知さを恥じました。

f:id:np2LKoo:20170513001932j:plain

社会人になったころは、さすがに紙テープを使っているところはありません。いまでいうIT企業に就職したのですが、COBOLの実習をしました。でも実務ではほとんどCOBOLはやらず、かわった言語ばかりでした。LISPで最適配置計算や自動設計プログラムを組んだこともありました。

パンチャーのおねえさんはすてき

研修では、コーディングシートにちゃんとした文字で手書きし、「パンチャー」のおねえさんにお願いして「紙カード」にパンチしてもらいました。おねえさんは「驚異的なスピード」でパンチします。おどろきました。汚い字だとおねえさんがよめず、「これなんて字?」と聞かれ赤面することに。。。

紙カードは「落とすとたいへん」

落とすとたいへんなので、「落とさないように!」といったとたんにぶちまける人がいるのは、「なんとかの法則」とおりです。

順番がばらばらになった紙カードを順番に並べ替えて読ませないと、プログラムエラー

f:id:np2LKoo:20170513010421j:plain

になります。(1枚の紙は80桁まで文字を書け(穴あけ)、順番に紙カードリーダに読ませます。)なお、パンチャーのおねえさんがいたのは研修のときだけで、社に戻ってからは「実は紙カードなんてもう使ってない」ことを知りました。

f:id:np2LKoo:20170513010754p:plain

媒体といえば、「オープンリール磁気テープ」は本当に「鉄腕アトム」のお茶の水博士の研究所にあるようなものが動いていました。回転する方向や速度が生き物のように変わり、動きが独特なんです。わたくしが触っていた機器は、リールが録音機材のように横並びではなく、縦並びでしたが。。。ほかには、いまでいう「カーリング」のストーンよりも大きな「着脱式ハードディスク媒体」(ウインチェスタ型可換媒体)にも驚きました。10.5インチだったかな?なんと当時で70MBもの容量でした。媒体が可換ですがこれは触る機会はありませんでした。(オープンリールは何度も着脱しましたが、ワクワクしました)

人工知能には興味がありました

仕事がらパソコンに興味がなくなりました。(IT関係なので、帰ってまでさわらなくなったのです)このころも第何次だったか忘れましたが、人工知能がブームでアルゴリズムをいくつか考えたものです。そのうちのひとつは、いわゆる「ボルツマンマシン」に通ずるものだったと勝手に思っています。

ボルツマンマシン似てはいますが、発想が違い、当時スペック的に実現できなかった(一部のみ実現した)のですが、今のパソコンスペックでは十分実現可能なので、そのうちKooにお願いして創ってもらおうと思います。

UNIXは好きでした

 機器の廃棄にはゆるい時代だったので、会社で不要になったSun SparcStationというUNIX機を廃棄時にもらいうけ、自宅で遊んでいました。まだLINUXがなかった時代だったので重宝しました。でも仕事でUNIX系を使うことがほとんどなくなり、忘れてしまいました。一世を風靡したSunもいまやOracleの一部かぁ。。。(しみじみ)UNIX/LINUX系は今度また再入門してみます。(たまにLinux/UNIXを触る機会がありますが、vi(エディタ)は体が覚えているので、Windows系のエディタよりもいまだにしっくりきます。けれどOSコマンドはほとんど忘れてしまいました。MS-DOSコマンドよりは覚えていますが。。。(苦笑)コマンドプロンプトに必ずls と入力するのはお約束です。ちなみにawkが大好きでした。)

音楽の趣味など

音楽好きですが、あまり面白い話はないです。

箇条書きします。

・?学生の時、「カーペンターズ」の来日公演をみて感激した。

・高校のときピンクフロイドの「原子心母」を聞いて「プログレ」に泥沼的にはまった。

・同じく高校で「民族音楽」に目覚めた。「小泉文夫」大先生のFM放送は欠かさず聞いた。

・高校3年生のときに、受験勉強から逃避するため、独学で(といってもピアノを習っていたへたくそな妹から教えてもらったけど)ドビュッシーを中心に練習した。(そして今はもう弾けない)

・大学のとき「AMDEC CMU-800」を購入し「打ち込み」にはまる。*3

f:id:np2LKoo:20170513002542j:plain

とても高かったけど買ってしまった

CMU-800 写真はどなたのかを拝借

f:id:np2LKoo:20170513002336j:plain

こちらは憧れのMC-8

春の祭典」は発表されず残念

・知人のご家族がコルグ社に務めていたので、口利きで楽器店経由でしたが、Δ(デルタ)というキーボードを購入した。「シンセ好きなのに、MS-20でないの??」的な感じでしたが、ピアノの練習をしていた私はどうしても「和音」が弾きたかった。*4

・社会人?になってからは、平凡な生活を送ったが、アルバイトで「ゲーム音楽」の作曲をしたこともある。

・クーが計算機音楽の原点かつ古典的バイブル!と崇拝する「Bit別冊 コンピュータと音楽(1987/9)」はリアルタイムで買った。ほかにはMICROという雑誌(1984)のコンピュータと音楽の記事もおもしろかった(人工知能の記事も連載していた:捨ててないのでどこかにはある)。(追記)Bit別冊は1987/3に「コンピュータ・サイエンス」というのがでており、実は半分程度が計算機音楽関連の記事(論文の翻訳)です。「音楽と人工知能に関する研究」「計算機音楽のシステム構造」「計算機音楽の合成、演奏、作曲用プログラミング言語」「計算機と音楽のインターフェース:調査」があります。日本において計算機音楽の言葉は、この雑誌でごく一部に定着したと考えています。こちらも持ってます。(追記ここまで)

・クーと出会う何年か前からエレキギターを改めて練習し始めた。KingCrimsonのRedあたりをよく練習したが、最近はサボっている。

・今年はお庭で菜園をがんばる!ピアノとギターの練習も頑張る!

・最近まで「いえに帰ってねるだけ」な平凡な毎日。kooの助手になってからは面倒くさいながらも、ワクワクする日々。

う~ん。以上の情報でプロファイリングするとで、全世界の中から3人ぐらいまでに絞り込めるなぁ。(いや最後1つで特定可能になってる)

 

駄文をお許しください。

 

*1:パソコンといわれはじめたのは、そこそこ後になってからだった気がします。

*2:今調べなおすとCOMPO 80/BS発売と「PC-8001」の発表時期がかなり近かったのに驚きます。PC-8001はCOMPO 80/BSの1年後くらいに初めて見た記憶があります。

*3:CMU-800は「冨田勲」大先生が使われていたRoland MC-8 の子分にあたります。MIDIはまだなく、VCO/VCAの時代(ボルテージコントロール)だった。AMDECのVCインターフェースに接続するためにシンセサイザーの回路設計をして、オペアンプと抵抗をやまほど買い込んだ。(結局作らなかったけど、オペアンプはまだある)ちなみにAMDEC CMU-800はコンテストで入賞するほど入れ込んだ。(このCMU-800のソフトウェアを完全逆アセンブル解析&改造した人もいるらしい。。。)なお、MIDI規格はMC-8の情報の扱いを参考に作成されたので、CMU-800も同じコンセプトなのでMIDIには割とすんなりはいれた。CMU-800のインターフェースを改造してシリアルインターフェースチップとフォトカプラでMIDI出力できるようにも改造した。そのあとすぐMIDIインターフェース製品を買ったので、CMU-800/MIDIはほとんど使わなかったけど楽しかったなぁ

*4:このあとYAMAHAクラビノーバROLANDのデジタルシンセや録音機材など、すべてのおこずかい(社会人?になってから給料)が楽器につぎ込まれていくことになりました。楽器コレクタ癖はこのころからです。青春期の抑圧された欲望のパワーはすさまじく、今だ発散しきれていません。家には入力機器を含めてキーボード(鍵盤)が6台、ギターはアコースティックも含めて10本くらいあります。趣味とはそういうものです。(笑)

計算機音楽の自由研究(準備:その2.6)~サウンド学習実験用のデータベースを考える

実験用のデータについて

MNIST*1の楽器版のような機械学習用の(音の)データセットを探しているのですが、ボクの実験に適したモノは見当たりませんでした。MNISTの音版/楽器版データベースは誰もが作ろうと思うはずで、どこかに必ずあるはずですが、見つかりません。

f:id:np2LKoo:20170509020022p:plain


YouTube-8M Dataset でPIANOのカテゴリがありますが、基本ビデオなので、ボクの実験には荷が重すぎます。
そのうち、ビデオから音だけを抽出してデータベース化することもやってみたいですが、時間がかかりそうなので自前で取り繕ってみます。MIDIを使いサンンプラーや各種VSTから音を拾っていきます。(6月末まで反省キャンペーン中です。内容を順次リファインします。)

フォーマット再考

学習する音のサンプルとしては楽器音の立ち上がり300mSec(0.3秒)を考えました。それだけあれば、音の学習には十分だと判断しました。ところが、ジブンで聞く分にはちょっと短いなと思い改めました。従来の計画よりデータが倍になりますが、0.6秒程度にしてみます。
以下のスペックでデータを準備してみます。
・1音32768Sample(48KHzサンプルで約0.68秒)
・Bit数は16Bit(演算時は内部で32Bitにアップサンプリングするつもりなので、入力・出力としては16BitでOKと判断)
・ファイル形式はベタでwav形式にします。多数のこまかな音を扱うには無圧縮でwavでいいのでは?と考えました。(容量が大きいですが。。。)
0.68秒*10000個=>約1.9時間:625MBなので、ネットにアップするには大きいですが、実験するには許容範囲です。

MNISTのデータベースの種類とバリエーションは大変すぐれていると思っています。これにならい(倣う必要もないのですが)10種類の楽器の単音を1万件ずつ学習(Train)用データとし、テスト用の1万件データを含め、6.9GBくらいになる計算です。(ちなみにMNIST 数字データは非圧縮で学習用(Train)が約45MB,テスト用が7.7MBあわせて約53MB程度です。これに比べると大きすぎだとは思いますが、上記のとおりすすめてみて不都合なら調整しようと思います。)

いきなりフルで作成するのはきついので、とりあえずピアノの最低音から最高音までの88音を作って評価してみます。(他2~3種練習で作成してみます)

ひさびさのソース登場です

久しぶりにPythonをさわりましたが、Pythonを完全に忘れていることはさほど驚きませんでした。いつものようにいろいろなページを参考にして作成しました。いつもながらネットにサンプルソースをアップされる方々には感謝します。ジブンのページもかなり参考になりました。記録は大事だと改めて思いました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pyaudio
import wave
import time
import pygame.midi
import numpy as np
# ========================================================================
# PythonMidi音源を鳴らして、オーディオインターフェースから出た音を
# サウンド機械学習用のデータベースとして編集する練習プログラム
# ========================================================================
#
# リアルタイムのMIDI制御なのでPygameを使用します。
pygame.init()
pygame.midi.init()
pygame.fastevent.init()
event_get = pygame.fastevent.get
event_post = pygame.fastevent.post
input_id = pygame.midi.get_default_input_id()
print("input MIDI:%d" % input_id)
midi_in = pygame.midi.Input(input_id)
midi_out = pygame.midi.Output(4, latency = 0) #環境にあわせて調整します。

#各種パラメータを定義します。48KHzサンプリングで32768サンプルに編集します。
INPUT_DEVICE_NO = 2 #Audioの入力です。環境にあわせて調整します。ボクの環境では現在これです。
FORMAT = pyaudio.paInt16
CHANNELS = 1 #モノラルなので1チャンネルです
SAMPLING_RATE = 48000 #48KHzサンプリングにします。
CHUNK = 2 ** 11
RECORD_SECONDS = 1.0 #1秒間録音して、そこからデータを切り出します
TOTAL_SAMPLES = 32768 # 0.6826mSec at 48K sampling
OROGINAL_SAMPLES = 32000 #音の立ち上がりからの原音のサンプル幅
DECAY_SAMPLES = 752 #減衰するサンプル幅
NO_TONE_SAMPLES = TOTAL_SAMPLES - (OROGINAL_SAMPLES + DECAY_SAMPLES) #無音のサンプル幅
MIDI_NOTE_START = 21 #ピアノの一番低い音 A0
MIDI_NOTE_END = 108 #ピアノの一番高い音 C8
MIDI_VOLUME = 127
THRESHOLD_AMPLITUDE = 10 #多少ノイズがのることを想定し、音開始の振幅閾値を設定します。実際に録音した音を使う場合は環境雑音にあわせて調整します。
WAVE_OUTPUT_FILENAME = "piano01.wav" #このファイル名で出力します。
audio = pyaudio.PyAudio() #クラスのショートカットです
recFrames = [] #録音フレーム用 空リストに初期化します。
editFrames =[] #編集フレーム用
totalFrames = [] #最終出力フレーム

# オーディオ録音用のコールバック定義です
def callback(in_data, frame_count, time_info, status):
recFrames.append(in_data) #録音の本体部分です。
return (None, pyaudio.paContinue)

# frameをwavファイルに出力する定義です
def waveWrite(fileName, frames):
waveFile = wave.open(fileName, 'wb')
waveFile.setnchannels(CHANNELS)
waveFile.setsampwidth(audio.get_sample_size(FORMAT))
waveFile.setframerate(SAMPLING_RATE)
waveFile.writeframes(b''.join(frames)) # 配列をバイナリ列に変換します。
waveFile.close()

# フレームにサンプリング録音したデータを所定のフォーマットに編集する定義です
def getEditFrames(frames):
frames = b''.join(frames) #バイナリ列に変換します。
frames = np.frombuffer(frames, dtype="int16") # 16ビットデータの配列に編集します。
# 音のフレーム内で出始めを探します(振幅閾値を超えれば音が開始したと判断します)
for i in range(frames.size):
if (frames[i] < -THRESHOLD_AMPLITUDE or frames[i] > THRESHOLD_AMPLITUDE):
iStart = i
break
print("StartFrameNo=" + "{0:d}".format(iStart)); # 確認用にスタートのフレーム位置を出力します。
returnFrames = frames[iStart - 1:TOTAL_SAMPLES + iStart - 1] # 閾値超の1個前を音開始とし、所定のサンプル幅を切り出します。
returnFrames.flags.writeable = True #編集可能に設定を変えます。
# 減衰部分の設定(DECAY_SAMPLESの幅をかけて、振幅ゼロにまでリニアに減衰させます)
for i in range(DECAY_SAMPLES):
returnFrames[OROGINAL_SAMPLES + i] = returnFrames[OROGINAL_SAMPLES + i] * (DECAY_SAMPLES - i) / DECAY_SAMPLES
# 無音部分の設定(NO_TONE_SAMPLES幅を無音にします)
for i in range(NO_TONE_SAMPLES):
returnFrames[OROGINAL_SAMPLES + DECAY_SAMPLES + i] = 0
#最大振幅が符号付き16bitの最大値となるように振幅を調整(最大化)します。リミッタをかけないマキシマイザです。
nMax = max(max(returnFrames), -min(returnFrames)) #最大振幅を取得します。(プラス値)
for i in range(TOTAL_SAMPLES):
returnFrames[i] = returnFrames[i] * 32767.0 * 0.80 / nMax #全般に対して振幅調整します。ぎりぎりにすると再生ボリュームによっては振幅が飽和するので、お好みで0.80かけています。
return returnFrames

#オーディオをオープンしStreamオブジェクトを獲得します。「音のストリーム」はcallback定義につながります。
stream = audio.open(format=FORMAT, channels=CHANNELS,
rate=SAMPLING_RATE, input=True,
input_device_index=INPUT_DEVICE_NO,
frames_per_buffer=CHUNK,
stream_callback=callback)

print("recording...")
#MIDIを順次鳴らしてフレームを所定のフォーマットに編集します。
#所定のMIDIチャンネルで実音がでるように音源を立ち上げておきます。
#(VSTホスト+VSTi もしくはDTMソフト:排他的にオーディオインターフェースを取得するものは
# Pythonからの割り込みを受け付けないかもしれないので、そのときはVSTホストを使用します。)
for i in range(MIDI_NOTE_END - MIDI_NOTE_START + 1):
recFrames = []
time.sleep(0.5) #ASIOを使わなかったので、充分な時間をあけています。(タイムラグがあるため)
stream.start_stream() #ストリームオブジェクトをスタートさせます(録音開始)
midi_out.write([[[144, MIDI_NOTE_START + i, MIDI_VOLUME, 0], 0]]) #所定の音のMIDI出力
time.sleep(RECORD_SECONDS) #この時間が実際の録音時間に相当します。
midi_out.write([[[144, MIDI_NOTE_START + i, 0, 0], 0]]) #所定の音をOFFします。
time.sleep(1.0) #同じく次の音まで間をあけます。Decay音も(使いませんが)一旦録音します。
print("finished recording:" + "{0:03d}".format(MIDI_NOTE_START + i))
stream.stop_stream() #ストリームオブジェクトを終了します(録音終了)
fileName = "Check" + "{0:03d}".format(MIDI_NOTE_START + i) + ".wav" # for Check(1音ずつの確認用出力です)
time.sleep(0.5)
editFrames = getEditFrames(recFrames) #所定の形式にフレームを編集します。
waveWrite(fileName, editFrames) #編集後の1音を出力します。(確認用)
totalFrames.extend(editFrames) #学習用データはこれです。順番につなげていきます。

waveWrite(WAVE_OUTPUT_FILENAME, totalFrames) #学習用データをファイルに出力します。

#あと片付けはちゃんと行いましょう。
midi_in.close()
midi_out.close()
stream.close()
audio.terminate()

pygame.midi.quit()
pygame.quit()

ソースの貼り方はいまだによくわかりません。見にくくてすみません。((追記)インデントが分かりにかったので少し調整しました)練習用のプログラムで、クラスもなければ、main()もありません。あしからず。


(追記)AudioのポートとMIDIのPortや接続に関して分かりにくかったので追記します。確認は(その5)と(その6)に載っているプログラムで確認ください。ボクのマシンの環境例です。

Audioの一覧の出し方(Python プログラム)は(その5)参照。文字化けはまんま貼ってますがご愛敬ということで;。

0 Microsoft サウンド マッパー - Input
1 ƒ}ƒCƒN (US-2x2)
2 ƒ}ƒCƒN (Mixing Driver 2 for US-
3 MAIN (QUAD-CAPTURE)
4 3-4 (QUAD-CAPTURE)
5 1-2 (QUAD-CAPTURE)
6 ƒ}ƒCƒN (Mixing Driver 1 for US-
7 Microsoft サウンド マッパー - Output
8 スピーカー (Mixing Driver 1 for
9 スピーカー (US-2x2)
10 3-4 (QUAD-CAPTURE)
11 1-2 (QUAD-CAPTURE)
12 デジタル オーディオ (S/PDIF) (H
13 スピーカー (Mixing Driver 2 for
14 デジタル オーディオ (S/PDIF) (H

MIDIの一覧の出し方(Python プログラム)は(その6)参照

get_default_input_id:1
get_default_output_id:0
No:(interf, name, input, output, opened)
0:(b'MMSystem', b'Microsoft MIDI Mapper', 0, 1, 0)
1:(b'MMSystem', b'QUAD-CAPTURE', 1, 0, 0)
2:(b'MMSystem', b'US-2x2', 1, 0, 0)
3:(b'MMSystem', b'Microsoft GS Wavetable Synth', 0, 1, 0)
4:(b'MMSystem', b'QUAD-CAPTURE', 0, 1, 0)
5:(b'MMSystem', b'US-2x2', 0, 1, 0)

Process finished with exit code 0

です。US-2x2が増えたのでいっぱいでてきます。(TASCAMオーディオインターフェースUS-2x2はubuntuで使いたいので買いましたがまだ実験できていません)

(1)PythonプログラムでMIDI情報をMIDI OUT No.4のQUAD-CAPTUREへ出力

(2)QUAD-CAPTUREのMIDI-OUTをQUAD-CAPTUREのMIDI-IN実接続MIDIケーブル接続)している。:内部でソフトディスパッチできるサウンドカードは設定ででつなげられると思いますが、実接続がてっとり早いです。

(3)QUAD-CAPTUREのMIDI-INからVST HOST(またはDTMソフト)につないでいる(ソフトの入力設定)

(4)VSTから出た音は Audio No.2のMixing Driver 2 for US-2x2 から出力する。

(5)PythonプログラムでAudio No.2の音を録音する。

(追記ここまで)


pythonMIDIを制御して音をだして、録音して編集(所定の長さのサンプリング切り出し、減衰・無音区間設定&音量最大化)してファイル保存しています。

このプログラムで出力した音(練習版データベース)はこんな感じです。

すべて32768サンプルにしているので、読み込んで配列変換して範囲指定すれば、特定の音が簡単に取り出せます。

使用した音は以下です。

以前紹介した Keppy-Steinway-Piano *2をTX16WxというサウンドフォントプレーヤーのVSTiに読み込み、SAVIHost(Standalone VSTi Host)というホストプログラムに乗せて動作させています。(さらに追記)KeppyさんのBlack MIDIはたとえばこちら↓。

www.youtube.com(さらに追記ここまで)

(追記)間違ってVSTの音ではなく、Microsoft サウンド マッパーのWindowsの音を録音していました。Steinwayにしては音がしょぼいと思った方さすがです。ボクは間違いに気が付きませんでした。比較も兼ねて追加で貼ります。

音がしょぼいですが、ギターとバイオリンも貼ってみます。

(追記ここまで)

PythonからMIDIやSoundを扱う方法は去年の実験準備も参照ください。


ピアノはこれで88音ですが、がんばってピアノで1万音あつめます。114種類集めれば88音×114種類=10032音で一万超えにできます。幸い音源(ハード、ソフトとも)をもっている人が周りに多くいるのでなんとかなると思います。

最終的には10種類くらいの楽器でデータベースをつくってMNISTみたいなことをやるつもりです。

まとまらない

準備しなければならないことがまだまだあるのですが、のんびり進みます。

プロモーションに協力

プロモーションのかいあって(あるいは努力むなしく)先週から1アクセス増えました。めざせ!20ヒット!! ぜひご覧ください。

youtu.be

Koo'sキッチン 今回は飲み物です

「三ツ矢 新搾り ぶどう」「三ツ矢 新搾り もも」はどちらもたいへんおいしく頂いております。最近はおいしい飲み物がたくさんでていますが、個人的な好みとしてはさっぱりして、自然な果実の香りが生きており、着色していないことを好感して、「三ツ矢 新搾り」が見事殿堂入りを果たしました。おめでとうございます。

*1:去年はずっとMNISTやってました。興味のある方は去年の実験を参照ください。よく見る数字の学習です。

*2:Keppyさんは最近ディスクがクラッシュして落ち込んでいました。