import music21

from music21.chord import Chord
from music21.duration import Duration
from music21.instrument import Instrument
from music21.note import Note, Rest
from music21.stream import Stream
from music21.tempo import MetronomeMark
from music21.volume import Volume

import os
data_dir = 'data/working/example-parametric-note'
os.makedirs(data_dir, exist_ok=True)

"""
Explanation: Generating simple audio samples with music21
We'd like to synthesize simple audio samples containing a single note or a chord. The samples, however, should be parameterized by several attributes. We'd like to modify:

pitch
[precise frequency]
instrument
volume
duration
location in the sample

The main output should be MIDI which can be synthesized to audio. Besides that we'd like to store the parameters and metadata like the the pitch class, frequency, etc.
In this notebook we'll explore how to acomplish this with the music21 library.
End of explanation
"""


Note('C')

s = Stream([Note('C')])

"""
Explanation: We're about to create a Note object which represents a single note and both its pitch and duration.
End of explanation
"""


s.show()

s.show('midi')

"""
Explanation: If we have MuseScore installed, we can we the music sheet representation.
End of explanation
"""


s.write('midi', data_dir + '/c.midi')

"""
Explanation: Note that there's some rest at the beginning and end of the MIDI file. It looks like a quarter-note rest. The reason is that "MIDI controllers may not be able to play notes at deltaTime=0" See: https://groups.google.com/d/msg/music21list/ne_P_ZUvRNk/XynaiODzAgAJ
Anyway this might be good in the audio processing, since there will be a space for the windowing function.
End of explanation
"""


n = Note('C')
n

def describe_note(note):
    p = note.pitch
    print(note)
    print('pitch:', note.pitch)
    print('duration:', note.duration)
    print('name:', p.name)
    print('full name:', p.fullName)
    print('pitch class:', p.pitchClass)
    print('octave:', p.octave)
    print('frequency', p.frequency, 'Hz')
    print('midi:', p.midi)
    print('pitch space:', p.ps) # like MIDI, but floating point

describe_note(n)

"""
Explanation: Properties of the Note
End of explanation
"""


# different note in the default octave
describe_note(Note('E'))

# a note in the specific octave
describe_note(Note('G#3'))

# note specified by its octave and pitch class within an octave
describe_note(Note(octave=2, pitchClass=3))

# note specified by its integer MIDI number
describe_note(Note(midi=21))

# microtonal pitch using the pitch space attribute (like MIDI but floating point)
describe_note(Note(ps=21.25))

"""
Explanation: Creating Note with parameters
End of explanation
"""


# note with duration of half of a quarter note
note = Note(midi=21, duration=Duration(0.5))
describe_note(note)

# note with duration of half of a quarter note
note = Note(midi=21, duration=Duration(2.5))
describe_note(note)

"""
Explanation: Changing duration
End of explanation
"""


for v in [0, 32, 64, 127]:
    print(Volume(velocity=v))

for v in [0, 0.25, 0.5, 1.0]:
    print(Volume(velocityScalar=v))

Chord(['C']).volume

c = Chord([Note('C')])
c.volume = Volume(velocityScalar=0.25)
c.volume

"""
Explanation: Changing volume
Volume can be specified by parameters:

velocity with range from 0 to 127 or by
velocityScalar with range from 0.0 to 1.0
End of explanation
"""


metronome = MetronomeMark(number=60)
metronome.durationToSeconds(Duration(1.0))

"""
Explanation: How to set tempo?
End of explanation
"""


Stream([MetronomeMark(number=60), Note('C')]).show()

"""
Explanation: Just add a metronome mark at the beginning of the stream.
End of explanation
"""


def make_instrument(id):
    i = Instrument()
    i.midiProgram = id
    return i

def chord_with_volume(chord, volume):
    chord.volume = Volume(velocityScalar=volume)
    return chord

def generate_single_note(midi_number, midi_instrument=0, volume=1.0, duration=1.0, tempo=120):
    """
    Generates a stream containing a single note with given parameters.
    midi_number - MIDI note number, 0 to 127
    midi_instrument - MIDI intrument number, 0 to 127
    duration - floating point number (in quarter note lengths)
    volume - 0.0 to 1.0
    tempo - number of quarter notes per minute (eg. 120)

    Note that there's a quarter note rest at the beginning and at the end.
    """
    return Stream([
        MetronomeMark(number=tempo),
        make_instrument(midi_instrument),
        chord_with_volume(Chord([
            Note(midi=midi_number, duration=Duration(duration))
        ]), volume)
    ])

generate_single_note(60).show('midi')

"""
Explanation: Sequence of notes
End of explanation
"""


s = Stream()
s.append(make_instrument(50))
s.append(Note(midi=60))
s.append(Note(midi=64))
s.append(Note(midi=67))
s.write('midi', data_dir + '/sequence_separated.midi')
s.show('midi')

"""
Explanation: Let's make a sequence. Note that by just passing a list of notes to the Stream we get a chord, not a sequence, so we must append each note separately.
End of explanation
"""


s = Stream()
s.append(make_instrument(50))
s.append(Note(midi=60))
s.append(Rest(duration=Duration(2.0)))
s.append(Note(midi=64))
s.append(Rest(duration=Duration(2.0)))
s.append(Note(midi=67))
s.write('midi', data_dir + '/sequence_separated.midi')
s.show('midi')

"""
Explanation: In the previous example we see, that notes may overlap. So let's add some rests to make better separation.
End of explanation
"""


