雑記帳スマートフォン用の表示で見るのんびり雑記帳2017-11-18VBScript・VBAでWAVのサンプリングレートなどを取得【備忘録】Program以前エクセルにてWAVのサンプリングレートなどの情報を取得するスクリプトを作ったのですが、ファイルによって想定外の動作があったり、エクセルではなくてVBSファイルのドラッグにて動作させたいなと思い、作り直すことにしました。以前作った時はとりあえず動けば良いやと思ってたので、参考スクリプトを殆どコピーしてあまり意味は分かっていなかったのですが、今回は色々調べたり検証したりして勉強になりました。完全には理解出来ていないですね。多少は理解できたのかなといった感じです。バイナリとかそういうのは難しいですね。スクリプトのベース一番参考になって、本記事のベースにもなってるのは、Yahoo!知恵袋のサンプルです。https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1372320783ただこのサンプル、自分が理解出来てないだけの可能性もありますが、結構間違えている部分があると思います。これは動作には問題ないけど、サンプリングレートの変数名がslateとなってたり。これも含め、多分かなりざっくりと参考になればとささっと書いたのだと思われます。自分はこの分野は全然詳しくないので、そもそもが間違ってるものを正解だと思って、何でこの記述になるのだろう...と所々疑問が解けず随分悩みました。構造体で取得のパターンhttp://www.gizcollabo.jp/vbtomo/log/archive/vbqanda_28025_0.htmlバイナリから取得するパターンとしては上記もありましたが、これでは取得出来たり出来なかったりしました。はじめ自分の用意したWAVで試して出来なかったので、スクリプトの投稿日が古く環境的なものかと思いましたが、スクリプトに書いてあるWAVのパスそのままで取得できました。パスをきちんと見てなく適当なものが仮で書いてあるかと思ってましたが、Windowsに初めから入ってる音源なんですね。詳細は後程書きますが、取得できないのは恐らくチャンクの位置が後方にズレている音源が原因かと思われます。バイナリエディタでそのようになってる音源で試すと取得できず、バイナリの文字列がWAVEfmtとWAVEにfmtが続く音源は取得出来ます。本記事のスクリプトのように取得位置を調整出来たりすれば良いのですが、このサンプルではどうやっていいか分からず。因みにデバッグしてみると、取得できないのはHdrFormat(WaveFormat)だけでした。構造体自体は知っていたけど、自分の知識が浅いことからこのサンプルは興味深く思いました。構造体の各要素は、例えばリストなどからある人の名前、年齢、性別などの各値を構造体の各要素に個別に代入して成すものだと思っていました。このサンプルは構造体の変数本体をGetの引数に渡していて、結果自身の各要素に正しい値が入っています。なっているのか全く分かりません。チャンク自体が構造体という表現を目にしたので、構造体の変数を投げれば自動取得出来るのかも。あと、定数で定義されてる数字をバイナリエディタで探してみましたが、はじめ何か一致しないなと思ったけど順番が逆になっていることに気付きました。これも後に詳しく書きますが、もしかしたらビットシフトとかも関係してるのかも。一纏めにして扱う場合後ろにあるバイト程大きな桁になるため、一つの数字としてまとめて書くと逆になるのかも。追記やはりそうですね。65536とやると88200が出ることを理解しました。(カッコは10進数、カッコ内の方を掛けます。後述のハイレートになると3バイト目を使う理由も上記で理解しました。(例えば44.1kHzは16進数でAC44だけど、5桁目を使っていません。64kHzまでは4桁で、88.2kHzから5桁になり、つまり3バイト目を使用します。今回最初に作ったVBScriptのみのバージョンです。FunctionReadBinary関数バイナリを読み込みます。VBScriptではVBAで使えるOpenステートメントが使えないため、ADODB.Streamオブジェクトを使用しました。これには難があって、結局VBA版を作って本処理はこちらですることになります...。ReadBinary関数にLimitと設けたように、読み込み容量を制限することが出来ます。ヘッダの一部が読み込めれば良いので、全ては要らないのです。ADODB.Streamオブジェクトでは一旦自身に全て読み込む必要があるようで、このため大変処理が遅いです。数秒のサンプルファイルなどならまだ良いのですが、普通の音楽ファイルを複数となると、現実的な使用には向いていない遅さでした。改善しようと読み込んで配列に格納していく際にLimitで制限した数だけ格納する仕様にしました。これだけでも速くなりました。Openステートメントは予め配列の容量を確保しておき、その分しか取得しないので、高速です。ファイルを全て読み込んでも、Openステートメントの方が速かったです。本当はLimitは任意で、指定されていたらその値にし、指定されてなかったら全て読み込む仕様にしたかったのですが、VBScriptではOptionalが使えないので、0に指定されてたら全て読み込む仕様にしました。GetChunkPos関数チャンクの位置を取得します。先程チャンクの位置がズレている場合があると書きましたが、その調整で必要になります。チャンクを基準としてその地点から各データの位置は一定なので、チャンクの位置を取得してから各データの位置を加算してデータを取得します。最初はGetFMTPos関数という形でfmtチャンクの位置を取得するための関数として作りましたが、後日ブログに書く予定のAIFFでも同様にCOMMチャンクの位置を取得する必要があるので、引数で指定して任意のチャンクの位置を返すような仕様にしました。引数には一文字ずつ文字を表す数字を配列に格納し、渡します。」を16進数の数字で渡しています。因みにfmtチャンクの位置ですが、前述のWAVEfmtとなる場合は最小の12です。Cubaseで書き出されたファイルの場合、48であることが多いです。互換のファイル形式を使用しない」をチェックすると12になります。Toolsの722です。Toolsに合わせています。IF文でチャンクの位置が取得できなかったらメッセージボックスを表示させる仕様にしてますが、WAVデータは必ずfmtチャンクが必要になりますので、通常はLimitで指定した数字以上の位置にfmtチャンクがある場合に表示されます。その場合Limitを適当に大きくしたり全て読みこんだりして、ckPosをデバッグしてみたりバイナリエディタで見てみて位置を確認してLimitを再調整すると良いと思います。IsWave関数WAVデータかどうか判定し、WAVならTrueを返します。初めの4文字の「RIFF」とその少し後ろにある「WAVE」の文字で判定しています。GetWaveSamplingRate関数サンプリングレートを取得します。知恵袋にあるサンプルでは2個目(3バイト目)と3個目(4バイト目)のシフトでそれぞれ&H1000と&H10000となっていたので、何故こうなるのか嵌まりました。冒頭で想定外の動作と書きましたが、これが原因でした。想定外の不具合というのは、88.2kHzや96kHzなどのハイレートの際値がおかしくなってしまうものでした。Ryzenになってから常にハイレートでの制作作業になったのもあってか、たまたまこれに気付きました。分かっていなかったのですが、1バイト目から4バイト目をそれぞれデバッグして値を見てみると、44.1kHzなどでは使っていなかった3バイト目をハイレートでは使っていることが分かりました。そのため&H1000を掛けるのが間違いだと分かるきっかけになりました。&H1000ではなく&H10000を掛けると正常な値になります。ビットシフトの根本的なところはやはり分かっていないのですが、&H100は10進数で256、1バイトは256通りの表現が可能なので、1バイト分シフトしているというのが分かりました。2で512、&H200じゃないのかと思ったりもしたのですが、これは間違いでした。1バイトは8ビットで、8ビットは2を8乗した数字(256)を扱えます。2で16ビットなので、2を16乗すると65,536になります。&H10000になり、また&H1000は間違いだと分かりました。16ビットと65,536という数字は音楽でもよく目にする数字で、今改めて思えば2バイトが512というのはおかしいですね。サンプリングレートではありませんが、以下のサイトでも同様の記述をしており、またビットシフトを行っていることも分かりました。それぞれRGBA値とWAVデータの長さで、4バイトです。8)」といった記述でも同じなんですね。http://blog.livedoor.jp/spqm8sc9/archives/2549766.htmlhttp://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=18644とりあえず複数バイトを一つのデータとして扱う場合には必要になるものと覚えました。本記事のスクリプトとは関係なく、少し脱線するのですが、ビットシフトについて調べてみるとビット演算やビット演算子という用語をよく目にしました。これも完全には理解出来てませんが、今まで疑問だったことが解決しました。昔VBScriptでウィンドウを透過するスクリプトを書きました。これに以下のような処理がありました。dwStyle)このスクリプトの透過する処理については、ネットによくある記述をそのまま使っていたため、このビット演算についての部分はよく分からず使ってました。上記2行目がビット演算です。ビット演算ではあるそれぞれの数字の2進数の各桁を照らし合わせ、0にしたり1にしたりします。そのためフラグとして利用されるようです。IF文と同じような感じで、ANDは両方が1なら1、ORは片方が0でももう片方が1なら1となります。0101と0110で演算子がANDだったら、1桁目が1と0で「0」、2桁目が0と1で「0」、3桁目が1と1で「1」、4桁目が0と0で「0」となり、「0100」が演算結果になります。ORの場合は、1桁目が1と0で「1」、2桁目が0と1で「1」、3桁目が1と1で「1」、4桁目が0と0で「0」となり、「0111」が演算結果になります。上記のようにORは0を1にする働きがあるため、フラグを立てるために利用されるようです。上記スクリプトの場合、GetWindowLongであるウィンドウの現在の拡張ウィンドウスタイル情報を取得し、その情報にレイヤードウィンドウのフラグが立っていなかったらフラグを立てて、SetWindowLongでレイヤードウィンドウが適用された拡張ウィンドウスタイルを指定している、ということが分かりました。GetWaveBitRate関数ビットレートを取得します。ビットレートは2バイト分あるのですが、実質1バイトしか使っていないようなので、上記スクリプトでは知恵袋のサンプルのチャンネルと同様に1バイトから取得することにしました。最初の内や今回の作業前はサンプルと同様に2バイト分処理する形で書いていたのですが、これも間違いが含まれていました。最後にサンプリングレートを掛けているのですが、これの意味がどうしても分かりませんでした。これは間違いなのですが、それでも正常動作するのは、2バイト目は使っておらず0が入っており、掛けても値が0になるためです。1以上の値が入っている場合、とんでもないビットレート数になりますよね。ビットシフトで256掛けたあとに更に例えば44100とかを掛ける訳ですので。Rate)とビットレートがごちゃごちゃになっているのだと思います。Rateを思わせます。処理しているバイト数は2バイトです。位置はビットレートの位置なので、チャンネル数やブロックサイズは掛けておらず、ビットレートにサンプリングレートを掛けています。GetWaveChannel関数ステレオかモノラルかを文字列で返します。GetWaveBitRate同様、チャンネルも2バイト用意されているものの実質1バイトしか使用されておらず、サンプルもそうなっていたので1バイトで判定し、2が入っていたらステレオ、それ以外はモノラル、と処理しています。してもドラッグして取得するようにしたかったので、WAVデータをVBSにドラッグして、各ファイルのパスをエクセルに投げる仕様にしました。VBScript版ではコマンドプロンプトに結果を表示していましたが、エクセルを使う訳ですのでエクセルの表に書き出すようにしました。前項のGetChunkPos以降の関数は同じなため、省略です。今回はVBScriptからの移植で、面倒なので型定義などは基本的にしませんでした。"ボタンを押してエクセルを終了します。FunctionエクセルファイルとVBSファイルを同じ名前で、同じフォルダに置いて使用します。エクセルの2行目から結果を表示するので、1行目は予め項目を作っておきます。VBScriptからエクセル操作VBScriptからエクセルを操作出来るのは知ってて、昔ちょろっとやったことがあったけど、個人的に使い所があんまりなく使ってませんでした。今回ちょっと驚いたのは、VBScriptからエクセルのマクロを指定して実行出来ることです。引数も指定出来るんですね。そんなことは出来ないだろうと思ってたので、当初はVBScriptで各ファイルのパスをテキストファイルに書き出し、エクセルを起動し、エクセルはWorkbook_Openで起動とともに実行してからテキストを削除するような仕様を考えてました。セキュリティ的には良くないんだろうけど、便利ですね。エクセル操作の流れとしては、次のようになります。このスクリプトでは多くの情報が見れるようにウィンドウを最大化をしたいものの、それが保存されるのは嫌だったので、起動してから現在のウィンドウの状態を変数に保存しておいてから最大化します。ファイルを開いてマクロを実行し、メッセージボックスで待機します。F4などで終了することも考えましたが、保存しますか?のダイアログが表示されるのが嫌だなと思ったので、終了もVBScriptに委ねることにしました。F4で終了したら、ウィンドウも最大化のままですしね。最後にウィンドウを元の状態に戻してからダイアログを表示させずにファイルを保存せず終了します。VBA、IE制御ちょくちょく小ネタを掲載していきたいと思います。過去のエントリーで、指定文字...2010-12-09VBScriptでパスからフォルダ構造を作成パスからフォルダ構造を作成する簡易プログラムを作ってみた。月別アーカイブはてなブログをはじめよう!Shinezさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。