# Copyright 2023 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 performance_encoder_decoder."""

import math

from absl.testing import absltest
from note_seq import performance_encoder_decoder
from note_seq import performance_lib
from note_seq.performance_encoder_decoder import ModuloPerformanceEventSequenceEncoderDecoder
from note_seq.performance_encoder_decoder import NotePerformanceEventSequenceEncoderDecoder
from note_seq.performance_encoder_decoder import PerformanceModuloEncoding
from note_seq.performance_lib import PerformanceEvent

cos = math.cos
sin = math.sin
pi = math.pi


class PerformanceOneHotEncodingTest(absltest.TestCase):

  def setUp(self):
    self.enc = performance_encoder_decoder.PerformanceOneHotEncoding(
        num_velocity_bins=16)

  def testEncodeDecode(self):
    expected_pairs = [
        (PerformanceEvent(
            event_type=PerformanceEvent.NOTE_ON, event_value=60), 60),
        (PerformanceEvent(
            event_type=PerformanceEvent.NOTE_ON, event_value=0), 0),
        (PerformanceEvent(
            event_type=PerformanceEvent.NOTE_ON, event_value=127), 127),
        (PerformanceEvent(
            event_type=PerformanceEvent.NOTE_OFF, event_value=72), 200),
        (PerformanceEvent(
            event_type=PerformanceEvent.NOTE_OFF, event_value=0), 128),
        (PerformanceEvent(
            event_type=PerformanceEvent.NOTE_OFF, event_value=127), 255),
        (PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=10), 265),
        (PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=1), 256),
        (PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=100), 355),
        (PerformanceEvent(
            event_type=PerformanceEvent.VELOCITY, event_value=5), 360),
        (PerformanceEvent(
            event_type=PerformanceEvent.VELOCITY, event_value=1), 356),
        (PerformanceEvent(
            event_type=PerformanceEvent.VELOCITY, event_value=16), 371)
    ]

    for expected_event, expected_index in expected_pairs:
      index = self.enc.encode_event(expected_event)
      self.assertEqual(expected_index, index)
      event = self.enc.decode_event(expected_index)
      self.assertEqual(expected_event, event)

  def testEventToNumSteps(self):
    self.assertEqual(0, self.enc.event_to_num_steps(
        PerformanceEvent(event_type=PerformanceEvent.NOTE_ON, event_value=60)))
    self.assertEqual(0, self.enc.event_to_num_steps(
        PerformanceEvent(event_type=PerformanceEvent.NOTE_OFF, event_value=67)))
    self.assertEqual(0, self.enc.event_to_num_steps(
        PerformanceEvent(event_type=PerformanceEvent.VELOCITY, event_value=10)))

    self.assertEqual(1, self.enc.event_to_num_steps(
        PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=1)))
    self.assertEqual(45, self.enc.event_to_num_steps(
        PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=45)))
    self.assertEqual(100, self.enc.event_to_num_steps(
        PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=100)))


