Skip to content

Commit

Permalink
fix: units refactoring and minor improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
michaldivis committed Nov 11, 2023
1 parent 87ac47c commit 93e432b
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 363 deletions.
3 changes: 2 additions & 1 deletion sample/SampleConsoleApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using SampleConsoleApp.Demos;
using DarkMusicConcepts;
using SampleConsoleApp.Demos;

var demos = new Demo[]
{
Expand Down
36 changes: 8 additions & 28 deletions src/DarkMusicConcepts.Units/Bpm.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
namespace DarkMusicConcepts;

public class Bpm : Unit<double, Bpm>
/// <summary>
/// <para>Beats per minute.</para>
/// <para>In musical terminology, tempo (Italian, 'time'; plural tempos, or tempi from the Italian plural) also known as beats per minute, is the speed or pace of a given composition. In classical music, tempo is typically indicated with an instruction at the start of a piece (often using conventional Italian terms) and is usually measured in beats per minute (or bpm). In modern classical compositions, a "metronome mark" in beats per minute may supplement or replace the normal tempo marking, while in modern genres like electronic dance music, tempo will typically simply be stated in BPM.</para>
/// </summary>
public class Bpm : Unit<double, Bpm>, IUnit<double, Bpm>
{
public const double MinValue = 0;
public const double MaxValue = double.MaxValue;

public static Bpm Min { get; } = From(MinValue);
public static Bpm Max { get; } = From(MaxValue);
public static double MinValue { get; } = 0;
public static double MaxValue { get; } = double.MaxValue;

private Bpm(double value) : base(value)
{
}

protected override double GetMinValue() => MinValue;
protected override double GetMaxValue() => MaxValue;

public static Bpm From(double value)
{
var bpm = new Bpm(value);

bpm.Validate();

return bpm;
}

public static bool TryFrom(double value, out Bpm bpm)
{
var x = new Bpm(value);

bpm = x.TryValidate()
? x
: null!;

return bpm is not null;
}
static Bpm IUnit<double, Bpm>.Create(double value) => new(value);
}
37 changes: 5 additions & 32 deletions src/DarkMusicConcepts.Units/Frequency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,16 @@
/// <para>Note frequencies are mathematically related to each other, and are defined around the central note, A4 (A @ OneLine octave) <see href="http://en.wikipedia.org/wiki/Piano_key_frequencies"/>. The current "standard pitch" or modern "concert pitch" for this note is 440 Hz. The formula for frequency calculatio is:</para>
/// <para>f = 2^n/12 * 440Hz(n is the pitch distance to A4 or A @ OneLine).</para>
/// </summary>
public class Frequency : Unit<double, Frequency>
public class Frequency : Unit<double, Frequency>, IUnit<double, Frequency>
{
public const double MinValue = 0;
public const double MaxValue = double.MaxValue;

public static Frequency Min { get; } = From(MinValue);
public static Frequency Max { get; } = From(MaxValue);
public static double MinValue { get; } = 0;
public static double MaxValue { get; } = double.MaxValue;

private Frequency(double value) : base(value)
{
}

protected override double GetMinValue() => MinValue;
protected override double GetMaxValue() => MaxValue;

public override string ToString()
{
return $"{Value} Hz";
}

public static Frequency From(double value)
{
var frequency = new Frequency(value);

frequency.Validate();
static Frequency IUnit<double, Frequency>.Create(double value) => new(value);

return frequency;
}

public static bool TryFrom(double value, out Frequency frequency)
{
var x = new Frequency(value);

frequency = x.TryValidate()
? x
: null!;

return frequency is not null;
}
public override string ToString() => $"{Value} Hz";
}
11 changes: 11 additions & 0 deletions src/DarkMusicConcepts.Units/IUnit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace DarkMusicConcepts;

public interface IUnit<TValue, TThis>
where TValue : IComparable, IComparable<TValue>, IEquatable<TValue>
{
public static abstract TValue MinValue { get; }
public static abstract TValue MaxValue { get; }

internal static abstract TThis Create(TValue value);
public static abstract bool IsValidValue(TValue? value);
}
32 changes: 4 additions & 28 deletions src/DarkMusicConcepts.Units/MidiNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,14 @@
/// <para>In MIDI, each note is assigned a numeric value, which is transmitted with any Note-On/Off message. Middle C has a reference value of 60. The MIDI standard only says that the note number 60 is a C, it does not say of which octave.</para>
/// <para>Due to the limitation of MIDI number's range (0-127), not all existing notes can be assigned one. There are more than 128 notes.</para>
/// </summary>
public class MidiNumber : Unit<int, MidiNumber>
public class MidiNumber : Unit<int, MidiNumber>, IUnit<int, MidiNumber>
{
public const int MinValue = 0;
public const int MaxValue = 127;

public static MidiNumber Min { get; } = From(MinValue);
public static MidiNumber Max { get; } = From(MaxValue);
public static int MinValue { get; } = 0;
public static int MaxValue { get; } = 127;

private MidiNumber(int value) : base(value)
{
}

protected override int GetMinValue() => MinValue;
protected override int GetMaxValue() => MaxValue;

public static MidiNumber From(int value)
{
var midiNumber = new MidiNumber(value);

midiNumber.Validate();

return midiNumber;
}

public static bool TryFrom(int value, out MidiNumber midiNumber)
{
var x = new MidiNumber(value);

midiNumber = x.TryValidate()
? x
: null!;

return midiNumber is not null;
}
static MidiNumber IUnit<int, MidiNumber>.Create(int value) => new(value);
}
34 changes: 5 additions & 29 deletions src/DarkMusicConcepts.Units/MidiVelocity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,14 @@
/// <para>MIDI velocity indicates how hard the key was struck when the note was played, which usually corresponds to the note's loudness.</para>
/// <para>The MIDI velocity range is from 0–127, with 127 being the loudest.</para>
/// </summary>
public class MidiVelocity : Unit<int, MidiVelocity>
public class MidiVelocity : Unit<int, MidiVelocity>, IUnit<int, MidiVelocity>
{
public const int MinValue = 0;
public const int MaxValue = 127;

public static MidiVelocity Min { get; } = From(MinValue);
public static MidiVelocity Max { get; } = From(MaxValue);
public static int MinValue { get; } = 0;
public static int MaxValue { get; } = 127;

private MidiVelocity(int value) : base(value)
{
}

protected override int GetMinValue() => MinValue;
protected override int GetMaxValue() => MaxValue;

public static MidiVelocity From(int value)
{
var midiVelocity = new MidiVelocity(value);

midiVelocity.Validate();

return midiVelocity;
}

public static bool TryFrom(int value, out MidiVelocity midiVelocity)
{
var x = new MidiVelocity(value);

midiVelocity = x.TryValidate()
? x
: null!;

return midiVelocity is not null;
}
}
static MidiVelocity IUnit<int, MidiVelocity>.Create(int value) => new(value);
}
152 changes: 63 additions & 89 deletions src/DarkMusicConcepts.Units/Time.cs
Original file line number Diff line number Diff line change
@@ -1,102 +1,79 @@
namespace DarkMusicConcepts;

