| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.Linq; |
| | 4 | |
|
| | 5 | | namespace MusicTheory.Theory.Time; |
| | 6 | |
|
| | 7 | | /// <summary> |
| | 8 | | /// グルーヴ / ヒューマナイズ適用ユーティリティ。 |
| | 9 | | /// </summary> |
| | 10 | | public static class GrooveHumanize |
| | 11 | | { |
| | 12 | | /// <summary>位置配列にグルーヴテンプレート (tick オフセット) を適用。</summary> |
| | 13 | | public static IEnumerable<long> ApplyGroove(IEnumerable<long> ticks, int gridTicks, IReadOnlyList<int> pattern) |
| 4 | 14 | | => ticks.Select(t => Quantize.ApplyGroove(t, gridTicks, pattern)); |
| | 15 | |
|
| | 16 | | /// <summary>スイング + グルーヴ + 強拍バイアスをまとめて適用。</summary> |
| | 17 | | public static long ProcessOne(long tick, int gridTicks, double swingRatio, int swingSubdivisionTicks, int ticksPerBa |
| | 18 | | { |
| 3 | 19 | | var q = Quantize.ToGrid(tick, gridTicks); |
| 3 | 20 | | if (swingRatio>0 && swingSubdivisionTicks>0) |
| 1 | 21 | | q = Quantize.ApplySwing(q, swingSubdivisionTicks, swingRatio); |
| 3 | 22 | | if (strongBeats.Count>0) |
| 1 | 23 | | q = Quantize.ToGridStrongBeat(q, gridTicks, ticksPerBar, strongBeats, strongBias); |
| 3 | 24 | | if (groovePattern.Count>0) |
| 1 | 25 | | q = Quantize.ApplyGroove(q, gridTicks, groovePattern); |
| 3 | 26 | | return q; |
| | 27 | | } |
| | 28 | |
|
| | 29 | | /// <summary>タイミングジッター (±maxJitterTicks の整数一様) を付与。</summary> |
| | 30 | | public static IEnumerable<long> AddTimingJitter(IEnumerable<long> ticks, int maxJitterTicks, int? seed=null) |
| | 31 | | { |
| 3 | 32 | | if (maxJitterTicks<=0) return ticks; |
| 1 | 33 | | var rnd = seed.HasValue? new Random(seed.Value) : new Random(); |
| 4 | 34 | | return ticks.Select(t => t + rnd.Next(-maxJitterTicks, maxJitterTicks+1)); |
| | 35 | | } |
| | 36 | |
|
| | 37 | | /// <summary>ベロシティヒューマナイズ (割合 ±percent / clamp 0..127)。</summary> |
| | 38 | | public static IEnumerable<int> HumanizeVelocity(IEnumerable<int> velocities, double percent, int? seed=null) |
| | 39 | | { |
| 4 | 40 | | if (percent<=0) return velocities; |
| 2 | 41 | | var rnd = seed.HasValue? new Random(seed.Value) : new Random(); |
| 7 | 42 | | return velocities.Select(v => (int)Math.Clamp(v + v * (rnd.NextDouble()*2-1)*percent, 0, 127)); |
| | 43 | | } |
| | 44 | | } |
| | 45 | |
|
| | 46 | | /// <summary>量子化プリセット (スイングとグルーヴ合成) を提供。</summary> |
| | 47 | | public static class QuantizePresets |
| | 48 | | { |
| | 49 | | public record Preset(string Name, int GridTicks, double SwingRatio, int SwingSubdivisionTicks, IReadOnlyList<int> Gr |
| | 50 | |
|
| | 51 | | public static Preset Straight16 => new("Straight16", Duration.TicksPerQuarter/4, 0, 0, Array.Empty<int>(), Array.Emp |
| | 52 | | public static Preset Swing8 => new("Swing8", Duration.TicksPerQuarter/2, 0.5, Duration.TicksPerQuarter/2, Array.Empt |
| | 53 | | public static Preset Funk16 => new("Funk16", Duration.TicksPerQuarter/4, 0.0, 0, new[]{0,5,0,-5}, new[]{0, Duration. |
| | 54 | |
|
| | 55 | | public static long Apply(Preset p, long tick, int ticksPerBar) |
| | 56 | | => GrooveHumanize.ProcessOne(tick, p.GridTicks, p.SwingRatio, p.SwingSubdivisionTicks, ticksPerBar, p.StrongBeat |
| | 57 | | } |