init
This commit is contained in:
0
__init__.py
Normal file
0
__init__.py
Normal file
BIN
__pycache__/major.cpython-310.pyc
Normal file
BIN
__pycache__/major.cpython-310.pyc
Normal file
Binary file not shown.
51
app.py
Normal file
51
app.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import numpy as np
|
||||
import gradio as gr
|
||||
from major import MajorScale,chromatic,chromatic_notes_set,Note
|
||||
|
||||
# sample rate, default is 48000
|
||||
sr = 48000
|
||||
|
||||
def generate_tone(note, octave, duration):
|
||||
note_in_scale = Note(chromatic[note][0], octave)
|
||||
frequency = note_in_scale.get_frequency()
|
||||
# debug print
|
||||
# print(note_in_scale,frequency)
|
||||
duration = int(duration)
|
||||
audio = np.linspace(0, duration, duration * sr)
|
||||
audio = (20000 * np.sin(audio * (2 * np.pi * frequency))).astype(np.int16)
|
||||
return sr, audio
|
||||
|
||||
def generate_rhythm(rhythm, duration_per_note):
|
||||
rhythms = rhythm.split(",")
|
||||
audio = np.zeros(int(duration_per_note * len(rhythms) * sr))
|
||||
for i, rhythm_note in enumerate(rhythms):
|
||||
if rhythm != "x":
|
||||
if rhythm_note[:-1] not in chromatic_notes_set:
|
||||
raise ValueError(f"Invalid note: {rhythm_note}")
|
||||
note = Note(rhythm_note[:-1], int(rhythm_note[-1]))
|
||||
# debug print
|
||||
print(note, note.get_frequency(), note.code)
|
||||
segment_audio = np.linspace(0, duration_per_note, duration_per_note * sr)
|
||||
segment_audio = (20000 * np.sin(segment_audio * (2 * np.pi * note.get_frequency()))).astype(np.int16)
|
||||
audio[int(i * duration_per_note * sr):int((i + 1) * duration_per_note * sr)] = segment_audio
|
||||
else:
|
||||
audio[int(i * duration_per_note * sr):int((i + 1) * duration_per_note * sr)] = 0
|
||||
return sr, audio.astype(np.int16)
|
||||
|
||||
demo = gr.Interface(
|
||||
# generate_tone,
|
||||
# [
|
||||
# gr.Dropdown(chromatic, type="index"),
|
||||
# gr.Slider(0, 10, step=1),
|
||||
# gr.Textbox(value="1", label="Duration in seconds"),
|
||||
# ],
|
||||
# "audio",
|
||||
generate_rhythm,
|
||||
[
|
||||
gr.Textbox(value="C4,D4,E4,F4,G4,A4,B4,C5", label="Rhythm"),
|
||||
gr.Slider(0, 10, value=1, step=1, label="Duration per note"),
|
||||
],
|
||||
"audio",
|
||||
)
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
||||
262
chord.py
Normal file
262
chord.py
Normal file
@@ -0,0 +1,262 @@
|
||||
from major import MajorScale,Note
|
||||
|
||||
intervals_triad = {
|
||||
(4,3): "major",
|
||||
(3,4): "minor",
|
||||
(3,3): "diminished",
|
||||
(4,4): "augmented"
|
||||
}
|
||||
|
||||
triad_intervals = {
|
||||
"major": [4,3],
|
||||
"minor": [3,4],
|
||||
"diminished": [3,3],
|
||||
"augmented": [4,4]
|
||||
}
|
||||
|
||||
seven_chord_intervals = {
|
||||
"dominant": [4,3,3],
|
||||
"diminished": [3,4,3],
|
||||
"half-diminished": [3,3,4],
|
||||
"augmented major": [4,4,3],
|
||||
"minor-major": [3,4,4],
|
||||
"major": [4,3,4],
|
||||
"full-diminished": [3,3,3]
|
||||
}
|
||||
|
||||
intervals_seven_chord = {
|
||||
(4,3,3): ("major-minor 7","dominant 7","7"),
|
||||
(3,4,3): ("minor-minor 7","diminished 7","m7"),
|
||||
(3,3,4): ("diminished-minor 7","half-diminished 7","m7b5"),
|
||||
(4,4,3): ("augmented major 7","augmented major 7","M7#5"),
|
||||
(3,4,4): ("minor-major 7","minor-major 7","mM7"),
|
||||
(4,3,4): ("major-major 7","major 7","M7"),
|
||||
(3,3,3): ("diminished-diminished 7","full-diminished 7","dim7"),
|
||||
}
|
||||
|
||||
Roman_numerals = ["I","II","III","IV","V","VI","VII"]
|
||||
|
||||
class Chord:
|
||||
def __init__(self, root:Note, keys:list[Note]=None, zero_indexed:bool=False):
|
||||
"""
|
||||
Build a chord object.
|
||||
Args:
|
||||
root: Note, the root note of the chord
|
||||
keys: list[str], the notes of the chord, default is None, if want to build with initial triad, use build_triad method
|
||||
zero_indexed: bool, if the keys are zero indexed, default is False
|
||||
"""
|
||||
self.root = root
|
||||
self.major = MajorScale(root)
|
||||
self.keys = keys
|
||||
self.zero_indexed = zero_indexed
|
||||
|
||||
def get_intervals(self) -> list[int]:
|
||||
"""
|
||||
Get the intervals of the chord.
|
||||
Returns:
|
||||
list[int], the quality of the chord, empty list if not built yet
|
||||
"""
|
||||
if self.keys is None:
|
||||
return []
|
||||
difference = [self.keys[i].code - self.keys[i-1].code for i in range(1, len(self.keys))]
|
||||
return difference
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.get_quality()} chord under major scale {self.root.get_name()}"
|
||||
|
||||
class Triad(Chord):
|
||||
def __init__(self, root:Note, keys:list[Note]=None, zero_indexed:bool=False):
|
||||
if len(keys) != 3:
|
||||
raise ValueError("Triad must have 3 keys")
|
||||
super().__init__(root, keys, zero_indexed)
|
||||
|
||||
@classmethod
|
||||
def build_triad(self, root:Note, starting_index:int=1, quality:str=None) -> 'Triad':
|
||||
"""
|
||||
Build a triad chord object.
|
||||
Args:
|
||||
root: Note, the root note of the chord
|
||||
starting_index: int, the starting index of the chord, default is 1
|
||||
quality: str, the quality of the chord, default is None
|
||||
"""
|
||||
keys = self.get_triad(starting_index, quality)
|
||||
return Triad(root, keys, self.zero_indexed)
|
||||
|
||||
def get_triad(self, starting_index:int, quality:str=None) -> list[Note]:
|
||||
"""
|
||||
Get a triad chord from the major scale.
|
||||
Args:
|
||||
starting_index: int, the starting index of the chord, default is 1, assume is positive indexed
|
||||
quality: str, the quality of the chord ["major", "minor", "diminished", "augmented"], default is None
|
||||
Returns:
|
||||
list[Note], the triad chord
|
||||
"""
|
||||
if not self.zero_indexed:
|
||||
starting_index = starting_index - 1
|
||||
triad = [self.major.root + starting_index]
|
||||
major_notes=self.major.get_major_scale(starting_index+6)
|
||||
if quality is None:
|
||||
# major triad from selected note
|
||||
for i in range(starting_index, starting_index + 5,2):
|
||||
triad.append(major_notes[i])
|
||||
else:
|
||||
# major triad from quality, note may not be in major scale
|
||||
if quality not in triad_intervals:
|
||||
raise ValueError(f"Invalid quality: {quality}")
|
||||
current_note_code = major_notes[starting_index].code
|
||||
for interval in triad_intervals[quality]:
|
||||
current_note_code += interval
|
||||
triad.append(Note.from_int(current_note_code, self.root.is_sharp))
|
||||
return triad
|
||||
|
||||
def get_quality(self) -> str:
|
||||
"""
|
||||
Get the quality of the chord.
|
||||
Returns:
|
||||
str, the quality of the chord, empty string if not built yet
|
||||
"""
|
||||
intervals=tuple(self.get_intervals())
|
||||
if intervals not in triad_intervals.keys():
|
||||
return "Unknown chord"
|
||||
return triad_intervals[intervals]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.keys[0].get_name()}{self.get_quality()} triad under major scale {self.root.get_name()}"
|
||||
|
||||
|
||||
class SevenChord(Chord):
|
||||
def __init__(self, root:Note, keys:list[Note]=None, zero_indexed:bool=False):
|
||||
if len(keys) != 4:
|
||||
raise ValueError("Seven chord must have 4 keys")
|
||||
super().__init__(root, keys, zero_indexed)
|
||||
|
||||
@classmethod
|
||||
def build_seven_chord(self, root:Note, starting_index:int=1, quality:str=None) -> 'SevenChord':
|
||||
"""
|
||||
Build a seven chord object.
|
||||
Args:
|
||||
root: str, the root note of the chord
|
||||
starting_index: int, the starting note index of the chord, default is 1
|
||||
quality: str, the quality of the chord, default is None
|
||||
"""
|
||||
keys = self.get_seven_chord(self, starting_index, quality)
|
||||
return SevenChord(root, keys, self.zero_indexed)
|
||||
|
||||
def get_seven_chord(self, starting_index:int, quality:str=None) -> list[Note]:
|
||||
"""
|
||||
Get a seven chord from the major scale.
|
||||
Args:
|
||||
starting_index: int, the starting index of the chord, default is 1, assume is positive indexed
|
||||
quality: str, the quality of the chord ["dominant", "diminished", "half-diminished", "augmented major", "minor-major", "major", "full-diminished"], default is None
|
||||
Returns:
|
||||
list[Note], the seven chord
|
||||
"""
|
||||
print(type(self))
|
||||
if not self.zero_indexed:
|
||||
starting_index = starting_index - 1
|
||||
major_notes = self.major.get_major_scale(starting_index+8)
|
||||
seven_chord = []
|
||||
if quality is None:
|
||||
# major seven chord from selected note
|
||||
for i in range(starting_index, starting_index + 8,2):
|
||||
seven_chord.append(major_notes[i])
|
||||
else:
|
||||
# major seven chord from quality, note may not be in major scale
|
||||
if quality not in seven_chord_intervals:
|
||||
raise ValueError(f"Invalid quality: {quality}")
|
||||
current_note_code = major_notes[starting_index].code
|
||||
for interval in seven_chord_intervals[quality]:
|
||||
current_note_code += interval
|
||||
seven_chord.append(Note.from_int(current_note_code, self.root.is_sharp))
|
||||
return seven_chord
|
||||
|
||||
def get_quality(self, name_type:str="shortest") -> str:
|
||||
"""
|
||||
Get the quality of the chord.
|
||||
Args:
|
||||
name_type: str, the type of the name, default is "shortest", can be "short" or "full"
|
||||
Returns:
|
||||
str, the quality of the chord, empty string if not built yet
|
||||
"""
|
||||
intervals=tuple(self.get_intervals())
|
||||
if intervals not in intervals_seven_chord.keys():
|
||||
return "Unknown chord"
|
||||
if name_type == "shortest":
|
||||
return f'{self.keys[0].get_name()}{intervals_seven_chord[intervals][0]} '
|
||||
elif name_type == "short":
|
||||
return f'{self.keys[0].get_name()}{intervals_seven_chord[intervals][1]} '
|
||||
elif name_type == "full":
|
||||
return f'{self.keys[0].get_name()}{intervals_seven_chord[intervals][2]} '
|
||||
else:
|
||||
raise ValueError(f"Invalid name type: {name_type}")
|
||||
|
||||
def is_primary_dominant(self) -> bool:
|
||||
"""
|
||||
Check if the chord is a primary dominant chord.
|
||||
Returns:
|
||||
bool, True if the chord is a primary dominant chord (I, IV, V), False otherwise
|
||||
"""
|
||||
intervals=tuple(self.get_intervals())
|
||||
if intervals not in intervals_seven_chord.keys():
|
||||
return False
|
||||
if self.keys[0]-self.root==0 and intervals==seven_chord_intervals["major"]:
|
||||
return True
|
||||
elif self.keys[0]-self.root==5 and intervals==seven_chord_intervals["major"]:
|
||||
return True
|
||||
elif self.keys[0]-self.root==7 and intervals==seven_chord_intervals["dominant"]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def is_secondary_dominant(self) -> bool:
|
||||
"""
|
||||
Check if the chord is a secondary dominant chord.
|
||||
Returns:
|
||||
bool, True if the chord is a secondary chord (ii, iii, vi, vii*), False otherwise
|
||||
"""
|
||||
intervals=tuple(self.get_intervals())
|
||||
if intervals not in intervals_seven_chord.keys():
|
||||
return False
|
||||
if self.keys[0]-self.root==2 and intervals==seven_chord_intervals["minor"]:
|
||||
return True
|
||||
elif self.keys[0]-self.root==4 and intervals==seven_chord_intervals["minor"]:
|
||||
return True
|
||||
elif self.keys[0]-self.root==7 and intervals==seven_chord_intervals["minor"]:
|
||||
return True
|
||||
elif self.keys[0]-self.root==9 and intervals==seven_chord_intervals["diminished"]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_secondary_dominant(self) -> 'SevenChord':
|
||||
"""
|
||||
Get the secondary dominant chord.
|
||||
Returns:
|
||||
Chord, the secondary dominant chord
|
||||
"""
|
||||
if not((self.is_secondary_dominant() or self.is_primary_dominant()) and self.get_intervals()!=seven_chord_intervals["diminished"]):
|
||||
raise ValueError("Chord is not primary or secondary dominant")
|
||||
root_note = self.keys[0]-2
|
||||
return SevenChord.build_seven_chord(root_note, starting_index=8, quality='dominant')
|
||||
|
||||
def tritone_substitution(self) -> 'SevenChord':
|
||||
"""
|
||||
Get the tritone substitution of the chord.
|
||||
Returns:
|
||||
Chord, the tritone substitution chord
|
||||
"""
|
||||
if self.get_quality()!="dominant":
|
||||
raise ValueError("Chord is not dominant")
|
||||
root_note = self.keys[2]-4
|
||||
return SevenChord.build_seven_chord(root_note, starting_index=8, quality='dominant')
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.keys[0].get_name()}{self.get_quality()} seven chord under major scale {self.root.get_name()}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
words=""
|
||||
while words != "quit":
|
||||
words = input("Enter a root note for chord: ")
|
||||
chord = SevenChord.build_seven_chord(Note(words), quality='dominant')
|
||||
print([note.get_name() for note in chord.keys])
|
||||
54
main.py
Normal file
54
main.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# minimal counting function used for review
|
||||
|
||||
chromatic_scale = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#']
|
||||
note_value = {e:i for i, e in enumerate(chromatic_scale)}
|
||||
seven_chord_interval_name = {
|
||||
'M7#5':(4,4,3),
|
||||
'M7':(4,3,4),
|
||||
'7':(4,3,3),
|
||||
'mM7':(3,4,4),
|
||||
'm7':(3,4,3),
|
||||
'm7b5':(3,3,4),
|
||||
'dim7':(3,3,3),
|
||||
}
|
||||
major_scale_intervals = [2,2,1,2,2,2,1]
|
||||
|
||||
def get_major_scale(root):
|
||||
scale = []
|
||||
scale.append(root)
|
||||
root_index = note_value[root]
|
||||
for interval in major_scale_intervals:
|
||||
root_index = (root_index + interval) % 12
|
||||
scale.append(chromatic_scale[root_index])
|
||||
return scale
|
||||
|
||||
def get_number_of_half_steps(note1, note2):
|
||||
return min(abs(note_value[note1] - note_value[note2]), 12 - abs(note_value[note1] - note_value[note2]))
|
||||
|
||||
def _construct_chord(root, interval_type=(4,3,3)):
|
||||
chord = []
|
||||
chord.append(root)
|
||||
root_index = note_value[root]
|
||||
for interval in interval_type:
|
||||
root_index = (root_index + interval) % 12
|
||||
chord.append(chromatic_scale[root_index])
|
||||
return chord
|
||||
|
||||
words=""
|
||||
|
||||
while words != "quit":
|
||||
words = input("Enter a note or chord: ")
|
||||
notes = words.split(" ")
|
||||
if notes[0][1:] in seven_chord_interval_name.keys():
|
||||
# construct dominant 7 chord
|
||||
chord = _construct_chord(notes[0][0],seven_chord_interval_name[notes[0][1:]])
|
||||
print(chord,get_major_scale(notes[0][0]))
|
||||
else:
|
||||
# calculate the interval between notes
|
||||
res=[]
|
||||
for i in range(len(notes)-1):
|
||||
if notes[i] not in note_value or notes[i+1] not in note_value:
|
||||
print("Invalid note")
|
||||
break
|
||||
res.append(get_number_of_half_steps(notes[i], notes[i+1]))
|
||||
print(res)
|
||||
183
major.py
Normal file
183
major.py
Normal file
@@ -0,0 +1,183 @@
|
||||
chromatic=[('A','A'),
|
||||
('A#','Bb'),
|
||||
('B','B'),
|
||||
('C','C'),
|
||||
('C#','Db'),
|
||||
('D','D'),
|
||||
('D#','Eb'),
|
||||
('E','E'),
|
||||
('F','F'),
|
||||
('F#','Gb'),
|
||||
('G','G'),
|
||||
('G#','Ab')]
|
||||
chromatic_notes_set=set(note for pair in chromatic for note in pair)
|
||||
major_scale_intervals = [2,2,1,2,2,2,1]
|
||||
natural_minor_scale_intervals = [2,1,2,2,1,2,2]
|
||||
harmonic_minor_scale_intervals = [2,1,2,2,1,3,1]
|
||||
melodic_minor_scale_intervals = [2,1,2,2,2,2,1]
|
||||
# use A4 as 0th note, note octave number change from B to C.
|
||||
a4_freq = 440
|
||||
|
||||
class Note:
|
||||
def __init__(self, note:str, octave:int=4):
|
||||
if note not in chromatic_notes_set:
|
||||
raise ValueError(f"Invalid note: {note}, must be in {chromatic_notes_set}")
|
||||
self.is_sharp = 'b' not in note
|
||||
self.position = next(i for i, v in enumerate(chromatic) if note in v)
|
||||
self.octave_position = self.position
|
||||
# if octave_position is greater than or equal to 3 (C), subtract 12
|
||||
if self.octave_position >= 3:
|
||||
self.octave_position -= 12
|
||||
self.code = 12 * (octave - 3) + self.octave_position
|
||||
|
||||
@classmethod
|
||||
def from_int(self, code:int, is_sharp:bool=True) -> 'Note':
|
||||
note = chromatic[code % len(chromatic)][0] if is_sharp else chromatic[code % len(chromatic)][1]
|
||||
return Note(note, code // 12 + 4)
|
||||
|
||||
def whole_step(self, invert:bool=False) -> 'Note':
|
||||
return self.from_int(self.code + 2) if not invert else self.from_int(self.code - 2)
|
||||
|
||||
def half_step(self, invert:bool=False) -> 'Note':
|
||||
return self.from_int(self.code + 1) if not invert else self.from_int(self.code - 1)
|
||||
|
||||
def __add__(self, other) -> 'Note' or int:
|
||||
"""
|
||||
Add a note or an integer to a note.
|
||||
Args:
|
||||
other: Note or int, the note or integer to add
|
||||
Returns:
|
||||
Note, the result of the addition
|
||||
"""
|
||||
if isinstance(other, Note):
|
||||
return self.from_int(self.code + other.code)
|
||||
elif isinstance(other, int):
|
||||
return self.from_int(self.code + other)
|
||||
else:
|
||||
raise ValueError(f"Instance of {type(other)} is not supported")
|
||||
|
||||
def __sub__(self, other) -> 'Note' or int:
|
||||
"""
|
||||
Subtract a note or an integer from a note.
|
||||
Args:
|
||||
other: Note or int, the note or integer to subtract
|
||||
Returns:
|
||||
Note, the result of the subtraction
|
||||
int, the result of the subtraction if other is an integer
|
||||
"""
|
||||
if isinstance(other, Note):
|
||||
return self.from_int(self.code - other.code)
|
||||
elif isinstance(other, int):
|
||||
return self.code - other
|
||||
else:
|
||||
raise ValueError(f"Instance of {type(other)} is not supported")
|
||||
|
||||
def __eq__(self, other: 'Note') -> bool:
|
||||
return self.code == other.code
|
||||
|
||||
def enharmonic_equivalent(self) -> 'Note':
|
||||
return chromatic[self.position%12][1 if self.is_sharp else 0]
|
||||
|
||||
def get_frequency(self) -> float:
|
||||
"""
|
||||
Get the frequency of a note.
|
||||
Returns:
|
||||
float, the frequency of the note
|
||||
"""
|
||||
return a4_freq * 2 ** ((self.code) / 12)
|
||||
|
||||
def get_name(self) -> str:
|
||||
return chromatic[self.position][0] if self.is_sharp else chromatic[self.position][1]
|
||||
|
||||
def get_octave(self) -> int:
|
||||
"""
|
||||
Get the octave of a note.
|
||||
A4 is 0, A#4 is 1, B4 is 2, C5 is 3 (change octave), etc.
|
||||
Returns:
|
||||
int, the octave of the note
|
||||
"""
|
||||
return (self.code - self.octave_position) // 12 + 4
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.get_name()}{self.get_octave()}"
|
||||
|
||||
class MajorScale:
|
||||
def __init__(self, root:Note):
|
||||
if not isinstance(root, Note):
|
||||
raise ValueError(f"Root must be a Note object, got {type(root)}")
|
||||
self.root = root
|
||||
|
||||
@classmethod
|
||||
def from_note_int(self, root_code:int, is_sharp:bool=True) -> 'MajorScale':
|
||||
return MajorScale(Note.from_int(root_code, is_sharp))
|
||||
|
||||
@classmethod
|
||||
def from_note_name(self, root_name:str, octave:int=4) -> 'MajorScale':
|
||||
return MajorScale(Note(root_name, octave))
|
||||
|
||||
def is_in_scale(self, list_of_notes:list[Note]) -> bool:
|
||||
"""
|
||||
Check if a list of notes is in the major scale.
|
||||
Args:
|
||||
list_of_notes: list[Note], the list of notes to check
|
||||
Returns:
|
||||
bool, True if the list of notes is in the major scale, False otherwise
|
||||
"""
|
||||
for note in list_of_notes:
|
||||
if note.code not in [note.code for note in self.get_major_scale()]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_major_scale(self, length:int=7) -> list[Note]:
|
||||
scale = []
|
||||
scale.append(self.root)
|
||||
cur_note_pos = self.root.code
|
||||
interval_index = 0
|
||||
for _ in range(length):
|
||||
cur_note_pos += major_scale_intervals[interval_index]
|
||||
# keep sharp notation
|
||||
scale.append(Note.from_int(cur_note_pos, self.root.is_sharp))
|
||||
interval_index = (interval_index + 1) % len(major_scale_intervals)
|
||||
return scale
|
||||
|
||||
def get_minor_scale(self, type:str='natural', length:int=7) -> list[Note]:
|
||||
scale = []
|
||||
scale.append(self.root)
|
||||
cur_note_pos = self.root.code
|
||||
minor_scale_intervals=natural_minor_scale_intervals
|
||||
if type!='natural':
|
||||
if type=='harmonic':
|
||||
minor_scale_intervals=harmonic_minor_scale_intervals
|
||||
elif type=='melodic':
|
||||
minor_scale_intervals=melodic_minor_scale_intervals
|
||||
else:
|
||||
raise ValueError("Invalid minor scale type. Choose 'natural', 'harmonic', or 'melodic'.")
|
||||
interval_index = 0
|
||||
for _ in range(length):
|
||||
cur_note_pos += minor_scale_intervals[interval_index]
|
||||
# keep sharp notation
|
||||
scale.append(Note.from_int(cur_note_pos, self.root.is_sharp))
|
||||
interval_index = (interval_index + 1) % len(minor_scale_intervals)
|
||||
return scale
|
||||
|
||||
def get_parallel_minor(self) -> 'MajorScale':
|
||||
"""
|
||||
Get the parallel minor scale.
|
||||
Returns:
|
||||
MajorScale, the parallel minor scale
|
||||
"""
|
||||
return MajorScale(self.root+5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
a=input("Enter a root note for major scale: ")
|
||||
while a != "quit":
|
||||
if a not in chromatic_notes_set:
|
||||
a = input("Invalid note. Enter a root note for major scale: ")
|
||||
continue
|
||||
major_scale = MajorScale.from_note_name(a)
|
||||
print(f"Major scale for {a}: {[note.get_name() for note in major_scale.get_major_scale()]}")
|
||||
print(f"Natural minor scale for {a}: {[note.get_name() for note in major_scale.get_minor_scale('natural')]}")
|
||||
print(f"Harmonic minor scale for {a}: {[note.get_name() for note in major_scale.get_minor_scale('harmonic')]}")
|
||||
print(f"Melodic minor scale for {a}: {[note.get_name() for note in major_scale.get_minor_scale('melodic')]}")
|
||||
a=input("Enter a root note for major scale: ")
|
||||
22
test/major_test.py
Normal file
22
test/major_test.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import major
|
||||
|
||||
import unittest
|
||||
|
||||
class MajorScaleTest(unittest.TestCase):
|
||||
def test_from_note_int(self):
|
||||
scale = major.MajorScale.from_note_int(0)
|
||||
self.assertEqual(scale.root, "A")
|
||||
self.assertEqual(scale.octave, 4)
|
||||
self.assertEqual(scale.scale, ["A", "B", "C#", "D", "E", "F#", "G#"])
|
||||
|
||||
def test_get_major_scale(self):
|
||||
scale = major.MajorScale.from_note_int(0)
|
||||
self.assertEqual(scale.get_major_scale(), ["A", "B", "C#", "D", "E", "F#", "G#"])
|
||||
|
||||
def test_get_minor_scale(self):
|
||||
scale = major.MajorScale.from_note_int(0)
|
||||
self.assertEqual(scale.get_minor_scale(), ["A", "B", "C#", "D", "E", "F#", "G#"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user