Пример #1
0
def test_percussive_samples():
    mod = load_file(TEST_PATH / 'androidr.mod')
    assert percussive_samples(mod) == {2, 3, 4}

    mod = load_file(TEST_PATH / 'big_blunts.mod')
    assert percussive_samples(mod) == {17, 21}

    mod = load_file(TEST_PATH / 'boner.mod')
    assert percussive_samples(mod) == {1, 2, 3, 6}

    mod = load_file(TEST_PATH / 'lambada.mod')
    assert percussive_samples(mod) == {2, 4, 5, 6}

    mod = load_file(TEST_PATH / 'mist-eek.mod')
    assert percussive_samples(mod) == {10, 11, 12}

    # This is a difficult one
    mod = load_file(TEST_PATH / 'zodiak_-_gasp.mod')
    assert percussive_samples(mod) == {3, 4}

    # Percussive instruments are repeating...
    mod = load_file(TEST_PATH / 'alfrdchi_endofgame1.mod')
    assert percussive_samples(mod) == {1, 2, 3, 4, 5, 6, 8}

    # 1 and 2 are chord samples incorrectly classified as drums.
    mod = load_file(TEST_PATH / 'afro_afro.mod')
    assert percussive_samples(mod) == {1, 2, 5, 6, 7, 8}
Пример #2
0
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
Пример #3
0
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)
Пример #4
0
def extract_mod_file_samples(mod_file):
    mod = load_file(mod_file)
    samples = load_samples(mod)

    name_prefix = mod_file.stem
    for idx, sample in enumerate(samples):
        fname = '%s-%02d.wav' % (name_prefix, idx + 1)
        write_sample(sample, fname)
Пример #5
0
def mod_file_to_patterns(mod_file):
    SP.print(str(mod_file))
    try:
        mod = load_file(mod_file)
    except PowerPackerModule:
        return []
    rows = linearize_rows(mod)
    volumes = [header.volume for header in mod.sample_headers]
    notes = rows_to_mod_notes(rows, volumes)
    percussive = {s for (s, p) in sample_props(mod, notes) if p.is_percussive}
    return [pattern_to_matrix(pat, percussive) for pat in mod.patterns]
Пример #6
0
def main():
    parser = ArgumentParser(description='Module stripper')
    parser.add_argument('input', type=FileType('rb'))
    parser.add_argument('output', type=FileType('wb'))
    parser.add_argument('--samples', help='Samples to keep (default: all)')
    parser.add_argument('--pattern-table',
                        help='Pattern table (default: existing)')
    parser.add_argument('--channels', help='Channels to keep (default: all)')
    parser.add_argument('--info',
                        help='Print module information',
                        action='store_true')
    args = parser.parse_args()
    args.input.close()
    args.output.close()
    mod = load_file(args.input.name)

    sp = StructuredPrinter(args.info)

    # Parse sample indices
    sample_indices = list(range(1, 32))
    if args.samples:
        sample_indices = parse_comma_list(args.samples)

    # Parse channel indices
    col_indices = list(range(4))
    if args.channels:
        col_indices = [c - 1 for c in parse_comma_list(args.channels)]

    # Print pattern table
    pattern_table = [mod.pattern_table[i] for i in range(mod.n_orders)]
    s = ' '.join(map(str, pattern_table))
    sp.print('Input pattern table: %s', s)

    if args.pattern_table:
        # Parse pattern indices
        pattern_indices = parse_comma_list(args.pattern_table)
        # Install new pattern table
        update_pattern_table(mod, pattern_indices)

    # Strip effects
    for pattern in mod.patterns:
        for i in range(4):
            strip_column(pattern.rows, i, sample_indices, col_indices)

    sp.header('Output patterns')
    for idx, pattern in enumerate(mod.patterns):
        sp.header('Pattern', '%2d', idx)
        for row in pattern.rows:
            sp.print(row_to_string(row))
        sp.leave()
    sp.leave()

    save_file(args.output.name, mod)
