midigen-lib 0.1.0__py3-none-any.whl
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.
- midigen/__init__.py +9 -0
- midigen/chord.py +209 -0
- midigen/drums.py +104 -0
- midigen/instruments.py +146 -0
- midigen/key.py +203 -0
- midigen/midigen.py +213 -0
- midigen/note.py +56 -0
- midigen/scale.py +21 -0
- midigen/section.py +5 -0
- midigen/song.py +59 -0
- midigen/tests/__init__.py +0 -0
- midigen/tests/test_chord.py +268 -0
- midigen/tests/test_drums.py +110 -0
- midigen/tests/test_key.py +42 -0
- midigen/tests/test_midigen.py +152 -0
- midigen/tests/test_note.py +31 -0
- midigen/tests/test_scale.py +49 -0
- midigen/tests/test_song.py +41 -0
- midigen/tests/test_track.py +165 -0
- midigen/track.py +215 -0
- midigen_lib-0.1.0.dist-info/LICENSE +674 -0
- midigen_lib-0.1.0.dist-info/METADATA +208 -0
- midigen_lib-0.1.0.dist-info/RECORD +24 -0
- midigen_lib-0.1.0.dist-info/WHEEL +4 -0
midigen/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .midigen import MidiGen
|
|
2
|
+
from .track import Track
|
|
3
|
+
from .note import Note
|
|
4
|
+
from .chord import Chord, ChordProgression, Arpeggio
|
|
5
|
+
from .key import Key, KEY_MAP
|
|
6
|
+
from .drums import DrumKit
|
|
7
|
+
from .song import Song
|
|
8
|
+
from .section import Section
|
|
9
|
+
from .instruments import INSTRUMENT_MAP
|
midigen/chord.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from midigen.note import Note
|
|
3
|
+
from midigen.key import KEY_MAP, Key
|
|
4
|
+
from enum import Enum
|
|
5
|
+
import music21
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Chord:
|
|
9
|
+
def __init__(self, notes: List[Note]):
|
|
10
|
+
self.notes = notes
|
|
11
|
+
self.root = self.get_root()
|
|
12
|
+
self._calculate_start_time()
|
|
13
|
+
self._calculate_duration()
|
|
14
|
+
|
|
15
|
+
def __str__(self) -> str:
|
|
16
|
+
return f"[{', '.join(str(note) for note in self.notes)}]"
|
|
17
|
+
|
|
18
|
+
def _calculate_start_time(self) -> int:
|
|
19
|
+
"""
|
|
20
|
+
Calculate the start time of the chord.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
The start time of the chord.
|
|
24
|
+
"""
|
|
25
|
+
self.time = min(note.time for note in self.notes) if self.notes else 0
|
|
26
|
+
return self.time
|
|
27
|
+
|
|
28
|
+
def _calculate_duration(self) -> int:
|
|
29
|
+
"""
|
|
30
|
+
Calculate the duration of the chord.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The duration of the longest note in the chord.
|
|
34
|
+
"""
|
|
35
|
+
if not self.notes:
|
|
36
|
+
return 0
|
|
37
|
+
|
|
38
|
+
# Find the earliest start time of any note in the chord
|
|
39
|
+
earliest_start_time = min(note.time for note in self.notes)
|
|
40
|
+
# Find the latest ending time, calculated as start time plus duration for each note
|
|
41
|
+
latest_end_time = max(note.time + note.duration for note in self.notes)
|
|
42
|
+
|
|
43
|
+
# The duration of the chord is the difference between the earliest start and the latest end
|
|
44
|
+
self.duration = latest_end_time - earliest_start_time
|
|
45
|
+
return self.duration
|
|
46
|
+
|
|
47
|
+
def add_note(self, note: Note) -> None:
|
|
48
|
+
self.notes.append(note)
|
|
49
|
+
self._calculate_duration()
|
|
50
|
+
self._calculate_start_time()
|
|
51
|
+
|
|
52
|
+
def get_chord(self) -> List[Note]:
|
|
53
|
+
return self.notes
|
|
54
|
+
|
|
55
|
+
def get_root(self) -> Note:
|
|
56
|
+
if self.notes:
|
|
57
|
+
self.root = self.notes[0]
|
|
58
|
+
return self.root
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
def major_triad(self) -> List[Note]:
|
|
62
|
+
return [self.root, self.root + 4, self.root + 7]
|
|
63
|
+
|
|
64
|
+
def minor_triad(self) -> List[Note]:
|
|
65
|
+
return [self.root, self.root + 3, self.root + 7]
|
|
66
|
+
|
|
67
|
+
def dominant_seventh(self) -> List[Note]:
|
|
68
|
+
return self.major_triad() + [self.root + 10]
|
|
69
|
+
|
|
70
|
+
def major_seventh(self) -> List[Note]:
|
|
71
|
+
return self.major_triad() + [self.root + 11]
|
|
72
|
+
|
|
73
|
+
def minor_seventh(self) -> List[Note]:
|
|
74
|
+
return self.minor_triad() + [self.root + 10]
|
|
75
|
+
|
|
76
|
+
def half_diminished_seventh(self) -> List[Note]:
|
|
77
|
+
return [self.root, self.root + 3, self.root + 6, self.root + 10]
|
|
78
|
+
|
|
79
|
+
def diminished_seventh(self) -> List[Note]:
|
|
80
|
+
return [self.root, self.root + 3, self.root + 6, self.root + 9]
|
|
81
|
+
|
|
82
|
+
def minor_ninth(self) -> List[Note]:
|
|
83
|
+
return self.minor_seventh() + [self.root + 14]
|
|
84
|
+
|
|
85
|
+
def major_ninth(self) -> List[Note]:
|
|
86
|
+
return self.major_seventh() + [self.root + 14]
|
|
87
|
+
|
|
88
|
+
def dominant_ninth(self) -> List[Note]:
|
|
89
|
+
return self.dominant_seventh() + [self.root + 14]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ChordProgression:
|
|
93
|
+
def __init__(self, chords: List[Chord]):
|
|
94
|
+
self.chords = chords
|
|
95
|
+
self._calculate_start_time()
|
|
96
|
+
self._calculate_duration()
|
|
97
|
+
|
|
98
|
+
def __str__(self) -> str:
|
|
99
|
+
return f"[{', '.join(str(chord) for chord in self.chords)}]"
|
|
100
|
+
|
|
101
|
+
def get_progression(self) -> List[Chord]:
|
|
102
|
+
return self.chords
|
|
103
|
+
|
|
104
|
+
def _calculate_duration(self) -> int:
|
|
105
|
+
self.duration = sum(chord._calculate_duration() for chord in self.chords)
|
|
106
|
+
return self.duration
|
|
107
|
+
|
|
108
|
+
def _calculate_start_time(self) -> int:
|
|
109
|
+
self.time = min(chord._calculate_start_time() for chord in self.chords) if self.chords else 0
|
|
110
|
+
return self.time
|
|
111
|
+
|
|
112
|
+
def __eq__(self, other) -> bool:
|
|
113
|
+
return self.chords == other.chords
|
|
114
|
+
|
|
115
|
+
def add_chord(self, chord: Chord) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Add a chord (simultaneous notes) to the track.
|
|
118
|
+
|
|
119
|
+
:param chord: A Chord object.
|
|
120
|
+
"""
|
|
121
|
+
self.chords.append(chord)
|
|
122
|
+
self._calculate_duration()
|
|
123
|
+
self._calculate_start_time()
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def from_roman_numerals(
|
|
127
|
+
cls,
|
|
128
|
+
key: Key,
|
|
129
|
+
progression_string: str,
|
|
130
|
+
octave: int = 4,
|
|
131
|
+
duration: int = 480,
|
|
132
|
+
time_per_chord: int = 0
|
|
133
|
+
):
|
|
134
|
+
m21_key = music21.key.Key(key.name, key.mode)
|
|
135
|
+
roman_numerals = progression_string.split('-')
|
|
136
|
+
chords = []
|
|
137
|
+
current_time = 0
|
|
138
|
+
for rn_str in roman_numerals:
|
|
139
|
+
rn = music21.roman.RomanNumeral(rn_str, m21_key)
|
|
140
|
+
pitches = rn.pitches
|
|
141
|
+
notes = []
|
|
142
|
+
for i, pitch in enumerate(pitches):
|
|
143
|
+
note_name = f"{pitch.nameWithOctave}"
|
|
144
|
+
# A simple way to handle octave, might need refinement
|
|
145
|
+
note_name_without_octave = ''.join(filter(str.isalpha, pitch.name))
|
|
146
|
+
full_note_name = f"{note_name_without_octave}{octave}"
|
|
147
|
+
midi_pitch = KEY_MAP.get(full_note_name)
|
|
148
|
+
|
|
149
|
+
# If the note is not in the current octave, try the next one
|
|
150
|
+
if midi_pitch is None:
|
|
151
|
+
full_note_name = f"{note_name_without_octave}{octave + 1}"
|
|
152
|
+
midi_pitch = KEY_MAP.get(full_note_name)
|
|
153
|
+
|
|
154
|
+
if midi_pitch:
|
|
155
|
+
# The first note of the chord starts at `current_time`, subsequent notes start at the same time.
|
|
156
|
+
note_time = current_time if i == 0 else 0
|
|
157
|
+
notes.append(Note(pitch=midi_pitch, velocity=64, duration=duration, time=note_time))
|
|
158
|
+
|
|
159
|
+
if notes:
|
|
160
|
+
chords.append(Chord(notes))
|
|
161
|
+
current_time += time_per_chord
|
|
162
|
+
|
|
163
|
+
return cls(chords)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ArpeggioPattern(Enum):
|
|
167
|
+
ASCENDING = "ascending"
|
|
168
|
+
DESCENDING = "descending"
|
|
169
|
+
ALTERNATING = "alternating"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Arpeggio(Chord):
|
|
173
|
+
def __init__(self, notes: List[Note], delay: int = 0, pattern: ArpeggioPattern = ArpeggioPattern.ASCENDING, loops: int = 1):
|
|
174
|
+
"""
|
|
175
|
+
:param root_note: The root note of the arpeggio.
|
|
176
|
+
:param delay: The delay between each note in the arpeggio.
|
|
177
|
+
"""
|
|
178
|
+
super().__init__(notes)
|
|
179
|
+
self.delay = delay
|
|
180
|
+
self.pattern = pattern
|
|
181
|
+
self.loops = loops
|
|
182
|
+
|
|
183
|
+
def get_notes(self) -> List[Note]:
|
|
184
|
+
return self.notes
|
|
185
|
+
|
|
186
|
+
def get_sequential_notes(self) -> List[Note]:
|
|
187
|
+
"""
|
|
188
|
+
Get the sequential notes of the arpeggio based on the pattern, delay, and looping.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
A list of notes representing the arpeggio.
|
|
192
|
+
"""
|
|
193
|
+
sequential_notes = []
|
|
194
|
+
for loop in range(self.loops):
|
|
195
|
+
if self.pattern == ArpeggioPattern.ASCENDING:
|
|
196
|
+
notes = self.notes
|
|
197
|
+
elif self.pattern == ArpeggioPattern.DESCENDING:
|
|
198
|
+
notes = list(reversed(self.notes))
|
|
199
|
+
elif self.pattern == ArpeggioPattern.ALTERNATING:
|
|
200
|
+
notes = self.notes if loop % 2 == 0 else list(reversed(self.notes))
|
|
201
|
+
|
|
202
|
+
for i, note in enumerate(notes):
|
|
203
|
+
# Add an offset to the time for the second and subsequent loops
|
|
204
|
+
time_offset = loop * len(notes) * self.delay
|
|
205
|
+
time = note.time + time_offset if i == 0 else self.delay * i + time_offset
|
|
206
|
+
new_note = Note(note.pitch, note.velocity, note.duration, time)
|
|
207
|
+
sequential_notes.append(new_note)
|
|
208
|
+
|
|
209
|
+
return sequential_notes
|
midigen/drums.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from midigen.note import Note
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
GM1_DRUM_MAP = {
|
|
5
|
+
"Acoustic Bass Drum": 35,
|
|
6
|
+
"Bass Drum 1": 36,
|
|
7
|
+
"Side Stick": 37,
|
|
8
|
+
"Acoustic Snare": 38,
|
|
9
|
+
"Hand Clap": 39,
|
|
10
|
+
"Electric Snare": 40,
|
|
11
|
+
"Low Floor Tom": 41,
|
|
12
|
+
"Closed Hi Hat": 42,
|
|
13
|
+
"High Floor Tom": 43,
|
|
14
|
+
"Pedal Hi-Hat": 44,
|
|
15
|
+
"Low Tom": 45,
|
|
16
|
+
"Open Hi-Hat": 46,
|
|
17
|
+
"Low-Mid Tom": 47,
|
|
18
|
+
"Hi-Mid Tom": 48,
|
|
19
|
+
"Crash Cymbal 1": 49,
|
|
20
|
+
"High Tom": 50,
|
|
21
|
+
"Ride Cymbal 1": 51,
|
|
22
|
+
"Chinese Cymbal": 52,
|
|
23
|
+
"Ride Bell": 53,
|
|
24
|
+
"Tambourine": 54,
|
|
25
|
+
"Splash Cymbal": 55,
|
|
26
|
+
"Cowbell": 56,
|
|
27
|
+
"Crash Cymbal 2": 57,
|
|
28
|
+
"Vibraslap": 58,
|
|
29
|
+
"Ride Cymbal 2": 59,
|
|
30
|
+
"Hi Bongo": 60,
|
|
31
|
+
"Low Bongo": 61,
|
|
32
|
+
"Mute Hi Conga": 62,
|
|
33
|
+
"Open Hi Conga": 63,
|
|
34
|
+
"Low Conga": 64,
|
|
35
|
+
"High Timbale": 65,
|
|
36
|
+
"Low Timbale": 66,
|
|
37
|
+
"High Agogo": 67,
|
|
38
|
+
"Low Agogo": 68,
|
|
39
|
+
"Cabasa": 69,
|
|
40
|
+
"Maracas": 70,
|
|
41
|
+
"Short Whistle": 71,
|
|
42
|
+
"Long Whistle": 72,
|
|
43
|
+
"Short Guiro": 73,
|
|
44
|
+
"Long Guiro": 74,
|
|
45
|
+
"Claves": 75,
|
|
46
|
+
"Hi Wood Block": 76,
|
|
47
|
+
"Low Wood Block": 77,
|
|
48
|
+
"Mute Cuica": 78,
|
|
49
|
+
"Open Cuica": 79,
|
|
50
|
+
"Mute Triangle": 80,
|
|
51
|
+
"Open Triangle": 81,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Drum:
|
|
56
|
+
"""
|
|
57
|
+
A drum sound in a drum kit, represented as a MIDI note.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, pitch: int, velocity: int, duration: int, time: int):
|
|
61
|
+
if duration < 0:
|
|
62
|
+
raise ValueError("Duration must be non-negative")
|
|
63
|
+
if velocity < 0:
|
|
64
|
+
raise ValueError("Velocity must be non-negative")
|
|
65
|
+
if not 0 <= pitch <= 127:
|
|
66
|
+
raise ValueError("Pitch must be within the MIDI standard range of 0 to 127")
|
|
67
|
+
|
|
68
|
+
self.note = Note(pitch, velocity, duration, time)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DrumKit:
|
|
72
|
+
"""
|
|
73
|
+
A collection of drum sounds, each represented as a MIDI note.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self):
|
|
77
|
+
self.drums = []
|
|
78
|
+
|
|
79
|
+
def add_drum(
|
|
80
|
+
self, drum_name: str, velocity: int = 64, duration: int = 1, time: int = 0
|
|
81
|
+
) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Add a drum to the drum kit.
|
|
84
|
+
|
|
85
|
+
:param drum_name: The name of the drum, as defined in the GM1_DRUM_MAP.
|
|
86
|
+
:param velocity: The velocity of the drum sound.
|
|
87
|
+
:param duration: The duration of the drum sound.
|
|
88
|
+
:param time: The time at which the drum sound should start.
|
|
89
|
+
"""
|
|
90
|
+
if drum_name not in GM1_DRUM_MAP:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Invalid drum name: {drum_name}. Please use a valid drum name from the GM1_DRUM_MAP."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
drum = Drum(GM1_DRUM_MAP[drum_name], velocity, duration, time)
|
|
96
|
+
self.drums.append(drum)
|
|
97
|
+
|
|
98
|
+
def get_drums(self) -> List[Note]:
|
|
99
|
+
"""
|
|
100
|
+
Get the list of drums in the drum kit.
|
|
101
|
+
|
|
102
|
+
:return: A list of Drum objects.
|
|
103
|
+
"""
|
|
104
|
+
return [drum.note for drum in self.drums]
|
midigen/instruments.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
INSTRUMENT_MAP = {
|
|
2
|
+
# Piano
|
|
3
|
+
"Acoustic Grand Piano": 0,
|
|
4
|
+
"Bright Acoustic Piano": 1,
|
|
5
|
+
"Electric Grand Piano": 2,
|
|
6
|
+
"Honky-tonk Piano": 3,
|
|
7
|
+
"Electric Piano 1": 4,
|
|
8
|
+
"Electric Piano 2": 5,
|
|
9
|
+
"Harpsichord": 6,
|
|
10
|
+
"Clavinet": 7,
|
|
11
|
+
# Chromatic Percussion
|
|
12
|
+
"Celesta": 8,
|
|
13
|
+
"Glockenspiel": 9,
|
|
14
|
+
"Music Box": 10,
|
|
15
|
+
"Vibraphone": 11,
|
|
16
|
+
"Marimba": 12,
|
|
17
|
+
"Xylophone": 13,
|
|
18
|
+
"Tubular Bells": 14,
|
|
19
|
+
"Dulcimer": 15,
|
|
20
|
+
# Organ
|
|
21
|
+
"Drawbar Organ": 16,
|
|
22
|
+
"Percussive Organ": 17,
|
|
23
|
+
"Rock Organ": 18,
|
|
24
|
+
"Church Organ": 19,
|
|
25
|
+
"Reed Organ": 20,
|
|
26
|
+
"Accordion": 21,
|
|
27
|
+
"Harmonica": 22,
|
|
28
|
+
"Tango Accordion": 23,
|
|
29
|
+
# Guitar
|
|
30
|
+
"Acoustic Guitar (nylon)": 24,
|
|
31
|
+
"Acoustic Guitar (steel)": 25,
|
|
32
|
+
"Electric Guitar (jazz)": 26,
|
|
33
|
+
"Electric Guitar (clean)": 27,
|
|
34
|
+
"Electric Guitar (muted)": 28,
|
|
35
|
+
"Overdriven Guitar": 29,
|
|
36
|
+
"Distortion Guitar": 30,
|
|
37
|
+
"Guitar Harmonics": 31,
|
|
38
|
+
# Bass
|
|
39
|
+
"Acoustic Bass": 32,
|
|
40
|
+
"Electric Bass (finger)": 33,
|
|
41
|
+
"Electric Bass (pick)": 34,
|
|
42
|
+
"Fretless Bass": 35,
|
|
43
|
+
"Slap Bass 1": 36,
|
|
44
|
+
"Slap Bass 2": 37,
|
|
45
|
+
"Synth Bass 1": 38,
|
|
46
|
+
"Synth Bass 2": 39,
|
|
47
|
+
# Strings
|
|
48
|
+
"Violin": 40,
|
|
49
|
+
"Viola": 41,
|
|
50
|
+
"Cello": 42,
|
|
51
|
+
"Contrabass": 43,
|
|
52
|
+
"Tremolo Strings": 44,
|
|
53
|
+
"Pizzicato Strings": 45,
|
|
54
|
+
"Orchestral Harp": 46,
|
|
55
|
+
"Timpani": 47,
|
|
56
|
+
# Ensemble
|
|
57
|
+
"String Ensemble 1": 48,
|
|
58
|
+
"String Ensemble 2": 49,
|
|
59
|
+
"Synth Strings 1": 50,
|
|
60
|
+
"Synth Strings 2": 51,
|
|
61
|
+
"Choir Aahs": 52,
|
|
62
|
+
"Voice Oohs": 53,
|
|
63
|
+
"Synth Voice": 54,
|
|
64
|
+
"Orchestra Hit": 55,
|
|
65
|
+
# Brass
|
|
66
|
+
"Trumpet": 56,
|
|
67
|
+
"Trombone": 57,
|
|
68
|
+
"Tuba": 58,
|
|
69
|
+
"Muted Trumpet": 59,
|
|
70
|
+
"French Horn": 60,
|
|
71
|
+
"Brass Section": 61,
|
|
72
|
+
"Synth Brass 1": 62,
|
|
73
|
+
"Synth Brass 2": 63,
|
|
74
|
+
# Reed
|
|
75
|
+
"Soprano Sax": 64,
|
|
76
|
+
"Alto Sax": 65,
|
|
77
|
+
"Tenor Sax": 66,
|
|
78
|
+
"Baritone Sax": 67,
|
|
79
|
+
"Oboe": 68,
|
|
80
|
+
"English Horn": 69,
|
|
81
|
+
"Bassoon": 70,
|
|
82
|
+
"Clarinet": 71,
|
|
83
|
+
# Pipe
|
|
84
|
+
"Piccolo": 72,
|
|
85
|
+
"Flute": 73,
|
|
86
|
+
"Recorder": 74,
|
|
87
|
+
"Pan Flute": 75,
|
|
88
|
+
"Blown Bottle": 76,
|
|
89
|
+
"Shakuhachi": 77,
|
|
90
|
+
"Whistle": 78,
|
|
91
|
+
"Ocarina": 79,
|
|
92
|
+
# Synth Lead
|
|
93
|
+
"Lead 1 (square)": 80,
|
|
94
|
+
"Lead 2 (sawtooth)": 81,
|
|
95
|
+
"Lead 3 (calliope)": 82,
|
|
96
|
+
"Lead 4 (chiff)": 83,
|
|
97
|
+
"Lead 5 (charang)": 84,
|
|
98
|
+
"Lead 6 (voice)": 85,
|
|
99
|
+
"Lead 7 (fifths)": 86,
|
|
100
|
+
"Lead 8 (bass + lead)": 87,
|
|
101
|
+
# Synth Pad
|
|
102
|
+
"Pad 1 (new age)": 88,
|
|
103
|
+
"Pad 2 (warm)": 89,
|
|
104
|
+
"Pad 3 (polysynth)": 90,
|
|
105
|
+
"Pad 4 (choir)": 91,
|
|
106
|
+
"Pad 5 (bowed)": 92,
|
|
107
|
+
"Pad 6 (metallic)": 93,
|
|
108
|
+
"Pad 7 (halo)": 94,
|
|
109
|
+
"Pad 8 (sweep)": 95,
|
|
110
|
+
# Synth Effects
|
|
111
|
+
"FX 1 (rain)": 96,
|
|
112
|
+
"FX 2 (soundtrack)": 97,
|
|
113
|
+
"FX 3 (crystal)": 98,
|
|
114
|
+
"FX 4 (atmosphere)": 99,
|
|
115
|
+
"FX 5 (brightness)": 100,
|
|
116
|
+
"FX 6 (goblins)": 101,
|
|
117
|
+
"FX 7 (echoes)": 102,
|
|
118
|
+
"FX 8 (sci-fi)": 103,
|
|
119
|
+
# Ethnic
|
|
120
|
+
"Sitar": 104,
|
|
121
|
+
"Banjo": 105,
|
|
122
|
+
"Shamisen": 106,
|
|
123
|
+
"Koto": 107,
|
|
124
|
+
"Kalimba": 108,
|
|
125
|
+
"Bagpipe": 109,
|
|
126
|
+
"Fiddle": 110,
|
|
127
|
+
"Shanai": 111,
|
|
128
|
+
# Percussive
|
|
129
|
+
"Tinkle Bell": 112,
|
|
130
|
+
"Agogo": 113,
|
|
131
|
+
"Steel Drums": 114,
|
|
132
|
+
"Woodblock": 115,
|
|
133
|
+
"Taiko Drum": 116,
|
|
134
|
+
"Melodic Tom": 117,
|
|
135
|
+
"Synth Drum": 118,
|
|
136
|
+
"Reverse Cymbal": 119,
|
|
137
|
+
# Sound Effects
|
|
138
|
+
"Guitar Fret Noise": 120,
|
|
139
|
+
"Breath Noise": 121,
|
|
140
|
+
"Seashore": 122,
|
|
141
|
+
"Bird Tweet": 123,
|
|
142
|
+
"Telephone Ring": 124,
|
|
143
|
+
"Helicopter": 125,
|
|
144
|
+
"Applause": 126,
|
|
145
|
+
"Gunshot": 127,
|
|
146
|
+
}
|
midigen/key.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
VALID_KEYS = [
|
|
2
|
+
(note, mode)
|
|
3
|
+
for note in [
|
|
4
|
+
"A",
|
|
5
|
+
"A#",
|
|
6
|
+
"Ab",
|
|
7
|
+
"B",
|
|
8
|
+
"Bb",
|
|
9
|
+
"C",
|
|
10
|
+
"C#",
|
|
11
|
+
"Cb",
|
|
12
|
+
"D",
|
|
13
|
+
"D#",
|
|
14
|
+
"Db",
|
|
15
|
+
"E",
|
|
16
|
+
"Eb",
|
|
17
|
+
"F",
|
|
18
|
+
"F#",
|
|
19
|
+
"Fb",
|
|
20
|
+
"G",
|
|
21
|
+
"G#",
|
|
22
|
+
"Gb",
|
|
23
|
+
]
|
|
24
|
+
for mode in ["major", "minor"]
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
KEY_MAP = {
|
|
28
|
+
"C0": 12,
|
|
29
|
+
"C#0": 13,
|
|
30
|
+
"Db0": 13,
|
|
31
|
+
"D0": 14,
|
|
32
|
+
"D#0": 15,
|
|
33
|
+
"Eb0": 15,
|
|
34
|
+
"E0": 16,
|
|
35
|
+
"F0": 17,
|
|
36
|
+
"F#0": 18,
|
|
37
|
+
"Gb0": 18,
|
|
38
|
+
"G0": 19,
|
|
39
|
+
"G#0": 20,
|
|
40
|
+
"Ab0": 20,
|
|
41
|
+
"A0": 21,
|
|
42
|
+
"A#0": 22,
|
|
43
|
+
"Bb0": 22,
|
|
44
|
+
"B0": 23,
|
|
45
|
+
"C1": 24,
|
|
46
|
+
"C#1": 25,
|
|
47
|
+
"Db1": 25,
|
|
48
|
+
"D1": 26,
|
|
49
|
+
"D#1": 27,
|
|
50
|
+
"Eb1": 27,
|
|
51
|
+
"E1": 28,
|
|
52
|
+
"F1": 29,
|
|
53
|
+
"F#1": 30,
|
|
54
|
+
"Gb1": 30,
|
|
55
|
+
"G1": 31,
|
|
56
|
+
"G#1": 32,
|
|
57
|
+
"Ab1": 32,
|
|
58
|
+
"A1": 33,
|
|
59
|
+
"A#1": 34,
|
|
60
|
+
"Bb1": 34,
|
|
61
|
+
"B1": 35,
|
|
62
|
+
"C2": 36,
|
|
63
|
+
"C#2": 37,
|
|
64
|
+
"Db2": 37,
|
|
65
|
+
"D2": 38,
|
|
66
|
+
"D#2": 39,
|
|
67
|
+
"Eb2": 39,
|
|
68
|
+
"E2": 40,
|
|
69
|
+
"F2": 41,
|
|
70
|
+
"F#2": 42,
|
|
71
|
+
"Gb2": 42,
|
|
72
|
+
"G2": 43,
|
|
73
|
+
"G#2": 44,
|
|
74
|
+
"Ab2": 44,
|
|
75
|
+
"A2": 45,
|
|
76
|
+
"A#2": 46,
|
|
77
|
+
"Bb2": 46,
|
|
78
|
+
"B2": 47,
|
|
79
|
+
"C3": 48,
|
|
80
|
+
"C#3": 49,
|
|
81
|
+
"Db3": 49,
|
|
82
|
+
"D3": 50,
|
|
83
|
+
"D#3": 51,
|
|
84
|
+
"Eb3": 51,
|
|
85
|
+
"E3": 52,
|
|
86
|
+
"F3": 53,
|
|
87
|
+
"F#3": 54,
|
|
88
|
+
"Gb3": 54,
|
|
89
|
+
"G3": 55,
|
|
90
|
+
"G#3": 56,
|
|
91
|
+
"Ab3": 56,
|
|
92
|
+
"A3": 57,
|
|
93
|
+
"A#3": 58,
|
|
94
|
+
"Bb3": 58,
|
|
95
|
+
"B3": 59,
|
|
96
|
+
"C4": 60,
|
|
97
|
+
"C#4": 61,
|
|
98
|
+
"Db4": 61,
|
|
99
|
+
"D4": 62,
|
|
100
|
+
"D#4": 63,
|
|
101
|
+
"Eb4": 63,
|
|
102
|
+
"E4": 64,
|
|
103
|
+
"F4": 65,
|
|
104
|
+
"F#4": 66,
|
|
105
|
+
"Gb4": 66,
|
|
106
|
+
"G4": 67,
|
|
107
|
+
"G#4": 68,
|
|
108
|
+
"Ab4": 68,
|
|
109
|
+
"A4": 69,
|
|
110
|
+
"A#4": 70,
|
|
111
|
+
"Bb4": 70,
|
|
112
|
+
"B4": 71,
|
|
113
|
+
"C5": 72,
|
|
114
|
+
"C#5": 73,
|
|
115
|
+
"Db5": 73,
|
|
116
|
+
"D5": 74,
|
|
117
|
+
"D#5": 75,
|
|
118
|
+
"Eb5": 75,
|
|
119
|
+
"E5": 76,
|
|
120
|
+
"F5": 77,
|
|
121
|
+
"F#5": 78,
|
|
122
|
+
"Gb5": 78,
|
|
123
|
+
"G5": 79,
|
|
124
|
+
"G#5": 80,
|
|
125
|
+
"Ab5": 80,
|
|
126
|
+
"A5": 81,
|
|
127
|
+
"A#5": 82,
|
|
128
|
+
"Bb5": 82,
|
|
129
|
+
"B5": 83,
|
|
130
|
+
"C6": 84,
|
|
131
|
+
"C#6": 85,
|
|
132
|
+
"Db6": 85,
|
|
133
|
+
"D6": 86,
|
|
134
|
+
"D#6": 87,
|
|
135
|
+
"Eb6": 87,
|
|
136
|
+
"E6": 88,
|
|
137
|
+
"F6": 89,
|
|
138
|
+
"F#6": 90,
|
|
139
|
+
"Gb6": 90,
|
|
140
|
+
"G6": 91,
|
|
141
|
+
"G#6": 92,
|
|
142
|
+
"Ab6": 92,
|
|
143
|
+
"A6": 93,
|
|
144
|
+
"A#6": 94,
|
|
145
|
+
"Bb6": 94,
|
|
146
|
+
"B6": 95,
|
|
147
|
+
"C7": 96,
|
|
148
|
+
"C#7": 97,
|
|
149
|
+
"Db7": 97,
|
|
150
|
+
"D7": 98,
|
|
151
|
+
"D#7": 99,
|
|
152
|
+
"Eb7": 99,
|
|
153
|
+
"E7": 100,
|
|
154
|
+
"F7": 101,
|
|
155
|
+
"F#7": 102,
|
|
156
|
+
"Gb7": 102,
|
|
157
|
+
"G7": 103,
|
|
158
|
+
"G#7": 104,
|
|
159
|
+
"Ab7": 104,
|
|
160
|
+
"A7": 105,
|
|
161
|
+
"A#7": 106,
|
|
162
|
+
"Bb7": 106,
|
|
163
|
+
"B7": 107,
|
|
164
|
+
"C8": 108,
|
|
165
|
+
"C#8": 109,
|
|
166
|
+
"Db8": 109,
|
|
167
|
+
"D8": 110,
|
|
168
|
+
"D#8": 111,
|
|
169
|
+
"Eb8": 111,
|
|
170
|
+
"E8": 112,
|
|
171
|
+
"F8": 113,
|
|
172
|
+
"F#8": 114,
|
|
173
|
+
"Gb8": 114,
|
|
174
|
+
"G8": 115,
|
|
175
|
+
"G#8": 116,
|
|
176
|
+
"Ab8": 116,
|
|
177
|
+
"A8": 117,
|
|
178
|
+
"A#8": 118,
|
|
179
|
+
"Bb8": 118,
|
|
180
|
+
"B8": 119,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Key:
|
|
185
|
+
def __init__(self, name: str, mode: str = "major"):
|
|
186
|
+
if (name, mode) not in VALID_KEYS:
|
|
187
|
+
raise ValueError(
|
|
188
|
+
f"Invalid key. Please use a valid key from the list: {format(VALID_KEYS)}"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
self.name = name
|
|
192
|
+
self.mode = mode
|
|
193
|
+
|
|
194
|
+
def __str__(self) -> str:
|
|
195
|
+
return f"{self.name}{'' if self.mode == 'major' else 'm'}"
|
|
196
|
+
|
|
197
|
+
def __repr__(self) -> str:
|
|
198
|
+
return f"Key(name='{self.name}', mode='{self.mode}')"
|
|
199
|
+
|
|
200
|
+
def __eq__(self, other) -> bool:
|
|
201
|
+
if isinstance(other, Key):
|
|
202
|
+
return self.name == other.name and self.mode == other.mode
|
|
203
|
+
return False
|