-
-
Save mirichi/9b0bb4620063ff9fabec to your computer and use it in GitHub Desktop.
SoundTestStr廃止
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ruby.h" | |
#include "ruby/thread.h" | |
#include "dsound.h" | |
// mingw64になぜか定義されてないので自前で定義 | |
#ifndef DS3DALG_DEFAULT | |
GUID DS3DALG_DEFAULT = {0}; | |
#endif | |
// RubyのSoundTestクラス | |
static VALUE cSoundTest; | |
// Rubyの例外オブジェクト | |
static VALUE eSoundTestError; | |
// COMのDirectSoundオブジェクト | |
static LPDIRECTSOUND8 g_pDSound; | |
// RubyのSoundTestオブジェクトが持つC構造体 | |
struct SoundTest { | |
LPDIRECTSOUNDBUFFER8 pDSBuffer8; | |
HANDLE event[3]; | |
size_t buffer_size; | |
unsigned long hz; | |
unsigned long channels; | |
unsigned long byte_per_sample; | |
unsigned long sample_size; | |
unsigned long pos; | |
int duplicate_flg; | |
int streaming_flg; | |
VALUE vproc; | |
VALUE vstreaming_thread; | |
}; | |
// COM終了条件はshutdown+すべてのSoundTestの解放なのでカウントする | |
static int g_refcount = 0; | |
// プロトタイプ宣言 | |
static void SoundTest_mark(void *s); | |
static void SoundTest_free(void *s); | |
static size_t SoundTest_memsize(const void *s); | |
void Init_soundogg(void); | |
void Init_customdecoder(void); | |
static VALUE SoundTest_im_stop(VALUE self); | |
// TypedData用の型データ | |
const rb_data_type_t SoundTest_data_type = { | |
"SoundTest", | |
{ | |
SoundTest_mark, // マーク関数 | |
SoundTest_free, // 解放関数 | |
SoundTest_memsize, // サイズ関数 | |
}, | |
NULL, NULL | |
}; | |
// GCのマークで呼ばれるマーク関数 | |
static void SoundTest_mark(void *s) | |
{ | |
struct SoundTest *st = (struct SoundTest *)s; | |
// マーク | |
rb_gc_mark(st->vproc); | |
rb_gc_mark(st->vstreaming_thread); | |
} | |
// DirectSoundバッファを開放する内部用関数 | |
static void SoundTest_release(struct SoundTest *st) | |
{ | |
if (st->pDSBuffer8) { | |
SetEvent(st->event[2]); | |
st->pDSBuffer8->lpVtbl->Stop(st->pDSBuffer8); | |
st->pDSBuffer8->lpVtbl->Release(st->pDSBuffer8); | |
st->pDSBuffer8 = NULL; | |
st->buffer_size = 0; | |
st->hz = 0; | |
st->channels = 0; | |
st->byte_per_sample = 0; | |
st->sample_size = 0; | |
st->pos = 0; | |
st->duplicate_flg = 0; | |
st->streaming_flg = 0; | |
st->vproc = Qnil; | |
st->vstreaming_thread = Qnil; | |
// shutduwn+すべてのSoundTestが解放されたらDirectSound解放 | |
g_refcount--; | |
if (g_refcount == 0) { | |
g_pDSound->lpVtbl->Release(g_pDSound); | |
CoUninitialize(); | |
} | |
} | |
} | |
// GCで回収されたときに呼ばれる解放関数 | |
static void SoundTest_free(void *s) | |
{ | |
// バッファ解放 | |
SoundTest_release((struct SoundTest *)s); | |
// SoundTest解放 | |
xfree(s); | |
} | |
// ObjectSpaceからのサイズの問い合わせに応答する | |
static size_t SoundTest_memsize(const void *s) | |
{ | |
struct SoundTest *st = (struct SoundTest *)s; | |
if (st->duplicate_flg == 0) { | |
// ざっくりstruct SoundTestとバッファのサイズを足して返す | |
return sizeof(struct SoundTest) + st->buffer_size; | |
} else { | |
return sizeof(struct SoundTest); | |
} | |
} | |
// SoundTest.newするとまずこれが呼ばれ、次にinitializeが呼ばれる | |
static VALUE SoundTest_allocate(VALUE klass) | |
{ | |
VALUE obj; | |
struct SoundTest *st; | |
// RubyのTypedData型オブジェクトを生成する | |
obj = TypedData_Make_Struct(klass, struct SoundTest, &SoundTest_data_type, st); | |
// allocate時点ではバッファサイズが不明なのでバッファは作らない | |
st->pDSBuffer8 = NULL; | |
st->buffer_size = 0; | |
st->hz = 0; | |
st->channels = 0; | |
st->byte_per_sample = 0; | |
st->sample_size = 0; | |
st->pos = 0; | |
st->duplicate_flg = 0; | |
st->streaming_flg = 0; | |
st->vproc = Qnil; | |
st->vstreaming_thread = Qnil; | |
st->event[0] = NULL; | |
st->event[1] = NULL; | |
st->event[2] = NULL; | |
// 生成したSoundTestオブジェクトを返す | |
return obj; | |
} | |
// SoundTest#initialize | |
static VALUE SoundTest_initialize(int argc, VALUE *argv, VALUE self) | |
{ | |
DSBUFFERDESC desc; | |
WAVEFORMATEX pcmwf; | |
LPDIRECTSOUNDBUFFER pDSBuffer; | |
DSBPOSITIONNOTIFY pos[2]; | |
LPDIRECTSOUNDNOTIFY pDSNotify; | |
HRESULT hr; | |
VALUE vsample, vhz, vbyte_per_sample, vchannels, vproc, vstreaming; | |
// selfからSoundTest構造体を取り出す | |
struct SoundTest *st = (struct SoundTest *)RTYPEDDATA_DATA(self); | |
// SoundTest#__send__ :initializeで呼ばれるとリークするのでチェックしてバッファがあれば解放 | |
if (st->pDSBuffer8) { | |
SoundTest_release(st); | |
} | |
rb_scan_args(argc, argv, "33", &vsample, &vproc, &vstreaming, &vhz, &vbyte_per_sample, &vchannels); | |
if (!RTEST(rb_obj_is_proc(vproc))) rb_raise(rb_eTypeError, "wrong argument type %s (expected Proc)", rb_obj_classname(vproc)); | |
// フォーマット設定 | |
pcmwf.wFormatTag = WAVE_FORMAT_PCM; | |
pcmwf.nChannels = NUM2INT(vchannels); // モノラルかステレオか | |
pcmwf.nSamplesPerSec = NUM2INT(vhz); // サンプル周波数 | |
pcmwf.wBitsPerSample = NUM2INT(vbyte_per_sample) * 8; // 量子化ビット数 | |
pcmwf.nBlockAlign = pcmwf.nChannels * pcmwf.wBitsPerSample / 8; // 1sampleのバイト数 | |
pcmwf.nAvgBytesPerSec = pcmwf.nSamplesPerSec * pcmwf.nBlockAlign; // 1secに必要なバイト数 | |
pcmwf.cbSize = 0; | |
// DirectSoundバッファ設定 | |
desc.dwSize = sizeof(desc); | |
desc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY; | |
desc.dwBufferBytes = NUM2INT(vsample) * pcmwf.nBlockAlign; // 引数で渡されたサンプル数でバッファを作る | |
desc.dwReserved = 0; | |
desc.lpwfxFormat = &pcmwf; | |
desc.guid3DAlgorithm = DS3DALG_DEFAULT; | |
// DirectSoundバッファ生成 | |
hr = g_pDSound->lpVtbl->CreateSoundBuffer(g_pDSound, &desc, &pDSBuffer, NULL); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "create buffer error"); | |
hr = pDSBuffer->lpVtbl->QueryInterface(pDSBuffer, &IID_IDirectSoundBuffer8, (void**)&(st->pDSBuffer8)); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "query interface error"); | |
pDSBuffer->lpVtbl->Release(pDSBuffer); | |
st->buffer_size = desc.dwBufferBytes; | |
st->hz = pcmwf.nSamplesPerSec; | |
st->channels = pcmwf.nChannels; | |
st->byte_per_sample = pcmwf.wBitsPerSample / 8; | |
st->sample_size = pcmwf.nBlockAlign; | |
st->pos = 0; | |
st->duplicate_flg = 0; | |
st->streaming_flg = RTEST(vstreaming); | |
st->vproc = vproc; | |
// イベント作成 | |
st->event[0] = CreateEvent(NULL, FALSE, FALSE, NULL); | |
st->event[1] = CreateEvent(NULL, FALSE, FALSE, NULL); | |
st->event[2] = CreateEvent(NULL, FALSE, FALSE, NULL); | |
// 再生イベント通知オブジェクト | |
hr = st->pDSBuffer8->lpVtbl->QueryInterface(st->pDSBuffer8, &IID_IDirectSoundNotify, (void**)&pDSNotify); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "query interface(notify) error"); | |
pos[0].dwOffset = st->buffer_size / 2 - 1; | |
pos[0].hEventNotify = st->event[0]; | |
pos[1].dwOffset = st->buffer_size - 1; | |
pos[1].hEventNotify = st->event[1]; | |
hr = pDSNotify->lpVtbl->SetNotificationPositions(pDSNotify, 2, pos); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "set notification psitions error"); | |
pDSNotify->lpVtbl->Release(pDSNotify); | |
g_refcount++; | |
rb_funcall(self, rb_intern_const("writebuf"), 0); | |
return self; | |
} | |
// SoundTest#dispose | |
static VALUE SoundTest_im_dispose(VALUE self) | |
{ | |
// selfからSoundTest構造体を取り出す | |
struct SoundTest *st = (struct SoundTest *)RTYPEDDATA_DATA(self); | |
// st->pDSBuffer8がNULLの場合はdispose済み | |
if (!st->pDSBuffer8) rb_raise(eSoundTestError, "disposed object"); | |
SoundTest_release(st); | |
return self; | |
} | |
// SoundTest#writebuf | |
// 必要なバッファサイズをRubyに渡し、ブロックを呼びだして返ってきたデータ(String)をバッファ書き込む。 | |
static VALUE SoundTest_im_writebuf(VALUE self) | |
{ | |
void *block1; | |
void *block2; | |
unsigned long blockSize1; | |
unsigned long blockSize2; | |
unsigned long i, j; | |
HRESULT hr; | |
unsigned long playpos; | |
VALUE vbuf; | |
char *block; | |
// selfからSoundTest構造体を取り出す | |
struct SoundTest *st = (struct SoundTest *)RTYPEDDATA_DATA(self); | |
// st->pDSBuffer8がNULLの場合はdispose済み | |
if (!st->pDSBuffer8) rb_raise(eSoundTestError, "disposed object"); | |
// 再生カーソルを取得する | |
hr = st->pDSBuffer8->lpVtbl->GetCurrentPosition(st->pDSBuffer8, &playpos, 0); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "get current position error"); | |
// Rubyブロックに必要なバッファサイズを渡してバッファをもらう | |
vbuf = rb_proc_call(st->vproc, rb_ary_new3(1, UINT2NUM(st->pos < playpos ? playpos - st->pos : st->buffer_size - st->pos + playpos))); | |
if (vbuf == Qnil) { | |
SoundTest_im_stop(self); | |
return self; | |
} | |
Check_Type(vbuf, T_STRING); | |
// バッファロック | |
hr = st->pDSBuffer8->lpVtbl->Lock(st->pDSBuffer8, st->pos, RSTRING_LEN(vbuf), &block1, &blockSize1, &block2, &blockSize2, 0); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "lock error"); | |
// バッファへのデータ書き込み | |
block = (char *)block1; | |
for(i = 0; i < blockSize1; i++) { | |
*(block + i) = (char)(RSTRING_PTR(vbuf)[i]); | |
} | |
// バッファがループした場合の処理 | |
if (block2 && blockSize2) { | |
block = (char *)block2; | |
for(j = 0; j < blockSize2; j++, i++) { | |
*(block + j) = (char)(RSTRING_PTR(vbuf)[i]); | |
} | |
} | |
// st->posがバッファを越えたらループさせる | |
st->pos += blockSize1 + blockSize2; | |
if (st->buffer_size <= st->pos) { | |
st->pos -= st->buffer_size; | |
} | |
// バッファアンロック | |
hr = st->pDSBuffer8->lpVtbl->Unlock(st->pDSBuffer8, block1, blockSize1, block2, blockSize2); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "unlock error"); | |
return self; | |
} | |
// SoundTest#size | |
// バッファのサンプル数を返す | |
static VALUE SoundTest_im_size(VALUE self) | |
{ | |
// selfからSoundTest構造体を取り出す | |
struct SoundTest *st = (struct SoundTest *)RTYPEDDATA_DATA(self); | |
// st->pDSBuffer8がNULLの場合はdispose済み | |
if (!st->pDSBuffer8) rb_raise(eSoundTestError, "disposed object"); | |
return UINT2NUM(st->buffer_size / st->sample_size); | |
} | |
// SoundTest#getpos | |
// 再生カーソルと書き込みカーソルを配列で返す | |
static VALUE SoundTest_im_getpos(VALUE self) | |
{ | |
unsigned long playpos; | |
unsigned long writepos; | |
HRESULT hr; | |
// selfからSoundTest構造体を取り出す | |
struct SoundTest *st = (struct SoundTest *)RTYPEDDATA_DATA(self); | |
// st->pDSBuffer8がNULLの場合はdispose済み | |
if (!st->pDSBuffer8) rb_raise(eSoundTestError, "disposed object"); | |
hr = st->pDSBuffer8->lpVtbl->GetCurrentPosition(st->pDSBuffer8, &playpos, &writepos); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "getpos error"); | |
return rb_ary_new3(3, UINT2NUM(st->pos / st->sample_size), UINT2NUM(playpos / st->sample_size), UINT2NUM(writepos / st->sample_size)); | |
} | |
// 内部ストリーミング再生用スレッド関数 | |
static VALUE SoundTest_streaming_thread(void *arg) | |
{ | |
VALUE self = (VALUE)arg; | |
while(!RTEST(rb_funcall(self, rb_intern_const("wait"), 0))) { | |
rb_funcall(self, rb_intern_const("writebuf"), 0); | |
} | |
return Qnil; | |
} | |
// SoundTest#play | |
// 再生する | |
static VALUE SoundTest_im_play(int argc, VALUE *argv, VALUE self) | |
{ | |
HRESULT hr; | |
VALUE vloop; | |
// selfからSoundTest構造体を取り出す | |
struct SoundTest *st = (struct SoundTest *)RTYPEDDATA_DATA(self); | |
// st->pDSBuffer8がNULLの場合はdispose済み | |
if (!st->pDSBuffer8) rb_raise(eSoundTestError, "disposed object"); | |
// 引数取得。vloopは省略可能引数 | |
rb_scan_args(argc, argv, "01", &vloop); | |
ResetEvent(st->event[0]); | |
ResetEvent(st->event[1]); | |
ResetEvent(st->event[2]); | |
// ストリーミング再生する場合はThreadを生成してバッファをループ再生する | |
if (st->streaming_flg) { | |
// ストリーミング再生用スレッドを生成する | |
st->vstreaming_thread = rb_thread_create(SoundTest_streaming_thread, (void *)self); | |
// ストリーミング再生用スレッドでコケたら全体がコケるように設定する | |
rb_funcall(st->vstreaming_thread, rb_intern_const("abort_on_exception="), 1, Qtrue); | |
// 再生 | |
hr = st->pDSBuffer8->lpVtbl->Play(st->pDSBuffer8, 0, 0, DSBPLAY_LOOPING); | |
} else { | |
// とりあえず停止する | |
hr = st->pDSBuffer8->lpVtbl->Stop(st->pDSBuffer8); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "stop error"); | |
// カーソルを先頭にセット | |
hr = st->pDSBuffer8->lpVtbl->SetCurrentPosition(st->pDSBuffer8, 0); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "setpos error"); | |
// 再生。vloopがtrueの場合ループ再生する | |
hr = st->pDSBuffer8->lpVtbl->Play(st->pDSBuffer8, 0, 0, RTEST(vloop) ? DSBPLAY_LOOPING : 0); | |
} | |
if (FAILED(hr)) rb_raise(eSoundTestError, "play error"); | |
return self; | |
} | |
// SoundTest#stop | |
// 停止する | |
static VALUE SoundTest_im_stop(VALUE self) | |
{ | |
HRESULT hr; | |
// selfからSoundTest構造体を取り出す | |
struct SoundTest *st = (struct SoundTest *)RTYPEDDATA_DATA(self); | |
// st->pDSBuffer8がNULLの場合はdispose済み | |
if (!st->pDSBuffer8) rb_raise(eSoundTestError, "disposed object"); | |
// 停止 | |
SetEvent(st->event[2]); | |
hr = st->pDSBuffer8->lpVtbl->Stop(st->pDSBuffer8); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "stop error"); | |
// カーソルを先頭にセット | |
hr = st->pDSBuffer8->lpVtbl->SetCurrentPosition(st->pDSBuffer8, 0); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "setpos error"); | |
return self; | |
} | |
// GVLアンロック状態で呼ばれる関数 | |
static void *SoundTest_wait_blocking(void *data) | |
{ | |
DWORD result; | |
result = WaitForMultipleObjects(3, (HANDLE*)data, 0, INFINITE); | |
return (HANDLE *)data + result - WAIT_OBJECT_0; | |
} | |
// SoundTest#wait | |
// 通知を待つメソッド | |
static VALUE SoundTest_im_wait(VALUE self) | |
{ | |
HANDLE *result; | |
// selfからSoundTest構造体を取り出す | |
struct SoundTest *st = (struct SoundTest *)RTYPEDDATA_DATA(self); | |
// GVLをアンロックしてSoundTest_wait_blockingを呼び出す | |
result = (HANDLE *)rb_thread_call_without_gvl(SoundTest_wait_blocking, (void*)(st->event), RUBY_UBF_PROCESS, 0); | |
// 終了イベントを受けたらtrueを返す | |
return result == &st->event[2] ? Qtrue : Qfalse; | |
} | |
// SoundTest#duplicate | |
// selfとバッファを共有する新しいSoundTestオブジェクトを生成して返す | |
static VALUE SoundTest_im_duplicate(VALUE self) | |
{ | |
HRESULT hr; | |
// selfからSoundTest構造体を取り出す | |
struct SoundTest *src = (struct SoundTest *)RTYPEDDATA_DATA(self); | |
// 新しいSoundTestオブジェクトを生成して構造体を取り出す | |
VALUE obj = SoundTest_allocate(cSoundTest); | |
struct SoundTest *dst = (struct SoundTest *)RTYPEDDATA_DATA(obj); | |
// selfのDirectSoundバッファからバッファ共有型DirectSoundバッファを生成する | |
hr = g_pDSound->lpVtbl->DuplicateSoundBuffer(g_pDSound, (LPDIRECTSOUNDBUFFER)src->pDSBuffer8, (LPDIRECTSOUNDBUFFER *)&dst->pDSBuffer8); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "duplicate error"); | |
// 内部情報のコピー | |
dst->buffer_size = src->buffer_size; | |
dst->hz = src->hz; | |
dst->channels = src->channels; | |
dst->byte_per_sample = src->byte_per_sample; | |
dst->sample_size = src->sample_size; | |
dst->pos = src->pos; | |
dst->duplicate_flg = 1; | |
dst->event[0] = NULL; | |
dst->event[1] = NULL; | |
dst->event[2] = NULL; | |
g_refcount++; | |
return obj; | |
} | |
// Rubyのクラス定義 | |
static void Init_soundtest_class(void) | |
{ | |
// 例外定義 | |
eSoundTestError = rb_define_class( "SoundTestError", rb_eRuntimeError ); | |
// SoundTestクラス生成 | |
cSoundTest = rb_define_class("SoundTest", rb_cObject); | |
rb_define_private_method(cSoundTest, "initialize", SoundTest_initialize, -1); | |
rb_define_method(cSoundTest, "play", SoundTest_im_play, -1); | |
rb_define_method(cSoundTest, "stop", SoundTest_im_stop, 0); | |
rb_define_method(cSoundTest, "getpos", SoundTest_im_getpos, 0); | |
rb_define_method(cSoundTest, "size", SoundTest_im_size, 0); | |
rb_define_method(cSoundTest, "writebuf", SoundTest_im_writebuf, 0); | |
rb_define_method(cSoundTest, "dispose", SoundTest_im_dispose, 0); | |
rb_define_method(cSoundTest, "wait", SoundTest_im_wait, 0); | |
rb_define_method(cSoundTest, "duplicate", SoundTest_im_duplicate, 0); | |
// SoundTestオブジェクトを生成した時にinitializeの前に呼ばれるメモリ割り当て関数登録 | |
rb_define_alloc_func(cSoundTest, SoundTest_allocate); | |
} | |
// 終了時に実行されるENDブロックに登録する関数 | |
static void SoundTest_shutdown(VALUE obj) | |
{ | |
// shutduwn+すべてのSoundTestが解放されたらDirectSound解放 | |
g_refcount--; | |
if (g_refcount == 0) { | |
g_pDSound->lpVtbl->Release(g_pDSound); | |
CoUninitialize(); | |
} | |
} | |
void Init_soundtest(void) | |
{ | |
HWND hWnd; | |
HINSTANCE hInstance; | |
WNDCLASSEX wcex; | |
HRESULT hr; | |
// COM初期化 | |
CoInitialize(NULL); | |
// ウィンドウクラス設定 | |
hInstance = (HINSTANCE)GetModuleHandle(NULL); | |
wcex.cbSize = sizeof(WNDCLASSEX); | |
wcex.style = 0; | |
wcex.lpfnWndProc = DefWindowProc; | |
wcex.cbClsExtra = 0; | |
wcex.cbWndExtra = 0; | |
wcex.hInstance = hInstance; | |
wcex.hIcon = 0; | |
wcex.hIconSm = 0; | |
wcex.hCursor = 0; | |
wcex.hbrBackground = 0; | |
wcex.lpszMenuName = NULL; | |
wcex.lpszClassName = "SoundTest"; | |
// ウィンドウ生成 | |
RegisterClassEx(&wcex); | |
hWnd = CreateWindow("SoundTest", "", 0, 0, 0, 0, 0, 0, NULL, hInstance, NULL); | |
// DirectSoundオブジェクト生成 | |
hr = DirectSoundCreate8(&DSDEVID_DefaultPlayback, &g_pDSound, NULL); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "create directsound error"); | |
// 協調レベル設定 | |
hr = g_pDSound->lpVtbl->SetCooperativeLevel(g_pDSound, hWnd, DSSCL_PRIORITY); | |
if (FAILED(hr)) rb_raise(eSoundTestError, "setcooperativelevel error"); | |
g_refcount++; | |
// 終了時に実行する関数 | |
rb_set_end_proc(SoundTest_shutdown, Qnil); | |
// SoundTestクラス生成 | |
Init_soundtest_class(); | |
// SoundOggクラス生成 | |
Init_soundogg(); | |
// CustomDecoderクラス生成 | |
Init_customdecoder(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment