Example #1
0
        /// <summary>
        /// Create a new instance of the BmpCoffer manager based on the given LiteDB database.
        /// </summary>
        /// <param name="dbPath"></param>
        /// <returns></returns>
        internal static BmpCoffer CreateInstance(string dbPath)
        {
            var mapper = new BsonMapper();

            mapper.RegisterType
            (
                group => group.Index,
                bson => AutoToneInstrumentGroup.Parse(bson.AsInt32)
            );
            mapper.RegisterType
            (
                group => group.Index,
                bson => AutoToneOctaveRange.Parse(bson.AsInt32)
            );
            mapper.RegisterType
            (
                group => group.Index,
                bson => Instrument.Parse(bson.AsInt32)
            );
            mapper.RegisterType
            (
                group => group.Index,
                bson => InstrumentTone.Parse(bson.AsInt32)
            );
            mapper.RegisterType
            (
                group => group.Index,
                bson => OctaveRange.Parse(bson.AsInt32)
            );
            mapper.RegisterType
            (
                tempoMap => SerializeTempoMap(tempoMap),
                bson => DeserializeTempoMap(bson.AsBinary)
            );
            mapper.RegisterType
            (
                trackChunk => SerializeTrackChunk(trackChunk),
                bson => DeserializeTrackChunk(bson.AsBinary)
            );

            var dbi = new LiteDatabase(dbPath, mapper);

            MigrateDatabase(dbi);

            return(new BmpCoffer(dbi));
        }
Example #2
0
        /// <summary>
        /// Equips an instrument tone
        /// </summary>
        /// <param name="game"></param>
        /// <param name="instrumentToneWanted"></param>
        /// <returns></returns>
        public static async Task <bool> EquipTone(this Game game, InstrumentTone instrumentToneWanted)
        {
            if (!BmpGrunt.Instance.Started)
            {
                throw new BmpGruntException("Grunt not started.");
            }

            if (!game.IsBard)
            {
                return(false);
            }

            // TODO for 5.55
            if ((int)game.GameRegion < 4)
            {
                throw new BmpGruntException("Equipping a Tone is not supported in region " + game.GameRegion);
            }

            var exitLock = 5;

            while (exitLock > 0)
            {
                if (game.InstrumentToneHeld.Equals(instrumentToneWanted))
                {
                    return(true);
                }

                if (!instrumentToneWanted.Equals(InstrumentTone.None) && game.InstrumentToneHeld.Equals(InstrumentTone.None))
                {
                    await SyncTapKey(game, game.InstrumentToneKeys[instrumentToneWanted]);
                }
                else
                {
                    await SyncTapKey(game, game.NavigationMenuKeys[NavigationMenuKey.ESC]);
                }
                exitLock--;
            }

            return(game.InstrumentToneHeld.Equals(instrumentToneWanted));
        }
