def get_audio_file(filename): filename = helper.getCaseInsensitivePath(filename) if not filename or not os.path.exists(filename): return None if filename.lower().endswith('.xa'): wav_filename = helper.getCaseInsensitivePath(filename.lower().replace( '.xa', '.wav')) if not os.path.exists(wav_filename): filename = get_wav_from_xa(filename) else: filename = wav_filename return pydub.AudioSegment.from_file(filename, "wav")
def merge_bgm(bgm_info, input_foldername, output_filename=None): longest_duration = bgm_info['end'] # Find maximum duration of BGM channels = 1 for bgm in bgm_info['data']: filename = helper.getCaseInsensitivePath( os.path.join(input_foldername, bgm['filename'])) print(filename) bgm['file'] = pydub.AudioSegment.from_file(filename) duration = bgm['timestamp'] + len(bgm['file']) / 1000 if bgm['file'].channels > channels: channels = bgm['file'].channels if duration > longest_duration: longest_duration = duration output = pydub.AudioSegment.silent(duration=longest_duration * 1000, frame_rate=48000) output.set_channels(channels) for bgm in bgm_info['data']: output = output.overlay(bgm['file'], position=bgm['timestamp'] * 1000) if output_filename: temp_filename = output_filename else: temp_filename = tmpfile.mkstemp(suffix=".wav") output.export(temp_filename, format="wav") return temp_filename
def get_processed_wav(input_filename, output_filename=None, channels=1, bits=16, rate=48000): input_filename = helper.getCaseInsensitivePath(input_filename) output = get_audio_file(input_filename) if not output: return None made_change = False if output.sample_width != bits // 8: made_change = True output = output.set_sample_width(bits // 8) if output.channels != channels: made_change = True output = output.set_channels(channels) if output.frame_rate != rate: made_change = True output = output.set_frame_rate(rate) if not made_change and input_filename.lower().endswith('.wav'): # This file is already the exact requirements, just return the original return input_filename if output_filename == None: output_filename = tmpfile.mkstemp() #print("Converted {} to {}".format(input_filename, output_filename)) output.export(output_filename, format="wav") return output_filename
def get_duration(filename): filename = helper.getCaseInsensitivePath(filename) sound_file = get_audio_file(filename) if not sound_file: return 0 return len(sound_file) / 1000
def get_audio_file(filename): filename = helper.getCaseInsensitivePath(filename) if not filename or not os.path.exists(filename): return None if filename.lower().endswith('.xa'): filename = get_wav_from_xa(filename) return pydub.AudioSegment.from_file(filename)
def get_wav_from_xa(input_filename): input_filename = helper.getCaseInsensitivePath(input_filename) prefix = "" if os.name != "nt": prefix = "wine" cmd = "{} xa.exe -d \"{}\"".format(prefix, input_filename.replace("/","\\")) subprocess.call(cmd, shell=True) temp_filename = os.path.splitext(input_filename)[0] + ".wav" tmpfile.add_temp_file(temp_filename) return temp_filename
def get_wav_from_pcm(input_filename): input_filename = helper.getCaseInsensitivePath(input_filename) prefix = "" if os.name != "nt": prefix = "wine" wav_filename = os.path.splitext(input_filename)[0] + ".wav" cmd = "{} vgmstream_cli.exe -q -o \"{}\" \"{}\"".format( prefix, wav_filename.replace("/", "\\"), input_filename.replace("/", "\\")) subprocess.call(cmd, shell=True) return wav_filename
def clip_audio(input_filename, output_filename, duration, loop_duration=0.370): filename = helper.getCaseInsensitivePath(input_filename) sound_file = get_audio_file(filename) while duration * 1000 > len(sound_file): if len(sound_file) < loop_duration * 1000: tail_loop = sound_file[::] else: tail_loop = sound_file[-loop_duration * 1000:] sound_file = sound_file.append( tail_loop, crossfade=30 if len(tail_loop) > 30 else len(tail_loop) - 1) sound_file = sound_file[:duration * 1000] sound_file.export(output_filename, format="wav")
def get_wav_from_pcm(input_filename): input_filename = helper.getCaseInsensitivePath(input_filename) prefix = "" if helper.is_wsl(): prefix = "./" elif os.name != "nt": prefix = "wine " wav_filename = os.path.splitext(input_filename)[0] + ".wav" cmd = "{}vgmstream_cli.exe -q -o \"{}\" \"{}\"".format( prefix, helper.get_windows_path(wav_filename), helper.get_windows_path(input_filename)) subprocess.call(cmd, shell=True) return wav_filename
def get_base_audio(input_foldername, bgm_filename, chart_data, no_bgm): if no_bgm: # Find last timestamp last_timestamp = int(sorted(chart_data['timestamp'].keys(), key=lambda x: int(x))[-1]) # TODO: Find a better way to calculate the ending of the audio # Convert last timestamp into a duration and add 2 seconds in # case the final notes ring out for long duration = ((last_timestamp) / 0x12c) + 2 # Create silent audio file output_audio = pydub.AudioSegment.silent(duration=duration * 1000) else: filename = os.path.join(input_foldername, bgm_filename) filename = helper.getCaseInsensitivePath(filename) output_audio = audio.get_audio_file(filename) return output_audio
def write_vas3(input_foldername, output_filename, metadata=None): if not input_foldername: input_foldername = "" if not output_filename: output_filename = "" if not metadata: metadata = json.load( open(os.path.join(input_foldername, "metadata.json"), "r")) with open(output_filename, "wb") as outfile: version = 2 gdx_size = GDX_SIZES[ metadata['type']] if metadata['type'] in GDX_SIZES else 0x14 gdx_start = 0x40 gdx_entry_start = gdx_start + gdx_size data_start = gdx_start + gdx_size + (len(metadata['entries']) * 0x40) data_start_padding = 16 - (data_start % 16) if data_start_padding != 16: data_start += data_start_padding outfile.write("VA3W".encode('ascii')) outfile.write(struct.pack("<B", 1)) outfile.write(struct.pack("<B", 0)) outfile.write(struct.pack("<B", 0)) outfile.write(struct.pack( "<B", version)) # TODO: Add support for saving old archives? outfile.write(struct.pack("<I", len(metadata['entries']))) outfile.write(struct.pack( "<I", gdx_size)) # Change depending on archive version outfile.write(struct.pack("<I", gdx_start)) outfile.write(struct.pack("<I", gdx_entry_start)) outfile.write(struct.pack("<I", data_start)) if outfile.tell() < gdx_start: outfile.write(bytearray([0] * (gdx_start - outfile.tell()))) # Padding outfile.write(metadata['type'].encode('ascii')) outfile.write(struct.pack("<H", metadata['defaults']['default_hihat'])) outfile.write(struct.pack("<H", metadata['defaults']['default_snare'])) outfile.write(struct.pack("<H", metadata['defaults']['default_bass'])) outfile.write( struct.pack("<H", metadata['defaults']['default_hightom'])) outfile.write(struct.pack("<H", metadata['defaults']['default_lowtom'])) outfile.write( struct.pack("<H", metadata['defaults']['default_rightcymbal'])) if metadata['type'] == "GDXH": outfile.write(struct.pack("<B", metadata['gdx_type_unk1'])) outfile.write(struct.pack("<B", metadata['gdx_volume_flag'])) elif metadata['type'] == "GDXG": outfile.write( struct.pack("<H", metadata['defaults']['default_leftcymbal'])) outfile.write( struct.pack("<H", metadata['defaults']['default_floortom'])) outfile.write( struct.pack("<H", metadata['defaults']['default_leftpedal'])) else: print("Unknown type %s" % metadata['type']) exit(1) if outfile.tell() < gdx_entry_start: outfile.write(bytearray( [0] * (gdx_entry_start - outfile.tell()))) # Padding defaults = [metadata['defaults'][x] for x in metadata['defaults']] data_section = bytearray() for entry in metadata['entries']: filename = entry['filename'] if "NoFilename" in entry['flags']: filename = "%04x" % entry['sound_id'] if not os.path.exists(os.path.join(input_foldername, filename)): # Lame way to check if it has an extension for ext in ['wav', 'ogg', 'mp3']: new_filename = "{}.{}".format(filename, ext).replace("\\", "/") if os.path.exists( os.path.join(input_foldername, new_filename)): filename = new_filename break # Build full path filename = os.path.join( input_foldername, os.path.normpath(filename.replace("\\", "/"))) filename = helper.getCaseInsensitivePath(filename) if not os.path.exists(filename): print("Could not find %s" % filename) continue # Set entry filename to just the filename without extension or path entry['filename'] = os.path.splitext(os.path.basename(filename))[0] if 'flags' not in entry: entry['flags'] = [] if 'extra' not in entry: entry['extra'] = 255 # Normal? # try: # rate, raw_data, bits = wavfile.read(filename) # except: # Try using pysoundfile if wavfile failed # If this code works well enough, I can probably get rid of # wavfile for the was3tool since looping isn't required # TODO: Replace this with code to detect if it's a WAV, 16bit, mono, and 48000 and if so, use wavfile instead #print(filename) filename = audio.get_processed_wav(filename, channels=1, rate=48000, bits=16) rate, raw_data, bits = wavfile.read(filename) channels = 1 if len(raw_data.shape) == 1 else raw_data.shape[1] encoded_data = adpcmwave.encode_data(raw_data, channels) sound_flag = 0 for flag in entry['flags']: if flag in FLAG_MAP: sound_flag |= FLAG_MAP[flag] elif type(flag) == int: sound_flag |= flag else: print( "Don't know how to handle flag {}, ignoring...".format( flag)) if version >= 2: if entry['sound_id'] in defaults: sound_flag |= 0x04 elif len(defaults) > 0: # Is this right? sound_flag |= 0x02 # Not a default? volume = entry['volume'] if version < 2: volume = VOLUME_TABLE.index( min(VOLUME_TABLE, key=lambda x: abs(x - entry['volume']))) outfile.write(struct.pack("<I", len(data_section))) outfile.write(struct.pack("<I", len(encoded_data))) outfile.write(struct.pack("<H", channels)) outfile.write(struct.pack("<H", 0x10)) # Will this ever not be 16 bit? outfile.write(struct.pack("<I", rate)) outfile.write(struct.pack( "<I", 0)) # This should always be 0 for v2 I think? outfile.write(struct.pack( "<I", 0)) # This should always be 0 for v2 I think? outfile.write(struct.pack("<B", volume)) outfile.write(struct.pack("<B", entry['pan'])) outfile.write(struct.pack("<H", entry['sound_id'])) outfile.write(struct.pack("<H", sound_flag)) outfile.write(struct.pack("<H", entry['extra'])) filename_bytes = entry['filename'].encode('ascii') outfile.write(filename_bytes[:0x20]) if len(filename_bytes) < 0x20: outfile.write(bytearray([0] * (0x20 - len(filename_bytes)))) data_section += encoded_data padding = 0x10 - (len(data_section) % 0x10) if padding != 0x10: data_section += bytearray([0] * padding) if outfile.tell() < data_start: outfile.write(bytearray([0] * (data_start - outfile.tell()))) # Padding outfile.write(data_section)
def clip_audio(input_filename, output_filename, duration): filename = helper.getCaseInsensitivePath(input_filename) sound_file = get_audio_file(filename)[:duration * 1000] sound_file.export(output_filename, format="wav") print("Generated", output_filename, len(sound_file) / 1000, duration)
def create_wav_from_chart(chart_data, input_foldername, sound_metadata, output_filename, bgm_filename="bgm.wav", tags=None, no_bgm=False, ext="mp3", quality="320k", volume_part=100, volume_bgm=100, volume_auto=100, ignore_auto=False): output_audio = get_base_audio(input_foldername, bgm_filename, chart_data, no_bgm) output_audio = make_silent(output_audio) if volume_bgm != 100: volume_bgm_db = percentage_to_db(volume_bgm) if not volume_bgm_db: output_audio = make_silent(output_audio) elif volume_bgm_db != 0: output_audio += volume_bgm_db sound_files = {} for timestamp_key in sorted(chart_data['timestamp'].keys(), key=lambda x: int(x)): for cd in chart_data['timestamp'][timestamp_key]: if cd['name'] != "note": continue if ignore_auto and (cd['data'].get('auto_volume', 0) != 0 or cd['data'].get('auto_note', 0) != 0): continue if 'volume' not in cd['data']: cd['data']['volume'] = 127 is_auto = cd['data'].get( 'auto_volume') == 1 and cd['data'].get('auto_note') != 0 if is_auto: # Change 2/3 later if other games use different ratios cd['data']['volume'] = int( round(cd['data']['volume'] * (2 / 3))) if 'pan' not in cd['data']: cd['data']['pan'] = 64 volume = 127 # 100% volume pan = 64 # Center wav_filename = "%04x.wav" % int(cd['data']['sound_id']) sound_key = "%04d_%03d_%03d" % (cd['data']['sound_id'], cd['data']['volume'], cd['data']['pan']) if sound_metadata and 'entries' in sound_metadata: for sound_entry in sound_metadata['entries']: if int(sound_entry['sound_id']) == int( cd['data']['sound_id']): volume = sound_entry.get('volume', volume) pan = sound_entry.get('pan', pan) if 'flags' not in sound_entry or "NoFilename" not in sound_entry[ 'flags']: wav_filename = sound_entry['filename'] break if sound_key not in sound_files: if cd['data'].get('volume'): volume = (cd['data']['volume'] / 127) * (volume / 127) * 127 if cd['data'].get('pan'): pan = (cd['data']['pan'] - ((128 - pan) / 2)) / (128 / 2) else: pan = (pan - (128 / 2)) / (128 / 2) wav_filename = find_sound_filename( helper.getCaseInsensitivePath( os.path.join(input_foldername, wav_filename))) if os.path.exists(wav_filename): keysound = audio.get_audio_file(wav_filename) keysound = keysound.pan(pan) db = percentage_to_db((volume / 127) * 100) keysound += db if is_auto: volume_key = volume_auto else: volume_key = volume_part if volume_key != 100: volume_db = percentage_to_db(volume_key) if not volume_db: keysound = make_silent(keysound) elif volume_db != 0: keysound += volume_db sound_files[sound_key] = keysound else: print("Couldn't find file: %s" % wav_filename) if sound_key in sound_files: position = int(timestamp_key) / 0x12c #print("Overlaying sound at %f" % position) output_audio = output_audio.overlay(sound_files[sound_key], position=position * 1000) return output_audio