Skip to content

Commit d242d4b

Browse files
authored
Merge pull request #135 from falconlee236/feat/add-soundgen
[FEAT] 웹페이지와 효과음 생성 API 연동
2 parents a837ff4 + 72c1716 commit d242d4b

File tree

4 files changed

+290
-13
lines changed

4 files changed

+290
-13
lines changed

src/logged_out/components/home/Profile.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ function Profile(props) {
191191
};
192192
} catch (e){
193193
console.error(e);
194-
throw e;
194+
return []
195195
}
196196
}
197197
fetchLikeData()

src/logged_out/components/register_login/MyLoginDialog.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ const GoogleLoginButton = (props) => {
4444
const {accessToken, refreshToken} = response.data.data.authTokens;
4545
const userData = response.data.data.memberResponse;
4646
setTimeout(() => {
47-
Cookies.set('accessToken', accessToken);
48-
Cookies.set('refreshToken', refreshToken);
47+
Cookies.set('accessToken', accessToken, { expires: 1 });
48+
Cookies.set('refreshToken', refreshToken, { expires: 1 });
4949
localStorage.setItem('userinfo', JSON.stringify(userData));
5050
history.push("/");
5151
onClose();

src/logged_out/components/soundGen/SoundGen.js

+192-10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
import React, {useEffect, useState} from "react";
22
import withStyles from "@mui/styles/withStyles";
3-
import {Box, Typography} from "@mui/material";
3+
import {Box, Button, Card, Divider, Grid, keyframes, Stack, Typography} from "@mui/material";
44
import axios from "axios";
5+
import {styled} from "@mui/material/styles";
6+
import WaveSurferWithoutStar from "./WaveSurferWithoutStar";
7+
import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
8+
import SoundDetailPaper from "../soundList/SoundDetailPaper";
59

6-
const styles = () => ({
10+
const jump = keyframes`
11+
0%, 100% {
12+
transform: translateY(0);
13+
}
14+
50% {
15+
transform: translateY(-20px);
16+
}
17+
`;
18+
19+
const styles = (theme) => ({
720
container: {
821
height: "100vh",
922
display: "flex",
@@ -58,23 +71,126 @@ const styles = () => ({
5871
alignItems: "center",
5972
padding: "0.5rem",
6073
},
74+
blogContentWrapper: {
75+
backgroundColor: theme.palette.background.default,
76+
marginLeft: theme.spacing(1),
77+
marginRight: theme.spacing(1),
78+
[theme.breakpoints.up("sm")]: {
79+
marginLeft: theme.spacing(4),
80+
marginRight: theme.spacing(4),
81+
},
82+
marginBottom: theme.spacing(-50),
83+
maxWidth: 1280,
84+
width: "100%",
85+
},
86+
card: {
87+
backgroundColor: theme.palette.background.default,
88+
boxShadow: theme.shadows[4],
89+
},
90+
titleContainer:{
91+
display: 'flex',
92+
flexDirection: 'row',
93+
justifyContent: 'space-between'
94+
},
95+
titleButtonContainer:{
96+
display: 'flex',
97+
flexDirection: 'column',
98+
justifyContent: 'center',
99+
},
100+
itemPaperContainer:{
101+
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
102+
...theme.typography.body2,
103+
padding: theme.spacing(1),
104+
textAlign: 'center',
105+
},
61106
});
62107

108+
const Letter = styled('span')(({ delay, animate }) => ({
109+
display: 'inline-block',
110+
animation: animate ? `${jump} 4s infinite` : 'none',
111+
animationDelay: delay,
112+
}));
113+
63114
function SoundGen(props) {
64115
const {classes, selectSoundGen} = props;
65116
const [input, setInput] = useState("");
117+
const [animate, setAnimate] = useState(false);
118+
const [soundBlob, setSoundBlob] = useState(null);
119+
const [currentTime, setCurrentTime] = useState(0);
120+
const [duration, setDuration] = useState(0);
121+
const [soundEffectName, setSoundEffectName] = useState("");
122+
const [soundEffectTypes, setSoundEffectTypes] = useState([]);
123+
const [description, setDescription] = useState("");
124+
125+
const formatTime = (timeInSeconds) => {
126+
const minutes = Math.floor((timeInSeconds % 3600) / 60);
127+
const seconds = Math.floor(timeInSeconds % 60);
128+
const hours = Math.floor(timeInSeconds / 3600);
129+
130+
// 시, 분, 초를 두 자리수로 표시하고 앞에 0을 붙임
131+
const formattedHours = hours.toString().padStart(2, '0');
132+
const formattedMinutes = minutes.toString().padStart(2, '0');
133+
const formattedSeconds = seconds.toString().padStart(2, '0');
134+
135+
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
136+
};
137+
66138
const inputChange = (event) => {
67139
setInput(event.target.value);
68140
}
141+
69142
const sendToServer = async (event) => {
70143
if (event.key === "Enter") {
71-
const url = "https://jsonplaceholder.typicode.com/comments"
72-
const resData = await axios.get(url);
73-
console.dir(resData);
74-
console.dir(input);
144+
const englishRegex = /^[a-zA-Z\s.,!?'"]+$/;
145+
if (!englishRegex.test(input)) {
146+
alert("This service is available English only");
147+
setInput("");
148+
return;
149+
}
150+
151+
const url = "https://soundeffect-search.p-e.kr/api/v1/soundeffect"
152+
try{
153+
setAnimate(true);
154+
setSoundBlob(null);
155+
const resData = await axios.post(url, { input });
156+
if (resData.status !== 200) return;
157+
const {file: soundEffectBytes} = resData.data.data;
158+
setSoundEffectName(resData.data.data.soundEffectName);
159+
setSoundEffectTypes(resData.data.data.soundEffectTypes);
160+
setDescription(resData.data.data.description);
161+
// Base64 디코딩 및 Blob 생성
162+
const byteCharacters = atob(soundEffectBytes);
163+
const byteNumbers = new Array(byteCharacters.length)
164+
for (let i = 0; i < byteCharacters.length; i++) {
165+
byteNumbers[i] = byteCharacters.charCodeAt(i);
166+
}
167+
const byteArray = new Uint8Array(byteNumbers);
168+
const blob = new Blob([byteArray], { type: 'audio/wav' });
169+
setSoundBlob(blob); // soundBlob 상태 업데이트
170+
} catch (error) {
171+
console.error(error);
172+
alert("Failed SoundGeneration, Please Try Again")
173+
} finally {
174+
setAnimate(false); // 애니메이션 종료
175+
}
75176
}
76177
}
77178

179+
const handleDownload = () => {
180+
// 다운로드 링크 생성
181+
const link = document.createElement('a');
182+
link.href = window.URL.createObjectURL(soundBlob);
183+
link.download = soundEffectName;
184+
185+
// 링크 클릭 및 정리
186+
document.body.appendChild(link);
187+
link.click();
188+
document.body.removeChild(link);
189+
190+
// Blob URL 해제
191+
window.URL.revokeObjectURL(link.href);
192+
};
193+
78194
useEffect(() => {
79195
selectSoundGen();
80196
}, [selectSoundGen]);
@@ -86,10 +202,10 @@ function SoundGen(props) {
86202
className={classes.logoContainer}
87203
variant="h1"
88204
>
89-
<span style={{color: '#4285F4'}}>A</span>
90-
<span style={{color: '#EA4335'}}>u</span>
91-
<span style={{color: '#FBBC05'}}>L</span>
92-
<span style={{color: '#34A853'}}>o</span>
205+
<Letter style={{ color: '#4285F4' }} delay="0s" animate={animate}>A</Letter>
206+
<Letter style={{ color: '#EA4335' }} delay="1s" animate={animate}>u</Letter>
207+
<Letter style={{ color: '#FBBC05' }} delay="2s" animate={animate}>L</Letter>
208+
<Letter style={{ color: '#34A853' }} delay="3s" animate={animate}>o</Letter>
93209
</Typography>
94210
</Box>
95211
<Box className={classes.innerContainer}>
@@ -109,6 +225,72 @@ function SoundGen(props) {
109225
/>
110226
</Box>
111227
</Box>
228+
{soundBlob && (
229+
<Box className={classes.blogContentWrapper}>
230+
<Box sx={{display: "flex", justifyContent: "center"}}>
231+
<Grid item md={9}>
232+
<Card className={classes.card}>
233+
<Box sx={{border: '2px solid black'}}>
234+
<WaveSurferWithoutStar
235+
audioBlob={soundBlob}
236+
setCurrentTime={setCurrentTime}
237+
setDuration={setDuration}
238+
/>
239+
</Box>
240+
<Box pt={1} pr={3} pl={3} className={classes.titleContainer}>
241+
<Typography>
242+
{formatTime(currentTime)}
243+
</Typography>
244+
<Typography>
245+
{formatTime(duration)}
246+
</Typography>
247+
</Box>
248+
<Box pt={3} pr={3} pl={3} pb={2} className={classes.titleContainer}>
249+
<Box>
250+
<Typography variant="h4">
251+
<b>{soundEffectName}</b>
252+
</Typography>
253+
</Box>
254+
<Box className={classes.titleButtonContainer}>
255+
<Button
256+
component="label"
257+
role={undefined}
258+
variant="contained"
259+
tabIndex={-1}
260+
startIcon={<CloudDownloadIcon />}
261+
onClick={handleDownload}
262+
>
263+
Download
264+
</Button>
265+
</Box>
266+
</Box>
267+
<Box p={3}>
268+
<Typography paragraph>
269+
{description}
270+
</Typography>
271+
<Divider />
272+
<Box pt={2} sx={{display:'flex', justifyContent: 'center'}}>
273+
<Stack
274+
direction={{ xs: 'column', sm: 'row' }}
275+
spacing={{ xs: 1, sm: 2, md: 4 }}
276+
sx={{width: '100%', height: '100%'}}
277+
justifyContent="space-between"
278+
alignItems="center"
279+
>
280+
<SoundDetailPaper title="Type" value={"wav"} />
281+
<SoundDetailPaper title="Duration" value={soundEffectTypes?.length + "s"} />
282+
<SoundDetailPaper title="File Size" value={soundEffectTypes?.fileSize + "MB"} />
283+
<SoundDetailPaper title="Sample Rate" value={soundEffectTypes?.sampleRate + "HZ"} />
284+
<SoundDetailPaper title="Bit depth" value={soundEffectTypes?.bitDepth + "bit"} />
285+
<SoundDetailPaper title="Channels" value={soundEffectTypes?.channels} />
286+
</Stack>
287+
</Box>
288+
</Box>
289+
</Card>
290+
</Grid>
291+
</Box>
292+
</Box>
293+
)}
112294
</Box>
113295
);
114296
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {useEffect, useRef, useState} from "react";
2+
import WaveSurfer from "wavesurfer.js";
3+
import {Box, IconButton} from "@mui/material";
4+
import {PauseCircle, PlayCircle} from "@mui/icons-material";
5+
import withStyles from "@mui/styles/withStyles";
6+
import theme from "../../../theme";
7+
8+
const styles = () => ({
9+
waveSurferContainer: {
10+
display: 'flex',
11+
flexDirection: 'column',
12+
alignItems: 'flex-start',
13+
width: "100%",
14+
},
15+
waveformBox: {
16+
width: "100%",
17+
height: 100, // 파형 높이 설정
18+
marginLeft: 10,
19+
},
20+
bottomButtonBox: {
21+
width: "100%",
22+
height: "100%",
23+
display: 'flex',
24+
alignItems: 'center',
25+
zIndex: 5
26+
},
27+
});
28+
29+
const WaveSurferWithoutStar = (props) => {
30+
const {classes, audioBlob, setCurrentTime, setDuration} = props
31+
const waveform = useRef(null);
32+
const wavesurfer = useRef(null);
33+
const [isPlaying, setIsPlaying] = useState(false);
34+
35+
useEffect(() => {
36+
if(waveform.current){
37+
wavesurfer.current =
38+
WaveSurfer.create({
39+
container: waveform.current,
40+
barWidth: 4,
41+
barHeight: 1,
42+
barGap: 2,
43+
progressColor: theme.palette.primary.main,
44+
waveColor: theme.palette.secondary.main,
45+
width: '100%'
46+
});
47+
wavesurfer.current.loadBlob(audioBlob);
48+
wavesurfer.current.on('ready', () => {
49+
wavesurfer.current.setVolume(0.5);
50+
wavesurfer.current.pause();
51+
if (setDuration){
52+
setDuration(wavesurfer.current.getDuration());
53+
}
54+
});
55+
56+
wavesurfer.current.on('audioprocess', () => {
57+
if (setCurrentTime) {
58+
setCurrentTime(wavesurfer.current.getCurrentTime());
59+
}
60+
});
61+
62+
wavesurfer.current.on("finish", () => {
63+
wavesurfer.current.seekTo(0);
64+
wavesurfer.current.pause();
65+
setIsPlaying(false);
66+
});
67+
}
68+
},[audioBlob, setCurrentTime, setDuration])
69+
70+
const buttonClick = (event) => {
71+
event.preventDefault();
72+
if (wavesurfer.current) {
73+
if (isPlaying) {
74+
wavesurfer.current.pause();
75+
} else {
76+
wavesurfer.current.play();
77+
}
78+
setIsPlaying(!isPlaying);
79+
}
80+
}
81+
82+
return (
83+
<Box className={classes.waveSurferContainer}>
84+
<Box className={classes.waveformBox} ref={waveform} />
85+
<Box className={classes.bottomButtonBox}>
86+
<IconButton onClick={buttonClick}>
87+
{isPlaying ? <PauseCircle/> : <PlayCircle/>}
88+
</IconButton>
89+
</Box>
90+
</Box>
91+
);
92+
}
93+
94+
95+
export default withStyles(styles, { withTheme: true })(WaveSurferWithoutStar);

0 commit comments

Comments
 (0)