pedromsantos/glenn

View on GitHub
src/__test__/Domain/Note.test.ts

Summary

Maintainability
B
6 hrs
Test Coverage
import * as fc from 'fast-check';

import { ChordPattern, ClosedChord } from '../../Domain/Chord';
import { Duration } from '../../Domain/Duration';
import { Interval, IntervalDirection } from '../../Domain/Interval';
import { MelodicLine, Note, Octave, Rest } from '../../Domain/Note';
import { Pitch } from '../../Domain/Pitch';

describe('Rest', () => {
  test('has a duration', () => {
    const rest = new Rest(Duration.Quarter);

    expect(rest.Duration).toBe(Duration.Quarter);
    expect(rest.DurationName).toBe(Duration.Quarter.Name);
    expect(rest.tick).toBe(Duration.Quarter.tick);
  });

  test('does not have a pitch', () => {
    const rest = new Rest(Duration.Quarter);

    expect([...rest.Pitches]).toHaveLength(0);
  });

  test('does not have an octave', () => {
    const rest = new Rest(Duration.Quarter);

    expect([...rest.Octaves]).toHaveLength(0);
    expect([...rest.OctaveNames]).toHaveLength(0);
  });

  test('does not have a note', () => {
    const rest = new Rest(Duration.Quarter);

    expect([...rest.Notes]).toHaveLength(0);
    expect([...rest.MidiNumbers]).toHaveLength(0);
  });
});

