diff --git a/.gitignore b/.gitignore index 00bba9b..39364f2 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ # misc .DS_Store *.pem +backlog.txt # debug npm-debug.log* diff --git a/app/audio/page.tsx b/app/audio/page.tsx new file mode 100644 index 0000000..58534f2 --- /dev/null +++ b/app/audio/page.tsx @@ -0,0 +1,124 @@ +"use client"; + +import React, { useState } from "react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; + +const validMimeTypes = ["audio/mpeg", "audio/wav", "audio/ogg, audio/flac"]; +const apiBaseUrl = "http://localhost:8000"; + +const formSchema = z.object({ + audio_file: z + .any() + .refine((fileList) => fileList && fileList.length > 0, { + message: "Please upload a valid file.", + }) + .refine( + (fileList) => { + if (!fileList || fileList.length === 0) return false; + const file = fileList[0]; + return validMimeTypes.includes(file.type); + }, + { + message: + "Invalid file type. Only MP3, WAV, OGG, or FLAC files are allowed.", + }, + ), +}); + +export default function AudioToMidiForm() { + const [isSubmitting, setIsSubmitting] = useState(false); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + audio_file: null, + }, + }); + + async function onSubmit(values: z.infer) { + const fileList = values.audio_file; + const file = fileList[0]; + if (!file) return; + + const formData = new FormData(); + formData.append("audio_file", file); + + try { + setIsSubmitting(true); + const response = await fetch(`${apiBaseUrl}/audio-to-midi`, { + method: "POST", + mode: "cors", + body: formData, + }); + + if (!response.ok) { + throw new Error("Failed to convert audio"); + } + + const midiBlob = await response.blob(); + const midiUrl = URL.createObjectURL(midiBlob); + + // Trigger the download of the MIDI file + const link = document.createElement("a"); + link.href = midiUrl; + link.download = "converted.mid"; // Specify the filename + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + // Optionally revoke the object URL after download + URL.revokeObjectURL(midiUrl); + } catch (error) { + console.error(error); + alert("Error converting audio to MIDI"); + } finally { + setIsSubmitting(false); + } + } + + return ( +
+
+ + ( + + Upload Audio File + + { + field.onChange(e.target.files); + }} + /> + + + Upload an audio file to convert to MIDI. + + + + )} + /> + + + +
+ ); +} diff --git a/components/ui/form.tsx b/components/ui/form.tsx new file mode 100644 index 0000000..ce264ae --- /dev/null +++ b/components/ui/form.tsx @@ -0,0 +1,178 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +