/// <summary>
        /// Compiler.
        /// </summary>
        /// <param name="phoneSet">Phone Set.</param>
        /// <param name="outputStream">OutputStream.</param>
        /// <returns>ErrorSet.</returns>
        public static ErrorSet Compile(TtsPhoneSet phoneSet, Stream outputStream)
        {
            if (phoneSet == null)
            {
                throw new ArgumentNullException("phoneSet");
            }

            if (outputStream == null)
            {
                throw new ArgumentNullException("outputStream");
            }

            phoneSet.Validate();
            ErrorSet errorSet = phoneSet.ErrorSet;
            if (!errorSet.Contains(ErrorSeverity.MustFix))
            {
                BinaryWriter bw = new BinaryWriter(outputStream);
                {
                    // write the size of the structure for verification purchase
                    bw.Write((uint)Marshal.SizeOf(typeof(PhoneData)));

                    bw.Write((uint)phoneSet.Phones.Count);

                    foreach (Phone phone in phoneSet.Phones)
                    {
                        // Skips those features whose id is greater than uint.MaxValue.
                        uint feature = (uint)phone.FeatureId;
                        PhoneData phoneData = new PhoneData(phone.CompilingName,
                            Convert.ToUInt16(phone.Id),
                            feature);
                        bw.Write(Helper.ToBytes(phoneData));
                    }
                }
            }

            return errorSet;
        }
        /// <summary>
        /// Compiler.
        /// </summary>
        /// <param name="truncRuleFileName">File path of trunc rule.</param>
        /// <param name="phoneSet">Phone set.</param>
        /// <param name="outputStream">Output Stream.</param>
        /// <returns>ErrorSet.</returns>
        public static ErrorSet Compile(string truncRuleFileName,
            TtsPhoneSet phoneSet, Stream outputStream)
        {
            if (string.IsNullOrEmpty(truncRuleFileName))
            {
                throw new ArgumentNullException("truncRuleFileName");
            }

            // pauseLengthFileName could be null
            if (phoneSet == null)
            {
                throw new ArgumentNullException("phoneSet");
            }

            if (outputStream == null)
            {
                throw new ArgumentNullException("outputStream");
            }

            ErrorSet errorSet = new ErrorSet();
            phoneSet.Validate();
            if (phoneSet.ErrorSet.Contains(ErrorSeverity.MustFix))
            {
                errorSet.Add(UnitGeneratorDataCompilerError.InvalidPhoneSet);
            }
            else
            {
                BinaryWriter bw = new BinaryWriter(outputStream);
                {
                    errorSet.Merge(CompTruncRuleData(truncRuleFileName, phoneSet, bw));
                }
            }

            return errorSet;
        }
        /// <summary>
        /// Compiler.
        /// </summary>
        /// <param name="syllabifyRuleFileName">Path of syllabify rule.</param>
        /// <param name="phoneSet">Phone set.</param>
        /// <param name="outputStream">Output Stream.</param>
        /// <returns>ErrorSet.</returns>
        public static ErrorSet Compile(string syllabifyRuleFileName, TtsPhoneSet phoneSet, 
            Stream outputStream)
        {
            if (string.IsNullOrEmpty(syllabifyRuleFileName))
            {
                throw new ArgumentNullException("syllabifyRuleFileName");
            }

            if (phoneSet == null)
            {
                throw new ArgumentNullException("phoneSet");
            }

            if (outputStream == null)
            {
                throw new ArgumentNullException("outputStream");
            }

            // maximum rule length is 3 phonmes currently
            const int MaxRuleLength = 3;
            ErrorSet errorSet = new ErrorSet();
            phoneSet.Validate();
            if (phoneSet.ErrorSet.Contains(ErrorSeverity.MustFix))
            {
                errorSet.Add(SyllabifyRuleCompilerError.InvalidPhoneSet);
            }
            else
            {
                BinaryWriter bw = new BinaryWriter(outputStream);
                {
                    List<ushort[]> rules = new List<ushort[]>();

                    XmlDocument xmldoc = new XmlDocument();
                    xmldoc.Load(syllabifyRuleFileName);
                    XmlNamespaceManager nm = new XmlNamespaceManager(xmldoc.NameTable);
                    nm.AddNamespace("tts", _ttsSchemaUri);
                    XmlNodeList nodeList = xmldoc.DocumentElement.SelectNodes(
                        "/tts:syllabifyRules/tts:initialConsonants", nm);

                    if (nodeList != null)
                    {
                        foreach (XmlNode node in nodeList)
                        {
                            XmlNodeList phoneNodeList;
                            XmlElement xmlNode = node as XmlElement;

                            phoneNodeList = xmlNode.SelectNodes("tts:phone", nm);
                            if (phoneNodeList.Count > MaxRuleLength)
                            {
                                errorSet.Add(SyllabifyRuleCompilerError.RuleLengthExceeded,
                                    MaxRuleLength.ToString(CultureInfo.InvariantCulture), xmlNode.InnerXml);
                            }
                            else
                            {
                                ushort[] rule = new ushort[MaxRuleLength + 1];
                                int idx = 0;

                                foreach (XmlNode phoneNode in phoneNodeList)
                                {
                                    XmlElement xmlPhoneNode = phoneNode as XmlElement;

                                    string phoneValue = xmlPhoneNode.GetAttribute("value");
                                    Phone phone = phoneSet.GetPhone(phoneValue);
                                    if (phone != null)
                                    {
                                        rule[idx++] = (ushort)phone.Id;
                                    }
                                    else
                                    {
                                        errorSet.Add(SyllabifyRuleCompilerError.InvalidPhone, phoneValue);
                                    }
                                }

                                rule[idx] = 0;
                                rules.Add(rule);
                            }
                        }

                        bw.Write(rules.Count);
                        foreach (ushort[] ci in rules)
                        {
                            for (int i = 0; i < ci.Length; i++)
                            {
                                bw.Write(BitConverter.GetBytes(ci[i]));
                            }
                        }
                    }
                }
            }

            return errorSet;
        }
        /// <summary>
        /// Load Phone set Data object.
        /// </summary>
        /// <param name="errorSet">ErrorSet.</param>
        /// <returns>Phone set Data object.</returns>
        internal override object LoadDataObject(ErrorSet errorSet)
        {
            if (errorSet == null)
            {
                throw new ArgumentNullException("errorSet");
            }

            TtsPhoneSet phoneSet = new TtsPhoneSet();
            phoneSet.Load(this.Path);
            phoneSet.Validate();
            errorSet.Merge(phoneSet.ErrorSet);
            if (phoneSet.ErrorSet.Contains(ErrorSeverity.MustFix))
            {
                phoneSet = null;
            }

            return phoneSet;
        }
        /// <summary>
        /// Compiler entrance.
        /// </summary>
        /// <param name="phoneSet">Phone set object.</param>
        /// <param name="phoneConverter">PhoneConverter.</param>
        /// <param name="outputStream">Output stream.</param>
        /// <returns>ErrorSet.</returns>
        public static ErrorSet Compile(TtsPhoneSet phoneSet,
            IPhoneConverter phoneConverter, Stream outputStream)
        {
            if (phoneSet == null)
            {
                throw new ArgumentNullException("phoneSet");
            }

            if (outputStream == null)
            {
                throw new ArgumentNullException("outputStream");
            }

            ErrorSet errorSet = new ErrorSet();
            phoneSet.Validate();
            if (phoneSet.ErrorSet.Contains(ErrorSeverity.MustFix))
            {
                errorSet.Add(PhoneEventCompilerError.InvalidPhoneSet);
            }
            else
            {
                BinaryWriter bw = new BinaryWriter(outputStream);
                int inUpsCount = visemeMap.Length / 2;
                Dictionary<char, byte> dicVisemeMap = new Dictionary<char, byte>(inUpsCount);
                for (int i = 0; i < inUpsCount; i++)
                {
                    dicVisemeMap.Add((char)visemeMap[i, 0], (byte)visemeMap[i, 1]);
                }

                foreach (Phone phm in phoneSet.Phones)
                {
                    bw.Write((short)phm.Id);
                    string strViseme = string.Empty;
                    string ttsPhoneID = new string(Convert.ToChar(phm.Id), 1);

                    char[] upsIds;

                    try
                    {
                        upsIds = phoneConverter.TTS2UPS(ttsPhoneID).ToCharArray();
                    }
                    catch (Exception ex)
                    {
                        upsIds = string.Empty.ToCharArray();
                        errorSet.Add(DataCompilerError.CompilingLog, 
                            string.Format("Failed to convert TTS phone to UPS, Id={0}. {1}", phm.Id, Helper.BuildExceptionMessage(ex)));
                    }

                    // check upsid's length
                    if (upsIds.Length > MAX_UPS_PHONE_PER_TTS)
                    {
                        throw new NotSupportedException("Too many UPS phones for one TTS phone.");
                    }

                    // write viseme
                    int k = 0;
                    for (int j = 0; j < upsIds.Length; j++)
                    {
                        byte visemeVal = dicVisemeMap[upsIds[j]];
                        if (visemeVal != VISEME_0)
                        {
                            // ignore those silence viseme in-between one ups phone's viseme series since 
                            // it's not natural to close mouth in one phone.
                            bw.Write(visemeVal);
                            k++;
                        }
                    }

                    // pad zero. If all viseme are silence, then save one silence viseme.
                    // otherwise, zero represent the end of viseme id series.
                    for (; k < MAX_UPS_PHONE_PER_TTS + 1; k++)
                    {
                        bw.Write((byte)0);
                    }

                    if (phoneConverter != null)
                    {
                        string sapiPhoneId = phoneConverter.TTS2SAPI(ttsPhoneID);
                        if (sapiPhoneId.Length > MAX_SAPI_PHONE_PER_TTS)
                        {
                            throw new NotSupportedException("Too many SAPI phones for one TTS phone.");
                        }

                        char[] phoneIdArray = sapiPhoneId.ToCharArray();
                        int i = 0;
                        for (i = 0; i < phoneIdArray.Length; i++)
                        {
                            bw.Write((ushort)phoneIdArray[i]);
                        }

                        for (; i < MAX_SAPI_PHONE_PER_TTS; i++)
                        {
                            bw.Write((ushort)0);
                        }
                    }
                    else
                    {
                        for (int i = 0; i < MAX_SAPI_PHONE_PER_TTS; ++i)
                        {
                            bw.Write((ushort)0);
                        }
                    }

                    bw.Write((ushort)0);
                }
            }

            return errorSet;
        }