describe('Note', () => {
  describe('transpose using a', () => {
    const note = new Note(Pitch.C, Duration.Quarter, Octave.C1);

    test('Unison from C to C', () => {
      expect(note.transpose(Interval.Unison).Pitch).toBe(Pitch.C);
    });

    test('MinorSecond from C to D flat', () => {
      expect(note.transpose(Interval.MinorSecond).Pitch).toBe(Pitch.DFlat);
    });

    test('MajorSecond from C to D', () => {
      expect(note.transpose(Interval.MajorSecond).Pitch).toBe(Pitch.D);
    });

    test('AugmentedSecond from C to D sharp', () => {
      expect(note.transpose(Interval.AugmentedSecond).Pitch).toBe(Pitch.DSharp);
    });

    test('MinorThird from C to E flat', () => {
      expect(note.transpose(Interval.MinorThird).Pitch).toBe(Pitch.EFlat);
    });

    test('MajorThird from C to E', () => {
      expect(note.transpose(Interval.MajorThird).Pitch).toBe(Pitch.E);
    });

    test('PerfectFourth from C to F', () => {
      expect(note.transpose(Interval.PerfectFourth).Pitch).toBe(Pitch.F);
    });

    test('AugmentedFourth from C to F sharp', () => {
      expect(note.transpose(Interval.AugmentedFourth).Pitch).toBe(Pitch.FSharp);
    });

    test('DiminishedFifth from C to G flat', () => {
      expect(note.transpose(Interval.DiminishedFifth).Pitch).toBe(Pitch.GFlat);
    });

    test('PerfectFifth from C to G', () => {
      expect(note.transpose(Interval.PerfectFifth).Pitch).toBe(Pitch.G);
    });

    test('AugmentedFifth from C to G sharp', () => {
      expect(note.transpose(Interval.AugmentedFifth).Pitch).toBe(Pitch.GSharp);
    });

    test('MinorSixth from C to A flat', () => {
      expect(note.transpose(Interval.MinorSixth).Pitch).toBe(Pitch.AFlat);
    });

    test('MajorSixth from C to A', () => {
      expect(note.transpose(Interval.MajorSixth).Pitch).toBe(Pitch.A);
    });

    test('diminished seventh from C to B flat flat', () => {
      expect(note.transpose(Interval.DiminishedSeventh).Pitch).toBe(Pitch.A);
    });

    test('minor seventh from C to B flat', () => {
      expect(note.transpose(Interval.MinorSeventh).Pitch).toBe(Pitch.BFlat);
    });

    test('MajorSeventh from C to B', () => {
      expect(note.transpose(Interval.MajorSeventh).Pitch).toBe(Pitch.B);
    });
  });

  describe('measure intervals between', () => {
    const note = new Note(Pitch.C, Duration.Quarter, Octave.C1);

    test('C and Db to minor second', () => {
      expect(note.intervalTo(new Note(Pitch.DFlat, Duration.Quarter, Octave.C1))).toBe(
        Interval.MinorSecond
      );
    });

    test('C and D as MajorSecond', () => {
      expect(note.intervalTo(new Note(Pitch.D, Duration.Quarter, Octave.C1))).toBe(
        Interval.MajorSecond
      );
    });

    test('C and E flat as MinorThird', () => {
      expect(note.intervalTo(new Note(Pitch.EFlat, Duration.Quarter, Octave.C1))).toBe(
        Interval.MinorThird
      );
    });

    test('C and E as MajorThird', () => {
      expect(note.intervalTo(new Note(Pitch.E, Duration.Quarter, Octave.C1))).toBe(
        Interval.MajorThird
      );
    });

    test('C and F as PerfectFourth', () => {
      expect(note.intervalTo(new Note(Pitch.F, Duration.Quarter, Octave.C1))).toBe(
        Interval.PerfectFourth
      );
    });

    test('C and F sharp as AugmentedFourth', () => {
      expect(note.intervalTo(new Note(Pitch.FSharp, Duration.Quarter, Octave.C1))).toBe(
        Interval.AugmentedFourth
      );
    });

    test('C and G flat as DiminishedFifth', () => {
      expect(note.intervalTo(new Note(Pitch.GFlat, Duration.Quarter, Octave.C1))).toBe(
        Interval.DiminishedFifth
      );
    });

    test('C and G as PerfectFifth', () => {
      expect(note.intervalTo(new Note(Pitch.G, Duration.Quarter, Octave.C1))).toBe(
        Interval.PerfectFifth
      );
    });

    test('C and G sharp as AugmentedFifth', () => {
      expect(note.intervalTo(new Note(Pitch.GSharp, Duration.Quarter, Octave.C1))).toBe(
        Interval.AugmentedFifth
      );
    });

    test('C and A flat as MinorSixth', () => {
      expect(note.intervalTo(new Note(Pitch.AFlat, Duration.Quarter, Octave.C1))).toBe(
        Interval.MinorSixth
      );
    });

    test('C and A as MajorSixth', () => {
      expect(note.intervalTo(new Note(Pitch.A, Duration.Quarter, Octave.C1))).toBe(
        Interval.MajorSixth
      );
    });

    test('C and B flat as MinorSeventh', () => {
      expect(note.intervalTo(new Note(Pitch.BFlat, Duration.Quarter, Octave.C1))).toBe(
        Interval.MinorSeventh
      );
    });

    test('C and B as MajorSeventh', () => {
      expect(note.intervalTo(new Note(Pitch.B, Duration.Quarter, Octave.C1))).toBe(
        Interval.MajorSeventh
      );
    });
  });

  describe('interval direction between G and', () => {
    const note = new Note(Pitch.G, Duration.Quarter, Octave.C1);

    test('C to be descending', () => {
      expect(note.intervalDirection(new Note(Pitch.C, Duration.Quarter, Octave.C1))).toBe(
        IntervalDirection.Descending
      );
    });

    test('D to be descending', () => {
      expect(note.intervalDirection(new Note(Pitch.D, Duration.Quarter, Octave.C1))).toBe(
        IntervalDirection.Descending
      );
    });

    test('E to be descending', () => {
      expect(note.intervalDirection(new Note(Pitch.E, Duration.Quarter, Octave.C1))).toBe(
        IntervalDirection.Descending
      );
    });

    test('F to be descending', () => {
      expect(note.intervalDirection(new Note(Pitch.F, Duration.Quarter, Octave.C1))).toBe(
        IntervalDirection.Descending
      );
    });

    test('Gb to be descending', () => {
      expect(note.intervalDirection(new Note(Pitch.GFlat, Duration.Quarter, Octave.C1))).toBe(
        IntervalDirection.Descending
      );
    });

    test('G to be level', () => {
      expect(note.intervalDirection(new Note(Pitch.G, Duration.Quarter, Octave.C1))).toBe(
        IntervalDirection.Level
      );
    });

    test('G# to be ascending', () => {
      expect(note.intervalDirection(new Note(Pitch.GSharp, Duration.Quarter, Octave.C1))).toBe(
        IntervalDirection.Ascending
      );
    });

    test('A to be ascending', () => {
      expect(note.intervalDirection(new Note(Pitch.A, Duration.Quarter, Octave.C1))).toBe(
        IntervalDirection.Ascending
      );
    });

    test('B to be ascending', () => {
      expect(note.intervalDirection(new Note(Pitch.A, Duration.Quarter, Octave.C1))).toBe(
        IntervalDirection.Ascending
      );
    });
  });

  test('convert to primitive note', () => {
    const note = new Note(Pitch.C, Duration.Quarter, Octave.C1);

    const notePrimitive = note.To;

    expect(notePrimitive).toStrictEqual({
      duration: { value: 0.25, name: 'Quarter', fraction: '1/4' },
      pitch: { name: 'C', naturalName: 'C', value: 0, accidental: 0 },
      octave: { midi: 12, name: 'Contra', value: -8 },
    });
  });

  test('midi values for notes start at 0 and go to 128', () => {
    fc.assert(
      fc.property(
        fc.constantFrom(...Pitch.pitches),
        fc.constantFrom(...Octave.octaves),
        (pitch: Pitch, octave: Octave) => {
          const note = new Note(pitch, Duration.Quarter, octave);

          expect(note.MidiNumbers.pop()).toBe(octave.MidiBaseValue + pitch.NumericValue);
        }
      ),
      { verbose: true }
    );
  });

  test('Has a single pitch, octave and note', () => {
    const note = new Note(Pitch.C, Duration.Quarter, Octave.C1);

    expect(note.Pitch).toBe(Pitch.C);
    expect([...note.Pitches][0]).toBe(Pitch.C);
    expect([...note.Pitches]).toHaveLength(1);
    expect([...note.Notes][0]).toBe(note);
    expect([...note.Notes]).toHaveLength(1);
    expect([...note.Octaves][0]).toBe(Octave.C1);
    expect([...note.Octaves]).toHaveLength(1);
    expect(note.OctaveNames).toBe(Octave.C1.Name);
  });

  describe('C is chord tone of', () => {
    const note = new Note(Pitch.C, Duration.Quarter, Octave.C1);

    test('C Major', () => {
      const chord = new ClosedChord(Pitch.C, ChordPattern.Major, Duration.Quarter, Octave.C4);

      expect(note.isChordToneOf(chord)).toBeTruthy();
    });

    test('A minor 6', () => {
      const chord = new ClosedChord(Pitch.A, ChordPattern.Minor6, Duration.Quarter, Octave.C4);

      expect(note.isChordToneOf(chord)).toBeTruthy();
    });

    test('D minor 7', () => {
      const chord = new ClosedChord(Pitch.D, ChordPattern.Minor7, Duration.Quarter, Octave.C4);

      expect(note.isChordToneOf(chord)).toBeTruthy();
    });
  });

  describe('C is not chord tone of', () => {
    const note = new Note(Pitch.C, Duration.Quarter, Octave.C1);

    test('D minor', () => {
      const chord = new ClosedChord(Pitch.D, ChordPattern.Minor, Duration.Quarter, Octave.C4);

      expect(note.isChordToneOf(chord)).toBeFalsy();
    });
  });
});

