private char GetSMCharForNote(LaneNote note) { if (note.DestType?.Length == 1 && SMCommon.NoteChars.Contains(note.DestType[0])) { return(note.DestType[0]); } if (MatchesSourceFileFormatType() && note.SourceType?.Length == 1 && SMCommon.NoteChars.Contains(note.SourceType[0])) { return(note.SourceType[0]); } if (note is LaneTapNote) { return(SMCommon.NoteChars[(int)SMCommon.NoteType.Tap]); } if (note is LaneHoldEndNote) { return(SMCommon.NoteChars[(int)SMCommon.NoteType.HoldEnd]); } if (note is LaneHoldStartNote) { return(SMCommon.NoteChars[(int)SMCommon.NoteType.HoldStart]); } return(SMCommon.NoteChars[(int)SMCommon.NoteType.None]); }
/// <summary> /// Creates an ExpressedChart MineEvent. /// If possible, this MineEvent will be an AfterArrow type. /// If no arrow precedes this mine then this MineEvent will be a BeforeArrow type. /// If no arrow exists in the same lane as the mine then this will be a NoArrow type. /// </summary> /// <param name="numArrows"> /// Number of arrows in the chart being used to generate the ExpressedChart. /// This is used to ease the searches into neighboring events. /// </param> /// <param name="releases"> /// List of all FootActionEvents representing release events for the ExpressedChart. /// See GetReleasesAndSteps for release FootActionEvent generation. /// </param> /// <param name="releaseIndex"> /// The index into releases of the release event which precedes the given mine. /// This could be determined by this method, but requiring it as a parameter is a /// performance optimization as it allows the caller to call this method in loop /// and avoid another scan. /// </param> /// <param name="steps"> /// List of all FootActionEvents representing step events for the ExpressedChart. /// See GetReleasesAndSteps for release FootActionEvent generation. /// </param> /// <param name="stepIndex"> /// The index into steps of the step event which follows the given mine. /// This could be determined by this method, but requiring it as a parameter is a /// performance optimization as it allows the caller to call this method in loop /// and avoid another scan. /// </param> /// <param name="mine">The LaneNote representing a mine from the original chart.</param> /// <returns>New MineEvent.</returns> public static ExpressedChart.MineEvent CreateExpressedMineEvent( int numArrows, List <FootActionEvent> releases, int releaseIndex, List <FootActionEvent> steps, int stepIndex, LaneNote mine) { // Try first to create an AfterArrow type of mine by associating the mine // with a preceding arrow. var(n, f) = GetHowRecentIsNeighboringArrow(true, releaseIndex, numArrows, releases, mine.Lane); if (n >= 0) { return(new ExpressedChart.MineEvent(mine.Position, mine.TimeMicros, mine.Lane) { Type = MineType.AfterArrow, ArrowIsNthClosest = n, FootAssociatedWithPairedNote = f }); } // Next, try to create a BeforeArrow type of mine by associating the mine // with a following arrow. (n, f) = GetHowRecentIsNeighboringArrow(false, stepIndex, numArrows, steps, mine.Lane); if (n >= 0) { return(new ExpressedChart.MineEvent(mine.Position, mine.TimeMicros, mine.Lane) { Type = MineType.BeforeArrow, ArrowIsNthClosest = n, FootAssociatedWithPairedNote = f }); } // The mine could not be associated with an arrow, use the default NoArrow type. return(new ExpressedChart.MineEvent(mine.Position, mine.TimeMicros, mine.Lane)); }
public LaneNote(LaneNote other) : base(other) { Lane = other.Lane; }
/// <summary> /// Parses the sm/ssc string representation of measures and notes as /// events into the given Chart. /// </summary> /// <param name="chart">Chart to parse notes into.</param> /// <param name="notesStr">String representation of notes from an sm or ssc chart.</param> /// <returns>Whether the notes represent a valid chart or not.</returns> protected bool ParseNotes(Chart chart, string notesStr) { if (chart.NumInputs < 1) { Logger?.Warn( $"Cannot parse notes for {chart.Type} {chart.DifficultyType} Chart. Unknown number of inputs. This Chart will be ignored."); return(false); } var validChart = true; var player = 0; var measure = 0; var currentMeasureEvents = new List <Event>(); notesStr = notesStr.Trim(SMCommon.SMAllWhiteSpace); var notesStrsPerPlayer = notesStr.Split('&'); foreach (var notesStrForPlayer in notesStrsPerPlayer) { var holding = new bool[chart.NumInputs]; var rolling = new bool[chart.NumInputs]; // RemoveEmptyEntries seems wrong, but matches Stepmania parsing logic. var measures = notesStrForPlayer.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (var measureStr in measures) { var lines = measureStr.Trim(SMCommon.SMAllWhiteSpace) .Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); var linesInMeasure = lines.Length; var lineInMeasure = 0; foreach (var line in lines) { var trimmedLine = line.Trim(SMCommon.SMAllWhiteSpace); // Parse this line as note data for (int charIndex = 0, laneIndex = 0; charIndex < trimmedLine.Length && laneIndex < chart.NumInputs; charIndex++, laneIndex++) { // Get the note type. var c = trimmedLine[charIndex]; var noteType = SMCommon.NoteType.None; var noteString = SMCommon.NoteStrings[(int)noteType]; for (var i = 0; i < SMCommon.NoteChars.Length; i++) { if (c == SMCommon.NoteChars[i]) { noteType = (SMCommon.NoteType)i; noteString = SMCommon.NoteStrings[i]; break; } } // Validation. if (noteType == SMCommon.NoteType.Tap || noteType == SMCommon.NoteType.Mine || noteType == SMCommon.NoteType.Lift || noteType == SMCommon.NoteType.Fake || noteType == SMCommon.NoteType.KeySound || noteType == SMCommon.NoteType.HoldStart || noteType == SMCommon.NoteType.RollStart) { if (holding[laneIndex]) { Logger?.Error( $"Invalid {chart.Type} {chart.DifficultyType} Chart. {noteString} during hold on lane {laneIndex} during measure {measure}. This Chart will be ignored."); validChart = false; } if (rolling[laneIndex]) { Logger?.Error( $"Invalid {chart.Type} {chart.DifficultyType} Chart. {noteString} during roll on lane {laneIndex} during measure {measure}. This Chart will be ignored."); validChart = false; } } else if (noteType == SMCommon.NoteType.HoldEnd) { if (!holding[laneIndex] && !rolling[laneIndex]) { Logger?.Error( $"Invalid {chart.Type} {chart.DifficultyType} Chart. {noteString} while neither holding nor rolling on lane {laneIndex} during measure {measure}. This Chart will be ignored."); validChart = false; } } if (!validChart) { break; } // Create a LaneNote based on the note type. LaneNote note = null; if (noteType == SMCommon.NoteType.Tap || noteType == SMCommon.NoteType.Fake || noteType == SMCommon.NoteType.Lift) { note = new LaneTapNote { SourceType = c.ToString() }; } else if (noteType == SMCommon.NoteType.Mine || noteType == SMCommon.NoteType.KeySound) { note = new LaneNote { SourceType = c.ToString() }; } else if (noteType == SMCommon.NoteType.HoldStart) { holding[laneIndex] = true; note = new LaneHoldStartNote { SourceType = c.ToString() }; } else if (noteType == SMCommon.NoteType.RollStart) { rolling[laneIndex] = true; note = new LaneHoldStartNote { SourceType = c.ToString() }; } else if (noteType == SMCommon.NoteType.HoldEnd) { note = new LaneHoldEndNote { SourceType = c.ToString() }; holding[laneIndex] = false; rolling[laneIndex] = false; } // Keysound parsing. // TODO: Parse keysounds properly. For now, putting them in SourceExtras. if (charIndex + 1 < trimmedLine.Length && trimmedLine[charIndex + 1] == '[') { var startIndex = charIndex + 1; while (charIndex < trimmedLine.Length) { if (trimmedLine[charIndex] == ']') { break; } charIndex++; } var endIndex = charIndex - 1; if (endIndex > startIndex && note != null) { if (int.TryParse(trimmedLine.Substring(startIndex, endIndex - startIndex), out var keySoundIndex)) { note.Extras.AddSourceExtra(SMCommon.TagFumenKeySoundIndex, keySoundIndex, true); } } } // Deprecated Attack parsing if (charIndex + 1 < trimmedLine.Length && trimmedLine[charIndex + 1] == '{') { while (charIndex < trimmedLine.Length) { if (trimmedLine[charIndex] == '}') { break; } charIndex++; } } // Deprecated Item parsing if (charIndex + 1 < trimmedLine.Length && trimmedLine[charIndex + 1] == '<') { while (charIndex < trimmedLine.Length) { if (trimmedLine[charIndex] == '>') { break; } charIndex++; } } // No note at this position, continue. if (null == note) { continue; } // Configure common parameters on the note and add it. var beat = (lineInMeasure * SMCommon.NumBeatsPerMeasure) / linesInMeasure; var subDivisionNumerator = lineInMeasure * SMCommon.NumBeatsPerMeasure - linesInMeasure * beat; var subDivisionDenominator = linesInMeasure; note.Lane = laneIndex; note.Player = player; note.Position = new MetricPosition(measure, beat, subDivisionNumerator, subDivisionDenominator); currentMeasureEvents.Add(note); } if (!validChart) { break; } // Advance line marker lineInMeasure++; } if (!validChart) { break; } chart.Layers[0].Events.AddRange(currentMeasureEvents); currentMeasureEvents.Clear(); measure++; } // Validation. for (var i = 0; i < chart.NumInputs; i++) { if (holding[i]) { Logger?.Error( $"Invalid {chart.Type} {chart.DifficultyType} Chart. Incomplete hold on lane {i}. This Chart will be ignored."); validChart = false; } if (rolling[i]) { Logger?.Error( $"Invalid {chart.Type} {chart.DifficultyType} Chart. Incomplete roll on lane {i}. This Chart will be ignored."); validChart = false; } } if (!validChart) { break; } player++; } return(validChart); }