/*
 * WavePlay.cs
 * Copyright (c) 2009 kbinani
 *
 * This file is part of Boare.Lib.Media.
 *
 * Boare.Lib.Media is free software; you can redistribute it and/or
 * modify it under the terms of the BSD License.
 *
 * Boare.Lib.Media is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
using System;
using System.Runtime.InteropServices;

using bocoree;

namespace Boare.Lib.Media {

    public delegate void FirstBufferWrittenCallback();

    public unsafe class WavePlay {
        const int _NUM_BUF = 3;              // バッファの数
        int s_block_size;           // 1個のバッファのサイズ(サンプル)
        int s_sample_rate;          // サンプリングレート
        WAVEFORMATEX s_wave_formatx;         // WAVEファイルヘッダ
        //IntPtr s_ptr_wave_formatx;
        IntPtr s_hwave_out;            // WAVE再生デバイス

        WAVEHDR[] s_wave_header = new WAVEHDR[_NUM_BUF];// WAVEヘッダ
        IntPtr[] s_ptr_wave_header = new IntPtr[_NUM_BUF];

        uint*[] s_wave = new uint*[_NUM_BUF];     // バッファ
        IntPtr[] s_ptr_wave = new IntPtr[_NUM_BUF];

        bool[] s_done = new bool[_NUM_BUF];
        int s_current_buffer;       // 次回書き込むべきバッファのインデクス
        uint s_processed_count;      // 初回はバッファを_NUM_BUF個全部埋めなければいけないので、最初の _NUM_BUF + 1 回はカウントを行う。そのためのカウンタ
        bool s_abort_required;       // 再生の中断が要求された時立つフラグ
        int s_buffer_loc;           // 書き込み中のバッファ内の、現在位置
        bool s_playing;              // 再生中かどうかを表すフラグ
        int s_error_samples;        // appendされた波形データの内、先頭のs_error_samples分を省く。通常の使い方なら常に0だが、vocaloid2 vstiで使う場合、プリセンド分を除いてwaveOutWriteしなければいけないので非0になる。
        int s_last_buffer;          // 最後に再生されるバッファの番号。負値の場合、append_lastが未だ呼ばれていないことを意味する。
        FirstBufferWrittenCallback s_first_buffer_written_callback; // 最初のバッファが書き込まれたとき呼び出されるコールバック関数
        WaveReader[] s_wave_reader;
        int s_num_wave_reader; // s_wave_readerの個数
        float*[] s_another_wave_l;
        IntPtr[] s_ptr_another_wave_l;
        float*[] s_another_wave_r;
        IntPtr[] s_ptr_another_wave_r;
        long s_wave_read_offset_samples;
        float* s_wave_buffer_l;
        IntPtr s_ptr_wave_buffer_l;
        float* s_wave_buffer_r;
        IntPtr s_ptr_wave_buffer_r;

        delegateWaveOutProc s_wave_callback;

        /// コールバック関数
        void wave_callback( IntPtr hwo, uint uMsg, uint dwInstance, uint dwParam1, uint dwParam2 ) {
#if DEBUG
            Console.WriteLine( "WavePlay.wave_callback; uMsg=" + uMsg );
#endif
            if ( uMsg == windows.MM_WOM_DONE ) {
                int index_done = 0;
                WAVEHDR whdr = (WAVEHDR)Marshal.PtrToStructure( new IntPtr( dwParam1 ), typeof( WAVEHDR ) );
                int dwuser = whdr.dwUser.ToInt32();
                if ( dwuser >= _NUM_BUF ) {
                    index_done = dwuser - _NUM_BUF;
                } else {
                    index_done = dwuser;
                }
                if ( 0 <= index_done && index_done < _NUM_BUF ) {
                    s_done[index_done] = true;
                    if ( s_last_buffer == index_done ) {
                        s_playing = false;
                    }
                    if ( dwuser >= _NUM_BUF ) {
                        s_wave_header[index_done].dwUser = new IntPtr( index_done );
                    }
                }
            }
        }

        void append_cor( float** a_data, uint length, double amp_left, double amp_right, bool is_last_mode ) {
#if DEBUG

            bocoree.debug.push_log( "append_cor *************************************************************" );
            bocoree.debug.push_log( "    length=" + length );
            bocoree.debug.push_log( "    s_hwave_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif
            s_playing = true;
            int jmax = (int)length;
            int remain = 0;
            IntPtr ptr_data = IntPtr.Zero;
            IntPtr ptr_data0 = IntPtr.Zero;
            IntPtr ptr_data1 = IntPtr.Zero;
            ptr_data = Marshal.AllocHGlobal( sizeof( float* ) * 2 );
            float** data = (float**)ptr_data.ToPointer();// new float*[2];
            bool cleaning_required = false;
            if ( s_error_samples > 0 ) {
                if ( s_error_samples >= length ) {
                    s_error_samples -= (int)length;
                    return;
                }
                cleaning_required = true;
                int actual_length = (int)length - s_error_samples;
#if DEBUG
                bocoree.debug.push_log( "    actual_length=" + actual_length );
#endif
                ptr_data0 = Marshal.AllocHGlobal( sizeof( float ) * actual_length );
                ptr_data1 = Marshal.AllocHGlobal( sizeof( float ) * actual_length );
                data[0] = (float*)ptr_data0.ToPointer();
                data[1] = (float*)ptr_data1.ToPointer();
                for ( int i = 0; i < actual_length; i++ ) {
                    data[0][i] = a_data[0][i + s_error_samples];
                    data[1][i] = a_data[1][i + s_error_samples];
                }
                s_error_samples = 0;
                length = (uint)actual_length;
                jmax = (int)length;
            } else {
                data = a_data;
            }

            if ( length + s_buffer_loc >= s_block_size ) {
                jmax = s_block_size - s_buffer_loc;
                remain = (int)length - (int)jmax;
            }
            float aright = (float)amp_right;
            float aleft = (float)amp_left;

            for ( int j = 0; j < jmax; j++ ) {
                s_wave_buffer_l[j + s_buffer_loc] = data[1][j];
                s_wave_buffer_r[j + s_buffer_loc] = data[0][j];
            }
            s_buffer_loc += jmax;

            if ( s_buffer_loc >= s_block_size ) {
                // バッファー充填完了．バッファーを転送し、waveOutWriteが書き込めるタイミングまで待機
#if DEBUG
                bocoree.debug.push_log( "append_cor; waiting(1) " + s_current_buffer + "..." );
#endif
                while ( true ) {
                    if ( s_abort_required ) {
                        s_abort_required = false;
                        goto clean_and_exit;
                    }
                    if ( s_done[s_current_buffer] ) {
                        break;
                    }
                }
#if DEBUG
                bocoree.debug.push_log( "append_cor; ...exit" );
#endif

                s_processed_count++;
                mix( (int)s_processed_count, aleft, aright );

                if ( s_processed_count == _NUM_BUF ) {
                    s_done[0] = false;
#if DEBUG
                    bocoree.debug.push_log( "calling waveOutWrite...; s_hawve_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif
                    uint ret = windows.waveOutWrite( s_hwave_out, ref s_wave_header[0], (uint)sizeof( WAVEHDR ) );
#if DEBUG
                    bocoree.debug.push_log( "...done; ret=" + ret );
#endif
#if DEBUG
                    bocoree.debug.push_log( "(s_first_buffer_wirtten_callback==null)=" + (s_first_buffer_written_callback == null) );
#endif
                    if ( s_first_buffer_written_callback != null ) {
#if DEBUG
                        bocoree.debug.push_log( "append_cor; calling s_first_buffer_written_callback" );
#endif
                        s_first_buffer_written_callback();
                    }
                    for ( int buffer_index = 1; buffer_index < _NUM_BUF; buffer_index++ ) {
                        s_done[buffer_index] = false;
#if DEBUG
                        bocoree.debug.push_log( "calling waveOutWrite...; s_hawve_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif
                        uint ret2 = windows.waveOutWrite( s_hwave_out, ref s_wave_header[buffer_index], (uint)sizeof( WAVEHDR ) );
#if DEBUG
                        bocoree.debug.push_log( "...done; ret2=" + ret2 );
#endif
                    }
                    s_current_buffer = _NUM_BUF - 1;
                } else if ( s_processed_count > _NUM_BUF ) {
                    s_done[s_current_buffer] = false;
#if DEBUG
                    bocoree.debug.push_log( "calling waveOutWrite...; s_hawve_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif
                    uint ret3 = windows.waveOutWrite( s_hwave_out, ref s_wave_header[s_current_buffer], (uint)sizeof( WAVEHDR ) );
#if DEBUG
                    bocoree.debug.push_log( "...done; ret3=" + ret3 );
#endif
                }
                s_current_buffer++;
                if ( s_current_buffer >= _NUM_BUF ) {
                    s_current_buffer = 0;
                }

                s_buffer_loc = 0;
            }

            if ( remain > 0 ) {
                for ( int j = jmax; j < length; j++ ) {
                    s_wave_buffer_l[j - jmax] = data[1][j];
                    s_wave_buffer_r[j - jmax] = data[0][j];
                }
                if ( is_last_mode ) {
                    for ( int j = (int)length - jmax; j < s_block_size; j++ ) {
                        s_wave_buffer_l[j] = 0.0f;
                        s_wave_buffer_r[j] = 0.0f;
                    }
                }
                s_buffer_loc = remain;
            }

            if ( is_last_mode ) {
                if ( s_processed_count < _NUM_BUF ) {
                    // _NUM_BUFブロック分のデータを未だ全て受信していない場合。バッファが未だひとつも書き込まれていないので
                    // 0番のブロックから順に書き込む
                    s_processed_count++;
                    mix( (int)s_processed_count, aleft, aright );
                    s_done[0] = false;
#if DEBUG
                    bocoree.debug.push_log( "calling waveOutWrite...; s_hawve_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif
                    uint ret35 = windows.waveOutWrite( s_hwave_out, ref s_wave_header[0], (uint)sizeof( WAVEHDR ) );
#if DEBUG
                    bocoree.debug.push_log( "...done; ret35=" + ret35 );
                    bocoree.debug.push_log( "(s_first_buffer_written_callback==null)=" + (s_first_buffer_written_callback == null) );
#endif
                    if ( s_first_buffer_written_callback != null ) {
#if DEBUG
                        bocoree.debug.push_log( "append_cor; calling s_first_buffer_written_callback" );
#endif
                        s_first_buffer_written_callback();
                    }
                    for ( int i = 1; i < _NUM_BUF - 1; i++ ) {
                        s_processed_count++;
                        mix( (int)s_processed_count, aleft, aright );
                        s_done[i] = false;
#if DEBUG
                        bocoree.debug.push_log( "calling waveOutWrite...; s_hawve_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif
                        uint ret36 = windows.waveOutWrite( s_hwave_out, ref s_wave_header[i], (uint)sizeof( WAVEHDR ) );
#if DEBUG
                        bocoree.debug.push_log( "...done; ret36=" + ret36 );
#endif
                    }
                }
                ulong zero = MAKELONG( 0, 0 );
                for ( int j = s_buffer_loc; j < s_block_size; j++ ) {
                    s_wave_buffer_l[j] = 0.0f;
                    s_wave_buffer_r[j] = 0.0f;
                }
#if DEBUG
                bocoree.debug.push_log( "append_cor; waiting(3) " + s_current_buffer + "..." );
#endif
                while ( !s_done[s_current_buffer] ) {
                    if ( s_abort_required ) {
                        s_abort_required = false;
                        goto clean_and_exit;
                    }
                }
#if DEBUG
                bocoree.debug.push_log( "append_cor; ...exit" );
#endif
                s_processed_count++;
                mix( (int)s_processed_count, aleft, aright );
                s_done[s_current_buffer] = false;
#if DEBUG
                bocoree.debug.push_log( "calling waveOutWrite...; s_hawve_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif
                uint ret4 = windows.waveOutWrite( s_hwave_out, ref s_wave_header[s_current_buffer], (uint)sizeof( WAVEHDR ) );
#if DEBUG
                bocoree.debug.push_log( "...done; ret4=" + ret4 );
#endif
            }
        clean_and_exit:
            if ( is_last_mode ) {
                s_last_buffer = s_current_buffer;
            }
            if ( cleaning_required ) {
                Marshal.FreeHGlobal( ptr_data0 ); //delete [] data[0];
                Marshal.FreeHGlobal( ptr_data1 ); //delete [] data[1];
                Marshal.FreeHGlobal( ptr_data ); //delete [] data;
            }
        }

        void mix( int processed_count, float amp_left, float amp_right ) {
            int current_buffer = (processed_count - 1) % _NUM_BUF;
            for ( int k = 0; k < s_num_wave_reader; k++ ) {
                s_wave_reader[k].Read( s_block_size * (processed_count - 1) + (int)s_wave_read_offset_samples,
                                       s_block_size,
                                       out s_ptr_another_wave_l[k],
                                       out s_ptr_another_wave_r[k] );
            }
            for ( int i = 0; i < s_block_size; i++ ) {
                float l = s_wave_buffer_l[i] * amp_left;
                float r = s_wave_buffer_r[i] * amp_right;
                for ( int k = 0; k < s_num_wave_reader; k++ ) {
                    l += s_another_wave_l[k][i];
                    r += s_another_wave_r[k][i];
                }
                s_wave[current_buffer][i] = MAKELONG( (ushort)(r * 32768.0f), (ushort)(l * 32768.0f) );
            }
        }

        string util_get_errmsg( uint msg ) {
            //IntPtr ptr_err = Marshal.AllocHGlobal( sizeof( byte ) * 260 );
            //byte* err = (byte*)ptr_err.ToPointer();
            //System.Text.StringBuilder sb = new System.Text.StringBuilder( 260 );
            string ret = "";
            windows.mciGetErrorStringA( msg, ret, 260 );
            /*int len = 260;
            for ( int i = 1; i < 260; i++ ) {
                if ( err[i] == '\0' ) {
                    len = i - 1;
                    break;
                }
            }*/
            //string ret = new string( err );
            //string ret = sb.ToString();
            //Marshal.FreeHGlobal( ptr_err );
            return ret;
        }

        private WavePlay() {
        }

        /// 初期化関数
        public WavePlay( int block_size, int sample_rate ) {
#if DEBUG
            Console.WriteLine( "waveplay..ctor" );
#endif
            s_block_size = block_size;
            s_sample_rate = sample_rate;

            //s_ptr_wave_formatx = Marshal.AllocHGlobal( sizeof( WAVEFORMATEX ) );
            s_wave_formatx = new WAVEFORMATEX();
            //Marshal.PtrToStructure( s_ptr_wave_formatx, s_wave_formatx );
            s_wave_formatx.cbSize = (ushort)sizeof( WAVEFORMATEX );
#if DEBUG
            Console.WriteLine( "    s_wave_fomratx.cbSize=" + s_wave_formatx.cbSize );
            Console.WriteLine( "    sizeof( WAVEHDR )=" + sizeof( WAVEHDR ) );
#endif
            s_wave_formatx.wFormatTag = windows.WAVE_FORMAT_PCM;
            s_wave_formatx.nChannels = 2;
            s_wave_formatx.wBitsPerSample = 16;
            s_wave_formatx.nBlockAlign = (ushort)(s_wave_formatx.nChannels * s_wave_formatx.wBitsPerSample / 8);
            s_wave_formatx.nSamplesPerSec = (uint)s_sample_rate;
            s_wave_formatx.nAvgBytesPerSec = s_wave_formatx.nSamplesPerSec * s_wave_formatx.nBlockAlign;

            s_wave_callback = new delegateWaveOutProc( wave_callback );
            s_hwave_out = IntPtr.Zero;
            Console.WriteLine( "    calling waveOutOpen..." );
            uint ret = windows.waveOutOpen( ref s_hwave_out, 
                                            windows.WAVE_MAPPER, 
                                            ref s_wave_formatx,
                                            s_wave_callback,
                                            IntPtr.Zero, 
                                            (uint)windows.CALLBACK_FUNCTION );
            Console.WriteLine( "    ...done; ret=" + ret );
#if DEBUG
            bocoree.debug.push_log( "    s_hwave_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif

            for ( int k = 0; k < _NUM_BUF; k++ ) {
                s_ptr_wave[k] = Marshal.AllocHGlobal( sizeof( uint ) * s_block_size );
                s_wave[k] = (uint*)s_ptr_wave[k];// = (ulong*)calloc( sizeof( ulong ), s_block_size );
                s_ptr_wave_header[k] = Marshal.AllocHGlobal( sizeof( WAVEHDR ) );
                s_wave_header[k] = (WAVEHDR)Marshal.PtrToStructure( s_ptr_wave_header[k], typeof( WAVEHDR ) );
                s_wave_header[k].lpData = s_ptr_wave[k];
                s_wave_header[k].dwBufferLength = (uint)(sizeof( uint ) * s_block_size);
                s_wave_header[k].dwFlags = windows.WHDR_BEGINLOOP | windows.WHDR_ENDLOOP;
                s_wave_header[k].dwLoops = 1;

#if DEBUG
                Console.WriteLine( "calling waveOutPrepareHeader..." );
#endif
                uint ret2 = windows.waveOutPrepareHeader( s_hwave_out, ref s_wave_header[k], (uint)sizeof( WAVEHDR ) );
#if DEBUG
                Console.WriteLine( "...done; ret2=" + ret2 );
#endif
                s_wave_header[k].dwUser = new IntPtr( k );
            }
#if DEBUG
            bocoree.debug.push_log( "   exit waveplay..ctor; s_hwave_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif
        }

        /// 波形データをバッファに追加する。バッファが再生中などの理由で即座に書き込めない場合、バッファが書き込み可能となるまで待機させられる
        public void append( float** data, uint length, double amp_left, double amp_right ) {
            append_cor( data, length, amp_left, amp_right, false );
        }

        public void flush_and_exit( double amp_left, double amp_right ) {
            append_cor( (float**)0, 0, amp_left, amp_right, true );
        }

        /// 再生中断を要求する
        public void abort() {
            s_abort_required = true;
            reset();
            for ( int k = 0; k < _NUM_BUF; k++ ) {
                if ( s_ptr_wave[k] != IntPtr.Zero ) {
                    for ( int i = 0; i < s_block_size; i++ ) {
                        s_wave[k][i] = 0;
                    }
                    //memset( s_wave[k], 0, s_block_size * sizeof( ulong ) );
                }
            }
            s_buffer_loc = 0;
            s_current_buffer = 0;
            s_processed_count = 0;
        }

        /// 現在の再生位置を取得する。再生中でない場合負の値となる。
        public float get_play_time() {
#if DEBUG
            bocoree.debug.push_log( "WavePlay.get_play_time" );
#endif
            if ( s_playing ) {
                MMTIME mmt = new MMTIME();
                mmt.cb = (uint)sizeof( MMTIME );
                mmt.wType = windows.TIME_MS;
                uint ret = windows.waveOutGetPosition( s_hwave_out, ref mmt, (uint)sizeof( MMTIME ) );
#if DEBUG
                bocoree.debug.push_log( "    ret=" + ret );
#endif
                float ms = 0.0f;
                switch ( mmt.wType ) {
                    case windows.TIME_MS:
                        return mmt.ms * 0.001f;
                    case windows.TIME_SAMPLES:
                        return (float)mmt.sample / (float)s_wave_formatx.nSamplesPerSec;
                    case windows.TIME_BYTES:
                        return (float)mmt.cb / (float)s_wave_formatx.nAvgBytesPerSec;
                    default:
                        return -1.0f;
                }
            } else {
                return -1.0f;
            }
        }

        /// リセットする。abort関数でも呼び出される。
        public void reset() {
            s_playing = false;
            if ( s_hwave_out.ToInt32() != 0 ) {
                for ( int k = 0; k < _NUM_BUF; k++ ) {
                    s_wave_header[k].dwUser = new IntPtr( _NUM_BUF + k );
                }
                windows.waveOutReset( s_hwave_out );
                uint zero = MAKELONG( 0, 0 );
                for ( int k = 0; k < _NUM_BUF; k++ ) {
                    for ( int i = 0; i < s_block_size; i++ ) {
                        s_wave[k][i] = zero;
                    }
                }
            }
            for ( int i = 0; i < s_num_wave_reader; i++ ) {
                s_wave_reader[i].Close();
            }
        }

        /// 再生のための準備を行う。この関数を呼び出した後は、バッファが再生開始されるまでget_play_timeの戻り値は0となる（負値にならない）。
        /// 戻り値は、filesに指定されたファイルの内、最も再生時間の長いwaveファイルの、合計サンプル数
        public int on_your_mark( string[] files, long wave_read_offset_samples ) {
#if DEBUG
            bocoree.debug.push_log( "on_your_mark; s_hwave_out=0x" + Convert.ToString( s_hwave_out.ToInt32(), 16 ) );
#endif
            int num_files = files.Length;
            reset();
            s_wave_read_offset_samples = wave_read_offset_samples;
            for ( int k = 0; k < _NUM_BUF; k++ ) {
                s_wave_header[k].dwUser = new IntPtr( k );
                s_done[k] = true;
            }
            s_abort_required = false;
            s_buffer_loc = 0;
            s_current_buffer = 0;
            s_processed_count = 0;
            s_playing = true;
            s_last_buffer = -1;

            if ( (int)s_ptr_wave_buffer_l.ToPointer() == 0 ) {
                s_ptr_wave_buffer_l = Marshal.AllocHGlobal( sizeof( float ) * s_block_size );//        s_wave_buffer_l = new float[s_block_size];
                s_wave_buffer_l = (float*)s_ptr_wave_buffer_l.ToPointer();
            }
            if ( (int)s_ptr_wave_buffer_r.ToPointer() == 0 ) {
                s_ptr_wave_buffer_r = Marshal.AllocHGlobal( sizeof( float ) * s_block_size ); //s_wave_buffer_r = new float[s_block_size];
                s_wave_buffer_r = (float*)s_ptr_wave_buffer_r.ToPointer();
            }

            if ( s_wave_reader != null ) {
                for ( int i = 0; i < s_num_wave_reader; i++ ) {
                    s_wave_reader[i].Close();
                }
                //delete [] s_wave_reader;
            }
            s_wave_reader = new WaveReader[num_files];

            if ( s_another_wave_l != null ) {
                for ( int i = 0; i < s_num_wave_reader; i++ ) {
                    Marshal.FreeHGlobal( s_ptr_another_wave_l[i] );// delete [] s_another_wave_l[i];
                }
                //delete [] s_another_wave_l;
            }
            if ( s_another_wave_r != null ) {
                for ( int i = 0; i < s_num_wave_reader; i++ ) {
                    Marshal.FreeHGlobal( s_ptr_another_wave_r[i] ); //delete [] s_another_wave_r[i];
                }
                //delete [] s_another_wave_r;
            }
            s_another_wave_l = new float*[num_files];
            s_another_wave_r = new float*[num_files];
            int max_samples = 0;
            for ( int i = 0; i < num_files; i++ ) {
                // waveファイルヘッダを読込む
                /*int len = files[i].Length;
                wchar_t *name = new wchar_t[len + 1];
                array<wchar_t> ^buf = files[i]->ToCharArray();
                for( int k = 0; k < len; k++ ){
                    name[k] = buf[k];
                }
                name[len] = '\0';*/
                s_wave_reader[i].Open( files[i] );
                int samples = s_wave_reader[i].TotalSamples;
                if ( samples > max_samples ) {
                    max_samples = samples;
                }
                //delete [] name;

                // バッファを用意
                s_ptr_another_wave_l[i] = Marshal.AllocHGlobal( sizeof( float ) * s_block_size );
                s_another_wave_l[i] = (float*)s_ptr_another_wave_l[i].ToPointer();
                s_ptr_another_wave_r[i] = Marshal.AllocHGlobal( sizeof( float ) * s_block_size );
                s_another_wave_r[i] = (float*)s_ptr_another_wave_r[i].ToPointer();
                //s_another_wave_l[i] = new float[s_block_size];
                //s_another_wave_r[i] = new float[s_block_size];
            }
            s_num_wave_reader = num_files;
            return max_samples;
        }

        public void set_error_samples( int error_samples ) {
            s_error_samples = error_samples;
        }

        /// コールバック関数を設定する
        public void set_first_buffer_written_callback( FirstBufferWrittenCallback proc ) {
            s_first_buffer_written_callback = proc;
        }

        public void terminate() {
            if ( s_hwave_out.ToInt32() != 0 ) {
                windows.waveOutReset( s_hwave_out );
#if DEBUG
                bocoree.debug.push_log( "waveplay::terminate; waveOutReset" );
#endif
                for ( int k = 0; k < _NUM_BUF; k++ ) {
                    windows.waveOutUnprepareHeader( s_hwave_out, ref s_wave_header[k], (uint)sizeof( WAVEHDR ) );
                }
                windows.waveOutClose( s_hwave_out );
            }
            for ( int i = 0; i < _NUM_BUF; i++ ) {
                if ( s_ptr_wave[i].ToInt32() != 0 ) {
                    Marshal.FreeHGlobal( s_ptr_wave[i] ); //delete [] s_wave[i];
                }
            }
        }

        /// 現在再生中かどうかを取得する
        public bool is_alive() {
            return s_playing;
        }

        /// ブロックサイズを変更します
        public bool change_block_size( int block_size ) {
            if ( s_playing ) {
                return false;
            }
            if ( block_size <= 0 ) {
                return false;
            }

            for ( int k = 0; k < _NUM_BUF; k++ ) {
                if ( s_ptr_wave[k].ToInt32() != 0 ) {
                    Marshal.FreeHGlobal( s_ptr_wave[k] );// delete [] s_wave[k];
                }
                s_ptr_wave[k] = Marshal.AllocHGlobal( sizeof( uint ) * block_size );
                s_wave[k] = (uint*)s_ptr_wave[k].ToPointer();// calloc( sizeof( ulong ), block_size );
                s_wave_header[k].lpData = s_ptr_wave[k];
                s_wave_header[k].dwBufferLength = (uint)(sizeof( uint ) * block_size);
            }

            // s_wave_buffer_l, s_wave_buffer_rは、NULLならばon_your_markで初期化されるので、開放だけやっておけばOK
            if ( s_ptr_wave_buffer_l.ToInt32() != 0 ) {
                Marshal.FreeHGlobal( s_ptr_wave_buffer_l ); //delete [] s_wave_buffer_l;
            }
            if ( s_ptr_wave_buffer_r.ToInt32() != 0 ) {
                Marshal.FreeHGlobal( s_ptr_wave_buffer_r ); //delete[] s_wave_buffer_r;
            }
            // s_another_wave_l, s_another_wave_rは、on_your_markで全自動で初期化されるので特に操作の必要なし
            s_block_size = block_size;
            return true;
        }

        uint MAKELONG( ushort a, ushort b ) {
            return (uint)(a & 0xffff) | (uint)((b & 0xffff) << 16);
        }
    }

}
