| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.Linq; |
| | 4 | |
|
| | 5 | | namespace MusicTheory.Theory.Time; |
| | 6 | |
|
| | 7 | | /// <summary> |
| | 8 | | /// NoteValueZoom を複数ノート選択へ適用するための補助ユーティリティ。 |
| | 9 | | /// </summary> |
| | 10 | | public static class NoteValueZoomSelection |
| | 11 | | { |
| | 12 | | /// <summary>選択の中央値(近傍インデックス)をターゲットとして返す。</summary> |
| | 13 | | public static int ChooseTargetIndexByMedian(IEnumerable<Note> notes) |
| | 14 | | { |
| 0 | 15 | | var idxs = notes.Select(n => NoteValueZoom.FindNearestIndex(n.Duration)).OrderBy(x=>x).ToArray(); |
| 0 | 16 | | if (idxs.Length == 0) return NoteValueZoom.FindNearestIndex(DurationFactory.Quarter()); |
| 0 | 17 | | return idxs[idxs.Length/2]; |
| | 18 | | } |
| | 19 | |
|
| | 20 | | /// <summary>全ノートを同一ステップへスナップ(開始位置は維持)。</summary> |
| | 21 | | public static IEnumerable<(long start, Note note)> SnapAllToIndexMaintainStarts(IEnumerable<(long start, Note note)> |
| | 22 | | { |
| 1 | 23 | | var entry = NoteValueZoom.Entries[Math.Clamp(targetIndex, 0, NoteValueZoom.Entries.Count-1)]; |
| 1 | 24 | | var d = entry.Create(); |
| 8 | 25 | | foreach (var (start, n) in sel) |
| 3 | 26 | | yield return (start, new Note(d, n.Pitch, n.Velocity, n.Channel)); |
| 1 | 27 | | } |
| | 28 | |
|
| | 29 | | /// <summary> |
| | 30 | | /// 全ノートを同一ステップへスナップ(選択の総スパン長を保持)。 |
| | 31 | | /// 先頭開始から最終ノート終端までの長さを保持し、最後のノートで差分を吸収(最小1tickにクランプ)。 |
| | 32 | | /// </summary> |
| | 33 | | public static IEnumerable<(long start, Note note)> SnapAllToIndexPreserveTotalSpan(IEnumerable<(long start, Note not |
| | 34 | | { |
| 5 | 35 | | var list = sel.OrderBy(x=>x.start).ToList(); if (list.Count==0) return list; |
| 1 | 36 | | long spanStart = list.First().start; |
| 1 | 37 | | long spanEnd = list.Last().start + list.Last().note.Duration.Ticks; |
| 1 | 38 | | long span = Math.Max(0, spanEnd - spanStart); |
| | 39 | |
|
| 1 | 40 | | var entry = NoteValueZoom.Entries[Math.Clamp(targetIndex, 0, NoteValueZoom.Entries.Count-1)]; |
| 1 | 41 | | int stepTicks = entry.Create().Ticks; |
| | 42 | |
|
| 1 | 43 | | var output = new List<(long, Note)>(); |
| 8 | 44 | | for (int i=0;i<list.Count;i++) |
| | 45 | | { |
| 3 | 46 | | var (s, n) = list[i]; |
| 3 | 47 | | int durTicks = (i==list.Count-1) |
| 3 | 48 | | ? (int)Math.Max(1, span - (s - spanStart) - (long)stepTicks * (list.Count-1==0?0:list.Count-1)) // fallb |
| 3 | 49 | | : stepTicks; |
| | 50 | | // 再計算: 最終ノート以外は stepTicks、最終ノートはスパンを満たすよう調整 |
| 3 | 51 | | if (i < list.Count-1) |
| 2 | 52 | | output.Add((s, new Note(Duration.FromTicks(stepTicks), n.Pitch, n.Velocity, n.Channel))); |
| | 53 | | else |
| | 54 | | { |
| 1 | 55 | | long usedBefore = 0; |
| 8 | 56 | | for (int k=0;k<list.Count-1;k++) usedBefore += stepTicks; |
| 1 | 57 | | int lastTicks = (int)Math.Max(1, span - (list[i].start - spanStart) - (long)0); // recompute below |
| | 58 | | // 最終長さは spanEnd' = lastStart + lastTicks になる。ここでは lastTicks = span - (lastStart - spanStart). |
| 1 | 59 | | lastTicks = (int)Math.Max(1, span - (list[i].start - spanStart)); |
| 1 | 60 | | output.Add((s, new Note(Duration.FromTicks(lastTicks), n.Pitch, n.Velocity, n.Channel))); |
| | 61 | | } |
| | 62 | | } |
| 1 | 63 | | return output; |
| | 64 | | } |
| | 65 | |
|
| | 66 | | /// <summary>選択へ一括ズーム(delta)。preserveTotalSpan=true で総スパン保持。</summary> |
| | 67 | | public static IEnumerable<(long start, Note note)> ZoomSelection(IEnumerable<(long start, Note note)> sel, int delta |
| | 68 | | { |
| 0 | 69 | | if (!preserveTotalSpan) |
| | 70 | | { |
| 0 | 71 | | foreach (var (s,n) in sel) |
| 0 | 72 | | yield return (s, new Note(NoteValueZoom.Zoom(n.Duration, delta), n.Pitch, n.Velocity, n.Channel)); |
| 0 | 73 | | yield break; |
| | 74 | | } |
| 0 | 75 | | var list = sel.OrderBy(x=>x.start).ToList(); if (list.Count==0){ yield break; } |
| 0 | 76 | | long spanStart = list.First().start; |
| 0 | 77 | | long spanEnd = list.Last().start + list.Last().note.Duration.Ticks; |
| 0 | 78 | | long span = Math.Max(0, spanEnd - spanStart); |
| 0 | 79 | | for (int i=0;i<list.Count;i++) |
| | 80 | | { |
| 0 | 81 | | var (s,n) = list[i]; |
| 0 | 82 | | var z = NoteValueZoom.Zoom(n.Duration, delta); |
| 0 | 83 | | if (i < list.Count-1) |
| 0 | 84 | | yield return (s, new Note(z, n.Pitch, n.Velocity, n.Channel)); |
| | 85 | | else |
| | 86 | | { |
| 0 | 87 | | int lastTicks = (int)Math.Max(1, span - (s - spanStart)); |
| 0 | 88 | | yield return (s, new Note(Duration.FromTicks(lastTicks), n.Pitch, n.Velocity, n.Channel)); |
| | 89 | | } |
| | 90 | | } |
| 0 | 91 | | } |
| | 92 | | } |