CompressWaveLibrary Ver1.8

 

概要

CompressWaveLibraryをDownloadして頂き有り難うございます。
このライブラリは、BGMをフェードイン・フェードアウト・クロスフェード・多重演奏等を行うために作ったクラスです。ストリーミング再生することを前提に作られていますが、用意するところまで出来れば比較的簡単な作業で扱えると思います。

このCompressWaveLibで扱うフォーマットは普通のWAVEではありません。独自の不可逆音声圧縮形式を使うことになります。MP3より圧縮率が悪く、聞ける範囲内で4〜5分の1程度にしか圧縮できませんが、まるでPSの音楽形式のように曲の無限ループや指定区間内の任意のループ演奏が可能です。例えば、前奏・間奏x9・終わりというように間奏をループさせて曲の容量を減らしたり、途切れることなく鳴らし続ける事が可能です。STG・AVGなんかの場合は特に有効かもしれません。

QD使用者には、TDDSDWaveDataに展開するヘルパー関数もオマケで付けておきましたので、DirectSoundeで鳴らす効果音の容量削減にも役立てるかと思います(w

必要環境

Delphi6(それ以下のVersionでも動くと思います)
project Quadruple D
注意して欲しいのがDDSD.PASのVersionです。最新のをお使い下さい。でないと、Windows2000においてSoundBlasterにてストリーミングが正しく行われません。

一応、コンパイルにはQDが必要ですが、Win32ストリーミング環境を考えて作ってありますので、無くても警告を出す部分をコメントアウトすればDelphi6単独でも動くでしょう。多少面倒かとおもいますけどね(^^;

Ver1.5→Ver1.6以上に伴う注意事項

長い間潜伏していましたが、その間にVersion1.6になってから随分とメソッドなどの構成に変化がございます。それ以前のVersionからの以降の際に躓く点をここに上げておきます。

・SetFeedの廃止。
フェードを設定するSetFeedが無くなりました。代わりにSetVolumeにフェードを設定することになりました。

・SetLoopの廃止。
ループ設定をPlayメソッドに戻しました。

・GetLoop/GetPlayの廃止
ループの有無・再生状態の有無を取得するメソッドが無くなり、代わりにGetPlayStateを用いて取得するようになりました。

・GetReflectTimeの追加。
ストリーミング再生において一番の問題は、実際に音に反映されるまでの遅延です。同期を取る場合など、反映されるまでの誤差分の時間がわかれば、処理自体もその分遅延させて同期を取ることが可能です。その場合にGetReflectTimeを使って誤差分の時間を取得出来ます。

・CWav_LoadWave/CWav_LoadWave3Dの返り値の変更
それぞれの返り値が、クラスを返すように変更。CWav_LoadWaveならTDDSDWaveData、CWav_LoadWav3DならTDDSDWave3D型のクラスを生成しアドレスを返します。実際の例はこちらを参照。
ファイルが存在しなかった場合はnilを返します。

VersionUp情報

ver1.8
解放時と同時に再生タイミングがくると一般保護エラー吐くと思われるバグ対策を施した。

ver1.7
フェードアウト後、Stop/SetVolumeで音量を上げないとPlayで再生されなかった点を修正。
指定区域間のループ設定において、正しくループされない点を修正(ぉぃ

ver1.6
SetFeedを廃止。
SetVolumeにフェードする時間を設定する引数を追加。
SetLoopを廃止。
GetPlayを廃止。
GetLoopを廃止。
Playにループ設定を引数として追加(というか戻した
GetPlayStateを追加。再生・ループ状態はコレで取得。
SetVolumeでは自動的に再生されないようにした。Playで再生させてね。

GetFadeで取得できる時間を、残りフェード秒とした。
GetReflectTimeで音が実際に反映されるまでの時間が取得できます。
CWav_LoadWave/CWav_LoadWave3Dの返り値がWaveDataを返すように変更。
PlayAll/StopAll/PauseAllを廃止

Ver1.5
TOOLのバグ修正。
わけあって、Playから設定をループを切り離し、SetLoopメソッドを追加。
22.05kHzの圧縮時のアルゴリズムを変更(以前凝った事して、逆にノイズが増えてたみたい)
暗号化機能を追加。

Ver1.4
え〜っと何したっけ?(マテ
TOOLで色んなフォーマットのWAVEも読めるようになったような・・・

Ver1.3
バグ抜き。
圧縮アルゴリズムにおいて、オーバーフローする恐れがあったのを回避。
関数名の変更(だって・・・・長くて、意味も変だったし・・・)

Ver1.2
フェードの仕組みや圧縮アルゴリズムの大改造(w
曲タイトル・作曲者が入力できるようになったり(w

Ver1.0
いぱ〜ん公開。暫定。

著作権・サポート

わたくし、Ko-Taにあります。が、堅いことは言いません。改造なり流用するなり何なりとしてくださってOKです。
礼儀として、流用・参考にした場合は、ちょこっとReadme(オレヲヨメ)に書いて頂けると私としても助かります(w
サポートは・・・色々と過密なので手に付かないと思いますが、バグ報告なんかあったら掲示板なりメールでオネガイします。
営利目的に使用する場合も特に制限は設けませんが、事前にMailを頂ければ幸いです。出来る限りサポートも致します。

http://ko-ta.u-hip2.com/

クラス概要

こんな感じです。

・TCompressWaveData
音楽CDと思ってクダサイ。音楽データの読み込みを行うだけのクラスです。単独でも再生機能を有しますが使わない方向で(w;ぶっちゃけた話、CreateとLoadFromFileとFreeしか使わないと言っても過言ではありません(うわ

・TCompressWavePlayer
再生の一括管理を行います。ようするに、コンポですね(w)。音量調節からフェードの設定とか、もうそんな感じ♪TMediaPlayerと似たようなメソッドを持っています。

主な使い方

コンポーネントではありませんから、ライブラリパスの通った場所(制作中のアプリケーションのフォルダ内、Delphi\Libなどがそれ)にCompressWaveLib.pas並びにHuffLib.pasをコピーしてお使い下さい。

もし、TCompressBmpLibをお使いの方はHuffLib.pasを上書きすることになりますが、構わず続行してクダサイ(笑

クラスってな〜にな方は参考書でも買って読んでください。

(おまじない)
このライブラリでは音声出力サンプリングレートは44100Hz/16Bit/2chのみなので、あらかじめストリーミングバッファ・出力先デバイスをこのサンプリングレートに合わせる必要があります。
DDSDを使う場合は、
DDSD1.SetPrimaryBufferFotmat(44100, 16, True);
でDirectSoundの出力サンプリングレートを調整してやりましょう。

 

(TDDSDWaveData/TDDSDWave3DにWAVEとして展開する)
今まで*.wavファイルで保存していた効果音・声などを*.cwavに置き換える場合、圧縮したファイルをTDDSDWaveDataなどに展開させる方法です。至ってカタ〜ン(ぉ

WaveBuf1 : TDDSDWaveData;
・・・
WaveBuf1 := CWav_LoadWav(WaveBuf1,"hoge.cwav",DDSD1);
・・・
WaveBuf1.Free

 

(ストリーミング再生してみるスレ)
このライブラリは圧縮が目的ではなく、いわば↑の機能はオマケみたいなもんです(笑)
真価はストリーミングに流してやって初めて現れてきます。
まず、TForm1.OnCreateあたりに初期化&ストリーミングバッファを確保します。
ストリーミングバッファを生成、ならびに再生させておきましょう。ここでは1秒のバッファ(44100x2x2Byte)を生成しておきます。これぐらいにしておくとSoundCardによって鳴らないというハード的な問題もかなり減少するみたいです。
赤い部分は関数ポインタで、ストリーミングが進むにつれ、一定間隔で呼ばれる関数をしていしてやります。後でUpadateWavという関数を作るので、一応この通り設定して置いてクダサイ。

//WAVE出力を44100/16/STEREOに設定
DDSD1.SetPrimaryBufferFotmat(44100, 16, True);
//ストリーミング再生用に作る
wav := TDDSDWaveData.CreateStream(DDSD1, 44100, 16, TRUE, 44100*4);
wav.OnUpdate := UpdateWav;
DDSD1[0].WaveData := wav;
DDSD1[0].LoopPlay;

再生クラスと曲データクラスを生成、ついでにファイルも読み込ませておきましょう(w)設定する音声展開用バッファの長さはストリーミングバッファの半分を指定しましょう〜。もし、暗号化を圧縮の際に行っていた場合は赤い部分を追加して、数値を設定してやる必要があります。

//再生クラスの生成
MusicPlayer := TCompressWavePlayer.Create;
//ストリーミング再生するWAVEバッファの容量の半分を指定
MusicPlayer.SetBufferSize(44100*4 div 2);
//圧縮ファイルデータのクラスを生成
pwav1 := TCompressWaveData.Create;
pwav1.SetCipherCode($12345678);
pwav1.LoadFromFile('Wipeout3_12.cwav');

このままでは鳴りません。まだドダイが空を飛んだだけで、上にグフが居ないと話になりません(謎)
再生クラスに曲データクラスを渡してやります。CD-PlayerにCD-DAを入れる作業です。

//チャンネル0に曲データを設定
MusicPlayer.Load(0,pwav1);
MusicPlayer.Play(0,TRUE);

これでもまだ鳴りません(笑)先ほど仮設定して置いたUpDateWav関数を作ってやる必要があります。ストリーミングバッファの長さは先ほど設定したとおり1秒と少ないものです。これで長い曲を演奏させるにはどうさせるのか?実際にはバッファを前後二つにわけて、0.5秒おきに前・後・前・後・・・の順で随時バッファの内容を書き換えることで実現しています。まだわからんという方は、ストリーミングについてWeb上で検索して、ちょっと調べておいてクダサイ。そこまでは説明しませんので(^^;以下の関数を追加します〜。引数に注意してクダサイね(w

procedure TForm1.UpdateWav(Sender:TDDSDGenWave; Player:TDDSDChannel; ofs,len:Cardinal);
begin
・・・・
end;

つぎに「・・・」となっている部分に、バッファを書き換えるコードを入れ込みます。
Renderingを呼び出して、圧縮情報から必要な分だけ(ここでは0.5秒分)の生Wave波形を生成します。その情報はBufferの示すポインタに格納されます。
後は、ストリーミング再生バッファであるwavにBlockCopyコマンドで、Bufferの内容を転送させます。lenにはストリーミングバッファの半分の値である0.5秒分の長さ、44100x2x2/2Byteが入っています。

//MusicPlayerにレンダリングさせる
MusicPlayer.Rendering;
//レンダリング結果をストリーミングに転送
wav.BlockCopy(ofs,Musicplayer.Buffer,len);

たぶん、これで鳴ると思われ〜(^^;
詳しい機能、フェードイン・アウトなどは付属のサンプルプログラムを参照してクダサイ。
終了時(OnDestory)に、解放処理も忘れずに。
解放する順序ですが、ストリーミングバッファの再生を停止させさえすれば、後は順不同に解放させていって構いません。

DDSD1[0].Stop;
MusicPlayer.Free;
wav.Free;
pwav1.Free;

メソッド説明

必要なものだけ、はしょって説明しましょう〜。全部はツカレル(^^;

(TCompressWavePlayer)

constructor Create;
procedure Free;
procedure SetBufferSize(len : DWORD);
procedure Load(ch : Integer; Music : TCompressWaveData);
procedure SetVolume(ch : Integer; vol : single; fade:single);
procedure Stop(ch : Integer);
procedure Previous(ch : Integer);
procedure pause(ch : Integer);
procedure Play(ch : Integer; loop:Boolean);
procedure Swap(ch1,ch2 : Integer);
procedure Init;
procedure Rendering;
procedure SetMasterVolume(vol : single; fade : single);
function Buffer : Pointer;
function GetVolume(ch : Integer) : single;
function GetPlayState(ch : Integer) : TCWPlayState;
function GetFade(ch : Integer) : single;
function GetPlayTime(ch : Integer) : Single;
function GetTotalTime(ch : Integer) : Single;
function GetTitle(ch : Integer) : string;
function GetArtist(ch : Integer) : String;
function GetMasterFade: single;
function GetMasterVolume: single;
function GetReflectTime : Integer;

TCompressWaveData

constructor Create;
procedure Free;
procedure SetCipherCode(code : DWORD);
procedure LoadFromStream(ss : TmemoryStream);
procedure LoadFromFile(filename : string);
procedure UnPressToDDSD(QS : TDDSD; buf : TDDSDWaveData);
etc

ヘルパー関数

function CWav_LoadWave(filename:String; QS:TDDSD) : TDDSDWavedata;
function CWav_LoadWave3D(filename:String; QS:TDDSD) : TDDSDWave3D;

constructor Create;

クラス生成。全てのはじまり・・・・
Createと同時に、
DDSD1.SetPrimaryBufferFotmat(44100, 16, True);

を実行してDirectSoundのWaveFormatを44100Hz/16Bit/STEREOに設定しておくことをオススメ致します。
また、
SetBufferSize(44100);
でレンダリングするバッファの長さも設定しておきましょう〜。

procedure Free;

解放です。

procedure SetBufferSize(len : DWORD);

一度にレンダリングWaveの長さを設定します。ストリーミングバッファの長さの半分を指定します(大抵)。
なお、出来る限り8の倍数になる方が好ましいのでヨロシク(w)単位はBYTE。
一秒間=176400BYTEになります。
長さなんですが、短過ぎると再生できないSoundCardもあるので、500msec(88200)ぐらいが理想のようです。大きくし過ぎると、逆に展開時のCPU負荷が集中するのと、反応が遅くなるので注意してクダサイ。

procedure Load(ch : Integer; Music : TCompressWaveData);

TCompressWaveDataにあらかじめ読み込んで置いた曲データを指定チャンネル(Ch)に登録します。今は無き、CDチェンジャーみたいなモンですか(笑
何も登録したくない・空にしたい場合はMusicにNilを渡します。
あらかじめ、Loadによって登録されたTCompressWaveDataに、新たに曲を読み込ませる時はそのチャンネルにNilを代入し、TCompressWavePlayerから引き離す(関係を解除)するようにしないと、動作が不安定になりますよ(w
チャンネル数は0〜7の8チャンネル。ということは、8曲同時再生も(ヤメレ

例)
MusicPlayer.Load(0,MusicData1);
・・・
MusicPlayer.Load(0,nil);
MusicData1.LoadFromFile('hoge.cwav');
MusicPlayer.Load(0,MusicData1);

procedure SetVolume(ch : Integer; vol : single, fade : single);

音量をフェードします。
vol(0〜1.0)で指定した音量に、fade(0〜)ミリ後にフェードします。
volがなって欲しい音量、fadeがそれに要する時間という感じですか・・・。
今現在の音量を設定したい場合はfade(時間)を0にする事で行うことが出来ます。
曲が停止状態の場合、SetVolumeを行っても再生されませんので、Playを呼び出して再生させてください。

フェードが開始されるタイミングは、ストリーミング再生の関係上すぐに反映されるわけではありません。また、フェードにかかる時間も再生と同じく反映されるまでの時間的な遅延が生じます。遅延時間について、または同期を取りたい場合はこちらを参照してください。

(音量(Volume)とフェード(Fade)の関係と対策)

procedure Stop(ch : Integer);

指定チャンネルの曲を停止させます。
曲を停止し、先頭に巻き戻し、音量を1.0にセットされます。

procedure Previous(ch : Integer);

曲の先頭に巻き戻しますが、Stopと違い、再生状態・音量はリセットされません。
まぁ、使う機会はないでしょう。

procedure pause(ch : Integer);

曲の再生を一時停止させます。再生は停止されますが、音量・フェードなど各設定はリセットされません。
Playによって再開できます。

procedure Play(ch : Integer; loop : Boolean);

曲の再生を行います。

例:)
//ループさせて再生する場合
MusicPlayer1.Play(0,TRUE);

procedure Swap(ch1,ch2 : Integer);

チャンネルのすり替えを行います。使い方によってはなにかと便利なものです。
存在意義不明。無味乾燥な命令。

procedure Init;

使わなくていいですよ。単に、全チャンネルにnilをぶち込むだけ。

procedure Rendering;

これがないと音が鳴りません(笑)そりゃぁそうだわな・・・・。
登録された曲(TCompressWaveData)をボリューム調整・合成し音声波形としてレンダリングします。
レンダリングした内容はBufferにアクセスして、必要に応じて読み込んでクダサイ。

function Buffer : Pointer;

レンダリングされた内容を格納したメモリアドレスを返します。
このメモリアドレスの長さはSetBufferSizeで指定した長さになります。

procedure SetMasterVolume(vol : single; fade : single);

全曲を統括したボリューム、ミキサーの大元の音量調節みたいなもんですね。
全体の音量を下げたい場合は、各曲の音量を下げるのではなく、このマスターを下げてやってください。
フェードの原理などはSetVolumeと同じです。

マスターは各チャンネル(CD)と違って、停止と言う概念がありません。STOPとPLAYが存在しないのはそのためです。ストリーミング要求が来る限り、例えVolumeが0であっても内部では再生しています。

function GetVolume(ch : Integer) : single;

曲の現在の音量を所得出来ます。が、何の意味があるんでしょうか・・・・疑問の残る関数です。

function GetPlayState(ch : Integer) : TCWPlayState;

指定チャンネルの再生状態を返します。
返り血はTCWPlayState型です。

TCWPlayState型
CWEmpty 曲が登録されていない。
CWStop 停止中
CWPlay ループ無し再生
CWLoop ループ再生

function GetReflectTime : Integer;

実際に音になって反映されるまでの時間(遅延時間)を逆算し返します。
この変数だけ、便宜上返値はミリ秒で返します(TimeGetTimeと同じように)
音と同期を取りたい場合など、Playの直前・直後にこのメソッドを使用し、返された時間だけSleepをかければ同期が取れます。
この反映されるまでの長さはSetBufferSizeによって異なります。

遅延時間とは。

function GetFade(ch : Integer) : single;

こちらはフェード情報を・・・・意味無いよなぁ〜・・・。

function GetPlayTime(ch : Integer) : Single;

再生時間を返します。単位は秒です。

function GetTotalTime(ch : Integer) : Single;

曲の長さを返します。単位は秒です。

function GetTitle(ch : Integer) : string;

圧縮時に指定した曲のタイトルを返します。あると便利ですよね〜♪

function GetArtist(ch : Integer) : String;

圧縮時に指定した作曲者を返します。FLUKEとかSTINGとかenyaとかとか。

function GetMasterFade: single;

イイカゲン同じような説明に飽きてきました・・・はぁ・・・

function GetMasterVolume: single;

ぐて・・・・・

constructor Create;

TCompressWaveDataを生成します。CD(コンパクトディスク)だと思ってください。曲のデータを入れるもの。それを生成します。これだけではまだ空っぽの状態ですね(^^。

procedure Free;

解放です。要らなくなったら捨てちゃいましょう。現実の用にリサイクル出来ませんから。

procedure LoadFromStream(ss : TmemoryStream);

付属のツールで圧縮した*.cwavのTCompressWaveDataをストリーム上から読み込みます。
ストリーム上から読み込む事なんて滅多に無いと思いますが(笑

procedure LoadFromFile(filename : string);

付属のツールで圧縮した*.cwavのTCompressWaveDataをファイル上から読み込みます。
読み込んだら、後はPlayerにつっこめば聞けます(^^

例:)
pwav1 := TCompressWaveData.Create;
pwav1.LoadFromFile(Opendialog1.FileName);
MusicPlayer.Load(0,pwav1);
・・・・
MusicPlayer.Load(0,pwav1);
MusicPlayer.Play(0);
MusicPlayer.Loop(0,TRUE);
・・・・
pwav1.Free;

procedure SetCipherCode(code : DWORD);

圧縮時に設定した暗号化コード(鍵?)を設定します。
暗号化していない場合(暗号化コード = 0)は、クラス生成時に初期値で0が入っておりますので呼び出す必要はありません。設定した場合のみ、Createで生成した直後にこのメソッドを呼び出して設定してからファイルを読み込んでクダサイ。でないと一般保護エラ〜だ(笑)また、暗号化コードが異なっていた場合も一般保護エラーだ(笑)

例:)
pwav1 := TCompressWaveData.Create;
pwav1.SetCipherCode($12345678);
pwav1.LoadFromFile(filename);
・・・・
pwav1.Free;

function CWav_LoadWave(filename:String; QS:TDDSD) : TDDSDWavedata;

TCompressWaveDataファイルをDDSDWaveDataにWAVEとして読み込むための、ヘルパー、お便利関数です。

function CWav_LoadWave3D(filename:String; QS:TDDSD) : TDDSDWave3D;

TCompressWaveDataファイルをDDSDWave3DファイルにWAVEとして読み込みます。
Direct3Dを使ったWave形式に展開する以外は、上と全く同じです。