/* * 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 } /// /// Parameters of first formanto detection algorithm /// public class FormantoDetectionArguments { private double m_peak_detection_threashold = 0.2; private double m_moving_average_width = 25.0; /// /// ピークを検出する閾値。フーリエ変換後の周波数の強度分布関数の値がこの値よりも大きい箇所で /// 密度関数の導関数の符号が正から負へ変わる位置をピークとみなす。周波数密度関数は、最大値で /// 規格化したのを使う。従って、0~1の値を指定する。 /// public double PeakDetectionThreshold { get { return m_peak_detection_threashold; } set { m_peak_detection_threashold = value; } } /// /// フーリエ変換した生の周波数の強度分布を平滑化する幅を2で割った値。Hzで指定。 /// 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() ); } /// /// 第timeサンプルにおけるフォルマントを取得する /// /// /// /// 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 peak_positions = new List(); 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 peaks = new List(); 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 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; }*/ /// /// 第timeサンプルにおけるフォルマントを取得する /// /// /// /// 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; } /// /// 指定したサンプル位置における音量を計算します /// /// /// /// /// 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; } /// /// 音量の時間変化を取得します /// /// 窓の幅(サンプル数) /// 使用する窓関数 /// 解像度(サンプル数) /// 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 ); } /// /// -1から1までに規格化された波形を取得します /// /// 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(); } /// /// サンプルあたりのビット数を8に変更する /// 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; } /// /// ファイルに保存 /// /// 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 ); } } } /// /// ステレオをモノラル化 /// 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; } } /// /// 前後の無音部分を削除します /// 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( 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( ref L16, count ); Array.Copy( buf, 0, L16, 0, count ); Array.Copy( R16, non_silence_begin, buf, 0, count ); Array.Resize( 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]); } } }