class PerformanceModuloEncodingTest(absltest.TestCase):
  """Test class for PerformanceModuloEncoding."""

  def setUp(self):
    self._num_velocity_bins = 16
    self._max_shift_steps = performance_lib.DEFAULT_MAX_SHIFT_STEPS
    self.enc = PerformanceModuloEncoding(
        num_velocity_bins=self._num_velocity_bins,
        max_shift_steps=self._max_shift_steps)

    self._expected_input_size = (
        2 * performance_encoder_decoder.MODULO_PITCH_ENCODER_WIDTH +
        performance_encoder_decoder.MODULO_VELOCITY_ENCODER_WIDTH +
        performance_encoder_decoder.MODULO_TIME_SHIFT_ENCODER_WIDTH)

    self._expected_num_classes = (self._num_velocity_bins +
                                  self._max_shift_steps +
                                  (performance_lib.MAX_MIDI_PITCH -
                                   performance_lib.MIN_MIDI_PITCH + 1) * 2)

  def testInputSize(self):
    self.assertEqual(self._expected_input_size, self.enc.input_size)

  def testEmbedPitchClass(self):
    # The following are true only for semitone_steps = 1.
    expected_pairs = [
        (0, (cos(0.0), sin(0.0))),
        (1, (cos(pi / 6.0), sin(pi / 6.0))),
        (2, (cos(pi / 3.0), sin(pi / 3.0))),
        (3, (cos(pi / 2.0), sin(pi / 2.0))),
        (4, (cos(2.0 * pi / 3.0), sin(2.0 * pi / 3.0))),
        (5, (cos(5.0 * pi / 6.0), sin(5.0 * pi / 6.0))),
        (6, (cos(pi), sin(pi))),
        (7, (cos(7.0 * pi / 6.0), sin(7.0 * pi / 6.0))),
        (8, (cos(4.0 * pi / 3.0), sin(4.0 * pi / 3.0))),
        (9, (cos(3.0 * pi / 2.0), sin(3.0 * pi / 2.0))),
        (10, (cos(5.0 * pi / 3.0), sin(5.0 * pi / 3.0))),
        (11, (cos(11.0 * pi / 6.0), sin(11.0 * pi / 6.0)))]

    for note, expected_embedding in expected_pairs:
      actual_embedding = self.enc.embed_pitch_class(note)
      self.assertEqual(actual_embedding[0], expected_embedding[0])
      self.assertEqual(actual_embedding[1], expected_embedding[1])

  def testEmbedNote(self):
    # The following are true only for semitone_steps = 1.
    base = 72.0
    expected_pairs = [
        (0, (cos(0.0), sin(0.0))),
        (13, (cos(pi * 13.0 / base), sin(pi * 13.0 / base))),
        (26, (cos(pi * 26.0 / base), sin(pi * 26.0 / base))),
        (39, (cos(pi * 39.0 / base), sin(pi * 39.0 / base))),
        (52, (cos(pi * 52.0 / base), sin(pi * 52.0 / base))),
        (65, (cos(pi * 65.0 / base), sin(pi * 65.0 / base))),
        (78, (cos(pi * 78.0 / base), sin(pi * 78.0 / base))),
        (91, (cos(pi * 91.0 / base), sin(pi * 91.0 / base))),
        (104, (cos(pi * 104.0 / base), sin(pi * 104.0 / base))),
        (117, (cos(pi * 117.0 / base), sin(pi * 117.0 / base))),
        (130, (cos(pi * 130.0 / base), sin(pi * 130.0 / base))),
        (143, (cos(pi * 143.0 / base), sin(pi * 143.0 / base)))]

    for note, expected_embedding in expected_pairs:
      actual_embedding = self.enc.embed_note(note)
      self.assertEqual(actual_embedding[0], expected_embedding[0])
      self.assertEqual(actual_embedding[1], expected_embedding[1])

  def testEmbedTimeShift(self):
    # The following are true only for semitone_steps = 1.
    base = self._max_shift_steps  # 100
    expected_pairs = [
        (0, (cos(0.0), sin(0.0))),
        (2, (cos(2.0 * pi * 2.0 / base), sin(2.0 * pi * 2.0 / base))),
        (5, (cos(2.0 * pi * 5.0 / base), sin(2.0 * pi * 5.0 / base))),
        (13, (cos(2.0 * pi * 13.0 / base), sin(2.0 * pi * 13.0 / base))),
        (20, (cos(2.0 * pi * 20.0 / base), sin(2.0 * pi * 20.0 / base))),
        (45, (cos(2.0 * pi * 45.0 / base), sin(2.0 * pi * 45.0 / base))),
        (70, (cos(2.0 * pi * 70.0 / base), sin(2.0 * pi * 70.0 / base))),
        (99, (cos(2.0 * pi * 99.0 / base), sin(2.0 * pi * 99.0 / base)))]

    for time_shift, expected_embedding in expected_pairs:
      actual_embedding = self.enc.embed_time_shift(time_shift)
      self.assertEqual(actual_embedding[0], expected_embedding[0])
      self.assertEqual(actual_embedding[1], expected_embedding[1])

  def testEmbedVelocity(self):
    # The following are true only for semitone_steps = 1.
    base = self._num_velocity_bins  # 16
    expected_pairs = [
        (0, (cos(0.0), sin(0.0))),
        (2, (cos(2.0 * pi * 2.0 / base), sin(2.0 * pi * 2.0 / base))),
        (5, (cos(2.0 * pi * 5.0 / base), sin(2.0 * pi * 5.0 / base))),
        (7, (cos(2.0 * pi * 7.0 / base), sin(2.0 * pi * 7.0 / base))),
        (10, (cos(2.0 * pi * 10.0 / base), sin(2.0 * pi * 10.0 / base))),
        (13, (cos(2.0 * pi * 13.0 / base), sin(2.0 * pi * 13.0 / base))),
        (15, (cos(2.0 * pi * 15.0 / base), sin(2.0 * pi * 15.0 / base)))]

    for velocity, expected_embedding in expected_pairs:
      actual_embedding = self.enc.embed_velocity(velocity)
      self.assertEqual(actual_embedding[0], expected_embedding[0])
      self.assertEqual(actual_embedding[1], expected_embedding[1])

  def testEncodeModuloEvent(self):
    expected_pairs = [
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_ON, event_value=60),
         (0, PerformanceEvent.NOTE_ON, 60)),
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_ON, event_value=0),
         (0, PerformanceEvent.NOTE_ON, 0)),
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_ON, event_value=127),
         (0, PerformanceEvent.NOTE_ON, 127)),
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_OFF, event_value=72),
         (5, PerformanceEvent.NOTE_OFF, 72)),
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_OFF, event_value=0),
         (5, PerformanceEvent.NOTE_OFF, 0)),
        (PerformanceEvent(
            event_type=PerformanceEvent.NOTE_OFF, event_value=127),
         (5, PerformanceEvent.NOTE_OFF, 127)),
        (PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=10),
         (10, PerformanceEvent.TIME_SHIFT, 9)),
        (PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=1),
         (10, PerformanceEvent.TIME_SHIFT, 0)),
        (PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=100),
         (10, PerformanceEvent.TIME_SHIFT, 99)),
        (PerformanceEvent(event_type=PerformanceEvent.VELOCITY, event_value=5),
         (13, PerformanceEvent.VELOCITY, 4)),
        (PerformanceEvent(event_type=PerformanceEvent.VELOCITY, event_value=1),
         (13, PerformanceEvent.VELOCITY, 0)),
        (PerformanceEvent(event_type=PerformanceEvent.VELOCITY, event_value=16),
         (13, PerformanceEvent.VELOCITY, 15)),
    ]

    # expected_encoded_modulo_event is of the following form:
    # (offset, encoder_width, event_type, value, bins)
    for event, expected_encoded_modulo_event in expected_pairs:
      actual_encoded_modulo_event = self.enc.encode_modulo_event(event)
      self.assertEqual(actual_encoded_modulo_event,
                       expected_encoded_modulo_event)


