pretty-midi 0.2.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pretty_midi-0.2.11/LICENSE.txt +21 -0
- pretty_midi-0.2.11/PKG-INFO +25 -0
- pretty_midi-0.2.11/README.md +54 -0
- pretty_midi-0.2.11/pretty_midi/TimGM6mb.sf2 +0 -0
- pretty_midi-0.2.11/pretty_midi/__init__.py +151 -0
- pretty_midi-0.2.11/pretty_midi/constants.py +72 -0
- pretty_midi-0.2.11/pretty_midi/containers.py +226 -0
- pretty_midi-0.2.11/pretty_midi/fluidsynth.py +72 -0
- pretty_midi-0.2.11/pretty_midi/instrument.py +539 -0
- pretty_midi-0.2.11/pretty_midi/pretty_midi.py +1573 -0
- pretty_midi-0.2.11/pretty_midi/utilities.py +583 -0
- pretty_midi-0.2.11/pretty_midi.egg-info/PKG-INFO +25 -0
- pretty_midi-0.2.11/pretty_midi.egg-info/SOURCES.txt +18 -0
- pretty_midi-0.2.11/pretty_midi.egg-info/dependency_links.txt +1 -0
- pretty_midi-0.2.11/pretty_midi.egg-info/requires.txt +7 -0
- pretty_midi-0.2.11/pretty_midi.egg-info/top_level.txt +1 -0
- pretty_midi-0.2.11/setup.cfg +4 -0
- pretty_midi-0.2.11/setup.py +34 -0
- pretty_midi-0.2.11/tests/test_pretty_midi.py +601 -0
- pretty_midi-0.2.11/tests/test_utilities.py +77 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014 Colin Raffel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pretty_midi
|
|
3
|
+
Version: 0.2.11
|
|
4
|
+
Summary: Functions and classes for handling MIDI data conveniently.
|
|
5
|
+
Home-page: https://github.com/craffel/pretty-midi
|
|
6
|
+
Author: Colin Raffel
|
|
7
|
+
Author-email: craffel@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: audio music midi mir
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
|
|
15
|
+
License-File: LICENSE.txt
|
|
16
|
+
Requires-Dist: numpy>=1.7.0
|
|
17
|
+
Requires-Dist: mido>=1.1.16
|
|
18
|
+
Requires-Dist: six
|
|
19
|
+
Requires-Dist: importlib_resources
|
|
20
|
+
Provides-Extra: fluidsynth
|
|
21
|
+
Requires-Dist: pyfluidsynth>=1.3.1; extra == "fluidsynth"
|
|
22
|
+
|
|
23
|
+
Functions and classes which make handling MIDI data easy in Python.
|
|
24
|
+
Provides methods for parsing, modifying, and analyzing MIDI files.
|
|
25
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
`pretty_midi` contains utility function/classes for handling MIDI data, so that it's in a format which is easy to modify and extract information from.
|
|
2
|
+
|
|
3
|
+
Documentation is available [here](http://craffel.github.io/pretty-midi/). You can also find a Jupyter notebook tutorial [here](https://nbviewer.org/github/craffel/pretty-midi/blob/main/Tutorial.ipynb) (click [here](https://colab.research.google.com/github/craffel/pretty-midi/blob/main/Tutorial.ipynb) to load in Colab).
|
|
4
|
+
|
|
5
|
+
`pretty_midi` is available via [pip](https://pypi.python.org/pypi/pretty_midi) or via the [setup.py](https://github.com/craffel/pretty-midi/blob/master/setup.py) script. In order to synthesize MIDI data using fluidsynth, you need the [fluidsynth](http://www.fluidsynth.org/) program and [pyfluidsynth](https://pypi.python.org/pypi/pyfluidsynth).
|
|
6
|
+
|
|
7
|
+
If you end up using `pretty_midi` in a published research project, please cite the following report:
|
|
8
|
+
|
|
9
|
+
Colin Raffel and Daniel P. W. Ellis. [_Intuitive Analysis, Creation and Manipulation of MIDI Data with pretty_midi_](http://colinraffel.com/publications/ismir2014intuitive.pdf). In Proceedings of the 15th International Conference on Music Information Retrieval Late Breaking and Demo Papers, 2014.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Example usage for analyzing, manipulating and synthesizing a MIDI file:
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import pretty_midi
|
|
16
|
+
# Load MIDI file into PrettyMIDI object
|
|
17
|
+
midi_data = pretty_midi.PrettyMIDI('example.mid')
|
|
18
|
+
# Print an empirical estimate of its global tempo
|
|
19
|
+
print(midi_data.estimate_tempo())
|
|
20
|
+
# Compute the relative amount of each semitone across the entire song, a proxy for key
|
|
21
|
+
total_velocity = sum(sum(midi_data.get_chroma()))
|
|
22
|
+
print([sum(semitone)/total_velocity for semitone in midi_data.get_chroma()])
|
|
23
|
+
# Shift all notes up by 5 semitones
|
|
24
|
+
for instrument in midi_data.instruments:
|
|
25
|
+
# Don't want to shift drum notes
|
|
26
|
+
if not instrument.is_drum:
|
|
27
|
+
for note in instrument.notes:
|
|
28
|
+
note.pitch += 5
|
|
29
|
+
# Synthesize the resulting MIDI data using sine waves
|
|
30
|
+
audio_data = midi_data.synthesize()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Example usage for creating a simple MIDI file:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import pretty_midi
|
|
37
|
+
# Create a PrettyMIDI object
|
|
38
|
+
cello_c_chord = pretty_midi.PrettyMIDI()
|
|
39
|
+
# Create an Instrument instance for a cello instrument
|
|
40
|
+
cello_program = pretty_midi.instrument_name_to_program('Cello')
|
|
41
|
+
cello = pretty_midi.Instrument(program=cello_program)
|
|
42
|
+
# Iterate over note names, which will be converted to note number later
|
|
43
|
+
for note_name in ['C5', 'E5', 'G5']:
|
|
44
|
+
# Retrieve the MIDI note number for this note name
|
|
45
|
+
note_number = pretty_midi.note_name_to_number(note_name)
|
|
46
|
+
# Create a Note instance for this note, starting at 0s and ending at .5s
|
|
47
|
+
note = pretty_midi.Note(velocity=100, pitch=note_number, start=0, end=.5)
|
|
48
|
+
# Add it to our cello instrument
|
|
49
|
+
cello.notes.append(note)
|
|
50
|
+
# Add the cello instrument to the PrettyMIDI object
|
|
51
|
+
cello_c_chord.instruments.append(cello)
|
|
52
|
+
# Write out the MIDI data
|
|
53
|
+
cello_c_chord.write('cello-C-chord.mid')
|
|
54
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
``pretty_midi`` contains utility function/classes for handling MIDI data,
|
|
3
|
+
so that it's in a format from which it is easy to modify and extract
|
|
4
|
+
information.
|
|
5
|
+
If you end up using ``pretty_midi`` in a published research project, please
|
|
6
|
+
cite the following report:
|
|
7
|
+
|
|
8
|
+
Colin Raffel and Daniel P. W. Ellis.
|
|
9
|
+
`Intuitive Analysis, Creation and Manipulation of MIDI Data with pretty_midi
|
|
10
|
+
<http://colinraffel.com/publications/ismir2014intuitive.pdf>`_.
|
|
11
|
+
In 15th International Conference on Music Information Retrieval Late Breaking
|
|
12
|
+
and Demo Papers, 2014.
|
|
13
|
+
|
|
14
|
+
Example usage for analyzing, manipulating and synthesizing a MIDI file:
|
|
15
|
+
|
|
16
|
+
.. code-block:: python
|
|
17
|
+
|
|
18
|
+
import pretty_midi
|
|
19
|
+
# Load MIDI file into PrettyMIDI object
|
|
20
|
+
midi_data = pretty_midi.PrettyMIDI('example.mid')
|
|
21
|
+
# Print an empirical estimate of its global tempo
|
|
22
|
+
print midi_data.estimate_tempo()
|
|
23
|
+
# Compute the relative amount of each semitone across the entire song,
|
|
24
|
+
# a proxy for key
|
|
25
|
+
total_velocity = sum(sum(midi_data.get_chroma()))
|
|
26
|
+
print [sum(semitone)/total_velocity for semitone in midi_data.get_chroma()]
|
|
27
|
+
# Shift all notes up by 5 semitones
|
|
28
|
+
for instrument in midi_data.instruments:
|
|
29
|
+
# Don't want to shift drum notes
|
|
30
|
+
if not instrument.is_drum:
|
|
31
|
+
for note in instrument.notes:
|
|
32
|
+
note.pitch += 5
|
|
33
|
+
# Synthesize the resulting MIDI data using sine waves
|
|
34
|
+
audio_data = midi_data.synthesize()
|
|
35
|
+
|
|
36
|
+
Example usage for creating a simple MIDI file:
|
|
37
|
+
|
|
38
|
+
.. code-block:: python
|
|
39
|
+
|
|
40
|
+
import pretty_midi
|
|
41
|
+
# Create a PrettyMIDI object
|
|
42
|
+
cello_c_chord = pretty_midi.PrettyMIDI()
|
|
43
|
+
# Create an Instrument instance for a cello instrument
|
|
44
|
+
cello_program = pretty_midi.instrument_name_to_program('Cello')
|
|
45
|
+
cello = pretty_midi.Instrument(program=cello_program)
|
|
46
|
+
# Iterate over note names, which will be converted to note number later
|
|
47
|
+
for note_name in ['C5', 'E5', 'G5']:
|
|
48
|
+
# Retrieve the MIDI note number for this note name
|
|
49
|
+
note_number = pretty_midi.note_name_to_number(note_name)
|
|
50
|
+
# Create a Note instance, starting at 0s and ending at .5s
|
|
51
|
+
note = pretty_midi.Note(
|
|
52
|
+
velocity=100, pitch=note_number, start=0, end=.5)
|
|
53
|
+
# Add it to our cello instrument
|
|
54
|
+
cello.notes.append(note)
|
|
55
|
+
# Add the cello instrument to the PrettyMIDI object
|
|
56
|
+
cello_c_chord.instruments.append(cello)
|
|
57
|
+
# Write out the MIDI data
|
|
58
|
+
cello_c_chord.write('cello-C-chord.mid')
|
|
59
|
+
|
|
60
|
+
Further examples can be found in the source tree's `examples directory
|
|
61
|
+
<https://github.com/craffel/pretty-midi/tree/master/examples>`_.
|
|
62
|
+
|
|
63
|
+
``pretty_midi.PrettyMIDI``
|
|
64
|
+
==========================
|
|
65
|
+
|
|
66
|
+
.. autoclass:: PrettyMIDI
|
|
67
|
+
:members:
|
|
68
|
+
:undoc-members:
|
|
69
|
+
|
|
70
|
+
``pretty_midi.Instrument``
|
|
71
|
+
==========================
|
|
72
|
+
|
|
73
|
+
.. autoclass:: Instrument
|
|
74
|
+
:members:
|
|
75
|
+
:undoc-members:
|
|
76
|
+
|
|
77
|
+
``pretty_midi.Note``
|
|
78
|
+
====================
|
|
79
|
+
|
|
80
|
+
.. autoclass:: Note
|
|
81
|
+
:members:
|
|
82
|
+
:undoc-members:
|
|
83
|
+
|
|
84
|
+
``pretty_midi.PitchBend``
|
|
85
|
+
=========================
|
|
86
|
+
|
|
87
|
+
.. autoclass:: PitchBend
|
|
88
|
+
:members:
|
|
89
|
+
:undoc-members:
|
|
90
|
+
|
|
91
|
+
``pretty_midi.ControlChange``
|
|
92
|
+
=============================
|
|
93
|
+
|
|
94
|
+
.. autoclass:: ControlChange
|
|
95
|
+
:members:
|
|
96
|
+
:undoc-members:
|
|
97
|
+
|
|
98
|
+
``pretty_midi.TimeSignature``
|
|
99
|
+
=============================
|
|
100
|
+
|
|
101
|
+
.. autoclass:: TimeSignature
|
|
102
|
+
:members:
|
|
103
|
+
:undoc-members:
|
|
104
|
+
|
|
105
|
+
``pretty_midi.KeySignature``
|
|
106
|
+
============================
|
|
107
|
+
|
|
108
|
+
.. autoclass:: KeySignature
|
|
109
|
+
:members:
|
|
110
|
+
:undoc-members:
|
|
111
|
+
|
|
112
|
+
``pretty_midi.Lyric``
|
|
113
|
+
=====================
|
|
114
|
+
|
|
115
|
+
.. autoclass:: Lyric
|
|
116
|
+
:members:
|
|
117
|
+
:undoc-members:
|
|
118
|
+
|
|
119
|
+
``pretty_midi.Text``
|
|
120
|
+
=====================
|
|
121
|
+
|
|
122
|
+
.. autoclass:: Text
|
|
123
|
+
:members:
|
|
124
|
+
:undoc-members:
|
|
125
|
+
|
|
126
|
+
Utility functions
|
|
127
|
+
=================
|
|
128
|
+
.. autofunction:: key_number_to_key_name
|
|
129
|
+
.. autofunction:: key_name_to_key_number
|
|
130
|
+
.. autofunction:: mode_accidentals_to_key_number
|
|
131
|
+
.. autofunction:: key_number_to_mode_accidentals
|
|
132
|
+
.. autofunction:: qpm_to_bpm
|
|
133
|
+
.. autofunction:: note_number_to_hz
|
|
134
|
+
.. autofunction:: hz_to_note_number
|
|
135
|
+
.. autofunction:: note_name_to_number
|
|
136
|
+
.. autofunction:: note_number_to_name
|
|
137
|
+
.. autofunction:: note_number_to_drum_name
|
|
138
|
+
.. autofunction:: drum_name_to_note_number
|
|
139
|
+
.. autofunction:: program_to_instrument_name
|
|
140
|
+
.. autofunction:: instrument_name_to_program
|
|
141
|
+
.. autofunction:: program_to_instrument_class
|
|
142
|
+
.. autofunction:: pitch_bend_to_semitones
|
|
143
|
+
.. autofunction:: semitones_to_pitch_bend
|
|
144
|
+
"""
|
|
145
|
+
from .pretty_midi import *
|
|
146
|
+
from .instrument import *
|
|
147
|
+
from .containers import *
|
|
148
|
+
from .utilities import *
|
|
149
|
+
from .constants import *
|
|
150
|
+
|
|
151
|
+
__version__ = '0.2.11'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""This file defines MIDI standard constants, which are useful for converting
|
|
2
|
+
between numeric MIDI data values and human-readable text.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# INSTRUMENT_MAP[program_number] maps the program_number to an instrument name
|
|
7
|
+
INSTRUMENT_MAP = ['Acoustic Grand Piano', 'Bright Acoustic Piano',
|
|
8
|
+
'Electric Grand Piano', 'Honky-tonk Piano',
|
|
9
|
+
'Electric Piano 1', 'Electric Piano 2', 'Harpsichord',
|
|
10
|
+
'Clavinet', 'Celesta', 'Glockenspiel', 'Music Box',
|
|
11
|
+
'Vibraphone', 'Marimba', 'Xylophone', 'Tubular Bells',
|
|
12
|
+
'Dulcimer', 'Drawbar Organ', 'Percussive Organ',
|
|
13
|
+
'Rock Organ', 'Church Organ', 'Reed Organ', 'Accordion',
|
|
14
|
+
'Harmonica', 'Tango Accordion', 'Acoustic Guitar (nylon)',
|
|
15
|
+
'Acoustic Guitar (steel)', 'Electric Guitar (jazz)',
|
|
16
|
+
'Electric Guitar (clean)', 'Electric Guitar (muted)',
|
|
17
|
+
'Overdriven Guitar', 'Distortion Guitar',
|
|
18
|
+
'Guitar Harmonics', 'Acoustic Bass',
|
|
19
|
+
'Electric Bass (finger)', 'Electric Bass (pick)',
|
|
20
|
+
'Fretless Bass', 'Slap Bass 1', 'Slap Bass 2',
|
|
21
|
+
'Synth Bass 1', 'Synth Bass 2', 'Violin', 'Viola', 'Cello',
|
|
22
|
+
'Contrabass', 'Tremolo Strings', 'Pizzicato Strings',
|
|
23
|
+
'Orchestral Harp', 'Timpani', 'String Ensemble 1',
|
|
24
|
+
'String Ensemble 2', 'Synth Strings 1', 'Synth Strings 2',
|
|
25
|
+
'Choir Aahs', 'Voice Oohs', 'Synth Choir', 'Orchestra Hit',
|
|
26
|
+
'Trumpet', 'Trombone', 'Tuba', 'Muted Trumpet',
|
|
27
|
+
'French Horn', 'Brass Section', 'Synth Brass 1',
|
|
28
|
+
'Synth Brass 2', 'Soprano Sax', 'Alto Sax', 'Tenor Sax',
|
|
29
|
+
'Baritone Sax', 'Oboe', 'English Horn', 'Bassoon',
|
|
30
|
+
'Clarinet', 'Piccolo', 'Flute', 'Recorder', 'Pan Flute',
|
|
31
|
+
'Blown bottle', 'Shakuhachi', 'Whistle', 'Ocarina',
|
|
32
|
+
'Lead 1 (square)', 'Lead 2 (sawtooth)',
|
|
33
|
+
'Lead 3 (calliope)', 'Lead 4 chiff', 'Lead 5 (charang)',
|
|
34
|
+
'Lead 6 (voice)', 'Lead 7 (fifths)',
|
|
35
|
+
'Lead 8 (bass + lead)', 'Pad 1 (new age)', 'Pad 2 (warm)',
|
|
36
|
+
'Pad 3 (polysynth)', 'Pad 4 (choir)', 'Pad 5 (bowed)',
|
|
37
|
+
'Pad 6 (metallic)', 'Pad 7 (halo)', 'Pad 8 (sweep)',
|
|
38
|
+
'FX 1 (rain)', 'FX 2 (soundtrack)', 'FX 3 (crystal)',
|
|
39
|
+
'FX 4 (atmosphere)', 'FX 5 (brightness)', 'FX 6 (goblins)',
|
|
40
|
+
'FX 7 (echoes)', 'FX 8 (sci-fi)', 'Sitar', 'Banjo',
|
|
41
|
+
'Shamisen', 'Koto', 'Kalimba', 'Bagpipe', 'Fiddle',
|
|
42
|
+
'Shanai', 'Tinkle Bell', 'Agogo', 'Steel Drums',
|
|
43
|
+
'Woodblock', 'Taiko Drum', 'Melodic Tom', 'Synth Drum',
|
|
44
|
+
'Reverse Cymbal', 'Guitar Fret Noise', 'Breath Noise',
|
|
45
|
+
'Seashore', 'Bird Tweet', 'Telephone Ring', 'Helicopter',
|
|
46
|
+
'Applause', 'Gunshot']
|
|
47
|
+
|
|
48
|
+
# INSTRUMENT_CLASSES contains the classes present in INSTRUMENTS
|
|
49
|
+
INSTRUMENT_CLASSES = ['Piano', 'Chromatic Percussion', 'Organ', 'Guitar',
|
|
50
|
+
'Bass', 'Strings', 'Ensemble', 'Brass', 'Reed', 'Pipe',
|
|
51
|
+
'Synth Lead', 'Synth Pad', 'Synth Effects', 'Ethnic',
|
|
52
|
+
'Percussive',
|
|
53
|
+
'Sound Effects']
|
|
54
|
+
|
|
55
|
+
# List which maps MIDI note number - 35 to drum name
|
|
56
|
+
# from http://www.midi.org/techspecs/gm1sound.php
|
|
57
|
+
DRUM_MAP = ['Acoustic Bass Drum', 'Bass Drum 1', 'Side Stick',
|
|
58
|
+
'Acoustic Snare', 'Hand Clap', 'Electric Snare',
|
|
59
|
+
'Low Floor Tom', 'Closed Hi Hat', 'High Floor Tom',
|
|
60
|
+
'Pedal Hi Hat', 'Low Tom', 'Open Hi Hat',
|
|
61
|
+
'Low-Mid Tom', 'Hi-Mid Tom', 'Crash Cymbal 1',
|
|
62
|
+
'High Tom', 'Ride Cymbal 1', 'Chinese Cymbal',
|
|
63
|
+
'Ride Bell', 'Tambourine', 'Splash Cymbal',
|
|
64
|
+
'Cowbell', 'Crash Cymbal 2', 'Vibraslap',
|
|
65
|
+
'Ride Cymbal 2', 'Hi Bongo', 'Low Bongo',
|
|
66
|
+
'Mute Hi Conga', 'Open Hi Conga', 'Low Conga',
|
|
67
|
+
'High Timbale', 'Low Timbale', 'High Agogo',
|
|
68
|
+
'Low Agogo', 'Cabasa', 'Maracas',
|
|
69
|
+
'Short Whistle', 'Long Whistle', 'Short Guiro',
|
|
70
|
+
'Long Guiro', 'Claves', 'Hi Wood Block',
|
|
71
|
+
'Low Wood Block', 'Mute Cuica', 'Open Cuica',
|
|
72
|
+
'Mute Triangle', 'Open Triangle']
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""These classes simply hold MIDI data in a convenient form.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import print_function
|
|
5
|
+
|
|
6
|
+
from .utilities import key_number_to_key_name
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Note(object):
|
|
10
|
+
"""A note event.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
velocity : int
|
|
15
|
+
Note velocity.
|
|
16
|
+
pitch : int
|
|
17
|
+
Note pitch, as a MIDI note number.
|
|
18
|
+
start : float
|
|
19
|
+
Note on time, absolute, in seconds.
|
|
20
|
+
end : float
|
|
21
|
+
Note off time, absolute, in seconds.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, velocity, pitch, start, end):
|
|
26
|
+
if end < start:
|
|
27
|
+
raise ValueError("Note end time must be greater than start time")
|
|
28
|
+
|
|
29
|
+
self.velocity = velocity
|
|
30
|
+
self.pitch = pitch
|
|
31
|
+
self.start = start
|
|
32
|
+
self.end = end
|
|
33
|
+
|
|
34
|
+
def get_duration(self):
|
|
35
|
+
"""Get the duration of the note in seconds."""
|
|
36
|
+
return self.end - self.start
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def duration(self):
|
|
40
|
+
return self.get_duration()
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return 'Note(start={:f}, end={:f}, pitch={}, velocity={})'.format(
|
|
44
|
+
self.start, self.end, self.pitch, self.velocity)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PitchBend(object):
|
|
48
|
+
"""A pitch bend event.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
pitch : int
|
|
53
|
+
MIDI pitch bend amount, in the range ``[-8192, 8191]``.
|
|
54
|
+
time : float
|
|
55
|
+
Time where the pitch bend occurs.
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, pitch, time):
|
|
60
|
+
self.pitch = pitch
|
|
61
|
+
self.time = time
|
|
62
|
+
|
|
63
|
+
def __repr__(self):
|
|
64
|
+
return 'PitchBend(pitch={:d}, time={:f})'.format(self.pitch, self.time)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ControlChange(object):
|
|
68
|
+
"""A control change event.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
number : int
|
|
73
|
+
The control change number, in ``[0, 127]``.
|
|
74
|
+
value : int
|
|
75
|
+
The value of the control change, in ``[0, 127]``.
|
|
76
|
+
time : float
|
|
77
|
+
Time where the control change occurs.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, number, value, time):
|
|
82
|
+
self.number = number
|
|
83
|
+
self.value = value
|
|
84
|
+
self.time = time
|
|
85
|
+
|
|
86
|
+
def __repr__(self):
|
|
87
|
+
return ('ControlChange(number={:d}, value={:d}, '
|
|
88
|
+
'time={:f})'.format(self.number, self.value, self.time))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TimeSignature(object):
|
|
92
|
+
"""Container for a Time Signature event, which contains the time signature
|
|
93
|
+
numerator, denominator and the event time in seconds.
|
|
94
|
+
|
|
95
|
+
Attributes
|
|
96
|
+
----------
|
|
97
|
+
numerator : int
|
|
98
|
+
Numerator of time signature.
|
|
99
|
+
denominator : int
|
|
100
|
+
Denominator of time signature.
|
|
101
|
+
time : float
|
|
102
|
+
Time of event in seconds.
|
|
103
|
+
|
|
104
|
+
Examples
|
|
105
|
+
--------
|
|
106
|
+
Instantiate a TimeSignature object with 6/8 time signature at 3.14 seconds:
|
|
107
|
+
|
|
108
|
+
>>> ts = TimeSignature(6, 8, 3.14)
|
|
109
|
+
>>> print(ts)
|
|
110
|
+
6/8 at 3.14 seconds
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, numerator, denominator, time):
|
|
115
|
+
if not (isinstance(numerator, int) and numerator > 0):
|
|
116
|
+
raise ValueError(
|
|
117
|
+
'{} is not a valid `numerator` type or value'.format(
|
|
118
|
+
numerator))
|
|
119
|
+
if not (isinstance(denominator, int) and denominator > 0):
|
|
120
|
+
raise ValueError(
|
|
121
|
+
'{} is not a valid `denominator` type or value'.format(
|
|
122
|
+
denominator))
|
|
123
|
+
if not (isinstance(time, (int, float)) and time >= 0):
|
|
124
|
+
raise ValueError(
|
|
125
|
+
'{} is not a valid `time` type or value'.format(time))
|
|
126
|
+
|
|
127
|
+
self.numerator = numerator
|
|
128
|
+
self.denominator = denominator
|
|
129
|
+
self.time = time
|
|
130
|
+
|
|
131
|
+
def __repr__(self):
|
|
132
|
+
return "TimeSignature(numerator={}, denominator={}, time={})".format(
|
|
133
|
+
self.numerator, self.denominator, self.time)
|
|
134
|
+
|
|
135
|
+
def __str__(self):
|
|
136
|
+
return '{}/{} at {:.2f} seconds'.format(
|
|
137
|
+
self.numerator, self.denominator, self.time)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class KeySignature(object):
|
|
141
|
+
"""Contains the key signature and the event time in seconds.
|
|
142
|
+
Only supports major and minor keys.
|
|
143
|
+
|
|
144
|
+
Attributes
|
|
145
|
+
----------
|
|
146
|
+
key_number : int
|
|
147
|
+
Key number according to ``[0, 11]`` Major, ``[12, 23]`` minor.
|
|
148
|
+
For example, 0 is C Major, 12 is C minor.
|
|
149
|
+
time : float
|
|
150
|
+
Time of event in seconds.
|
|
151
|
+
|
|
152
|
+
Examples
|
|
153
|
+
--------
|
|
154
|
+
Instantiate a C# minor KeySignature object at 3.14 seconds:
|
|
155
|
+
|
|
156
|
+
>>> ks = KeySignature(13, 3.14)
|
|
157
|
+
>>> print(ks)
|
|
158
|
+
C# minor at 3.14 seconds
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def __init__(self, key_number, time):
|
|
162
|
+
if not all((isinstance(key_number, int),
|
|
163
|
+
key_number >= 0,
|
|
164
|
+
key_number < 24)):
|
|
165
|
+
raise ValueError(
|
|
166
|
+
'{} is not a valid `key_number` type or value'.format(
|
|
167
|
+
key_number))
|
|
168
|
+
if not (isinstance(time, (int, float)) and time >= 0):
|
|
169
|
+
raise ValueError(
|
|
170
|
+
'{} is not a valid `time` type or value'.format(time))
|
|
171
|
+
|
|
172
|
+
self.key_number = key_number
|
|
173
|
+
self.time = time
|
|
174
|
+
|
|
175
|
+
def __repr__(self):
|
|
176
|
+
return "KeySignature(key_number={}, time={})".format(
|
|
177
|
+
self.key_number, self.time)
|
|
178
|
+
|
|
179
|
+
def __str__(self):
|
|
180
|
+
return '{} at {:.2f} seconds'.format(
|
|
181
|
+
key_number_to_key_name(self.key_number), self.time)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Lyric(object):
|
|
185
|
+
"""Timestamped lyric text.
|
|
186
|
+
|
|
187
|
+
Attributes
|
|
188
|
+
----------
|
|
189
|
+
text : str
|
|
190
|
+
The text of the lyric.
|
|
191
|
+
time : float
|
|
192
|
+
The time in seconds of the lyric.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
def __init__(self, text, time):
|
|
196
|
+
self.text = text
|
|
197
|
+
self.time = time
|
|
198
|
+
|
|
199
|
+
def __repr__(self):
|
|
200
|
+
return 'Lyric(text="{}", time={})'.format(
|
|
201
|
+
self.text.replace('"', r'\"'), self.time)
|
|
202
|
+
|
|
203
|
+
def __str__(self):
|
|
204
|
+
return '"{}" at {:.2f} seconds'.format(self.text, self.time)
|
|
205
|
+
|
|
206
|
+
class Text(object):
|
|
207
|
+
"""Timestamped text event.
|
|
208
|
+
|
|
209
|
+
Attributes
|
|
210
|
+
----------
|
|
211
|
+
text : str
|
|
212
|
+
The text.
|
|
213
|
+
time : float
|
|
214
|
+
The time it occurs in seconds.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
def __init__(self, text, time):
|
|
218
|
+
self.text = text
|
|
219
|
+
self.time = time
|
|
220
|
+
|
|
221
|
+
def __repr__(self):
|
|
222
|
+
return 'Text(text="{}", time={})'.format(
|
|
223
|
+
self.text.replace('"', r'\"'), self.time)
|
|
224
|
+
|
|
225
|
+
def __str__(self):
|
|
226
|
+
return '"{}" at {:.2f} seconds'.format(self.text, self.time)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Utility functions for handling fluidsynth
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import importlib_resources
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import fluidsynth
|
|
10
|
+
_HAS_FLUIDSYNTH = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
_HAS_FLUIDSYNTH = False
|
|
13
|
+
|
|
14
|
+
DEFAULT_SF2 = 'TimGM6mb.sf2'
|
|
15
|
+
DEFAULT_SAMPLE_RATE = 44100
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_fluidsynth_instance(synthesizer=None, sfid=0, fs=None):
|
|
19
|
+
""" Check if a valid fluidsynth.Synth instance is provided, and if not,
|
|
20
|
+
create one.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
synthesizer : fluidsynth.Synth or str
|
|
25
|
+
fluidsynth.Synth instance to use or a string with the path to a .sf2 file.
|
|
26
|
+
Default ``None``, which creates a new instance using the TimGM6mb.sf2 file
|
|
27
|
+
included with ``pretty_midi``.
|
|
28
|
+
sfid : int
|
|
29
|
+
Soundfont ID to use if an instance of fluidsynth.Synth is provided.
|
|
30
|
+
Default ``0``, which uses the first soundfont.
|
|
31
|
+
fs : int
|
|
32
|
+
Sampling rate to synthesize at.
|
|
33
|
+
Default ``None``, which falls back to ``pretty_midi.fluidsynth.DEFAULT_SAMPLE_RATE``
|
|
34
|
+
= 44100 if a new instance must be created.
|
|
35
|
+
If ``synthesizer`` is an existing instance and ``fs`` is specified, then
|
|
36
|
+
ValueError will be raised if the sample rates are not equal.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
synthesizer : fluidsynth.Synth
|
|
41
|
+
fluidsynth.Synth instance
|
|
42
|
+
sfid : int
|
|
43
|
+
Soundfont ID
|
|
44
|
+
new_instance_created : bool
|
|
45
|
+
Whether a new instance of fluidsynth.Synth was created.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
if not _HAS_FLUIDSYNTH:
|
|
49
|
+
raise ImportError("fluidsynth() was called but pyfluidsynth is not installed.")
|
|
50
|
+
|
|
51
|
+
if synthesizer is None:
|
|
52
|
+
synthesizer = os.path.join(str(importlib_resources.files(__name__)), DEFAULT_SF2)
|
|
53
|
+
|
|
54
|
+
# Create a fluidsynth instance if one wasn't provided
|
|
55
|
+
if isinstance(synthesizer, str):
|
|
56
|
+
sf2_path = synthesizer
|
|
57
|
+
if not os.path.exists(synthesizer):
|
|
58
|
+
raise ValueError("No soundfont file found at the supplied path {}".format(sf2_path))
|
|
59
|
+
fs = fs or DEFAULT_SAMPLE_RATE
|
|
60
|
+
synthesizer = fluidsynth.Synth(samplerate=fs)
|
|
61
|
+
sfid = synthesizer.sfload(sf2_path)
|
|
62
|
+
new_instance_created = True
|
|
63
|
+
elif isinstance(synthesizer, fluidsynth.Synth):
|
|
64
|
+
synth_fs = synthesizer.get_setting('synth.sample-rate')
|
|
65
|
+
if fs and synth_fs != fs:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"synthesizer sample rate of {synth_fs} doesn't match provided fs of {fs}")
|
|
68
|
+
new_instance_created = False
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError("synthesizer must be a str or a fluidsynth.Synth instance")
|
|
71
|
+
|
|
72
|
+
return synthesizer, sfid, new_instance_created
|