public static ProcessResultArray <Clip> Apply(SetPitchOptions options, params Clip[] clips) { var resultClips = ClipUtilities.CreateEmptyPlaceholderClips(clips); int[] pitches; if (options.PitchValues.Length > 0) { pitches = options.PitchValues; } else { pitches = options.By.Notes.Select(x => x.Pitch).ToArray(); } if (pitches.Length == 0) { return(new ProcessResultArray <Clip>(clips, "SetPitch did nothing, since neither pitches or -by clip was specified.")); } for (var i = 0; i < clips.Length; i++) { var clip = clips[i]; var resultClip = resultClips[i]; var pitchIx = 0; foreach (var note in clip.Notes) { var repitchedNote = new NoteEvent(note) { Pitch = pitches[pitchIx++ % pitches.Length] }; ClipUtilities.AddNoteCutting(resultClip, repitchedNote); } } return(new ProcessResultArray <Clip>(resultClips)); }
public static Clip CropClip(Clip clip, decimal start, decimal duration) { var processedClip = new Clip(duration, clip.IsLooping); processedClip.Notes.AddRange(ClipUtilities.GetSplitNotesInRangeAtPosition(start, start + duration, clip.Notes, 0)); return(processedClip); }
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); }
public static ProcessResultArray <Clip> Apply(ScaleOptions options, params Clip[] clips) { if (options.By != null) { clips = clips.Prepend(options.By).ToArray(); } ClipUtilities.NormalizeClipLengths(clips); if (clips.Length < 2) { return(new ProcessResultArray <Clip>(clips)); } var masterClip = clips[0]; var slaveClips = clips.Skip(1).ToArray(); var processedClips = slaveClips.Select(c => new Clip(c.Length, c.IsLooping)).ToArray(); for (var i = 0; i < slaveClips.Length; i++) { var slaveClip = slaveClips[i]; foreach (var note in slaveClip.Notes) { var constrainedNote = new NoteEvent(note); constrainedNote.Pitch = options.Strict ? ClipUtilities.FindNearestNotePitchInSet(note, masterClip.Notes) : ClipUtilities.FindNearestNotePitchInSetMusical(note, masterClip.Notes); processedClips[i].Notes.Add(constrainedNote); } } return(new ProcessResultArray <Clip>(processedClips)); }
public static Clip AddEchoes(Clip clip, decimal[] lengths, int[] echoes) { var lengthIx = 0; var echoIx = 0; var newNotes = new List <NoteEvent>(); foreach (var noteEvent in clip.Notes) { var delayTime = lengths[lengthIx++ % lengths.Length]; var echoCount = Math.Max(echoes[echoIx++ % echoes.Length], 2); var velocityFalloff = (int)Math.Round((noteEvent.Velocity - 10) / (echoCount - 1)); if (noteEvent.Duration > delayTime) { noteEvent.Duration = delayTime; } for (var i = 0; i < echoCount; i++) { var echoedNote = noteEvent with { }; echoedNote.Start += delayTime * i; echoedNote.Velocity -= velocityFalloff * i; newNotes.Add(echoedNote); } } foreach (var newNote in newNotes) { ClipUtilities.AddNoteCutting(clip, newNote); } // todo: handle wrapping echoes outside the length of the clip return(clip); }
//[TestMethod] public void MyTestMethod() { for (var i = 10; i < 36; i += 5) { Console.WriteLine($"pitch {i} nearest: {ClipUtilities.FindNearestNotePitchInSet(i, new int[] { 0,12,24,32 })}"); } }
public static ProcessResultArray <Clip> Apply(Command command, params Clip[] clips) { (var success, var msg) = OptionParser.TryParseOptions(command, out ScanOptions options); if (!success) { return(new ProcessResultArray <Clip>(msg)); } var processedClips = new Clip[clips.Length]; for (var c = 0; c < clips.Length; c++) { var clip = clips[c]; var processedClip = new Clip(options.Window * options.Count, clip.IsLooping); decimal delta = clip.Length / options.Count, curPos = 0; for (int i = 0; i < options.Count; i++) { processedClip.Notes.AddRange(ClipUtilities.GetSplitNotesInRangeAtPosition(curPos, curPos + options.Window, clip.Notes, options.Window * i)); curPos += delta; } processedClips[c] = processedClip; } return(new ProcessResultArray <Clip>(processedClips)); }
public static ProcessResult <Clip[]> Apply(LoopOptions options, params Clip[] clips) { foreach (var clip in clips) { ClipUtilities.EnlargeClipByLooping(clip, options.Length * clip.Length); } return(new ProcessResult <Clip[]>(clips)); }
// 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())); }
public static ProcessResult <Clip[]> Apply(SliceOptions options, params Clip[] clips) { var processedClips = new List <Clip>(); foreach (var clip in clips) { processedClips.Add(ClipUtilities.SplitNotesAtEvery(clip, options.Lengths)); } return(new ProcessResult <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())); }
// # desc: Concatenates two or more clips together. public static ProcessResult <Clip[]> Apply(params Clip[] clips) { Clip resultClip = new Clip(clips.Select(c => c.Length).Sum(), true); decimal pos = 0; foreach (var clip in clips) { resultClip.Notes.AddRange(ClipUtilities.GetNotesInRangeAtPosition(0, clip.Length, clip.Notes, pos)); pos += clip.Length; } return(new ProcessResult <Clip[]>(new[] { resultClip })); }
// TODO: Add option to cut overlapping events, so that more of the original clip is preserved public static ProcessResult <Clip[]> Apply(params Clip[] clips) { var resultClips = ClipUtilities.CreateEmptyPlaceholderClips(clips); for (var i = 0; i < clips.Length; i++) { var clip = clips[i]; var resultClip = resultClips[i]; foreach (var note in clip.Notes) { AddNoteCutting(resultClip, note with { });
public static ProcessResultArray <Clip> Apply(SetRhythmOptions options, params Clip[] clips) { if (options.By != null) { clips = clips.Prepend(options.By).ToArray(); } if (clips.Length < 2) { return(new ProcessResultArray <Clip>(clips, $"SetRhythm: Skipped command because it needs 2 clips, and {clips.Length} were passed in.")); } ClipUtilities.NormalizeClipLengths(clips); var resultClips = new Clip[clips.Length - 1]; var byClip = clips[0]; var byIndex = 0; var resultClipIx = 0; for (var i = 1; i < clips.Length; i++) { var clip = clips[i]; var resultClip = new Clip(0, clip.IsLooping); foreach (var note in clip.Notes) { var byNote = byClip.Notes[byIndex % byClip.Count]; // special case: add silence between start of clip and first note, but only the first time, since subsequent silences are handled by DurationUntilNextNote if (resultClip.Length == 0 && byIndex == 0) { resultClip.Length = byNote.Start; } resultClip.Add(new NoteEvent(note.Pitch, resultClip.Length, byNote.Duration, note.Velocity)); resultClip.Length += byClip.DurationUntilNextNote(byIndex % byClip.Count); byIndex++; } // stacked/overlapping notes will lead to incorrect final length of clip, so check if this is the case var latestNoteEnd = resultClip.Notes.Max(x => x.End); if (latestNoteEnd > resultClip.Length) { resultClip.Length = latestNoteEnd; } resultClip.Length = Utilities.RoundUpToNearestSixteenth(resultClip.Length); // quantize clip length to nearest 1/16, or Live won't accept it resultClips[resultClipIx++] = resultClip; } return(new ProcessResultArray <Clip>(resultClips)); }
public void TestNormalizeClipLengths() { var clip1 = new Clip(1, true) { Notes = new SortedList <NoteEvent>() { new NoteEvent(60, 0, .25m, 100), new NoteEvent(60, .4m, .1m, 100), } }; var clip2 = new Clip(4, true) { Notes = new SortedList <NoteEvent>() { new NoteEvent(62, 0, 1, 100), new NoteEvent(62, 1, 1, 100), new NoteEvent(62, 2, 1, 100), new NoteEvent(62, 3, 1, 100) } }; var clip3 = new Clip(2, true) { Notes = new SortedList <NoteEvent>() { new NoteEvent(64, .5m, 1, 100), new NoteEvent(64, 1.5m, .5m, 100), } }; ClipUtilities.NormalizeClipLengths(new[] { clip1, clip2, clip3 }); Assert.AreEqual(clip1.Notes[0].Start, 0); Assert.AreEqual(clip1.Notes[1].Start, .4m); Assert.AreEqual(clip1.Notes[2].Start, 1); Assert.AreEqual(clip1.Notes[3].Start, 1.4m); Assert.AreEqual(clip1.Notes[4].Start, 2); Assert.AreEqual(clip1.Notes[5].Start, 2.4m); Assert.AreEqual(clip1.Notes[6].Start, 3); Assert.AreEqual(clip1.Notes[7].Start, 3.4m); Assert.AreEqual(clip1.Notes.Count, 8); Assert.AreEqual(clip2.Notes[0].Start, 0); Assert.AreEqual(clip2.Notes[1].Start, 1); Assert.AreEqual(clip2.Notes[2].Start, 2); Assert.AreEqual(clip2.Notes[3].Start, 3); Assert.AreEqual(clip2.Notes.Count, 4); Assert.AreEqual(clip3.Notes[0].Start, .5m); Assert.AreEqual(clip3.Notes[1].Start, 1.5m); Assert.AreEqual(clip3.Notes[2].Start, 2.5m); Assert.AreEqual(clip3.Notes[3].Start, 3.5m); Assert.AreEqual(clip3.Notes.Count, 4); }
public static ProcessResult <Clip[]> Apply(SetLengthOptions options, params Clip[] clips) { var resultClips = ClipUtilities.CreateEmptyPlaceholderClips(clips); for (var index = 0; index < clips.Length; index++) { var clip = clips[index]; var resultClip = resultClips[index]; var lengthCounter = 0; foreach (var note in clip.Notes) { ClipUtilities.AddNoteCutting(resultClip, note with { Duration = options.Lengths[lengthCounter++ % options.Lengths.Length] });
public static ProcessResultArray <Clip> Apply(CropOptions options, params Clip[] clips) { var processedClips = new Clip[clips.Length]; var start = options.Lengths.Length > 1 ? options.Lengths[0] : 0; var duration = options.Lengths.Length > 1 ? options.Lengths[1] : options.Lengths[0]; var i = 0; foreach (var clip in clips) { var processedClip = new Clip(duration, clip.IsLooping); processedClip.Notes.AddRange(ClipUtilities.GetSplitNotesInRangeAtPosition(start, start + duration, clip.Notes, 0)); processedClips[i++] = processedClip; } return(new ProcessResultArray <Clip>(processedClips)); }
// 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 resultClips = ClipUtilities.CreateEmptyPlaceholderClips(clips); for (var i = 0; i < clips.Length; i++) { var clip = clips[i]; var resultClip = resultClips[i]; foreach (var note in clip.Notes) { var newNote = new NoteEvent(note); AddNoteCutting(resultClip, newNote); } } return(Filter.Apply(new FilterOptions(), resultClips)); }
public static ProcessResult <Clip[]> Apply(QuantizeOptions options, params Clip[] clips) { var maxLen = clips.Max(x => x.Length); if (options.By != null) { if (options.By.Length < maxLen) { ClipUtilities.EnlargeClipByLooping(options.By, maxLen); } options.Divisions = options.By.Notes.Select(x => x.Start).Distinct().ToArray(); } else { var currentPos = 0m; var quantizePositions = new List <decimal>(); var i = 0; while (currentPos <= maxLen) { quantizePositions.Add(currentPos); currentPos += options.Divisions[i % options.Divisions.Length]; i++; } options.Divisions = quantizePositions.ToArray(); } options.Amount = Math.Clamp(options.Amount, 0, 1); var resultClips = new Clip[clips.Length]; for (var i = 0; i < clips.Length; i++) { var clip = clips[i]; var resultClip = new Clip(clip.Length, clip.IsLooping); foreach (var note in clip.Notes) { var constrainedNote = note with { }; var newStart = ClipUtilities.FindNearestNoteStartInDecimalSet(note, options.Divisions); constrainedNote.Start += (newStart - constrainedNote.Start) * options.Amount; resultClip.Add(constrainedNote); } resultClips[i] = resultClip; } return(new ProcessResult <Clip[]>(resultClips)); }
public static ProcessResultArray <Clip> Apply(VelocityScaleOptions options, params Clip[] clips) { var processedClips = new Clip[clips.Length]; var i = 0; foreach (var clip in clips) { var processedClip = new Clip(clip.Length, clip.IsLooping); processedClip.Notes.AddRange(ClipUtilities.GetSplitNotesInRangeAtPosition(0, clip.Length, clip.Notes, 0)); foreach (var item in processedClip.Notes) { item.Velocity = System.Math.Min(FullMidiVelocityRange, System.Math.Abs((int)System.Math.Floor(item.Velocity * options.Strength))); } processedClips[i++] = processedClip; } return(new ProcessResultArray <Clip>(processedClips)); }
public static ProcessResult <Clip[]> Apply(ScaleOptions options, params Clip[] clips) { if (options.By != null) { clips = clips.Prepend(options.By).ToArray(); } ClipUtilities.NormalizeClipLengths(clips); if (clips.Length < 2) { return(new ProcessResult <Clip[]>(clips)); } var masterClip = clips[0]; var slaveClips = clips.Skip(1).ToArray(); var processedClips = slaveClips.Select(c => new Clip(c.Length, c.IsLooping)).ToArray(); for (var i = 0; i < slaveClips.Length; i++) { var slaveClip = slaveClips[i]; foreach (var note in slaveClip.Notes) { var masterNotes = SortedList <NoteEvent> .Empty; if (options.PositionAware) { masterNotes = masterClip.Notes.Where(x => x.StartsInsideIntervalInclusive(note.Start, note.End) || x.CoversInterval(note.Start, note.End)).ToSortedList(); } if (masterNotes.Count == 0) { masterNotes = masterClip.Notes; } var constrainedNote = note with { }; constrainedNote.Pitch = options.Strict ? ClipUtilities.FindNearestNotePitchInSet(note, masterNotes) : ClipUtilities.FindNearestNotePitchInSetMusical(note, masterNotes); processedClips[i].Notes.Add(constrainedNote); } } return(new ProcessResult <Clip[]>(processedClips)); }
public static ProcessResult <Clip[]> Apply(RemapOptions options, params Clip[] clips) { var resultClips = ClipUtilities.CreateEmptyPlaceholderClips(clips); for (var i = 0; i < clips.Length; i++) { var clip = clips[i]; var resultClip = resultClips[i]; var sourcePitches = clip.Notes.Select(x => x.Pitch).Distinct().OrderBy(x => x).ToList(); var destPitches = options.To.Count > 0 ? options.To.Notes.Select(x => x.Pitch).Distinct().OrderBy(x => x).ToList() : Enumerable.Range(36, Math.Min(sourcePitches.Count, 128 - 36)).ToList(); var inc = 1f; if (destPitches.Count < sourcePitches.Count) { inc = (float)destPitches.Count / sourcePitches.Count; } var map = new Dictionary <int, int>(); var destIx = 0f; foreach (var sourcePitch in sourcePitches) { map[sourcePitch] = destPitches[(int)Math.Floor(destIx)]; destIx += inc; } foreach (var note in clip.Notes) { var remappedNote = note with { Pitch = map[note.Pitch] }; ClipUtilities.AddNoteCutting(resultClip, remappedNote); } } return(new ProcessResult <Clip[]>(resultClips)); } }
public static ProcessResultArray <Clip> Apply(RatchetOptions options, params Clip[] clips) { options.Strength = Math.Clamp(options.Strength, 0, 1); if (options.By != null) { clips = clips.Prepend(options.By).ToArray(); } ClipUtilities.NormalizeClipLengths(clips); if (clips.Length < 2) { clips = new[] { clips[0], clips[0] }; } Clip controlSequence = new Clip(clips[0]); Clip[] targetSequences = clips.Skip(1).Select(x => new Clip(x)).ToArray(); Clip[] resultSequences = new Clip[targetSequences.Length]; if (options.RatchetValues.Length > 0) { for (var i = 0; i < options.RatchetValues.Length; i++) { if (options.RatchetValues[i] < 1) { options.RatchetValues[i] = 1; // todo: should be handled by optionparser and min max values in optioninfo } } for (var i = 0; i < targetSequences.Length; i++) { var targetSequence = targetSequences[i]; resultSequences[i] = DoManualRatchet(options.RatchetValues, targetSequence, options.Strength, options.VelocityToStrength, options.Shape, options.Mode); } } else { var(controlMin, controlMax, targetRange) = options.Mode == RatchetMode.Pitch ? GetControlValuesFromPitch(options, controlSequence) : GetControlValuesFromVelocity(options, controlSequence); float controlRange = Math.Max(controlMax - controlMin, 1); // set pitch for each note in control sequence if (options.Mode == RatchetMode.Pitch) { foreach (var note in controlSequence.Notes) { note.Pitch = (int)Math.Round((note.Pitch - controlMin) / controlRange * targetRange) + 1; } } else { foreach (var note in controlSequence.Notes) { note.Velocity = (int)Math.Round((Math.Clamp(note.Velocity, controlMin, controlMax) - controlMin) / controlRange * targetRange) + 1; } } for (var i = 0; i < targetSequences.Length; i++) { var targetSequence = targetSequences[i]; resultSequences[i] = DoRatchet(controlSequence, targetSequence, (float)options.Strength, options.VelocityToStrength, options.Shape, options.Mode); } } return(new ProcessResultArray <Clip>(resultSequences)); }
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 }
public static ProcessResultArray <Clip> Apply(InterleaveOptions options, ClipMetaData metadata, params Clip[] clips) { if (clips.Length < 2) { clips = new[] { clips[0], clips[0] }; } decimal position = 0; int repeatsIndex = 0; Clip resultClip = new Clip(4, true); switch (options.Mode) { case Event: if (options.ChunkChords) { foreach (var clip in clips) { clip.GroupSimultaneousNotes(); } } var noteCounters = clips.Select(c => new IntCounter(c.Notes.Count)).ToArray(); position = clips[0].Notes[0].Start; while (noteCounters.Any(nc => !nc.Overflow)) { for (var clipIndex = 0; clipIndex < clips.Length; clipIndex++) { var clip = clips[clipIndex]; var currentNoteCounter = noteCounters[clipIndex]; for (var repeats = 0; repeats < options.Repeats[repeatsIndex % options.Repeats.Length]; repeats++) { var note = clip.Notes[currentNoteCounter.Value]; if (!options.Solo || clip.ClipReference.Track == metadata.TrackNumber) { var newNote = new NoteEvent(note); newNote.Start = position; resultClip.Notes.Add(newNote); } position += clip.DurationUntilNextNote(currentNoteCounter.Value); } if (options.Skip) { foreach (var noteCounter in noteCounters) { noteCounter.Inc(); } } else { currentNoteCounter.Inc(); } repeatsIndex++; } } if (options.ChunkChords) { resultClip.Flatten(); } break; case Time: var srcPositions = clips.Select(c => new DecimalCounter(c.Length)).ToArray(); int timeRangeIndex = 0; while (srcPositions.Any(c => !c.Overflow)) { for (var clipIndex = 0; clipIndex < clips.Length; clipIndex++) { var clip = clips[clipIndex]; var currentTimeRange = options.Ranges[timeRangeIndex]; for (var repeats = 0; repeats < options.Repeats[repeatsIndex % options.Repeats.Length]; repeats++) { if (!options.Solo || clip.ClipReference.Track == metadata.TrackNumber) { resultClip.Notes.AddRange( ClipUtilities.GetSplitNotesInRangeAtPosition( srcPositions[clipIndex].Value, srcPositions[clipIndex].Value + currentTimeRange, clips[clipIndex].Notes, position ) ); } position += currentTimeRange; } if (options.Skip) { foreach (var srcPosition in srcPositions) { srcPosition.Inc(currentTimeRange); } } else { srcPositions[clipIndex].Inc(currentTimeRange); } repeatsIndex++; timeRangeIndex = (timeRangeIndex + 1) % options.Ranges.Length; // this means that you cannot use the Counts parameter to have varying time ranges for each repeat } } break; } resultClip.Length = position; return(new ProcessResultArray <Clip>(new[] { resultClip })); }