270 lines
7.6 KiB
Python
270 lines
7.6 KiB
Python
import random
|
|
from collections import defaultdict
|
|
from functools import cached_property
|
|
|
|
from constants import NOTES, RESET, REVERSE, RED, MAGENTA, GREEN, YELLOW, BOLD, WHITE
|
|
from audio_mixin import AudioMixin
|
|
from display_mixin import DisplayMixin
|
|
from settings_mixin import Settings, SettingsMixin
|
|
from input_mixin import InputMixin
|
|
|
|
|
|
class ToneRow(AudioMixin, DisplayMixin, SettingsMixin, InputMixin):
|
|
def __init__(self):
|
|
AudioMixin.__init__(self)
|
|
self.bpm = Settings.BPM
|
|
|
|
self.pr_map = {}
|
|
self.pr_map_populated = False
|
|
|
|
self.inv_map = {}
|
|
self.inv_map_populated = False
|
|
|
|
self.current_seed = None
|
|
self.use_current_seed = False
|
|
self.current_rhythm_seed = None
|
|
self.use_current_rhythm_seed = False
|
|
self.current_octave_seed = None
|
|
self.use_current_octave_seed = False
|
|
|
|
self.rhythm_to_divison_map = {
|
|
0: self.whole,
|
|
1: self.half,
|
|
2: self.quarter,
|
|
3: self.eigth
|
|
}
|
|
|
|
self.generate()
|
|
self.load_settings()
|
|
|
|
|
|
@cached_property
|
|
def octaves(self):
|
|
return [str(Settings.OCTAVE)] * 12
|
|
|
|
@property
|
|
def eigth(self):
|
|
return self.quarter / 2
|
|
|
|
@property
|
|
def quarter(self):
|
|
return 60.0 / self.bpm
|
|
|
|
@property
|
|
def half(self):
|
|
return self.quarter * 2
|
|
|
|
@property
|
|
def whole(self):
|
|
return self.half * 2
|
|
|
|
@cached_property
|
|
def tone_row(self):
|
|
if not self.use_current_seed:
|
|
self.current_seed = random.randint(100000, 999999)
|
|
else:
|
|
self.use_current_seed = False
|
|
|
|
random.seed(self.current_seed)
|
|
|
|
notes = NOTES.copy()
|
|
random.shuffle(notes)
|
|
|
|
random.seed(None)
|
|
return notes
|
|
|
|
@cached_property
|
|
def reordered_notes(self):
|
|
first_note_idx = NOTES.index(self.tone_row[0])
|
|
notes = NOTES.copy()
|
|
return notes[first_note_idx:] + notes[0:first_note_idx]
|
|
|
|
@cached_property
|
|
def prime(self):
|
|
prime = []
|
|
for tone in self.tone_row:
|
|
prime.append(self.reordered_notes.index(tone))
|
|
return prime
|
|
|
|
@cached_property
|
|
def inversion(self):
|
|
inversion = []
|
|
for note in self.prime:
|
|
inversion.append((2 * self.prime[0] - note) % 12)
|
|
return inversion
|
|
|
|
@cached_property
|
|
def matrix(self):
|
|
matrix = []
|
|
|
|
for shift in self.inversion:
|
|
row = []
|
|
for item in self.prime:
|
|
x = (item + shift) % 12
|
|
row.append(x)
|
|
matrix.append(row)
|
|
|
|
return matrix
|
|
|
|
@cached_property
|
|
def string_matrix(self):
|
|
smatrix = []
|
|
for row in self.matrix:
|
|
new_row = []
|
|
for tone in row:
|
|
new_row.append(self.reordered_notes[tone])
|
|
smatrix.append(new_row)
|
|
return smatrix
|
|
|
|
@cached_property
|
|
def rhythm_row(self):
|
|
if not self.use_current_rhythm_seed:
|
|
self.current_rhythm_seed = random.randint(100000, 999999)
|
|
else:
|
|
self.use_current_rhythm_seed = False
|
|
|
|
random.seed(self.current_rhythm_seed)
|
|
roptions = [0, 1, 2, 3]
|
|
random.shuffle(roptions)
|
|
random.seed(None)
|
|
return roptions * 3
|
|
|
|
@cached_property
|
|
def rhythm_map(self):
|
|
prime = self.string_matrix[0]
|
|
rmap = defaultdict(int)
|
|
|
|
for r, t in zip(self.rhythm_row, prime):
|
|
rmap[t] = r
|
|
|
|
return rmap
|
|
|
|
@cached_property
|
|
def octave_row(self):
|
|
if not self.use_current_octave_seed:
|
|
self.current_octave_seed = random.randint(100000, 999999)
|
|
else:
|
|
self.use_current_octave_seed = False
|
|
|
|
random.seed(self.current_octave_seed)
|
|
|
|
octave_options = [Settings.OCTAVE]
|
|
|
|
for i in range(Settings.OCTAVE_RANDOMIZATION):
|
|
positive = Settings.OCTAVE + (i + 1)
|
|
if positive > 5:
|
|
positive = 5
|
|
if positive not in octave_options:
|
|
octave_options.append(positive)
|
|
|
|
negative = Settings.OCTAVE - (i + 1)
|
|
if negative < 1:
|
|
negative = 1
|
|
if negative not in octave_options:
|
|
octave_options.append(negative)
|
|
|
|
random.shuffle(octave_options)
|
|
|
|
octave_row = []
|
|
|
|
left = 0
|
|
while len(octave_row) < 12:
|
|
octave_row.append(octave_options[left])
|
|
left += 1
|
|
if left > len(octave_options) - 1:
|
|
left = 0
|
|
|
|
random.seed(None)
|
|
return octave_row
|
|
|
|
@cached_property
|
|
def octave_map(self):
|
|
prime = self.string_matrix[0]
|
|
omap = defaultdict(int)
|
|
|
|
for o, t in zip(self.octave_row, prime):
|
|
omap[t] = o
|
|
|
|
return omap
|
|
|
|
def regenerate(self):
|
|
del self.tone_row
|
|
del self.reordered_notes
|
|
del self.prime
|
|
del self.inversion
|
|
del self.matrix
|
|
del self.string_matrix
|
|
del self.octaves
|
|
del self.rhythm_row
|
|
del self.rhythm_map
|
|
del self.octave_row
|
|
del self.octave_map
|
|
self.pr_map_populated = False
|
|
self.inv_map_populated = False
|
|
self.generate()
|
|
|
|
def generate(self):
|
|
_ = self.tone_row
|
|
_ = self.reordered_notes
|
|
_ = self.prime
|
|
_ = self.inversion
|
|
_ = self.matrix
|
|
_ = self.string_matrix
|
|
_ = self.octaves
|
|
_ = self.rhythm_row
|
|
_ = self.rhythm_map
|
|
_ = self.octave_row
|
|
_ = self.octave_map
|
|
|
|
def play_row(self, row):
|
|
division_map = {
|
|
3: self.eigth,
|
|
2: self.quarter,
|
|
1: self.half,
|
|
0: self.whole
|
|
}
|
|
|
|
division = division_map[Settings.DEFAULT_DIVISION]
|
|
|
|
for idx, note in enumerate(row):
|
|
padded_tone_row = [f'{BOLD}{REVERSE}{MAGENTA}TONE']
|
|
padded_rhythm_row = [f'{BOLD}{REVERSE}{YELLOW}RTHM']
|
|
padded_octave_row = [f'{BOLD}{REVERSE}{GREEN}OCTV']
|
|
|
|
for idx2, x in enumerate(row):
|
|
if idx == idx2:
|
|
padded_tone_row.append(f"{REVERSE}{WHITE}{x.ljust(3)}{RESET}{REVERSE}{MAGENTA}{BOLD}")
|
|
padded_rhythm_row.append(f"{REVERSE}{WHITE}{str(self.rhythm_map[x]).ljust(3)}{RESET}{REVERSE}{YELLOW}{BOLD}")
|
|
padded_octave_row.append(f"{REVERSE}{WHITE}{str(self.octave_map[x]).ljust(3)}{RESET}{REVERSE}{GREEN}{BOLD}")
|
|
else:
|
|
padded_tone_row.append(x.ljust(3))
|
|
padded_rhythm_row.append(str(self.rhythm_map[x]).ljust(3))
|
|
padded_octave_row.append(str(self.octave_map[x]).ljust(3))
|
|
|
|
self.clear_screen()
|
|
self.display(play_mode=True)
|
|
print(" | ".join(padded_tone_row) + f" {RESET}")
|
|
print(" | ".join(padded_rhythm_row) + f" {RESET}")
|
|
print(" | ".join(padded_octave_row) + f" {RESET}")
|
|
|
|
if Settings.RHYTHM_COMPLEXITY == 0:
|
|
self.play(note + str(self.octave_map[note]), division)
|
|
|
|
if Settings.RHYTHM_COMPLEXITY in [1, 2]:
|
|
rest = False
|
|
if Settings.RHYTHM_COMPLEXITY == 2:
|
|
dice_roll = random.randint(1, 100)
|
|
if dice_roll <= Settings.REST_PROBABILITY:
|
|
rest = True
|
|
|
|
division = self.rhythm_to_divison_map[self.rhythm_map[note]]
|
|
self.play(note + str(self.octave_map[note]), division, rest=rest)
|
|
|
|
if Settings.RHYTHM_COMPLEXITY == 3:
|
|
division = random.uniform(0.05, 1)
|
|
self.play(note + str(self.octave_map[note]), division)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
t = ToneRow()
|
|
t.loop()
|