Windowsで音声形式を切り替えるためのスクリプト

1つの光S/PDIF出力をサラウンドヘッドホンとスピーカーに送り、ヘッドホンを使うときには出力する音声形式をDTSに、スピーカーから流したいときにはPCMに手動で切り替えるという運用を考える。

f:id:chryfopp:20130327112803p:plain

P8Z77-Vでは、以下の操作手順により音声形式を「PCMからDTS」または「DTSからPCM(DVD品質)」へと切り替えることができる。サウンドコントロールパネルからも切り替えられるが、オーディオマネージャを使った方が操作手順は少ない。

  1. Realtek HD オーディオマネージャ」を開く
  2. 「デフォルトフォーマット」タブを開く
  3. 「dts interactive」ボタンを押す
  4. Realtek HD オーディオマネージャ」を閉じる

f:id:chryfopp:20130327121042j:plain

これをさらにスマートに、1ないし2クリックで実現する方法がないか探してみた。

どうやら一般的な方法は「サウンドコントロールパネルを開いて設定を変更する操作を行うスクリプトを使う」ようである。そしてJute Memoさんの記事は、すでに私の要求を9割がた満たしている状態である。私の要求との相違点は「サウンドコントロールパネルで変更したい項目」だけなので、そこだけ自分用に書き換えて使ってみることにする。

「指定したタイトルのウィンドウを持つアプリケーションをアクティブにできるまで繰り返す」関数を用意した上で、WshShellオブジェクトを作成してサウンドコントロールパネルを開いてアクティブにするところまでは同じ。

var wait = function(title){
    do {
        WScript.Sleep(100);
    } while(!WshShell.AppActivate(title));
};

var WshShell =  WScript.CreateObject("WScript.Shell");
WshShell.Run("control mmsys.cpl");
wait("サウンド")

f:id:chryfopp:20130327113324p:plain

自分の環境では光S/PDIF出力用のデバイス「Realtek Digital Output(Optical)」は再生デバイスの7個目にあるので、カーソルキーの↓を7回送ってRealtek Digital Output(Optical)を選択した状態にし、ALT+Pを送ってプロパティを開く。

WshShell.SendKeys("{DOWN 7}%P");

f:id:chryfopp:20130327113325p:plain

「CTRL+PAGE UP」を送り、「詳細」タブを開く。

WshShell.SendKeys("^{PGUP}");

f:id:chryfopp:20130327113326p:plain

音声形式をDTSに変更する場合、「D」を送って「DTS Interactive」を選択させる。

// WshShell.SendKeys("D"); // DTS Interactive

f:id:chryfopp:20130327113327p:plain

PCMに変更する場合は「D」「2」「2」の順に送り、何が選択されていたとしても「D(DTS Interactive)」「2(2 チャネル、16ビット、44100 Hz)」「2(2 チャネル、16ビット、48000 Hz)」と選択していって最終的に「DVDの音質」が選択されるようにする。

// WshShell.SendKeys("D22"); // 2ch, 16bit, 48000 Hz

以上2つはスクリプトの引数として扱い、スクリプトを呼び出すショートカットの側で選択できるようにする。

WshShell.SendKeys(WScript.Arguments.item(0));

以上で再生形式の選択が完了するので、あとは「ENTER」を2回送って「OK」「OK」でウィンドウを閉じる。

WshShell.SendKeys("~");
WshShell.SendKeys("~");

以上をまとめると以下のスクリプトとなる。

var wait = function(title){
    do {
        WScript.Sleep(100);
    } while(!WshShell.AppActivate(title));
};

var WshShell =  WScript.CreateObject("WScript.Shell");
WshShell.Run("control mmsys.cpl");
wait("サウンド")
WshShell.SendKeys("{DOWN 7}%P");
WshShell.SendKeys("^{PGUP}");
// WshShell.SendKeys("D"); // DTS Interactive
// WshShell.SendKeys("D22"); // 2ch, 16bit, 48000 Hz
WshShell.SendKeys(WScript.Arguments.item(0));
WshShell.SendKeys("~");
WshShell.SendKeys("~");

これを「changePlaybackFormat.js」と名付けて保存し、これを引数「D」を付けて呼び出す「DTS Interactive」ショートカットと、引数「D22」を付けて呼び出す「2 チャネル、16ビット、48000 Hz」ショートカットを作成する。

f:id:chryfopp:20130327123419p:plain

ショートカットに判りやすいアイコンを付けてクイック起動に登録して完了。

f:id:chryfopp:20130327121043p:plain

これで1クリックで音声形式を切り替えられるようになった。

ソースコードは最も詳細な設計書である

そもそも設計書とは何か。私は設計書(specification)とは「要求仕様(requirements specification)の実現手順を記したものであり、実際に実現を行う者に対する要求」と理解している。関数実装に当てはめると、「ソースコードとは関数要求仕様の実現手順を記したものであり、実際に実現を行うコンパイラに対する要求」となる。そして開発用の、移行要求(機能追加や仕様変更など)を受け付ける可能性のある設計書は、設計の部品(コンポーネント)それぞれがどの要求を実現するために、どのような意図をもって実装されるのかということを把握できるようにするために、要求のトレーサビリティを持たなければならない。

