# Copyright 2018 Google LLC # # 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 # # https://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. """Clef logic for OMR. A Clef object maps y positions on the staff to the MIDI pitch of the natural note at the y position. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function import librosa from moonlight.music import constants from moonlight.protobuf import musicscore_pb2 class Clef(object): """Represents a clef which maps y positions to MIDI notes. Attributes: center_line_pitch: A _ScalePitch representing the center line (3rd line of the staff). """ center_line_pitch = None def y_position_to_midi(self, y_position): return (self.center_line_pitch + y_position).midi class TrebleClef(Clef): """Represents a treble clef.""" def __init__(self): self.center_line_pitch = _ScalePitch(constants.MAJOR_SCALE, librosa.note_to_midi('B4')) self.glyph = musicscore_pb2.Glyph.CLEF_TREBLE class BassClef(Clef): """Represents a bass clef.""" def __init__(self): self.center_line_pitch = _ScalePitch(constants.MAJOR_SCALE, librosa.note_to_midi('D3')) self.glyph = musicscore_pb2.Glyph.CLEF_BASS class _ScalePitch(object): """A natural note which can be offset to get another note. Attributes: scale: The scale which this pitch is based on. A list of MIDI pitch values spanning one octave. index: The index of the pitch's pitch class within the scale. octave: The index of the octave that the pitch is in, relative to the octave spanning the scale notes. """ def __init__(self, scale, midi): self.scale = scale self.index = scale.index(midi % constants.NUM_SEMITONES_PER_OCTAVE) self.octave = (midi - scale[0]) // 12 @property def midi(self): """The MIDI value for the pitch.""" notes_per_octave = constants.NUM_SEMITONES_PER_OCTAVE return self.scale[self.index] + notes_per_octave * self.octave @property def pitch_index(self): """The index of the pitch in the C major scale.""" return self.index + len(self.scale) * self.octave def __add__(self, interval): """Returns the natural note `interval` away on `self.scale`.""" pitch = _ScalePitch(self.scale, self.midi) pitch_index = self.pitch_index + interval pitch.index = pitch_index % len(self.scale) pitch.octave = pitch_index // len(self.scale) return pitch