///|
pub(all) enum PitchClass {
  C
  Cs
  D
  Ds
  E
  F
  Fs
  G
  Gs
  A
  As
  B
} derive(Debug, Eq, ToJson)

///|
pub fn pitch_class(midi : Int) -> PitchClass {
  match interval_class(midi) {
    0 => C
    1 => Cs
    2 => D
    3 => Ds
    4 => E
    5 => F
    6 => Fs
    7 => G
    8 => Gs
    9 => A
    10 => As
    _ => B
  }
}

///|
pub fn pitch_class_name(pitch : PitchClass) -> String {
  match pitch {
    C => "C"
    Cs => "C#"
    D => "D"
    Ds => "D#"
    E => "E"
    F => "F"
    Fs => "F#"
    G => "G"
    Gs => "G#"
    A => "A"
    As => "A#"
    B => "B"
  }
}

///|
pub fn octave_number(midi : Int) -> Int {
  midi / 12 - 1
}

///|
pub fn pitch_label(midi : Int) -> String {
  "\{pitch_class_name(pitch_class(midi))}\{octave_number(midi)}"
}

///|
pub fn normalize_pitch(midi : Int, low : Int, high : Int) -> Int {
  if low > high {
    midi
  } else if midi < low {
    normalize_pitch(midi + 12, low, high)
  } else if midi > high {
    normalize_pitch(midi - 12, low, high)
  } else {
    midi
  }
}

///|
pub fn transpose_pitch_class(pitch : PitchClass, semitones : Int) -> PitchClass {
  let base = match pitch {
    C => 0
    Cs => 1
    D => 2
    Ds => 3
    E => 4
    F => 5
    Fs => 6
    G => 7
    Gs => 8
    A => 9
    As => 10
    B => 11
  }
  pitch_class(base + semitones)
}

///|
pub fn is_black_key(midi : Int) -> Bool {
  match pitch_class(midi) {
    Cs | Ds | Fs | Gs | As => true
    _ => false
  }
}

///|
pub fn is_extreme_register(midi : Int) -> Bool {
  midi < 36 || midi > 96
}

///|
pub fn note_with_pitch(note : Note, midi : Int) -> Note {
  { ..note, midi, }
}

///|
pub fn note_name(note : Note) -> String {
  if note.label == "" {
    pitch_label(note.midi)
  } else {
    note.label
  }
}

///|
pub fn semitone_distance(left : PitchClass, right : PitchClass) -> Int {
  let left_value = match left {
    C => 0
    Cs => 1
    D => 2
    Ds => 3
    E => 4
    F => 5
    Fs => 6
    G => 7
    Gs => 8
    A => 9
    As => 10
    B => 11
  }
  let right_value = match right {
    C => 0
    Cs => 1
    D => 2
    Ds => 3
    E => 4
    F => 5
    Fs => 6
    G => 7
    Gs => 8
    A => 9
    As => 10
    B => 11
  }
  interval_class(right_value - left_value)
}

///|
pub fn pitch_histogram(notes : Array[Note]) -> Array[Int] {
  let histogram = Array::make(12, 0)
  for note in notes {
    histogram[interval_class(note.midi)] += 1
  }
  histogram
}