public BeatTracker(BeatTracker tracker) { Interval = tracker.Interval; NextPrediction = tracker.NextPrediction; ProcessedItems = new List <BeatEvent>(tracker.ProcessedItems); Rating = tracker.Rating; OriginalScore = tracker.OriginalScore; }
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); }
public static BeatTracker FindBeat(List <IntervalCluster> tempoHypotheses, List <BeatEvent> events) { /* First, create all the trackers. */ List <BeatTracker> trackers = new List <BeatTracker>(); for (int i = 0; i < tempoHypotheses.Count; i++) { IntervalCluster cluster = tempoHypotheses[i]; foreach (BeatEvent startEvent in events.Where(e => e.Time < initialPeriod).ToList()) { trackers.Add(new BeatTracker(cluster.MeanLength, startEvent, cluster.Rating)); } } /* Iterate through every event in the rhythm, processing each tracker. */ foreach (BeatEvent _event in events) { List <BeatTracker> newTrackers = new List <BeatTracker>(); for (int i = trackers.Count - 1; i >= 0; i--) { /* If any tracker has gone too long without detecting a beat candidate, drop it. */ BeatTracker tracker = trackers[i]; if (_event.Time - tracker.ProcessedItems[tracker.ProcessedItems.Count - 1].Time > maximumInterval) { trackers.RemoveAt(i); } else { /* Catch the trackers up with the current event. */ while (tracker.NextPrediction + (outerWindowFactor * tracker.Interval) < _event.Time) { tracker.NextPrediction += tracker.Interval; } /* Check whether the event is a feasible beat time. */ if (_event.Time > tracker.NextPrediction - (outerWindowFactor * tracker.Interval) && (_event.Time < tracker.NextPrediction + (outerWindowFactor * tracker.Interval))) { /* If it's too close to be sure, create another tracker. */ if (Math.Abs(_event.Time - tracker.NextPrediction) > innerWindow) { newTrackers.Add(new BeatTracker(tracker)); } /* Update the tracker to prepare for the next loop iteration. */ double error = _event.Time - tracker.NextPrediction; tracker.Interval += error * correctionFactor; tracker.NextPrediction = _event.Time + tracker.Interval; tracker.ProcessedItems.Add(_event); // NOTE: It might be useful to have drum specific stuff as well as velocity tracker.Rating += (1 - (Math.Abs(error) / (2 * tracker.NextPrediction))) * _event.Notes.Sum(n => n.Velocity); } } } // Add new trackers to the list foreach (BeatTracker tracker in newTrackers) { trackers.Add(tracker); } // Remove duplicate trackers List <BeatTracker> nextTrackers = new List <BeatTracker>(); foreach (BeatTracker tracker in trackers) { bool matchFound = false; foreach (BeatTracker nextTracker in nextTrackers) { if (!matchFound && SimilarTrackers(tracker, nextTracker)) { nextTracker.TakeBestTracker(tracker); matchFound = true; } } if (!matchFound) { nextTrackers.Add(tracker); } } trackers = nextTrackers; } /* Weight each tracker's rating by the original score, so as to avoid very short beat selections. */ foreach (BeatTracker tracker in trackers) { tracker.Rating *= tracker.OriginalScore; } trackers = trackers.OrderByDescending(t => t.Rating).ToList(); // TODO: There are a lot of trackers that appear to be duplicates. Investigate. for (int i = 0; i < Math.Min(trackers.Count, 10); i++) { trackers[i].PrintTracker(); } return(trackers[0]); }
/* Fuzzy comparison of trackers, since they can be non-identical but functionally the same. */ static bool SimilarTrackers(BeatTracker first, BeatTracker second) { return((Math.Abs(first.Interval - second.Interval) < 10) && (Math.Abs(first.NextPrediction - second.NextPrediction) < 20)); }