diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..14ee4db --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "flake8.args": ["--max-line-length=125"] +} \ No newline at end of file diff --git a/main.py b/main.py index c813811..404884e 100644 --- a/main.py +++ b/main.py @@ -6,18 +6,20 @@ track = MidiTrack() mid.tracks.append(track) - -def set_bpm( bpm, time=0): + +def set_bpm(bpm, time=0): """ track = MidiTrack() set_bpm(track, bpm=100) """ return int(60_000_000 / bpm) - #track.append(MetaMessage('set_tempo', tempo=tempo, time=time)) + # track.append(MetaMessage('set_tempo', tempo=tempo, time=time)) + + +############################################################################### +# Busca instrumento -########################################################################################################################### -#Busca instrumento instrument_map = { "acoustic grand piano": 0, "bright acoustic piano": 1, @@ -146,32 +148,37 @@ def set_bpm( bpm, time=0): "telephone ring": 124, "helicopter": 125, "applause": 126, - "gunshot": 127 + "gunshot": 127, } + def buscar_programa(nome, instrument_map): nome_normalizado = nome.strip().lower() if nome_normalizado in instrument_map: return instrument_map[nome_normalizado] - raise ValueError(f"Nome do Instrumento '{nome}' nao encontrado.") + raise ValueError(f"Nome do Instrumento '{nome}' nao encontrado.") + + +#################################################################### -########################################################################################################################### +################################################################### # PrePro (futuramente: remove comentários, normaliza espaços, etc.) # Por enquanto, mantemos em branco +############################################################################### -######################################################################################################################### class Node: def __init__(self, value, children): self.value = value self.children = children - + def evaluate(self, st): return None + def beat_to_tick(beat, ticks_per_beat=480): return int(beat * ticks_per_beat) @@ -182,21 +189,25 @@ def criar_midi_com_duracoes(musica, bpm, nome, instrumentos, instrument_map): mid.tracks.append(track) tempo = int(60_000_000 / bpm) - track.append(MetaMessage('set_tempo', tempo=tempo, time=0)) - track.append(MetaMessage('time_signature', numerator=4, denominator=4, time=0)) + track.append(MetaMessage("set_tempo", tempo=tempo, time=0)) + track.append( + MetaMessage("time_signature", numerator=4, denominator=4, time=0) + ) # Lista com todos os eventos (tempo absoluto) eventos = [] - #program = buscar_programa(instrumentos[], instrument_map) - #print(program) + # program = buscar_programa(instrumentos[], instrument_map) + # print(program) # Instrumentos for nome_instr, dados in musica["instrumentos"].items(): - #print(nome_instr) + # print(nome_instr) canal = dados["canal"] program = buscar_programa(nome_instr, instrument_map) - track.append(Message('program_change', program=program, channel=canal, time=0)) + track.append( + Message("program_change", program=program, channel=canal, time=0) + ) tempo_atual = 0 # tempo em beats @@ -210,8 +221,28 @@ def criar_midi_com_duracoes(musica, bpm, nome, instrumentos, instrument_map): tick_on = beat_to_tick(tempo_atual) tick_off = beat_to_tick(tempo_atual + duracao) - eventos.append({"tick": tick_on, "msg": Message('note_on' , note=nota, velocity=64, channel=canal)}) - eventos.append({"tick": tick_off, "msg": Message('note_off', note=nota, velocity=64, channel=canal)}) + eventos.append( + { + "tick": tick_on, + "msg": Message( + "note_on", + note=nota, + velocity=64, + channel=canal, + ), + } + ) + eventos.append( + { + "tick": tick_off, + "msg": Message( + "note_off", + note=nota, + velocity=64, + channel=canal, + ), + } + ) tempo_atual += duracao elif "chord" in bloco: @@ -223,11 +254,30 @@ def criar_midi_com_duracoes(musica, bpm, nome, instrumentos, instrument_map): tick_on = beat_to_tick(tempo_atual) tick_off = beat_to_tick(tempo_atual + duracao) for nota in notas: - eventos.append({"tick": tick_on, "msg": Message('note_on', note=nota, velocity=64, channel=canal)}) - eventos.append({"tick": tick_off, "msg": Message('note_off', note=nota, velocity=64, channel=canal)}) + eventos.append( + { + "tick": tick_on, + "msg": Message( + "note_on", + note=nota, + velocity=64, + channel=canal, + ), + } + ) + eventos.append( + { + "tick": tick_off, + "msg": Message( + "note_off", + note=nota, + velocity=64, + channel=canal, + ), + } + ) tempo_atual += duracao - # Ordenar eventos por tick eventos.sort(key=lambda e: e["tick"]) @@ -239,15 +289,14 @@ def criar_midi_com_duracoes(musica, bpm, nome, instrumentos, instrument_map): track.append(evento["msg"]) ultimo_tick = evento["tick"] - mid.save(f'{nome}.mid') + mid.save(f"{nome}.mid") print(f"Arquivo '{nome}.mid' criado com sucesso.") - - - class MusicNode(Node): - def __init__(self, nome, bpm, varials, instrumentos, loops, ifs, chords, plays): + def __init__( + self, nome, bpm, varials, instrumentos, loops, ifs, chords, plays + ): self.nome = nome self.bpm = bpm self.varials = varials @@ -259,12 +308,12 @@ def __init__(self, nome, bpm, varials, instrumentos, loops, ifs, chords, plays): def evaluate(self, context=None): print(f"⏺ Criando música: {self.nome} | BPM: {self.bpm}") - + if context is None: context = {} if "instrumentos" not in context: context["instrumentos"] = {} - + # Criar dicionário varials no contexto, se ainda não existir if "varials" not in context: context["varials"] = {} @@ -278,7 +327,6 @@ def evaluate(self, context=None): instrumento.evaluate(context) - class VarialsNode(Node): def __init__(self, name, value): self.name = name @@ -293,15 +341,16 @@ def evaluate(self, context): valor = self.value.value # um número só context["varials"][nome] = valor - #print("VarialsNode") - #print(context) + # print("VarialsNode") + # print(context) + class VarialsBlockNode: def __init__(self, varials): self.varials = varials def evaluate(self, context): - #print(f'contexto no vblock{context}') + # print(f'contexto no vblock{context}') for varial in self.varials: varial.evaluate(context) @@ -322,11 +371,10 @@ def add_event(self, tipo, node): self._current_block = {tipo: []} self._current_block[tipo].append(node) - def finalize(self): if self._current_block: self.partitura.append(self._current_block) - #print(f'TEMOS UM JOGO: {self.partitura}') + # print(f'TEMOS UM JOGO: {self.partitura}') self._current_block = None self._current_block_type = None @@ -334,7 +382,7 @@ def evaluate(self, context): self.finalize() context["instrumentos"][self.nome] = { "canal": self.canal, - "partitura": [] + "partitura": [], } for bloco in self.partitura: @@ -342,8 +390,9 @@ def evaluate(self, context): for node in eventos: node.evaluate(context) - print(f"[InstrumentNode.evaluate] Instrumento '{self.nome}' avaliado e salvo no contexto") - + print( + f"[InstrumentNode.evaluate] Instrumento '{self.nome}' avaliado e salvo no contexto" + ) class PlayNode(Node): @@ -374,7 +423,9 @@ def evaluate(self, context): nota_val = self.nota else: - raise Exception(f"Tipo inesperado em nota: {self.nota} ({type(self.nota)})") + raise Exception( + f"Tipo inesperado em nota: {self.nota} ({type(self.nota)})" + ) # Se for lista (ex: acorde), pega a primeira nota if isinstance(nota_val, list): @@ -383,16 +434,23 @@ def evaluate(self, context): nota_final = nota_val # Resolve duração - duracao_final = self.duracao.evaluate(context) if hasattr(self.duracao, "evaluate") else self.duracao + duracao_final = ( + self.duracao.evaluate(context) + if hasattr(self.duracao, "evaluate") + else self.duracao + ) # Adiciona à partitura for nome, info in context["instrumentos"].items(): if info["canal"] == self.canal: - info["partitura"].append({"note": (int(nota_final), duracao_final)}) + info["partitura"].append( + {"note": (int(nota_final), duracao_final)} + ) break else: raise Exception(f"Nenhum instrumento com canal {self.canal}") + class ChordNode(Node): def __init__(self, notas, duracao, canal): self.notas = notas # Lista de notas ou tokens @@ -400,13 +458,17 @@ def __init__(self, notas, duracao, canal): self.canal = canal def evaluate(self, context): - duracao_val = self.duracao.evaluate(context) if hasattr(self.duracao, "evaluate") else self.duracao + duracao_val = ( + self.duracao.evaluate(context) + if hasattr(self.duracao, "evaluate") + else self.duracao + ) notas_com_duracao = [] # Transforma tudo em lista, mesmo que venha só uma string ou token notas = self.notas if isinstance(self.notas, list) else [self.notas] - #print(f'qq e isso: {self.notas}') + # print(f'qq e isso: {self.notas}') for n in notas: # Caso seja um nó que implementa evaluate() @@ -441,16 +503,16 @@ def evaluate(self, context): else: notas_com_duracao.append(val) - #print(notas_com_duracao) + # print(notas_com_duracao) for nome, info in context["instrumentos"].items(): if info["canal"] == self.canal: - info["partitura"].append({"chord": (notas_com_duracao, duracao_val)}) + info["partitura"].append( + {"chord": (notas_com_duracao, duracao_val)} + ) break else: raise Exception(f"Nenhum instrumento com canal {self.canal}") - - class LoopNode(Node): @@ -464,10 +526,10 @@ def evaluate(self, context): node.evaluate(context) - ######################################################################################################################### # Token + class Token: def __init__(self, type_, value): self.type = type_ @@ -506,7 +568,7 @@ def tokenize(self): "Instrument": "INSTRUMENT", "Music": "MUSIC", "Varials": "VARIALS", - "if": "IF" + "if": "IF", } while True: @@ -564,7 +626,9 @@ def tokenize(self): tokens.append(Token("PAREN", char)) elif char.isalpha(): ident = char - while (peek := self.peek_char()) and (peek.isalnum() or peek == "_"): + while (peek := self.peek_char()) and ( + peek.isalnum() or peek == "_" + ): ident += self.get_next_char() token_type = keywords.get(ident, "ID") tokens.append(Token(token_type, ident)) @@ -594,11 +658,16 @@ def eat(self, expected_type): if token.type == expected_type: self.pos += 1 return token - raise Exception(f"Esperado {expected_type}, mas encontrou {token.type}") - + raise Exception( + f"Esperado {expected_type}, mas encontrou {token.type}" + ) + def parse_expression(self): expr = self.parse_term() - while self.current_token() and self.current_token().type in ("PLUS", "MINUS"): + while self.current_token() and self.current_token().type in ( + "PLUS", + "MINUS", + ): op = self.eat(self.current_token().type) right = self.parse_term() expr = f"{expr} {op.value} {right}" @@ -606,7 +675,10 @@ def parse_expression(self): def parse_term(self): term = self.parse_factor() - while self.current_token() and self.current_token().type in ("MULT", "DIV"): + while self.current_token() and self.current_token().type in ( + "MULT", + "DIV", + ): op = self.eat(self.current_token().type) right = self.parse_factor() term = f"{term} {op.value} {right}" @@ -628,8 +700,8 @@ def parse(self): self.parse_program() def parse_program(self): - #print("novo bloco MUSIC \n################################################ ") - + # print("novo bloco MUSIC \n################################################ ") + self.eat("MUSIC") nome = self.current_token().value self.eat("ID") @@ -652,8 +724,10 @@ def parse_program(self): if token_type == "VARIALS": varials = self.parse_varials_block() elif token_type == "INSTRUMENT": - instrumento = self.parse_instrument_block() # captura o InstrumentNode criado - instrumentos.append(instrumento) # adiciona na lista + instrumento = ( + self.parse_instrument_block() + ) # captura o InstrumentNode criado + instrumentos.append(instrumento) # adiciona na lista elif token_type == "LOOP": loops.append(self.parse_loop_block()) elif token_type == "IF": @@ -667,32 +741,32 @@ def parse_program(self): self.eat("RBRACE") - return MusicNode(nome, bpm, varials, instrumentos, loops, ifs, chords, plays) - - + return MusicNode( + nome, bpm, varials, instrumentos, loops, ifs, chords, plays + ) def parse_varials_block(self): - #print("novo bloco VARIALS \n################################################ ") + # print("novo bloco VARIALS \n################################################ ") self.eat("VARIALS") self.eat("LBRACE") - + varials = [] while self.current_token() and self.current_token().type == "ID": varial_node = self.parse_assignment() - #print(f"valor no block: {varial_node.value}") - #print(f"nome no block: {varial_node.name}") + # print(f"valor no block: {varial_node.value}") + # print(f"nome no block: {varial_node.name}") varials.append(varial_node) - + self.eat("RBRACE") - return VarialsBlockNode(varials) + return VarialsBlockNode(varials) def parse_assignment(self): var_name = self.eat("ID") # supondo que eat retorna o valor do token self.eat("ASSIGN") - + if self.current_token().type == "NUMBER": value = self.eat("NUMBER") - #print(f"{var_name} = {value}") + # print(f"{var_name} = {value}") elif self.current_token().type == "LBRACK": self.eat("LBRACK") numbers = [self.eat("NUMBER")] @@ -700,21 +774,21 @@ def parse_assignment(self): self.eat("COMMA") numbers.append(self.eat("NUMBER")) self.eat("RBRACK") - #print(f"{var_name} = {numbers}") + # print(f"{var_name} = {numbers}") value = numbers else: raise Exception("Valor inválido em assignment") return VarialsNode(var_name, value) - - def parse_instrument_block(self): self.eat("INSTRUMENT") instrument_name = self.eat("ID").value canal = int(self.eat("NUMBER").value) - - self.instrumento_atual = InstrumentNode(instrument_name, canal) # salva instrumento ativo + + self.instrumento_atual = InstrumentNode( + instrument_name, canal + ) # salva instrumento ativo self.eat("LBRACE") while self.current_token() and self.current_token().type != "RBRACE": @@ -737,13 +811,12 @@ def parse_instrument_block(self): self.instrumento_atual.finalize() self.count += 1 return self.instrumento_atual - def parse_play_stmt(self): self.eat("PLAY") nota = self.parse_expression() duracao = self.parse_expression() - #print(f'esse é o canal: {self.instrumento_atual.canal}') + # print(f'esse é o canal: {self.instrumento_atual.canal}') node = PlayNode(nota, duracao, self.instrumento_atual.canal) self.instrumento_atual.add_event("note", node) @@ -762,27 +835,27 @@ def parse_chord_stmt(self): self.eat("RBRACK") break else: - raise Exception("Esperado vírgula ou colchete de fechamento") + raise Exception( + "Esperado vírgula ou colchete de fechamento" + ) else: notas = self.eat("ID").value # apenas o nome da variável duracao = self.parse_expression() - #print(f'parse chord: {notas}') + # print(f'parse chord: {notas}') node = ChordNode(notas, duracao, canal=self.instrumento_atual.canal) self.instrumento_atual.add_event("chord", node) - def parse_inc_stmt(self): - var = self.eat("ID") + self.eat("ID") self.eat("PLUS_ASSIGN") - value = self.eat("NUMBER") - #print(f"{var.value} += {value.value}") + self.eat("NUMBER") def parse_loop_block(self): - #print("novo bloco LOOP \n################################################ ") + # print("novo bloco LOOP \n################################################ ") self.eat("LOOP") - loop_id = self.eat("ID").value + self.eat("ID").value repeat_count = int(self.eat("NUMBER").value) # número de repetições self.eat("LBRACE") @@ -792,13 +865,15 @@ def parse_loop_block(self): while self.current_token() and self.current_token().type != "RBRACE": token = self.current_token() if token.type == "IF": - loop_instructions.append(('if', self.current_token())) # opcional, depende do seu uso + loop_instructions.append( + ("if", self.current_token()) + ) # opcional, depende do seu uso self.parse_if_block() elif token.type == "PLAY": self.eat("PLAY") note = self.parse_expression() duration = self.parse_expression() - loop_instructions.append(('play', note, duration)) + loop_instructions.append(("play", note, duration)) elif token.type == "CHORD": self.eat("CHORD") if self.current_token().type == "LBRACK": @@ -813,35 +888,40 @@ def parse_loop_block(self): self.eat("RBRACK") break else: - raise Exception("Esperado vírgula ou colchete de fechamento") + raise Exception( + "Esperado vírgula ou colchete de fechamento" + ) else: notas = self.parse_expression() # <-- ID passado direto! duracao = self.parse_expression() - loop_instructions.append(('chord', notas, duracao)) - + loop_instructions.append(("chord", notas, duracao)) + self.eat("RBRACE") # Agora executar o loop N vezes for _ in range(repeat_count): for instr in loop_instructions: tipo, *args = instr - if tipo == 'play': - note, duracao = args - #print(f"loop block play {note}") - #print(f'esse é o canal LOOP play: {self.instrumento_atual.canal}') - node = PlayNode(note, duracao, self.instrumento_atual.canal) + if tipo == "play": + note, duracao = args + # print(f"loop block play {note}") + # print(f'esse é o canal LOOP play: {self.instrumento_atual.canal}') + node = PlayNode( + note, duracao, self.instrumento_atual.canal + ) self.instrumento_atual.add_event("note", node) - elif tipo == 'chord': + elif tipo == "chord": notas, duracao = args - #print(f"loop block chord{notas}") - #print(f'esse é o canal LOOP chord: {self.instrumento_atual.canal}') - node = ChordNode(notas, duracao, canal=self.instrumento_atual.canal) + # print(f"loop block chord{notas}") + # print(f'esse é o canal LOOP chord: {self.instrumento_atual.canal}') + node = ChordNode( + notas, duracao, canal=self.instrumento_atual.canal + ) self.instrumento_atual.add_event("chord", node) - def parse_if_block(self): - #print("novo bloco IF \n################################################ ") + # print("novo bloco IF \n################################################ ") self.eat("IF") self.parse_condition() self.eat("LBRACE") @@ -851,21 +931,22 @@ def parse_if_block(self): self.eat("RBRACE") def parse_condition(self): - left = self.eat("ID").value - op = self.eat(self.current_token().type).value - right = self.parse_expression() - #print(f"condição: {left} {op} {right}") + self.eat("ID").value + self.eat(self.current_token().type).value + self.parse_expression() def peek(self): - return self.tokens[self.pos + 1] if self.pos + 1 < len(self.tokens) else Token("EOF", "") - - - + return ( + self.tokens[self.pos + 1] + if self.pos + 1 < len(self.tokens) + else Token("EOF", "") + ) ######################################################################################################################### # Main + def main(): if len(sys.argv) != 2: print("Uso: python classes.py arquivo.music") @@ -873,12 +954,12 @@ def main(): filename = sys.argv[1] - if not filename.endswith('.music'): + if not filename.endswith(".music"): print("Erro: o arquivo deve ter extensão .music") sys.exit(1) try: - with open(filename, 'r') as file: + with open(filename, "r") as file: content = file.read() except FileNotFoundError: print(f"Erro: arquivo '{filename}' não encontrado.") @@ -892,9 +973,16 @@ def main(): context = {} # contexto inicial vazio music_node.evaluate(context) # chama Evaluate para gerar o .mid - #build_midi_sequencial(context, bpm=music_node.bpm) + # build_midi_sequencial(context, bpm=music_node.bpm) print(context) - criar_midi_com_duracoes(context, music_node.bpm, music_node.nome, music_node.instrumentos, instrument_map) + criar_midi_com_duracoes( + context, + music_node.bpm, + music_node.nome, + music_node.instrumentos, + instrument_map, + ) + if __name__ == "__main__": main()