composer.py
4.2 kB · python · 120 lines
1from mrlypy.core.state import choice, sample2from typing import List3from .config import Config4from .enums import ChordType, Movement, Scale5from .models import Music, Voice67# COMPOSER89class Composer:1011 def __init__(self, config: Config):12 self.config = config1314 # 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 _bar(self, count: int, movements: list[Movement], note_pool: list[int],56 num_notes: list[int], start: list[int] = None) -> List[List[int]]:57 bar = []58 if start is not None:59 previous = start60 else:61 previous = sample(note_pool, choice(num_notes))62 bar.append(previous)63 for _ in range(count - 1):64 movement = choice(movements) if len(movements) > 1 else movements[0]65 match movement:66 case Movement.REPEAT:67 notes = previous68 case Movement.RANDOM:69 notes = sample(note_pool, choice(num_notes))70 case Movement.UP:71 notes = [note_pool[(note_pool.index(n) + 1) % len(note_pool)] for n in previous]72 case Movement.DOWN:73 notes = [note_pool[(note_pool.index(n) - 1) % len(note_pool)] for n in previous]74 case Movement.PAUSE:75 notes = []76 previous = notes77 bar.append(notes)78 return bar7980 def _voice_bar(self, voice: Voice, chord: str, bar_length: int) -> List[List[int]]:81 chord_pool = self._chord_pool(voice, chord)82 return self._bar(bar_length, voice.movements, chord_pool, voice.num_notes)8384 # TRACKS8586 def _concatenate(self, voices: list[Voice], tracks: dict[int, List[List[int]]]) -> List[List[int]]:87 track = []88 num_beats = 089 for i in range(len(voices)):90 if tracks[i]:91 num_beats = len(tracks[i])92 break93 for beat in range(num_beats):94 chord = []95 for i in range(len(voices)):96 if tracks[i]:97 chord.extend(tracks[i][beat])98 track.append(chord)99 return track100101 def _track(self, count: int, progression: str, voices: list[Voice],102 bar_length: int, repeat: bool) -> List[List[int]]:103 tracks = {i: [] for i in range(len(voices))}104 bar_library = {}105 for chord in progression:106 for i, voice in enumerate(voices):107 track = tracks[i]108 if repeat:109 key = (i, chord)110 if key not in bar_library:111 bar = self._voice_bar(voice, chord, bar_length)112 bar_library[key] = bar113 else:114 bar = bar_library[key]115 else:116 bar = self._voice_bar(voice, chord, bar_length)117 track.extend(bar)118 base_track = self._concatenate(voices, tracks)119 num_repeats = (count + len(base_track) - 1) // len(base_track)120 return (base_track * num_repeats)[:count]