Example #1
0
    def __init__(
            self,
            pulses_per_quarter: int = 96,
            middle_c: Union[Tone, int, str] = Tone.from_notation('C3'),
            bpm: float = 120.0,
            channel: int = 0
    ):
        """
        Initializes MIDIWriter object.

        Pulses per quarter note defines maximum resolution of the MIDI events.
        With this parameter equal to 1 one can not have notes shorter than 1/4.
        Default value should be suitable for any software and track.

        Middle C is a tone corresponding to integer value 60 in the MIDI specification.
        For most of the modern software it is C3. If resulting MIDI files
        is shifted by an octave in your software, try using value 'C4'.

        Beats per minute defines tempo of the track.

        MIDI channel can be any value from 0 to 15.

        :param pulses_per_quarter: number of pulses per quarter note (default: 96)
        :param middle_c: middle c tone (default: 'C3')
        :param bpm: beats per minute (default: 120.0)
        :param channel: MIDI channel to write to (default: 0)
        """
        if not 1 <= pulses_per_quarter <= 32767:
            raise ValueError('pulses_per_quarter must be in range [1, 32767]')

        if not 0 <= channel <= 15:
            raise ValueError('channel must be in range [0, 15]')

        self._pulses_per_quarter: int = pulses_per_quarter
        self._pulses_per_whole: int = pulses_per_quarter * 4

        self._middle_c = middle_c

        self._middle_c_delta: int
        if isinstance(middle_c, Tone):
            self._middle_c_delta = 60 - middle_c.pitch
        elif isinstance(middle_c, int):
            self._middle_c_delta = 60 - Tone(middle_c).pitch
        elif isinstance(middle_c, str):
            self._middle_c_delta = 60 - Tone.from_notation(middle_c).pitch
        else:
            raise TypeError('middle_c must be a Tone object, an integer or a string')

        self._bpm = bpm
        self._channel = channel
Example #2
0
def test_tone_from_notation():
    for notation, pitch in [
        ('C', 0),
        ('C#', 1),
        ('D', 2),
        ('D#', 3),
        ('E', 4),
        ('F', 5),
        ('F#', 6),
        ('G', 7),
        ('G#', 8),
        ('A', 9),
        ('A#', 10),
        ('B', 11),
        ('C1', 12),
        ('C#1', 13),
        ('Db', 1),
        ('Eb', 3),
        ('Fb', 4),
        ('E#', 5),
        ('Ab', 8),
        ('C2', 24),
        ('C3', 36),
        ('C#3', 37),
        ('Db3', 37),
        ('C-1', -12),
        ('C#-1', -11),
        ('B-1', -1)
    ]:
        assert Tone.from_notation(notation).pitch == pitch
Example #3
0
    def __init__(self,
                 tone: Union[Tone, int, str],
                 duration: Union[Signature, Tuple[int, int]] = Signature(1, 4),
                 velocity: float = 0.75):
        """
        Initializes Note object. Tone can be a Tone object, an integer or a string.
        In the case of an integer, Tone constructor is called.
        In the case of a string, Tone.from_notation is called.
        Duration can be a Signature object or a pair of integers.
        In the case of a pair of integers Signature constructor is called.
        Velocity must be a float in the range [0.0, 1.0] where 0.0 is the minimum velocity and
        1.0 is the maximum velocity. By default velocity is 0.75 (as in many DAWs).

        :param tone: tone of the note, must be Tone object, integer or string
        :param duration: duration of the note, must be Signature or pair of integers (default: (1, 4))
        :param velocity: float number in the range [0.0, 1.0] (default: 0.75)
        """
        self._tone: Tone
        if isinstance(tone, Tone):
            self._tone = tone
        elif isinstance(tone, int):
            self._tone = Tone(tone)
        elif isinstance(tone, str):
            self._tone = Tone.from_notation(tone)
        else:
            raise TypeError(
                'tone must be a Tone object, an integer or a string')

        self._duration: Signature
        if isinstance(duration, Signature):
            self._duration = duration
        elif isinstance(duration, Iterable):
            self._duration = Signature(*duration)
        else:
            raise TypeError(
                'duration must be a Signature object or a pair of integers')

        if not 0.0 <= velocity <= 1.0:
            raise ValueError('velocity must be in range [0.0, 1.0]')

        self._velocity: float = velocity
Example #4
0
def test_note_with_transposed():
    n1 = Tone.from_notation('C4')
    n2 = n1.transposed(1)

    assert n1.to_notation() == 'C4'
    assert n2.to_notation() == 'C#4'

    assert Tone.from_notation('C4').transposed(2).to_notation() == 'D4'
    assert Tone.from_notation('C4').transposed(-1).to_notation() == 'B3'
    assert Tone.from_notation('C4').transposed(-2).to_notation() == 'A#3'

    n = Tone(152)

    assert n.transposed(1).transposed(5).transposed(-3) == n.transposed(3)
