Lift
A sustained chord with shimmering high notes.
Customize
import {Scene, Video} from "odori";
import {BedLift} from "../components/bed-lift/bed-lift";
<Video>
<Scene id="bed-lift" duration="10s">
<BedLift />
</Scene>
</Video>Installation
pnpm odori add audio bed-liftnpx odori add audio bed-liftbunx odori add audio bed-liftEdit the source in videos/components/bed-lift/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files into your project
videos/components/bed-lift/bed-lift.tsx
import {
chord,
defineCue,
gain,
lowPass,
mix,
normalize,
note,
pad,
sequence,
shape,
sine,
step,
triangle,
type CueDefinition,
} from "odori";
export type BedLiftOptions = {
/** Tempo of the shimmer. Faster reads brighter, not louder. */
bpm?: number;
/** Root of the held chord, as a note name. */
root?: string;
/** Peak level before the mix normalizes the whole track. */
peak?: number;
};
/**
* Four bars with somewhere to be: a major add9 held underneath and a quiet
* arpeggio circling above it on the eighths. The shimmer is the point — it
* gives a cut forward motion without a drum in sight — and it stays quiet
* enough that it colours the bed rather than performing over it.
*
* The brightest voice is low passed just above a kilohertz, so the motion is
* felt more than heard and the voice keeps the range it needs.
*/
export const bedLift = ({bpm = 96, root = "c3", peak = 0.55}: BedLiftOptions = {}): CueDefinition => {
const beat = step(bpm, 4);
const bars = 4;
const samples = beat * 4 * bars;
const tones = chord(root, "add9");
// The circling order: root, fifth, third, ninth. Up and never quite back.
const circle = [0, 2, 1, 3];
return defineCue({
name: "bed.lift",
durationInFrames: Math.round((samples / 48000) * 30),
loops: true,
params: {bpm, root, peak},
render: () =>
normalize(
mix(
// The held chord, warm and below everything.
gain(lowPass(pad(tones, samples, {envelope: {attack: 1.4, sustain: 1, release: 1.4}}), 1000), 0.85),
// The shimmer: one soft voice per eighth, an octave up, each gone
// before the next arrives.
lowPass(
sequence(
Array.from({length: 8 * bars}, (_, index) => ({
at: index,
play: (length: number) =>
shape(triangle(length, tones[circle[index % circle.length]] * 2), {
attack: 0.015,
decay: 0.32,
sustain: 0,
release: 0.08,
}),
// The bar line leans in slightly, which is all the grid a bed needs.
gain: index % 8 === 0 ? 0.2 : 0.12,
})),
{bpm, division: 8, samples},
),
1600,
),
// A floor under the root.
gain(sine(samples, note(root) / 2), 0.22),
),
peak,
),
});
};
videos/components/bed-lift/bed-lift.preview.tsx
import {CueWave, defineComponentPreview} from "odori/preview";
import {bedLift, type BedLiftOptions} from "./bed-lift";
const Wave = ({bpm, root, peak}: BedLiftOptions) => <CueWave cue={bedLift({bpm, root, peak})} />;
export default defineComponentPreview({
title: "Lift",
category: "Sound/Beds",
description: "A sustained chord with shimmering high notes.",
component: Wave,
canvas: {width: 1920, height: 1080, duration: "5s"},
controls: {
bpm: {type: "number", defaultValue: 96, min: 72, max: 128, step: 4},
root: {type: "select", defaultValue: "c3", options: ["a2", "c3", "d3", "f3"]},
peak: {type: "number", defaultValue: 0.55, min: 0.1, max: 1, step: 0.05},
},
examples: [{name: "Default", props: {}}, {name: "Slower", props: {bpm: 80}}],
});
Add it to a scene
import {Scene, Video} from "odori";
import {BedLift} from "../components/bed-lift/bed-lift";
<Video>
<Scene id="bed-lift" duration="10s">
<BedLift />
</Scene>
</Video>Timing contract
Use these durations and content limits when composing a scene. Run odori test to check your video.
- Family
- Sound
- Brand cue
- bed.lift
- Recommended duration
- 300 frames · 10s
- Minimum duration
- 30 frames · 1s
- Entrance
- 0 frames
- Exit
- 0 frames
- Reduced motion
- the waveform renders without a playhead
- Requires
- mono font
- Content limits
- none