/// <summary>
/// Representation of musical time, the value is stored in MIDI ticks
/// </summary>
public class Time : Unit<long, Time>
public class Time : Unit<long, Time>, IUnit<long, Time>
{
public const long MinValue = 0;
public const long MaxValue = long.MaxValue;

public static Time Min { get; } = From(MinValue);
public static Time Max { get; } = From(MaxValue);
public static long MinValue { get; } = 0;
public static long MaxValue { get; } = long.MaxValue;

private Time(long value) : base(value)
{
}

protected override long GetMinValue() => MinValue;
protected override long GetMaxValue() => MaxValue;

public static Time From(long ticks)
{
var time = new Time(ticks);

time.Validate();

return time;
}

public static bool TryFrom(long ticks, out Time time)
{
var x = new Time(ticks);

time = x.TryValidate()
? x
: null!;
static Time IUnit<long, Time>.Create(long value) => new(value);

return time is not null;
}

private const long TicksPerQuarterNote = 960;
private const long TicksPerSixteenthNote = TicksPerQuarterNote / 4;
private const long TicksPerSixteenthNoteTriplet = TicksPerQuarterNote / 6;
private const long TicksPerSixteenthNoteDotted = 360;
private const long _ticksPerQuarterNote = 960;
private const long _ticksPerSixteenthNote = _ticksPerQuarterNote / 4;
private const long _ticksPerSixteenthNoteTriplet = _ticksPerQuarterNote / 6;
private const long _ticksPerSixteenthNoteDotted = 360;

#region Helper members

public long Ticks => Value;

public long Bars => Ticks / TicksPerSixteenthNote / 16 / 4;

public long WholeNotes => Ticks / TicksPerSixteenthNote / 16;
public long HalfNotes => Ticks / TicksPerSixteenthNote / 8;
public long QuarterNotes => Ticks / TicksPerSixteenthNote / 4;
public long EightNotes => Ticks / TicksPerSixteenthNote / 2;
public long SixteenthNotes => Ticks / TicksPerSixteenthNote;
public long ThirtySecondNotes => Ticks / TicksPerSixteenthNote * 2;
public long SixtyForthNotes => Ticks / TicksPerSixteenthNote * 4;

public long WholeNoteTriplets => Ticks / TicksPerSixteenthNoteTriplet / 16;
public long HalfNoteTriplets => Ticks / TicksPerSixteenthNoteTriplet / 8;
public long QuarterNoteTriplets => Ticks / TicksPerSixteenthNoteTriplet / 4;
public long EightNoteTriplets => Ticks / TicksPerSixteenthNoteTriplet / 2;
public long SixteenthNoteTriplets => Ticks / TicksPerSixteenthNoteTriplet;
public long ThirtySecondNoteTriplets => Ticks / TicksPerSixteenthNoteTriplet * 2;
public long SixtyForthNoteTriplets => Ticks / TicksPerSixteenthNoteTriplet * 4;

public long WholeDottedNotes => Ticks / TicksPerSixteenthNoteDotted / 16;
public long HalfDottedNotes => Ticks / TicksPerSixteenthNoteDotted / 8;
public long QuarterDottedNotes => Ticks / TicksPerSixteenthNoteDotted / 4;
public long EightDottedNotes => Ticks / TicksPerSixteenthNoteDotted / 2;
public long SixteenthDottedNotes => Ticks / TicksPerSixteenthNoteDotted;
public long ThirtySecondDottedNotes => Ticks / TicksPerSixteenthNoteDotted * 2;
public long SixtyForthDottedNotes => Ticks / TicksPerSixteenthNoteDotted * 4;

public static Time FromBars(long amount) => From(amount * TicksPerSixteenthNote * 64);

public static Time FromWholeNotes(long amount) => From(amount * TicksPerSixteenthNote * 16);
public static Time FromHalfNotes(long amount) => From(amount * TicksPerSixteenthNote * 8);
public static Time FromQuarterNotes(long amount) => From(amount * TicksPerSixteenthNote * 4);
public static Time FromEightNotes(long amount) => From(amount * TicksPerSixteenthNote * 2);
public static Time FromSixteenthNotes(long amount) => From(amount * TicksPerSixteenthNote);
public static Time FromThirtySecondNotes(long amount) => From(amount * TicksPerSixteenthNote / 2);
public static Time FromSixtyForthNotes(long amount) => From(amount * TicksPerSixteenthNote / 4);

public static Time FromWholeNoteTriplets(long amount) => From(amount * TicksPerSixteenthNoteTriplet * 16);
public static Time FromHalfNoteTriplets(long amount) => From(amount * TicksPerSixteenthNoteTriplet * 8);
public static Time FromQuarterNoteTriplets(long amount) => From(amount * TicksPerSixteenthNoteTriplet * 4);
public static Time FromEightNoteTriplets(long amount) => From(amount * TicksPerSixteenthNoteTriplet * 2);
public static Time FromSixteenthNoteTriplets(long amount) => From(amount * TicksPerSixteenthNoteTriplet);
public static Time FromThirtySecondNoteTriplets(long amount) => From(amount * TicksPerSixteenthNoteTriplet / 2);
public static Time FromSixtyForthNoteTriplets(long amount) => From(amount * TicksPerSixteenthNoteTriplet / 4);

public static Time FromWholeNoteDotteds(long amount) => From(amount * TicksPerSixteenthNoteDotted * 16);
public static Time FromHalfNoteDotteds(long amount) => From(amount * TicksPerSixteenthNoteDotted * 8);
public static Time FromQuarterNoteDotteds(long amount) => From(amount * TicksPerSixteenthNoteDotted * 4);
public static Time FromEightNoteDotteds(long amount) => From(amount * TicksPerSixteenthNoteDotted * 2);
public static Time FromSixteenthNoteDotteds(long amount) => From(amount * TicksPerSixteenthNoteDotted);
public static Time FromThirtySecondNoteDotteds(long amount) => From(amount * TicksPerSixteenthNoteDotted / 2);
public static Time FromSixtyForthNoteDotteds(long amount) => From(amount * TicksPerSixteenthNoteDotted / 4);
public long Bars => Ticks / _ticksPerSixteenthNote / 16 / 4;

public long WholeNotes => Ticks / _ticksPerSixteenthNote / 16;
public long HalfNotes => Ticks / _ticksPerSixteenthNote / 8;
public long QuarterNotes => Ticks / _ticksPerSixteenthNote / 4;
public long EightNotes => Ticks / _ticksPerSixteenthNote / 2;
public long SixteenthNotes => Ticks / _ticksPerSixteenthNote;
public long ThirtySecondNotes => Ticks / _ticksPerSixteenthNote * 2;
public long SixtyForthNotes => Ticks / _ticksPerSixteenthNote * 4;

public long WholeNoteTriplets => Ticks / _ticksPerSixteenthNoteTriplet / 16;
public long HalfNoteTriplets => Ticks / _ticksPerSixteenthNoteTriplet / 8;
public long QuarterNoteTriplets => Ticks / _ticksPerSixteenthNoteTriplet / 4;
public long EightNoteTriplets => Ticks / _ticksPerSixteenthNoteTriplet / 2;
public long SixteenthNoteTriplets => Ticks / _ticksPerSixteenthNoteTriplet;
public long ThirtySecondNoteTriplets => Ticks / _ticksPerSixteenthNoteTriplet * 2;
public long SixtyForthNoteTriplets => Ticks / _ticksPerSixteenthNoteTriplet * 4;

public long WholeDottedNotes => Ticks / _ticksPerSixteenthNoteDotted / 16;
public long HalfDottedNotes => Ticks / _ticksPerSixteenthNoteDotted / 8;
public long QuarterDottedNotes => Ticks / _ticksPerSixteenthNoteDotted / 4;
public long EightDottedNotes => Ticks / _ticksPerSixteenthNoteDotted / 2;
public long SixteenthDottedNotes => Ticks / _ticksPerSixteenthNoteDotted;
public long ThirtySecondDottedNotes => Ticks / _ticksPerSixteenthNoteDotted * 2;
public long SixtyForthDottedNotes => Ticks / _ticksPerSixteenthNoteDotted * 4;

public static Time FromBars(long amount) => From(amount * _ticksPerSixteenthNote * 64);

public static Time FromWholeNotes(long amount) => From(amount * _ticksPerSixteenthNote * 16);
public static Time FromHalfNotes(long amount) => From(amount * _ticksPerSixteenthNote * 8);
public static Time FromQuarterNotes(long amount) => From(amount * _ticksPerSixteenthNote * 4);
public static Time FromEightNotes(long amount) => From(amount * _ticksPerSixteenthNote * 2);
public static Time FromSixteenthNotes(long amount) => From(amount * _ticksPerSixteenthNote);
public static Time FromThirtySecondNotes(long amount) => From(amount * _ticksPerSixteenthNote / 2);
public static Time FromSixtyForthNotes(long amount) => From(amount * _ticksPerSixteenthNote / 4);

public static Time FromWholeNoteTriplets(long amount) => From(amount * _ticksPerSixteenthNoteTriplet * 16);
public static Time FromHalfNoteTriplets(long amount) => From(amount * _ticksPerSixteenthNoteTriplet * 8);
public static Time FromQuarterNoteTriplets(long amount) => From(amount * _ticksPerSixteenthNoteTriplet * 4);
public static Time FromEightNoteTriplets(long amount) => From(amount * _ticksPerSixteenthNoteTriplet * 2);
public static Time FromSixteenthNoteTriplets(long amount) => From(amount * _ticksPerSixteenthNoteTriplet);
public static Time FromThirtySecondNoteTriplets(long amount) => From(amount * _ticksPerSixteenthNoteTriplet / 2);
public static Time FromSixtyForthNoteTriplets(long amount) => From(amount * _ticksPerSixteenthNoteTriplet / 4);

public static Time FromWholeNoteDotteds(long amount) => From(amount * _ticksPerSixteenthNoteDotted * 16);
public static Time FromHalfNoteDotteds(long amount) => From(amount * _ticksPerSixteenthNoteDotted * 8);
public static Time FromQuarterNoteDotteds(long amount) => From(amount * _ticksPerSixteenthNoteDotted * 4);
public static Time FromEightNoteDotteds(long amount) => From(amount * _ticksPerSixteenthNoteDotted * 2);
public static Time FromSixteenthNoteDotteds(long amount) => From(amount * _ticksPerSixteenthNoteDotted);
public static Time FromThirtySecondNoteDotteds(long amount) => From(amount * _ticksPerSixteenthNoteDotted / 2);
public static Time FromSixtyForthNoteDotteds(long amount) => From(amount * _ticksPerSixteenthNoteDotted / 4);

public static readonly Time Zero = From(0);

Expand Down Expand Up @@ -158,15 +135,12 @@ public static bool TryFrom(long ticks, out Time time)
/// <param name="bpm">The tempo to get duration for</param>
public TimeSpan GetDuration(Bpm bpm)
{
var usPerQuarter = (double)60_000 / bpm.Value;
var usPerTick = (double)usPerQuarter / TicksPerQuarterNote;
var usPerQuarter = 60_000 / bpm.Value;
var usPerTick = (double)usPerQuarter / _ticksPerQuarterNote;
var ms = (double)usPerTick * Ticks;
var duration = TimeSpan.FromMilliseconds(ms);
return duration;
}

public override string ToString()
{
return $"{Ticks:n0} ticks";
}
public override string ToString() => $"{Ticks:n0} ticks";
}
Loading

0 comments on commit 93e432b

Please sign in to comment.