< Summary

Information
Class: MusicTheory.Theory.Time.NoteValueZoomSelection
Assembly: MusicTheory
File(s): /home/runner/work/MusicTheory/MusicTheory/Theory/Time/NoteValueZoomSelection.cs
Line coverage
56%
Covered lines: 25
Uncovered lines: 19
Coverable lines: 44
Total lines: 92
Line coverage: 56.8%
Branch coverage
50%
Covered branches: 12
Total branches: 24
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ChooseTargetIndexByMedian(...)0%620%
SnapAllToIndexMaintainStarts()100%22100%
SnapAllToIndexPreserveTotalSpan(...)83.33%1212100%
ZoomSelection()0%110100%

File(s)

/home/runner/work/MusicTheory/MusicTheory/Theory/Time/NoteValueZoomSelection.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4
 5namespace MusicTheory.Theory.Time;
 6
 7/// <summary>
 8/// NoteValueZoom を複数ノート選択へ適用するための補助ユーティリティ。
 9/// </summary>
 10public static class NoteValueZoomSelection
 11{
 12    /// <summary>選択の中央値(近傍インデックス)をターゲットとして返す。</summary>
 13    public static int ChooseTargetIndexByMedian(IEnumerable<Note> notes)
 14    {
 015        var idxs = notes.Select(n => NoteValueZoom.FindNearestIndex(n.Duration)).OrderBy(x=>x).ToArray();
 016        if (idxs.Length == 0) return NoteValueZoom.FindNearestIndex(DurationFactory.Quarter());
 017        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    {
 123        var entry = NoteValueZoom.Entries[Math.Clamp(targetIndex, 0, NoteValueZoom.Entries.Count-1)];
 124        var d = entry.Create();
 825        foreach (var (start, n) in sel)
 326            yield return (start, new Note(d, n.Pitch, n.Velocity, n.Channel));
 127    }
 28
 29    /// <summary>
 30    /// 全ノートを同一ステップへスナップ(選択の総スパン長を保持)。
 31    /// 先頭開始から最終ノート終端までの長さを保持し、最後のノートで差分を吸収(最小1tickにクランプ)。
 32    /// </summary>
 33    public static IEnumerable<(long start, Note note)> SnapAllToIndexPreserveTotalSpan(IEnumerable<(long start, Note not
 34    {
 535        var list = sel.OrderBy(x=>x.start).ToList(); if (list.Count==0) return list;
 136        long spanStart = list.First().start;
 137        long spanEnd = list.Last().start + list.Last().note.Duration.Ticks;
 138        long span = Math.Max(0, spanEnd - spanStart);
 39
 140        var entry = NoteValueZoom.Entries[Math.Clamp(targetIndex, 0, NoteValueZoom.Entries.Count-1)];
 141        int stepTicks = entry.Create().Ticks;
 42
 143        var output = new List<(long, Note)>();
 844        for (int i=0;i<list.Count;i++)
 45        {
 346            var (s, n) = list[i];
 347            int durTicks = (i==list.Count-1)
 348                ? (int)Math.Max(1, span - (s - spanStart) - (long)stepTicks * (list.Count-1==0?0:list.Count-1)) // fallb
 349                : stepTicks;
 50            // 再計算: 最終ノート以外は stepTicks、最終ノートはスパンを満たすよう調整
 351            if (i < list.Count-1)
 252                output.Add((s, new Note(Duration.FromTicks(stepTicks), n.Pitch, n.Velocity, n.Channel)));
 53            else
 54            {
 155                long usedBefore = 0;
 856                for (int k=0;k<list.Count-1;k++) usedBefore += stepTicks;
 157                int lastTicks = (int)Math.Max(1, span - (list[i].start - spanStart) - (long)0); // recompute below
 58                // 最終長さは spanEnd' = lastStart + lastTicks になる。ここでは lastTicks = span - (lastStart - spanStart).
 159                lastTicks = (int)Math.Max(1, span - (list[i].start - spanStart));
 160                output.Add((s, new Note(Duration.FromTicks(lastTicks), n.Pitch, n.Velocity, n.Channel)));
 61            }
 62        }
 163        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    {
 069        if (!preserveTotalSpan)
 70        {
 071            foreach (var (s,n) in sel)
 072                yield return (s, new Note(NoteValueZoom.Zoom(n.Duration, delta), n.Pitch, n.Velocity, n.Channel));
 073            yield break;
 74        }
 075        var list = sel.OrderBy(x=>x.start).ToList(); if (list.Count==0){ yield break; }
 076        long spanStart = list.First().start;
 077        long spanEnd = list.Last().start + list.Last().note.Duration.Ticks;
 078        long span = Math.Max(0, spanEnd - spanStart);
 079        for (int i=0;i<list.Count;i++)
 80        {
 081            var (s,n) = list[i];
 082            var z = NoteValueZoom.Zoom(n.Duration, delta);
 083            if (i < list.Count-1)
 084                yield return (s, new Note(z, n.Pitch, n.Velocity, n.Channel));
 85            else
 86            {
 087                int lastTicks = (int)Math.Max(1, span - (s - spanStart));
 088                yield return (s, new Note(Duration.FromTicks(lastTicks), n.Pitch, n.Velocity, n.Channel));
 89            }
 90        }
 091    }
 92}