< Summary

Information
Class: MusicTheory.Theory.Time.TrackNameEvent
Assembly: MusicTheory
File(s): /home/runner/work/MusicTheory/MusicTheory/Theory/Time/TimeSignature.cs
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 236
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Name()100%11100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4
 5namespace MusicTheory.Theory.Time;
 6
 7/// <summary>
 8/// 拍子記号。例: 4/4, 3/8 など。PPQ=Duration.TicksPerQuarter を前提に ticks 計算。
 9/// </summary>
 10public readonly struct TimeSignature : IEquatable<TimeSignature>
 11{
 12    public int Numerator { get; }
 13    public int Denominator { get; } // (1,2,4,8,16,...)
 14    public TimeSignature(int numerator, int denominator)
 15    {
 16        if (numerator <= 0) throw new ArgumentOutOfRangeException(nameof(numerator));
 17        if (denominator <= 0 || (denominator & (denominator - 1)) != 0) throw new ArgumentException("Denominator must be
 18        Numerator = numerator; Denominator = denominator;
 19    }
 20    public int TicksPerBeat => Duration.TicksPerQuarter * 4 / Denominator;
 21    public int TicksPerBar => TicksPerBeat * Numerator;
 22    public bool Equals(TimeSignature other) => Numerator == other.Numerator && Denominator == other.Denominator;
 23    public override bool Equals(object? obj) => obj is TimeSignature ts && Equals(ts);
 24    public override int GetHashCode() => HashCode.Combine(Numerator, Denominator);
 25    public override string ToString() => $"{Numerator}/{Denominator}";
 26}
 27
 28/// <summary>小節位置 (bar, beat, tickWithinBeat)。bar, beat は 0 基点。</summary>
 29public readonly struct BarBeatTick : IEquatable<BarBeatTick>
 30{
 31    public int Bar { get; }
 32    public int Beat { get; }
 33    public int TickWithinBeat { get; }
 34    public BarBeatTick(int bar, int beat, int tickWithinBeat)
 35    { if (bar<0||beat<0||tickWithinBeat<0) throw new ArgumentOutOfRangeException(); Bar=bar; Beat=beat; TickWithinBeat=t
 36    public bool Equals(BarBeatTick other) => Bar==other.Bar && Beat==other.Beat && TickWithinBeat==other.TickWithinBeat;
 37    public override bool Equals(object? obj)=> obj is BarBeatTick bbt && Equals(bbt);
 38    public override int GetHashCode()=> HashCode.Combine(Bar,Beat,TickWithinBeat);
 39    public override string ToString()=> $"{Bar}:{Beat}+{TickWithinBeat}";
 40}
 41
 42/// <summary>絶対 tick と拍子変換用。可変拍子対応は将来的に (マップを保持) 拡張。</summary>
 43public readonly struct TimePosition
 44{
 45    public TimeSignature Signature { get; }
 46    public long AbsoluteTicks { get; }
 47    public BarBeatTick BarBeatTick { get; }
 48    public TimePosition(TimeSignature sig, long absoluteTicks)
 49    {
 50        if (absoluteTicks < 0) throw new ArgumentOutOfRangeException(nameof(absoluteTicks));
 51        Signature = sig; AbsoluteTicks = absoluteTicks;
 52        var tpBar = sig.TicksPerBar;
 53        var bar = (int)(absoluteTicks / tpBar);
 54        var rem = (int)(absoluteTicks % tpBar);
 55        var beat = rem / sig.TicksPerBeat;
 56        var tickInBeat = rem % sig.TicksPerBeat;
 57        BarBeatTick = new BarBeatTick(bar, beat, tickInBeat);
 58    }
 59    private TimePosition(TimeSignature sig, long abs, BarBeatTick bbt)
 60    { Signature=sig; AbsoluteTicks=abs; BarBeatTick=bbt; }
 61    public static TimePosition FromBarBeatTick(TimeSignature sig, BarBeatTick bbt)
 62    {
 63        if (bbt.Beat >= sig.Numerator) throw new ArgumentOutOfRangeException(nameof(bbt),"Beat >= numerator");
 64        if (bbt.TickWithinBeat >= sig.TicksPerBeat) throw new ArgumentOutOfRangeException(nameof(bbt),"TickWithinBeat >=
 65        long abs = (long)bbt.Bar * sig.TicksPerBar + bbt.Beat * sig.TicksPerBeat + bbt.TickWithinBeat;
 66        return new TimePosition(sig, abs, bbt);
 67    }
 68    public TimePosition Add(Duration d) => new(Signature, AbsoluteTicks + d.Ticks);
 69    public override string ToString() => $"{BarBeatTick} ({AbsoluteTicks} ticks)";
 70}
 71
 72/// <summary>
 73/// Tuplet パターン生成ユーティリティ。
 74/// </summary>
 75public static class TupletPatterns
 76{
 77    /// <summary>任意範囲で n:k 連符 (n!=k) を生成 (既約化は行わず生の組)。</summary>
 78    public static IEnumerable<Tuplet> Generate(int maxActual=13, int maxNormal=12)
 79    {
 80        for (int normal=2; normal<=maxNormal; normal++)
 81            for (int actual=2; actual<=maxActual; actual++)
 82                if (actual!=normal)
 83                    yield return new Tuplet(actual, normal);
 84    }
 85}
 86
 87/// <summary>MIDI 非依存の簡易イベント。Status は 0x9x / 0x8x 相当を想定。</summary>
 88public readonly struct MidiNoteEvent
 89{
 90    public int Channel { get; }
 91    public int Pitch { get; }
 92    public int Velocity { get; }
 93    public long Tick { get; }
 94    public bool IsNoteOn { get; }
 95    public MidiNoteEvent(int channel, int pitch, int velocity, long tick, bool isNoteOn)
 96    { Channel=channel; Pitch=pitch; Velocity=velocity; Tick=tick; IsNoteOn=isNoteOn; }
 97    public override string ToString()=> $"{(IsNoteOn?"On":"Off")}[ch{Channel}] p{Pitch} v{Velocity} @{Tick}";
 98}
 99
 100/// <summary>Note から NoteOn/NoteOff イベント列を生成するヘルパ。</summary>
 101public static class MidiNoteBuilder
 102{
 103    /// <summary>シンプルな単音生成。</summary>
 104    public static IEnumerable<MidiNoteEvent> BuildSingle(int channel, int pitch, int velocity, long startTick, Duration 
 105    {
 106        yield return new MidiNoteEvent(channel, pitch, velocity, startTick, true);
 107        yield return new MidiNoteEvent(channel, pitch, 0, startTick + dur.Ticks, false);
 108    }
 109
 110    /// <summary>シーケンス (pitch, start, duration, velocity 可変) からソート済イベントを生成。</summary>
 111    public static IEnumerable<MidiNoteEvent> BuildMany(IEnumerable<(int pitch,long start,Duration dur,int velocity,int c
 112        => notes.SelectMany(n => BuildSingle(n.channel, n.pitch, n.velocity, n.start, n.dur))
 113                .OrderBy(e=>e.Tick).ThenBy(e=>!e.IsNoteOn); // 同 tick では NoteOff 先行で重なり制御可 (用途で逆も)
 114}
 115
 116/// <summary>拍子変更マップ (単純: ソート済境界 + 検索)。</summary>
 117public sealed class TimeSignatureMap
 118{
 119    private readonly List<(long tick, TimeSignature sig)> _entries = new();
 120    public TimeSignatureMap(TimeSignature initial) { _entries.Add((0, initial)); }
 121    public void AddChange(long tick, TimeSignature sig)
 122    {
 123        if (tick < 0) throw new ArgumentOutOfRangeException(nameof(tick));
 124        if (_entries.Any(e=>e.tick==tick)) _entries.RemoveAll(e=>e.tick==tick);
 125        _entries.Add((tick, sig));
 126        _entries.Sort((a,b)=>a.tick.CompareTo(b.tick));
 127    }
 128    public TimeSignature GetAt(long tick)
 129    {
 130        TimeSignature current = _entries[0].sig;
 131        foreach (var e in _entries)
 132        {
 133            if (e.tick > tick) break;
 134            current = e.sig;
 135        }
 136        return current;
 137    }
 138    public TimePosition ToPosition(long tick)
 139        => new(GetAt(tick), tick);
 140}
 141
 142/// <summary>テンポ (microseconds per quarter note)。</summary>
 143public readonly struct Tempo : IEquatable<Tempo>
 144{
 145    public int MicrosecondsPerQuarter { get; }
 146    public Tempo(int usPerQuarter)
 147    { if (usPerQuarter<=0) throw new ArgumentOutOfRangeException(nameof(usPerQuarter)); MicrosecondsPerQuarter=usPerQuar
 148    public double Bpm => 60_000_000.0 / MicrosecondsPerQuarter;
 149    public static Tempo FromBpm(double bpm) => new((int)(60_000_000 / bpm));
 150    public bool Equals(Tempo other)=> MicrosecondsPerQuarter==other.MicrosecondsPerQuarter;
 151    public override bool Equals(object? o)=> o is Tempo t && Equals(t);
 152    public override int GetHashCode()=> MicrosecondsPerQuarter;
 153    public override string ToString()=>$"{Bpm:F2} BPM";
 154}
 155
 156public readonly struct TempoChange { public long Tick { get; } public Tempo Tempo { get; } public TempoChange(long t, Te
 157
 158/// <summary>メタイベント (簡易)。</summary>
 159public abstract record MetaEvent(long Tick);
 160public sealed record TempoEvent(long Tick, Tempo Tempo): MetaEvent(Tick);
 161public sealed record TimeSignatureEvent(long Tick, TimeSignature Signature): MetaEvent(Tick);
 162public sealed record KeySignatureEvent(long Tick, int SharpsFlats, bool IsMinor): MetaEvent(Tick); // SharpsFlats: -7..+
 163public sealed record TextEvent(long Tick, string Text): MetaEvent(Tick);
 4164public sealed record TrackNameEvent(long Tick, string Name): MetaEvent(Tick);
 165public sealed record MarkerEvent(long Tick, string Label): MetaEvent(Tick);
 166
 167/// <summary>MIDI トラック的コンテナ (ノートイベント + メタイベント)。</summary>
 168public sealed class MidiTrack
 169{
 170    private readonly List<MidiNoteEvent> _notes = new();
 171    private readonly List<MetaEvent> _meta = new();
 172    public IReadOnlyList<MidiNoteEvent> Notes => _notes;
 173    public IReadOnlyList<MetaEvent> MetaEvents => _meta;
 174    public void AddNote(MidiNoteEvent e) => _notes.Add(e);
 175    public void AddMeta(MetaEvent e) => _meta.Add(e);
 176    public void AddNoteRange(IEnumerable<MidiNoteEvent> events) => _notes.AddRange(events);
 177    public void Sort()
 178    {
 179        _notes.Sort((a,b)=> a.Tick==b.Tick ? (a.IsNoteOn==b.IsNoteOn ? 0 : (a.IsNoteOn ? 1:-1)) : a.Tick.CompareTo(b.Tic
 180        _meta.Sort((a,b)=> a.Tick.CompareTo(b.Tick));
 181    }
 182}
 183
 184/// <summary>クオンタイズ & スイングユーティリティ。</summary>
 185public static class Quantize
 186{
 187    /// <summary>指定分解能 (ticks) に丸め (最近傍)。</summary>
 188    public static long ToGrid(long tick, int gridTicks)
 189    {
 190        if (gridTicks<=0) return tick;
 191        long q = tick / gridTicks;
 192        long r = tick % gridTicks;
 193        return r < gridTicks/2 ? q*gridTicks : (q+1)*gridTicks;
 194    }
 195    /// <summary>スイング (2つ目の8分を遅らせる) Ratio=0..1 (0=ストレート, 0.5=三連近似)。</summary>
 196    public static long ApplySwing(long tick, int subdivisionTicks, double ratio)
 197    {
 198        if (ratio<=0) return tick;
 199        int pair = subdivisionTicks*2;
 200        long inPair = tick % pair;
 201        if (inPair < subdivisionTicks) return tick; // 1つ目はそのまま
 202        double delay = subdivisionTicks * ratio;
 203        return tick + (long)delay;
 204    }
 205
 206    /// <summary>
 207    /// 強拍優先クオンタイズ: 四捨五入結果と隣接強拍のどちらが近いか + バイアス係数で決める。
 208    /// strongBeatsTicksWithinBar は 拍子内 (0..TicksPerBar) の強拍 tick (0 基点) リスト。
 209    /// bias (0..1) 高いほど強拍へ吸着しやすい。
 210    /// </summary>
 211    public static long ToGridStrongBeat(long tick, int gridTicks, int ticksPerBar, IReadOnlyList<int> strongBeatsTicksWi
 212    {
 213        if (gridTicks<=0 || strongBeatsTicksWithinBar.Count==0) return ToGrid(tick, gridTicks);
 214        long rounded = ToGrid(tick, gridTicks);
 215        long barIndex = tick / ticksPerBar;
 216        long posInBar = tick % ticksPerBar;
 217        int nearestStrong = strongBeatsTicksWithinBar.OrderBy(b => Math.Abs(b - posInBar)).First();
 218        long strongTick = barIndex * ticksPerBar + nearestStrong;
 219        long distRounded = Math.Abs(rounded - tick);
 220        long distStrong = Math.Abs(strongTick - tick);
 221        if (distStrong * (1.0 - bias) < distRounded) return strongTick; // バイアス適用
 222        return rounded;
 223    }
 224
 225    /// <summary>
 226    /// グルーヴテンプレート適用: pattern[i]=tick オフセット (±) を 1 サイクル (pattern.Length グリッド) で繰返し適用。
 227    /// 先にグリッドに丸めた後、オフセットを加算。
 228    /// </summary>
 229    public static long ApplyGroove(long tick, int gridTicks, IReadOnlyList<int> pattern)
 230    {
 231        if (gridTicks<=0 || pattern.Count==0) return tick;
 232        long baseGrid = ToGrid(tick, gridTicks);
 233        long index = (baseGrid / gridTicks) % pattern.Count;
 234        return baseGrid + pattern[(int)index];
 235    }
 236}

Methods/Properties

get_Name()