public void TemporaryBeatRemover_RemovesAddedBeats() { var testBeat = new Ebeat(222222, 1); var addedBeats = new List <Ebeat> { testBeat }; testArrangement.Ebeats.Add(testBeat); new TemporaryBeatRemover(addedBeats).Apply(testArrangement, nullLog); testArrangement.Ebeats.Should().NotContain(testBeat); }
public void BeatsInOrder(SngFile song) { Ebeat lastBeat = null; foreach (var beat in song.Beats) { if (lastBeat != null) { Assert.IsTrue(lastBeat.Time < beat.Time); if (lastBeat.Measure == beat.Measure) { Assert.AreEqual(lastBeat.Beat + 1, beat.Beat); } else { Assert.AreEqual(beat.Beat, 0); Assert.LessOrEqual(lastBeat.Measure + 1, beat.Measure); } } lastBeat = beat; } }
public TabFile(SngFile sngFile, int maxDifficulty) { StringCount = 6; _measureQueue = new List <TabMeasure>(); _currentMeasure = null; Lines = new List <string>(); // holds text stream // Create the tuning information table TuningInfos = new Dictionary <Tuning, TuningInfo>(); TuningInfos[Tuning.Standard] = new TuningInfo(this, Tuning.Standard, "Standard", new string[] { "e", "B", "G", "D", "A", "E" }); TuningInfos[Tuning.DropD] = new TuningInfo(this, Tuning.DropD, "Drop D", new string[] { "e", "B", "G", "D", "A", "D" }); TuningInfos[Tuning.EFlat] = new TuningInfo(this, Tuning.EFlat, "E Flat", new string[] { "eb", "Bb", "Gb", "Db", "Ab", "Eb" }); TuningInfos[Tuning.OpenG] = new TuningInfo(this, Tuning.OpenG, "Open G", new string[] { "D", "B", "D", "G", "D", "G" }); TuningInfo = TuningInfos[(Tuning)sngFile.Metadata.Tuning]; // Start assembling the tab TabHeader header = new TabHeader(this, sngFile); /* Tab Entities are the parts that together make up a tab, i.e. notes, chords, measures and sections. * Since the notes and chords are all contained in a single array without any measure or section data, * the measure and section data needs to be merged with notes and chords so that all of that data can be * iterated over in the correct order. Entities get sorted by time index (the SortedList key). * Multiple entities (of different types) can have the same time index and need to be processed in the * correct order, i.e. sections before measures and measures before notes/chords. */ SortedList <float, LinkedList <TabEntity> > entities = new SortedList <float, LinkedList <TabEntity> >(); // Add SECTION entities foreach (SongSection s in sngFile.SongSections) { addEntity(new TabSection(this, s), entities); } // Add MEASURE entities float lastTime = sngFile.Beats[0].Time; int beatCount = 1; int lastMeasure = sngFile.Beats[0].Measure; for (int i = 1; i < sngFile.Beats.Length; i++) { Ebeat b = sngFile.Beats[i]; // To indicate that the current beat is simply the next beat in the current measure and does not define // a new measure, either set b.Measure to -1 or to the same value as the beat before if (b.Measure == -1 || b.Measure == lastMeasure) { beatCount++; } else { addEntity(new TabMeasure(this, lastTime, b.Time, beatCount), entities); lastTime = b.Time; beatCount = 1; lastMeasure = b.Measure; } } // Don't forget the final measure TabMeasure finalMeasure = new TabMeasure(this, lastTime, sngFile.Beats[sngFile.Beats.Length - 1].Time, beatCount); addEntity(finalMeasure, entities); MaxDifficulty = 0; // Add NOTE / CHORD entities foreach (PhraseIteration pi in sngFile.PhraseIterations) { Phrase p = sngFile.Phrases[pi.Id]; if (MaxDifficulty < p.MaxDifficulty) { MaxDifficulty = p.MaxDifficulty; } int level = Math.Min(maxDifficulty, p.MaxDifficulty); List <Note> notes = new List <Note>(); foreach (Note n in sngFile.SongLevels[level].Notes) { if (n.Time < pi.StartTime || n.Time > pi.EndTime) { continue; } if (n.ChordId != -1) { addEntity(new TabChord(this, n, sngFile), entities); } else { addEntity(new TabNote(this, n), entities); } } } // Now that the entity list has been build, iterate over it and Apply every single entity to this TabFile, // which will format the entity's data and add it to the TabFile's output buffer. header.Apply(this, maxDifficulty); foreach (LinkedList <TabEntity> lle in entities.Values) { foreach (TabEntity e in lle) { e.Apply(this); // "this" contains a stream to make the tablature text } } flushMeasureQueue(); // Don't forget, otherwise the last section's measures will be missing }
public void Apply(InstrumentalArrangement arrangement, Action <string> Log) { var phrasesToMove = arrangement.Phrases .Where(p => p.Name.StartsWith("moveto", StringComparison.OrdinalIgnoreCase) || p.Name.StartsWith("moveR", StringComparison.OrdinalIgnoreCase)) .ToList(); if (phrasesToMove.Count > 0) { Log("Processing 'move' phrases:"); } else { return; } foreach (var phraseToMove in phrasesToMove) { // Find phrase iterations by matching phrase index int phraseId = arrangement.Phrases.IndexOf(phraseToMove); foreach (var phraseIterationToMove in arrangement.PhraseIterations.Where(pi => pi.PhraseId == phraseId)) { int phraseTime = phraseIterationToMove.Time; int movetoTime; string phraseToMoveName = phraseToMove.Name; // Relative phrase moving right if (phraseToMoveName.StartsWith("moveR", StringComparison.OrdinalIgnoreCase)) { if (int.TryParse(phraseToMoveName.Substring("moveR".Length), NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out int moveRightBy)) { var level = arrangement.Levels[phraseToMove.MaxDifficulty]; movetoTime = TimeParser.FindTimeOfNthNoteFrom(level, phraseTime, moveRightBy); } else { string errorMessage = $"Unable to read value for 'moveR' phrase at {phraseTime.TimeToString()}. (Usage example: moveR2)"; _statusMessages.Add(new ImproverMessage(errorMessage, MessageType.Warning)); Log(errorMessage); continue; } } else // Parse the absolute time to move to from the phrase name { int?parsedTime = TimeParser.Parse(phraseToMoveName); if (parsedTime.HasValue) { movetoTime = parsedTime.Value; } else { MoveToParseFailure(phraseTime, Log); continue; } } // Check if anchor(s) should be moved foreach (var level in arrangement.Levels) { if (level.Difficulty > phraseToMove.MaxDifficulty) { break; } var anchors = level.Anchors; int originalAnchorIndex = anchors.FindIndexByTime(phraseTime); int movetoAnchorIndex = anchors.FindIndexByTime(movetoTime); // If there is an anchor at the original position, but not at the new position, move it if (originalAnchorIndex != -1 && movetoAnchorIndex == -1) { var originalAnchor = anchors[originalAnchorIndex]; anchors.Insert(originalAnchorIndex + 1, new Anchor(originalAnchor.Fret, movetoTime, originalAnchor.Width)); // Remove anchor at original phrase position if no note or chord present if (level.Notes.FindIndexByTime(phraseTime) == -1 && level.Chords.FindIndexByTime(phraseTime) == -1) { anchors.RemoveAt(originalAnchorIndex); Log($"--Moved anchor from {phraseTime.TimeToString()} to {movetoTime.TimeToString()}"); } else { Log($"--Added anchor at {movetoTime.TimeToString()}"); } } } // Move phraseIteration phraseIterationToMove.Time = movetoTime; // Move section (if present) var sectionToMove = arrangement.Sections.FindByTime(phraseTime); if (sectionToMove is not null) { sectionToMove.Time = movetoTime; Log($"--Moved phrase and section from {phraseTime.TimeToString()} to {movetoTime.TimeToString()}"); } else { Log($"--Moved phrase from {phraseTime.TimeToString()} to {movetoTime.TimeToString()}"); } // Add new temporary beat var beatToAdd = new Ebeat(movetoTime, XMLProcessor.TempMeasureNumber); var insertIndex = arrangement.Ebeats.FindIndex(b => b.Time > movetoTime); arrangement.Ebeats.Insert(insertIndex, beatToAdd); // Set the beat for later removal _addedBeats.Add(beatToAdd); } } }