Skip to content

Instantly share code, notes, and snippets.

@mirichi
Created January 6, 2015 13:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mirichi/9b0bb4620063ff9fabec to your computer and use it in GitHub Desktop.
Save mirichi/9b0bb4620063ff9fabec to your computer and use it in GitHub Desktop.
SoundTestStr廃止
#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