public void TestMonophonize() { var clip1 = new Clip(1, true) { Notes = new SortedList <NoteEvent>() { new NoteEvent(60, 0, 1, 100), new NoteEvent(62, 0, 0.2m, 100), new NoteEvent(62, 0.3m, 0.2m, 100), new NoteEvent(62, 0.5m, 0.2m, 100), new NoteEvent(62, 0.8m, 0.2m, 100), new NoteEvent(60, 1, 1, 100), } }; Assert.AreEqual(6, clip1.Notes.Count); ClipUtilities.Monophonize(clip1); Assert.AreEqual(2, clip1.Notes.Count); clip1 = new Clip(1, true) { Notes = new SortedList <NoteEvent>() { new NoteEvent(60, 0, .2m, 100), new NoteEvent(62, .1m, .2m, 100), new NoteEvent(62, .2m, .2m, 100) } }; Assert.AreEqual(3, clip1.Notes.Count); ClipUtilities.Monophonize(clip1); Assert.AreEqual(2, clip1.Notes.Count); }
// TODO: Add option to cut overlapping events, so that more of the original clip is preserved public static ProcessResultArray <Clip> Apply(params Clip[] clips) { var processedClips = new List <Clip>(); foreach (var clip in clips) { processedClips.Add(ClipUtilities.Monophonize(clip)); } return(new ProcessResultArray <Clip>(processedClips.ToArray())); }
// Add option to dynamically set # of events that should be rescaled to another note, probably via velocity. public static ProcessResultArray <Clip> Apply(ArpeggiateOptions options, params Clip[] clips) { Clip arpSequence = ClipUtilities.Monophonize(options.By ?? clips[0]); foreach (var clip in clips) { ClipUtilities.Monophonize(clip); } var processedClips = new List <Clip>(clips.Length); // If arp sequence doesn't start at zero and remove offset is specified, make it start at zero if (arpSequence.Notes[0].Start != 0 && options.RemoveOffset) { foreach (var arpNote in arpSequence.Notes) { arpNote.Start -= arpSequence.Notes[0].Start; } } var count = Math.Min(arpSequence.Notes.Count, options.Rescale); var arpNotes = arpSequence.Notes.Take(count); var actualLength = arpNotes.Last().Start + arpNotes.Last().Duration; // Rescale arp events to the range 0-1 foreach (var arpNote in arpNotes) { arpNote.Start = arpNote.Start / actualLength; arpNote.Duration = arpNote.Duration / actualLength; } foreach (var clip in clips) { var resultClip = new Clip(clip.Length, clip.IsLooping); for (var i = 0; i < clip.Notes.Count; i++) { var note = clip.Notes[i]; var processedNotes = new List <NoteEvent>(count); int ix = 0; foreach (var currentArpNote in arpNotes) { NoteEvent processedNote = new NoteEvent(currentArpNote); processedNote.Start = note.Start + (processedNote.Start * note.Duration); processedNote.Duration *= note.Duration; processedNote.Pitch = note.Pitch + arpSequence.RelativePitch(ix); processedNotes.Add(processedNote); ix++; } resultClip.Notes.AddRange(processedNotes); } processedClips.Add(resultClip); } return(new ProcessResultArray <Clip>(processedClips.ToArray())); }
public static ProcessResult <Clip[]> Apply(ShuffleOptions options, params Clip[] clips) { if (options.By.Notes.Count == 0) { options.By = clips[0]; } if (options.By.Count == 0 && options.ShuffleValues.Length == 0) { return(new ProcessResult <Clip[]>("No -by clip or shuffle values specified.")); } ClipUtilities.Monophonize(options.By); var targetClips = new Clip[clips.Length]; int[] shuffleValues; if (options.ShuffleValues.Length == 0) { int minPitch = options.By.Notes.Min(x => x.Pitch); shuffleValues = options.By.Notes.Select(x => x.Pitch - minPitch).ToArray(); } else { shuffleValues = options.ShuffleValues.Select(x => Math.Clamp(x, 1, 100) - 1).ToArray(); } var c = 0; foreach (var clip in clips) // we only support one generated clip since these are tied to a specific clip slot. Maybe support multiple clips under the hood, but discard any additional clips when sending the output is the most flexible approach. { clip.GroupSimultaneousNotes(); targetClips[c] = new Clip(clip.Length, clip.IsLooping); var numShuffleIndexes = shuffleValues.Length; if (numShuffleIndexes < clip.Notes.Count) { numShuffleIndexes = clip.Notes.Count; } var indexes = new int[numShuffleIndexes]; for (var i = 0; i < numShuffleIndexes; i++) { // Calc shuffle indexes as long as there are notes in the source clip. If the clip to be shuffled contains more events than the source, add zero-indexes so that the rest of the sequence is produced sequentially. if (i < shuffleValues.Length) { indexes[i] = (int)Math.Floor(((float)shuffleValues[i] / clip.Notes.Count) * clip.Notes.Count); } else { indexes[i] = 0; } } // preserve original durations until next note var durationUntilNextNote = new List <decimal>(clip.Notes.Count); for (var i = 0; i < clip.Notes.Count; i++) { durationUntilNextNote.Add(clip.DurationUntilNextNote(i)); } // do shuffle var j = 0; decimal pos = 0m; while (clip.Notes.Count > 0) { int currentIx = indexes[j++] % clip.Notes.Count; targetClips[c].Notes.Add( clip.Notes[currentIx] with { Start = pos }