Spaces:
Paused
Paused
Commit Β·
b162b24
0
Parent(s):
music generation
Browse files- .eslintrc.json +3 -0
- .gitignore +37 -0
- Dockerfile +25 -0
- README.md +14 -0
- app/api/generate/cover/route.ts +44 -0
- app/api/generate/title/route.ts +49 -0
- app/favicon.ico +0 -0
- app/layout.tsx +22 -0
- app/page.tsx +19 -0
- assets/globals.css +33 -0
- components/form.tsx +234 -0
- components/hooks/useGeneration.ts +83 -0
- components/length.tsx +69 -0
- components/moods.tsx +66 -0
- components/prompt.tsx +23 -0
- components/styles.tsx +67 -0
- next.config.mjs +15 -0
- package-lock.json +0 -0
- package.json +30 -0
- postcss.config.mjs +8 -0
- public/next.svg +1 -0
- public/vercel.svg +1 -0
- tailwind.config.ts +21 -0
- tsconfig.json +26 -0
- utils/index.ts +252 -0
.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 |
+
"{formattedPrompt}"
|
| 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';
|