Пример #7
0
def mod_file_to_piano_roll(file_path):
    SP.header('PARSING %s' % str(file_path))
    try:
        mod = load_file(file_path)
    except PowerPackerModule:
        SP.print('PowerPacker module.')
        return None
    rows = linearize_rows(mod)
    volumes = [header.volume for header in mod.sample_headers]
    notes = rows_to_mod_notes(rows, volumes)
    props = sample_props(mod, notes)
    mat = notes_to_matrix(notes, props, len(rows))
    SP.leave()
    return mat
Пример #8
0
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()
Пример #9
0
def main():
    parser = ArgumentParser(description='Sample synthesizer and player')
    parser.add_argument('module', type=FileType('rb'))
    parser.add_argument('--samples',
                        required=True,
                        help='Indices of samples to play')
    parser.add_argument('--period', required=True, help='Sample period')
    parser.add_argument('--volume', type=int)
    args = parser.parse_args()
    args.module.close()

    mod = load_file(args.module.name)
    samples = load_samples(mod)

    init_player(SAMPLE_RATE)

    sample_indices = [int(s) - 1 for s in args.samples.split(',')]
    note_idx = notestr_to_idx(args.period)
    freq = FREQS[note_idx]
    for sample_idx in sample_indices:
        header = mod.sample_headers[sample_idx]
        name = header.name
        volume = args.volume
        if not args.volume:
            volume = header.volume
        fine_tune = header.fine_tune
        sample = samples[sample_idx]

        length = header.size * 2
        repeat_from = header.repeat_from
        repeat_len = 0 if header.repeat_len < 2 else header.repeat_len

        print(f'*** Sample "{name}" (#{sample_idx + 1}) ***')
        print(f'Volume     : {volume}')
        print(f'Fine tune  : {fine_tune}')
        print(f'Length     : {length}')
        print(f'Repeat from: {repeat_from}')
        print(f'Repeat len : {repeat_len}')

        if volume == 0:
            volume = 64

        play_sample_at_freq(sample, freq, volume)
        sleep(0.5)
Пример #10
0
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')
Пример #11
0
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)
Пример #12
0
def test_rows_to_string():
    mod = load_file(TEST_PATH / 'entity.mod')
    str = rows_to_string(mod.patterns[0].rows)
    assert len(str) == 64 * (10 * 4 + 7) - 1

    assert mod.patterns[0].rows[0][0].period == 509
Пример #13
0
def test_weird_cells():
    mod = load_file(TEST_PATH / 'drive_faster.mod')
    volumes = [header.volume for header in mod.sample_headers]
    notes = column_to_mod_notes(mod.patterns[0].rows, 1, volumes)
    assert len(notes) == 32
Пример #14
0
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
Пример #15
0
def test_load_samples():
    mod = load_file(TEST_PATH / 'entity.mod')
    samples = load_samples(mod)
    assert samples[13].repeat_len == 0
Пример #16
0
def test_broken_mod():
    mod = load_file(TEST_PATH / 'operation_wolf-wolf31.mod')
    volumes = [header.volume for header in mod.sample_headers]
    notes = column_to_mod_notes(mod.patterns[1].rows, 3, volumes)
Пример #17
0
        vol_idx = sample_idx - 1
        if not 0 <= vol_idx < len(volumes):
            fmt = 'Sample %d out of bounds at cell %4d:%d. MOD bug?'
            SP.print(fmt % (sample_idx, row_idx, col_idx))
            continue
        vol = mod_note_volume(volumes[vol_idx], cell)
        pitch_idx = period_to_idx(period)
        assert 0 <= pitch_idx < 60

        note = ModNote(row_idx, col_idx, sample_idx, pitch_idx, vol, time_ms)
        notes.append(note)

    # Add durations
    for n1, n2 in zip(notes, notes[1:]):
        n1.duration = n2.row_idx - n1.row_idx
    if notes:
        notes[-1].duration = len(rows) - notes[-1].row_idx
    return notes


