/* test for zone overlap.  the specified pitch range is inclusive.  returns True */
        /* if there is no overlap. */
        public static bool MultiInstrSpecTestOverlap(
            MultiInstrSpecRec Spec,
            int BasePitch,
            int TopPitch)
        {
#if DEBUG
            if ((BasePitch < 0) || (BasePitch > Constants.NUMNOTES - 1))
            {
                // BasePitch out of range
                Debug.Assert(false);
                throw new ArgumentException();
            }
            if ((TopPitch < 0) || (TopPitch > Constants.NUMNOTES - 1))
            {
                // TopPitch out of range
                Debug.Assert(false);
                throw new ArgumentException();
            }
            if (TopPitch < BasePitch)
            {
                // Pitch range is inverted
                Debug.Assert(false);
                throw new ArgumentException();
            }
#endif
            for (int i = 0; i < Spec.Array.Length; i++)
            {
                if (((Spec.Array[i].BasePitch >= BasePitch) && (Spec.Array[i].BasePitch <= TopPitch)) ||
                    ((Spec.Array[i].TopPitch >= BasePitch) && (Spec.Array[i].TopPitch <= TopPitch)))
                {
                    return(false);
                }
            }
            return(true);
        }
        /* create a new multi-instrument spec */
        public static MultiInstrSpecRec NewMultiInstrSpec()
        {
            MultiInstrSpecRec Spec = new MultiInstrSpecRec();

            Spec.Array = new ZoneRec[0];

            return(Spec);
        }
        /* add pitch zone to multi-instrument spec.  returns False if it runs out of memory. */
        /* the specified pitch range is inclusive. */
        public static void MultiInstrSpecAddZone(
            MultiInstrSpecRec Spec,
            string InstrName,
            int BasePitch,
            int TopPitch,
            int EffectiveBasePitch)
        {
#if DEBUG
            if ((BasePitch < 0) || (BasePitch > Constants.NUMNOTES - 1))
            {
                // BasePitch out of range
                Debug.Assert(false);
                throw new ArgumentException();
            }
            if ((TopPitch < 0) || (TopPitch > Constants.NUMNOTES - 1))
            {
                // TopPitch out of range
                Debug.Assert(false);
                throw new ArgumentException();
            }
            if (TopPitch < BasePitch)
            {
                // Pitch range is inverted
                Debug.Assert(false);
                throw new ArgumentException();
            }
            if ((EffectiveBasePitch < 0) || (EffectiveBasePitch > Constants.NUMNOTES - 1))
            {
                // EffectiveBasePitch out of range
                Debug.Assert(false);
                throw new ArgumentException();
            }
            if (!MultiInstrSpecTestOverlap(Spec, BasePitch, TopPitch))
            {
                // Pitch range overlaps existing range
                Debug.Assert(false);
                throw new ArgumentException();
            }
#endif

            ZoneRec Zone = new ZoneRec();

            Zone.InstrName          = InstrName;
            Zone.BasePitch          = (short)BasePitch;
            Zone.TopPitch           = (short)TopPitch;
            Zone.EffectiveBasePitch = (short)EffectiveBasePitch;

            Array.Resize(ref Spec.Array, Spec.Array.Length + 1);
            Spec.Array[Spec.Array.Length - 1] = Zone;
        }
        /* create multi-instrument spec from string */
        public static MultiInstrParseError MultiInstrParse(
            out MultiInstrSpecRec Spec,
            string String)
        {
            Spec = NewMultiInstrSpec();

            ScannerRec <MultiInstrKeywords> Scanner = new ScannerRec <MultiInstrKeywords>(String, MultiInstrKeywordTable);

            /* parse all the thingies */
            while (true)
            {
                TokenRec <MultiInstrKeywords> Token;
                string InstrName;
                int    BasePitch;
                int    TopPitch;
                int    EffectiveBasePitch;
                int    Sign;

                /* end of input, or "instrname" */
                Token = Scanner.GetNextToken();
                if (Token.GetTokenType() == TokenTypes.eTokenEndOfInput)
                {
                    return(MultiInstrParseError.eMultiInstrParseOK);
                }
                if (Token.GetTokenType() != TokenTypes.eTokenString)
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedInstrNameString);
                }
                InstrName = Token.GetTokenStringValue();

                /* base pitch */

                /* letter */
                Token = Scanner.GetNextToken();
                if (Token.GetTokenType() != TokenTypes.eTokenKeyword)
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedPitchLetter);
                }
                switch (Token.GetTokenKeywordTag())
                {
                default:
                    return(MultiInstrParseError.eMultiInstrParseExpectedPitchLetter);

                case MultiInstrKeywords.eMIK_C:
                    BasePitch = Constants.CENTERNOTE;
                    break;

                case MultiInstrKeywords.eMIK_D:
                    BasePitch = Constants.CENTERNOTE + 2;
                    break;

                case MultiInstrKeywords.eMIK_E:
                    BasePitch = Constants.CENTERNOTE + 4;
                    break;

                case MultiInstrKeywords.eMIK_F:
                    BasePitch = Constants.CENTERNOTE + 5;
                    break;

                case MultiInstrKeywords.eMIK_G:
                    BasePitch = Constants.CENTERNOTE + 7;
                    break;

                case MultiInstrKeywords.eMIK_A:
                    BasePitch = Constants.CENTERNOTE + 9;
                    break;

                case MultiInstrKeywords.eMIK_B:
                    BasePitch = Constants.CENTERNOTE + 11;
                    break;
                }

                Token = Scanner.GetNextToken();

                /* optional "flat" or "sharp" */
                if (Token.GetTokenType() == TokenTypes.eTokenKeyword)
                {
                    if (Token.GetTokenKeywordTag() == MultiInstrKeywords.eMIK_flat)
                    {
                        BasePitch -= 1;
                    }
                    else if (Token.GetTokenKeywordTag() == MultiInstrKeywords.eMIK_sharp)
                    {
                        BasePitch += 1;
                    }
                    else
                    {
                        return(MultiInstrParseError.eMultiInstrParseExpectedFlatOrSharp);
                    }

                    Token = Scanner.GetNextToken();
                }

                /* octave number */
                Sign = 1;
                if (Token.GetTokenType() == TokenTypes.eTokenMinus)
                {
                    Sign  = -1;
                    Token = Scanner.GetNextToken();
                }
                if (Token.GetTokenType() != TokenTypes.eTokenInteger)
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedFlatOrSharpOrOctave);
                }
                BasePitch += 12 * Sign * Token.GetTokenIntegerValue();
                if ((BasePitch < 0) || (BasePitch > Constants.NUMNOTES - 1))
                {
                    return(MultiInstrParseError.eMultiInstrParseBasePitchOutOfRange);
                }

                Token = Scanner.GetNextToken();
                if ((Token.GetTokenType() != TokenTypes.eTokenKeyword) ||
                    (Token.GetTokenKeywordTag() != MultiInstrKeywords.eMIK_to))
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedTo);
                }

                /* top pitch */

                /* letter */
                Token = Scanner.GetNextToken();
                if (Token.GetTokenType() != TokenTypes.eTokenKeyword)
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedPitchLetter);
                }
                switch (Token.GetTokenKeywordTag())
                {
                default:
                    return(MultiInstrParseError.eMultiInstrParseExpectedPitchLetter);

                case MultiInstrKeywords.eMIK_C:
                    TopPitch = Constants.CENTERNOTE;
                    break;

                case MultiInstrKeywords.eMIK_D:
                    TopPitch = Constants.CENTERNOTE + 2;
                    break;

                case MultiInstrKeywords.eMIK_E:
                    TopPitch = Constants.CENTERNOTE + 4;
                    break;

                case MultiInstrKeywords.eMIK_F:
                    TopPitch = Constants.CENTERNOTE + 5;
                    break;

                case MultiInstrKeywords.eMIK_G:
                    TopPitch = Constants.CENTERNOTE + 7;
                    break;

                case MultiInstrKeywords.eMIK_A:
                    TopPitch = Constants.CENTERNOTE + 9;
                    break;

                case MultiInstrKeywords.eMIK_B:
                    TopPitch = Constants.CENTERNOTE + 11;
                    break;
                }

                Token = Scanner.GetNextToken();

                /* optional "flat" or "sharp" */
                if (Token.GetTokenType() == TokenTypes.eTokenKeyword)
                {
                    if (Token.GetTokenKeywordTag() == MultiInstrKeywords.eMIK_flat)
                    {
                        TopPitch -= 1;
                    }
                    else if (Token.GetTokenKeywordTag() == MultiInstrKeywords.eMIK_sharp)
                    {
                        TopPitch += 1;
                    }
                    else
                    {
                        return(MultiInstrParseError.eMultiInstrParseExpectedFlatOrSharp);
                    }

                    Token = Scanner.GetNextToken();
                }

                /* octave number */
                Sign = 1;
                if (Token.GetTokenType() == TokenTypes.eTokenMinus)
                {
                    Sign  = -1;
                    Token = Scanner.GetNextToken();
                }
                if (Token.GetTokenType() != TokenTypes.eTokenInteger)
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedFlatOrSharpOrOctave);
                }
                TopPitch += 12 * Sign * Token.GetTokenIntegerValue();
                if ((TopPitch < 0) || (TopPitch > Constants.NUMNOTES - 1))
                {
                    return(MultiInstrParseError.eMultiInstrParseTopPitchOutOfRange);
                }

                Token = Scanner.GetNextToken();
                if ((Token.GetTokenType() != TokenTypes.eTokenKeyword) ||
                    (Token.GetTokenKeywordTag() != MultiInstrKeywords.eMIK_as))
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedAs);
                }

                /* effective base pitch */

                /* letter */
                Token = Scanner.GetNextToken();
                if (Token.GetTokenType() != TokenTypes.eTokenKeyword)
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedPitchLetter);
                }
                switch (Token.GetTokenKeywordTag())
                {
                default:
                    return(MultiInstrParseError.eMultiInstrParseExpectedPitchLetter);

                case MultiInstrKeywords.eMIK_C:
                    EffectiveBasePitch = Constants.CENTERNOTE;
                    break;

                case MultiInstrKeywords.eMIK_D:
                    EffectiveBasePitch = Constants.CENTERNOTE + 2;
                    break;

                case MultiInstrKeywords.eMIK_E:
                    EffectiveBasePitch = Constants.CENTERNOTE + 4;
                    break;

                case MultiInstrKeywords.eMIK_F:
                    EffectiveBasePitch = Constants.CENTERNOTE + 5;
                    break;

                case MultiInstrKeywords.eMIK_G:
                    EffectiveBasePitch = Constants.CENTERNOTE + 7;
                    break;

                case MultiInstrKeywords.eMIK_A:
                    EffectiveBasePitch = Constants.CENTERNOTE + 9;
                    break;

                case MultiInstrKeywords.eMIK_B:
                    EffectiveBasePitch = Constants.CENTERNOTE + 11;
                    break;
                }

                Token = Scanner.GetNextToken();

                /* optional "flat" or "sharp" */
                if (Token.GetTokenType() == TokenTypes.eTokenKeyword)
                {
                    if (Token.GetTokenKeywordTag() == MultiInstrKeywords.eMIK_flat)
                    {
                        EffectiveBasePitch -= 1;
                    }
                    else if (Token.GetTokenKeywordTag() == MultiInstrKeywords.eMIK_sharp)
                    {
                        EffectiveBasePitch += 1;
                    }
                    else
                    {
                        return(MultiInstrParseError.eMultiInstrParseExpectedFlatOrSharp);
                    }

                    Token = Scanner.GetNextToken();
                }

                /* octave number */
                Sign = 1;
                if (Token.GetTokenType() == TokenTypes.eTokenMinus)
                {
                    Sign  = -1;
                    Token = Scanner.GetNextToken();
                }
                if (Token.GetTokenType() != TokenTypes.eTokenInteger)
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedFlatOrSharpOrOctave);
                }
                EffectiveBasePitch += 12 * Sign * Token.GetTokenIntegerValue();
                if ((EffectiveBasePitch < 0) || (EffectiveBasePitch > Constants.NUMNOTES - 1))
                {
                    return(MultiInstrParseError.eMultiInstrParseTopPitchOutOfRange);
                }

                Token = Scanner.GetNextToken();
                if (Token.GetTokenType() != TokenTypes.eTokenSemicolon)
                {
                    return(MultiInstrParseError.eMultiInstrParseExpectedSemicolon);
                }

                /* put value */
                if (BasePitch > TopPitch)
                {
                    return(MultiInstrParseError.eMultiInstrParseInvertedRange);
                }
                if (!MultiInstrSpecTestOverlap(
                        Spec,
                        BasePitch,
                        TopPitch))
                {
                    return(MultiInstrParseError.eMultiInstrParseOverlapExistingRange);
                }
                MultiInstrSpecAddZone(
                    Spec,
                    InstrName,
                    BasePitch,
                    TopPitch,
                    EffectiveBasePitch);
            }
        }
 /* get effective base pitch of the zone's range */
 public static short MultiInstrSpecGetIndexedEffectiveBasePitch(
     MultiInstrSpecRec Spec,
     int Index)
 {
     return(Spec.Array[Index].EffectiveBasePitch);
 }
 /* get top pitch of the zone's range */
 public static short MultiInstrSpecGetIndexedTopPitch(
     MultiInstrSpecRec Spec,
     int Index)
 {
     return(Spec.Array[Index].TopPitch);
 }
 /* get actual instrument name for the specified zone */
 public static string MultiInstrSpecGetIndexedActualInstrName(
     MultiInstrSpecRec Spec,
     int Index)
 {
     return(Spec.Array[Index].InstrName);
 }
 /* get number of instruments */
 public static int MultiInstrSpecGetLength(MultiInstrSpecRec Spec)
 {
     return(Spec.Array.Length);
 }
 /* add maximum zone */
 public static void MultiInstrSpecAddMaximumDefaultZone(
     MultiInstrSpecRec Spec,
     string InstrName)
 {
     MultiInstrSpecAddZone(Spec, InstrName, 0, Constants.NUMNOTES - 1, 0);
 }