/*
 * WaveReader.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.IO;
using System.Runtime.InteropServices;

namespace Boare.Lib.Media {

    public class WaveReader : IDisposable{
        private int m_channel;
        private int m_byte_per_sample;
        private bool m_opened;
        private FileStream m_stream;
        private int m_total_samples;

        public WaveReader() {
            m_opened = false;
        }

        public WaveReader( string file ) {
            Open( file );
        }

        public void Dispose() {
            Close();
        }

        public bool Open( string file ) {
            if ( m_opened ) {
                m_stream.Close();
            }
            m_stream = new FileStream( file, FileMode.Open, FileAccess.Read );

            // RIFF
            byte[] buf = new byte[4];
            m_stream.Read( buf, 0, 4 );
            if ( buf[0] != 'R' || buf[1] != 'I' || buf[2] != 'F' || buf[3] != 'F' ) {
                m_stream.Close();
                return false;
            }

            // ファイルサイズ - 8最後に記入
            m_stream.Read( buf, 0, 4 );

            // WAVE
            m_stream.Read( buf, 0, 4 );
            if ( buf[0] != 'W' || buf[1] != 'A' || buf[2] != 'V' || buf[3] != 'E' ) {
                m_stream.Close();
                return false;
            }

            // fmt 
            m_stream.Read( buf, 0, 4 );
            if ( buf[0] != 'f' || buf[1] != 'm' || buf[2] != 't' || buf[3] != ' ' ) {
                m_stream.Close();
                return false;
            }

            // fmt チャンクのサイズ
            m_stream.Read( buf, 0, 4 );

            // format ID
            m_stream.Read( buf, 0, 2 );

            // チャンネル数
            m_stream.Read( buf, 0, 2 );
            m_channel = buf[1] << 8 | buf[0];

            // サンプリングレート
            m_stream.Read( buf, 0, 4 );

            // データ速度
            m_stream.Read( buf, 0, 4 );

            // ブロックサイズ
            m_stream.Read( buf, 0, 2 );

            // サンプルあたりのビット数
            m_stream.Read( buf, 0, 2 );
            int bit_per_sample = buf[1] << 8 | buf[0];
            m_byte_per_sample = bit_per_sample / 8;

            // 拡張部分
            m_stream.Read( buf, 0, 2 );

            // data
            m_stream.Read( buf, 0, 4 );
            if ( buf[0] != 'd' || buf[1] != 'a' || buf[2] != 't' || buf[3] != 'a' ) {
                m_stream.Close();
                return false;
            }

            // size of data chunk
            m_stream.Read( buf, 0, 4 );
            int size = BitConverter.ToInt32( buf, 0 );
            m_total_samples = size / (m_channel * m_byte_per_sample);

            m_opened = true;
            return true;
        }

        public int TotalSamples {
            get {
                return m_total_samples;
            }
        }

        public void Read( long start, int length, out double[] left, out double[] right ) {
            left = new double[length];
            right = new double[length];
            if ( !m_opened ) {
                return;
            }
            long loc = 0x2e + m_byte_per_sample * m_channel * start;
            m_stream.Seek( loc, SeekOrigin.Begin );

            if ( m_byte_per_sample == 2 ) {
                if ( m_channel == 2 ) {
                    byte[] buf = new byte[4];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 4 );
                        if ( ret < 4 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        short l = (short)(buf[0] | buf[1] << 8);
                        short r = (short)(buf[2] | buf[3] << 8);
                        left[i] = l / 32768.0f;
                        right[i] = r / 32768.0f;
                    }
                } else {
                    byte[] buf = new byte[2];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 2 );
                        if ( ret < 2 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        short l = (short)(buf[0] | buf[1] << 8);
                        left[i] = l / 32768.0f;
                        right[i] = left[i];
                    }
                }
            } else {
                if ( m_channel == 2 ) {
                    byte[] buf = new byte[2];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 2 );
                        if ( ret < 2 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        left[i] = (buf[0] - 64.0f) / 64.0f;
                        right[i] = (buf[1] - 64.0f) / 64.0f;
                    }
                } else {
                    byte[] buf = new byte[1];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 1 );
                        if ( ret < 1 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        left[i] = (buf[0] - 64.0f) / 64.0f;
                        right[i] = left[i];
                    }
                }
            }
        }

        public void Read( long start, int length, out float[] left, out float[] right ) {
            left = new float[length];
            right = new float[length];
            if ( !m_opened ) {
                return;
            }
            long loc = 0x2e + m_byte_per_sample * m_channel * start;
            m_stream.Seek( loc, SeekOrigin.Begin );

            if ( m_byte_per_sample == 2 ) {
                if ( m_channel == 2 ) {
                    byte[] buf = new byte[4];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 4 );
                        if ( ret < 4 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        short l = (short)(buf[0] | buf[1] << 8);
                        short r = (short)(buf[2] | buf[3] << 8);
                        left[i] = l / 32768.0f;
                        right[i] = r / 32768.0f;
                    }
                } else {
                    byte[] buf = new byte[2];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 2 );
                        if ( ret < 2 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        short l = (short)(buf[0] | buf[1] << 8);
                        left[i] = l / 32768.0f;
                        right[i] = left[i];
                    }
                }
            } else {
                if ( m_channel == 2 ) {
                    byte[] buf = new byte[2];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 2 );
                        if ( ret < 2 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        left[i] = (buf[0] - 64.0f) / 64.0f;
                        right[i] = (buf[1] - 64.0f) / 64.0f;
                    }
                } else {
                    byte[] buf = new byte[1];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 1 );
                        if ( ret < 1 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        left[i] = (buf[0] - 64.0f) / 64.0f;
                        right[i] = left[i];
                    }
                }
            }
        }

        public unsafe void Read( long start, int length, out IntPtr ptr_left, out IntPtr ptr_right ) {
            ptr_left = Marshal.AllocHGlobal( sizeof( float ) * length );
            ptr_right = Marshal.AllocHGlobal( sizeof( float ) * length );
            float* left = (float*)ptr_left.ToPointer();
            float* right = (float*)ptr_right.ToPointer();
            if ( !m_opened ) {
                return;
            }
            long loc = 0x2e + m_byte_per_sample * m_channel * start;
            m_stream.Seek( loc, SeekOrigin.Begin );

            if ( m_byte_per_sample == 2 ) {
                if ( m_channel == 2 ) {
                    byte[] buf = new byte[4];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 4 );
                        if ( ret < 4 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        short l = (short)(buf[0] | buf[1] << 8);
                        short r = (short)(buf[2] | buf[3] << 8);
                        left[i] = l / 32768.0f;
                        right[i] = r / 32768.0f;
                    }
                } else {
                    byte[] buf = new byte[2];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 2 );
                        if ( ret < 2 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        short l = (short)(buf[0] | buf[1] << 8);
                        left[i] = l / 32768.0f;
                        right[i] = left[i];
                    }
                }
            } else {
                if ( m_channel == 2 ) {
                    byte[] buf = new byte[2];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 2 );
                        if ( ret < 2 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        left[i] = (buf[0] - 64.0f) / 64.0f;
                        right[i] = (buf[1] - 64.0f) / 64.0f;
                    }
                } else {
                    byte[] buf = new byte[1];
                    for ( int i = 0; i < length; i++ ) {
                        int ret = m_stream.Read( buf, 0, 1 );
                        if ( ret < 1 ) {
                            for ( int j = i; j < length; j++ ) {
                                left[j] = 0.0f;
                                right[j] = 0.0f;
                            }
                            break;
                        }
                        left[i] = (buf[0] - 64.0f) / 64.0f;
                        right[i] = left[i];
                    }
                }
            }
        }

        public void Close() {
            m_opened = false;
            if ( m_stream != null ) {
                m_stream.Close();
                m_stream = null;
            }
        }

        ~WaveReader() {
            Close();
        }
    }

}
