Notification
A soft bell for notifications.
Customize
import {Scene, Video} from "odori";
import {Notify} from "../components/notify/notify";
<Video>
<Scene id="notify" duration="0.9s">
<Notify />
</Scene>
</Video>Installation
pnpm odori add audio notifynpx odori add audio notifybunx odori add audio notifyEdit the source in videos/components/notify/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files into your project
videos/components/notify/notify.tsx
import {defineCue, gain, mix, normalize, note, shape, sine, type CueDefinition} from "odori";
export type NotifyOptions = {
/** The note the bell rings on. */
pitch?: string;
/** How much of the metallic partial is kept, 0 to 1. */
shimmer?: number;
/** Peak level before the mix normalizes the whole track. */
peak?: number;
};
/**
* A soft bell: a fundamental with two inharmonic partials over it, decaying
* together. For a toast arriving or a message landing.
*
* The partials are what make it a bell rather than a beep — they are tuned to
* ratios a struck object actually produces, not to the harmonic series.
*/
export const notify = ({pitch = "a5", shimmer = 0.5, peak = 0.55}: NotifyOptions = {}): CueDefinition => {
const samples = Math.round(48000 * 0.9);
const base = note(pitch);
return defineCue({
name: "ui.notify",
durationInFrames: 27,
params: {pitch, shimmer, peak},
render: () =>
normalize(
mix(
shape(sine(samples, base), {attack: 0.003, decay: 0.7, sustain: 0.02, release: 0.18}),
gain(shape(sine(samples, base * 2.76), {attack: 0.002, decay: 0.35, sustain: 0}), 0.34 * shimmer),
gain(shape(sine(samples, base * 5.4), {attack: 0.002, decay: 0.16, sustain: 0}), 0.18 * shimmer),
),
peak,
),
});
};
videos/components/notify/notify.preview.tsx
import {CueWave, defineComponentPreview} from "odori/preview";
import {notify, type NotifyOptions} from "./notify";
const Wave = ({pitch, shimmer, peak}: NotifyOptions) => <CueWave cue={notify({pitch, shimmer, peak})} />;
export default defineComponentPreview({
title: "Notification",
category: "Sound/Interface",
description: "A soft bell for notifications.",
component: Wave,
canvas: {width: 1920, height: 1080, duration: "2s"},
controls: {
pitch: {type: "select", defaultValue: "a5", options: ["f5", "a5", "c6", "e6"]},
shimmer: {type: "number", defaultValue: 0.5, min: 0, max: 1, step: 0.05},
peak: {type: "number", defaultValue: 0.55, min: 0.1, max: 1, step: 0.05},
},
examples: [{name: "Default", props: {}}, {name: "Pure", props: {shimmer: 0}}],
});
Add it to a scene
import {Scene, Video} from "odori";
import {Notify} from "../components/notify/notify";
<Video>
<Scene id="notify" duration="0.9s">
<Notify />
</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
- ui.notify
- Recommended duration
- 27 frames · 0.9s
- Minimum duration
- 27 frames · 0.9s
- Entrance
- 0 frames
- Exit
- 0 frames
- Reduced motion
- the waveform renders without a playhead
- Requires
- mono font
- Content limits
- none