/* This function strips down a rhythm into its smallest repeating unit as a power of two semiquavers. */ public static RhythmStructure FindRepeatingUnit(RhythmStructure rhythm) { int rhythmLength = rhythm.drums.Count; int maxRepeatingUnitLength = 4 * (rhythmLength / 4); bool repeatingUnitFound = false; int unitLength = 4; RhythmStructure rhythmUnit = new RhythmStructure(rhythm.beatInterval); /* Basically, start with a unit of length one and check it against the rest of the rhythm, * if it doesn't match keep doubling the length until it matches, or take the original rhythm. */ while (!repeatingUnitFound && unitLength <= maxRepeatingUnitLength) { rhythmUnit = rhythm.CopySub(0, unitLength, rhythm.beatInterval); int repetitions = (rhythmLength / unitLength) - 1; repeatingUnitFound = true; for (int i = 0; i < repetitions; i++) { if (!rhythm.CheckMatch(rhythmUnit, (i + 1) * unitLength)) { repeatingUnitFound = false; } } unitLength += 4; } return(rhythmUnit); }
public RhythmStructure CopySub(int startIndex, int length, double interval) { RhythmStructure copy = new RhythmStructure(interval); for (int i = startIndex; i < startIndex + length; i++) { copy.AddDrums(GetAtIndex(i)); } return(copy); }
public static HierarchicalRhythm CreateHierarchicalRhythm(RhythmStructure rhythm) { RhythmStructure rhythmCopy = new RhythmStructure(rhythm); int treeDepth = (int)Math.Log(rhythm.drums.Count, 2); HierarchicalRhythm hRhythm = new HierarchicalRhythm(rhythm.beatInterval, treeDepth); foreach (Drum drum in drumList) { /* Tree level 0 is the root, for drums that occur on every semiquaver of the rhythm, * and the bottom level is the notes that occur only once across the rhythm. */ for (int level = 0; level <= treeDepth; level++) { int intervalBetweenNotes = (int)Math.Pow(2, level); /* This index is the amount that the recurring note is "shifted" across the rhythm. * For example, if we're at minim level, then at i=3 we have notes occuring every minim, * starting on the semiquaver with index 3. */ for (int shift = 0; shift < intervalBetweenNotes; shift++) { /* If the particular drum we're looking at is found in the rhythm, * at the location we're dealing with, start scanning along. */ if (rhythmCopy.GetAtIndex(shift).Contains(drum)) { /* Finally, iterate across the entire rhythm according to the shift and interval. * TODO: This isn't very efficient, would be better to stop immediately if there's a mismatch. */ bool notesLineUp = true; for (int i = shift; i < rhythmCopy.drums.Count; i += intervalBetweenNotes) { if (!rhythmCopy.GetAtIndex(i).Contains(drum)) { notesLineUp = false; } } if (notesLineUp) { hRhythm.AddDrum(level, shift, drum); for (int i = shift; i < rhythmCopy.drums.Count; i += intervalBetweenNotes) { rhythmCopy.RemoveDrumAt(i, drum); } } } } } } return(hRhythm); }
// Check all of the input rhythm against the subsection of this rhythm given by the parameters. public bool CheckMatch(RhythmStructure otherRhythm, int startIndex) { bool match = true; for (int i = startIndex; i < startIndex + otherRhythm.drums.Count; i++) { int otherRhythmIndex = i - startIndex; if (!drums[i].SetEquals(otherRhythm.drums[otherRhythmIndex])) { match = false; } } return(match); }
static void Main(string[] args) { bool edit_only = false; bool find_hierarchy = true; if (!edit_only) { MidiReader reader = new MidiReader(); Console.WriteLine("Ready to record. Press any key to begin."); Console.ReadKey(); reader.Start(); Console.WriteLine("Recording started. Press any key to stop the recording."); Console.ReadKey(); reader.Stop(); Console.WriteLine("Recording finished. Processing..."); List <BeatEvent> beatEvents = TempoInferrer.NotesToEvents(reader.FetchEventList()); List <IntervalCluster> intervalClusters = TempoInferrer.EventsToClusters(beatEvents); intervalClusters = TempoInferrer.RateClusters(intervalClusters); BeatTracker finalBeat = BeatInferrer.FindBeat(intervalClusters, beatEvents); RhythmStructure rhythm = RhythmCreator.CreateRhythm(finalBeat, beatEvents); if (find_hierarchy) { rhythm = HierarchicalRhythmInferrer.FindRepeatingUnit(rhythm); } AsciiTabRenderer.RenderAsciiTab(rhythm); Console.WriteLine("ASCII Tab rendered to tab.txt."); if (find_hierarchy) { HierarchicalRhythm hRhythm = HierarchicalRhythmInferrer.CreateHierarchicalRhythm(rhythm); hRhythm.Print(); } } /* Read in (possibly edited) ASCII tab and convert to Sonic Pi */ SonicPiEmitter.EmitSonicPi(); Console.WriteLine("Sonic Pi code emitted to sonicpi.txt"); Console.ReadKey(); }
/* Create a rhythm from a tracker and list of events, by quantizing the events * according to the beat tracker given to the function. */ public static RhythmStructure CreateRhythm(BeatTracker tracker, List <BeatEvent> events) { const byte numDivisions = 4; int eventIndex = 0; RhythmStructure rhythm = new RhythmStructure(tracker.Interval); /* Iterate over all the beats and semiquavers, and set the semiquaver interval. */ for (int i = 0; i < tracker.ProcessedItems.Count; i++) { BeatEvent baseEvent = tracker.ProcessedItems[i]; double interval; if (i != tracker.ProcessedItems.Count - 1) { BeatEvent nextEvent = tracker.ProcessedItems[i + 1]; interval = (nextEvent.Time - baseEvent.Time) / 4; } else { interval = (tracker.NextPrediction - baseEvent.Time) / 4; } for (int j = 0; j < numDivisions; j++) { HashSet <Drum> drums = new HashSet <Drum>(); /* Determine the time at which one semiquaver event occurs and then quantizes each note event to the semiquaver. */ double baseTime = baseEvent.Time + (j * interval); if (eventIndex < events.Count && baseTime - (interval / 2) < events[eventIndex].Time && baseTime + (interval / 2) > events[eventIndex].Time) { foreach (NoteEvent noteEvent in events[eventIndex].Notes) { if (indexToDrum.Keys.Contains(noteEvent.Channel)) { Drum drum = indexToDrum[noteEvent.Channel]; drums.Add(drum); } } eventIndex++; } rhythm.AddDrums(drums); } } return(rhythm); }
/* We render the ASCII tab based on the original rhythm. */ public static void RenderAsciiTab(RhythmStructure rhythm) { Dictionary <string, string> tab = new Dictionary <string, string>(); foreach (LabelSymbolPair pair in drumToName.Values) { tab[pair.label] = pair.label + "|"; } /* Initialize each slot with the "-" symbol then replace it if we find any of the right drum during this interval. */ for (int i = 0; i < rhythm.drums.Count; i++) { HashSet <Drum> drums = rhythm.drums[i]; foreach (string index in tab.Keys.ToList()) { tab[index] += "-"; } foreach (Drum drum in drums) { if (drumToName.Keys.Contains(drum)) { LabelSymbolPair pair = drumToName[drum]; tab[pair.label] = tab[pair.label].Remove(i + 3, 1).Insert(i + 3, pair.symbol); } } } /* Finally write out the tab to a text file. */ using (StreamWriter writer = new StreamWriter("tab.txt")) { writer.WriteLine(Convert.ToInt32(rhythm.beatInterval)); foreach (string str in tab.Values) { writer.WriteLine(str); } } }
// NOTE: This copy may need to go deeper as it copies hash set references. public RhythmStructure(RhythmStructure rhythm) { beatInterval = rhythm.beatInterval; drums = new List <HashSet <Drum> >(rhythm.drums); }