/*
 * Wave.cs
 * Copyright (c) 2008-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.IO;
using System.Collections.Generic;

using bocoree;

namespace Boare.Lib.Media {

    public enum Channel {
        Right,
        Left,
    }

    public enum WaveChannel : int {
        Monoral = 1,
        Stereo = 2
    }

    /// <summary>
    /// Parameters of first formanto detection algorithm
    /// </summary>
    public class FormantoDetectionArguments {
        private double m_peak_detection_threashold = 0.2;
        private double m_moving_average_width = 25.0;

        /// <summary>
        /// ピークを検出する閾値。フーリエ変換後の周波数の強度分布関数の値がこの値よりも大きい箇所で
        /// 密度関数の導関数の符号が正から負へ変わる位置をピークとみなす。周波数密度関数は、最大値で
        /// 規格化したのを使う。従って、0～1の値を指定する。
        /// </summary>
        public double PeakDetectionThreshold {
            get {
                return m_peak_detection_threashold;
            }
            set {
                m_peak_detection_threashold = value;
            }
        }

        /// <summary>
        /// フーリエ変換した生の周波数の強度分布を平滑化する幅を2で割った値。Hzで指定。
        /// </summary>
        public double MovingAverageWidth {
            get {
                return m_moving_average_width;
            }
            set {
                m_moving_average_width = value;
            }
        }
    }

    public class Wave : IDisposable {
        private WaveChannel m_channel;
        private ushort m_bit_per_sample;
        private uint m_sample_rate;
        private uint m_total_samples;
        private byte[] L8;
        private byte[] R8;
        private short[] L16;
        private short[] R16;

        //public void 
#if DEBUG
        private static bool s_test = false;
        public static bool TestEnabled {
            get {
                return s_test;
            }
            set {
                s_test = value;
            }
        }
#endif
        public unsafe double TEST_GetF0( uint time, double[] window_function ) {
            int window_size = window_function.Length;
            double[] formanto = GetFormanto( time, window_function );
#if DEBUG
            if ( s_test ) {
                using ( StreamWriter sw = new StreamWriter( @"C:\TEST_GetF0_formanto.txt" ) ) {
                    for ( int i = 0; i < formanto.Length; i++ ) {
                        sw.WriteLine( Math.Abs( formanto[i] ) );
                    }
                }
            }
#endif
            int size = formanto.Length;
            double[] wv = new double[size + 1];
            for ( int i = 0; i < size; i++ ) {
                wv[i] = formanto[i];
            }
            int nmaxsqrt = (int)Math.Sqrt( size );
            int[] ip_ = new int[nmaxsqrt + 2];
            double[] w_ = new double[size * 5 / 4];
            ip_[0] = 0;

            fixed ( int* ip = &ip_[0] )
            fixed ( double* w = &w_[0] )
            fixed ( double* a = &wv[0] ) {
                fft.rdft( size, 1, a, ip, w );
            }
#if DEBUG
            if ( s_test ) {
                using ( StreamWriter sw = new StreamWriter( @"C:\TEST_GetF0_fft_formanto.txt" ) ) {
                    for ( int i = 0; i < wv.Length; i++ ) {
                        sw.WriteLine( Math.Abs( wv[i] ) );
                    }
                }
            }
#endif
            return 0;
        }

        public double GetF0( uint time, double[] window_function ) {
            return GetF0( time, window_function, new FormantoDetectionArguments() );
        }

        /// <summary>
        /// 第timeサンプルにおけるフォルマントを取得する
        /// </summary>
        /// <param name="time"></param>
        /// <param name="window_function"></param>
        /// <returns></returns>
        public double GetF0(  uint time, double[] window_function, FormantoDetectionArguments argument ) {
            int window_size = window_function.Length;
            double[] formanto = GetFormanto( time, window_function );
            int length = formanto.Length;
            double max = 0.0;
            for ( int i = 0; i < length; i++ ) {
                formanto[i] = Math.Abs( formanto[i] );
                max = Math.Max( max, formanto[i] );
            }
            double inv_max = 1.0 / max;
            for ( int i = 0; i < length; i++ ) {
                formanto[i] = formanto[i] * inv_max;
            }

            double hz_from_index = 1.0 / (double)window_size * m_sample_rate * 0.5;
#if DEBUG
            Console.WriteLine( "Wave+GetF0" );
            if ( s_test ) {
                using ( StreamWriter sw = new StreamWriter( "formanto.txt" ) ) {
                    for ( int i = 0; i < length; i++ ) {
                        sw.WriteLine( (i * hz_from_index) + "\t" + formanto[i] );
                    }
                }
            }
#endif
            // 移動平均を計算
            double ma_width = argument.MovingAverageWidth; // 移動平均を取るHzを2で割ったもの
            int ma_width_sample = (int)(window_size * 2 * ma_width / (double)m_sample_rate);
            if ( ma_width_sample < 1 ) {
                ma_width_sample = 1;
            }
#if DEBUG
            Console.WriteLine( "ma_width_sample=" + ma_width_sample );
#endif
            double[] ma = new double[length];
            for ( int i = 0; i < length; i++ ) {
                int count = 0;
                double sum = 0.0;
                for ( int j = i - ma_width_sample; j < i + 2 * ma_width_sample; j++ ) {
                    if ( 0 <= j && j < length ) {
                        sum += formanto[j];
                        count++;
                    }
                }
                ma[i] = sum / (double)count;
            }
#if DEBUG
            if ( s_test ) {
                using ( StreamWriter sw = new StreamWriter( "ma.txt" ) ) {
                    for ( int i = 0; i < length; i++ ) {
                        sw.WriteLine( (i * hz_from_index) + "\t" + ma[i] );
                    }
                }
            }
#endif
            // ピークの位置を探す
            double threshold = argument.PeakDetectionThreshold;
            double last_slope = 0.0;
            int index = -1;
            List<double> peak_positions = new List<double>();
            bool first = true;
            for ( int i = 1; i < window_size - 1; i++ ) {
                double slope = ma[i + 1] - ma[i - 1];
                if ( ma[i] > threshold && slope <= 0.0 && 0.0 < last_slope ) {
                    if ( first ) {
                        index = i;
                        first = false;
                    }
                    //break;
                    peak_positions.Add( i * hz_from_index );
                }
                last_slope = slope;
            }
#if DEBUG
            if ( s_test ) {
                using ( StreamWriter sw = new StreamWriter( "peak_positions.txt" ) ) {
                    for ( int i = 0; i < peak_positions.Count; i++ ) {
                        sw.WriteLine( peak_positions[i].ToString() );
                    }
                }
            }
#endif
            if ( index > 0 ) {
                List<double> peaks = new List<double>();
                double peak_distance_tolerance = index * hz_from_index / 5.0; //最小の周波数の5分の1
                double last_peak_pos = index * hz_from_index;
                peaks.Add( last_peak_pos );
                for ( int i = 1; i < peak_positions.Count; i++ ) {
                    if ( peak_positions[i] - last_peak_pos > peak_distance_tolerance ) {
                        peaks.Add( peak_positions[i] );
                        last_peak_pos = peak_positions[i];
                    }
                }
#if DEBUG
                if ( s_test ) {
                    using ( StreamWriter sw = new StreamWriter( "peaks.txt" ) ) {
                        for ( int i = 0; i < peaks.Count; i++ ) {
                            sw.WriteLine( peaks[i] );
                        }
                    }
                }
#endif
                double min_peak_distance = index * hz_from_index * 2.0 / 3.0;
                /*for ( int i = 1; i < peaks.Count; i++ ) {
                    min_peak_distance = Math.Min( min_peak_distance, peaks[i] - peaks[i - 1] );
                }*/
