Skip to content

Commit

Permalink
Normalize tempo of DJ instrumental version
Browse files Browse the repository at this point in the history
  • Loading branch information
john-kurkowski committed Jul 26, 2024
1 parent c33fd28 commit 6894566
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 4 deletions.
75 changes: 75 additions & 0 deletions src/music/render/contextmanagers.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,81 @@ def mute_tracks(tracks: Collection[reapy.core.Track]) -> Iterator[None]:
track.unmute()


@contextlib.contextmanager
def normalize_tempo(project: ExtendedProject) -> Iterator[None]:
"""Normalize the tempo of the project to a single tempo, then restore the project's custom tempo markers.
Assumes the "normal" tempo of the project is whatever singular tempo is set
in the project's settings. Does not inspect the marker values themselves.
Saves the master track tempo envelope data to project's external state
storage (vs. relying on this Python program's memory), deletes the markers,
then sets the markers back.
H/T https://forum.cockos.com/showpost.php?p=1992179&postcount=12
"""
tempo_envelope = project.master_track.envelopes[0]
is_undo_optional = False
envelope_chunk = reapy.RPR.GetEnvelopeStateChunk( # type: ignore[attr-defined]
tempo_envelope, "", 999, is_undo_optional
)[1]
reapy.RPR.SetProjExtState( # type: ignore[attr-defined]
project.id, "MPL_TOGGLETEMPOENV", "temptimesignenv", envelope_chunk
)

num_markers = project.n_tempo_markers
for i in range(num_markers):
reapy.RPR.DeleteTempoTimeSigMarker(project.id, num_markers - i - 1) # type: ignore[attr-defined]

yield

envelope_chunk = reapy.RPR.GetProjExtState( # type: ignore[attr-defined]
project.id, "MPL_TOGGLETEMPOENV", "temptimesignenv", "", 999
)[4]
reapy.RPR.SetEnvelopeStateChunk( # type: ignore[attr-defined]
tempo_envelope, envelope_chunk, is_undo_optional
)

# TODO: why set only the last marker?

(
_,
_,
_,
timepos,
measurepos,
beatpos,
bpm,
timesig_num,
timesig_denom,
lineartempo,
) = reapy.RPR.GetTempoTimeSigMarker( # type: ignore[attr-defined]
project.id,
project.n_tempo_markers - 1,
999.9,
999,
999.9,
999.9,
999,
999,
False,
)

reapy.RPR.SetTempoTimeSigMarker( # type: ignore[attr-defined]
project.id,
project.n_tempo_markers - 1,
timepos,
measurepos,
beatpos,
bpm,
timesig_num,
timesig_denom,
lineartempo,
)

reapy.RPR.UpdateTimeline() # type: ignore[attr-defined]


@contextlib.contextmanager
def toggle_fx_for_tracks(
tracks: Collection[reapy.core.Track], is_enabled: bool
Expand Down
17 changes: 13 additions & 4 deletions src/music/render/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
adjust_render_settings,
avoid_fx_tails,
mute_tracks,
normalize_tempo,
toggle_fx_for_tracks,
)
from .result import RenderResult
Expand Down Expand Up @@ -166,6 +167,7 @@ async def _render_instrumental_dj(
with (
adjust_master_limiter_threshold(project, vocal_loudness_worth),
mute_tracks(tracks_to_mute),
normalize_tempo(project),
):
return await render_version(project, SongVersion.INSTRUMENTAL_DJ)

Expand Down Expand Up @@ -203,6 +205,10 @@ async def _render_stems(
return await render_version(project, SongVersion.STEMS)


def _has_instrumental_dj_difference(project: ExtendedProject) -> bool:
return bool(find_vox_tracks_to_mute(project)) or project.n_tempo_markers > 1


class Process:
"""Encapsulate the state of rendering a Reaper project."""

Expand Down Expand Up @@ -257,7 +263,9 @@ async def process(
)
)

if SongVersion.INSTRUMENTAL_DJ in versions and find_vox_tracks_to_mute(project):
if SongVersion.INSTRUMENTAL_DJ in versions and _has_instrumental_dj_difference(
project
):
results.append(
(
SongVersion.INSTRUMENTAL_DJ,
Expand Down Expand Up @@ -306,9 +314,10 @@ async def process(

self.progress.update(task, advance=1)

if len(results):
# Render causes a project to have unsaved changes, no matter what. Save the user a step.
project.save()
# TODO: REVERT ME. Avoiding saves while developing experimental feature.
# if len(results):
# # Render causes a project to have unsaved changes, no matter what. Save the user a step.
# project.save()

@cached_property
def progress(self) -> rich.progress.Progress:
Expand Down

0 comments on commit 6894566

Please sign in to comment.