def rows_to_mod_notes(rows, volumes):
    return flatten([column_to_mod_notes(rows, i, volumes) for i in range(4)])


if __name__ == '__main__':
    from sys import argv
    from musicgen.parser import load_file
    mod = load_file(argv[1])
    for play_order, rows in linearize_subsongs(mod, 1):
        print(play_order)
Пример #18
0
def test_load_stk_module():
    mod = load_file(TEST_PATH / '3ddance.mod')
    assert mod.n_orders == 28
Пример #19
0
def test_sample_length():
    mod = load_file(TEST_PATH / 'his_hirsute_ant.mod')
    assert len(mod.samples[0].bytes) == 0x23ca
Пример #20
0
def test_loading_truncated_module():
    mod = load_file(TEST_PATH / 'after-the-rain.mod')
    assert len(mod.samples[8].bytes) == 7990
Пример #21
0
def test_weird_magic():
    # This mod has the signature "M&K!"
    mod = load_file(TEST_PATH / 'im_a_hedgehog.mod')
    assert mod.n_orders == 13
Пример #22
0
def test_protracker_15_sample_module():
    mod = load_file(TEST_PATH / 'am-fm_-_0ldsk00l_w1z4rd.mod')
    for i in range(15, 31):
        assert len(mod.samples[i].bytes) == 0
Пример #23
0
def test_loading_tricky_mods():
    mod = load_file(TEST_PATH / 'pachabel.mod')
    assert len(mod.samples[2].bytes) == 0x1ee1c
Пример #24
0
def main():
    args = docopt(__doc__, version='MOD Melody Extractor 1.0')

    # Argument parsing
    SP.enabled = args['--verbose']
    input_file = args['<input-mod>']
    output_file = args['<output-mod>']
    max_distance = int(args['--max-distance'])
    min_length = int(args['--min-length'])
    min_unique = int(args['--min-unique'])
    max_repeat = int(args['--max-repeat'])
    mu_factor = float(args['--mu-factor'])
    mu_threshold = int(args['--mu-threshold'])
    trailer = int(args['--trailer'])
    transpose = not args['--no-transpose']

    # Load mod
    mod = load_file(input_file)
    rows = linearize_rows(mod)

    # Extract and filter melodies
    melodies = flatten(
        extract_sample_groups(rows, col_idx, max_distance, mu_factor,
                              mu_threshold) for col_idx in range(4))
    melodies = [
        melody for (melody, msg) in melodies
        if is_melody(melody, min_length, min_unique, max_repeat)
    ]

    if transpose:
        melodies = [move_to_c(melody) for melody in melodies]
    melodies = [
        remove_ending_silence(melody)
        for melody in filter_duplicate_melodies(melodies)
    ]
    SP.header('%d MELODIES' % len(melodies))
    for melody in melodies:
        for cell in melody:
            SP.print(cell_to_string(cell))
        SP.print('')
    SP.leave()
    melodies = [add_trailer(melody, trailer) for melody in melodies]
    if not melodies:
        fmt = 'Sorry, found no melodies in "%s"!'
        print(fmt % args.input_module.name)
        exit(1)

    cells = flatten(melodies)
    rows = [[c, ZERO_CELL, ZERO_CELL, ZERO_CELL] for c in cells]

    patterns = list(rows_to_patterns(rows))
    n_patterns = len(patterns)

    pattern_table = list(range(n_patterns)) + [0] * (128 - n_patterns)

    mod_out = dict(title=mod.title,
                   sample_headers=mod.sample_headers,
                   n_orders=n_patterns,
                   restart_pos=0,
                   pattern_table=bytearray(pattern_table),
                   initials='M.K.'.encode('utf-8'),
                   patterns=patterns,
                   samples=mod.samples)
    save_file(output_file, mod_out)