#if DEBUG
                Console.WriteLine( "min_peak_distance=" + min_peak_distance );
                if ( s_test ) {
                    using ( StreamWriter sw = new StreamWriter( "evaluation.txt" ) ) {
                        for ( int i = (int)min_peak_distance; i < (int)(4 * min_peak_distance); i++ ) {
                            sw.WriteLine( i + "\t" + GetFormantoGetEvaluationValue( peaks, i ) );
                        }
                    }
                }
#endif
                int smallest = (int)min_peak_distance;
                double min_eval = GetFormantoGetEvaluationValue( peaks, smallest );
                for ( int i = (int)min_peak_distance; i < (int)(4 * min_peak_distance); i++ ) {
                    double eval = GetFormantoGetEvaluationValue( peaks, i );
                    if ( min_eval > eval ) {
                        min_eval = eval;
                        smallest = i;
                    }
                }
                return smallest;
            } else {
                return 0;
            }
        }

        private static double GetFormantoGetEvaluationValue( List<double> peaks, double t ) {
            double ret = 0.0;
            for ( int i = 0; i < peaks.Count; i++ ) {
                int n_i = (int)(peaks[i] / t + 0.5);
                double dt_i = peaks[i] - n_i * t;
                ret += Math.Abs( dt_i );
            }
            return ret / t;
        }

        /*public unsafe double[,] GetFormanto( uint time, uint count, double[] window_function ) {
            int size = window_function.Length;
            double[] buffer = new double[size];    // 波形のバッファ
            int nmaxsqrt = (int)Math.Sqrt( size );
            int[] ip_ = new int[nmaxsqrt + 2];
            double[] w_ = new double[size * 5 / 4];
            ip_[0] = 0;

            double[,] ret = new double[count, size];

            for ( uint j = time; j < time + count; j++ ) {
                GetNormalizedWave( (uint)(time - size / 2), ref buffer );
                // 窓関数をかける
                for ( int i = 0; i < size; i++ ) {
                    buffer[i] *= window_function[i];
                }

                fixed ( int* ip = &ip_[0] )
                fixed ( double* w = &w_[0] )
                fixed ( double* a = &buffer[0] ) {
                    fft.rdft( size, 1, a, ip, w );
                }
                for ( int i = 0; i < size; i++ ) {
                    ret[j - time, i] = buffer[i];
                }
            }
            return ret;
        }*/

        /// <summary>
        /// 第timeサンプルにおけるフォルマントを取得する
        /// </summary>
        /// <param name="time"></param>
        /// <param name="window_function"></param>
        /// <returns></returns>
        public unsafe double[] GetFormanto( uint time, double[] window_function ) {
            int size = window_function.Length;
            double[] wv = GetNormalizedWave( (int)(time - size / 2), (uint)(size + 1) );
            // 窓関数をかける
            for ( int i = 0; i < size; i++ ) {
                wv[i] = wv[i] * window_function[i];
            }
            int nmaxsqrt = (int)Math.Sqrt( size );
            int[] ip_ = new int[nmaxsqrt + 2];
            double[] w_ = new double[size * 5 / 4];
            ip_[0] = 0;

            fixed ( int* ip = &ip_[0] )
            fixed ( double* w = &w_[0] )
            fixed ( double* a = &wv[0] ) {
                fft.rdft( size, 1, a, ip, w );
            }
            return wv;
        }


        public double GetVolume( int start_sample, double[] window_function ) {
            int window_size = window_function.Length;
            double[] conv = new double[window_size];
            GetNormalizedWave( start_sample - window_size / 2, ref conv );
            double ret = 0.0;
            for ( int i = 0; i < window_size; i++ ) {
                ret += Math.Abs( conv[i] ) * window_function[i];
            }
            return ret / (double)window_size;
        }


        /// <summary>
        /// 指定したサンプル位置における音量を計算します
        /// </summary>
        /// <param name="start_sample"></param>
        /// <param name="window_size"></param>
        /// <param name="window_function_type"></param>
        /// <returns></returns>
        public double GetVolume( int start_sample, int window_size, math.WindowFunctionType window_function_type ) {
            double[] conv = new double[window_size];
            GetNormalizedWave( start_sample - window_size / 2, ref conv );
            double ret = 0.0;
            for ( int i = 0; i < window_size; i++ ) {
                double x = i / (double)window_size;
                ret += Math.Abs( conv[i] ) * math.window_func( window_function_type, x );
            }
            return ret / (double)window_size;
        }


        /// <summary>
        /// 音量の時間変化を取得します
        /// </summary>
        /// <param name="window_size">窓の幅（サンプル数）</param>
        /// <param name="window_function_type">使用する窓関数</param>
        /// <param name="resulution">解像度（サンプル数）</param>
        /// <returns></returns>
        public double[] GetVolume( int window_size, math.WindowFunctionType window_function_type ) {
            double[] conv = GetNormalizedWave();
            for ( int i = 0; i < conv.Length; i++ ) {
                conv[i] = Math.Abs( conv[i] );
            }
            // 最初に重み関数を取得
            double[] w = new double[window_size];
            for ( int i = 0; i < window_size; i++ ) {
                double x = i / (double)window_size;
                w[i] = math.window_func( window_function_type, x );
            }
            double[] ret = new double[m_total_samples];
            double inv = 1.0 / (double)window_size;
            int ws2 = window_size / 2;
            for ( int i = 0; i < m_total_samples; i++ ) {
                int start0 = i - ws2;
                int start = start0;
                int end = i + ws2;
                double tinv = inv;
                if ( start < 0 ) {
                    start = 0;
                    tinv = 1.0 / (double)(end - start + 1);
                }
                if ( m_total_samples <= end ) {
                    end = (int)m_total_samples - 1;
                    tinv = 1.0 / (double)(end - start + 1);
                }
                ret[i] = 0.0;
                for ( int j = start; j <= end; j++ ) {
                    ret[i] += conv[j] * w[i - start0];
                }
                ret[i] = ret[i] * tinv;
            }
            return ret;
        }


        public void GetNormalizedWave( int start_index, ref double[] conv ) {
            int count = conv.Length;
            int cp_start = start_index;
            int cp_end = start_index + count;
            if ( start_index < 0 ) {
                for ( int i = 0; i < cp_start - start_index; i++ ) {
                    conv[i] = 0.0;
                }
                cp_start = 0;
            }
            if ( m_total_samples <= cp_end ) {
                for ( int i = cp_end - start_index; i < count; i++ ) {
                    conv[i] = 0.0;
                }
                cp_end = (int)m_total_samples - 1;
            }
            if ( m_channel == WaveChannel.Monoral ) {
                if ( m_bit_per_sample == 8 ) {
                    for ( int i = cp_start; i < cp_end; i++ ) {
                        conv[i - start_index] = (L8[i] - 64.0) / 64.0;
                    }
                } else {
                    for (int i = cp_start; i < cp_end; i++ ) {
                        conv[i - start_index] = L16[i] / 32768.0;
                    }
                }
            } else {
                if ( m_bit_per_sample == 8 ) {
                    for (int i = cp_start; i < cp_end; i++ ) {
                        conv[i - start_index] = ((L8[i] + R8[i]) * 0.5 - 64.0) / 64.0;
                    }
                } else {
                    for (int i = cp_start; i < cp_end; i++ ) {
                        conv[i - start_index] = (L16[i] + R16[i]) * 0.5 / 32768.0;
                    }
                }
            }
        }


        public double[] GetNormalizedWave() {
            return GetNormalizedWave( 0, m_total_samples );
        }



        /// <summary>
        /// -1から1までに規格化された波形を取得します
        /// </summary>
        /// <returns></returns>
        public double[] GetNormalizedWave( int start_index, uint count ) {
            double[] conv = new double[count];
            GetNormalizedWave( start_index, ref conv );
            return conv;
        }


        public int this[int index, Channel channel] {
            get {
                if ( m_channel == WaveChannel.Monoral || channel == Channel.Left ) {
                    if ( m_bit_per_sample == 8 ) {
                        return L8[index];
                    } else {
                        return L16[index];
                    }
                } else {
                    if ( m_bit_per_sample == 8 ) {
                        return R8[index];
                    } else {
                        return R16[index];
                    }
                }
            }
            set {
                if ( m_channel == WaveChannel.Monoral || channel == Channel.Left ) {
                    if ( m_bit_per_sample == 8 ) {
                        L8[index] = (byte)value;
                    } else {
                        L16[index] = (short)value;
                    }
                } else {
                    if ( m_bit_per_sample == 8 ) {
                        R8[index] = (byte)value;
                    } else {
                        R16[index] = (short)value;
                    }
                }
            }
        }


        public int this[int index] {
            get {
                if ( m_bit_per_sample == 8 ) {
                    return L8[index];
                } else {
                    return L16[index];
                }
            }
            set {
                if ( m_bit_per_sample == 8 ) {
                    L8[index] = (byte)value;
                    if ( m_channel == WaveChannel.Stereo ) {
                        R8[index] = (byte)value;
                    }
                } else {
                    L16[index] = (short)value;
                    if ( m_channel == WaveChannel.Stereo ) {
                        R16[index] = (short)value;
                    }
                }
            }
        }

        public double Get( int index ) {
            if ( m_channel == WaveChannel.Monoral ) {
                if ( m_bit_per_sample == 8 ) {
                    return (L8[index] - 64.0) / 64.0;
                } else {
                    return L16[index] / 32768.0;
                }
            } else {
                if ( m_bit_per_sample == 8 ) {
                    return ((L8[index] + R8[index]) * 0.5 - 64.0) / 64.0;
                } else {
                    return (L16[index] + R16[index]) * 0.5 / 32768.0;
                }
            }
        }

        public uint SampleRate {
            get {
                return m_sample_rate;
            }
        }


        public uint TotalSamples {
            get {
                return m_total_samples;
            }
            set {
                m_total_samples = value;
                if ( m_channel == WaveChannel.Monoral ) {
                    if ( m_bit_per_sample == 8 ) {
                        if ( L8 == null ) {
                            L8 = new byte[m_total_samples];
                        } else {
                            Array.Resize( ref L8, (int)m_total_samples );
                        }
                    } else {
                        if ( L16 == null ) {
                            L16 = new short[m_total_samples];
                        } else {
                            Array.Resize( ref L16, (int)m_total_samples );
                        }
                    }
                } else {
                    if ( m_bit_per_sample == 8 ) {
                        if ( L8 == null ) {
                            L8 = new byte[m_total_samples];
                            R8 = new byte[m_total_samples];
                        } else {
                            Array.Resize( ref L8, (int)m_total_samples );
                            Array.Resize( ref R8, (int)m_total_samples );
                        }
                    } else {
                        if ( L16 == null ) {
                            L16 = new short[m_total_samples];
                            R16 = new short[m_total_samples];
                        } else {
                            Array.Resize( ref L16, (int)m_total_samples );
                            Array.Resize( ref R16, (int)m_total_samples );
                        }
                    }
                }
            }
        }


        public void Replace( Wave srcWave, uint srcStart, uint destStart, uint count ) {
#if DEBUG
            Console.WriteLine( "Wave.Replace(Wave,uint,uint,uint)" );
#endif
            if ( m_channel != srcWave.m_channel || m_bit_per_sample != srcWave.m_bit_per_sample ) {
                return;
            }
            if ( m_channel == WaveChannel.Monoral ) {
                if ( m_bit_per_sample == 8 ) {
                    if ( L8 == null || srcWave.L8 == null ) {
                        return;
                    }
                } else {
                    if ( L16 == null || srcWave.L16 == null ) {
                        return;
                    }
                }
            } else {
                if ( m_bit_per_sample == 8 ) {
                    if ( L8 == null || R8 == null || srcWave.L8 == null || srcWave.R8 == null ) {
                        return;
                    }
                } else {
                    if ( L16 == null || R16 == null || srcWave.L16 == null || srcWave.R16 == null ) {
                        return;
                    }
                }
            }

            count = (count > srcWave.TotalSamples - srcStart) ? srcWave.TotalSamples - srcStart : count;
            uint new_last_index = destStart + count;
            if ( m_total_samples < new_last_index ) {
                if ( m_channel == WaveChannel.Monoral ) {
                    if ( m_bit_per_sample == 8 ) {
                        Array.Resize( ref L8, (int)new_last_index );
                    } else {
                        Array.Resize( ref L16, (int)new_last_index );
                    }
                } else {
                    if ( m_bit_per_sample == 8 ) {
                        Array.Resize( ref L8, (int)new_last_index );
                        Array.Resize( ref R8, (int)new_last_index );
                    } else {
                        Array.Resize( ref L16, (int)new_last_index );
                        Array.Resize( ref R16, (int)new_last_index );
                    }
                }
                m_total_samples = new_last_index;
            }
            if ( m_channel == WaveChannel.Monoral ) {
                if ( m_bit_per_sample == 8 ) {
                    Array.Copy( srcWave.L8, srcStart, L8, destStart, count );
                } else {
                    Array.Copy( srcWave.L16, srcStart, L16, destStart, count );
                }
            } else {
                if ( m_bit_per_sample == 8 ) {
                    Array.Copy( srcWave.L8, srcStart, L8, destStart, count );
                    Array.Copy( srcWave.R8, srcStart, R8, destStart, count );
                } else {
                    Array.Copy( srcWave.L16, srcStart, L16, destStart, count );
                    Array.Copy( srcWave.R16, srcStart, R16, destStart, count );
                }
            }
        }


        public void Replace( byte[] data, uint start_index ) {
            if ( m_channel != WaveChannel.Monoral || m_bit_per_sample != 8 || L8 == null ) {
                return;
            }
            uint new_last_index = (uint)(start_index + data.Length);
            if ( m_total_samples < new_last_index ) {
                Array.Resize( ref L8, (int)new_last_index );
                m_total_samples = new_last_index;
            }
            for ( int i = 0; i < data.Length; i++ ) {
                L8[i + start_index] = data[i];
            }
        }


        public void Replace( short[] data, uint start_index ) {
            if ( m_channel != WaveChannel.Monoral || m_bit_per_sample != 16 || L16 == null ) {
                return;
            }
            uint new_last_index = (uint)(start_index + data.Length);
            if ( m_total_samples < new_last_index ) {
                Array.Resize( ref L16, (int)new_last_index );
                m_total_samples = new_last_index;
            }
            for ( int i = 0; i < data.Length; i++ ) {
                L16[i + start_index] = data[i];
            }
        }


        public void Replace( byte[] left, byte[] right, uint start_index ) {
            if ( m_channel != WaveChannel.Stereo || m_bit_per_sample != 8 || L8 == null || R8 == null ) {
                return;
            }
            uint new_last_index = (uint)(start_index + left.Length);
            if ( m_total_samples < new_last_index ) {
                Array.Resize( ref L8, (int)new_last_index );
                Array.Resize( ref R8, (int)new_last_index );
                m_total_samples = new_last_index;
            }
            for ( int i = 0; i < left.Length; i++ ) {
                L8[i + start_index] = left[i];
                R8[i + start_index] = right[i];
            }
        }


        public void Replace( short[] left, short[] right, uint start_index ) {
            if ( m_channel != WaveChannel.Stereo || m_bit_per_sample != 16 || L16 == null || R16 == null ) {
                return;
            }
            uint new_last_index = (uint)(start_index + left.Length);
            if ( m_total_samples < new_last_index ) {
                Array.Resize( ref L16, (int)new_last_index );
                Array.Resize( ref R16, (int)new_last_index );
                m_total_samples = new_last_index;
            }
            for ( int i = 0; i < left.Length; i++ ) {
                L16[i + start_index] = left[i];
                R16[i + start_index] = right[i];
            }
        }

        public void Replace( float[] left, float[] right, uint start_index ) {
            uint new_last_index = (uint)(start_index + left.Length);
            if ( m_total_samples < new_last_index ) {
                if ( m_channel == WaveChannel.Monoral ) {
                    if ( m_bit_per_sample == 8 ) {
                        if ( L8 == null ) {
                            return;
                        }
                        Array.Resize( ref L8, (int)new_last_index );
                    } else {
                        if ( L16 == null ) {
                            return;
                        }
                        Array.Resize( ref L16, (int)new_last_index );
                    }
                } else {
                    if ( m_bit_per_sample == 8 ) {
                        if ( L8 == null || R8 == null ) {
                            return;
                        }
                        Array.Resize( ref L8, (int)new_last_index );
                        Array.Resize( ref R8, (int)new_last_index );
                    } else {
                        if ( L16 == null || R16 == null ) {
                            return;
                        }
                        Array.Resize( ref L16, (int)new_last_index );
                        Array.Resize( ref R16, (int)new_last_index );
                    }
                }
                m_total_samples = new_last_index;
            }
            if ( m_channel == WaveChannel.Monoral ) {
                float[] mono = new float[left.Length];
                for( int i = 0; i < left.Length; i++ ){
                    mono[i] = (left[i] + right[i]) / 2f;
                }
                if ( m_bit_per_sample == 8 ) {
                    for ( int i = 0; i < mono.Length; i++ ) {
                        L8[i + start_index] = (byte)((mono[i] + 1.0f) / 2f * 255f);
                    }
                } else {
                    for ( int i = 0; i < mono.Length; i++ ) {
                        L16[i + start_index] = (short)(mono[i] * 32768f);
                    }
                }
            } else {
                if ( m_bit_per_sample == 8 ) {
                    for ( int i = 0; i < left.Length; i++ ) {
                        L8[i + start_index] = (byte)((left[i] + 1.0f) / 2f * 255f);
                        R8[i + start_index] = (byte)((right[i] + 1.0f) / 2f * 255f);
                    }
                } else {
                    for ( int i = 0; i < left.Length; i++ ) {
                        L16[i + start_index] = (short)(left[i] * 32768f);
                        R16[i + start_index] = (short)(right[i] * 32768f);
                    }
                }
            }
        }

        public void PrintToText( string path ) {
            using ( StreamWriter sw = new StreamWriter( path ) ) {
                if ( m_channel == WaveChannel.Monoral ) {
                    if ( m_bit_per_sample == 8 ) {
                        for ( int i = 0; i < m_total_samples; i++ ) {
                            sw.WriteLine( L8[i] );
                        }
                    } else {
                        for ( int i = 0; i < m_total_samples; i++ ) {
                            sw.WriteLine( L16[i] );
                        }
                    }
                } else {
                    if ( m_bit_per_sample == 8 ) {
                        for ( int i = 0; i < m_total_samples; i++ ) {
                            sw.WriteLine( L8[i] + "\t" + R8[i] );
                        }
                    } else {
                        for ( int i = 0; i < m_total_samples; i++ ) {
                            sw.WriteLine( L16[i] + "\t" + R16[i] );
                        }
                    }
                }
            }
        }

        public void Dispose() {
            L8 = null;
            R8 = null;
            L16 = null;
            R16 = null;
            GC.Collect();
        }

        /// <summary>
        /// サンプルあたりのビット数を8に変更する
        /// </summary>
        public void ConvertTo8Bit() {
            if ( m_bit_per_sample == 8 ) {
                return;
            }

            // 先ず左チャンネル
            L8 = new byte[L16.Length];
            for ( int i = 0; i < L16.Length; i++ ) {
                double new_val = (L16[i] + 32768.0) / 65535.0 * 255.0;
                L8[i] = (byte)new_val;
            }
            L16 = null;

            // 存在すれば右チャンネルも
            if ( m_channel == WaveChannel.Stereo ) {
                R8 = new byte[R16.Length];
                for ( int i = 0; i < R16.Length; i++ ) {
                    double new_val = (R16[i] + 32768.0) / 65535.0 * 255.0;
                    R8[i] = (byte)new_val;
                }
                R16 = null;
            }

            m_bit_per_sample = 8;
        }

        /// <summary>
        /// ファイルに保存
        /// </summary>
        /// <param name="file"></param>
        public void Write( string file ) {
            using ( FileStream fs = new FileStream( file, FileMode.Create ) ) {
                // RIFF
                fs.WriteByte( 0x52 );
                fs.WriteByte( 0x49 );
                fs.WriteByte( 0x46 );
                fs.WriteByte( 0x46 );

                // ファイルサイズ - 8最後に記入
                fs.WriteByte( 0x00 );
                fs.WriteByte( 0x00 );
                fs.WriteByte( 0x00 );
                fs.WriteByte( 0x00 );

                // WAVE
                fs.WriteByte( 0x57 );
                fs.WriteByte( 0x41 );
                fs.WriteByte( 0x56 );
                fs.WriteByte( 0x45 );

                // fmt 
                fs.WriteByte( 0x66 );
                fs.WriteByte( 0x6d );
                fs.WriteByte( 0x74 );
                fs.WriteByte( 0x20 );

                // fmt チャンクのサイズ
                fs.WriteByte( 0x12 );
                fs.WriteByte( 0x00 );
                fs.WriteByte( 0x00 );
                fs.WriteByte( 0x00 );

                // format ID
                fs.WriteByte( 0x01 );
                fs.WriteByte( 0x00 );

                // チャンネル数
                if ( m_channel == WaveChannel.Monoral ) {
                    fs.WriteByte( 0x01 );
                    fs.WriteByte( 0x00 );
                } else {
                    fs.WriteByte( 0x02 );
                    fs.WriteByte( 0x00 );
                }

                // サンプリングレート
                byte[] buf = BitConverter.GetBytes( m_sample_rate );
                WriteByteArray( fs, buf, 4 );

                // データ速度
                ushort block_size = (ushort)(m_bit_per_sample / 8 * (int)m_channel);
                uint data_rate = m_sample_rate * block_size;
                buf = BitConverter.GetBytes( data_rate );
                WriteByteArray( fs, buf, 4 );

                // ブロックサイズ
                buf = BitConverter.GetBytes( block_size );
                WriteByteArray( fs, buf, 2 );

                // サンプルあたりのビット数
                buf = BitConverter.GetBytes( m_bit_per_sample );
                WriteByteArray( fs, buf, 2 );

                // 拡張部分
                fs.WriteByte( 0x00 );
                fs.WriteByte( 0x00 );

                // data
                fs.WriteByte( 0x64 );
                fs.WriteByte( 0x61 );
                fs.WriteByte( 0x74 );
                fs.WriteByte( 0x61 );

                // size of data chunk
                uint size = block_size * m_total_samples;
                buf = BitConverter.GetBytes( size );
                WriteByteArray( fs, buf, 4 );

                // 波形データ
                for ( int i = 0; i < m_total_samples; i++ ) {
                    if ( m_bit_per_sample == 8 ) {
                        fs.WriteByte( L8[i] );
                        if ( m_channel == WaveChannel.Stereo ) {
                            fs.WriteByte( R8[i] );
                        }
                    } else {
                        buf = BitConverter.GetBytes( L16[i] );
                        WriteByteArray( fs, buf, 2 );
                        if ( m_channel == WaveChannel.Stereo ) {
                            buf = BitConverter.GetBytes( R16[i] );
                            WriteByteArray( fs, buf, 2 );
                        }
                    }
                }

                // 最後にWAVEチャンクのサイズ
                uint position = (uint)fs.Position;
                fs.Seek( 4, SeekOrigin.Begin );
                buf = BitConverter.GetBytes( position - 8 );
                WriteByteArray( fs, buf, 4 );
            }
        }

        private static void WriteByteArray( FileStream fs, byte[] dat, int limit ) {
            fs.Write( dat, 0, (dat.Length > limit) ? limit : dat.Length );
            if ( dat.Length < limit ) {
                for ( int i = 0; i < limit - dat.Length; i++ ) {
                    fs.WriteByte( 0x00 );
                }
            }
        }

        /// <summary>
        /// ステレオをモノラル化
        /// </summary>
        public void Monoralize() {
            if ( m_channel != WaveChannel.Stereo ) {
                return;
            }
            if ( m_bit_per_sample == 8 ) {
                for ( int i = 0; i < L8.Length; i++ ) {
                    L8[i] = (byte)((L8[i] + R8[i]) / 2);
                }
                R8 = null;
                m_channel = WaveChannel.Monoral;
            } else {
                for ( int i = 0; i < L16.Length; i++ ) {
                    L16[i] = (short)((L16[i] + R16[i]) / 2);
                }
                R16 = null;
                m_channel = WaveChannel.Monoral;
            }
        }


        /// <summary>
        /// 前後の無音部分を削除します
        /// </summary>
        public void TrimSilence() {
#if DEBUG
            Console.WriteLine( "Wave.TrimSilence" );
#endif
            if ( m_bit_per_sample == 8 ) {
                if ( m_channel == WaveChannel.Monoral ) {
                    int non_silence_begin = 1;
                    for ( int i = 1; i < L8.Length; i++ ) {
                        if ( L8[i] != 0x80 ) {
                            non_silence_begin = i;
                            break;
                        }
                    }
                    int non_silence_end = L8.Length - 1;
                    for ( int i = L8.Length - 1; i >= 0; i-- ) {
                        if ( L8[i] != 0x80 ) {
                            non_silence_end = i;
                            break;
                        }
                    }
                    int count = non_silence_end - non_silence_begin + 1;
                    R8 = new byte[count];
                    Array.Copy( L8, non_silence_begin, R8, 0, count );
                    L8 = null;
                    L8 = new byte[count];
                    Array.Copy( R8, L8, count );
                    R8 = null;
                } else {
                    int non_silence_begin = 1;
                    for ( int i = 1; i < L8.Length; i++ ) {
                        if ( L8[i] != 0x80 || R8[i] != 0x80 ) {
                            non_silence_begin = i;
                            break;
                        }
                    }
                    int non_silence_end = L8.Length - 1;
                    for ( int i = L8.Length - 1; i >= 0; i-- ) {
                        if ( L8[i] != 0x80 || R8[i] != 0x80 ) {
                            non_silence_end = i;
                            break;
                        }
                    }
                    int count = non_silence_end - non_silence_begin + 1;
                    byte[] buf = new byte[count];
                    Array.Copy( L8, non_silence_begin, buf, 0, count );
                    L8 = null;
                    L8 = new byte[count];
                    Array.Copy( buf, L8, count );
                    Array.Copy( R8, non_silence_begin, buf, 0, count );
                    R8 = null;
                    R8 = new byte[count];
                    Array.Copy( buf, R8, count );
                    buf = null;
                }
            } else {
                if ( m_channel == WaveChannel.Monoral ) {
                    int non_silence_begin = 1;
                    for ( int i = 1; i < L16.Length; i++ ) {
                        if ( L16[i] != 0 ) {
                            non_silence_begin = i;
                            break;
                        }
                    }
                    int non_silence_end = L16.Length - 1;
                    for ( int i = L16.Length - 1; i >= 0; i-- ) {
                        if ( L16[i] != 0 ) {
                            non_silence_end = i;
                            break;
                        }
                    }
                    int count = non_silence_end - non_silence_begin + 1;
                    R16 = new short[count];
                    Array.Copy( L16, non_silence_begin, R16, 0, count );
                    Array.Resize<short>( ref L16, count );
                    Array.Copy( R16, L16, count );
                    R16 = null;
                } else {
                    int non_silence_begin = 1;
                    for ( int i = 1; i < L16.Length; i++ ) {
                        if ( L16[i] != 0 || R16[i] != 0 ) {
                            non_silence_begin = i;
                            break;
                        }
                    }
                    int non_silence_end = L16.Length - 1;
                    for ( int i = L16.Length - 1; i >= 0; i-- ) {
                        if ( L16[i] != 0 || R16[i] != 0 ) {
                            non_silence_end = i;
                            break;
                        }
                    }
#if DEBUG
                    Console.WriteLine( "    non_silence_beign=" + non_silence_begin );
                    Console.WriteLine( "    non_silence_end=" + non_silence_end );
#endif
                    int count = non_silence_end - non_silence_begin + 1;
                    short[] buf = new short[count];
                    Array.Copy( L16, non_silence_begin, buf, 0, count );
                    Array.Resize<short>( ref L16, count );
                    Array.Copy( buf, 0, L16, 0, count );
                    
                    Array.Copy( R16, non_silence_begin, buf, 0, count );
                    Array.Resize<short>( ref R16, count );
                    Array.Copy( buf, 0, R16, 0, count );
                    buf = null;
                }
            }

            if ( m_bit_per_sample == 8 ) {
                m_total_samples = (uint)L8.Length;
            } else {
                m_total_samples = (uint)L16.Length;
            }
        }

        public Wave() {
            m_channel = WaveChannel.Stereo;
            m_bit_per_sample = 16;
            m_sample_rate = 44100;
        }

        public Wave( WaveChannel channels, ushort bit_per_sample, uint sample_rate, uint initial_capacity ) {
            m_channel = channels;
            m_bit_per_sample = bit_per_sample;
            m_sample_rate = sample_rate;
            m_total_samples = initial_capacity;
            if ( m_bit_per_sample == 8 ) {
                L8 = new byte[m_total_samples];
                if ( m_channel == WaveChannel.Stereo ) {
                    R8 = new byte[m_total_samples];
                }
            } else if ( m_bit_per_sample == 16 ) {
                L16 = new short[m_total_samples];
                if ( m_channel == WaveChannel.Stereo ) {
                    R16 = new short[m_total_samples];
                }
            }
        }

        public void Append( double[] L, double[] R ) {
            int length = Math.Min( L.Length, R.Length );
            int old_length = L16.Length;
            if ( m_bit_per_sample == 16 && m_channel == WaveChannel.Stereo ) {
                Array.Resize( ref L16, (int)(m_total_samples + length) );
                Array.Resize( ref R16, (int)(m_total_samples + length) );
                m_total_samples += (uint)length;
                for ( int i = old_length; i < m_total_samples; i++ ) {
                    L16[i] = (short)(L[i - old_length] * 32768f);
                    R16[i] = (short)(R[i - old_length] * 32768f);
                }
            } //else ... TODO: Wave+Append他のbitpersec, channelのとき
        }

        /*public void Append( short[] L, short[] R ) {
            int length = Math.Min( L.Length, R.Length );
            int old_length = L16.Length;
            if ( m_bit_per_sample == 16 && m_channel == WaveChannel.Stereo ) {
                Array.Resize( ref L16, (int)(m_total_samples + length) );
                Array.Resize( ref R16, (int)(m_total_samples + length) );
                m_total_samples += (uint)length;
                for ( int i = old_length; i < m_total_samples; i++ ) {
                    L16[i] = L[i - old_length];
                    R16[i] = R[i - old_length];
                }
            } //else ... TODO: Wave+Append他のbitpersec, channelのとき
        }*/

        public Wave( string path ) {
            Read( path );
        }


        public bool Read( string path ) {
            FileStream fs = null;
            try {
                fs = new FileStream( path, FileMode.Open );
                // check RIFF header
                byte[] buf = new byte[4];
                fs.Read( buf, 0, 4 );
                if ( buf[0] != 0x52 ||
                    buf[1] != 0x49 ||
                    buf[2] != 0x46 ||
                    buf[3] != 0x46 ) {
#if DEBUG
                    System.Diagnostics.Debug.WriteLine( "invalid header" );
#endif
                    fs.Close();
                    return false;
                }

                // detect size of RIFF chunk
                fs.Read( buf, 0, 4 );
                uint riff_chunk_size = GetUIntFrom4Bytes( buf );
#if DEBUG
                System.Diagnostics.Debug.WriteLine( "riff_chunk_size=" + riff_chunk_size );
#endif

                // check wave header
                fs.Seek( 8, SeekOrigin.Begin );
                fs.Read( buf, 0, 4 );
                if ( buf[0] != 0x57 ||
                    buf[1] != 0x41 ||
                    buf[2] != 0x56 ||
                    buf[3] != 0x45 ) {
#if DEBUG
                    System.Diagnostics.Debug.WriteLine( "invalid wave header" );
#endif
                    fs.Close();
                    return false;
                }

                // check fmt chunk header
                fs.Read( buf, 0, 4 );
                if ( buf[0] != 0x66 ||
                    buf[1] != 0x6d ||
                    buf[2] != 0x74 ||
                    buf[3] != 0x20 ) {
#if DEBUG
                    System.Diagnostics.Debug.WriteLine( "invalid fmt header" );
#endif
                    fs.Close();
                    return false;
                }

                // detect size of fmt chunk
                uint fmt_chunk_bytes;
                fs.Read( buf, 0, 4 );
                fmt_chunk_bytes = GetUIntFrom4Bytes( buf );
#if DEBUG
                System.Diagnostics.Debug.WriteLine( "fmt_chunk_bytes=" + fmt_chunk_bytes );
#endif

                // get format ID
                fs.Read( buf, 0, 2 );
                ushort format_id = GetUShortFrom2Bytes( buf );
                if ( format_id != 1 ) {
                    fs.Close();
                    return false;
                }
#if DEBUG
                System.Diagnostics.Debug.WriteLine( "format_id=" + format_id );
#endif

                // get the number of channel(s)
                fs.Read( buf, 0, 2 );
                ushort num_channels = GetUShortFrom2Bytes( buf );
                if ( num_channels == 1 ) {
                    m_channel = WaveChannel.Monoral;
                } else if ( num_channels == 2 ) {
                    m_channel = WaveChannel.Stereo;
                } else {
                    fs.Close();
                    return false;
                }
#if DEBUG
                System.Diagnostics.Debug.WriteLine( "num_channels=" + num_channels );
#endif

                // get sampling rate
                fs.Read( buf, 0, 4 );
                m_sample_rate = GetUIntFrom4Bytes( buf );
#if DEBUG
                System.Diagnostics.Debug.WriteLine( "m_sample_rate=" + m_sample_rate );
#endif

                // get bit per sample
                fs.Seek( 0x22, SeekOrigin.Begin );
                fs.Read( buf, 0, 2 );
                m_bit_per_sample = GetUShortFrom2Bytes( buf );
#if DEBUG
                System.Diagnostics.Debug.WriteLine( "m_bit_per_sample=" + m_bit_per_sample );
#endif
                if ( m_bit_per_sample != 0x08 && m_bit_per_sample != 0x10 ) {
                    fs.Close();
                    return false;
                }

                // move to the end of fmt chunk
                fs.Seek( 0x14 + fmt_chunk_bytes, SeekOrigin.Begin );

                // move to the top of data chunk
                fs.Read( buf, 0, 4 );
                string tag = new string( new char[] { (char)buf[0], (char)buf[1], (char)buf[2], (char)buf[3] } );
#if DEBUG
                System.Diagnostics.Debug.WriteLine( "tag=" + tag );
#endif
                while ( tag != "data" ) {
                    fs.Read( buf, 0, 4 );
                    uint size = GetUIntFrom4Bytes( buf );
                    fs.Seek( size, SeekOrigin.Current );
                    fs.Read( buf, 0, 4 );
                    tag = new string( new char[] { (char)buf[0], (char)buf[1], (char)buf[2], (char)buf[3] } );
#if DEBUG
                    System.Diagnostics.Debug.WriteLine( "tag=" + tag );
#endif
                }

                // get size of data chunk
                fs.Read( buf, 0, 4 );
                uint data_chunk_bytes = GetUIntFrom4Bytes( buf );
                m_total_samples = (uint)(data_chunk_bytes / (num_channels * m_bit_per_sample / 8));
#if DEBUG
                System.Diagnostics.Debug.WriteLine( "m_total_samples=" + m_total_samples );
#endif

                // prepare data
                if ( m_bit_per_sample == 8 ) {
                    L8 = new byte[m_total_samples];
                    if ( m_channel == WaveChannel.Stereo ) {
                        R8 = new byte[m_total_samples];
                    }
                } else {
                    L16 = new short[m_total_samples];
                    if ( m_channel == WaveChannel.Stereo ) {
                        R16 = new short[m_total_samples];
                    }
                }

                // read data
                byte[] buf2 = new byte[2];
                for ( int i = 0; i < m_total_samples; i++ ) {
                    if ( m_bit_per_sample == 8 ) {
                        fs.Read( buf, 0, 1 );
                        L8[i] = buf[0];
                        if ( m_channel == WaveChannel.Stereo ) {
                            fs.Read( buf, 0, 1 );
                            R8[i] = buf[0];
                        }
                    } else {
                        fs.Read( buf2, 0, 2 );
                        /*if ( BitConverter.IsLittleEndian ) {
                            byte b = buf2[0];
                            buf2[0] = buf2[1];
                            buf2[1] = b;
                        }*/
                        L16[i] = BitConverter.ToInt16( buf2, 0 );
                        if ( m_channel == WaveChannel.Stereo ) {
                            fs.Read( buf2, 0, 2 );
                            /*if ( BitConverter.IsLittleEndian ) {
                                byte b = buf2[0];
                                buf2[0] = buf2[1];
                                buf2[1] = b;
                            }*/
                            R16[i] = BitConverter.ToInt16( buf2, 0 );
                        }
                    }
                }
            } catch ( Exception ex ) {
#if DEBUG
                System.Diagnostics.Debug.Write( ex.StackTrace );
#endif
            } finally {
                if ( fs != null ) {
                    fs.Close();
                }
            }
            return true;
        }


        private uint GetUIntFrom4Bytes( byte[] dat ) {
            return (uint)(dat[3] << 24) + (uint)(dat[2] << 16) + (uint)(dat[1] << 8) + (uint)dat[0];
        }


        private ushort GetUShortFrom2Bytes( byte[] dat ) {
            return (ushort)((dat[1] << 8) | dat[0]);
        }
    }

}