Example #5
0
    def __init__(
            self,
            middle_c: Union[Tone, int, str] = Tone.from_notation('C3')
    ):
        """
        Initializes MIDIReader object.

        Middle C is a tone corresponding to integer value 60 in the MIDI specification.
        For most of the modern software it is C3. If parsed track
        is shifted by an octave, try using value 'C4'.

        :param middle_c: middle c tone (default: 'C3')
        """
        self._middle_c = middle_c

        self._middle_c_delta: int
        if isinstance(middle_c, Tone):
            self._middle_c_delta = 60 - middle_c.pitch
        elif isinstance(middle_c, int):
            self._middle_c_delta = 60 - Tone(middle_c).pitch
        elif isinstance(middle_c, str):
            self._middle_c_delta = 60 - Tone.from_notation(middle_c).pitch
        else:
            raise TypeError('middle_c must be a Tone object, an integer or a string')
Example #6
0
 def _pitch_to_tone(self, pitch: int) -> Tone:
     return Tone(pitch - self._middle_c_delta)
Example #7
0
def test_tone_octave():
    for tone in 'ABCDEFG':
        for octave in range(-5, 5):
            assert Tone.from_notation(f'{tone}{octave}').octave == octave
Example #8
0
def test_tone_to_frequency():
    assert Tone.from_notation('A-1').to_frequency() == 13.75
    assert Tone.from_notation('A0').to_frequency() == 27.5
    assert Tone.from_notation('A1').to_frequency() == 55.0
    assert Tone.from_notation('A2').to_frequency() == 110.0
    assert Tone.from_notation('A3').to_frequency() == 220.0
    assert Tone.from_notation('A4').to_frequency() == 440.0
    assert Tone.from_notation('A5').to_frequency() == 880.0
    assert Tone.from_notation('A6').to_frequency() == 1760.0
    assert Tone.from_notation('A7').to_frequency() == 3520.0
    assert Tone.from_notation('A8').to_frequency() == 7040.0
    assert Tone.from_notation('A9').to_frequency() == 14080.0

    assert abs(Tone.from_notation('C5').to_frequency() - 523.2511306011972) < 1e-3
    assert abs(Tone.from_notation('F4').to_frequency() - 349.2282314330039) < 1e-4
Example #9
0
def test_tone_str():
    for i in range(30):
        tone = Tone(i)
        assert str(tone) == tone.to_notation()
Example #10
0
def test_tone_to_notation():
    assert Tone.from_notation('C#').to_notation() == 'C#0'
    assert Tone.from_notation('C#b').to_notation() == 'C0'
    assert Tone.from_notation('C##b').to_notation() == 'C#0'
    assert Tone.from_notation('C#b#').to_notation() == 'C#0'
    assert Tone.from_notation('Cb##').to_notation() == 'C#0'
    assert Tone.from_notation('Ab##43').to_notation() == 'A#43'
    assert Tone.from_notation('Ab##-43').to_notation() == 'A#-43'

    assert Tone.from_notation('C#').to_notation(transpose_down=True) == 'Db0'
    assert Tone.from_notation('C#b').to_notation(transpose_down=True) == 'C0'
    assert Tone.from_notation('C##b').to_notation(transpose_down=True) == 'Db0'
    assert Tone.from_notation('C#b#').to_notation(transpose_down=True) == 'Db0'
    assert Tone.from_notation('Cb##').to_notation(transpose_down=True) == 'Db0'
    assert Tone.from_notation('Ab##43').to_notation(transpose_down=True) == 'Bb43'
    assert Tone.from_notation('Ab##-43').to_notation(transpose_down=True) == 'Bb-43'
Example #11
0
def test_tone_comp():
    assert Tone(42) < Tone(43)
    assert Tone(42) <= Tone(42)
    assert Tone(42) >= Tone(42)
    assert Tone(42) > Tone(41)
Example #12
0
def test_tone_eq():
    assert Tone(0) == Tone(0)
    assert Tone(0) != Tone(1)
