atonatui/tonerow.py
2026-08-09 09:12:15 -04:00

269 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()