Beispiel #1
0
def audio_clip_to_reaper_source(clip, path_prefix, tempo):
    file_path = clip.file_path

    # TODO. Hack to point to the right file (thanks to collect?).
    #file_path = "/Users/dcower/Downloads/bug1/Bug1/" + file_path[8:].replace("/", "_")
    file_path = os.path.join(path_prefix, file_path)

    reaper_source_wave = rpp.Element(tag="SOURCE",
                                     attrib=["WAVE"],
                                     children=[
                                         ["FILE", file_path],
                                     ])

    # The length of the stored selected audio sample (the chunk between the start and end sample
    # positions).
    sample_length_seconds = (clip.end_sample_pos - clip.start_sample_pos
                             ) / float(pydel.SAMPLE_RATE_HZ)
    # The actual length of the audio *clip*.
    clip_length_seconds = pydel.pulses_to_seconds(clip.length, tempo)
    playback_rate = sample_length_seconds / clip_length_seconds

    # TODO: Should we round playback rate and clip length to get nicer numbers?
    # playback_rate = round(playback_rate, 4)

    reaper_source_additional_children = [[
        "PLAYRATE",
        playback_rate,
        # 1 = Preserve pitch when changing rate.
        int(clip.pitch_speed_independent),
        # float, pitch adjust, in semitones.cents
        clip.transpose,
        # Default pitch shift mode.
        -1,
        # ?
        0,
        # ?
        0.0025,
    ]]

    section_children = [
        ["STARTPOS", clip.start_sample_pos / float(pydel.SAMPLE_RATE_HZ)],
        ["LENGTH", sample_length_seconds],
    ]

    if clip.reversed:
        section_children.append(["MODE", 2])

    section_children.append(reaper_source_wave)

    reaper_source_section = rpp.Element(tag="SOURCE",
                                        attrib=["SECTION"],
                                        children=section_children)

    return reaper_source_section, reaper_source_additional_children
Beispiel #2
0
def clip_instance_to_reaper_item(clip_instance,
                                 clip,
                                 pretty_clip_idx,
                                 reaper_source,
                                 tempo,
                                 additional_children=None):
    if additional_children is None:
        additional_children = []

    color = util.color_to_reaper(pydel.section_to_color(clip.section))
    volume = clip.params.volume
    pan = clip.params.pan

    start_in_seconds = pydel.pulses_to_seconds(clip_instance.start, tempo)
    length_in_seconds = pydel.pulses_to_seconds(clip_instance.length, tempo)

    return rpp.Element(
        tag="ITEM",
        attrib=[],
        children=[
            ["POSITION", start_in_seconds],
            ["LENGTH", length_in_seconds],
            ["LOOP", 1],
            # Item GUID.
            ["IGUID", util.generate_guid()],
            # First take GUID.
            ["GUID", util.generate_guid()],
            ["NAME", "Clip {}".format(pretty_clip_idx)],
            ["VOLPAN", volume, pan],
            ["COLOR", color, "B"],
            # I think this is a no-op?
            ["POOLCOLOR", color, "B"],
            reaper_source,
        ] + additional_children)
Beispiel #3
0
def midi_clip_to_reaper_source(clip, clip_idx, length,
                               midi_clip_idx_to_reaper_midi_pool):
    midi_messages = convert_notes_to_midi(clip.channel, clip.notes,
                                          clip.length, length)

    # TODO: Move pooling into something else -- clip_idx shouldn't come into here.

    # TODO for MIDI pooling:
    # - If we have only condition codes >= 0x14, we can pool all instances and just create the largest
    #   instance.
    #   Right now, without this, if there are 2 instances *with condition codes* with different
    #   lengths, they will not be pooled.
    if (clip_idx in midi_clip_idx_to_reaper_midi_pool and midi_messages
            == midi_clip_idx_to_reaper_midi_pool[clip_idx].midi_messages):
        reaper_midi_pool = midi_clip_idx_to_reaper_midi_pool[clip_idx]
        # Skip putting the MIDI data into the pool.
        midi_messages = []
    else:
        reaper_midi_pool = ReaperMidiPool()
        reaper_midi_pool.guid = util.generate_guid()
        reaper_midi_pool.pooledevts = util.generate_guid()
        reaper_midi_pool.midi_messages = midi_messages
        midi_clip_idx_to_reaper_midi_pool[clip_idx] = reaper_midi_pool

    return rpp.Element(tag="SOURCE",
                       attrib=["MIDIPOOL"],
                       children=[
                           ["HASDATA", [1, pydel.PPQN, "QN"]],
                           ["POOLEDEVTS", reaper_midi_pool.pooledevts],
                           ["GUID", reaper_midi_pool.guid],
                       ] + midi_messages)
