Example #1
0
def flacize_after_sampling(output_folder,
                           groups,
                           sfzfile,
                           cleanup_aif_files=True):
    new_groups = []

    old_paths_to_unlink = [
        full_path(output_folder, r.attributes['sample']) for group in groups
        for r in group.regions
    ]

    for group in groups:
        # Make one FLAC file per key, to get more compression.
        output = sum([
            list(concat_samples(key_regions, output_folder, note_name(key)))
            for key, key_regions in group_by_attr(
                group.regions, ['key', 'pitch_keycenter']).iteritems()
        ], [])
        new_groups.append(Group(group.attributes, output))

    with open(sfzfile + '.flac.sfz', 'w') as file:
        file.write("\n".join([str(group) for group in new_groups]))

    if cleanup_aif_files:
        for path in old_paths_to_unlink:
            try:
                os.unlink(path)
            except OSError as e:
                print "Could not unlink path: %s: %s" % (path, e)
Example #2
0
def check_for_clipping(
    midiout,
    midi_channel,
    threshold,
    bit_depth,
    audio_interface_name,
):
    time.sleep(1)
    print "Checking for clipping and balance on note %s..." % (
        note_name(CLIPPING_CHECK_NOTE))

    sample_width, data, release_time = generate_sample(
        limit=2.0,
        midiout=midiout,
        note=CLIPPING_CHECK_NOTE,
        velocity=127,
        midi_channel=midi_channel,
        threshold=threshold,
        print_progress=True,
        audio_interface_name=audio_interface_name,
    )
    max_volume = (numpy.amax(numpy.absolute(data)) / float(2**(bit_depth - 1)))

    # All notes off, but like, a lot, again
    for _ in xrange(0, 2):
        all_notes_off(midiout, midi_channel)

    print "Maximum volume is around %8.8f dBFS" % percent_to_db(max_volume)
    if max_volume >= CLIPPING_THRESHOLD:
        print "Clipping detected (%2.2f dBFS >= %2.2f dBFS) at max volume!" % (
            percent_to_db(max_volume), percent_to_db(CLIPPING_THRESHOLD))
        if EXIT_ON_CLIPPING:
            raise ValueError("Clipping detected at max volume!")
Example #3
0
def filename_for(note, velocity):
    return '%s_v%s.aif' % (note_name(note), velocity)
