/// <summary> /// Throws an exception if family is not valid. /// </summary> /// <param name="family">The family to validate.</param> /// <exception cref="ArgumentOutOfRangeException">The family is out-of-range.</exception> public static void Validate(this NoteFamily family) { if (!family.IsValid()) { throw new ArgumentOutOfRangeException("NoteFamily out of range"); } }
/// <summary> /// Returns the number of semitones that note is above tonic. /// </summary> /// <param name="note">The note.</param> /// <param name="tonic">The tonic.</param> /// <returns>The number of semitones, always in [0..11].</returns> public static int SemitonesAbove(this Note note, NoteFamily tonic) { int result = ((int)note % 12 - (int)tonic); if (result < 0) { result += 12; } return(result); }
/// <summary> /// Returns a copy of family which is made valid by wrapping if necessary. /// </summary> /// <param name="family">The family to wrap.</param> /// <returns>The wrapped copy of family.</returns> public static NoteFamily Wrapped(this NoteFamily family) { if ((int)family < 0) { return((NoteFamily)(11 - ((-(int)family - 1) % 12))); } else { return((NoteFamily)((int)family % 12)); } }
/// <summary> /// Constructs a chord from its root note, pattern, and inversion. /// </summary> /// <param name="root">The root note of the chord.</param> /// <param name="pattern">The chord pattern.</param> /// <param name="inversion">The inversion, in [0..N-1] where N is the number of notes /// in pattern.</param> /// <exception cref="ArgumentOutOfRangeException">root is invalid or inversion is out of /// range.</exception> public Chord(NoteFamily root, Pattern pattern, int inversion) { root.Validate(); if (inversion < 0 || inversion >= pattern.SemitoneSequence.Length) { throw new ArgumentOutOfRangeException("inversion out of range."); } this.root = root; this.pattern = pattern; this.inversion = inversion; this.invertedSequence = InvertSequence(pattern.SemitoneSequence, inversion); }
/// <summary> /// Constructs a scale from its tonic and its pattern. /// </summary> /// <param name="tonic"></param> /// <param name="pattern"></param> public Scale(NoteFamily tonic, Pattern pattern) { this.tonic = tonic; this.pattern = pattern; this.ascendingMask = new bool[13] { true, false, false, false, false, false, false, false, false, false, false, false, true }; this.descendingMask = new bool[13] { true, false, false, false, false, false, false, false, false, false, false, false, true }; if (!ComputeMasks()) { throw new ArgumentException("Invalid pattern."); } }
/// <summary> /// Returns the number of semitones that note is above tonic. /// </summary> /// <param name="note">The note.</param> /// <param name="tonic">The tonic.</param> /// <returns>The number of semitones, always in [0..11].</returns> public static int SemitonesAbove(this Note note, NoteFamily tonic) { int result = ((int)note % 12 - (int)tonic); if (result < 0) { result += 12; } return result; }
/// <summary> /// Returns the human-readable name of a note family. /// </summary> /// <param name="family">The family.</param> /// <exception cref="ArgumentOutOfRangeException">The family is out-of-range.</exception> public static string Name(this NoteFamily family) { family.Validate(); return(NoteFamilyNames[(int)family]); }
/// <summary> /// Returns true if the specified note family is valid. /// </summary> /// <param name="family">The family to test.</param> public static bool IsValid(this NoteFamily family) { return((int)family >= 0 && (int)family < 12); }
/// <summary> /// Fills ascendingMask and descendingMask based on ascendingDescendingPattern. /// </summary> /// <returns>True if the operation succeeded, false if the pattern was invalid.</returns> private bool ComputeMasks() { int[] semitoneSequence = pattern.SemitoneSequence; int numAscendingNotes = 2; int numDescendingNotes = 2; // First make sure it's non-empty and starts at zero. if (semitoneSequence == null || semitoneSequence.Length == 0 || semitoneSequence[0] != 0) { return false; } // Now run through the rest of the pattern and make sure it ascends and then descends, // and populate the masks as we go. bool ascending = true; for (int i = 1; i < semitoneSequence.Length; ++i) { if (ascending) { // Make sure we've just gone up. if (semitoneSequence[i] <= semitoneSequence[i - 1]) { return false; } // Make sure we haven't gone up too far. if (semitoneSequence[i] > 12) { return false; } // If we've reached 12, start descending, otherwise add this to the ascending // mask. if (semitoneSequence[i] == 12) { ascending = false; } else { // Add this to the ascending mask. ascendingMask[semitoneSequence[i]] = true; numAscendingNotes++; } } else { // Make sure we've just gone down. if (semitoneSequence[i] >= semitoneSequence[i - 1]) { return false; } // Make sure we haven't gone down too far. if (semitoneSequence[i] < 0) { return false; } // If we've reached 0, make sure we're the last element, otherwise add this // to the mask. if (semitoneSequence[i] == 0) { if (i != semitoneSequence.Length - 1) { return false; } } else { // Add this to the descending mask. descendingMask[semitoneSequence[i]] = true; numDescendingNotes++; } } } ascendingNotes = new NoteFamily[numAscendingNotes]; descendingNotes = new NoteFamily[numDescendingNotes]; int numAdded = 0; for (int i = 0; i <= 12; ++i) { if (ascendingMask[i]) { ascendingNotes[numAdded++] = (NoteFamily)(tonic + i).Wrapped(); } } numAdded = 0; for (int i = 12; i >= 0; --i) { if (descendingMask[i]) { descendingNotes[numAdded++] = (NoteFamily)(tonic + i).Wrapped(); } } return true; }
/// <summary> /// Fills ascendingMask and descendingMask based on ascendingDescendingPattern. /// </summary> /// <returns>True if the operation succeeded, false if the pattern was invalid.</returns> private bool ComputeMasks() { int[] semitoneSequence = pattern.SemitoneSequence; int numAscendingNotes = 2; int numDescendingNotes = 2; // First make sure it's non-empty and starts at zero. if (semitoneSequence == null || semitoneSequence.Length == 0 || semitoneSequence[0] != 0) { return(false); } // Now run through the rest of the pattern and make sure it ascends and then descends, // and populate the masks as we go. bool ascending = true; for (int i = 1; i < semitoneSequence.Length; ++i) { if (ascending) { // Make sure we've just gone up. if (semitoneSequence[i] <= semitoneSequence[i - 1]) { return(false); } // Make sure we haven't gone up too far. if (semitoneSequence[i] > 12) { return(false); } // If we've reached 12, start descending, otherwise add this to the ascending // mask. if (semitoneSequence[i] == 12) { ascending = false; } else { // Add this to the ascending mask. ascendingMask[semitoneSequence[i]] = true; numAscendingNotes++; } } else { // Make sure we've just gone down. if (semitoneSequence[i] >= semitoneSequence[i - 1]) { return(false); } // Make sure we haven't gone down too far. if (semitoneSequence[i] < 0) { return(false); } // If we've reached 0, make sure we're the last element, otherwise add this // to the mask. if (semitoneSequence[i] == 0) { if (i != semitoneSequence.Length - 1) { return(false); } } else { // Add this to the descending mask. descendingMask[semitoneSequence[i]] = true; numDescendingNotes++; } } } ascendingNotes = new NoteFamily[numAscendingNotes]; descendingNotes = new NoteFamily[numDescendingNotes]; int numAdded = 0; for (int i = 0; i <= 12; ++i) { if (ascendingMask[i]) { ascendingNotes[numAdded++] = (NoteFamily)(tonic + i).Wrapped(); } } numAdded = 0; for (int i = 12; i >= 0; --i) { if (descendingMask[i]) { descendingNotes[numAdded++] = (NoteFamily)(tonic + i).Wrapped(); } } return(true); }