class ModuloPerformanceEventSequenceEncoderTest(absltest.TestCase):
  """Test class for ModuloPerformanceEventSequenceEncoder.

  ModuloPerformanceEventSequenceEncoderDecoder is tightly coupled with the
  PerformanceModuloEncoding, and PerformanceOneHotEncoding classes. As a result,
  in the test set up, the test object is initialized with one of each objects
  and tested accordingly. Since this class only modifies the input encoding
  of performance events, and otherwise its treatment of labels is the same as
  OneHotEventSequenceEncoderDecoder, the events_to_labels(), and
  class_index_to_event() methods of the class are not tested.
  """

  def setUp(self):
    self._num_velocity_bins = 32
    self._max_shift_steps = 100
    self.enc = ModuloPerformanceEventSequenceEncoderDecoder(
        num_velocity_bins=self._num_velocity_bins,
        max_shift_steps=self._max_shift_steps)

    self._expected_input_size = (
        2 * performance_encoder_decoder.MODULO_PITCH_ENCODER_WIDTH +
        performance_encoder_decoder.MODULO_VELOCITY_ENCODER_WIDTH +
        performance_encoder_decoder.MODULO_TIME_SHIFT_ENCODER_WIDTH)

    self._expected_num_classes = (self._num_velocity_bins +
                                  self._max_shift_steps +
                                  2 * (performance_lib.MAX_MIDI_PITCH -
                                       performance_lib.MIN_MIDI_PITCH + 1))

  def testInputSize(self):
    self.assertEqual(self._expected_input_size, self.enc.input_size)

  def testNumClasses(self):
    self.assertEqual(self._expected_num_classes, self.enc.num_classes)

  def testDefaultEventLabel(self):
    label = self._expected_num_classes - self._num_velocity_bins - 1
    self.assertEqual(label, self.enc.default_event_label)

  def testEventsToInput(self):
    num_shift_bins = self._max_shift_steps
    num_velocity_bins = self._num_velocity_bins
    slow_base = 2.0 * pi / 144.0
    fast_base = 2.0 * pi / 12.0
    shift_base = 2.0 * pi / num_shift_bins
    velocity_base = 2.0 * pi / num_velocity_bins

    expected_pairs = [
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_ON, event_value=60),
         [1.0, cos(60.0 * slow_base), sin(60.0 * slow_base),
          cos(60.0 * fast_base), sin(60.0 * fast_base),
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_ON, event_value=0),
         [1.0, cos(0.0 * slow_base), sin(0.0 * slow_base),
          cos(0.0 * fast_base), sin(0.0 * fast_base),
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_ON, event_value=127),
         [1.0, cos(127.0 * slow_base), sin(127.0 * slow_base),
          cos(127.0 * fast_base), sin(127.0 * fast_base),
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_OFF, event_value=72),
         [0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
          cos(72.0 * slow_base), sin(72.0 * slow_base),
          cos(72.0 * fast_base), sin(72.0 * fast_base),
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        (PerformanceEvent(event_type=PerformanceEvent.NOTE_OFF, event_value=0),
         [0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
          cos(0.0 * slow_base), sin(0.0 * slow_base),
          cos(0.0 * fast_base), sin(0.0 * fast_base),
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        (PerformanceEvent(
            event_type=PerformanceEvent.NOTE_OFF, event_value=127),
         [0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
          cos(127.0 * slow_base), sin(127.0 * slow_base),
          cos(127.0 * fast_base), sin(127.0 * fast_base),
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        (PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=10),
         [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
          1.0, cos(9.0 * shift_base), sin(9.0 * shift_base),
          0.0, 0.0, 0.0]),
        (PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=1),
         [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
          1.0, cos(0.0 * shift_base), sin(0.0 * shift_base),
          0.0, 0.0, 0.0]),
        (PerformanceEvent(
            event_type=PerformanceEvent.TIME_SHIFT, event_value=100),
         [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
          1.0, cos(99.0 * shift_base), sin(99.0 * shift_base),
          0.0, 0.0, 0.0]),
        (PerformanceEvent(event_type=PerformanceEvent.VELOCITY, event_value=5),
         [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0,
          1.0, cos(4.0 * velocity_base), sin(4.0 * velocity_base)]),
        (PerformanceEvent(event_type=PerformanceEvent.VELOCITY, event_value=1),
         [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0,
          1.0, cos(0.0 * velocity_base), sin(0.0 * velocity_base)]),
        (PerformanceEvent(event_type=PerformanceEvent.VELOCITY, event_value=16),
         [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0,
          1.0, cos(15.0 * velocity_base), sin(15.0 * velocity_base)]),
    ]

    events = []
    position = 0
    for event, expected_encoded_modulo_event in expected_pairs:
      events += [event]
      actual_encoded_modulo_event = self.enc.events_to_input(events, position)
      position += 1
      for i in range(self._expected_input_size):
        self.assertAlmostEqual(expected_encoded_modulo_event[i],
                               actual_encoded_modulo_event[i])


class NotePerformanceEventSequenceEncoderDecoderTest(absltest.TestCase):

  def setUp(self):
    self.enc = NotePerformanceEventSequenceEncoderDecoder(
        num_velocity_bins=16, max_shift_steps=99, max_duration_steps=500)

    self.assertEqual(10, self.enc.shift_steps_segments)
    self.assertEqual(20, self.enc.duration_steps_segments)

  def testEncodeDecode(self):
    pe = PerformanceEvent
    performance = [
        (pe(pe.TIME_SHIFT, 0), pe(pe.NOTE_ON, 60),
         pe(pe.VELOCITY, 13), pe(pe.DURATION, 401)),
        (pe(pe.TIME_SHIFT, 55), pe(pe.NOTE_ON, 64),
         pe(pe.VELOCITY, 13), pe(pe.DURATION, 310)),
        (pe(pe.TIME_SHIFT, 99), pe(pe.NOTE_ON, 67),
         pe(pe.VELOCITY, 16), pe(pe.DURATION, 100)),
        (pe(pe.TIME_SHIFT, 0), pe(pe.NOTE_ON, 67),
         pe(pe.VELOCITY, 16), pe(pe.DURATION, 1)),
        (pe(pe.TIME_SHIFT, 0), pe(pe.NOTE_ON, 67),
         pe(pe.VELOCITY, 16), pe(pe.DURATION, 500)),
    ]

    labels = [self.enc.events_to_label(performance, i)
              for i in range(len(performance))]

    expected_labels = [
        (0, 0, 60, 12, 16, 0),
        (5, 5, 64, 12, 12, 9),
        (9, 9, 67, 15, 3, 24),
        (0, 0, 67, 15, 0, 0),
        (0, 0, 67, 15, 19, 24),
    ]

    self.assertEqual(expected_labels, labels)

    inputs = [self.enc.events_to_input(performance, i)
              for i in range(len(performance))]

    for input_ in inputs:
      self.assertEqual(6, input_.nonzero()[0].shape[0])

    decoded_performance = [self.enc.class_index_to_event(label, None)
                           for label in labels]

    self.assertEqual(performance, decoded_performance)


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