Example #4
0
def sample_program(
    output_folder='foo',
    low_key=21,
    high_key=109,
    max_attempts=8,
    midi_channel=1,
    midi_port_name=None,
    audio_interface_name=None,
    program_number=None,
    flac=True,
    velocity_levels=VELOCITIES,
    key_range=1,
    cleanup_aif_files=True,
    limit=None,
    looping_enabled=False,
    print_progress=False,
    has_portamento=False,
    sample_asc=False,
    sample_rate=SAMPLE_RATE,
):
    if (key_range % 2) != 1:
        raise NotImplementedError("Key skip must be an odd number for now.")

    midiout = open_midi_port(midi_port_name)

    path_prefix = output_folder
    if program_number is not None:
        print "Sampling program number %d into path %s" % (
            program_number, output_folder
        )
    else:
        print "Sampling into path %s" % (output_folder)

    try:
        os.mkdir(path_prefix)
    except OSError:
        pass

    sfzfile = os.path.join(path_prefix, 'file.sfz')
    try:
        regions = sum([group.regions
                       for group in SFZFile(open(sfzfile).read()).groups],
                      [])
        regions = [region for region in regions if region.exists(path_prefix)]
    except IOError:
        regions = []

    if program_number is not None:
        print "Sending program change to program %d..." % program_number
        midiout.send_message([
            CHANNEL_OFFSET + midi_channel, 0xC0, program_number
        ])

    # All notes off, but like, a lot
    for _ in xrange(0, 2):
        all_notes_off(midiout, midi_channel)

    threshold = sample_threshold_from_noise_floor(
        bit_depth,
        audio_interface_name
    )

    check_for_clipping(
        midiout,
        midi_channel,
        threshold,
        bit_depth,
        audio_interface_name
    )

    groups = []
    note_regions = []

    key_range_under = key_range / 2
    key_range_over = key_range / 2
    notes_to_sample = range(
        low_key,
        (high_key - key_range_over) + 1,
        key_range
    )

    for note, velocity, done_note in tqdm(list(all_notes(
        notes_to_sample,
        velocity_levels,
        sample_asc
    ))):
        keys = range(note + key_range_under, note + key_range_over + 1)
        if not keys:
            keys = [note]
        already_sampled_region = first_non_none([
            region for region in regions
            if region.attributes['hivel'] == str(velocity) and
            region.attributes.get(
                'key', region.attributes.get(
                    'pitch_keycenter', None
                )) == str(note)])
        if already_sampled_region is None:
            filename = os.path.join(path_prefix, filename_for(note, velocity))

            if print_progress:
                print "Sampling %s at velocity %s..." % (
                    note_name(note), velocity
                )

            if has_portamento:
                sample_width, data, release_time = generate_sample(
                    limit=PORTAMENTO_PRESAMPLE_LIMIT,
                    midiout=midiout,
                    note=note,
                    velocity=velocity,
                    midi_channel=midi_channel,
                    threshold=threshold,
                    print_progress=print_progress,
                    audio_interface_name=audio_interface_name,
                    sample_rate=sample_rate,
                )
                time.sleep(PORTAMENTO_PRESAMPLE_WAIT)

            for attempt in xrange(0, MAX_ATTEMPTS):
                try:
                    region = generate_and_save_sample(
                        limit=limit,
                        midiout=midiout,
                        note=note,
                        velocity=velocity,
                        midi_channel=midi_channel,
                        filename=filename,
                        threshold=threshold,
                        velocity_levels=velocity_levels,
                        keys=keys,
                        looping_enabled=looping_enabled,
                        print_progress=print_progress,
                        audio_interface_name=audio_interface_name,
                        sample_rate=sample_rate,
                    )
                    if region:
                        regions.append(region)
                        note_regions.append(region)
                        with open(sfzfile, 'w') as file:
                            file.write("\n".join([str(r) for r in regions]))
                    elif PRINT_SILENCE_WARNINGS:
                        print "Got no sound for %s at velocity %s." % (
                            note_name(note), velocity
                        )
                except IOError:
                    pass
                else:
                    break
            else:
                print "Could not sample %s at vel %s: too many IOErrors." % (
                    note_name(note), velocity
                )
        else:
            note_regions.append(already_sampled_region)

        if done_note and len(note_regions) > 0:
            groups.append(level_volume(note_regions, output_folder))
            note_regions = []

    # Write the volume-leveled output:
    with open(sfzfile + '.leveled.sfz', 'w') as file:
        file.write("\n".join([str(group) for group in groups]))

    if flac:
        # Do a FLAC compression pass afterwards
        # TODO: Do this after each note if possible
        # would require graceful re-parsing of FLAC-combined regions
        flacize_after_sampling(
            output_folder,
            groups,
            sfzfile,
            cleanup_aif_files=True
        )
Example #5
0
            region.attributes['end'] = (global_offset + sample_length -
                                        ANTI_CLICK_OFFSET)
            # TODO: make sure endpoint is a zero crossing to prevent clicks
            region.attributes['sample'] = output_filename
            outfile.write("file '%s'\n" % full_path(path, sample))
            global_offset += sample_length

    create_flac(concat_filename, full_path(path, output_filename))
    os.unlink(concat_filename)

    return regions


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description='flac-ize SFZ files into one sprite sample')
    parser.add_argument('files', type=str, help='files to process', nargs='+')
    args = parser.parse_args()

    for filename in args.files:
        for group in SFZFile(open(filename).read()).groups:
            # Make one FLAC file per key, to get more compression.
            output = sum([
                list(concat_samples(regions, filename, note_name(key)))
                for key, regions in tqdm(
                    group_by_attr(group.regions, 'key').iteritems())
            ], [])
            print group.just_group()
            for region in output:
                print region
