fixes #190
This commit is contained in:
@@ -2,7 +2,6 @@ import css from "./MusicalUnitDisplay.sass?inline"
|
||||
import {Html} from "@opendaw/lib-dom"
|
||||
import {DefaultObservableValue, Lifecycle} from "@opendaw/lib-std"
|
||||
import {createElement} from "@opendaw/lib-jsx"
|
||||
import {PPQN} from "@opendaw/lib-dsp"
|
||||
import {UnitDisplay} from "@/ui/header/UnitDisplay"
|
||||
import {StudioService} from "@/service/StudioService"
|
||||
import {StudioPreferences} from "@opendaw/studio-core"
|
||||
@@ -30,11 +29,10 @@ export const MusicalUnitDisplay = ({lifecycle, service}: Construct) => {
|
||||
lifecycle.ownAll(
|
||||
service.engine.position.catchupAndSubscribe(owner => {
|
||||
const position = owner.getValue()
|
||||
const [nominator, denominator] = service.projectProfileService.getValue().match({
|
||||
some: ({project}) => project.timelineBoxAdapter.signatureTrack.signatureAt(position),
|
||||
none: () => [4, 4]
|
||||
const {bars, beats, semiquavers, ticks} = service.projectProfileService.getValue().match({
|
||||
some: ({project}) => project.timelineBoxAdapter.signatureTrack.toParts(position),
|
||||
none: () => ({bars: 0, beats: 0, semiquavers: 0, ticks: 0})
|
||||
})
|
||||
const {bars, beats, semiquavers, ticks} = PPQN.toParts(Math.abs(position), nominator, denominator)
|
||||
barUnitString.setValue((bars + 1).toString().padStart(3, "0"))
|
||||
beatUnitString.setValue((beats + 1).toString())
|
||||
semiquaverUnitString.setValue((semiquavers + 1).toString())
|
||||
|
||||
@@ -131,7 +131,7 @@ export const TimeAxis = ({lifecycle, service, snapping, range, mapper}: Construc
|
||||
return resolvers
|
||||
}} provider={() => ({
|
||||
unit: "bars",
|
||||
value: (PPQN.toParts(durationInPulses.getValue()).bars + 1).toString()
|
||||
value: (signatureTrack.toParts(durationInPulses.getValue()).bars + 1).toString()
|
||||
})} location={() => {
|
||||
const rect = endMarkerElement.getBoundingClientRect()
|
||||
return {x: rect.left - 32, y: rect.top}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {Terminable} from "@opendaw/lib-std"
|
||||
import {PPQN, ppqn} from "@opendaw/lib-dsp"
|
||||
import {int, Terminable} from "@opendaw/lib-std"
|
||||
import {ppqn} from "@opendaw/lib-dsp"
|
||||
|
||||
export interface VideoOverlayConfig {
|
||||
readonly width: number
|
||||
readonly height: number
|
||||
readonly projectName: string
|
||||
readonly toParts: (position: ppqn) => { bars: int, beats: int, semiquavers: int, ticks: int }
|
||||
}
|
||||
|
||||
export class VideoOverlay implements Terminable {
|
||||
@@ -66,8 +67,8 @@ export class VideoOverlay implements Terminable {
|
||||
|
||||
#renderPosition(position: ppqn, fontSize: number): void {
|
||||
const ctx = this.#ctx
|
||||
const {width, height, projectName} = this.#config
|
||||
const {bars, beats, semiquavers, ticks} = PPQN.toParts(Math.abs(position) | 0)
|
||||
const {width, height, projectName, toParts} = this.#config
|
||||
const {bars, beats, semiquavers, ticks} = toParts(position)
|
||||
const positionFontSize = Math.round(fontSize * 0.7)
|
||||
const charWidth = positionFontSize * 0.65
|
||||
const separatorWidth = positionFontSize * 0.4
|
||||
|
||||
@@ -34,7 +34,8 @@ export const VideoOverlayPreview = ({lifecycle}: Construct) => {
|
||||
const overlay = await VideoOverlay.create({
|
||||
width: WIDTH,
|
||||
height: HEIGHT,
|
||||
projectName: "Dub Techno"
|
||||
projectName: "Dub Techno",
|
||||
toParts: (position) => PPQN.toParts(Math.abs(position) | 0)
|
||||
})
|
||||
lifecycle.own(overlay)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {asInstanceOf, DefaultObservableValue, Option, panic, RuntimeNotifier, TimeSpan} from "@opendaw/lib-std"
|
||||
import {dbToGain, RenderQuantum} from "@opendaw/lib-dsp"
|
||||
import {dbToGain, ppqn, RenderQuantum} from "@opendaw/lib-dsp"
|
||||
import {OfflineEngineRenderer, Project} from "@opendaw/studio-core"
|
||||
import {Files} from "@opendaw/lib-dom"
|
||||
import {ShadertoyState} from "@/ui/shadertoy/ShadertoyState"
|
||||
@@ -60,7 +60,10 @@ export namespace VideoRenderer {
|
||||
}
|
||||
const compositionCanvas = new OffscreenCanvas(width, height)
|
||||
const compositionCtx = compositionCanvas.getContext("2d")!
|
||||
const overlay = await VideoOverlay.create({width, height, projectName})
|
||||
const overlay = await VideoOverlay.create({
|
||||
width, height, projectName,
|
||||
toParts: (position: ppqn) => project.timelineBoxAdapter.signatureTrack.toParts(position)
|
||||
})
|
||||
|
||||
const renderer = await OfflineEngineRenderer.create(project, Option.None, sampleRate)
|
||||
renderer.play()
|
||||
|
||||
@@ -279,6 +279,12 @@ export class SignatureTrackAdapter implements Terminable {
|
||||
return event.accumulatedPpqn + barsFromEvent * barPpqn
|
||||
}
|
||||
|
||||
toParts(position: ppqn): { bars: int, beats: int, semiquavers: int, ticks: int } {
|
||||
const {event} = this.#findSignatureEventAt(position)
|
||||
const parts = PPQN.toParts(position - event.accumulatedPpqn, event.nominator, event.denominator)
|
||||
return {...parts, bars: parts.bars + event.accumulatedBars}
|
||||
}
|
||||
|
||||
barLengthAt(position: ppqn): ppqn {
|
||||
const [nom, denom] = this.signatureAt(position)
|
||||
return PPQN.fromSignature(nom, denom)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {BoxGraph} from "@opendaw/lib-box"
|
||||
import {AudioPitchStretchBox, AudioTimeStretchBox, WarpMarkerBox} from "@opendaw/studio-boxes"
|
||||
import {ppqn} from "@opendaw/lib-dsp"
|
||||
import {ppqn, seconds} from "@opendaw/lib-dsp"
|
||||
import {UUID} from "@opendaw/lib-std"
|
||||
import {WarpMarkerTemplate} from "./WarpMarkerTemplate"
|
||||
|
||||
@@ -8,11 +8,11 @@ export namespace AudioContentHelpers {
|
||||
export const addDefaultWarpMarkers = (boxGraph: BoxGraph,
|
||||
playMode: AudioPitchStretchBox | AudioTimeStretchBox,
|
||||
durationInPPQN: ppqn,
|
||||
durationInSeconds: number) => {
|
||||
durationInSeconds: seconds) => {
|
||||
WarpMarkerBox.create(boxGraph, UUID.generate(), box => {
|
||||
box.owner.refer(playMode.warpMarkers)
|
||||
box.position.setValue(0)
|
||||
box.seconds.setValue(0)
|
||||
box.seconds.setValue(0.0)
|
||||
})
|
||||
WarpMarkerBox.create(boxGraph, UUID.generate(), box => {
|
||||
box.owner.refer(playMode.warpMarkers)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {EmptyExec, Exec, isDefined, isInstanceOf, RuntimeNotifier, UUID} from "@opendaw/lib-std"
|
||||
import {TimeBase} from "@opendaw/lib-dsp"
|
||||
import {seconds, TimeBase} from "@opendaw/lib-dsp"
|
||||
import {
|
||||
AudioPitchStretchBox,
|
||||
AudioRegionBox,
|
||||
@@ -17,6 +17,8 @@ export namespace AudioContentModifier {
|
||||
const audioAdapters = adapters.filter(adapter => !adapter.isPlayModeNoStretch)
|
||||
if (audioAdapters.length === 0) {return EmptyExec}
|
||||
return () => audioAdapters.forEach((adapter) => {
|
||||
const audibleDuration = adapter.optWarpMarkers
|
||||
.mapOr(warpMarkers => warpMarkers.last()?.seconds ?? 0, 0)
|
||||
adapter.box.playMode.defer()
|
||||
adapter.asPlayModeTimeStretch.ifSome(({box}) => {
|
||||
if (box.pointerHub.filter(Pointers.AudioPlayMode).length === 0) {box.delete()}
|
||||
@@ -24,7 +26,7 @@ export namespace AudioContentModifier {
|
||||
adapter.asPlayModePitchStretch.ifSome(({box}) => {
|
||||
if (box.pointerHub.filter(Pointers.AudioPlayMode).length === 0) {box.delete()}
|
||||
})
|
||||
switchTimeBaseToSeconds(adapter)
|
||||
switchTimeBaseToSeconds(adapter, audibleDuration)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,7 +54,8 @@ export namespace AudioContentModifier {
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
AudioContentHelpers.addDefaultWarpMarkers(boxGraph, pitchStretch, adapter.duration, adapter.file.endInSeconds)
|
||||
AudioContentHelpers.addDefaultWarpMarkers(boxGraph, pitchStretch,
|
||||
adapter.duration, adapter.box.duration.getValue())
|
||||
}
|
||||
switchTimeBaseToMusical(adapter)
|
||||
})
|
||||
@@ -93,7 +96,8 @@ export namespace AudioContentModifier {
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
AudioContentHelpers.addDefaultWarpMarkers(boxGraph, timeStretch, adapter.duration, adapter.file.endInSeconds)
|
||||
AudioContentHelpers.addDefaultWarpMarkers(boxGraph, timeStretch,
|
||||
adapter.duration, adapter.box.duration.getValue())
|
||||
}
|
||||
if (isDefined(transients) && adapter.file.transients.length() === 0) {
|
||||
const markersField = adapter.file.box.transientMarkers
|
||||
@@ -106,15 +110,14 @@ export namespace AudioContentModifier {
|
||||
})
|
||||
}
|
||||
|
||||
const switchTimeBaseToSeconds = ({box, file, timeBase}: AudioContentBoxAdapter): void => {
|
||||
const switchTimeBaseToSeconds = ({box, timeBase}: AudioContentBoxAdapter, audibleDuration: seconds): void => {
|
||||
if (timeBase === TimeBase.Seconds) {return}
|
||||
// Reset to 100% playback speed (original file speed)
|
||||
box.timeBase.setValue(TimeBase.Seconds)
|
||||
box.duration.setValue(file.endInSeconds)
|
||||
box.duration.setValue(audibleDuration)
|
||||
box.accept({
|
||||
visitAudioRegionBox: (box: AudioRegionBox) => {
|
||||
box.loopOffset.setValue(0)
|
||||
box.loopDuration.setValue(file.endInSeconds)
|
||||
box.loopDuration.setValue(audibleDuration)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user