import xml.etree.ElementTree as ET # ----------------------------------------------------------- # FUNÇÃO AUXILIAR: CALCULAR STEPS A PARTIR DE POSIÇÕES DE NOTAS # (Esta função é necessária para a injeção de steps) # ----------------------------------------------------------- def calculate_steps_from_notes(pattern_element, ticks_per_step=12): """ Calcula o array de steps [True, False, ...] a partir das notas XML. :param pattern_element: O objeto ElementTree para a tag . :param ticks_per_step: O número de ticks por step (12 para 1/16 no LMMS). :return: Um array de booleanos representando os steps marcados. """ try: # Pega o número total de steps (ex: 16, 64) total_steps = int(pattern_element.attrib.get('steps', 16)) except ValueError: total_steps = 16 steps = [False] * total_steps notes = pattern_element.findall('note') for note in notes: try: # Posição da nota em ticks note_pos_ticks = int(note.attrib.get('pos', 0)) # Converte a posição em ticks para o índice do step (arredondando) step_index = round(note_pos_ticks / ticks_per_step) if 0 <= step_index < total_steps: steps[step_index] = True except ValueError: # Ignora notas com posição inválida continue return steps # ----------------------------------------------------------- # FUNÇÃO PRINCIPAL: PARSE_BASSLINES (MESCLADA) # ----------------------------------------------------------- def parse_basslines(track): """ Processa uma track (Beat/Bassline), extrai todos os seus instrumentos (sub-tracks), coleta detalhes e calcula os steps para cada pattern. """ # Nome da track principal (ex: "Beat/Bassline 0") track_name = track.attrib.get('name', 'N/A') bbtrack = track.find('./bbtrack') instruments = [] # 'tags' agora é usado para coletar os tipos de plugins para o retorno plugin_tags = set() if bbtrack is None: # Se não houver bbtrack, pode ser uma bassline vazia (como a 1 e 2) return { 'bassline_name': track_name, 'type': 'bassline', 'tags': '', # Retorna tag vazia 'instruments': instruments # Retorna lista vazia } for container in bbtrack.findall('./trackcontainer'): # Itera sobre cada instrumento (sub-track tipo 0) dentro da bassline for instrument_track in container.findall('./track'): instrument_info = {} # Pega o nome do instrumento (ex: "kicker", "snare_hiphop02.ogg") instrument_info['instrument_name'] = instrument_track.attrib.get('name', 'N/A') instrument_info['instrument_type'] = instrument_track.attrib.get('type', 'N/A') # --- Início da Coleta de Detalhes (da sua função base) --- # instrumenttrack = instrument_track.find('./instrumenttrack') if instrumenttrack is not None: instrument_info.update({ 'pitch': instrumenttrack.attrib.get('pitch', ''), 'pan': instrumenttrack.attrib.get('pan', ''), 'vol': instrumenttrack.attrib.get('vol', ''), 'pitchrange': instrumenttrack.attrib.get('pitchrange', ''), 'basenote': instrumenttrack.attrib.get('basenote', ''), 'fxch': instrumenttrack.attrib.get('fxch', ''), 'usemasterpitch': instrumenttrack.attrib.get('usemasterpitch', '') }) # Adiciona o nome real do plugin (kicker, audiofileprocessor, etc.) instrument_tag = instrument_track.find('.//instrument') if instrument_tag is not None: plugin_name = instrument_tag.attrib.get('name') if plugin_name: plugin_tags.add(plugin_name) # Adiciona à tag da bassline instrument_info['plugin_name'] = plugin_name # Salva o nome do plugin # audiofileprocessor = instrument_track.find('.//audiofileprocessor') if audiofileprocessor is not None: instrument_info['audiofileprocessor'] = { 'amp': audiofileprocessor.attrib.get('amp', ''), 'src': audiofileprocessor.attrib.get('src', ''), 'lframe': audiofileprocessor.attrib.get('lframe', ''), 'stutter': audiofileprocessor.attrib.get('stutter', ''), 'interp': audiofileprocessor.attrib.get('interp', ''), 'sframe': audiofileprocessor.attrib.get('sframe', ''), 'looped': audiofileprocessor.attrib.get('looped', ''), 'eframe': audiofileprocessor.attrib.get('eframe', ''), 'reversed': audiofileprocessor.attrib.get('reversed', ''), } # eldata = instrument_track.find('./eldata') if eldata is not None: eldata_info = { 'fwet': eldata.attrib.get('fwet', ''), 'ftype': eldata.attrib.get('ftype', ''), 'fcut': eldata.attrib.get('fcut', ''), 'fres': eldata.attrib.get('fres', '') } elvol = eldata.find('./elvol') if elvol is not None: eldata_info['elvol'] = { 'sustain': elvol.attrib.get('sustain', ''), 'lamt': elvol.attrib.get('lamt', ''), 'lshp': elvol.attrib.get('lshp', ''), 'amt': elvol.attrib.get('amt', ''), 'pdel': elvol.attrib.get('pdel', ''), 'lpdel': elvol.attrib.get('lpdel', ''), 'lspd_numerator': elvol.attrib.get('lspd_numerator', ''), 'lspd_syncmode': elvol.attrib.get('lspd_syncmode', ''), 'latt': elvol.attrib.get('latt', ''), 'ctlenvamt': elvol.attrib.get('ctlenvamt', ''), 'x100': elvol.attrib.get('x100', ''), 'dec': elvol.attrib.get('dec', ''), 'hold': elvol.attrib.get('hold', ''), 'rel': elvol.attrib.get('rel', ''), 'lspd_denominator': elvol.attrib.get('lspd_denominator', ''), 'userwavefile': elvol.attrib.get('userwavefile', ''), 'att': elvol.attrib.get('att', ''), 'lspd': elvol.attrib.get('lspd', '') } elcut = eldata.find('./elcut') if elcut is not None: eldata_info['elcut'] = { 'cutoff': elcut.attrib.get('cutoff', ''), 'q': elcut.attrib.get('q', '') } elres = eldata.find('./elres') if elres is not None: eldata_info['elres'] = { 'res': elres.attrib.get('res', '') } instrument_info['eldata'] = eldata_info # chordcreator = instrument_track.find('./chordcreator') if chordcreator is not None: instrument_info['chordcreator'] = { 'chord_enabled': chordcreator.attrib.get('chord-enabled', ''), 'chordrange': chordcreator.attrib.get('chordrange', ''), 'chord': chordcreator.attrib.get('chord', '') } # arpeggiator = instrument_track.find('./arpeggiator') if arpeggiator is not None: instrument_info['arpeggiator'] = { 'arptime': arpeggiator.attrib.get('arptime', ''), 'arpmode': arpeggiator.attrib.get('arpmode', ''), 'arp': arpeggiator.attrib.get('arp', ''), 'arprange': arpeggiator.attrib.get('arprange', '') } # midiport = instrument_track.find('./midiport') if midiport is not None: instrument_info['midiport'] = { 'outputprogram': midiport.attrib.get('outputprogram', ''), 'inputchannel': midiport.attrib.get('inputchannel', ''), 'outputcontroller': midiport.attrib.get('outputcontroller', ''), 'inputcontroller': midiport.attrib.get('inputcontroller', ''), 'outputchannel': midiport.attrib.get('outputchannel', ''), 'writable': midiport.attrib.get('writable', ''), 'fixedinputvelocity': midiport.attrib.get('fixedinputvelocity', ''), 'basevelocity': midiport.attrib.get('basevelocity', ''), 'readable': midiport.attrib.get('readable', ''), 'fixedoutputvelocity': midiport.attrib.get('fixedoutputvelocity', ''), 'fixedoutputnote': midiport.attrib.get('fixedoutputnote', '') } # --- Fim da Coleta de Detalhes --- # --- INJEÇÃO DA LÓGICA DE STEPS --- # Encontra todos os patterns associados a este instrumento pattern_elements = instrument_track.findall('pattern') patterns_with_steps = [] for pattern_xml in pattern_elements: # Chama a função auxiliar para calcular o array de steps steps_array = calculate_steps_from_notes(pattern_xml) pattern_data = { 'name': pattern_xml.attrib.get('name'), 'pos': pattern_xml.attrib.get('pos'), 'steps': steps_array, # <-- AQUI ESTÃO OS STEPS } patterns_with_steps.append(pattern_data) # Adiciona a lista de patterns (com steps) ao instrumento instrument_info['patterns'] = patterns_with_steps # --- FIM DA INJEÇÃO DE STEPS --- instruments.append(instrument_info) # Lógica de Tags (Melhorada) # Define a tag principal da bassline com base nos plugins encontrados final_tag = '' if 'audiofileprocessor' in plugin_tags: final_tag = 'audiofileprocessor' elif 'kicker' in plugin_tags: final_tag = 'kicker' elif 'tripleoscillator' in plugin_tags: final_tag = 'tripleoscillator' elif plugin_tags: final_tag = list(plugin_tags)[0] # Pega o primeiro que encontrar return { 'bassline_name': track_name, 'type': 'bassline', 'tags': final_tag, 'instruments': instruments } # Se este arquivo for executado diretamente, ele não fará nada if __name__ == "__main__": print("Este arquivo contém a função parse_basslines() e deve ser importado por outro script.")