class Foo
{
private:
    Foo(const Foo&);
    Foo& operator=(const Foo&);

C++プログラマC++コンパイラはこのクラス定義を見れば即座に「クラスFooはコピー不可である」と認識する。つまりコピーコンストラクタとコピー代入演算子を非公開メンバとするという定義文は「コピー不可」というクラス仕様を記述していることと相違ない。

然るに、このクラスがコピー不可と定義されていることについてこれで記述が十分かと言うとそんなことはない。これだけではクラスFooがなぜコピー不可なのかが判らない。つまり要求のトレーサビリティがない。そのため、たとえば移行要求によってこのクラスを「コピー可」に変更する必要性が生じたとき、その妥当性を的確に判断できない。コピー不可としている理由については、「コピー可にしなければならないという要求がない限り、クラスはコピー不可として定義する」という規則があってそれに則っているだけというのがもっとも有力だが、それを裏付ける情報はここにはない。

bool greater_than_5(unsigned short n)
{
    return (n - 5) > 0;
}

上記のC++関数定義は、intを4バイトとして扱う処理系では「n1が5より大きいならばtrueを返却」し、intを2バイトとして扱う処理系では「n1が0以外ならばtrueを返却」する。そしてこの挙動が、この関数の仕様ということになる。

とはいえ、普通に考えれば、この関数定義は単に「intが4バイト以上という処理系依存」なのであり、関数に対する要求仕様は「n1が5より大きいならばtrueを返却する」と考えるのが正しいように思われる。この認識が正しいかは、この関数に対する要求仕様書を見れば確認できるだろう。しかし、なぜ

return n > 5;

このように定義しないのかという理由は実装都合なので関数要求仕様書からは理解することはできない。可能性としては「特定の処理系では0以外との比較ができないという問題があり、それを回避しなければならない」「主要な処理系では5と比較するよりも0と比較した方がオブジェクトコードが最適化されることが確認されたため、人間にとっての解りやすさよりも計算機にとっての解りやすさを優先した」などのような、ソースコードの実現を担当するコンパイラの制限によるものから、「生産性のない関数でつまらないから少しでも小洒落た書き方をしてみようと思った」などといったコーダーの気まぐれまで考えられるが、確実に「これだ」と断言できる理由はない。この理由を即座にトレースできる状態になっていないとしたら、実装意図、すなわちこの実装を選択させた要求や制限が不明瞭ということで、これもまた一つの要求トレーサビリティが欠如した状態と言える。

ソースコードは最も詳細で正確な設計書である。ソースコードとそれを結合するための情報(言語規格、処理系、makefileなど)さえあれば、成果物となるソフトウェアの仕様まで書き上げることは可能である。しかしその「仕様」が正しいかどうか、すなわち本来の「要求」に適合しているか、またどのような「制限」があるかは正確には判らない。逆に言うと、要求や制限を正しく素早くトレースできるソースコードがあるならば、その言語を理解している人にとっては「完璧な設計書であり仕様書」と言うものになる。

開発工程イメージ:関数実装

要求仕様〜の流れを「関数実装」の工程に当てはめてみる。

f:id:chryfopp:20130211095523p:plain

関数実装の工程では『実装・構築』(ビルド)に掛かるコストが他の設計工程よりずっと低いはずなので、それに合わせて一部の流れを変更する。

ブラックボックス試験をユニットテストとしてコード化でき、ブラックボックス試験の実行にさほどコストが掛からない(ソースコードレビューより掛からない)のであれば、ソースコードが書き上がると同時にブラックボックス試験を通す流れにすると効率が良い。ソースコードを見て「これなら要求を満たせるであろう」などと言うよりも、実際に要求を満たせていることを明示した方が話が早い。

条件網羅のホワイトボックス試験は、関数が持つすべての条件分岐の組み合わせ、言うなれば「関数内の結合パターン」を試験するものである。『JIS X 0020:1992 情報処理用語(システム開発)』では結合試験(integration test)を「プログラムまたはモジュールが、全体システムにおいて正しく機能することを確認するために、それらを段階的に結合し、試験すること」と規定している。この「全体システム」を「関数単体」に置き換え可能とするならば、ホワイトボックス試験は関数実装における結合試験のようなものと考えられる。

開発工程イメージ:要求仕様から成果物まで

f:id:chryfopp:20130204233155p:plain

例えばインプットが「システム要求仕様」ならば、『設計』では「システム方式設計」を行う。
システム方式設計のアウトプットとなる『設計書』には、システムを構成するソフトウェアを構築するための「ソフトウェア要求仕様」が含まれていたりする。
そうすると今度は「ソフトウェア要求仕様」をインプットとする同じ流れが『実装・構築』の中に発生し、「ソフトウェア方式設計」という『設計』を経てさらに細かい要求仕様——クラスであったり、ライブラリであったり——が作られ、次の『実装・構築』の中にはまたそれをインプットとする同じ流れが発生する。

このようにしていくつもの要求仕様と設計が層を成しながら開発は進行する。