Example #13
0
class Note:
    """
    Note represents musical note with three main characteristics: tone, velocity and duration.
    """
    __slots__ = ('_tone', '_velocity', '_duration')

    def __init__(self,
                 tone: Union[Tone, int, str],
                 duration: Union[Signature, Tuple[int, int]] = Signature(1, 4),
                 velocity: float = 0.75):
        """
        Initializes Note object. Tone can be a Tone object, an integer or a string.
        In the case of an integer, Tone constructor is called.
        In the case of a string, Tone.from_notation is called.
        Duration can be a Signature object or a pair of integers.
        In the case of a pair of integers Signature constructor is called.
        Velocity must be a float in the range [0.0, 1.0] where 0.0 is the minimum velocity and
        1.0 is the maximum velocity. By default velocity is 0.75 (as in many DAWs).

        :param tone: tone of the note, must be Tone object, integer or string
        :param duration: duration of the note, must be Signature or pair of integers (default: (1, 4))
        :param velocity: float number in the range [0.0, 1.0] (default: 0.75)
        """
        self._tone: Tone
        if isinstance(tone, Tone):
            self._tone = tone
        elif isinstance(tone, int):
            self._tone = Tone(tone)
        elif isinstance(tone, str):
            self._tone = Tone.from_notation(tone)
        else:
            raise TypeError(
                'tone must be a Tone object, an integer or a string')

        self._duration: Signature
        if isinstance(duration, Signature):
            self._duration = duration
        elif isinstance(duration, Iterable):
            self._duration = Signature(*duration)
        else:
            raise TypeError(
                'duration must be a Signature object or a pair of integers')

        if not 0.0 <= velocity <= 1.0:
            raise ValueError('velocity must be in range [0.0, 1.0]')

        self._velocity: float = velocity

    @property
    def tone(self) -> Tone:
        """
        Returns tone of the note.

        :return: tone of the note
        """
        return self._tone

    @property
    def velocity(self) -> float:
        """
        Returns velocity of the note.

        :return: velocity of the note.
        """
        return self._velocity

    @property
    def duration(self) -> Signature:
        """
        Returns duration of the note.

        :return: duration of the note
        """
        return self._duration

    def transposed(self, transposition: int) -> 'Note':
        """
        Returns note with transposed tone. Duration and velocity remains the same.

        :param transposition: integer number of semitones to transpose tone
        :return: note with transposed tone
        """
        return Note(tone=self._tone.transposed(transposition),
                    duration=self._duration,
                    velocity=self._velocity)

    def with_duration(self, duration: Union[Signature, Tuple[int,
                                                             int]]) -> 'Note':
        """
        Returns note with new duration. Tone and velocity remains the same.

        :param duration: new duration
        :return: note with new duration
        """
        return Note(tone=self._tone,
                    duration=duration,
                    velocity=self._velocity)

    def with_velocity(self, velocity: float) -> 'Note':
        """
        Returns note with new velocity. Tone and duration remains the same.

        :param velocity: new velocity
        :return: note with new velocity
        """
        return Note(tone=self._tone,
                    duration=self._duration,
                    velocity=velocity)

    def _as_triplet(self) -> Tuple[Tone, Signature, float]:
        return self._tone, self._duration, self._velocity

    def __str__(self) -> str:
        """
        Returns human-readable representation of the note.

        :return: human-readable representation of the note
        """
        return f'Note {str(self._duration)} {str(self._tone)} ({self._velocity:.3f})'

    def __repr__(self) -> str:
        """
        Returns string representation of the note.

        :return: string representation of the note
        """
        name = self.__class__.__name__
        pitch = self._tone.pitch
        velocity = self._velocity
        duration = f'({self._duration.nominator}, {self._duration.denominator})'

        return f'{name}(tone={pitch}, duration={duration}, velocity={velocity}) '

    def __eq__(self, other: 'Note') -> bool:
        """
        Compares two notes for equality. Notes are equal if all their components are equal.

        :param other: other note
        :return: True if notes are equal, False otherwise
        """
        return self._as_triplet() == other._as_triplet()

    def __le__(self, other: 'Note') -> bool:
        """
        Compares two notes. Notes are compared as triplets (pitch, duration, velocity).

        :param other: other note
        :return: True if this note is less or equal than other note, False otherwise
        """
        return self._as_triplet() <= other._as_triplet()

    def __lt__(self, other: 'Note') -> bool:
        """
        Compares two notes. Notes are compared as triplets (pitch, duration, velocity).

        :param other: other note
        :return: True if this note is less than other note, False otherwise
        """
        return self._as_triplet() < other._as_triplet()

    def __ge__(self, other: 'Note') -> bool:
        """
        Compares two notes. Notes are compared as triplets (pitch, duration, velocity).

        :param other: other note
        :return: True if this note is greater or equal than other note, False otherwise
        """
        return self._as_triplet() >= other._as_triplet()

    def __gt__(self, other: 'Note') -> bool:
        """
        Compares two notes. Notes are compared as triplets (pitch, duration, velocity).

        :param other: other note
        :return: True if this note is greater than other note, False otherwise
        """
        return self._as_triplet() > other._as_triplet()