[SuppressMessage("ReSharper", "PossibleNullReferenceException")] void ParseMapFile(string filename) { var expressionMapRegEx = new Regex(@"(Map|Base|RootKey|BaseChannel|Art|Remotes|StdRemotes|KeyMode)\s*(\[[^]]*\])?:\s*(?:([^,]+),?)+"); var defaultRootKey = Array.IndexOf(NoteNames, "C0"); var lines = File.ReadAllLines(filename).Where(s => s != ""); ExpressionMap map = null; var rootKey = defaultRootKey; var keyMode = KeyModes.Chromatic; var keyStep = 0; foreach (var line in lines) { var match = expressionMapRegEx.Matches(line)[0]; Debug.Assert(match.Groups[1].Captures[0].Value.ToLower() == "map" || map != null, "Must declare map block first."); switch (match.Groups[1].Captures[0].Value.ToLower()) { case "map": map?.AssignRemotes(); var mapName = match.Groups[3].Captures[0].Value; ExpressionMaps[mapName] = map = new ExpressionMap(mapName, defaultRootKey); rootKey = defaultRootKey; keyMode = KeyModes.Chromatic; keyStep = 0; break; case "base": var baseMap = ExpressionMaps[match.Groups[3].Captures[0].Value]; map = ExpressionMaps[map.Name] = new ExpressionMap(map.Name, baseMap); rootKey = map.RootKey; keyStep = 0; keyMode = KeyModes.Chromatic; // note the key node is not inherited, and the key step restarts at zero. break; case "rootkey": rootKey = Array.IndexOf(NoteNames, match.Groups[3].Captures[0].Value); map.ChangeRootKey(rootKey); break; case "moverootkey": rootKey = Array.IndexOf(NoteNames, match.Groups[3].Captures[0].Value); map.MoveRootKey(rootKey); break; case "keymode": keyMode = match.Groups[3].Captures[0].Value.ToLower() == "diatonic" ? KeyModes.Diatonic : KeyModes.Chromatic; break; case "basechannel": map.BaseChannel = int.Parse(match.Groups[3].Captures[0].Value) - 1; break; case "stdremotes": map.StandardRemoteAssignments = match.Groups[3].Captures[0].Value.ToLower() == "true"; break; case "art": if (map.InheritedArticulations) { map.Articulations.Clear(); map.InheritedArticulations = false; } var key = rootKey; map.Remotes.Clear(); var defaultAttributesText = match.Groups[2].Captures.Count > 0 ? ("Default" + match.Groups[2].Captures[0].Value) : ""; foreach (Capture articulationEntry in match.Groups[3].Captures) { var articulation = new Articulation("", -1, -1, true, true, -1, -1); if (defaultAttributesText != "") { articulation = ParseArticulationAttributes(articulation, defaultAttributesText, key); } articulation = ParseArticulationAttributes(articulation, articulationEntry.Value, key); if (articulation.Keyswitched) { key += keyMode == KeyModes.Diatonic ? DiatonicSteps[keyStep++] : 1; } if (articulation.Name.ToLower() != "none") { map.Articulations.Add(articulation); if (articulation.MuteCC != -1) { Debug.Assert(map.MuteCC == -1); map.MuteCC = articulation.MuteCC; } } } break; case "remotes": map.Remotes.Clear(); foreach (Capture remoteEntry in match.Groups[3].Captures) { var remoteName = remoteEntry.Value.ToLower(); if (remoteName.ToLower() == "none") { map.Remotes.Add(new Articulation("None", -1, -1, true, false, -1, -1)); } else { var articulationNames = map.Articulations.Select(c => c.Name.ToLower()).ToArray(); var articulation = map.Articulations.First(c => c.Name.ToLower().StartsWith(remoteName, StringComparison.Ordinal)); map.Remotes.Add(articulation); } } break; default: Debug.Assert(false); break; } } map?.AssignRemotes(); }