diff --git a/app/audio/AudioForm.tsx b/app/audio/AudioForm.tsx index 0bdf8cd..0dce8be 100644 --- a/app/audio/AudioForm.tsx +++ b/app/audio/AudioForm.tsx @@ -9,8 +9,13 @@ import getTempo from "@/utils/getTempo"; import isolateAudio from "@/utils/isolateAudio"; import getAudioFromURL from "@/utils/getAudioFromUrl"; import { useToast } from "@/hooks/use-toast"; +import Loading from "@/components/Loading"; -export default function AudioForm() { +export default function AudioForm({ + setFormComplete, +}: { + setFormComplete: (value: boolean) => void; +}) { const { audioForm, setAudioForm, setAudioStorage, songName } = useContext(AudioContext); const isPart1Complete = audioForm.audio_file || audioForm.audio_link; @@ -72,7 +77,10 @@ export default function AudioForm() { // Make API Request isolateAudio(formData, setAudioStorage).then(() => { - setIsSubmitting(false); + setTimeout(() => { + setIsSubmitting(false); + setisLoading(false); + }, 2000); }); } catch (error) { console.error(error); @@ -151,16 +159,14 @@ export default function AudioForm() { in={isLoading} timeout={700} classNames="fade" + onExited={() => setFormComplete(true)} unmountOnExit >
-

- Loading... -

- {/* */} +
diff --git a/app/audio/AudioPart3.tsx b/app/audio/AudioPart3.tsx index 1043ac7..a3ba30f 100644 --- a/app/audio/AudioPart3.tsx +++ b/app/audio/AudioPart3.tsx @@ -132,7 +132,7 @@ export default function AudioPart3({ diff --git a/app/audio/MidiEditor.tsx b/app/audio/MidiEditor.tsx index d2451e0..fe4c6ed 100644 --- a/app/audio/MidiEditor.tsx +++ b/app/audio/MidiEditor.tsx @@ -3,10 +3,10 @@ import { Dispatch, FormEventHandler, + forwardRef, MouseEventHandler, SetStateAction, useContext, - useEffect, useRef, useState, } from "react"; @@ -24,182 +24,191 @@ import { import AudioMixer from "@/components/AudioMixer"; import { convertToMidi } from "@/utils/getMidi"; -export default function MidiEditor({ - conversionFlag, -}: { +interface Props { conversionFlag: Dispatch>; -}) { - const { audioStorage, setAudioStorage, audioForm } = useContext(AudioContext); - const [selectedMidi, setSelectedMidi] = useState(0); - const midiAdjustments = useRef(null); - const audioControls = useRef(null); - const [midiOpen, setMidiOpen] = useState(false); - - const [midiVolume, setMidiVolume] = useState(50); - const [midiPan, setMidiPan] = useState(0); - const midiControls = { - volume: midiVolume, - setVolume: setMidiVolume, - pan: midiPan, - setPan: setMidiPan, - }; - - const [audioVolume, setAudioVolume] = useState(50); - const [audioPan, setAudioPan] = useState(0); - const originalAudioControls = { - volume: audioVolume, - setVolume: setAudioVolume, - pan: audioPan, - setPan: setAudioPan, - }; - - const storageArray = Object.entries( - audioStorage as Record, - ); - const key = storageArray[selectedMidi][0]; - const isDrums = key === "drums"; - const isLastKey = selectedMidi === storageArray.length - 1; - const isFirstKey = selectedMidi === 0; - const stem = (audioStorage as Record)[ - key as keyof AudioStorage - ]; - - const handleOpen: MouseEventHandler = (_event) => { - setTimeout(() => { - if (!midiAdjustments.current || !audioControls.current) return; - if (midiAdjustments.current.dataset.state === "open") setMidiOpen(true); - else setMidiOpen(false); - }, 50); - }; - - const handleRegenerateMidi: FormEventHandler = async (e) => { - e.preventDefault(); - conversionFlag(true); - - setAudioStorage((prev) => { - if (!prev) return prev; - const updatedStorage: AudioStorage = { ...prev }; - const name = stem.name; - updatedStorage[name] = { - ...updatedStorage[name], - midiBlob: null, - }; +} - return updatedStorage; - }); +const MidiEditor = forwardRef( + ({ conversionFlag }: Props, ref: React.ForwardedRef) => { + const { audioStorage, setAudioStorage, audioForm } = + useContext(AudioContext); + const [selectedMidi, setSelectedMidi] = useState(0); + const midiAdjustments = useRef(null); + const audioControls = useRef(null); + const [midiOpen, setMidiOpen] = useState(false); + + const [midiVolume, setMidiVolume] = useState(50); + const [midiPan, setMidiPan] = useState(0); + const midiControls = { + volume: midiVolume, + setVolume: setMidiVolume, + pan: midiPan, + setPan: setMidiPan, + }; - const formData = new FormData(e.currentTarget); - const adjustments: midiAdjustments = { - onset_threshold: formData.get("note_segmentation") as string, - frame_threshold: formData.get("confidence_threshold") as string, - minimum_note_length: formData.get("minimum_note_length") as string, + const [audioVolume, setAudioVolume] = useState(50); + const [audioPan, setAudioPan] = useState(0); + const originalAudioControls = { + volume: audioVolume, + setVolume: setAudioVolume, + pan: audioPan, + setPan: setAudioPan, }; - const maxFreq = parseInt(formData.get("maximum_frequency") as string); - if (maxFreq < 18000) { - adjustments.maximum_frequency = maxFreq.toString(); - } - const minFreq = parseInt(formData.get("minimum_frequency") as string); - if (minFreq > 0) { - adjustments.minimum_frequency = minFreq.toString(); - } - - const newMidi = await convertToMidi( - stem, - audioForm.tempo as number, - false, - adjustments, + + const storageArray = Object.entries( + audioStorage as Record, ); + const key = storageArray[selectedMidi][0]; + const isDrums = key === "drums"; + const isLastKey = selectedMidi === storageArray.length - 1; + const isFirstKey = selectedMidi === 0; + const stem = (audioStorage as Record)[ + key as keyof AudioStorage + ]; + + const handleOpen: MouseEventHandler = (_event) => { + setTimeout(() => { + if (!midiAdjustments.current || !audioControls.current) return; + if (midiAdjustments.current.dataset.state === "open") setMidiOpen(true); + else setMidiOpen(false); + }, 50); + }; + + const handleRegenerateMidi: FormEventHandler = async ( + e, + ) => { + e.preventDefault(); + conversionFlag(true); - setAudioStorage((prev) => { - if (!prev) return prev; - const updatedStorage: AudioStorage = { ...prev }; - const name = stem.name; - updatedStorage[name] = { - ...updatedStorage[name], - midiBlob: newMidi, + setAudioStorage((prev) => { + if (!prev) return prev; + const updatedStorage: AudioStorage = { ...prev }; + const name = stem.name; + updatedStorage[name] = { + ...updatedStorage[name], + midiBlob: null, + }; + + return updatedStorage; + }); + + const formData = new FormData(e.currentTarget); + const adjustments: midiAdjustments = { + onset_threshold: formData.get("note_segmentation") as string, + frame_threshold: formData.get("confidence_threshold") as string, + minimum_note_length: formData.get("minimum_note_length") as string, }; + const maxFreq = parseInt(formData.get("maximum_frequency") as string); + if (maxFreq < 18000) { + adjustments.maximum_frequency = maxFreq.toString(); + } + const minFreq = parseInt(formData.get("minimum_frequency") as string); + if (minFreq > 0) { + adjustments.minimum_frequency = minFreq.toString(); + } + + const newMidi = await convertToMidi( + stem, + audioForm.tempo as number, + false, + adjustments, + ); + + setAudioStorage((prev) => { + if (!prev) return prev; + const updatedStorage: AudioStorage = { ...prev }; + const name = stem.name; + updatedStorage[name] = { + ...updatedStorage[name], + midiBlob: newMidi, + }; - return updatedStorage; - }); - conversionFlag(false); - }; - - return ( -
- {/* ^ div should have xl:max-w-1200px */} - - -
+ - {!isDrums && ( - +
+ {!isDrums && ( + + + Midi Adjustments + + + + + + )} + - Midi Adjustments + Audio Controls - - + + + - )} - - - Audio Controls - - - - - - -
- {!midiOpen && ( -
- {!isFirstKey && ( - setSelectedMidi(selectedMidi - 1)} - className="button-secondary flex h-min w-full cursor-pointer items-center justify-center gap-2 rounded-3xl border-2 border-border px-6 py-2 text-base font-semibold transition-colors xl:max-w-[300px]" - > - Back - - )} - {!isLastKey ? ( - setSelectedMidi(selectedMidi + 1)} - className="button-primary flex h-min w-full cursor-pointer items-center justify-center gap-2 rounded-3xl px-6 py-2 text-base font-semibold transition-colors xl:max-w-[300px]" - > - Next - - ) : ( - - Export - - )}
- )} -
-
- ); -} + {!midiOpen && ( +
+ {!isFirstKey && ( + setSelectedMidi(selectedMidi - 1)} + className="button-secondary flex h-min w-full cursor-pointer items-center justify-center gap-2 rounded-3xl border-2 border-border px-6 py-2 text-base transition-colors xl:max-w-[300px]" + > + Back + + )} + {!isLastKey ? ( + setSelectedMidi(selectedMidi + 1)} + className="button-primary flex h-min w-full cursor-pointer items-center justify-center gap-2 rounded-3xl px-6 py-2 text-base transition-colors xl:max-w-[300px]" + > + Next + + ) : ( + + Export + + )} +
+ )} +
+
+ ); + }, +); + +export default MidiEditor; diff --git a/app/audio/page.tsx b/app/audio/page.tsx index dbd4bde..bff91dc 100644 --- a/app/audio/page.tsx +++ b/app/audio/page.tsx @@ -1,6 +1,7 @@ "use client"; import React, { useContext, useEffect, useRef, useState } from "react"; +import { CSSTransition } from "react-transition-group"; import { AudioContext } from "./AudioProvider"; import AudioForm from "./AudioForm"; import handleMidiConversion from "@/utils/getMidi"; @@ -10,9 +11,11 @@ export default function Page() { const { audioForm, audioStorage, setAudioStorage, songName } = useContext(AudioContext); const [flatScore, setFlatScore] = useState(null); + const [formComplete, setFormComplete] = useState(false); const [isConverting, setIsConverting] = useState(false); const [isMidiComplete, setIsMidiComplete] = useState(false); const flatRef = useRef(null); + const editorRef = useRef(null); // Convert to Midi useEffect(() => { @@ -66,8 +69,16 @@ export default function Page() { className="flex w-full max-w-[1200px] flex-col items-center justify-around px-8 sm:px-20 xl:px-0" id="resize container" > - {!audioStorage && } - {audioStorage && } + {!formComplete && } + + + {/* {isMidiComplete && ( (null); + const [info, setInfo] = useState(null); + const [index, setIndex] = useState(-1); + if (index === -1) { + setIndex(Math.floor(Math.random() * infoList.length)); + } + + const dots = Array.from({ length: dotCount }, (_, index) => ( + + . + + )); + + const changeInfo = () => { + let randomIndex; + do { + randomIndex = Math.floor(Math.random() * infoList.length); + } while (randomIndex === index); + + setInfo(infoList[randomIndex]); + setIndex(randomIndex); + setInfoTrigger(true); + }; + + useEffect(() => { + const interval = setInterval(() => { + setInfoTrigger((prev) => !prev); + }, 12000); + + return () => clearInterval(interval); + }, [infoTrigger]); + + return ( + <> +
+ {text} + {dots} +
+ +

+ {info ?? infoList[index]} +

+
+ + ); +} diff --git a/components/MidiAdjustments.tsx b/components/MidiAdjustments.tsx index de6e27f..fdbfe49 100644 --- a/components/MidiAdjustments.tsx +++ b/components/MidiAdjustments.tsx @@ -1,6 +1,7 @@ "use client"; import MidiSlider from "@/components/MidiSlider"; import { Button } from "@/components/ui/button"; +import { useScreenSize } from "@/hooks/use-screen-size"; import { FormEvent, useState } from "react"; export default function MidiAdjustments({ @@ -9,6 +10,7 @@ export default function MidiAdjustments({ handleSubmit: (event: FormEvent) => void; }) { const [reset, setReset] = useState(false); + const screenSize = useScreenSize(); const handleReset = (event: FormEvent) => { event.preventDefault(); setReset((prev) => !prev); @@ -70,7 +72,7 @@ export default function MidiAdjustments({ Reset ); diff --git a/hooks/use-screen-size.ts b/hooks/use-screen-size.ts new file mode 100644 index 0000000..e3bc046 --- /dev/null +++ b/hooks/use-screen-size.ts @@ -0,0 +1,38 @@ +import { useState, useEffect } from "react"; + +type ScreenSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | ""; + +export const useScreenSize = (): ScreenSize => { + const [screenSize, setScreenSize] = useState(""); + + useEffect(() => { + const handleResize = () => { + if (window.innerWidth < 640) { + setScreenSize("xs"); + } else if (window.innerWidth >= 640 && window.innerWidth < 768) { + setScreenSize("sm"); + } else if (window.innerWidth >= 768 && window.innerWidth < 1024) { + setScreenSize("md"); + } else if (window.innerWidth >= 1024 && window.innerWidth < 1280) { + setScreenSize("lg"); + } else if (window.innerWidth >= 1280 && window.innerWidth < 1536) { + setScreenSize("xl"); + } else if (window.innerWidth >= 1536) { + setScreenSize("2xl"); + } else { + setScreenSize(""); // Fallback for unexpected cases + } + }; + + // Add the resize event listener + window.addEventListener("resize", handleResize); + + // Call the handler once to set the initial screen size + handleResize(); + + // Clean up the event listener on unmount + return () => window.removeEventListener("resize", handleResize); + }, []); + + return screenSize; +}; diff --git a/utils/infoList.ts b/utils/infoList.ts new file mode 100644 index 0000000..c7ceec3 --- /dev/null +++ b/utils/infoList.ts @@ -0,0 +1,8 @@ +export const infoList = [ + "You can adjust the volume and pan of the midi audio and original audio while it's playing.", + "It is recommended when uploading a solo instrument recording to provide a tempo. Otherwise, the tempo will automatically be calculated.", + "If the tempo was not able to be detected properly, it will default to 120 BPM.", + "Hey, I'm glad that you got the backend set up. Thank you so much for taking the time to check out my project! ^_^", + "If there are any unnecessarily high or low notes in the midi, you can adjust the min and max frequencies and regenerate it.", + "The backdoor dominant is the five chord in the relative major of the parallel minor.", +];