composer.py
4.4 kB · python · 123 lines
1from typing import List2from .config import Config3from .enums import ChordType, Movement, Scale4from .models import Music, Voice56# COMPOSER78class Composer:910 def __init__(self, config: Config, rng):11 self.config = config12 self.rng = rng1314 # COMPOSE1516 def compose(17 self,18 scale: Scale,19 progression: str,20 voices: list[Voice],21 bar_length: int = 4,22 count: int = 64,23 repeat: bool = False24 ) -> Music:25 track = self._track(count, progression, voices, bar_length, repeat)26 return Music(27 scale=scale,28 progression=progression,29 voices=voices,30 track=track,31 )3233 # CHORDS3435 def _create_chord(self, note_pool: list[int], chord: str, chord_type: ChordType) -> list[int]:36 chord_map = {"C": 0, "D": 1, "E": 2, "F": 3, "G": 4, "A": 5, "B": 6}37 degree_idx = chord_map[chord]38 chord_intervals = self.config.chord_types[chord_type]39 root_note_idx = degree_idx % len(note_pool)40 chord_notes = []41 for interval in chord_intervals:42 note_idx = (root_note_idx + interval) % len(note_pool)43 note = note_pool[note_idx]44 if note not in chord_notes:45 chord_notes.append(note)46 return chord_notes4748 def _chord_pool(self, voice: Voice, chord: str) -> list[int]:49 if voice.chord_type is None:50 return voice.note_pool51 return self._create_chord(voice.note_pool, chord, voice.chord_type)5253 # BARS5455 def _notes(self, note_pool: list[int], num_notes: list[int]) -> list[int]:56 return [note_pool[i] for i in self.rng.sample_indices(len(note_pool), self.rng.choice(num_notes))]5758 def _bar(self, count: int, movements: list[Movement], note_pool: list[int],59 num_notes: list[int], start: list[int] = None) -> List[List[int]]:60 bar = []61 if start is not None:62 previous = start63 else:64 previous = self._notes(note_pool, num_notes)65 bar.append(previous)66 for _ in range(count - 1):67 movement = self.rng.choice(movements) if len(movements) > 1 else movements[0]68 match movement:69 case Movement.REPEAT:70 notes = previous71 case Movement.RANDOM:72 notes = self._notes(note_pool, num_notes)73 case Movement.UP:74 notes = [note_pool[(note_pool.index(n) + 1) % len(note_pool)] for n in previous]75 case Movement.DOWN:76 notes = [note_pool[(note_pool.index(n) - 1) % len(note_pool)] for n in previous]77 case Movement.PAUSE:78 notes = []79 previous = notes80 bar.append(notes)81 return bar8283 def _voice_bar(self, voice: Voice, chord: str, bar_length: int) -> List[List[int]]:84 chord_pool = self._chord_pool(voice, chord)85 return self._bar(bar_length, voice.movements, chord_pool, voice.num_notes)8687 # TRACKS8889 def _concatenate(self, voices: list[Voice], tracks: dict[int, List[List[int]]]) -> List[List[int]]:90 track = []91 num_beats = 092 for i in range(len(voices)):93 if tracks[i]:94 num_beats = len(tracks[i])95 break96 for beat in range(num_beats):97 chord = []98 for i in range(len(voices)):99 if tracks[i]:100 chord.extend(tracks[i][beat])101 track.append(chord)102 return track103104 def _track(self, count: int, progression: str, voices: list[Voice],105 bar_length: int, repeat: bool) -> List[List[int]]:106 tracks = {i: [] for i in range(len(voices))}107 bar_library = {}108 for chord in progression:109 for i, voice in enumerate(voices):110 track = tracks[i]111 if repeat:112 key = (i, chord)113 if key not in bar_library:114 bar = self._voice_bar(voice, chord, bar_length)115 bar_library[key] = bar116 else:117 bar = bar_library[key]118 else:119 bar = self._voice_bar(voice, chord, bar_length)120 track.extend(bar)121 base_track = self._concatenate(voices, tracks)122 num_repeats = (count + len(base_track) - 1) // len(base_track)123 return (base_track * num_repeats)[:count]