1
1
import React , { useEffect , useState } from "react" ;
2
2
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" ;
4
4
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" ;
5
9
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 ) => ( {
7
20
container : {
8
21
height : "100vh" ,
9
22
display : "flex" ,
@@ -58,23 +71,126 @@ const styles = () => ({
58
71
alignItems : "center" ,
59
72
padding : "0.5rem" ,
60
73
} ,
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
+ } ,
61
106
} ) ;
62
107
108
+ const Letter = styled ( 'span' ) ( ( { delay, animate } ) => ( {
109
+ display : 'inline-block' ,
110
+ animation : animate ? `${ jump } 4s infinite` : 'none' ,
111
+ animationDelay : delay ,
112
+ } ) ) ;
113
+
63
114
function SoundGen ( props ) {
64
115
const { classes, selectSoundGen} = props ;
65
116
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
+
66
138
const inputChange = ( event ) => {
67
139
setInput ( event . target . value ) ;
68
140
}
141
+
69
142
const sendToServer = async ( event ) => {
70
143
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 - z A - 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
+ }
75
176
}
76
177
}
77
178
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
+
78
194
useEffect ( ( ) => {
79
195
selectSoundGen ( ) ;
80
196
} , [ selectSoundGen ] ) ;
@@ -86,10 +202,10 @@ function SoundGen(props) {
86
202
className = { classes . logoContainer }
87
203
variant = "h1"
88
204
>
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 >
93
209
</ Typography >
94
210
</ Box >
95
211
< Box className = { classes . innerContainer } >
@@ -109,6 +225,72 @@ function SoundGen(props) {
109
225
/>
110
226
</ Box >
111
227
</ 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
+ ) }
112
294
</ Box >
113
295
) ;
114
296
}
0 commit comments