Source code for djalgo.minimalism

from . import utils

[docs] class Minimalism: """ Main class for musical minimalism. """
[docs] class Process: """ Class for the process of musical minimalism. """ def __init__(self, operation='additive', direction='forward', repetition=0): """ Initializes the process with the specified operation and direction. Args: operation (str): The operation to be used. Can be 'additive' or 'subtractive'. direction (str): The direction of the operation. Can be 'forward', 'backward', 'inward' or 'outward'. repetition (int): The number of times the process is repeated. """ if operation in ['additive', 'subtractive']: self.operation = operation else: raise ValueError("Invalid output type. Choose 'additive' or 'subtractive'.") if direction in ['forward', 'backward', 'inward', 'outward']: self.direction = direction else: raise ValueError("Invalid output type. Choose 'forward', 'backward', 'inward' or 'outward'.") if repetition < 0 or not isinstance(repetition, int): raise ValueError("Invalid repetition value. Must be an integer greater of equal to 0.") self.repetition = repetition # Additive operations # -------------------
[docs] def additive_forward(self): """ Applies the additive forward operation to the sequence with repetition, as:: [ 'C4', 'C4', 'D4', 'C4', 'D4', 'E4', 'C4', 'D4', 'E4', 'F4', 'C4', 'D4', 'E4', 'F4', 'G4', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5' ] Returns: list: The processed sequence. """ processed = [] for i in range(len(self.sequence)): segment = [self.sequence[j] for j in range(i + 1)] for _ in range(self.repetition+1): processed.extend(segment) return processed
[docs] def additive_backward(self): """ Applies the additive backward operation to the sequence with repetition, as:: [ 'C5', 'B4', 'C5', 'A4', 'B4', 'C5', 'G4', 'A4', 'B4', 'C5', 'F4', 'G4', 'A4', 'B4', 'C5', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', ] Returns: list: The processed sequence. """ processed = [] for i in range(len(self.sequence), 0, -1): segment = [self.sequence[j] for j in range(i - 1, len(self.sequence))] for _ in range(self.repetition+1): processed.extend(segment) return processed
[docs] def additive_inward(self): """ Applies the additive inward operation to the sequence, as:: [ 'C4', 'C5', 'C4', 'D4', 'B4', 'C5', 'C4', 'D4', 'E4', 'A4', 'B4', 'C5', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5' ] Returns: list: The processed sequence. """ n = len(self.sequence) processed = [] segment = [] for i in range((n + 1) // 2): # Ensuring we cover all elements if i < n - i - 1: # Avoid adding the middle element twice segment = [self.sequence[:i+1], self.sequence[(n - i-1):]] else: segment = [self.sequence] for _ in range(self.repetition+1): processed.extend(segment) processed = [item for sublist in processed for item in sublist] return processed
[docs] def additive_outward(self): """ Applies the additive outward operation to the sequence with repetition, as:: [ 'F4', 'G4', 'E4', 'F4', 'G4', 'A4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5' ] Returns: list: The processed sequence. """ n = len(self.sequence) processed = [] # Check if the sequence is odd or even and find the middle if n % 2 == 0: # Even mid_left = n // 2 - 1 mid_right = n // 2 # Start from the middle two elements and expand outwards for i in range(n // 2): segment = self.sequence[mid_left - i : mid_right + i + 1] for _ in range(self.repetition+1): processed.extend(segment) else: # Odd mid = n // 2 # Start from the middle element and expand outwards for i in range(mid + 1): segment = self.sequence[mid - i : mid + i + 1] for _ in range(self.repetition+1): processed.extend(segment) return processed
# Subtractive operations # ----------------------
[docs] def subtractive_forward(self): """ Applies the subtractive forward operation to the sequence, as:: [ 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'F4', 'G4', 'A4', 'B4', 'C5', 'G4', 'A4', 'B4', 'C5', 'A4', 'B4', 'C5', 'B4', 'C5', 'C5' ] Returns: list: The processed sequence. """ processed = [] for i in range(len(self.sequence)): # Each time, remove one more note from the beginning segment = self.sequence[i:] for _ in range(self.repetition+1): # Here we consider if repetition is needed processed.extend(segment) # Directly append the segment without extending it, because we're dealing with segments now return processed
[docs] def subtractive_backward(self): """ Applies the subtractive backward operation to the sequence, as:: [ 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'C4', 'D4', 'E4', 'F4', 'G4', 'C4', 'D4', 'E4', 'F4', 'C4', 'D4', 'E4', 'C4', 'D4', 'C4' ] Returns: list: The processed sequence. """ processed = [] for i in range(len(self.sequence), 0, -1): # Start from the full sequence and decrement # Each time, create a segment that removes one more note from the end segment = self.sequence[:i] for _ in range(self.repetition+1): # Consider if repetition is needed processed.extend(segment) # Append the segment directly return processed
[docs] def subtractive_inward(self): """ Applies the subtractive inward operation to the sequence, as:: [ 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'E4', 'F4', 'G4', 'A4', 'F4', 'G4', ] Returns: list: The processed sequence. """ n = len(self.sequence) steps = n // 2 # Compute the steps for subtraction, which will be half the length of the sequence rounded down processed = self.sequence * (self.repetition + 1) # Add segments, removing elements from both ends, also repeated for i in range(1, steps + 1): segment = self.sequence[i: n - i] # Remove elements from both ends if segment: # Check if there's anything left to add. processed += segment * (self.repetition + 1) return processed
[docs] def subtractive_outward(self): """ Applies the subtractive outward operation to the sequence, as:: [ 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'C4', 'D4', 'E4', , 'A4', 'B4', 'C5', 'C4', 'D4', 'B4', 'C5', 'C4', 'C5', ] Returns: list: The processed sequence. """ segment = self.sequence processed = segment * (self.repetition+1) while len(segment) > 2: segment = segment[1:-1] # remove the first and last elements for _ in range(self.repetition+1): processed.extend(segment) return processed
[docs] def generate(self, sequence): """ Generates the processed sequence based on the operation and direction. Args: sequence (list): The sequence to be processed. Returns: list: The processed sequence. """ self.sequence = sequence if self.operation == 'additive' and self.direction == 'forward': processed = self.additive_forward() elif self.operation == 'additive' and self.direction == 'backward': processed = self.additive_backward() elif self.operation == 'additive' and self.direction == 'inward': processed = self.additive_inward() elif self.operation == 'additive' and self.direction == 'outward': processed = self.additive_outward() elif self.operation == 'subtractive' and self.direction == 'forward': processed = self.subtractive_forward() elif self.operation == 'subtractive' and self.direction == 'backward': processed = self.subtractive_forward() elif self.operation == 'subtractive' and self.direction == 'inward': processed = self.subtractive_inward() elif self.operation == 'subtractive' and self.direction == 'outward': processed = self.subtractive_outward() new_processed = [] current_offset = 0 # Adjust offsets based on the duration for note in processed: new_note = (note[0], note[1], current_offset) new_processed.append(new_note) current_offset += note[1] return new_processed
[docs] class Tintinnabuli: """ Class for the Tintinnabuli style of musical minimalism. """ def __init__(self, t_chord, direction='down', rank=0): """ Initializes the Tintinnabuli style with the specified t-chord, direction, and rank. Args: t_chord (list): The t-chord to be used. direction (str): The direction of the operation. Can be 'up', 'down', 'any' or 'alternate'. rank (int): The rank of the t-chord. """ if direction in ['up', 'down', 'any', 'alternate']: self.direction = direction else: raise ValueError("Invalid output type. Choose 'up', 'down', 'any' or 'alternate'.") if self.direction == 'alternate': self.is_alternate = True self.direction = 'up' else: self.is_alternate = False self.t_chord = t_chord if not isinstance(rank, int) or rank < 0: raise ValueError("Rank must be a non-negative integer lower or equal to the length of the t-chord.") else: self.rank = rank if self.rank >= len(self.t_chord): self.rank = len(self.t_chord) - 1 print("Rank exceeds the length of the t-chord. Defaulting to the last note of the t-chord.")
[docs] def generate(self, sequence): """ Generates the t-voice based on the t-chord, direction, and rank. Args: sequence (list): The m-voice sequence. Returns: list: The t-voice sequence. """ self.sequence = sequence # the m-voice if utils.check_input(self.sequence) == 'list of tuples': pitch_sequence = [note[0] for note in self.sequence] elif utils.check_input(self.sequence) == 'list': pitch_sequence = self.sequence else: raise ValueError("Invalid input type. Must be a list of tuples or a list.") t_voice = [] for m in pitch_sequence: if m is None: t_voice.append(None) continue differences = [t - m for t in self.t_chord] sorted_differences = sorted(enumerate(differences), key=lambda x: abs(x[1])) # Ensure rank is within the range of available notes effective_rank = self.rank # Default to the specified rank if self.direction in ['up', 'down']: filtered_differences = [(index, value) for index, value in sorted_differences if value >= 0] if self.direction == 'up' else [(index, value) for index, value in sorted_differences if value <= 0] if not filtered_differences: # If there are no notes in the desired direction, default to the extreme note in that direction t_voice_i = max(self.t_chord) if self.direction == 'up' else min(self.t_chord) else: # Adjust rank if it exceeds the length of filtered differences if effective_rank >= len(filtered_differences): effective_rank = len(filtered_differences) - 1 # Use the last available note chosen_index = filtered_differences[effective_rank][0] t_voice_i = self.t_chord[chosen_index] elif self.direction == 'any': # Adjust rank if it exceeds the length of sorted differences if effective_rank >= len(sorted_differences): effective_rank = len(sorted_differences) - 1 # Use the last available note chosen_index = sorted_differences[effective_rank][0] t_voice_i = self.t_chord[chosen_index] # Change direction if alternate if self.is_alternate: self.direction = 'down' if self.direction == 'up' else 'up' t_voice.append(t_voice_i) if utils.check_input(self.sequence) == 'list of tuples': t_voice = [(t_voice[i], note[1], note[2]) for i, note in enumerate(self.sequence)] return t_voice