もぐてっく

人は1つ歳をとるたび、1ビットづつ大きくなれると信じてた。

Alcohol52%のイメージファイルmdf + mdsをwav + cueに変換する

前回めでたく成功したbin + cue→wav + cue変換。

bin + cueをwav + cueに変換する - もぐてっく

でも、うちのライブラリはbin + cueじゃなくてAlcohol52%ネイティブのフォーマットであるmds + mdfファイルなのよね。

ひとつずつ再変換しても良いんだけど、超面倒だよね。うん。面倒だ。

まぁ、mdfとbinは完全互換なのは調査済みだから、後はmdsファイルを適当にパースしてcueファイル作れば良いんじゃね?

(mdsファイルをテキストエディットで開く)

バババ、バイナリィィィィィィィィィィィィィィ!?


バイナリファイルでした(泣)。


でも、こんなことではめげません。めげずに調べます。


mdsファイルのデータ構造はここにドキュメントがあるのですが、所々計算が間違っててどうも怪しいです。

http://developer.berlios.de/docman/display_doc.php?docid=840&group_id=2545

なので、大変参考にしつつ(ありがとうございます)hexdumpとにらめっこしながらそれっぽい構造体を作ってみました。


これでmdsファイルの中身が読めるようになりました。
でも、それだけではダメでした。ダメダメでした。

mdsのdata_blockに格納されてる値でcueファイルを作ると、全体的に2秒ずれてしまいました。単純に2秒引けば良いんですけど、なにかとてもきもち悪いから調べてみました。


なんでも、CD-DAは「きかくじょう」、トラック1の前に2秒(150フレーム)の無音区間(プリギャップ)が必要なのだそうです。

そして、その2秒の無音区間は無駄なので、mdfファイルからは削られてます。

でもでも、mdsは2秒のプリギャップを削る前のタイムスタンプを保持しているので、件のズレが発生します。

多分ヘッダのpregap_corrに入ってる-150が削ったプリギャップだと思うので、これを75で割って引き去るようにしました。


はい。これで出来ました。

moguno/mdsmdf2cuewav · GitHub

第一引数にmdsファイルを指定すると、同じフォルダにcueファイルとwavファイルが出来ます。


これで本気でWindowsが捨てられる準備が出来ましたv

bin + cueをwav + cueに変換する

約1ヶ月半にMacBook Airを買って、現時点での究極のGUIを堪能している今日この頃ですが、一つだけ不満が・・・。

MacってCD-DAのイメージファイルをマウント出来る仕組みが無いのよね。

Windows時代に物理的耐用年数(20年)を過ぎたCD達をイメージ化してたんだけど、これが死んでしまうのは非常に痛い。

LINDBERGのEXTRA FLIGHTとかFlight Recorderとかが聞けなくなったらと思うと、もうウルウルっす・・・。

ウルウルしながら調べたところ、XLDってフリーソフトを使えばwav + cueファイルを分割してiTunesに登録してくれることを発見。やった。

ああ、でも愛用のAlcohol52%はcueシート付きのイメージファイルが作れるけど、イメージ本体はbinファイルなのでアウト。

号泣しながらさらに調べたところ、binとwavの差はRIFFコンテナへの格納有無だけらしいので簡単に変換出来そう。

よし、bin + cueファイルをwav + cueファイルに変換してみよう!

やることの説明

CD-DAのイメージファイルであるところのbinファイルは、CD-DAの内容を44.1kHz、16bit、2チャンネル(ステレオ)のPCM形式で保存したものです。サウンドレコーダでいう「CDの音質」と言う奴です。
(CDなんだから当たり前ですが。てか最近の子ってサウンドレコーダ知ってんの?)


一方のwavファイルは、RIFFコンテナに格納されたサウンドデータです。
RIFFはチャンク構造を持っており、名前付きの可変長データを複数格納出来るようになっています。

[RIFFヘッダ][チャンクヘッダ1(チャンク名)(データサイズ)][データ1][チャックヘッダ2(チャンク名)(データサイズ)][データ2]・・・

最近の概念だと、キーバリューストアだと思ってもらったら良いです。

さて、RIFFについて何となく理解したところで、wavファイルを形成するためのチャンクを見ていきます。

最低限必要なチャンクは"fmt"と"data"の2つ。