Example #3
0
        internal static async Task <(MidiFile, Dictionary <int, Dictionary <long, string> >)> GetSynthMidi(this BmpSong song)
        {
            var file = new MidiFile {
                Division = 600
            };
            var events = new AlphaSynthMidiFileHandler(file);

            events.AddTempo(0, 100);

            var trackCounter = byte.MinValue;
            var veryLast     = 0L;

            var midiFile = await song.GetProcessedMidiFile();

            var trackChunks = midiFile.GetTrackChunks().ToList();

            var lyrics   = new Dictionary <int, Dictionary <long, string> >();
            var lyricNum = 0;

            foreach (var trackChunk in trackChunks)
            {
                var options = trackChunk.Events.OfType <SequenceTrackNameEvent>().First().Text.Split(':');
                switch (options[0])
                {
                case "lyric":
                {
                    if (!lyrics.ContainsKey(lyricNum))
                    {
                        lyrics.Add(lyricNum, new Dictionary <long, string>(int.Parse(options[1])));
                    }

                    foreach (var lyric in trackChunk.GetTimedEvents().Where(x => x.Event.EventType == MidiEventType.Lyric))
                    {
                        lyrics[lyricNum].Add(lyric.Time, ((LyricEvent)lyric.Event).Text);
                    }

                    lyricNum++;

                    break;
                }

                case "tone":
                {
                    var tone = InstrumentTone.Parse(options[1]);
                    foreach (var note in trackChunk.GetNotes())
                    {
                        var instrument = tone.GetInstrumentFromChannel(note.Channel);
                        var noteNum    = note.NoteNumber;
                        var dur        = (int)MinimumLength(instrument, noteNum - 48, note.Length);
                        var time       = (int)note.Time;
                        events.AddProgramChange(trackCounter, time, trackCounter, (byte)instrument.MidiProgramChangeCode);
                        events.AddNote(trackCounter, time, dur, noteNum, DynamicValue.FFF, trackCounter);
                        if (trackCounter == byte.MaxValue)
                        {
                            trackCounter = byte.MinValue;
                        }
                        else
                        {
                            trackCounter++;
                        }
                        if (time + dur > veryLast)
                        {
                            veryLast = time + dur;
                        }
                    }

                    break;
                }
                }
            }
            events.FinishTrack(byte.MaxValue, (byte)veryLast);
            return(file, lyrics);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="trackChunk"></param>
        /// <param name="trackNumber"></param>
        /// <param name="song"></param>
        /// <returns></returns>
        internal static Dictionary <long, ConfigContainer> ReadConfigs(this TrackChunk trackChunk, int trackNumber, BmpSong song)
        {
            var configContainers = new Dictionary <long, ConfigContainer>();

            if (trackChunk.GetNotes().Count == 0 && trackChunk.GetTimedEvents().All(x => x.Event.EventType != MidiEventType.Lyric))
            {
                BmpLog.I(BmpLog.Source.Transmogrify, "Skipping track " + trackNumber + " as it contains no notes and contains no lyric events.");
                return(configContainers);
            }

            var trackName = (trackChunk.Events.OfType <SequenceTrackNameEvent>().FirstOrDefault()?.Text ?? "").Replace(" ", "").ToLower();

            if (trackName.Contains("ignore"))
            {
                BmpLog.I(BmpLog.Source.Transmogrify, "Skipping track " + trackNumber + " as the track title contains \"Ignore\"");
                return(configContainers);
            }

            var groups = trackName.Split('|');

            var modifier = new Regex(@"^([A-Za-z0-9]+)([-+]\d)?");

            for (var groupCounter = 0; groupCounter < groups.Length; groupCounter++)
            {
                var configContainer = new ConfigContainer();
                var fields          = groups[groupCounter].Split(';');

                if (fields.Length == 0)
                {
                    continue;
                }

                // bmp 2.x style group name
                if (fields[0].StartsWith("vst:") || fields[0].Equals("lyrics"))
                {
                    var subfields = fields[0].Split(':');

                    switch (subfields[0])
                    {
                    case "vst" when subfields.Length < 2:
                        BmpLog.W(BmpLog.Source.Transmogrify, "Skipping VST on track " + trackNumber + " due to the configuration not specifying a tone.");
                        continue;

                    case "vst":
                        var manualToneConfig = (VSTProcessorConfig)(configContainer.ProcessorConfig = new VSTProcessorConfig {
                            Track = trackNumber
                        });
                        manualToneConfig.InstrumentTone = InstrumentTone.Parse(subfields[1]);
                        if (manualToneConfig.InstrumentTone.Equals(InstrumentTone.None))
                        {
                            BmpLog.W(BmpLog.Source.Transmogrify, "Skipping VST on track " + trackNumber + " due to the configuration specifying an invalid tone.");
                            continue;
                        }
                        if (subfields.Length > 2)
                        {
                            var shifts = subfields[2].Split(',');
                            foreach (var shift in shifts)
                            {
                                var toneIndexAndOctaveRange = modifier.Match(shift);
                                if (!toneIndexAndOctaveRange.Success)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid VST octave setting \"" + shift + "\" on track " + trackNumber);
                                    continue;
                                }
                                if (!toneIndexAndOctaveRange.Groups[1].Success)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid VST octave setting \"" + shift + "\" on track " + trackNumber + " because \"" + toneIndexAndOctaveRange.Groups[1].Value + "\" is not a valid tone number");
                                    continue;
                                }
                                if (!int.TryParse(toneIndexAndOctaveRange.Groups[1].Value, out var toneIndex) || toneIndex < 0 || toneIndex > 4)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid VST octave setting \"" + shift + "\" on track " + trackNumber + " because \"" + toneIndexAndOctaveRange.Groups[1].Value + "\" is not a valid tone number");
                                    continue;
                                }
                                var octaveRange = OctaveRange.C3toC6;
                                if (toneIndexAndOctaveRange.Groups[2].Success)
                                {
                                    octaveRange = OctaveRange.Parse(toneIndexAndOctaveRange.Groups[2].Value);
                                }
                                if (octaveRange.Equals(OctaveRange.Invalid))
                                {
                                    octaveRange = OctaveRange.C3toC6;
                                }
                                manualToneConfig.OctaveRanges[toneIndex] = octaveRange;
                            }
                        }
                        ParseAdditionalOptions(trackNumber, manualToneConfig, song, fields);
                        BmpLog.I(BmpLog.Source.Transmogrify, "Found VST Config Group with on track " + manualToneConfig.Track + " ;bards=" + manualToneConfig.PlayerCount + ";include=" + string.Join(",", manualToneConfig.IncludedTracks));
                        configContainers.Add(groupCounter, configContainer);
                        continue;

                    case "lyrics":
                        var lyricConfig = (LyricProcessorConfig)(configContainer.ProcessorConfig = new LyricProcessorConfig {
                            Track = trackNumber
                        });
                        ParseAdditionalOptions(trackNumber, lyricConfig, song, fields);
                        BmpLog.I(BmpLog.Source.Transmogrify, "Found Lyric Config on track " + lyricConfig.Track + " ;bards=" + lyricConfig.PlayerCount + ";include=" + string.Join(",", lyricConfig.IncludedTracks));
                        configContainers.Add(groupCounter, configContainer);
                        continue;
                    }
                }

                // bmp 1.x style group name
                else
                {
                    var classicConfig            = (ClassicProcessorConfig)(configContainer.ProcessorConfig = new ClassicProcessorConfig {
                        Track = trackNumber
                    });
                    var instrumentAndOctaveRange = modifier.Match(fields[0]);
                    if (!instrumentAndOctaveRange.Success)
                    {
                        continue;                                    // Invalid Instrument name.
                    }
                    if (instrumentAndOctaveRange.Groups[1].Success)
                    {
                        classicConfig.Instrument = Instrument.Parse(instrumentAndOctaveRange.Groups[1].Value);
                    }
                    if (classicConfig.Instrument.Equals(Instrument.None))
                    {
                        continue;                                                    // Invalid Instrument name.
                    }
                    if (instrumentAndOctaveRange.Groups[2].Success)
                    {
                        classicConfig.OctaveRange = OctaveRange.Parse(instrumentAndOctaveRange.Groups[2].Value);
                    }
                    if (classicConfig.OctaveRange.Equals(OctaveRange.Invalid))
                    {
                        classicConfig.OctaveRange = OctaveRange.C3toC6;
                    }
                    ParseAdditionalOptions(trackNumber, classicConfig, song, fields);
                    BmpLog.I(BmpLog.Source.Transmogrify, "Found Classic Config Instrument " + classicConfig.Instrument.Name + " OctaveRange " + classicConfig.OctaveRange.Name + " on track " + classicConfig.Track + " ;bards=" + classicConfig.PlayerCount + ";include=" + string.Join(",", classicConfig.IncludedTracks));
                    configContainers.Add(groupCounter, configContainer);
                }
            }

            if (configContainers.Count == 0)
            {
                BmpLog.I(BmpLog.Source.Transmogrify, "Found 0 configurations on track " + trackNumber + ", and the keyword \"Ignore\" is not in the track title. Adding a default harp.");
                configContainers.Add(0, new ConfigContainer {
                    ProcessorConfig = new ClassicProcessorConfig {
                        Track = trackNumber
                    }
                });
            }

            return(configContainers);
        }
 /// <summary>
 ///
 /// </summary>
 /// <param name="trackChunks"></param>
 /// <param name="tempoMap"></param>
 /// <param name="tone"></param>
 /// <param name="mapper"></param>
 /// <param name="lowClamp"></param>
 /// <param name="highClamp"></param>
 /// <param name="startingChannel"></param>
 /// <param name="readTones"></param>
 /// <param name="noteSampleOffset"></param>
 /// <returns></returns>
 internal static Task <Dictionary <int, Dictionary <long, Note> > > GetNoteDictionary(this List <TrackChunk> trackChunks, TempoMap tempoMap, InstrumentTone tone, Dictionary <int, (int, int)> mapper, int lowClamp = 12, int highClamp = 120, int startingChannel = 0, bool readTones = true, int noteSampleOffset = 0)
        /// <summary>
        ///
        /// </summary>
        /// <param name="trackChunk"></param>
        /// <param name="trackNumber"></param>
        /// <param name="song"></param>
        /// <returns></returns>
        internal static Dictionary <long, ConfigContainer> ReadConfigs(this TrackChunk trackChunk, int trackNumber, BmpSong song)
        {
            var configContainers = new Dictionary <long, ConfigContainer>();

            if (trackChunk.GetNotes().Count == 0 && trackChunk.GetTimedEvents().All(x => x.Event.EventType != MidiEventType.Lyric))
            {
                BmpLog.I(BmpLog.Source.Transmogrify, "Skipping track " + trackNumber + " as it contains no notes and contains no lyric events.");
                return(configContainers);
            }

            var trackName = (trackChunk.Events.OfType <SequenceTrackNameEvent>().FirstOrDefault()?.Text ?? "").Replace(" ", "").ToLower();

            if (trackName.Contains("ignore"))
            {
                BmpLog.I(BmpLog.Source.Transmogrify, "Skipping track " + trackNumber + " as the track title contains \"Ignore\"");
                return(configContainers);
            }

            var groups = trackName.Split('|');

            var modifier = new Regex(@"^([A-Za-z0-9]+)([-+]\d)?");

            for (var groupCounter = 0; groupCounter < groups.Length; groupCounter++)
            {
                var configContainer = new ConfigContainer();
                var fields          = groups[groupCounter].Split(';');

                if (fields.Length == 0)
                {
                    continue;
                }

                // bmp 2.x style group name
                if (fields[0].StartsWith("manualtone:") || fields[0].StartsWith("notetone:") || fields[0].StartsWith("autotone:") || fields[0].StartsWith("drumtone:") || fields[0].Equals("drumtone") || fields[0].StartsWith("octavetone") || fields[0].Equals("lyric"))
                {
                    var subfields = fields[0].Split(':');

                    switch (subfields[0])
                    {
                    case "manualtone" when subfields.Length < 2:
                        BmpLog.W(BmpLog.Source.Transmogrify, "Skipping ManualTone on track " + trackNumber + " due to the configuration not specifying a tone.");
                        continue;

                    case "manualtone":
                        var manualToneConfig = (ManualToneProcessorConfig)(configContainer.ProcessorConfig = new ManualToneProcessorConfig {
                            Track = trackNumber
                        });
                        manualToneConfig.InstrumentTone = InstrumentTone.Parse(subfields[1]);
                        if (manualToneConfig.InstrumentTone.Equals(InstrumentTone.None))
                        {
                            BmpLog.W(BmpLog.Source.Transmogrify, "Skipping ManualTone on track " + trackNumber + " due to the configuration specifying an invalid tone.");
                            continue;
                        }
                        if (subfields.Length > 2)
                        {
                            var shifts = subfields[2].Split(',');
                            foreach (var shift in shifts)
                            {
                                var toneIndexAndOctaveRange = modifier.Match(shift);
                                if (!toneIndexAndOctaveRange.Success)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid ManualTone octave setting \"" + shift + "\" on track " + trackNumber);
                                    continue;
                                }
                                if (!toneIndexAndOctaveRange.Groups[1].Success)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid ManualTone octave setting \"" + shift + "\" on track " + trackNumber + " because \"" + toneIndexAndOctaveRange.Groups[1].Value + "\" is not a valid tone number");
                                    continue;
                                }
                                if (!int.TryParse(toneIndexAndOctaveRange.Groups[1].Value, out var toneIndex) || toneIndex < 0 || toneIndex > 4)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid ManualTone octave setting \"" + shift + "\" on track " + trackNumber + " because \"" + toneIndexAndOctaveRange.Groups[1].Value + "\" is not a valid tone number");
                                    continue;
                                }
                                var octaveRange = OctaveRange.C3toC6;
                                if (toneIndexAndOctaveRange.Groups[2].Success)
                                {
                                    octaveRange = OctaveRange.Parse(toneIndexAndOctaveRange.Groups[2].Value);
                                }
                                if (octaveRange.Equals(OctaveRange.Invalid))
                                {
                                    octaveRange = OctaveRange.C3toC6;
                                }
                                manualToneConfig.OctaveRanges[toneIndex] = octaveRange;
                            }
                        }
                        ParseAdditionalOptions(trackNumber, manualToneConfig, song, fields);
                        BmpLog.I(BmpLog.Source.Transmogrify, "Found ManualTone Config Group with on track " + manualToneConfig.Track + " ;bards=" + manualToneConfig.PlayerCount + ";include=" + string.Join(",", manualToneConfig.IncludedTracks));
                        configContainers.Add(groupCounter, configContainer);
                        continue;

                    case "notetone" when subfields.Length < 2:
                        BmpLog.W(BmpLog.Source.Transmogrify, "Skipping NoteTone on track " + trackNumber + " due to the configuration not specifying a tone.");
                        continue;

                    case "notetone":
                    {
                        var noteToneConfig = (NoteToneProcessorConfig)(configContainer.ProcessorConfig = new NoteToneProcessorConfig {
                                Track = trackNumber
                            });
                        noteToneConfig.InstrumentTone = InstrumentTone.Parse(subfields[1]);
                        if (noteToneConfig.InstrumentTone.Equals(InstrumentTone.None))
                        {
                            BmpLog.W(BmpLog.Source.Transmogrify, "Skipping NoteTone on track " + trackNumber + " due to the configuration specifying an invalid tone.");
                            continue;
                        }
                        var noteToneSubConfigurations = 0;
                        if (subfields.Length > 2)
                        {
                            subfields = subfields.Skip(2).ToArray();
                            foreach (var mapping in subfields)
                            {
                                var split = mapping.Split(',');
                                if (split.Length != 3)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid NoteTone mapping \"" + mapping + "\" on track " + trackNumber);
                                    continue;
                                }
                                if (!int.TryParse(split[0], out var sourceNote) || sourceNote > 120 || sourceNote < 12)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid NoteTone mapping \"" + mapping + "\" on track " + trackNumber + " because source note \"" + split[0] + "\" is more then 120 or less then 12");
                                    continue;
                                }
                                if (!int.TryParse(split[1], out var toneIndex) || toneIndex < -1 || toneIndex > 4 || (toneIndex == 0 && noteToneConfig.InstrumentTone.Tone0.Equals(Instrument.None)) || (toneIndex == 1 && noteToneConfig.InstrumentTone.Tone1.Equals(Instrument.None)) || (toneIndex == 2 && noteToneConfig.InstrumentTone.Tone2.Equals(Instrument.None)) || (toneIndex == 3 && noteToneConfig.InstrumentTone.Tone3.Equals(Instrument.None)) || (toneIndex == 4 && noteToneConfig.InstrumentTone.Tone4.Equals(Instrument.None)))
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid NoteTone mapping \"" + mapping + "\" on track " + trackNumber + " because \"" + split[1] + "\" is not a valid tone number for Tone " + noteToneConfig.InstrumentTone.Name);
                                    continue;
                                }
                                if (!int.TryParse(split[2], out var destinationNote) || destinationNote < -1 || destinationNote > 36)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid NoteTone mapping \"" + mapping + "\" on track " + trackNumber + " because destination note \"" + split[2] + "\" is more then 36 or less then -1");
                                    continue;
                                }
                                noteToneConfig.Mapper[sourceNote] = (toneIndex, destinationNote);
                                noteToneSubConfigurations++;
                            }
                        }
                        if (noteToneSubConfigurations == 0)
                        {
                            BmpLog.W(BmpLog.Source.Transmogrify, "Skipping NoteTone on track " + trackNumber + " because no mappings are specified.");
                            continue;
                        }
                        ParseAdditionalOptions(trackNumber, noteToneConfig, song, fields);
                        BmpLog.I(BmpLog.Source.Transmogrify, "Found NoteTone Config Group " + noteToneConfig.InstrumentTone.Name + " with " + noteToneSubConfigurations + " mappings on track " + noteToneConfig.Track + " ;bards=" + noteToneConfig.PlayerCount + ";include=" + string.Join(",", noteToneConfig.IncludedTracks));
                        configContainers.Add(groupCounter, configContainer);
                        continue;
                    }

                    case "octavetone" when subfields.Length < 2:
                        BmpLog.W(BmpLog.Source.Transmogrify, "Skipping OctaveTone on track " + trackNumber + " due to the configuration not specifying a tone.");
                        continue;

                    case "octavetone":
                        var octaveToneConfig = (OctaveToneProcessorConfig)(configContainer.ProcessorConfig = new OctaveToneProcessorConfig {
                            Track = trackNumber
                        });
                        octaveToneConfig.InstrumentTone = InstrumentTone.Parse(subfields[1]);
                        if (octaveToneConfig.InstrumentTone.Equals(InstrumentTone.None))
                        {
                            BmpLog.W(BmpLog.Source.Transmogrify, "Skipping OctaveTone on track " + trackNumber + " due to the configuration specifying an invalid tone.");
                            continue;
                        }
                        var octaveToneSubConfigurations = 0;
                        if (subfields.Length > 2)
                        {
                            subfields = subfields.Skip(2).ToArray();
                            foreach (var mapping in subfields)
                            {
                                var split = mapping.Split(',');
                                if (split.Length != 3)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid OctaveTone mapping \"" + mapping + "\" on track " + trackNumber);
                                    continue;
                                }
                                if (!int.TryParse(split[0], out var sourceOctave) || sourceOctave > 8 || sourceOctave < 0)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid OctaveTone mapping \"" + mapping + "\" on track " + trackNumber + " because source octave \"" + split[0] + "\" is more then 8 or less then 0");
                                    continue;
                                }
                                if (!int.TryParse(split[1], out var toneIndex) || toneIndex < -1 || toneIndex > 4 || (toneIndex == 0 && octaveToneConfig.InstrumentTone.Tone0.Equals(Instrument.None)) || (toneIndex == 1 && octaveToneConfig.InstrumentTone.Tone1.Equals(Instrument.None)) || (toneIndex == 2 && octaveToneConfig.InstrumentTone.Tone2.Equals(Instrument.None)) || (toneIndex == 3 && octaveToneConfig.InstrumentTone.Tone3.Equals(Instrument.None)) || (toneIndex == 4 && octaveToneConfig.InstrumentTone.Tone4.Equals(Instrument.None)))
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid OctaveTone mapping \"" + mapping + "\" on track " + trackNumber + " because \"" + split[1] + "\" is not a valid tone number for Tone " + octaveToneConfig.InstrumentTone.Name);
                                    continue;
                                }
                                if (!int.TryParse(split[2], out var destinationOctave) || destinationOctave < -1 || destinationOctave > 3)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid OctaveTone mapping \"" + mapping + "\" on track " + trackNumber + " because destination octave \"" + split[2] + "\" is more then 3 or less then -1");
                                    continue;
                                }
                                octaveToneConfig.Mapper[sourceOctave] = (toneIndex, destinationOctave);
                                octaveToneSubConfigurations++;
                            }
                        }
                        if (octaveToneSubConfigurations == 0)
                        {
                            BmpLog.W(BmpLog.Source.Transmogrify, "Skipping OctaveTone on track " + trackNumber + " because no mappings are specified.");
                            continue;
                        }
                        ParseAdditionalOptions(trackNumber, octaveToneConfig, song, fields);
                        BmpLog.I(BmpLog.Source.Transmogrify, "Found OctaveTone Config Group " + octaveToneConfig.InstrumentTone.Name + " with " + octaveToneSubConfigurations + " mappings on track " + octaveToneConfig.Track + " ;bards=" + octaveToneConfig.PlayerCount + ";include=" + string.Join(",", octaveToneConfig.IncludedTracks));
                        configContainers.Add(groupCounter, configContainer);
                        continue;

                    case "autotone" when subfields.Length < 2:
                        BmpLog.W(BmpLog.Source.Transmogrify, "Skipping AutoTone on track " + trackNumber + " due to the configuration not specifying an autotone group.");
                        continue;

                    case "autotone":
                    {
                        var autoToneConfig           = (AutoToneProcessorConfig)(configContainer.ProcessorConfig = new AutoToneProcessorConfig {
                                Track = trackNumber
                            });
                        var instrumentAndOctaveRange = modifier.Match(subfields[1]);
                        if (!instrumentAndOctaveRange.Success)
                        {
                            BmpLog.W(BmpLog.Source.Transmogrify, "Skipping AutoTone on track " + trackNumber + " due to the configuration specifying an invalid autotone group.");
                            continue;
                        }
                        if (instrumentAndOctaveRange.Groups[1].Success)
                        {
                            autoToneConfig.AutoToneInstrumentGroup = AutoToneInstrumentGroup.Parse(instrumentAndOctaveRange.Groups[1].Value);
                        }
                        if (autoToneConfig.AutoToneInstrumentGroup.Equals(AutoToneInstrumentGroup.Invalid))
                        {
                            BmpLog.W(BmpLog.Source.Transmogrify, "Skipping AutoTone on track " + trackNumber + " due to the configuration specifying an invalid autotone group.");
                            continue;
                        }
                        if (instrumentAndOctaveRange.Groups[2].Success)
                        {
                            autoToneConfig.AutoToneOctaveRange = AutoToneOctaveRange.Parse(instrumentAndOctaveRange.Groups[2].Value);
                        }
                        if (autoToneConfig.AutoToneOctaveRange.Equals(AutoToneOctaveRange.Invalid))
                        {
                            autoToneConfig.AutoToneOctaveRange = AutoToneOctaveRange.C2toC7;
                        }
                        ParseAdditionalOptions(trackNumber, autoToneConfig, song, fields);
                        BmpLog.I(BmpLog.Source.Transmogrify, "Found AutoTone Config Group " + autoToneConfig.AutoToneInstrumentGroup.Name + " OctaveRange " + autoToneConfig.AutoToneOctaveRange.Name + " on track " + autoToneConfig.Track + " ;bards=" + autoToneConfig.PlayerCount + ";include=" + string.Join(",", autoToneConfig.IncludedTracks));
                        configContainers.Add(groupCounter, configContainer);
                        continue;
                    }

                    case "drumtone":
                        var drumToneConfig            = (DrumToneProcessorConfig)(configContainer.ProcessorConfig = new DrumToneProcessorConfig {
                            Track = trackNumber
                        });
                        var drumToneSubConfigurations = 0;
                        if (subfields.Length > 1)
                        {
                            subfields = subfields.Skip(1).ToArray();
                            foreach (var mapping in subfields)
                            {
                                var split = mapping.Split(',');
                                if (split.Length != 3)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid DrumTone mapping \"" + mapping + "\" on track " + trackNumber);
                                    continue;
                                }
                                if (!int.TryParse(split[0], out var sourceNote) || sourceNote > 87 || sourceNote < 27)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid DrumTone mapping \"" + mapping + "\" on track " + trackNumber + " because source note \"" + split[0] + "\" is more then 87 or less then 27");
                                    continue;
                                }
                                if (!int.TryParse(split[1], out var toneIndex) || toneIndex < -1 || toneIndex > 4 || (toneIndex == 0 && InstrumentTone.Drums.Tone0.Equals(Instrument.None)) || (toneIndex == 1 && InstrumentTone.Drums.Tone1.Equals(Instrument.None)) || (toneIndex == 2 && InstrumentTone.Drums.Tone2.Equals(Instrument.None)) || (toneIndex == 3 && InstrumentTone.Drums.Tone3.Equals(Instrument.None)) || (toneIndex == 4 && InstrumentTone.Drums.Tone4.Equals(Instrument.None)))
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid DrumTone mapping \"" + mapping + "\" on track " + trackNumber + " because \"" + split[1] + "\" is not a valid tone number for Tone " + InstrumentTone.Drums.Name);
                                    continue;
                                }
                                if (!int.TryParse(split[2], out var destinationNote) || destinationNote < -1 || destinationNote > 36)
                                {
                                    BmpLog.W(BmpLog.Source.Transmogrify, "Skipping invalid DrumTone mapping \"" + mapping + "\" on track " + trackNumber + " because destination note \"" + split[2] + "\" is more then 36 or less then -1");
                                    continue;
                                }
                                drumToneConfig.Mapper[sourceNote] = (toneIndex, destinationNote);
                                drumToneSubConfigurations++;
                            }
                        }
                        ParseAdditionalOptions(trackNumber, drumToneConfig, song, fields);
                        BmpLog.I(BmpLog.Source.Transmogrify, "Found DrumTone Config Group with " + drumToneSubConfigurations + " override mappings on track " + drumToneConfig.Track + " ;bards=" + drumToneConfig.PlayerCount + ";include=" + string.Join(",", drumToneConfig.IncludedTracks));
                        configContainers.Add(groupCounter, configContainer);
                        continue;

                    case "lyric":
                        var lyricConfig = (LyricProcessorConfig)(configContainer.ProcessorConfig = new LyricProcessorConfig {
                            Track = trackNumber
                        });
                        ParseAdditionalOptions(trackNumber, lyricConfig, song, fields);
                        BmpLog.I(BmpLog.Source.Transmogrify, "Found Lyric Config on track " + lyricConfig.Track + " ;bards=" + lyricConfig.PlayerCount + ";include=" + string.Join(",", lyricConfig.IncludedTracks));
                        configContainers.Add(groupCounter, configContainer);
                        continue;
                    }
                }

                // bmp 1.x style group name
                else
                {
                    var classicConfig            = (ClassicProcessorConfig)(configContainer.ProcessorConfig = new ClassicProcessorConfig {
                        Track = trackNumber
                    });
                    var instrumentAndOctaveRange = modifier.Match(fields[0]);
                    if (!instrumentAndOctaveRange.Success)
                    {
                        continue;                                    // Invalid Instrument name.
                    }
                    if (instrumentAndOctaveRange.Groups[1].Success)
                    {
                        classicConfig.Instrument = Instrument.Parse(instrumentAndOctaveRange.Groups[1].Value);
                    }
                    if (classicConfig.Instrument.Equals(Instrument.None))
                    {
                        continue;                                                    // Invalid Instrument name.
                    }
                    if (instrumentAndOctaveRange.Groups[2].Success)
                    {
                        classicConfig.OctaveRange = OctaveRange.Parse(instrumentAndOctaveRange.Groups[2].Value);
                    }
                    if (classicConfig.OctaveRange.Equals(OctaveRange.Invalid))
                    {
                        classicConfig.OctaveRange = OctaveRange.C3toC6;
                    }
                    ParseAdditionalOptions(trackNumber, classicConfig, song, fields);
                    BmpLog.I(BmpLog.Source.Transmogrify, "Found Classic Config Instrument " + classicConfig.Instrument.Name + " OctaveRange " + classicConfig.OctaveRange.Name + " on track " + classicConfig.Track + " ;bards=" + classicConfig.PlayerCount + ";include=" + string.Join(",", classicConfig.IncludedTracks));
                    configContainers.Add(groupCounter, configContainer);
                }
            }

            if (configContainers.Count == 0)
            {
                BmpLog.I(BmpLog.Source.Transmogrify, "Found 0 configurations on track " + trackNumber + ", and the keyword \"Ignore\" is not in the track title. Adding a default AutoTone.");
                configContainers.Add(0, new ConfigContainer {
                    ProcessorConfig = new AutoToneProcessorConfig {
                        Track = trackNumber
                    }
                });
            }

            return(configContainers);
        }