# Copyright 2022 The Magenta Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for chord_symbols_lib."""

from absl.testing import absltest
from note_seq import chord_symbols_lib

CHORD_QUALITY_MAJOR = chord_symbols_lib.CHORD_QUALITY_MAJOR
CHORD_QUALITY_MINOR = chord_symbols_lib.CHORD_QUALITY_MINOR
CHORD_QUALITY_AUGMENTED = chord_symbols_lib.CHORD_QUALITY_AUGMENTED
CHORD_QUALITY_DIMINISHED = chord_symbols_lib.CHORD_QUALITY_DIMINISHED
CHORD_QUALITY_OTHER = chord_symbols_lib.CHORD_QUALITY_OTHER


class ChordSymbolFunctionsTest(absltest.TestCase):

  def testTransposeChordSymbol(self):
    # Test basic triads.
    figure = chord_symbols_lib.transpose_chord_symbol('C', 2)
    self.assertEqual('D', figure)
    figure = chord_symbols_lib.transpose_chord_symbol('Abm', -3)
    self.assertEqual('Fm', figure)
    figure = chord_symbols_lib.transpose_chord_symbol('F#', 0)
    self.assertEqual('F#', figure)
    figure = chord_symbols_lib.transpose_chord_symbol('Cbb', 6)
    self.assertEqual('Fb', figure)
    figure = chord_symbols_lib.transpose_chord_symbol('C#', -5)
    self.assertEqual('G#', figure)

    # Test more complex chords.
    figure = chord_symbols_lib.transpose_chord_symbol('Co7', 7)
    self.assertEqual('Go7', figure)
    figure = chord_symbols_lib.transpose_chord_symbol('D+', -3)
    self.assertEqual('B+', figure)
    figure = chord_symbols_lib.transpose_chord_symbol('Fb9/Ab', 2)
    self.assertEqual('Gb9/Bb', figure)
    figure = chord_symbols_lib.transpose_chord_symbol('A6/9', -7)
    self.assertEqual('D6/9', figure)
    figure = chord_symbols_lib.transpose_chord_symbol('E7(add#9)', 0)
    self.assertEqual('E7(add#9)', figure)

  def testPitchesToChordSymbol(self):
    # Test basic triads.
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [60, 64, 67])
    self.assertEqual('C', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [45, 48, 52])
    self.assertEqual('Am', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [63, 66, 69])
    self.assertEqual('Ebo', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [71, 75, 79])
    self.assertEqual('B+', figure)

    # Test basic inversions.
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [59, 62, 67])
    self.assertEqual('G/B', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [65, 70, 73])
    self.assertEqual('Bbm/F', figure)

    # Test suspended chords.
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [62, 67, 69])
    self.assertEqual('Dsus', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [55, 60, 62, 65])
    self.assertEqual('Gsus7', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [67, 69, 74])
    self.assertEqual('Gsus2', figure)

    # Test more complex chords.
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [45, 46, 50, 53])
    self.assertEqual('Bbmaj7/A', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [63, 67, 70, 72, 74])
    self.assertEqual('Cm9/Eb', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [53, 60, 64, 67, 70])
    self.assertEqual('C7/F', figure)

    # Test chords with modifications.
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [67, 71, 72, 74, 77])
    self.assertEqual('G7(add4)', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [64, 68, 71, 74, 79])
    self.assertEqual('E7(#9)', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [60, 62, 64, 67])
    self.assertEqual('C(add2)', figure)
    figure = chord_symbols_lib.pitches_to_chord_symbol(
        [60, 64, 68, 70, 75])
    self.assertEqual('C+7(#9)', figure)

    # Test invalid chord.
    with self.assertRaises(chord_symbols_lib.ChordSymbolError):
      chord_symbols_lib.pitches_to_chord_symbol(
          [60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71])

  def testChordSymbolPitches(self):
    pitches = chord_symbols_lib.chord_symbol_pitches('Am')
    pitch_classes = set(pitch % 12 for pitch in pitches)
    self.assertEqual(set([0, 4, 9]), pitch_classes)
    pitches = chord_symbols_lib.chord_symbol_pitches('D7b9')
    pitch_classes = set(pitch % 12 for pitch in pitches)
    self.assertEqual(set([0, 2, 3, 6, 9]), pitch_classes)
    pitches = chord_symbols_lib.chord_symbol_pitches('F/o')
    pitch_classes = set(pitch % 12 for pitch in pitches)
    self.assertEqual(set([3, 5, 8, 11]), pitch_classes)
    pitches = chord_symbols_lib.chord_symbol_pitches('C-(M7)')
    pitch_classes = set(pitch % 12 for pitch in pitches)
    self.assertEqual(set([0, 3, 7, 11]), pitch_classes)
    pitches = chord_symbols_lib.chord_symbol_pitches('E##13')
    pitch_classes = set(pitch % 12 for pitch in pitches)
    self.assertEqual(set([1, 3, 4, 6, 8, 10, 11]), pitch_classes)
    pitches = chord_symbols_lib.chord_symbol_pitches('G(add2)(#5)')
    pitch_classes = set(pitch % 12 for pitch in pitches)
    self.assertEqual(set([3, 7, 9, 11]), pitch_classes)

  def testChordSymbolRoot(self):
    root = chord_symbols_lib.chord_symbol_root('Dm9')
    self.assertEqual(2, root)
    root = chord_symbols_lib.chord_symbol_root('E/G#')
    self.assertEqual(4, root)
    root = chord_symbols_lib.chord_symbol_root('Bsus2')
    self.assertEqual(11, root)
    root = chord_symbols_lib.chord_symbol_root('Abmaj7')
    self.assertEqual(8, root)
    root = chord_symbols_lib.chord_symbol_root('D##5(add6)')
    self.assertEqual(4, root)
    root = chord_symbols_lib.chord_symbol_root('F(b7)(#9)(b13)')
    self.assertEqual(5, root)

  def testChordSymbolBass(self):
    bass = chord_symbols_lib.chord_symbol_bass('Dm9')
    self.assertEqual(2, bass)
    bass = chord_symbols_lib.chord_symbol_bass('E/G#')
    self.assertEqual(8, bass)
    bass = chord_symbols_lib.chord_symbol_bass('Bsus2/A')
    self.assertEqual(9, bass)
    bass = chord_symbols_lib.chord_symbol_bass('Abm7/Cb')
    self.assertEqual(11, bass)
    bass = chord_symbols_lib.chord_symbol_bass('C#6/9/E#')
    self.assertEqual(5, bass)
    bass = chord_symbols_lib.chord_symbol_bass('G/o')
    self.assertEqual(7, bass)

  def testChordSymbolQuality(self):
    # Test major chords.
    quality = chord_symbols_lib.chord_symbol_quality('B13')
    self.assertEqual(CHORD_QUALITY_MAJOR, quality)
    quality = chord_symbols_lib.chord_symbol_quality('E7#9')
    self.assertEqual(CHORD_QUALITY_MAJOR, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Fadd2/Eb')
    self.assertEqual(CHORD_QUALITY_MAJOR, quality)
    quality = chord_symbols_lib.chord_symbol_quality('C6/9/Bb')
    self.assertEqual(CHORD_QUALITY_MAJOR, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Gmaj13')
    self.assertEqual(CHORD_QUALITY_MAJOR, quality)

    # Test minor chords.
    quality = chord_symbols_lib.chord_symbol_quality('C#-9')
    self.assertEqual(CHORD_QUALITY_MINOR, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Gm7/Bb')
    self.assertEqual(CHORD_QUALITY_MINOR, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Cbmmaj7')
    self.assertEqual(CHORD_QUALITY_MINOR, quality)
    quality = chord_symbols_lib.chord_symbol_quality('A-(M7)')
    self.assertEqual(CHORD_QUALITY_MINOR, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Bbmin')
    self.assertEqual(CHORD_QUALITY_MINOR, quality)

    # Test augmented chords.
    quality = chord_symbols_lib.chord_symbol_quality('D+/A#')
    self.assertEqual(CHORD_QUALITY_AUGMENTED, quality)
    quality = chord_symbols_lib.chord_symbol_quality('A+')
    self.assertEqual(CHORD_QUALITY_AUGMENTED, quality)
    quality = chord_symbols_lib.chord_symbol_quality('G7(#5)')
    self.assertEqual(CHORD_QUALITY_AUGMENTED, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Faug(add2)')
    self.assertEqual(CHORD_QUALITY_AUGMENTED, quality)

    # Test diminished chords.
    quality = chord_symbols_lib.chord_symbol_quality('Am7b5')
    self.assertEqual(CHORD_QUALITY_DIMINISHED, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Edim7')
    self.assertEqual(CHORD_QUALITY_DIMINISHED, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Bb/o')
    self.assertEqual(CHORD_QUALITY_DIMINISHED, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Fo')
    self.assertEqual(CHORD_QUALITY_DIMINISHED, quality)

    # Test other chords.
    quality = chord_symbols_lib.chord_symbol_quality('G5')
    self.assertEqual(CHORD_QUALITY_OTHER, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Bbsus2')
    self.assertEqual(CHORD_QUALITY_OTHER, quality)
    quality = chord_symbols_lib.chord_symbol_quality('Dsus')
    self.assertEqual(CHORD_QUALITY_OTHER, quality)
    quality = chord_symbols_lib.chord_symbol_quality('E(no3)')
    self.assertEqual(CHORD_QUALITY_OTHER, quality)


if __name__ == '__main__':
  absltest.main()
