次の方法で共有


Starting over with Voices.Interval

In the previous post in the Voices category I showed my re-imagined Voices.Note class and its close relatives. You can already see that the new design has a useful separation of tone from note. I had also mixed solfeggio and scale into my initial, abandoned, design. I’ll come to scale in the future but, in this post, I’ll show why solfeggio is, and should remain, distinct from note. At the same time I’ll introduce the second fundamental of music - interval. By the way if you want to know more about music theory check out the articles I’ve posted under the Voices Help category. These articles will be developed further over time.

So, the reason I’d made a horrific hybrid of solfeggio and note the first time round (like Seth Brundle spliced with the fly) was because I hadn’t appreciated what solfeggio is, or at least what the best way to interpret solfeggio is. Warburton’s Harmony is not alone in presenting solfeggio as a kind of algebra for notes. You’ll find many definitions describing the solfeggio syllables as the ‘notes of a scale’, and very few point out that they’re more usefully thought of as aliases for intervals above some scale tonic. So do is equivalent to perfect unison; di is augmented unison; me is major third; is minor third, and so on.

With solfeggio and interval neatly unified, I’ll now turn to interval itself and show how I implemented and optimised it in the Voices.Interval class. The kind of client code I want the Voices class library to be able to support is:

Note n1 = Note.C;

Interval interval = Interval.Second.Major;

Note n2 = n1 + interval.Inverse;

If you have experience of music theory you’ll know that an interval has two components – quality (e.g. major) and quantity (e.g. second). In my first, abandoned, version of Voices.Interval I gave the class just such fields. On top of those I gave it fields storing the number of compound intervals (for intervals greater than an octave) and the number of times the interval was diminished or augmented (which could be zero). It took me a long time and about 900 lines of C# to get the Interval class correct in its first version. Its methods (e.g. DecrementQuantity and IncrementQuantity), properties (e.g. Octaves) and operators were long and full of tortuous boundary condition logic.

What I’d missed is that there was no need to represent the state of an interval so literally. It eventually occurred to me that every note is some interval above or below C4. And you can represent the private state of any interval – ascending or descending, simple or compound – by the note which is that interval away from C4. It only remained to implement the properties in terms of that internal state by reusing the Voice.Note class and the methods, properties and operators I’d already implemented on it. This change was done surprisingly quickly and it was a real education to me that note and interval really are two aspects of the same idea. In lines of code the new class is well over a third shorter than its first incarnation. An extreme example is the Octaves property’s set clause which was reduced from 64 lines to 1 because Voices.Note has its own Octaves property.

The new Voices.Interval class, plus its nested and ancillary types, is around 2,500 lines of C# and it looks like this:

public class Interval : IComparable

Public Ancillary Types

class IntervalException : ApplicationException

enum IntervalQuantity

enum IntervalQuality

Public Nested Types

class GeneralIntervalList : IList, IEnumerator

abstract class GeneralInterval : IList, IEnumerator

class Unison : GeneralInterval

class Second : GeneralInterval

...

class Fifteenth : GeneralInterval

Public Fields

static bool NameIsAbbreviated

static bool NameIncludesCompoundText

static bool NameIncludesDirection

Public Constructors

Interval(Interval)

Interval(Interval interval, Int32 additionalAlteration)

Interval(Interval interval, Int32 additionalAlteration, Int32 additionalOctaves)

Interval(Interval interval, Int32 additionalAlteration, Int32 additionalOctaves, bool reverseDirection)

Public Properties

static Unison GeneralUnison { get; }

static Second GeneralSecond { get; }

...

static Fifteenth GeneralFifteenth { get; }

bool IsImmutable { get; }

bool IsAscending { get; }

Interval Complement { get; }

Interval Inversion { get; }

Interval Reverse { get; }

Int32 Alteration { get; set; }

Int32 Octaves { get; set; }

IntervalQuantity Quantity { get; set; }

IntervalQuality Quality { get; set; }

string CompoundTextTextual { get; }

string CompoundTextDigital { get; }

string AlterationText { get; }

string Name { get; }

Int32 Semitones { get; }

bool IsCompound { get; }

bool CanBePerfect { get; }

Interval SimpleInterval { get; }

Public Methods

void DecrementQuantity()

void IncrementQuantity()

void Diminish()

void Augment()

string GetName(bool abbreviated)

string GetName(bool abbreviated, bool includesCompoundText)

string GetName(bool abbreviated, bool includesCompoundText, bool includesDirection)

static bool Exists(IntervalQuantity quantity, IntervalQuality quality)

string ToString()

static Interval operator+(Interval lhs, Interval rhs)

static Interval operator-(Interval lhs, Interval rhs)

static bool operator==(Interval lhs, Interval rhs)

static bool operator!=(Interval lhs, Interval rhs)

static bool operator>(Interval lhs, Interval rhs)

static bool operator>=(Interval lhs, Interval rhs)

static bool operator<(Interval lhs, Interval rhs)

static bool operator<=(Interval lhs, Interval rhs)

bool Equals(object obj)

bool Equals(Interval rhs)

bool IdenticalTo(Interval rhs)

int GetHashCode()

int CompareTo(object arg)

By the way, I made Voices.Note a value type as I know I’ll be using a lot of Note[]. I don’t suspect I’ll be doing that with Interval, so I’ll leave it a reference type until there’s any good reason to change it.