enzostvs HF Staff commited on
Commit
b162b24
·
0 Parent(s):

music generation

Browse files
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": "next/core-web-vitals"
3
+ }
.gitignore ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+ .yarn/install-state.gz
8
+
9
+ # testing
10
+ /coverage
11
+
12
+ # next.js
13
+ /.next/
14
+ /out/
15
+ .env
16
+
17
+ # production
18
+ /build
19
+
20
+ # misc
21
+ .DS_Store
22
+ *.pem
23
+
24
+ # debug
25
+ npm-debug.log*
26
+ yarn-debug.log*
27
+ yarn-error.log*
28
+
29
+ # local env files
30
+ .env*.local
31
+
32
+ # vercel
33
+ .vercel
34
+
35
+ # typescript
36
+ *.tsbuildinfo
37
+ next-env.d.ts
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile
2
+
3
+ # Use an official Node.js runtime as the base image
4
+ FROM node:18
5
+
6
+ # Set the working directory in the container
7
+ WORKDIR /usr/src/app
8
+
9
+ # Copy package.json and package-lock.json to the container
10
+ COPY package.json package-lock.json ./
11
+
12
+ # Install dependencies
13
+ RUN npm install
14
+
15
+ # Copy the rest of the application files to the container
16
+ COPY . .
17
+
18
+ # Build the Next.js application for production
19
+ RUN npm run build
20
+
21
+ # Expose the application port (assuming your app runs on port 3000)
22
+ EXPOSE 3002
23
+
24
+ # Start the application
25
+ CMD ["npm", "start"]
README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: — AI Jukebox —
3
+ header: mini
4
+ short_description: Generate music powered by AI
5
+ emoji: 🎶
6
+ colorFrom: purple
7
+ colorTo: pink
8
+ sdk: docker
9
+ app_port: 3002
10
+ pinned: false
11
+ license: mit
12
+ ---
13
+
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app/api/generate/cover/route.ts ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest } from "next/server";
2
+
3
+ export async function POST(
4
+ request: NextRequest,
5
+ ) {
6
+ const { prompt } = await request.json();
7
+
8
+ const response = await fetch("https://api-inference.huggingface.co/models/CiroN2022/cd-md-music", {
9
+ method: "POST",
10
+ headers: {
11
+ Authorization: `Bearer ${process.env.HF_TOKEN}`,
12
+ 'Content-Type': 'application/json',
13
+ ['x-use-cache']: "0"
14
+ },
15
+ body: JSON.stringify({
16
+ inputs: prompt,
17
+ }),
18
+ })
19
+ .then((response: any) => {
20
+ if (response.status !== 200) return Response.json({ status: 500, ok: false, message: response.statusText })
21
+
22
+ return response.arrayBuffer()
23
+ })
24
+ .then((response) => {
25
+ return Buffer.from(response)
26
+ })
27
+ .catch((error) => {
28
+ return {
29
+ error: error.message,
30
+ }
31
+ })
32
+
33
+ if ("error" in response) {
34
+ return Response.json({ status: 500, ok: false, message: response.error })
35
+ }
36
+
37
+ // buffer to blob
38
+ const image = Buffer.from(response).toString('base64')
39
+
40
+ return Response.json({
41
+ image: "data:image/png;base64," + image,
42
+ })
43
+
44
+ }
app/api/generate/title/route.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest } from "next/server";
2
+
3
+ export async function POST(
4
+ request: NextRequest,
5
+ ) {
6
+ const { prompt } = await request.json();
7
+
8
+ const response = await fetch("https://api-inference.huggingface.co/models/HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1", {
9
+ method: "POST",
10
+ headers: {
11
+ Authorization: `Bearer ${process.env.HF_TOKEN}`,
12
+ 'Content-Type': 'application/json',
13
+ ['x-use-cache']: "0"
14
+ },
15
+ body: JSON.stringify({
16
+ inputs: `Generate and return only a music title based on the following prompt: ${prompt}`,
17
+ parameters: {
18
+ num_return_sequences: 1,
19
+ return_full_text: false
20
+ }
21
+ }),
22
+ })
23
+ .then((response: any) => {
24
+ if (response.status !== 200) return Response.json({ status: 500, ok: false, message: response.statusText })
25
+
26
+ return response.json()
27
+ })
28
+ .then((response) => {
29
+ let title = response?.[0]?.generated_text;
30
+ title = title?.substring(title.indexOf('"') + 1, title.lastIndexOf('"'))
31
+ return {
32
+ title: title,
33
+ }
34
+ })
35
+ .catch((error) => {
36
+ return {
37
+ error: error.message,
38
+ }
39
+ })
40
+
41
+ if ("error" in response) {
42
+ return Response.json({ status: 500, ok: false, message: response.error })
43
+ }
44
+
45
+ return Response.json({
46
+ title: response?.title,
47
+ })
48
+
49
+ }
app/favicon.ico ADDED
app/layout.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import { Inter } from "next/font/google";
3
+ import "@/assets/globals.css";
4
+
5
+ const inter = Inter({ subsets: ["latin"] });
6
+
7
+ export const metadata: Metadata = {
8
+ title: "Create Next App",
9
+ description: "Generated by create next app",
10
+ };
11
+
12
+ export default function RootLayout({
13
+ children,
14
+ }: Readonly<{
15
+ children: React.ReactNode;
16
+ }>) {
17
+ return (
18
+ <html lang="en">
19
+ <body className={inter.className}>{children}</body>
20
+ </html>
21
+ );
22
+ }
app/page.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Form } from "@/components/form";
2
+ import Image from "next/image";
3
+
4
+ export default function Home() {
5
+ return (
6
+ <section className="min-h-screen p-12 lg:p-24 bg-stone-950">
7
+ <Form>
8
+ <header>
9
+ <h1 className="text-white font-bold text-3xl">
10
+ Start making music with AI
11
+ </h1>
12
+ <h2 className="text-white/70 font-medium mt-2 text-lg">
13
+ In-browser text-to-music generation
14
+ </h2>
15
+ </header>
16
+ </Form>
17
+ </section>
18
+ );
19
+ }
assets/globals.css ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --foreground-rgb: 0, 0, 0;
7
+ --background-start-rgb: 214, 219, 220;
8
+ --background-end-rgb: 255, 255, 255;
9
+ }
10
+
11
+ @media (prefers-color-scheme: dark) {
12
+ :root {
13
+ --foreground-rgb: 255, 255, 255;
14
+ --background-start-rgb: 0, 0, 0;
15
+ --background-end-rgb: 0, 0, 0;
16
+ }
17
+ }
18
+
19
+ body {
20
+ color: rgb(var(--foreground-rgb));
21
+ background: linear-gradient(
22
+ to bottom,
23
+ transparent,
24
+ rgb(var(--background-end-rgb))
25
+ )
26
+ rgb(var(--background-start-rgb));
27
+ }
28
+
29
+ @layer utilities {
30
+ .text-balance {
31
+ text-wrap: balance;
32
+ }
33
+ }
components/form.tsx ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import Image from "next/image";
3
+ import {
4
+ AutoTokenizer,
5
+ MusicgenForConditionalGeneration,
6
+ BaseStreamer,
7
+ } from "@xenova/transformers";
8
+
9
+ import { Prompt } from "@/components/prompt";
10
+ import { Length } from "@/components/length";
11
+ import { Styles } from "@/components/styles";
12
+ import { Moods } from "@/components/moods";
13
+ import { useGeneration } from "@/components/hooks/useGeneration";
14
+ import { useState, useRef, useEffect } from "react";
15
+ import { encodeWAV, MODEL_ID } from "@/utils";
16
+ import classNames from "classnames";
17
+
18
+ class CallbackStreamer extends BaseStreamer {
19
+ [x: string]: any;
20
+ constructor(callback_fn: any) {
21
+ super();
22
+
23
+ this.callback_fn = callback_fn;
24
+ }
25
+
26
+ put(value: any) {
27
+ return this.callback_fn(value);
28
+ }
29
+
30
+ end() {
31
+ return this.callback_fn();
32
+ }
33
+ }
34
+
35
+ export const Form = ({ children }: { children: React.ReactNode }) => {
36
+ const [modelLoaded, setModelLoaded] = useState(false);
37
+ const [progress, setProgress] = useState(0);
38
+ const [statusText, setStatusText] = useState("Loading model (656MB)...");
39
+ const [loadProgress, setLoadProgress] = useState({});
40
+ const [track, setTrack] = useState("");
41
+
42
+ const {
43
+ form,
44
+ setForm,
45
+ formattedPrompt,
46
+ generate,
47
+ results,
48
+ loading,
49
+ setResults,
50
+ } = useGeneration();
51
+
52
+ const modelPromise = useRef(null);
53
+ const tokenizerPromise = useRef(null);
54
+
55
+ useEffect(() => {
56
+ modelPromise.current ??= MusicgenForConditionalGeneration.from_pretrained(
57
+ MODEL_ID,
58
+ {
59
+ progress_callback: (data: any) => {
60
+ if (data.status !== "progress") return;
61
+ setLoadProgress((prev) => ({ ...prev, [data.file]: data }));
62
+ },
63
+ dtype: {
64
+ text_encoder: "q8",
65
+ decoder_model_merged: "q8",
66
+ encodec_decode: "fp32",
67
+ },
68
+ device: "wasm",
69
+ }
70
+ );
71
+ //@ts-ignore
72
+ tokenizerPromise.current ??= AutoTokenizer.from_pretrained(MODEL_ID);
73
+ }, []);
74
+
75
+ useEffect(() => {
76
+ const items = Object.values(loadProgress);
77
+ if (items.length !== 5) return; // 5 files to load
78
+ let loaded = 0;
79
+ let total = 0;
80
+ for (const data of Object.values(loadProgress)) {
81
+ loaded += data.loaded;
82
+ total += data.total;
83
+ }
84
+ const progress = loaded / total;
85
+ setProgress(progress);
86
+ setStatusText(
87
+ progress === 1
88
+ ? "Ready!"
89
+ : `Loading model (${(progress * 100).toFixed()}% of 656MB)...`
90
+ );
91
+ if (progress === 1) {
92
+ setTimeout(() => setModelLoaded(true), 1500);
93
+ }
94
+ }, [loadProgress]);
95
+
96
+ const generateMusic = async () => {
97
+ const tokenizer: any = await tokenizerPromise.current;
98
+ const model: any = await modelPromise.current;
99
+
100
+ if (!tokenizer || !model) return null;
101
+
102
+ const max_length = Math.min(
103
+ Math.max(Math.floor(form.length * 50), 1) + 4,
104
+ model?.generation_config?.max_length ?? 1500
105
+ );
106
+
107
+ const streamer = new CallbackStreamer((value: string) => {
108
+ const percent = value === undefined ? 1 : value[0].length / max_length;
109
+ setStatusText(`Generating (${(percent * 100).toFixed()}%)...`);
110
+ setProgress(percent);
111
+ });
112
+
113
+ const inputs = tokenizer(formattedPrompt);
114
+
115
+ const audio_values = await model.generate({
116
+ ...inputs,
117
+ max_length,
118
+ streamer,
119
+ });
120
+
121
+ setStatusText("Encoding audio...");
122
+
123
+ const sampling_rate = model.config.audio_encoder.sampling_rate;
124
+ const wav = encodeWAV(audio_values.data, sampling_rate);
125
+ const blob = new Blob([wav], { type: "audio/wav" });
126
+ setTrack(URL.createObjectURL(blob));
127
+ setStatusText("Done!");
128
+ };
129
+
130
+ console.log("track is", track);
131
+
132
+ return (
133
+ <main className="grid grid-cols-2 gap-20">
134
+ <div className="grid grid-cols-1 gap-10">
135
+ {children}
136
+ <Prompt
137
+ value={form.prompt}
138
+ onChange={(value) => setForm({ ...form, prompt: value })}
139
+ />
140
+ <Length
141
+ value={form.length}
142
+ onChange={(value) => setForm({ ...form, length: value })}
143
+ />
144
+ <Styles
145
+ value={form.style}
146
+ onChange={(value) => setForm({ ...form, style: value })}
147
+ />
148
+ <Moods
149
+ value={form.mood}
150
+ onChange={(value) => setForm({ ...form, mood: value })}
151
+ />
152
+ </div>
153
+ <div>
154
+ <div className="w-full sticky top-10">
155
+ <div className="border rounded-xl p-6 bg-stone-900/40 border-white/5">
156
+ <p className="text-amber-200 font-semibold text-xs uppercase mb-3">
157
+ Generated prompt
158
+ </p>
159
+ <p className="text-white text-xl font-semibold">
160
+ &quot;{formattedPrompt}&quot;
161
+ </p>
162
+ </div>
163
+ <button
164
+ className="rounded-xl bg-stone-900/90 border-white/5 border px-6 py-3 font-semibold text-base text-white mt-6 hover:brightness-125 transition-all duration-200"
165
+ onClick={() => {
166
+ generate();
167
+ generateMusic();
168
+ }}
169
+ >
170
+ Generate track
171
+ </button>
172
+ {(loading || results?.title || results?.cover) && (
173
+ <div className="mt-6 space-y-6 flex flex-col">
174
+ {results.cover ? (
175
+ <Image
176
+ src={results.cover}
177
+ alt="Cover art"
178
+ className="w-[300px] h-[300px] object-contain bg-amber-950 rounded-xl mx-auto"
179
+ width={300}
180
+ height={300}
181
+ />
182
+ ) : (
183
+ <div className="w-[300px] h-[300px] bg-stone-900 animate-pulse rounded-xl mx-auto"></div>
184
+ )}
185
+ {results.title ? (
186
+ <p className="text-center text-white font-bold text-3xl">
187
+ {results.title}
188
+ </p>
189
+ ) : (
190
+ <div className="w-[450px] h-8 bg-stone-900 animate-pulse rounded-xl mx-auto"></div>
191
+ )}
192
+ {modelLoaded &&
193
+ (track !== "" ? (
194
+ <div className="mx-auto">
195
+ <audio controls type="audio/wav" src={track} />
196
+ </div>
197
+ ) : (
198
+ <div className="mx-auto w-full max-w-sm border rounded-xl p-6 bg-amber-900/10 border-white/10 overflow-hidden transition-all duration-200">
199
+ <p className="text-sm text-left mb-4 uppercase font-medium">
200
+ {statusText}
201
+ </p>
202
+ <div className="bg-gray-200 h-2.5 w-full rounded-full overflow-hidden">
203
+ <div
204
+ className="from-amber-500 to-orange-500 bg-gradient-to-r h-full"
205
+ style={{ width: `${100 * progress}%` }}
206
+ ></div>
207
+ </div>
208
+ </div>
209
+ ))}
210
+ </div>
211
+ )}
212
+ </div>
213
+ </div>
214
+ <div
215
+ className={classNames(
216
+ "absolute right-10 bottom-10 w-full max-w-sm border rounded-xl p-6 bg-amber-900/10 border-white/10 overflow-hidden transition-all duration-200",
217
+ {
218
+ "opacity-0 pointer-events-none translate-y-full": modelLoaded,
219
+ }
220
+ )}
221
+ >
222
+ <p className="text-sm text-left mb-4 uppercase font-medium">
223
+ {statusText}
224
+ </p>
225
+ <div className="bg-gray-200 h-2.5 w-full rounded-full overflow-hidden">
226
+ <div
227
+ className="from-amber-500 to-orange-500 bg-gradient-to-r h-full"
228
+ style={{ width: `${100 * progress}%` }}
229
+ ></div>
230
+ </div>
231
+ </div>
232
+ </main>
233
+ );
234
+ };
components/hooks/useGeneration.ts ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo, useState } from "react"
2
+ import { useUpdateEffect } from "react-use";
3
+
4
+ import { LENGTHS, STYLES, MOODS } from "@/utils";
5
+
6
+ export const useGeneration = () => {
7
+ const [form, setForm] = useState({
8
+ prompt: "80s pop track with bassy drums and synth",
9
+ length: LENGTHS[0].value,
10
+ style: STYLES[5].value,
11
+ mood: MOODS[4].value
12
+ });
13
+ const [loading, setLoading] = useState(false);
14
+ const [results, setResults] = useState<{ cover: string | null, title: string | null }>({
15
+ cover: null,
16
+ title: null,
17
+ });
18
+ const [timeCoverGenerated, setTimeCoverGenerated] = useState(0);
19
+
20
+ const formattedPrompt = useMemo(() => {
21
+ const stylePrompt = STYLES.find((style) => style.value === form.style)?.prompt;
22
+ const moodPrompt = MOODS.find((mood) => mood.value === form.mood)?.prompt;
23
+
24
+ return `${form.prompt} ${stylePrompt} ${moodPrompt ?? ""}`;
25
+ }, [form])
26
+
27
+ useUpdateEffect(() => {
28
+ if (results.cover && results.title) {
29
+ setLoading(false);
30
+ }
31
+ }, [results])
32
+
33
+ const generate = async () => {
34
+ setLoading(true);
35
+ let new_results = {
36
+ cover: null,
37
+ title: null,
38
+ track: null
39
+ }
40
+
41
+ fetch("/api/generate/cover", {
42
+ method: "POST",
43
+ headers: {
44
+ "Content-Type": "application/json"
45
+ },
46
+ body: JSON.stringify({
47
+ prompt: formattedPrompt
48
+ })
49
+ }).then((res) => res.json())
50
+ .then((res: any) => {
51
+ new_results.cover = res.image
52
+ setResults({
53
+ ...new_results,
54
+ })
55
+ })
56
+
57
+ fetch("/api/generate/title", {
58
+ method: "POST",
59
+ headers: {
60
+ "Content-Type": "application/json"
61
+ },
62
+ body: JSON.stringify({
63
+ prompt: form.prompt
64
+ })
65
+ }).then((res) => res.json())
66
+ .then((res: any) => {
67
+ new_results.title = res.title
68
+ setResults({
69
+ ...new_results,
70
+ })
71
+ })
72
+ }
73
+
74
+ return {
75
+ form,
76
+ setForm,
77
+ formattedPrompt,
78
+ generate,
79
+ results,
80
+ loading,
81
+ setResults,
82
+ }
83
+ }
components/length.tsx ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import { useMemo, useRef, useState } from "react";
3
+ import { useClickAway, useUpdateEffect } from "react-use";
4
+ import { FiChevronDown } from "react-icons/fi";
5
+
6
+ import { LENGTHS } from "@/utils";
7
+
8
+ export const Length = ({
9
+ value,
10
+ onChange,
11
+ }: {
12
+ value: number;
13
+ onChange: (value: number) => void;
14
+ }) => {
15
+ const [open, setOpen] = useState(false);
16
+ const ref = useRef<HTMLDivElement>(null);
17
+
18
+ useClickAway(ref, () => {
19
+ setOpen(false);
20
+ });
21
+
22
+ const label = useMemo(() => {
23
+ return LENGTHS.find((length) => length.value === value)?.label;
24
+ }, [value]);
25
+
26
+ useUpdateEffect(() => {
27
+ setOpen(false);
28
+ }, [value]);
29
+
30
+ return (
31
+ <div ref={ref} className="max-w-max">
32
+ <p className="text-white font-semibold text-base mb-4">Prompt</p>
33
+ <div className="relative z-1">
34
+ <p
35
+ className="text-transparent bg-gradient-to-r from-blue-500 to-pink-500 bg-clip-text text-5xl font-extrabold cursor-pointer relative"
36
+ onClick={() => setOpen(!open)}
37
+ >
38
+ {label}s
39
+ <FiChevronDown
40
+ className={classNames(
41
+ "inline-block text-white text-2xl ml-2 transition-all duration-200",
42
+ {
43
+ "transform rotate-180": open,
44
+ }
45
+ )}
46
+ />
47
+ </p>
48
+ <ul
49
+ className={classNames(
50
+ "border-white/10 bg-black shadow-lg absolute top-14 p-3 left-0 w-full border border-gray-800 rounded-lg max-w-max z-20",
51
+ {
52
+ "opacity-0 pointer-events-none": !open,
53
+ }
54
+ )}
55
+ >
56
+ {LENGTHS.map((length) => (
57
+ <li
58
+ key={length.value}
59
+ className="text-white text-base hover:from-blue-500/40 hover:to-pink-500/40 bg-gradient-to-r transition-all duration-200 p-2 rounded-md cursor-pointer"
60
+ onClick={() => onChange(length.value)}
61
+ >
62
+ {length.label} seconds
63
+ </li>
64
+ ))}
65
+ </ul>
66
+ </div>
67
+ </div>
68
+ );
69
+ };
components/moods.tsx ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import { useState } from "react";
3
+ import { FiChevronDown } from "react-icons/fi";
4
+
5
+ import { MOODS } from "@/utils";
6
+
7
+ export const Moods = ({
8
+ value,
9
+ onChange,
10
+ }: {
11
+ value: string;
12
+ onChange: (value: string) => void;
13
+ }) => {
14
+ const [viewAll, setViewAll] = useState(false);
15
+
16
+ return (
17
+ <div>
18
+ <p className="text-white font-semibold text-base mb-4">Select a mood</p>
19
+ <div className="grid grid-cols-4 gap-2 relative z-[1]">
20
+ {MOODS.slice(viewAll ? 0 : 0, viewAll ? MOODS.length : 12).map(
21
+ (style) => {
22
+ return (
23
+ <div
24
+ key={style.label}
25
+ className={classNames(
26
+ `w-full cursor-pointer transition-all duration-200 opacity-70 hover:opacity-100 rounded-xl bg-gradient-to-br bg-stone-900 border border-white/5 relative px-6 py-3.5 text-left flex items-center justify-between font-semibold text-white text-lg z-[1] overflow-hidden`,
27
+ {
28
+ "!opacity-100 brightness-125": style.value === value,
29
+ }
30
+ )}
31
+ onClick={() => onChange(style.value)}
32
+ >
33
+ {style.label}
34
+ <p className="text-3xl">{style.emoji}</p>
35
+ <div className="bg-black/30 -z-[1] w-full h-full absolute top-0 left-0"></div>
36
+ </div>
37
+ );
38
+ }
39
+ )}
40
+ <div
41
+ className={classNames(
42
+ "w-full h-[100px] bg-gradient-to-b from-transparent absolute -bottom-8 left-0 -z-[1] flex items-end justify-center",
43
+ {
44
+ "to-stone-950 !z-[1]": !viewAll,
45
+ }
46
+ )}
47
+ >
48
+ <p
49
+ className="text-white/50 hover:text-white/90 text-sm font-medium mt-2 cursor-pointer hover:underline"
50
+ onClick={() => setViewAll(!viewAll)}
51
+ >
52
+ View all
53
+ <FiChevronDown
54
+ className={classNames(
55
+ "inline-block ml-1 transition-all duration-200",
56
+ {
57
+ "transform rotate-180": viewAll,
58
+ }
59
+ )}
60
+ />
61
+ </p>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ );
66
+ };
components/prompt.tsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const Prompt = ({
2
+ value,
3
+ onChange,
4
+ }: {
5
+ value: string;
6
+ onChange: (value: string) => void;
7
+ }) => {
8
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
9
+ onChange(e.target.value);
10
+ };
11
+ return (
12
+ <div>
13
+ <p className="text-white font-semibold text-base mb-3">Prompt</p>
14
+ <input
15
+ type="text"
16
+ value={value}
17
+ placeholder="80s pop track with bassy drums and synth"
18
+ className="w-full p-2 mt-2 border border-white/10 bg-black/10 transition-all duration-200 focus:border-amber-200/20 focus:bg-amber-950/10 text-white rounded-xl px-5 py-5 text-lg outline-none"
19
+ onInput={handleChange}
20
+ />
21
+ </div>
22
+ );
23
+ };
components/styles.tsx ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import { useState } from "react";
3
+ import { FiChevronDown } from "react-icons/fi";
4
+
5
+ import { STYLES } from "@/utils";
6
+
7
+ export const Styles = ({
8
+ value,
9
+ onChange,
10
+ }: {
11
+ value: string;
12
+ onChange: (value: string) => void;
13
+ }) => {
14
+ const [viewAll, setViewAll] = useState(false);
15
+
16
+ return (
17
+ <div>
18
+ <p className="text-white font-semibold text-base mb-4">Select a style</p>
19
+ <div className="grid grid-cols-3 gap-2 relative z-[1]">
20
+ {STYLES.slice(viewAll ? 0 : 0, viewAll ? STYLES.length : 9).map(
21
+ (style) => (
22
+ <div
23
+ key={style.label}
24
+ className={classNames(
25
+ "w-full cursor-pointer transition-all duration-200 opacity-40 hover:opacity-100 rounded-xl bg-cover bg-center relative px-5 py-8 bg-gray-700 text-center font-bold text-white text-xl z-[1] overflow-hidden",
26
+ {
27
+ "!opacity-100 ring-[4px] ring-white/50":
28
+ style.value === value,
29
+ }
30
+ )}
31
+ style={{
32
+ backgroundImage: `url(${style.image})`,
33
+ }}
34
+ onClick={() => onChange(style.value)}
35
+ >
36
+ {style.label}
37
+ <div className="bg-black/50 -z-[1] w-full h-full absolute top-0 left-0"></div>
38
+ </div>
39
+ )
40
+ )}
41
+ <div
42
+ className={classNames(
43
+ "w-full h-[100px] bg-gradient-to-b from-transparent absolute -bottom-8 left-0 -z-[1] flex items-end justify-center",
44
+ {
45
+ "to-stone-950 !z-[1]": !viewAll,
46
+ }
47
+ )}
48
+ >
49
+ <p
50
+ className="text-white/50 hover:text-white/90 text-sm font-medium mt-2 cursor-pointer hover:underline"
51
+ onClick={() => setViewAll(!viewAll)}
52
+ >
53
+ View all
54
+ <FiChevronDown
55
+ className={classNames(
56
+ "inline-block ml-1 transition-all duration-200",
57
+ {
58
+ "transform rotate-180": viewAll,
59
+ }
60
+ )}
61
+ />
62
+ </p>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ );
67
+ };
next.config.mjs ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ webpack: (config) => {
4
+ // Ignore node-specific modules when bundling for the browser
5
+ // See https://webpack.js.org/configuration/resolve/#resolvealias
6
+ config.resolve.alias = {
7
+ ...config.resolve.alias,
8
+ "sharp$": false,
9
+ "onnxruntime-node$": false,
10
+ }
11
+ return config;
12
+ },
13
+ };
14
+
15
+ export default nextConfig;
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai-music-generation",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev -p 3001",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@xenova/transformers": "github:xenova/transformers.js#v3",
13
+ "classnames": "^2.5.1",
14
+ "next": "14.2.1",
15
+ "react": "^18",
16
+ "react-dom": "^18",
17
+ "react-icons": "^5.1.0",
18
+ "react-use": "^17.5.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20",
22
+ "@types/react": "^18",
23
+ "@types/react-dom": "^18",
24
+ "eslint": "^8",
25
+ "eslint-config-next": "14.2.1",
26
+ "postcss": "^8",
27
+ "tailwindcss": "^3.4.1",
28
+ "typescript": "^5"
29
+ }
30
+ }
postcss.config.mjs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
public/next.svg ADDED
public/vercel.svg ADDED
tailwind.config.ts ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Config } from "tailwindcss";
2
+
3
+ const config: Config = {
4
+ content: [
5
+ "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
7
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
8
+ "./utils/**/*.{js,ts,jsx,tsx,mdx}"
9
+ ],
10
+ theme: {
11
+ extend: {
12
+ backgroundImage: {
13
+ "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
14
+ "gradient-conic":
15
+ "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
16
+ },
17
+ },
18
+ },
19
+ plugins: [],
20
+ };
21
+ export default config;
tsconfig.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["dom", "dom.iterable", "esnext"],
4
+ "allowJs": true,
5
+ "skipLibCheck": true,
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "esModuleInterop": true,
9
+ "module": "esnext",
10
+ "moduleResolution": "bundler",
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "jsx": "preserve",
14
+ "incremental": true,
15
+ "plugins": [
16
+ {
17
+ "name": "next"
18
+ }
19
+ ],
20
+ "paths": {
21
+ "@/*": ["./*"]
22
+ }
23
+ },
24
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "components/form.tsx"],
25
+ "exclude": ["node_modules"]
26
+ }
utils/index.ts ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const LENGTHS = [
2
+ { value: 5, label: "00:05" },
3
+ { value: 10, label: "00:10" },
4
+ { value: 15, label: "00:15" },
5
+ { value: 30, label: "00:30" },
6
+ ];
7
+
8
+ export const STYLES = [{
9
+ value: "hiphop",
10
+ prompt: "hip hop track with a chill vibe",
11
+ label: "Hip Hop",
12
+ image: "https://images.unsplash.com/photo-1601643157091-ce5c665179ab?q=80&w=2072&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
13
+ }, {
14
+ value: "classic",
15
+ prompt: "classic track with a chill vibe",
16
+ label: "Classic",
17
+ image: "https://images.unsplash.com/photo-1519139270028-ab664cf42264?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
18
+ }, {
19
+ value: "jazz",
20
+ prompt: "jazz track with a chill vibe",
21
+ label: "Jazz",
22
+ image: "https://images.unsplash.com/photo-1511192336575-5a79af67a629?q=80&w=2064&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
23
+ }, {
24
+ value: "electro",
25
+ prompt: "jazz track with a chill vibe",
26
+ label: "Electro & Dance",
27
+ image: "https://images.unsplash.com/photo-1622386010273-646e12d1c02f?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
28
+ }, {
29
+ value: "rock",
30
+ prompt: "jazz track with a chill vibe",
31
+ label: "Rock'N'Roll",
32
+ image: "https://plus.unsplash.com/premium_photo-1681876467464-33495108737c?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
33
+ }, {
34
+ value: "funk",
35
+ prompt: "jazz track with a chill vibe",
36
+ label: "Funk",
37
+ image: "https://plus.unsplash.com/premium_photo-1683129651802-1c7ba429a137?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
38
+ }, {
39
+ value: "dubstep",
40
+ prompt: "jazz track with a chill vibe",
41
+ label: "Dubstep",
42
+ image: "https://images.unsplash.com/photo-1578946956088-940c3b502864?q=80&w=2046&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
43
+ }, {
44
+ value: "afrobeats",
45
+ prompt: "jazz track with a chill vibe",
46
+ label: "Afrobeats",
47
+ image: "https://plus.unsplash.com/premium_photo-1702220976033-50f47c7a58a6?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
48
+ }, {
49
+ value: "orchestral",
50
+ prompt: "jazz track with a chill vibe",
51
+ label: "Orchestral",
52
+ image: "https://plus.unsplash.com/premium_photo-1682098438728-fa774b584c18?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
53
+ }, {
54
+ value: "pop",
55
+ prompt: "jazz track with a chill vibe",
56
+ label: "Pop",
57
+ image: "https://images.unsplash.com/photo-1520872024865-3ff2805d8bb3?q=80&w=2104&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
58
+ }, {
59
+ value: "reggae",
60
+ prompt: "jazz track with a chill vibe",
61
+ label: "Reggae",
62
+ image: "https://images.unsplash.com/photo-1538598450935-581f6a5fa7e0?q=80&w=2088&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
63
+ }, {
64
+ value: "metal",
65
+ prompt: "jazz track with a chill vibe",
66
+ label: "Metal",
67
+ image: "https://images.unsplash.com/photo-1506091403742-e3aa39518db5?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
68
+ }, {
69
+ value: "country",
70
+ prompt: "jazz track with a chill vibe",
71
+ label: "Country",
72
+ image: "https://images.unsplash.com/photo-1525814230241-7f78c608c54c?q=80&w=1976&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
73
+ }, {
74
+ value: "blues",
75
+ prompt: "jazz track with a chill vibe",
76
+ label: "Blues",
77
+ image: "https://plus.unsplash.com/premium_photo-1661333454734-9184250f7226?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
78
+ }, {
79
+ value: "soul",
80
+ prompt: "jazz track with a chill vibe",
81
+ label: "Soul",
82
+ image: "https://images.unsplash.com/photo-1581297848080-c84ac0438210?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
83
+ }, {
84
+ value: "rnb",
85
+ prompt: "jazz track with a chill vibe",
86
+ label: "R&B",
87
+ image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR3D-yMSiaEBtOOoxrKh8InCYzRqjn1UnYVHPhbDGkPrXH32k7i091MRvRTP7Nyts8dMJY&usqp=CAU"
88
+ }, {
89
+ value: "disco",
90
+ prompt: "jazz track with a chill vibe",
91
+ label: "Disco",
92
+ image: "https://images.unsplash.com/photo-1559424452-eeb3a13ffe2b?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
93
+ }, {
94
+ value: "trap",
95
+ prompt: "jazz track with a chill vibe",
96
+ label: "Trap",
97
+ image: "https://images.unsplash.com/photo-1620281428428-bce2bf9ceee4?q=80&w=1970&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
98
+ }, {
99
+ value: "ambient",
100
+ prompt: "jazz track with a chill vibe",
101
+ label: "Ambient",
102
+ image: "https://images.unsplash.com/photo-1616085290694-4b9cc5c97a12?q=80&w=2128&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
103
+ }, {
104
+ value: "lofi",
105
+ prompt: "jazz track with a chill vibe",
106
+ label: "Lofi",
107
+ image: "https://miro.medium.com/v2/resize:fit:1358/0*FjF2hZ8cJQN9aBxk.jpg"
108
+ }, {
109
+ value: "chill",
110
+ prompt: "jazz track with a chill vibe",
111
+ label: "Chill",
112
+ image: "https://images.unsplash.com/photo-1531574373289-ad0d66e39ba9?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
113
+ }]
114
+
115
+ export const MOODS = [{
116
+ value: "happy",
117
+ label: "Happy",
118
+ prompt: "happy track with a chill vibe",
119
+ emoji: "😊"
120
+ }, {
121
+ value: "sad",
122
+ label: "Sad",
123
+ prompt: "sad track with a chill vibe",
124
+ emoji: "😢"
125
+ }, {
126
+ value: "angry",
127
+ label: "Angry",
128
+ prompt: "angry track with a chill vibe",
129
+ emoji: "😡"
130
+ }, {
131
+ value: "chill",
132
+ label: "Chill",
133
+ prompt: "chill track with a chill vibe",
134
+ emoji: "😌"
135
+ }, {
136
+ value: "romantic",
137
+ label: "Romantic",
138
+ prompt: "romantic track with a chill vibe",
139
+ emoji: "😍"
140
+ }, {
141
+ value: "epic",
142
+ label: "Epic",
143
+ prompt: "epic track with a chill vibe",
144
+ emoji: "🚀"
145
+ }, {
146
+ value: "energetic",
147
+ label: "Energetic",
148
+ prompt: "energetic track with a chill vibe",
149
+ emoji: "🔥"
150
+ }, {
151
+ value: "dreamy",
152
+ label: "Dreamy",
153
+ prompt: "dreamy track with a chill vibe",
154
+ emoji: "🌌"
155
+ }, {
156
+ value: "mysterious",
157
+ label: "Mysterious",
158
+ prompt: "mysterious track with a chill vibe",
159
+ emoji: "🕵️"
160
+ }, {
161
+ value: "relaxing",
162
+ label: "Relaxing",
163
+ prompt: "relaxing track with a chill vibe",
164
+ emoji: "😴"
165
+ }, {
166
+ value: "dark",
167
+ label: "Dark",
168
+ prompt: "dark track with a chill vibe",
169
+ emoji: "🖤"
170
+ }, {
171
+ value: "upbeat",
172
+ label: "Upbeat",
173
+ prompt: "upbeat track with a chill vibe",
174
+ emoji: "🎉"
175
+ }, {
176
+ value: "motivational",
177
+ label: "Motivational",
178
+ prompt: "motivational track with a chill vibe",
179
+ emoji: "💪"
180
+ }, {
181
+ value: "inspiring",
182
+ label: "Inspiring",
183
+ prompt: "inspiring track with a chill vibe",
184
+ emoji: "🌟"
185
+ }, {
186
+ value: "nostalgic",
187
+ label: "Nostalgic",
188
+ prompt: "nostalgic track with a chill vibe",
189
+ emoji: "📼"
190
+ }, {
191
+ value: "groovy",
192
+ label: "Groovy",
193
+ prompt: "groovy track with a chill vibe",
194
+ emoji: "🕺"
195
+ }, {
196
+ value: "melancholic",
197
+ label: "Melancholic",
198
+ prompt: "melancholic track with a chill vibe",
199
+ emoji: "😔"
200
+ }, {
201
+ value: "hopeful",
202
+ label: "Hopeful",
203
+ prompt: "hopeful track with a chill vibe",
204
+ emoji: "🌈"
205
+ }]
206
+
207
+
208
+ export function encodeWAV(samples: any[], sampleRate = 16000) {
209
+ let offset = 44;
210
+ const buffer = new ArrayBuffer(offset + samples.length * 4);
211
+ const view = new DataView(buffer);
212
+
213
+ /* RIFF identifier */
214
+ writeString(view, 0, 'RIFF')
215
+ /* RIFF chunk length */
216
+ view.setUint32(4, 36 + samples.length * 4, true)
217
+ /* RIFF type */
218
+ writeString(view, 8, 'WAVE')
219
+ /* format chunk identifier */
220
+ writeString(view, 12, 'fmt ')
221
+ /* format chunk length */
222
+ view.setUint32(16, 16, true)
223
+ /* sample format (raw) */
224
+ view.setUint16(20, 3, true)
225
+ /* channel count */
226
+ view.setUint16(22, 1, true)
227
+ /* sample rate */
228
+ view.setUint32(24, sampleRate, true)
229
+ /* byte rate (sample rate * block align) */
230
+ view.setUint32(28, sampleRate * 4, true)
231
+ /* block align (channel count * bytes per sample) */
232
+ view.setUint16(32, 4, true)
233
+ /* bits per sample */
234
+ view.setUint16(34, 32, true)
235
+ /* data chunk identifier */
236
+ writeString(view, 36, 'data')
237
+ /* data chunk length */
238
+ view.setUint32(40, samples.length * 4, true)
239
+
240
+ for (let i = 0; i < samples.length; ++i, offset += 4) {
241
+ view.setFloat32(offset, samples[i], true)
242
+ }
243
+
244
+ return buffer
245
+ }
246
+ function writeString(view: any, offset: number, string: string) {
247
+ for (let i = 0; i < string.length; ++i) {
248
+ view.setUint8(offset + i, string.charCodeAt(i))
249
+ }
250
+ }
251
+
252
+ export const MODEL_ID = 'Xenova/musicgen-small';