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)
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!")
def filename_for(note, velocity): return '%s_v%s.aif' % (note_name(note), velocity)
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 )
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
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, )