packaging
This commit is contained in:
parent
d2b54ae907
commit
51d53c38ab
31 changed files with 12 additions and 761 deletions
2
PKGBUILD
2
PKGBUILD
|
|
@ -1,7 +1,7 @@
|
|||
# Maintainer: Dominic DiTaranto <me@domdit.com>
|
||||
pkgname=atonatui
|
||||
_name=atonatui
|
||||
pkgver=0.1.1
|
||||
pkgver=0.1.2
|
||||
pkgrel=1
|
||||
pkgdesc="12 Tone Matrix Generator, Looper, Sequencer."
|
||||
arch=('any')
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -12,6 +12,9 @@ Features:
|
|||
|
||||
## Installation Guide
|
||||
|
||||
PIP: `pipx install atonatui`
|
||||
ARCH: `yay -S python-atonatui`
|
||||
|
||||
## Main Page
|
||||
|
||||
The main page consists of a 12 Tone Matrix.
|
||||
|
|
@ -81,3 +84,10 @@ __Advanced Commands:__
|
|||
- Save the current tone matrix to a file, you can manually load this later
|
||||
- LOAD_MATRIX
|
||||
- Load a previously saved tone matrix
|
||||
|
||||
# Upcoming Features
|
||||
- Support independent generation, saving, and loading for rhythm and octave rows
|
||||
- Support loading of custom soundfonts
|
||||
- Support custom input of Tone Row + Rhythm and Octave rows
|
||||
- Output rows to midi
|
||||
- Up arrow functionality so users can rerun commands easily
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
{"BPM": 150, "INSTRUMENT": "Xylophone", "OCTAVE": 4, "OCTAVE_RANDOMIZATION": 0, "RHYTHM_COMPLEXITY": 0, "DEFAULT_DIVISION": 3, "REST_PROBABILITY": 0, "SAVE_SETTINGS": null, "SAVE_MATRIX": "oops", "LOAD_MATRIX": null}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import time
|
||||
import os
|
||||
|
||||
import fluidsynth
|
||||
|
||||
from constants import NOTE_TO_MIDI, GENERAL_MIDI_PROGRAMS, SOUNDFONTS
|
||||
from settings_mixin import Settings
|
||||
|
||||
|
||||
class AudioMixin:
|
||||
def __init__(self):
|
||||
self.fs = fluidsynth.Synth()
|
||||
self.fs.start(driver="alsa" if os.name != "nt" else "dsound")
|
||||
|
||||
self.sf_id = None
|
||||
self.update_sf(SOUNDFONTS[Settings.INSTRUMENT])
|
||||
self.clear_screen()
|
||||
|
||||
def update_sf(self, path):
|
||||
if self.sf_id is not None and self.sf_id != -1:
|
||||
self.fs.sfunload(self.sf_id)
|
||||
sf2_path = os.path.join(os.path.dirname(__file__), "assets/audio", path)
|
||||
|
||||
self.sf_id = self.fs.sfload(sf2_path)
|
||||
self.fs.program_select(0, self.sf_id, 0, GENERAL_MIDI_PROGRAMS[Settings.INSTRUMENT])
|
||||
|
||||
def play(self, note_name, duration, rest=False):
|
||||
if note_name not in NOTE_TO_MIDI:
|
||||
return
|
||||
|
||||
midi_num = NOTE_TO_MIDI[note_name]
|
||||
channel = 0
|
||||
velocity = 100
|
||||
|
||||
if not rest:
|
||||
self.fs.noteon(channel, midi_num, velocity)
|
||||
|
||||
time.sleep(duration)
|
||||
|
||||
if not rest:
|
||||
self.fs.noteoff(channel, midi_num)
|
||||
|
||||
def close(self):
|
||||
self.fs.delete()
|
||||
86
constants.py
86
constants.py
|
|
@ -1,86 +0,0 @@
|
|||
BPM = 180
|
||||
QUARTER = 60.0 / BPM
|
||||
HALF = QUARTER * 2
|
||||
|
||||
NOTE_TO_FREQ = {
|
||||
"C1": 32.70, "C#1": 34.65, "D1": 36.71, "D#1": 38.89, "E1": 41.20, "F1": 43.65,
|
||||
"F#1": 46.25, "G1": 49.00, "G#1": 51.91, "A1": 55.00, "A#1": 58.27, "B1": 61.74,
|
||||
"C2": 65.41, "C#2": 69.30, "D2": 73.42, "D#2": 77.78, "E2": 82.41, "F2": 87.31,
|
||||
"F#2": 92.50, "G2": 98.00, "G#2": 103.83, "A2": 110.00, "A#2": 116.54, "B2": 123.47,
|
||||
"C3": 130.81, "C#3": 138.59, "D3": 146.83, "D#3": 155.56, "E3": 164.81, "F3": 174.61,
|
||||
"F#3": 185.00, "G3": 196.00, "G#3": 207.65, "A3": 220.00, "A#3": 233.08, "B3": 246.94,
|
||||
"C4": 261.63, "C#4": 277.18, "D4": 293.66, "D#4": 311.13, "E4": 329.63, "F4": 349.23,
|
||||
"F#4": 369.99, "G4": 392.00, "G#4": 415.30, "A4": 440.00, "A#4": 466.16, "B4": 493.88,
|
||||
"C5": 523.25, "C#5": 554.37, "D5": 587.33, "D#5": 622.25, "E5": 659.25, "F5": 698.46,
|
||||
"F#5": 739.99, "G5": 783.99, "G#5": 830.61, "A5": 880.00, "A#5": 932.33, "B5": 987.77
|
||||
}
|
||||
|
||||
NOTE_TO_MIDI = {
|
||||
"C1": 24, "C#1": 25, "D1": 26, "D#1": 27, "E1": 28, "F1": 29, "F#1": 30, "G1": 31, "G#1": 32, "A1": 33, "A#1": 34, "B1": 35,
|
||||
"C2": 36, "C#2": 37, "D2": 38, "D#2": 39, "E2": 40, "F2": 41, "F#2": 42, "G2": 43, "G#2": 44, "A2": 45, "A#2": 46, "B2": 47,
|
||||
"C3": 48, "C#3": 49, "D3": 50, "D#3": 51, "E3": 52, "F3": 53, "F#3": 54, "G3": 55, "G#3": 56, "A3": 57, "A#3": 58, "B3": 59,
|
||||
"C4": 60, "C#4": 61, "D4": 62, "D#4": 63, "E4": 64, "F4": 65, "F#4": 66, "G4": 67, "G#4": 68, "A4": 69, "A#4": 70, "B4": 71,
|
||||
"C5": 72, "C#5": 73, "D5": 74, "D#5": 75, "E5": 76, "F5": 77, "F#5": 78, "G5": 79, "G#5": 80, "A5": 81, "A#5": 82, "B5": 83
|
||||
}
|
||||
|
||||
NOTES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
|
||||
|
||||
|
||||
SOUNDFONTS = {
|
||||
'Bassoon': 'SJO - Bassoon.sf2',
|
||||
'Cello': 'SJO - Cello.sf2',
|
||||
'Choir Aahs': 'SJO - Choir Aahs.sf2',
|
||||
'Clarinet': 'SJO - Clarinet.sf2',
|
||||
'Double Bass': 'SJO - Double Bass.sf2',
|
||||
'English Horn': 'SJO - English Horn.sf2',
|
||||
'Flute': 'SJO - Flute.sf2',
|
||||
'French Horn': 'SJO - French Horn.sf2',
|
||||
'Glockenspiel': 'SJO - Glockenspiel.sf2',
|
||||
'Harp': 'SJO - Harp.sf2',
|
||||
'Marimbas': 'SJO - Marimbas.sf2',
|
||||
'Strings slow': 'SJO - Strings slow.sf2',
|
||||
'Strings': 'SJO - Strings.sf2',
|
||||
'Timpani': 'SJO - Timpani.sf2',
|
||||
'Trombone': 'SJO - Trombone.sf2',
|
||||
'Trumpet - Velocity fix': 'SJO - Trumpet - Velocity fix.sf2',
|
||||
'Tuba': 'SJO - Tuba.sf2',
|
||||
'Vibes': 'SJO - Vibes.sf2',
|
||||
'Viola': 'SJO - Viola.sf2',
|
||||
'Xylophone': 'SJO - Xylophone.sf2'
|
||||
}
|
||||
|
||||
GENERAL_MIDI_PROGRAMS = {
|
||||
'Glockenspiel': 9,
|
||||
'Vibes': 11,
|
||||
'Marimbas': 12,
|
||||
'Xylophone': 13,
|
||||
'Harp': 46,
|
||||
'Timpani': 47,
|
||||
'Strings': 48,
|
||||
'Strings slow': 49,
|
||||
'Viola': 41,
|
||||
'Cello': 42,
|
||||
'Double Bass': 43,
|
||||
'Choir Aahs': 52,
|
||||
'Trumpet - Velocity fix': 56,
|
||||
'Trombone': 57,
|
||||
'Tuba': 58,
|
||||
'French Horn': 60,
|
||||
'Flute': 73,
|
||||
'English Horn': 69,
|
||||
'Clarinet': 71,
|
||||
'Bassoon': 70
|
||||
}
|
||||
|
||||
|
||||
RESET = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
REVERSE = "\033[7m"
|
||||
BLACK = "\033[30m"
|
||||
RED = "\033[31m"
|
||||
GREEN = "\033[32m"
|
||||
YELLOW = "\033[33m"
|
||||
BLUE = "\033[34m"
|
||||
MAGENTA = "\033[35m"
|
||||
CYAN = "\033[36m"
|
||||
WHITE = "\033[37m"
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import sys
|
||||
import subprocess
|
||||
from time import sleep
|
||||
|
||||
from constants import RESET, REVERSE, BOLD, GREEN, RED, BLUE, YELLOW, MAGENTA
|
||||
from settings_mixin import Settings
|
||||
|
||||
|
||||
class DisplayMixin:
|
||||
|
||||
def message(self, msg, callback, sleep_time=1.5, type="ERROR"):
|
||||
self.clear_screen()
|
||||
|
||||
color = RED if type == "ERROR" else GREEN
|
||||
print(f"{REVERSE}{color}{msg}{RESET}")
|
||||
|
||||
sleep(sleep_time)
|
||||
|
||||
self.clear_screen()
|
||||
|
||||
callback()
|
||||
|
||||
def clear_screen(self):
|
||||
subprocess.run(['clear'])
|
||||
|
||||
def display(self, play_mode=False):
|
||||
print(f"{GREEN}============================== TWELVE TONE MATRIX ==============================={RESET}\n")
|
||||
|
||||
print(f"{YELLOW}{Settings.BPM} BPM - OCTAVE {Settings.OCTAVE} - {Settings.INSTRUMENT} {RESET}")
|
||||
print(f'{GREEN}{REVERSE} {BOLD}{" | ".join(f"I{i}".ljust(3) for i in self.inversion)} {RESET}')
|
||||
|
||||
for row, prime in zip(self.string_matrix, self.prime):
|
||||
padded_row = [note.ljust(3) for note in row]
|
||||
pr_label = self.reordered_notes.index(row[0])
|
||||
|
||||
if not self.pr_map_populated:
|
||||
self.pr_map[f"P{pr_label}"] = row
|
||||
|
||||
print(f'{BOLD}{GREEN}{REVERSE}{f"P{pr_label}".ljust(3)}{RESET} | {" | ".join(padded_row)}| {BOLD}{GREEN}{REVERSE}{f"R{pr_label}".ljust(3)}{RESET}')
|
||||
|
||||
self.pr_map_populated = True
|
||||
|
||||
if not self.inv_map_populated:
|
||||
for idx, i in enumerate(self.inversion):
|
||||
new_row = []
|
||||
for idx2, row in enumerate(self.matrix):
|
||||
new_row.append(self.reordered_notes[row[idx]])
|
||||
self.inv_map[f"I{i}"] = new_row
|
||||
self.inv_map_populated = True
|
||||
|
||||
print(f'{GREEN}{REVERSE} {BOLD}{" | ".join(f"i{i}".ljust(3) for i in self.inversion)} {RESET}\n')
|
||||
|
||||
if not play_mode:
|
||||
print(f"{MAGENTA}{BOLD}{REVERSE}TONE | {' | '.join([i.ljust(3) for i in self.tone_row])} {RESET}")
|
||||
print(f"{YELLOW}{BOLD}{REVERSE}RTHM | {' | '.join([str(x).ljust(3) for x in self.rhythm_row])} {RESET}")
|
||||
print(f"{GREEN}{BOLD}{REVERSE}OCTV | {' | '.join([str(x).ljust(3) for x in self.octave_row])} {RESET}")
|
||||
|
||||
def loop(self):
|
||||
self.clear_screen()
|
||||
self.display()
|
||||
|
||||
self.get_user_input()
|
||||
|
||||
self.loop()
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
import sys
|
||||
|
||||
from prompt_toolkit import prompt
|
||||
from prompt_toolkit.formatted_text import HTML
|
||||
|
||||
|
||||
class InputMixin:
|
||||
|
||||
def get_user_input(self):
|
||||
loop_count = 1
|
||||
|
||||
request = self.request(help_text='Type "help" for more options, "q" to quit')
|
||||
self.check_valid_request(request)
|
||||
|
||||
request = request.split(' ')
|
||||
|
||||
if "loop" in request:
|
||||
loop_count = self.parse_loop_request(request)
|
||||
|
||||
if "seq" in request:
|
||||
self.parse_seq_request(request, loop_count)
|
||||
|
||||
if "debug" in request:
|
||||
breakpoint()
|
||||
|
||||
if request[0] == "x" or request[0] == "regen":
|
||||
self.regenerate()
|
||||
self.loop()
|
||||
|
||||
elif request[0] == "q" or request[0] == "quit":
|
||||
self.close()
|
||||
sys.exit()
|
||||
|
||||
elif request[0] == "s" or request[0] == "settings":
|
||||
self.display_settings()
|
||||
|
||||
elif request[0][0] in ["P", "R", "I", "i"]:
|
||||
self.play_tone_row(request[0][0], request[0][1], loop=loop_count)
|
||||
|
||||
def request(self, help_text):
|
||||
return prompt(
|
||||
"\n>>> ", placeholder=HTML(f'<style fg="#888888">{help_text}</style>')
|
||||
)
|
||||
|
||||
def check_valid_request(self, request):
|
||||
if not request or request == "":
|
||||
self.loop()
|
||||
|
||||
def parse_loop_request(self, request):
|
||||
loop_index = request.index("loop")
|
||||
loop_count = request[loop_index + 1]
|
||||
|
||||
if not loop_count.isdigit():
|
||||
self.message("Not a valid loop count", self.loop)
|
||||
else:
|
||||
loop_count = int(loop_count)
|
||||
|
||||
request.pop(loop_index + 1)
|
||||
request.pop(loop_index)
|
||||
return loop_count
|
||||
|
||||
def parse_seq_request(self, request, loop_count):
|
||||
seq_index = request.index("seq")
|
||||
sequence = request[seq_index + 1].split(',')
|
||||
for seq in sequence:
|
||||
if seq[0] not in ["P", "R", "I", "i"] or int(seq[1:]) > 11:
|
||||
self.message(f"Invalid sequence at {seq}", self.loop)
|
||||
|
||||
for loop in range(loop_count):
|
||||
for seq in sequence:
|
||||
self.play_tone_row(seq[0], seq[1:], loop=1)
|
||||
|
||||
def play_tone_row(self, row_type, row_id, loop=1):
|
||||
reverse = True if row_type in ["R", "i"] else False
|
||||
|
||||
if row_id in [str(x) for x in range(0, 12)]:
|
||||
|
||||
if not reverse:
|
||||
if row_type == "I":
|
||||
target_row = self.inv_map[row_type + row_id]
|
||||
else:
|
||||
target_row = self.pr_map[row_type + row_id]
|
||||
else:
|
||||
row_type = "P" if row_type == "R" else "I"
|
||||
if row_type == "I":
|
||||
target_row = self.inv_map[row_type + row_id][::-1]
|
||||
else:
|
||||
target_row = self.pr_map[row_type + row_id][::-1]
|
||||
|
||||
for i in range(loop):
|
||||
self.play_row(target_row)
|
||||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "atonatui"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
description = "12 Tone Matrix Generator, Looper, Sequencer."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
|
|
|||
|
|
@ -1,204 +0,0 @@
|
|||
import os
|
||||
import json
|
||||
from functools import cached_property
|
||||
from dataclasses import dataclass, fields
|
||||
from constants import RESET, REVERSE, SOUNDFONTS
|
||||
|
||||
_INSTRUMENT_LIST = [f"[{str(idx).rjust(2)}] {name}" for idx, name in enumerate(SOUNDFONTS.keys())]
|
||||
|
||||
|
||||
_SETTINGS_SAVE_FILE = 'assets/settings.json'
|
||||
|
||||
_MATRIX_SAVE_DIR = os.path.join(os.path.dirname(__file__), "assets/saves")
|
||||
|
||||
|
||||
def get_saved_matrix_list():
|
||||
if not os.path.exists(_MATRIX_SAVE_DIR):
|
||||
return [" [No saved matrices found]"]
|
||||
|
||||
save_files = [
|
||||
f for f in os.listdir(_MATRIX_SAVE_DIR)
|
||||
if os.path.isfile(os.path.join(_MATRIX_SAVE_DIR, f)) and f.endswith(".sav")
|
||||
]
|
||||
save_files.sort()
|
||||
|
||||
if not save_files:
|
||||
return [" [No saved matrices found]"]
|
||||
|
||||
return [f"[{str(idx).rjust(2)}] {name}" for idx, name in enumerate(save_files)]
|
||||
|
||||
|
||||
_SAVED_MATRIX_LIST = get_saved_matrix_list()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Settings:
|
||||
BPM: int = 140
|
||||
INSTRUMENT: str = "Bassoon"
|
||||
OCTAVE: int = 3
|
||||
OCTAVE_RANDOMIZATION: int = 0
|
||||
RHYTHM_COMPLEXITY: int = 0
|
||||
DEFAULT_DIVISION: int = 2
|
||||
REST_PROBABILITY: int = 0
|
||||
SAVE_SETTINGS: None = None
|
||||
SAVE_MATRIX: None = None
|
||||
LOAD_MATRIX: None = None
|
||||
|
||||
@classmethod
|
||||
def max_len(cls):
|
||||
return max(len(f.name) for f in fields(cls)) + 2
|
||||
|
||||
|
||||
@dataclass
|
||||
class SettingsHelp:
|
||||
BPM: str = "Enter a BPM between 30 - 300"
|
||||
INSTRUMENT: str = f"Select Soundfont:\n\n{'\n'.join(_INSTRUMENT_LIST)}"
|
||||
OCTAVE: str = "Change the octave. Select an octave between 1 and 5"
|
||||
OCTAVE_RANDOMIZATION: int = 0
|
||||
RHYTHM_COMPLEXITY: str = "\n[0] Off, All Eigth Notes\n[1] On, Engage Rhythm Matrix\n[2] On, Rhythm Matrix and Rest Randomization\n[3] On, Chaos Mode, A random duration is chosen between 0.05 seconds and 1 second"
|
||||
DEFAULT_DIVISION: str = "\n[0] Whole Note\n[1] Half Note\n[2] Quarter Note\n[3] Eight Note"
|
||||
REST_PROBABILITY: str = "Probability in which a note in row will be replaced by a rest.\nEnter a value between 0 and 100, where 0 is absolutely no chance of a rest and 100 is all rests.\nThis only works in RHYTHM_COMPLEXITY MODE 2."
|
||||
SAVE_SETTINGS: str = "Save current settings to file"
|
||||
SAVE_MATRIX: str = "Save current matrix to file, Enter name of matrix to save"
|
||||
LOAD_MATRIX: str = f"Select Saved Matrix to Load:\n\n{'\n'.join(_SAVED_MATRIX_LIST)}"
|
||||
EXIT: None = None
|
||||
|
||||
|
||||
SETTINGS_RULES = {
|
||||
"BPM": {"type": int, "min": 30, "max": 300},
|
||||
"OCTAVE": {"type": int, "min": 1, "max": 5},
|
||||
"OCTAVE_RANDOMIZATION": {"type": int, "min": 0, "max": 4},
|
||||
"RHYTHM_COMPLEXITY": {"type": int, "min": 0, "max": 3},
|
||||
"DEFAULT_DIVISION": {"type": int, "min": 0, "max": 3},
|
||||
"REST_PROBABILITY": {"type": int, "min": 0, "max": 100},
|
||||
"INSTRUMENT": {"type": int, "min": 0, "max": len(SOUNDFONTS)},
|
||||
"SAVE_MATRIX": {"type": str, "min": 0, "max": 10},
|
||||
"LOAD_MATRIX": {"type": str, "min": 0, "max": 100},
|
||||
|
||||
}
|
||||
|
||||
|
||||
class SettingsMixin:
|
||||
|
||||
@cached_property
|
||||
def settings(self):
|
||||
return [f.name for f in fields(Settings)]
|
||||
|
||||
def save_settings(self):
|
||||
setting_obj = {}
|
||||
|
||||
for field in fields(Settings):
|
||||
setting_obj[field.name] = getattr(Settings, field.name)
|
||||
|
||||
with open(_SETTINGS_SAVE_FILE, "w+") as f:
|
||||
json.dump(setting_obj, f)
|
||||
|
||||
def load_settings(self):
|
||||
with open(_SETTINGS_SAVE_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
for k, v in data.items():
|
||||
setattr(Settings, k, int(v) if str(v).isdigit() else v)
|
||||
|
||||
self.update_sf(SOUNDFONTS[Settings.INSTRUMENT])
|
||||
self.bpm = int(Settings.BPM)
|
||||
|
||||
def display_settings(self):
|
||||
self.clear_screen()
|
||||
print(f"{REVERSE} SETTINGS: {RESET}\n")
|
||||
for idx, setting in enumerate(self.settings):
|
||||
value = getattr(Settings, setting)
|
||||
print(f"[{idx}] {setting.ljust(Settings.max_len())}{": " + str(value) if value != None else ''}")
|
||||
|
||||
self.user_input()
|
||||
|
||||
self.display_settings()
|
||||
|
||||
def user_input(self, setting_name=None):
|
||||
request = self.request('Enter ID of setting you want to change, "q" to exit')
|
||||
|
||||
if request[0] == "q":
|
||||
self.loop()
|
||||
|
||||
request = self.validate_request(request)
|
||||
|
||||
setting_name = self.settings[request]
|
||||
|
||||
if setting_name == "SAVE_SETTINGS":
|
||||
self.save_settings()
|
||||
self.message("Settings Saved..", self.display_settings, type="SUCCESS")
|
||||
|
||||
self.display_setting(setting_name)
|
||||
|
||||
value = self.request("Update setting by entering value")
|
||||
|
||||
value = self.validate_new_setting(setting_name, value)
|
||||
|
||||
setattr(Settings, setting_name, value)
|
||||
|
||||
if setting_name == "LOAD_MATRIX":
|
||||
self.handle_load_matrix()
|
||||
|
||||
if setting_name == "SAVE_MATRIX":
|
||||
self.handle_save_matrix()
|
||||
|
||||
if setting_name == "BPM":
|
||||
self.bpm = int(Settings.BPM)
|
||||
|
||||
if setting_name == "INSTRUMENT":
|
||||
instrument = list(SOUNDFONTS.keys())[value]
|
||||
setattr(Settings, setting_name, instrument)
|
||||
self.update_sf(SOUNDFONTS[instrument])
|
||||
|
||||
if setting_name == "OCTAVE_RANDOMIZATION":
|
||||
del self.octave_row
|
||||
del self.octave_map
|
||||
_ = self.octave_row
|
||||
_ = self.octave_map
|
||||
|
||||
if setting_name == "OCTAVE":
|
||||
del self.octave_row
|
||||
del self.octave_map
|
||||
_ = self.octave_row
|
||||
_ = self.octave_map
|
||||
|
||||
def validate_request(self, request):
|
||||
try:
|
||||
request = int(request)
|
||||
if request >= len(self.settings):
|
||||
raise ValueError
|
||||
return request
|
||||
except ValueError:
|
||||
self.message(f"'{request}' is not a valid input, try again!", self.display_settings)
|
||||
|
||||
def display_setting(self, setting_name):
|
||||
self.clear_screen()
|
||||
print(f"{REVERSE}{setting_name.ljust(Settings.max_len())} CURRENT VALUE: {getattr(Settings, setting_name)} {RESET}\n")
|
||||
print(f"INFO: {getattr(SettingsHelp, setting_name)}\n")
|
||||
|
||||
def validate_new_setting(self, setting_name, value):
|
||||
try:
|
||||
if SETTINGS_RULES[setting_name]['type'] == int:
|
||||
value = int(value)
|
||||
if value >= SETTINGS_RULES[setting_name]['min'] and value <= SETTINGS_RULES[setting_name]['max']:
|
||||
pass
|
||||
else:
|
||||
raise ValueError
|
||||
return value
|
||||
else:
|
||||
return value
|
||||
except ValueError:
|
||||
self.message(f"'{value}' is not a valid input, try again!", self.display_settings)
|
||||
|
||||
def handle_load_matrix(self):
|
||||
file_name = " ".join(_SAVED_MATRIX_LIST[int(Settings.LOAD_MATRIX)].split("] ")[1:])
|
||||
with open(f"assets/saves/{file_name}", 'r') as f:
|
||||
seed = f.readline()
|
||||
|
||||
self.current_seed = int(seed)
|
||||
self.regenerate()
|
||||
self.use_current_seed = True
|
||||
|
||||
def handle_save_matrix(self):
|
||||
Settings.SAVE_MATRIX = Settings.SAVE_MATRIX.strip().replace(".", " ").replace(" ", "_")
|
||||
with open(f"assets/saves/{Settings.SAVE_MATRIX}.sav", 'w+') as f:
|
||||
f.write(str(self.current_seed))
|
||||
269
tonerow.py
269
tonerow.py
|
|
@ -1,269 +0,0 @@
|
|||
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()
|
||||
Loading…
Reference in a new issue