Spaces:
Runtime error
Runtime error
from .utils import C_SLICE, C_JOIN, C_MISC, C_MATH | |
import numpy as np | |
from . import io, utils, main | |
def _getnum(pattern, cur, symbols = '+-*/'): | |
number = '' | |
while pattern[cur].isdecimal() or pattern[cur] in symbols: | |
number+=pattern[cur] | |
cur+=1 | |
return number, cur-1 | |
def parse(pattern:str, samples:dict, pattern_length:int = None, | |
c_slice:str = C_SLICE, | |
c_join:str = C_JOIN, | |
c_misc:str = C_MISC, | |
log = True, | |
simple_mode = False): | |
"""Returns (beats, operators, pattern_length, c_slice, c_misc, c_join)""" | |
if log is True: print(f'Beatswapping with `{pattern}`') | |
#load samples: | |
if isinstance(samples, str): samples = (samples,) | |
if not isinstance(samples, dict): | |
samples = {str(i+1):samples[i] for i in range(len(samples))} | |
#preprocess pattern | |
separator = c_join[0] | |
#forgot separator | |
if simple_mode is True: | |
if c_join[0] not in pattern and c_join[1] not in pattern and c_join[2] not in pattern and c_join[3] not in pattern: pattern = pattern.replace(' ', separator) | |
if ' ' not in c_join: pattern = pattern.replace(' ', '') # ignore spaces | |
for i in c_join: | |
while i+i in pattern: pattern = pattern.replace(i+i, i) #double separator | |
while pattern.startswith(i): pattern = pattern[1:] | |
while pattern.endswith(i): pattern = pattern[:-1] | |
# Creates a list of beat strings so that I can later see if there is a `!` in the string | |
separated = pattern | |
for i in c_join: | |
separated = separated.replace(i, c_join[0]) | |
separated = separated.split(c_join[0]) | |
pattern = pattern.replace(c_misc[6], '') | |
# parsing | |
length = 0 | |
num = '' | |
cur = 0 | |
beats = [] | |
operators = [separator] | |
shuffle_beats = [] | |
shuffle_groups = [] | |
current_beat = 0 | |
effect = None | |
pattern += ' ' | |
sample_toadd = None | |
# Loops over all characters | |
while cur < len(pattern): | |
char = pattern[cur] | |
#print(f'char = {char}, cur = {cur}, num = {num}, current_beat = {current_beat}, effect = {effect}, len(beats) = {len(beats)}, length = {length}') | |
if char == c_misc[3]: char = str(current_beat+1) # Replaces `i` with current number | |
# If character is `", ', `, or [`: searches for closing quote and gets the sample rate, | |
# moves cursor to the character after last quote/bracket, creates a sample_toadd variable with the sample. | |
elif char in c_misc[0:3]+c_misc[10:12]: | |
quote = char | |
if quote == c_misc[10]: quote = c_misc[11] # `[` is replaced with `]` | |
cur += 1 | |
sample = '' | |
# Gets sample name between quote characters, moves cursor to the ending quote. | |
while pattern[cur] != quote: | |
sample += pattern[cur] | |
cur += 1 | |
assert sample in samples, f"No sample named `{sample}` found in samples. Available samples: {samples.keys()}" | |
# If sample is a song, it will be converted to a song if needed, and beatmap will be generated | |
if quote == c_misc[11]: | |
if not isinstance(samples[sample], main.song): samples[sample] = main.song(samples[sample]) | |
if samples[sample].beatmap is None: | |
samples[sample].beatmap_generate() | |
samples[sample].beatmap_adjust() | |
# Else sample is a sound file | |
elif not isinstance(samples[sample], np.ndarray): samples[sample] = io._load(samples[sample])[0] | |
sample_toadd = [samples[sample], [], quote, None] # Creates the sample_toadd variable | |
cur += 1 | |
char = pattern[cur] | |
# If character is a math character, a slice character, or `@_?!%` - random, not count, skip, create variable - | |
# - it gets added to `num`, and the loop repeats. | |
# _safer_eval only takes part of the expression to the left of special characters (@%#), so it won't affect length calculation | |
if char.isdecimal() or char in (C_MATH + c_slice + c_misc[4:8] + c_misc[9]): | |
num += char | |
#print(f'char = {char}, added it to num: num = {num}') | |
# If character is `%` and beat hasn't been created yet, it takes the next character as well | |
if char == c_misc[7] and len(beats) == current_beat: | |
cur += 1 | |
char = pattern[cur] | |
num += char | |
# If character is a shuffle character `#` + math expression, beat number gets added to `shuffle_beats`, | |
# beat shuffle group gets added to `shuffle_groups`, cursor is moved to the character after the math expression, and loop repeats. | |
# That means operations after this will only execute once character is not a math character. | |
elif char == c_misc[8]: | |
cur+=1 | |
number, cur = _getnum(pattern, cur) | |
char = pattern[cur] | |
shuffle_beats.append(current_beat) | |
shuffle_groups.append(number) | |
# If character is not math/shuffle, that means math expression has ended. Now it tries to figure out where the expression belongs, | |
# and parses the further characters | |
else: | |
# If the beat has not been added, it adds the beat. Also figures out pattern length. | |
if len(beats) == current_beat and len(num) > 0: | |
# Checks all slice characters in the beat expression. If slice character is found, splits the slice and breaks. | |
for c in c_slice: | |
if c in num: | |
num = num.split(c)[:2] + [c] | |
#print(f'slice: split num by `{c}`, num = {num}, whole beat is {separated[current_beat]}') | |
if pattern_length is None and c_misc[6] not in separated[current_beat]: | |
num0, num1 = utils._safer_eval(num[0]), utils._safer_eval(num[1]) | |
if c == c_slice[0]: length = max(num0, num1, length) | |
if c == c_slice[1]: length = max(num0-1, num0+num1-1, length) | |
if c == c_slice[2]: length = max(num0-num1, num0, length) | |
break | |
# If it didn't break, the expression is not a slice, so it pattern length is just compared with the beat number. | |
else: | |
#print(f'single beat: {num}. Whole beat is {separated[current_beat]}') | |
if c_misc[6] not in separated[current_beat]: length = max(utils._safer_eval(num), length) | |
# If there no sample saved in `sample_toadd`, adds the beat to list of beats. | |
if sample_toadd is None: beats.append([num, []]) | |
# If `sample_toadd` is not None, beat is a sample/song. Adds the beat and sets sample_toadd to None | |
else: | |
sample_toadd[3] = num | |
beats.append(sample_toadd) | |
sample_toadd = None | |
#print(f'char = {char}, got num = {num}, appended beat {len(beats)}') | |
# Sample might not have a `num` with a slice, this adds the sample without a slice | |
elif len(beats) == current_beat and len(num) == 0 and sample_toadd is not None: | |
beats.append(sample_toadd) | |
sample_toadd = None | |
# If beat has been added, it now parses beats. | |
if len(beats) == current_beat+1: | |
#print(f'char = {char}, parsing effects:') | |
# If there is an effect and current character is not a math character, effect and value are added to current beat, and effect is set to None | |
if effect is not None: | |
#print(f'char = {char}, adding effect: type = {effect}, value = {num}') | |
beats[current_beat][1].append([effect, num if num!='' else None]) | |
effect = None | |
# If current character is a letter, it sets that letter to `effect` variable. | |
# Since loop repeats after that, that while current character is a math character, it gets added to `num`. | |
if char.isalpha() and effect is None: | |
#print(f'char = {char}, effect type is {effect}') | |
effect = char | |
# If character is a beat separator, it starts parsing the next beat in the next loop. | |
if char in c_join and len(beats) == current_beat + 1: | |
#print(f'char = {char}, parsing next beat') | |
current_beat += 1 | |
effect = None | |
operators.append(char) | |
num = '' # `num` is set to empty string. btw `num` is only used in this loop so it needs to be here | |
cur += 1 # cursor goes to the next character | |
#for i in beats: print(i) | |
import math | |
if pattern_length is None: pattern_length = int(math.ceil(length)) | |
return beats, operators, pattern_length, shuffle_groups, shuffle_beats, c_slice, c_misc, c_join | |
# I can't be bothered to annotate this one. It just works, okay? | |
def _random(beat:str, length:int, rchar = C_MISC[4], schar = C_MISC[5]) -> str: | |
"""Takes a string and replaces stuff like `@1_4_0.5` with randomly generated number where 1 - start, 4 - stop, 0.5 - step. Returns string.""" | |
import random | |
beat+=' ' | |
while rchar in beat: | |
rand_index = beat.find(rchar)+1 | |
char = beat[rand_index] | |
number = '' | |
while char.isdecimal() or char in '.+-*/': | |
number += char | |
rand_index+=1 | |
char = beat[rand_index] | |
if number != '': start = utils._safer_eval(number) | |
else: start = 0 | |
if char == schar: | |
rand_index+=1 | |
char = beat[rand_index] | |
number = '' | |
while char.isdecimal() or char in '.+-*/': | |
number += char | |
rand_index+=1 | |
char = beat[rand_index] | |
if number != '': stop = utils._safer_eval(number) | |
else: stop = length | |
if char == schar: | |
rand_index+=1 | |
char = beat[rand_index] | |
number = '' | |
while char.isdecimal() or char in '.+-*/': | |
number += char | |
rand_index+=1 | |
char = beat[rand_index] | |
if number != '': step = utils._safer_eval(number) | |
else: step = length | |
choices = [] | |
while start <= stop: | |
choices.append(start) | |
start+=step | |
beat = list(beat) | |
beat[beat.index(rchar):rand_index] = list(str(random.choice(choices))) | |
beat = ''.join(beat) | |
return beat | |
def _shuffle(pattern: list, shuffle_beats: list, shuffle_groups: list) -> list: | |
"""Shuffles pattern according to shuffle_beats and shuffle_groups""" | |
import random | |
done = [] | |
result = pattern.copy() | |
for group in shuffle_groups: | |
if group not in done: | |
shuffled = [i for n, i in enumerate(shuffle_beats) if shuffle_groups[n] == group] | |
unshuffled = shuffled.copy() | |
random.shuffle(shuffled) | |
for i in range(len(shuffled)): | |
result[unshuffled[i]] = pattern[shuffled[i]] | |
done.append(group) | |
return result | |
def _metric_get(v, beat, metrics, c_misc7 = C_MISC[7]): | |
assert v[v.find(c_misc7)+1] in metrics, f'`%{v[v.find(c_misc7)+1]}`: No metric called `{v[v.find(c_misc7)+1]}` found in metrics. Available metrics: {metrics.keys()}' | |
metric = metrics[v[v.find(c_misc7)+1]](beat) | |
return metric | |
def _metric_replace(v, metric, c_misc7 = C_MISC[7]): | |
for _ in range(v.count(c_misc7)): | |
v= v[:v.find(c_misc7)] + str(metric) + v[v.find(c_misc7)+2:] | |
return v |