def test_subsongs(): mod = load_file(TEST_PATH / 'beast2-ingame-st.mod') subsongs = list(linearize_subsongs(mod, 1)) orders = [o for (o, _) in subsongs] assert len(orders) == 6 assert orders[0] == [0, 1, 2, 3, 1, 2, 3] assert orders[2] == [14] mod = load_file(TEST_PATH / 'satanic.mod') subsongs = list(linearize_subsongs(mod, 1)) orders = [o for (o, _) in subsongs] assert len(orders) == 114 mod = load_file(TEST_PATH / 'entity.mod') subsongs = list(linearize_subsongs(mod, 1)) assert len(subsongs) == 2
def main(): args = docopt(__doc__, version='MIDI file generator 1.0') SP.enabled = args['--verbose'] # Parse mod_file = args['<mod>'] programs = parse_programs(args['--programs']) mod_file = Path(mod_file) midi_mapping = args['--midi-mapping'] if midi_mapping != 'auto': with open(midi_mapping, 'r') as f: midi_mapping = load(f) midi_mapping = {int(k): v for (k, v) in midi_mapping.items()} mod = load_file(mod_file) subsongs = linearize_subsongs(mod, 1) volumes = [header.volume for header in mod.sample_headers] for idx, (_, rows) in enumerate(subsongs): notes = rows_to_mod_notes(rows, volumes) if midi_mapping == 'auto': props = sample_props(mod, notes) samples = [(sample_idx, props.is_percussive, props.note_duration) for (sample_idx, props) in props.items()] midi_mapping = assign_instruments(samples, programs) midi_file = 'test-%02d.mid' % idx notes_to_midi_file(notes, midi_file, midi_mapping)
def percussive_samples(mod): subsongs = linearize_subsongs(mod, 1) rows = flatten(r for (_, r) in subsongs) volumes = [header.volume for header in mod.sample_headers] notes = list(rows_to_mod_notes(rows, volumes)) return {sample for (sample, p) in sample_props(mod, notes).items() if p.is_percussive}
def mod_file_to_codes_w_progress(i, n, file_path, code_type): SP.header('[ %4d / %4d ] PARSING %s' % (i, n, file_path)) try: mod = load_file(file_path) except UnsupportedModule as e: SP.print('Unsupported module format.') SP.leave() err_arg = e.args[0] if e.args else e.__class__.__name__ yield False, 0, (ERR_PARSE_ERROR, err_arg) return code_mod = CODE_MODULES[code_type] subsongs = list(linearize_subsongs(mod, 1)) volumes = [header.volume for header in mod.sample_headers] parsed_subsongs = [] for idx, (order, rows) in enumerate(subsongs): SP.header('SUBSONG %d' % idx) notes = rows_to_mod_notes(rows, volumes) percussion = guess_percussive_instruments(mod, notes) if notes: fmt = '%d rows, %d ms/row, percussion %s, %d notes' args = (len(rows), notes[0].time_ms, set(percussion), len(notes)) SP.print(fmt % args) err = training_error(notes, percussion) if err: yield False, idx, err else: pitches = {n.pitch_idx for n in notes if n.sample_idx not in percussion} min_pitch = min(pitches, default = 0) # Subtract min pitch for n in notes: n.pitch_idx -= min_pitch code = list(code_mod.to_code(notes, percussion)) if code_mod.is_transposable(): codes = code_mod.code_transpositions(code) else: codes = [code] fmt = '%d transpositions of length %d' SP.print(fmt % (len(codes), len(code))) yield True, idx, codes SP.leave() SP.leave()
def main(): args = docopt(__doc__, version='MIDI file generator 1.0') SP.enabled = args['--verbose'] file_path = args['<mod>'] mod = load_file(file_path) rows = list(linearize_subsongs(mod, 1))[0][1] n_rows = len(rows) sample_headers = mod.sample_headers volumes = [header.volume for header in mod.sample_headers] notes = rows_to_mod_notes(rows, volumes) props = sample_props(mod, notes) mel_notes = {n for n in notes if not props[n.sample_idx].is_percussive} perc_notes = {n for n in notes if props[n.sample_idx].is_percussive} pitches = {n.pitch_idx for n in mel_notes} n_unique_mel_notes = len(pitches) pitch_range = max(pitches) - min(pitches) header = [ '#', 'MC freq', 'Notes', 'Uniq', 'PCs', 'Longest rep', 'Size', 'Dur', 'Repeat pct', 'Max ringout', 'Perc?' ] row_fmt = [ '%2d', '%.2f', '%3d', '%2d', '%2d', '%3d', '%5d', '%2d', '%.2f', '%.2f', lambda x: 'T' if x else 'F' ] # Make a table rows = [(sample, ) + p for (sample, p) in props.items()] print_term_table(row_fmt, rows, header, 'rrrrrrrrrrc') n_chords, n_diss_chords = dissonant_chords(mel_notes) diss_frac = n_diss_chords / n_chords if n_chords else 0.0 header = ['Item', 'Value'] rows = [['Rows', n_rows], ['Melodic notes', len(mel_notes)], ['Percussive notes', len(perc_notes)], ['Unique melodic notes', n_unique_mel_notes], ['Pitch range', pitch_range], ['Chords', n_chords], ['Chord dissonance', '%.2f' % diss_frac]] print_term_table(['%s', '%s'], rows, ['Key', 'Value'], 'lr')
def convert_to_midi(code_type, mod_file): code_mod = CODE_MODULES[code_type] mod = load_file(mod_file) subsongs = linearize_subsongs(mod, 1) volumes = [header.volume for header in mod.sample_headers] for idx, (_, rows) in enumerate(subsongs): notes = rows_to_mod_notes(rows, volumes) percussion = guess_percussive_instruments(mod, notes) pitches = {n.pitch_idx for n in notes if n.sample_idx not in percussion} min_pitch = min(pitches, default = 0) for n in notes: n.pitch_idx -= min_pitch code = list(code_mod.to_code(notes, percussion)) fmt = '%d notes, %d rows, %d tokens, %d ms/row, percussion %s' args = (len(notes), len(rows), len(code), notes[0].time_ms if notes else - 1, set(percussion)) SP.print(fmt % args) row_time = code_mod.estimate_row_time(code) notes = code_mod.to_notes(code, row_time) fname = Path('test-%02d.mid' % idx) notes_to_audio_file(notes, fname, CODE_MIDI_MAPPING, False)
def test_pattern_jump(): mod = load_file(TEST_PATH / 'wax-rmx.mod') subsongs = list(linearize_subsongs(mod, 1)) assert len(subsongs) == 1 assert len(subsongs[0][1]) == 2640