/* If we want to merge two clusters together, we add all the intervals from one into the other. */ public void MergeCluster(IntervalCluster cluster) { foreach (EventInterval interval in cluster.Intervals) { AddInterval(interval); } }
/* In order to obtain a ranking for our different hypotheses, we compare the clusters * against each other to see which of them are integer multiples within a reasonable margin. * Those with many multiples will have a higher score overall than ones without. */ public static List <IntervalCluster> RateClusters(List <IntervalCluster> clusters) { foreach (IntervalCluster baseCluster in clusters) { foreach (IntervalCluster comparisonCluster in clusters) { for (int i = 1; i < 9; i++) { if (Math.Abs(baseCluster.MeanLength - (i * comparisonCluster.MeanLength)) < clusterWidth) { baseCluster.Rating += Weight(i) * comparisonCluster.Intervals.Count; } } } } /* Order the clusters by their rating from highest to lowest and then print them if desired. */ clusters = clusters.OrderByDescending(c => c.Rating).ToList(); /* Remove clusters with unrealistically large or small intervals, * greater than 240 BPM or less than 60 BPM. */ for (int i = clusters.Count - 1; i >= 0; i--) { IntervalCluster cluster = clusters[i]; if (cluster.MeanLength < 250 || cluster.MeanLength > 1000) { clusters.Remove(cluster); } } if (debugPrintRatedClusters) { foreach (IntervalCluster cluster in clusters) { Console.WriteLine("Interval Cluster " + cluster.GetBPM() + " BPM, with " + cluster.Intervals.Count + " notes, score " + cluster.Rating + "."); } } return(clusters); }
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]); }
/* EventsToClusters takes a list of beat events, finds all the intervals between them * that are less than 2 seconds long, and then clusters the intervals together into a list of * tempo hypotheses. */ public static List <IntervalCluster> EventsToClusters(List <BeatEvent> beatEvents) { List <IntervalCluster> clusters = new List <IntervalCluster>(); for (int i = 0; i < beatEvents.Count; i++) { for (int j = i + 1; j < beatEvents.Count; j++) { EventInterval interval = new EventInterval(beatEvents[i], beatEvents[j]); if (interval.Length < 2000) { bool clusterFound = false; int clusterIndex = 0; double clusterDistance = double.PositiveInfinity; for (int c = 0; c < clusters.Count; c++) { IntervalCluster cluster = clusters[c]; double difference = Math.Abs(cluster.MeanLength - interval.Length); if (difference < clusterWidth && difference < clusterDistance) { clusterFound = true; clusterIndex = c; clusterDistance = difference; } } if (clusterFound) { clusters[clusterIndex].AddInterval(interval); } else { clusters.Add(new IntervalCluster(interval)); } } } } /* Now cluster the clusters just in case any of the averages have strayed close together. */ List <IntervalCluster> newClusters = new List <IntervalCluster>(); foreach (IntervalCluster cluster in clusters) { bool matchFound = false; foreach (IntervalCluster newCluster in newClusters) { if (!matchFound && Math.Abs(cluster.MeanLength - newCluster.MeanLength) < clusterWidth) { newCluster.MergeCluster(cluster); matchFound = true; } } if (!matchFound) { newClusters.Add(cluster); } } /* If the cluster printing flag is set, then display a list of all the found clusters. */ if (debugPrintClusters) { foreach (IntervalCluster cluster in newClusters) { Console.WriteLine("Interval Cluster " + cluster.MeanLength + "ms, with " + cluster.Intervals.Count + " notes."); } } return(newClusters); }