Beispiel #4
0
def project_to_reaper_tracks(project, path_prefix):
    midi_clip_idx_to_reaper_midi_pool = {}
    reaper_tracks = []

    # Deluge instruments/tracks are stored bottom-to-top.
    for instrument in reversed(project.instruments):
        guid = util.generate_guid()

        reaper_items = []

        # TODO: Add unused clip instances to the end of the timeline.

        # Add the clip ("item" in REAPER) instances.
        for clip_instance in instrument.clip_instances:
            # Clip index is encoded as an arrange-only clip.
            # TODO: Deal with this as a clip-level abstraction?
            if clip_instance.clip_idx & 0x80000000:
                clip_idx = clip_instance.clip_idx - 0x80000000
                clip = project.arrange_only_clips[clip_idx]
                pretty_clip_idx = -clip_idx
            else:
                clip = project.clips[clip_instance.clip_idx]
                pretty_clip_idx = clip_instance.clip_idx

            reaper_item_additional_children = []

            if clip.has_audio():
                assert instrument.name == clip.track_name
                reaper_source, reaper_item_additional_children = audio_clip_to_reaper_source(
                    clip, path_prefix, project.tempo)
            elif clip.has_midi():  # MIDI, synths, and kits.
                reaper_source = midi_clip_to_reaper_source(
                    clip, clip_instance.clip_idx, clip_instance.length,
                    midi_clip_idx_to_reaper_midi_pool)
            else:
                print("WARNING: Clip has neither audio nor MIDI")
                continue

            reaper_items.append(
                clip_instance_to_reaper_item(clip_instance, clip,
                                             pretty_clip_idx, reaper_source,
                                             project.tempo,
                                             reaper_item_additional_children))

        fx_chain = []
        if type(instrument) is pydel.instrument.Kit:
            fx_chain = [kit.generate_kit_fx_chain(instrument, path_prefix)]

        reaper_tracks.append(
            rpp.Element(
                tag="TRACK",
                attrib=[guid],
                children=[["NAME", instrument.pretty_name()], [
                    "TRACKID", guid
                ], ["MUTESOLO", [int(instrument.muted), 0, 0]]] +
                reaper_items + fx_chain))

    return reaper_tracks
Beispiel #5
0
def convert(args):
    print("Converting {}".format(args.input_file.name))

    try:
        tree = ET.parse(args.input_file)
        root = tree.getroot()

        if root.tag != "song":
            print("ERROR: Root tag is not 'song'.")
            raise Error()
    except:
        print(
            "ERROR: Only songs from Deluge 3.x are supported. Try re-saving song using 3.x firmware."
        )
        print(traceback.format_exc())
        return

    project = pydel.Project.from_element(root)

    # Prefix for file paths -- corresponds to root dir on Deluge SD card.
    input_dir = os.path.dirname(args.input_file.name)
    output_dir = os.path.dirname(args.output_file.name)
    # relpath doesn't handle empty input paths.
    if input_dir == "":
        input_dir = "./"
    path_prefix, songs = os.path.split(os.path.relpath(input_dir, output_dir))

    if songs != "SONGS":
        print(
            "WARNING: Expected song to be in SONGS/ directory. Audio clip paths may be incorrect."
        )
    # TODO: Support collect songs.

    reaper_project = rpp.Element(
        tag="REAPER_PROJECT",
        attrib=["0.1", "5.972/OSX64", "1372525904"],
        children=[
            ["RIPPLE", "0"],
            ["GROUPOVERRIDE", "0", "0", "0"],
            ["AUTOXFADE", "1"],
            ["TEMPO", project.tempo],
            ["PLAYRATE", 1, 0, 0.25, 4],
            ["SAMPLERATE", pydel.util.SAMPLE_RATE_HZ, 1, 0],
        ] + project_to_reaper_tracks(project, path_prefix))

    # TODO: Add markers + unused clips.

    rpp.dump(reaper_project, args.output_file)

    args.input_file.close()
    args.output_file.close()