/// <summary>
        /// Save the all-in-one cmp file.
        /// </summary>
        /// <param name="savingEnv">Working environment.</param>
        /// <param name="cmpSetInfo">Information of the cmp set.</param>
        private static void SaveAllInOneFile(SavingEnv savingEnv, CmpSetInfo cmpSetInfo)
        {
            FileStream stream = File.Open(savingEnv.ResultCmpFile, FileMode.Create);
            try
            {
                using (BinaryWriter writer = new BinaryWriter(stream))
                {
                    stream = null;
                    writer.Write(cmpSetInfo.SamplePeriod);
                    writer.Write(cmpSetInfo.RecordSize);
                    writer.Write(cmpSetInfo.LspOrder);
                    writer.Write(cmpSetInfo.MbeOrder);
                    writer.Write(cmpSetInfo.PowerOrder);
                    writer.Write(cmpSetInfo.GuidanceLspOrder);
                    writer.Write(cmpSetInfo.SentenceCount);

                    for (int i = 0; i < savingEnv.CmpInfoDict.Count; i++)
                    {
                        SingleCmpInfo singleCmpInfo = savingEnv.CmpInfoDict.Values.ElementAt(i);

                        writer.Write(singleCmpInfo.SentID);
                        writer.Write(singleCmpInfo.SampleCount);

                        // temporarily use indexIntoFile to denotes the position of itself in file
                        singleCmpInfo.IndexIntoFile = (int)writer.BaseStream.Position;
                        writer.Write(singleCmpInfo.IndexIntoFile);

                        savingEnv.CmpInfoDict[savingEnv.CmpInfoDict.Keys.ElementAt(i)] = singleCmpInfo;
                    }

                    int offset = (int)writer.BaseStream.Position;

                    foreach (KeyValuePair<string, SingleCmpInfo> kvp in savingEnv.CmpInfoDict)
                    {
                        SingleCmpInfo singleCmpInfo = kvp.Value;

                        writer.Seek(singleCmpInfo.IndexIntoFile, SeekOrigin.Begin);
                        singleCmpInfo.IndexIntoFile = offset;
                        writer.Write(singleCmpInfo.IndexIntoFile);

                        offset += cmpSetInfo.RecordSize * singleCmpInfo.SampleCount;
                    }

                    writer.Seek(0, SeekOrigin.End);

                    const int SingleCmpHeadSize = 12;

                    for (int i = 0; i < savingEnv.SentPaths.Count; i++)
                    {
                        string sentID = savingEnv.SentIDs[i];
                        string partialPath = savingEnv.SentPaths[i];

                        FileStream file = File.Open(FormCmpFullPath(savingEnv.WorkingPath, partialPath), FileMode.Open);
                        try
                        {
                            using (BinaryReader reader = new BinaryReader(file))
                            {
                                file = null;
                                reader.BaseStream.Seek(SingleCmpHeadSize, SeekOrigin.Begin);

                                CopyStreamData(reader, writer);
                            }
                        }
                        finally
                        {
                            if (null != file)
                            {
                                file.Dispose();
                            }
                        }
                    }
                }
            }
            finally
            {
                if (null != stream)
                {
                    stream.Dispose();
                }
            }
        }
        /// <summary>
        /// Get information of all the single cmp files.
        /// </summary>
        /// <param name="savingEnv">Working environment.</param>
        private static void GetCmpInfoDict(SavingEnv savingEnv)
        {
            for (int i = 0; i < savingEnv.SentPaths.Count; i++)
            {
                string sentID = savingEnv.SentIDs[i];
                string partialPath = savingEnv.SentPaths[i];

                FileStream file = File.Open(FormCmpFullPath(savingEnv.WorkingPath, partialPath), FileMode.Open);
                try
                {
                    using (BinaryReader reader = new BinaryReader(file))
                    {
                        file = null;
                        int sampleCount = reader.ReadInt32();   // number of samples in file

                        SingleCmpInfo singleCmpInfo = new SingleCmpInfo();
                        singleCmpInfo.SentID = sentID;
                        singleCmpInfo.SampleCount = sampleCount;

                        savingEnv.CmpInfoDict.Add(sentID, singleCmpInfo);
                    }
                }
                finally
                {
                    if (null != file)
                    {
                        file.Dispose();
                    }
                }
            }
        }
        /// <summary>
        /// Parse sentence IDs and paths.
        /// </summary>
        /// <param name="savingEnv">Working environment.</param>
        private static void ParseSentPaths(SavingEnv savingEnv)
        {
            using (StreamReader reader = new StreamReader(savingEnv.FileMapName))
            {
                while (!reader.EndOfStream)
                {
                    string line = reader.ReadLine();
                    string[] tokens = line.Split(new char[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

                    if (tokens.Length == 2)
                    {
                        string id = tokens[0];
                        string path = tokens[1];
                        if (System.IO.File.Exists(FormCmpFullPath(savingEnv.WorkingPath, path)))
                        {
                            savingEnv.SentIDs.Add(id);
                            savingEnv.SentPaths.Add(path);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Get general information of the cmp set.
        /// </summary>
        /// <param name="savingEnv">Working environment.</param>
        /// <param name="cmpSetInfo">Information of the cmp set.</param>
        private static void GetCmpSetInfo(SavingEnv savingEnv, ref CmpSetInfo cmpSetInfo)
        {
            cmpSetInfo.SentenceCount = savingEnv.SentIDs.Count;

            if (cmpSetInfo.SentenceCount > 0)
            {
                FileStream file = File.Open(FormCmpFullPath(savingEnv.WorkingPath, savingEnv.SentPaths[0]), FileMode.Open);
                try
                {
                    using (BinaryReader reader = new BinaryReader(file))
                    {
                        file = null;
                        reader.BaseStream.Seek(sizeof(int), SeekOrigin.Begin);    // number of samples in file

                        int samplePeriod = reader.ReadInt32();        // sample period in 100ns units
                        cmpSetInfo.SamplePeriod = samplePeriod / 10000;  // make it in milliseconds

                        short sampleSize = reader.ReadInt16();  // number of bytes per sample
                        cmpSetInfo.RecordSize = sampleSize;

                        int gainAndF0Order = 2;
                        int mbeOrder = cmpSetInfo.MbeOrder;
                        int powerOrder = cmpSetInfo.PowerOrder;
                        int guidanceLspOrder = cmpSetInfo.GuidanceLspOrder;
                        int placeHolder = gainAndF0Order + mbeOrder + powerOrder + guidanceLspOrder;

                        cmpSetInfo.LspOrder = (sampleSize / (3 * sizeof(float))) - placeHolder;
                    }
                }
                finally
                {
                    if (null != file)
                    {
                        file.Dispose();
                    }
                }
            }
        }
        /// <summary>
        /// Compile an all-in-one cmp set file from a set of separate cmp files.
        /// </summary>
        /// <param name="workingPath">Working path.</param>
        /// <param name="fileMapName">File map name.</param>
        /// <param name="resultCmpFile">Name of the all-in-one cmp file.</param>
        /// <param name="cmpSetInfo">Information of the cmp set.</param>
        /// <returns>Success or failure.</returns>
        public static bool CompileCmpFiles(string workingPath, string fileMapName,
            string resultCmpFile, ref CmpSetInfo cmpSetInfo)
        {
            SavingEnv savingEnv = new SavingEnv();
            savingEnv.WorkingPath = workingPath;
            savingEnv.FileMapName = fileMapName;
            savingEnv.ResultCmpFile = resultCmpFile;

            ParseSentPaths(savingEnv);

            GetCmpSetInfo(savingEnv, ref cmpSetInfo);

            GetCmpInfoDict(savingEnv);

            SaveAllInOneFile(savingEnv, cmpSetInfo);

            return true;
        }