Example #6
0
def sample_program(
    output_folder='foo',
    low_key=21,
    high_key=109,
    max_attempts=8,
    midi_channel=1,
    midi_port_name=None,
    midi_port_index=None,
    audio_interface_name=None,
    audio_interface_index=None,
    cc_before=None,
    program_number=None,
    cc_after=None,
    flac=True,
    velocity_levels=VELOCITIES,
    key_range=1,
    cleanup_aif_files=True,
    limit=None,
    looping_enabled=False,
    print_progress=False,
    has_portamento=False,
    sample_asc=False,
    sample_rate=SAMPLE_RATE,
):
    if midi_port_name:
        midiout = open_midi_port(midi_port_name)
    else:
        midiout = open_midi_port_by_index(midi_port_index)

    if not audio_interface_name:
        audio_interface_name = get_input_device_name_by_index(
            audio_interface_index)

    path_prefix = output_folder
    if program_number is not None:
        print("Sampling program number %d into path %s" %
              (program_number, output_folder))
    else:
        print("Sampling into path %s" % (output_folder))

    try:
        os.mkdir(path_prefix)
    except OSError:
        pass

    sfzfile = os.path.join(path_prefix, 'file.sfz')
    try:
        regions = sum(
            [group.regions for group in SFZFile(open(sfzfile).read()).groups],
            [])
        regions = [region for region in regions if region.exists(path_prefix)]
    except IOError:
        regions = []

    midi = Midi(midiout, channel=midi_channel)
    for cc in cc_before or []:  # Send out MIDI controller changes
        midi.cc(cc[0], cc[1])
    set_program_number(midiout, midi_channel, program_number)
    for cc in cc_after or []:  # Send out MIDI controller changes
        midi.cc(cc[0], cc[1])

    threshold = sample_threshold_from_noise_floor(bit_depth,
                                                  audio_interface_name)

    check_for_clipping(midiout, midi_channel, threshold, bit_depth,
                       audio_interface_name)

    # Remove repeated velocity levels that might exist in user input
    temp_vel = {int(v) for v in velocity_levels}
    # Sort velocity levels ascending
    velocity_levels = sorted(temp_vel)

    groups = []
    note_regions = []

    zones_to_sample = compute_zones(Zone(low=low_key, high=high_key),
                                    step=key_range)

    for zone, velocity, done_note in tqdm(
            list(all_notes(zones_to_sample, velocity_levels, sample_asc))):
        already_sampled_region = first_non_none([
            region for region in regions
            if region.attributes['hivel'] == str(velocity) and region.
            attributes.get('key', region.attributes.get(
                'pitch_keycenter', None)) == str(zone.center)
        ])
        if already_sampled_region is None:
            filename = os.path.join(path_prefix,
                                    filename_for(zone.center, velocity))

            if print_progress:
                print("Sampling %s at velocity %s..." %
                      (note_name(zone.center), velocity))

            if has_portamento:
                sample_width, data, release_time = generate_sample(
                    limit=PORTAMENTO_PRESAMPLE_LIMIT,
                    midiout=midiout,
                    note=zone.center,
                    velocity=velocity,
                    midi_channel=midi_channel,
                    threshold=threshold,
                    print_progress=print_progress,
                    audio_interface_name=audio_interface_name,
                    sample_rate=sample_rate,
                )
                time.sleep(PORTAMENTO_PRESAMPLE_WAIT)

            for attempt in xrange(0, MAX_ATTEMPTS):
                try:
                    region = generate_and_save_sample(
                        limit=limit,
                        midiout=midiout,
                        zone=zone,
                        velocity=velocity,
                        midi_channel=midi_channel,
                        filename=filename,
                        threshold=threshold,
                        velocity_levels=velocity_levels,
                        looping_enabled=looping_enabled,
                        print_progress=print_progress,
                        audio_interface_name=audio_interface_name,
                        sample_rate=sample_rate,
                    )
                    if region:
                        regions.append(region)
                        note_regions.append(region)
                        with open(sfzfile, 'w') as file:
                            file.write("\n".join([str(r) for r in regions]))
                    elif PRINT_SILENCE_WARNINGS:
                        print("Got no sound for %s at velocity %s." %
                              (note_name(zone.center), velocity))
                except IOError:
                    pass
                else:
                    break
            else:
                print("Could not sample %s at vel %s: too many IOErrors." %
                      (note_name(zone.center), velocity))
        else:
            note_regions.append(already_sampled_region)

        if done_note and len(note_regions) > 0:
            groups.append(level_volume(note_regions, output_folder))
            note_regions = []

    # Write the volume-leveled output:
    with open(sfzfile + '.leveled.sfz', 'w') as file:
        file.write("\n".join([str(group) for group in groups]))

    if flac:
        # Do a FLAC compression pass afterwards
        # TODO: Do this after each note if possible
        # would require graceful re-parsing of FLAC-combined regions
        flacize_after_sampling(
            output_folder,
            groups,
            sfzfile,
            cleanup_aif_files=cleanup_aif_files,
        )