├── .eslintrc
├── .github
└── workflows
│ └── render-video.yml
├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── README.md
├── out
└── video.mp4
├── package-lock.json
├── package.json
├── remotion.config.ts
├── src
├── Composition.tsx
├── ToneJS
│ ├── EndCard.tsx
│ ├── Note.tsx
│ └── index.tsx
├── Video.tsx
└── index.tsx
└── tsconfig.json
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@remotion"
3 | }
4 |
--------------------------------------------------------------------------------
/.github/workflows/render-video.yml:
--------------------------------------------------------------------------------
1 | on: workflow_dispatch
2 |
3 | name: Render video
4 | jobs:
5 | render:
6 | name: Render video
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@main
10 | - uses: actions/setup-node@main
11 | - run: sudo apt update
12 | - run: sudo apt install ffmpeg
13 | - run: npm i
14 | - run: echo $WORKFLOW_INPUT > input-props.json
15 | env:
16 | WORKFLOW_INPUT: ${{ toJson(github.event.inputs) }}
17 | - run: npm run build -- --props="./input-props.json"
18 | - uses: actions/upload-artifact@v2
19 | with:
20 | name: video.mp4
21 | path: out/video.mp4
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 |
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "bracketSpacing": false,
4 | "jsxBracketSameLine": false,
5 | "useTabs": true,
6 | "overrides": [
7 | {
8 | "files": ["*.yml"],
9 | "options": {
10 | "singleQuote": false
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.tabSize": 2,
3 | "typescript.tsdk": "node_modules/typescript/lib",
4 | "editor.codeActionsOnSave": {
5 | "source.organizeImports": "never",
6 | "source.fixAll": "explicit"
7 | },
8 | "typescript.enablePromptUseWorkspaceTsdk": true
9 | }
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tone.JS X Remotion
2 |
3 | https://user-images.githubusercontent.com/1629785/150182821-67241528-8bb2-4c50-89b2-5acf0984735e.mp4
4 |
5 | ## Commands
6 |
7 | **Start Preview**
8 |
9 | ```console
10 | npm start
11 | ```
12 |
13 | **Render video**
14 |
15 | ```console
16 | npm run build
17 | ```
18 |
19 | **Upgrade Remotion**
20 |
21 | ```console
22 | npm run upgrade
23 | ```
24 |
25 | ## Docs
26 |
27 | Get started with Remotion by reading the [fundamentals page](https://www.remotion.dev/docs/the-fundamentals).
28 |
29 | ## Help
30 |
31 | We provide help [on our Discord server](https://discord.gg/6VzzNDwUwV).
32 |
33 | ## Issues
34 |
35 | Found an issue with Remotion? [File an issue here](https://github.com/remotion-dev/remotion/issues/new).
36 |
37 | ## License
38 |
39 | Note that for some entities a company license is needed. Read [the terms here](https://github.com/remotion-dev/remotion/blob/main/LICENSE.md).
40 |
--------------------------------------------------------------------------------
/out/video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/remotion-dev/tone-js-example/b98ef5d8b575f112b30e74e5ab76bc8ecf8cb9f5/out/video.mp4
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "remotion-template",
3 | "version": "1.0.0",
4 | "description": "My Remotion video",
5 | "scripts": {
6 | "start": "remotion studio src/index.tsx",
7 | "build": "remotion render src/index.tsx tone out/video.mp4",
8 | "upgrade": "remotion upgrade",
9 | "test": "eslint src --ext ts,tsx,js,jsx && tsc"
10 | },
11 | "repository": {},
12 | "license": "UNLICENSED",
13 | "dependencies": {
14 | "@remotion/bundler": "4.0.118",
15 | "@remotion/cli": "4.0.118",
16 | "@remotion/eslint-config": "4.0.118",
17 | "@remotion/media-utils": "4.0.118",
18 | "@remotion/renderer": "4.0.118",
19 | "eslint": "^8.44.0",
20 | "prettier": "^2.8.8",
21 | "react": "^18.2.0",
22 | "react-dom": "^18.2.0",
23 | "remotion": "4.0.118",
24 | "tone": "^14.7.77",
25 | "typescript": "^4.9.5"
26 | },
27 | "devDependencies": {
28 | "@types/react": "^18.2.14",
29 | "@types/web": "^0.0.61"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/remotion.config.ts:
--------------------------------------------------------------------------------
1 | import {Config} from '@remotion/cli/config';
2 |
3 | Config.setVideoImageFormat('jpeg');
4 | Config.setOverwriteOutput(true);
5 |
--------------------------------------------------------------------------------
/src/Composition.tsx:
--------------------------------------------------------------------------------
1 | export const MyComposition = () => {
2 | return null;
3 | };
4 |
--------------------------------------------------------------------------------
/src/ToneJS/EndCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const EndCard: React.FC = () => {
4 | return (
5 |
73 | );
74 | };
75 |
--------------------------------------------------------------------------------
/src/ToneJS/Note.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {AbsoluteFill} from 'remotion';
3 |
4 | export const Note: React.FC<{
5 | note: string;
6 | }> = ({note}) => {
7 | return (
8 |
14 |
33 |
45 | {``}
46 |
47 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/src/ToneJS/index.tsx:
--------------------------------------------------------------------------------
1 | import {useCallback, useEffect, useState} from 'react';
2 | import {
3 | AbsoluteFill,
4 | Audio,
5 | continueRender,
6 | delayRender,
7 | interpolate,
8 | Sequence,
9 | spring,
10 | useCurrentFrame,
11 | useVideoConfig,
12 | } from 'remotion';
13 | import * as Tone from 'tone';
14 | import {audioBufferToDataUrl} from '@remotion/media-utils';
15 | import {Note} from './Note';
16 | import {EndCard} from './EndCard';
17 |
18 | const notesSequence = [
19 | 'E4',
20 | 'E4',
21 | 'E4',
22 | 'E4',
23 | 'E4',
24 | 'E4',
25 | 'E4',
26 | 'G4',
27 | 'C4',
28 | 'D4',
29 | 'E4',
30 | 'F4',
31 | 'F4',
32 | 'F4',
33 | 'F4',
34 | 'F4',
35 | 'E4',
36 | 'E4',
37 | 'E4',
38 | 'E4',
39 | 'E4',
40 | 'D4',
41 | 'D4',
42 | 'E4',
43 | 'D4',
44 | 'D4',
45 | 'E4',
46 | 'D4',
47 | 'G4',
48 | 'E4',
49 | 'E4',
50 | 'E4',
51 | 'E4',
52 | 'E4',
53 | 'E4',
54 | 'E4',
55 | 'G4',
56 | 'C4',
57 | 'D4',
58 | 'E4',
59 | 'F4',
60 | 'F4',
61 | 'F4',
62 | 'F4',
63 | 'F4',
64 | 'E4',
65 | 'E4',
66 | 'E4',
67 | 'E4',
68 | 'G4',
69 | 'G4',
70 | 'F4',
71 | 'D4',
72 | 'C4',
73 | ].map((note) => ({note, duration: '8n'}));
74 |
75 | export const ToneJS: React.FC = () => {
76 | const frame = useCurrentFrame();
77 | const [handle] = useState(() => delayRender());
78 | const [audioBuffer, setAudioBuffer] = useState(null);
79 | const {fps, durationInFrames} = useVideoConfig();
80 |
81 | const lengthInSeconds = durationInFrames / fps;
82 |
83 | const renderAudio = useCallback(async () => {
84 | const toneBuffer = await Tone.Offline(() => {
85 | const synth = new Tone.Synth().toDestination();
86 | const now = Tone.now();
87 | let accumulatedTime = 0;
88 | notesSequence.forEach((sequenceItem) => {
89 | accumulatedTime += Tone.Time(sequenceItem.duration).toSeconds();
90 | synth.triggerAttackRelease(
91 | sequenceItem.note,
92 | sequenceItem.duration,
93 | now + accumulatedTime
94 | );
95 | });
96 | }, lengthInSeconds);
97 |
98 | const buffer = toneBuffer.get() as AudioBuffer;
99 | setAudioBuffer(audioBufferToDataUrl(buffer));
100 |
101 | continueRender(handle);
102 | }, [handle, lengthInSeconds]);
103 |
104 | useEffect(() => {
105 | renderAudio();
106 | }, [renderAudio]);
107 |
108 | let accumulatedFrames = 0;
109 | const sequenceWithFrames = notesSequence.map((sequenceItem) => {
110 | accumulatedFrames += Tone.Time(sequenceItem.duration).toSeconds() * fps;
111 | return {
112 | note: sequenceItem.note,
113 | midiNote: Tone.Midi(sequenceItem.note).toMidi(),
114 | frame: accumulatedFrames,
115 | };
116 | });
117 |
118 | const transition = spring({
119 | fps,
120 | frame: frame - 440,
121 | config: {
122 | damping: 200,
123 | },
124 | });
125 |
126 | const rotateY = interpolate(transition, [0, 0.5], [0, Math.PI / 2], {
127 | extrapolateRight: 'clamp',
128 | extrapolateLeft: 'clamp',
129 | });
130 | const rotateY2 = interpolate(transition, [0.5, 1], [-Math.PI / 2, 0], {
131 | extrapolateLeft: 'clamp',
132 | extrapolateRight: 'clamp',
133 | });
134 |
135 | return (
136 |
141 |
142 |
149 | {sequenceWithFrames
150 | .filter((sequenceItem) => frame > sequenceItem.frame)
151 | .slice(-1)
152 | .map((sequenceItem, i) => {
153 | return (
154 |
159 |
160 |
161 | );
162 | })}
163 |
164 |
165 | {audioBuffer && }
166 |
167 |
172 |
173 |
174 |
175 |
176 | );
177 | };
178 |
179 | export default ToneJS;
180 |
--------------------------------------------------------------------------------
/src/Video.tsx:
--------------------------------------------------------------------------------
1 | import {Composition} from 'remotion';
2 | import ToneJS from './ToneJS';
3 |
4 | export const RemotionVideo: React.FC = () => {
5 | return (
6 | <>
7 |
15 | >
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import {registerRoot} from 'remotion';
2 | import {RemotionVideo} from './Video';
3 |
4 | registerRoot(RemotionVideo);
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "commonjs",
5 | "jsx": "react-jsx",
6 | "outDir": "./dist",
7 | "strict": true,
8 | "noEmit": true,
9 | "lib": ["es2015"],
10 | "esModuleInterop": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------