先に書いたサンプリングレートその他の情報はイメージファイルからは推測が不能なため、"fmt"チャンクにフォーマットの情報を格納する必要があります。
そして、肝心のデータは"data"チャンクに格納します。

受け売り文献:wav ファイルフォーマット


と、言う訳でbinファイルをwavファイルに変換するには、以下の情報を納めたファイルを作れば良いことになります。

  1. RIFFヘッダ(ファイルサイズ以外は固定データ)
  2. fmtチャンクヘッダ(固定データ)
  3. フォーマット情報(固定データ)
  4. dataチャンクヘッダ(データサイズ以外は固定データ)
  5. binファイルの内容(可変データ)


割と簡単。

実装してみた

複雑なことはしなくても良いので、シェルスクリプトで変換することにしました。
ついでにcueシート内のイメージファイル名の変換機能も付けました。

#! /bin/sh

encode_int32()
{
	X01000000=`printf %d 0x01000000`
	X00010000=`printf %d 0x00010000`
	X00000100=`printf %d 0x00000100`

	printf '\\x%x' `expr $1 % $X00000100`
	printf '\\x%x' `expr $1 / $X00000100 % $X00000100`
	printf '\\x%x' `expr $1 / $X00010000 % $X00000100`
	printf '\\x%x' `expr $1 / $X01000000 % $X00000100`
}


bin2wav()
{
	WAVE_SIZE=`wc -c "$1" | cut -f2 -d " "`
	SIZE=`expr 48 + $WAVE_SIZE - 8`

	HEAD_1=RIFF
	HEAD_2=`encode_int32 $SIZE`
	HEAD_3='WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00\x44\xAC\x00\x00\x10\xB1\x02\x00\x04\x00\x10\x00data'
	HEAD_4=`encode_int32 $WAVE_SIZE`

	printf "$HEAD_1$HEAD_2$HEAD_3$HEAD_4" > "$2"
	cat "$1" >> "$2"
}


rewrite_cue()
{
	mv "$1" "$1"_bin2wav
	echo FILE \"$2\" WAVE > tmp
	tail +2 "$1"_bin2wav >> tmp
	mv tmp "$1"
}


cd `dirname "$1"`

CUE_EXT=`echo "$1" | sed -E 's/^.+(\.[^\.]+)$/\1/g'`
BASENAME=`basename "$1" $CUE_EXT`

if [ "$CUE_EXT" == ".cue" ];then
	BIN_EXT=".bin"
	WAV_EXT=".wav"
elif [ "$CUE_EXT" == ".CUE" ];then
	BIN_EXT=".BIN"
	WAV_EXT=".WAV"
else
	echo Invalid cue file > /dev/stderr
	exit 1
fi

bin2wav "$BASENAME$BIN_EXT" "$BASENAME$WAV_EXT"
rewrite_cue "$BASENAME$CUE_EXT" "$BASENAME$WAV_EXT"


第一引数にcueファイルのファイル名を指定してやれば、めでたくXLDで読めるwav + cueが完成します。

デスクトップの左端でタイトルバーが死んでる的なmikutterプラグイン

なんかMacのmikutterを下記のようにします。

f:id:moguno:20130217194548p:plain

# -*- coding: utf-8 -*-
# デスクトップの左端でタイトルバーが死んでる
# mikutterのタイトルバーを消して、画面の左端に配置します。 for Mountain Lion

Plugin.create(:hide_titlebar) do

  # タイトルバーを消す
  def hide_titlebar(window)
    window.decorated = false
    window.hide
    window.show
    window.move(0, 0)
    window.set_size_request(0, 5000)
  end


  # 起動時処理(for 0.1)
  onboot do |service|
    # メインウインドウを取得
    window_tmp = Plugin.filtering(:get_windows, [])

    if (window_tmp == nil) || (window_tmp[0][0] == nil) then
      next
    end

    window = window_tmp[0][0]

    hide_titlebar(window)
  end


  # 起動時処理(for 0.2)
  on_window_created do |i_window|
    # メインウインドウを取得
    window_tmp = Plugin.filtering(:gui_get_gtk_widget,i_window)

    if (window_tmp == nil) || (window_tmp[0] == nil) then
      next
    end

    window = window_tmp[0]

    hide_titlebar(window)
  end
end


まぁ、前作った「いつでもいっしょ」の超機能縮小版な感じです。

mikutterといつも一緒にいられるプラグインを作りました! - もぐてっく