describe('calculate interval between C1 and', () => {
  const firstNote = new Note(Pitch.C, Duration.Quarter, Octave.C1);

  test('D1 to be a major second', () => {
    const secondNote = new Note(Pitch.D, Duration.Quarter, Octave.C1);

    expect(firstNote.intervalTo(secondNote)).toStrictEqual(Interval.MajorSecond);
  });

  test('Eb1 to be a minor third', () => {
    const secondNote = new Note(Pitch.EFlat, Duration.Quarter, Octave.C1);

    expect(firstNote.intervalTo(secondNote)).toStrictEqual(Interval.MinorThird);
  });

  test('C1 to be a unison', () => {
    const secondNote = new Note(Pitch.C, Duration.Quarter, Octave.C1);

    expect(firstNote.intervalTo(secondNote)).toStrictEqual(Interval.Unison);
  });

  test('C2 to be an octave', () => {
    const secondNote = new Note(Pitch.C, Duration.Quarter, Octave.C2);

    expect(firstNote.intervalTo(secondNote)).toStrictEqual(Interval.PerfectOctave);
  });

  test('F2 to be an eleventh', () => {
    const secondNote = new Note(Pitch.F, Duration.Quarter, Octave.C2);

    expect(firstNote.intervalTo(secondNote)).toStrictEqual(Interval.PerfectEleventh);
  });

  test('F#2 to be a sharp eleventh', () => {
    const secondNote = new Note(Pitch.FSharp, Duration.Quarter, Octave.C2);

    expect(firstNote.intervalTo(secondNote)).toStrictEqual(Interval.AugmentedEleventh);
  });
});

describe('Octave', () => {
  test('should go up in frequency', () => {
    expect(Octave.C0.up().up().up().up().up().up().up().up().up()).toBe(Octave.C8);
  });

  test('should go down in frequency', () => {
    expect(Octave.C8.down().down().down().down().down().down().down().down().down()).toBe(
      Octave.C0
    );
  });

  test('convert to primitive octave', () => {
    expect(Octave.C1.To).toStrictEqual({
      midi: 12,
      name: 'Contra',
      value: -8,
    });
  });

  test('convert from primitive octave', () => {
    expect(
      Octave.From({
        midi: 12,
        name: 'Contra',
        value: -8,
      })
    ).toBe(Octave.C1);
  });

  test('not convert from invalid primitive octave', () => {
    expect(() =>
      Octave.From({
        midi: 12,
        name: 'Contra',
        value: -99,
      })
    ).toThrow();
  });
});

describe('Melodic line', () => {
  test('convert to primitive phrase', () => {
    const phrase = new MelodicLine([new Note(Pitch.C, Duration.Quarter, Octave.C1)]);
    const phrasePrimitive = phrase.To;

    expect(phrasePrimitive).toStrictEqual({
      notes: [
        {
          duration: { value: 0.25, name: 'Quarter', fraction: '1/4' },
          pitch: { name: 'C', value: 0, naturalName: 'C', accidental: 0 },
          octave: { midi: 12, name: 'Contra', value: -8 },
        },
      ],
    });
  });
});