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
Exemple #3
0
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
Exemple #5
0
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)
Exemple #6
0
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
Exemple #8
0
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")
Exemple #9
0
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
Exemple #10
0
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