PomodifyïŒp5.jsã§äœãAudio Visualizerð à Pomodoro Timerð°ïž
0. ææç©
ããã«ã¡ã¯ãã¯ãªãšãŒã¿ãŒã®çããŸïŒ
ä»åã玹ä»ããã®ã¯...ãã¡ãâ
ãªãŒãã£ãªã»ããžã¥ã¢ã©ã€ã¶ãŒãšãã¢ããŒãã¿ã€ããŒãçµã¿åããããPomodifyããå¶äœããŸããïŒ
ãããžã¥ã¢ã©ã€ã¶ãŒïŒãã¢ããŒãïŒããšèããŠãã³ãšããªãæ¹ããå®å¿ãã ããã
ãŸãã¯ãã¢åç»ããã²ã芧ãã ããïŒãã£ãšãããããããïŒããšçŽåŸããŠããã ããã¯ãã§ãã
ãã¢æ å
WEBãµã€ããžã®ãªã³ã¯
1. ã¯ããã«
èªå·±çŽ¹ä»
æ ªåŒäŒç€ŸSun AsteriskïŒä»¥äžãSun*ïŒã«ãŠããã³ããšã³ããšã³ãžãã¢ãããŠããŸããããã§ããã§ãã
Sun*ã«ã¯22åãšããŠå
¥ç€ŸããShopifyãçšããECãµã€ãæ§ç¯ã®ã»ããçŸåšã¯HRç³»ãµãŒãã¹ã®ããã³ããšã³ãã®èšèšã»å®è£
ãæ
åœããŠããŸãã
ä»åã¯æ¥åã§äœ¿ãæ©äŒã®å°ãªããp5.jsããçšããŠããªãŒãã£ãªã»ããžã¥ã¢ã©ã€ã¶ãŒãäœæããããšã«ææŠããŸããã
ãã£ãã
ãªãŒãã£ãªã»ããžã¥ã¢ã©ã€ã¶ãŒãšããã°ãå°çšãœãããæ åç·šéãœããã§äœæããã®ãäžè¬çã§ãããå¶äœã«ã¯å°éçãªç¥èãå¿ èŠã§ãããããããWebäžã§æ軜ã«ãªãŒãã£ãªã»ããžã¥ã¢ã©ã€ã¶ãŒãå¶äœã§ããã°ãããå€ãã®äººãæ åå¶äœãã¢ããªå¶äœãæ°è»œã«æ¥œãããã®ã§ã¯ãšèããŸããããã®ç¬¬äžæ©ãšããŠãä»åã¯ãªãŒãã£ãªã»ããžã¥ã¢ã©ã€ã¶ãŒãšçžæ§æ矀ã®ãã¢ããŒãã¿ã€ããŒãå¶äœããŸããã
ð ãªãŒãã£ãªã»ããžã¥ã¢ã©ã€ã¶ãŒïŒ
ãªãŒãã£ãªã»ããžã¥ã¢ã©ã€ã¶ãŒãšã¯ãé³é¿ä¿¡å·ã®æ³¢åœ¢ããªãºã ãªã©ãèŠèŠçãªã¢ãã¡ãŒã·ã§ã³ãã°ã©ãã£ãã¯ãšããŠè¡šçŸããããŒã«ã®ããšã§ãã
ð°ïž ãã¢ããŒãã¿ã€ããŒïŒ
ãã¢ããŒãã¿ã€ããŒãšã¯ã25åã1ã€ã®äœæ¥æéãšããŠåºåãããã®ã¿ã€ããŒã®ããšã§ãããã®ã¿ã€ããŒã䜿ã£ãæé管çè¡ã¯ããã¢ããŒãã»ãã¯ããã¯ããšåŒã°ããŸãã
ããã¢ããŒã (Pomodoro)ãã¯ã€ã¿ãªã¢èªã§ããããããæå³ããŸãããã®ååã¯ãæé管çè¡ã®èæ¡è ã§ãããã©ã³ãã§ã¹ã³ã»ã·ãªãæ°ãããããåã®ãããã³ã¿ã€ããŒã䜿çšããŠããããšã«ç±æ¥ããŠããŸãã
2. æè¡ã¹ã¿ãã¯
䜿çšããæè¡ã¹ã¿ãã¯ã¯ä»¥äžã®éãã§ãã
- ð p5.js
- ð Web Audio API
- ReactïŒã³ã³ããŒãã³ãããŒã¹ã®UIæ§ç¯ãè¡ãããã®ã©ã€ãã©ãªã
- TypeScriptïŒJavaScriptã®ã¹ãŒããŒã»ãããéçåä»ãã«ãããåãšã©ãŒãäºåã«æ€åºã§ãããããéçºã®ä¿¡é Œæ§ãåäžãããŸãã
- styled-componentsïŒã³ã³ããŒãã³ãããšã®ã¹ã¿ã€ã«ç®¡çã«äœ¿çšã§ããCSS in JS ã©ã€ãã©ãªã
- ViteïŒé«éã«ã¯ã³ã³ãã³ãã§éçºç°å¢ãæäŸããããã®æ¬¡äžä»£ããã³ããšã³ãããŒã«ã
p5.jsãšã¯
p5.js ã¯ãã¯ãªãšã€ãã£ããªããžã¥ã¢ã«è¡šçŸãç°¡åã«å®è£ ã§ããJavaScriptã©ã€ãã©ãªã§ããProcessingã®æµããæ±²ãã©ã€ãã©ãªã§ãããã¢ãŒããããžã¥ã¢ã©ã€ãŒãŒã·ã§ã³ã«é©ããè±å¯ãªæ©èœãæäŸããŠããŸãã
Web Audio APIãšã¯
Web Audio API ã¯ãWebãã©ãŠã¶äžã§é³å£°ã®åŠçã解æãè¡ãããã®APIã§ãããªã¢ã«ã¿ã€ã ã®é³å£°è§£æããšãã§ã¯ãã®é©çšãªã©ãããŸããŸãªé³é¿åŠçãå¯èœã§ãã
3. å®è£
åç« ãŸã§ã§ãPomodifyãäœã£ããã£ãããšäœ¿çšããæè¡ã¹ã¿ãã¯ã«ã€ããŠãäŒãããŸããã
ããããã¯ãããããPomodifyã®å®è£
ã«å
¥ããŸãïŒ
æ¬ç« ã§ã¯ãPomodifyã§å©çšããäž»ãªå®è£ ãã€ã³ãã«ã€ããŠèª¬æããŸããç¹ã«ãp5.jsã䜿ã£ãã°ã©ãã£ãã¯å¶åŸ¡ãWeb Audio APIãå©çšããé³é¿åŠçããããŠããããçµã¿åãããæ¹æ³ã«ã€ããŠè§£èª¬ããŸãã
ã°ã©ãã£ãã¯ãé³é¿åŠçã«ã¯æ°åŠçãªèŠçŽ ãå«ãŸããŸããã詳现ãªæ°åŒã®è§£èª¬ã¯çç¥ããŸããããããªæãã§æ°åŠçãªèšç®ã䜿ã£ãŠããã®ãããšè»œãç解ããŠããã ããã°ååã§ãïŒ
ããžã¥ã¢ã©ã€ã¶ãŒ
Pomodifyã§ã¯ãé³æ¥œã«åãããŠã°ã©ããã£ãã¯ãåçã«å€åããè²ã圢ãå€ããä»çµã¿ã«ãªã£ãŠããŸãïŒé³æ¥œã«ã¯ãåšæ³¢æ°ãæ¯å¹ ãªã©æéã«ãã£ãŠå€åããæ§ã ãªå€ããããŸãã
ãããã®å€ãå©çšããŠãã°ã©ããŒã·ã§ã³ãæãç·ã®æç»ïŒæãç·ã®äŒžçž®ïŒãå®çŸããŠããŸããæ¬ç« ã§ã¯ãã©ã®ãããªã«å®è£ ããŠãããã説æããŠãããŸãã
ã°ã©ããŒã·ã§ã³ã®èšå®
-
map
ã¡ãœããã¯ã第1åŒæ°ã®å€ã第2åŒæ°ãã第3åŒæ°ã®ç¯å²ãã第4åŒæ°ãã第5åŒæ°ã®ç¯å²ã«ã¹ã±ãŒãªã³ã°ããããã®ã¡ãœããã§ãã- äŸïŒ
p.map(4, 0, 10, 0, 100)
ã®å Žåã¯ã第1åŒæ°ã®4
ã®å€ã0~10
ãã0~100
ã®ç¯å²ã«ã¹ã±ãŒãªã³ã°ããåŠçã«ãªããçµæã¯40
ãšãªããŸãã
- äŸïŒ
-
stroke
ã¡ãœããã¯ãå³åœ¢ã®è²ã®æããã調æŽããããã®ã¡ãœããã§ãã
ä»åã¯ãæç»ããå³åœ¢ã®è²ãRGBå€ãšããŠåŒæ°ã§æå®ãããã®å€ãstroke
ã¡ãœããå ã§åšæ³¢æ°ã®æ¯å¹ ã«åãããŠé©åã«å€åãããä»çµã¿ãå®è£ ããŠããŸãã -
map
ã¡ãœããã§æå®ããè²(RGB)ã決å®ããŠããŸãããã¹ã±ãŒã«ãããæå°å€ãããŒãã«ã©ãŒã«æå®ããããšã§ãã©ããªé³æ¥œã§ãPomodifyã®äžç芳ãå£ããªãããã«å·¥å€«ããŠããŸãïŒ- ããŒãã«ã©ãŒïŒ
rgb(133,73,152)
- ããŒãã«ã©ãŒïŒ
- Pomodifyã§ã¯ã
map
ã¡ãœãããšstroke
ã¡ãœããã掻çšããé³æ¥œã®åšæ³¢æ°ã®æ¯å¹ ãªã©ã®å€æ°ãããšã«è²ãææãå€åãããããšã§ãåãã®ããã°ã©ãã£ãã¯ãå®çŸããŠããŸããå ·äœçã«ã¯ãmap
ã¡ãœããã䜿ã£ãŠRGBã®å€ãç¹å®ã®ç¯å²å ã«åãã€ã€å€åãããstroke
ã¡ãœããã§å³åœ¢ã®è²ã決å®ããŠããŸãã
// src/components/Visualizer.tsx
const r = p.map(value * 1.6, COLOR_MIN_VALUE, COLOR_MAX_VALUE, RED_MIN_VALUE, RED_MAX_VALUE);
const g = p.map(value * 1.6, COLOR_MIN_VALUE, COLOR_MAX_VALUE, GREEN_MIN_VALUE, GREEN_MAX_VALUE);
const b = p.map(value * 1.6, COLOR_MIN_VALUE, COLOR_MAX_VALUE, BLUE_MIN_VALUE, BLUE_MAX_VALUE);
p.stroke(r, g, b);
æãç·ã®æç»
- åç¶ã«é
眮ãããããžã¥ã¢ã©ã€ã¶ãŒã®ç·ãè¡šçŸããããã«ãç·ã®å§ç¹ãšçµç¹ãäžè§é¢æ°ãçšããŠç®åºããŸããã
-
sin()
ãcos()
ãã¯ãããšããå€ãã®ç®è¡æŒç®ãp5.jsã«çšæãããŠãããããæ§ã ãªå³åœ¢ã容æã«è¡šçŸããããšãã§ããŸãã
-
- ãŸããè§åºŠãå転ãããããã®
angleOffset
ãååŒæ°ã«è¿œå ããããšã§ãç·ãå転ãããŠãŸãã
// src/components/Visualizer.tsx
p.draw = () => {
for (let i = 0; i < CIRCLE_DEGREES; i += CIRCLE_DIVISION_ANGLE) {
const radius = p.map(value, 0, 255, CIRCLE_RADIUS, 200); // ååŸ
const x1 = radius * p.cos(i + angleOffset);
const y1 = radius * p.sin(i + angleOffset);
const x2 = (radius + LINE_LENGTH) * p.cos(i + angleOffset);
const y2 = (radius + LINE_LENGTH) * p.sin(i + angleOffset);
p.line(x1, y1, x2, y2);
}
};
Web Audio APIã§é³æ¥œãšããžã¥ã¢ã©ã€ã¶ãŒã®é£å
é³æ¥œã®åç
- 以äžã®å³ã¯ãé³æ¥œåçãŸã§ã®ç°¡æãããŒãã£ãŒãã瀺ããŠããŸãã
- ãŸãããã©ãŠã¶ãAudioContextããµããŒãããŠãããã©ããã確èªãããµããŒãããŠããå Žåã«é³å£°ãã¡ã€ã«ããã€ããªãŒã®ãªããžã§ã¯ãã«å€æãããã³ãŒããããã®ã
audioBufferRef
ã«ä¿åããŸãã - ãŸããåã¬ã³ããªã³ã°æã«
AudioContext
ãåçæãããªãããã«useRefãå©çšããŠããŸãã
// src/components/Timer.tsx
const audioContextRef = useRef<AudioContext | null>(null); // åçæããªãããã®useRef
useEffect(() => {
const AudioContextClass = getAudioContext();
if (AudioContextClass) {
audioContextRef.current = new AudioContextClass();
analyserNodeRef.current = audioContextRef.current.createAnalyser();
fetch('/audio/sound.mp3')
.then((response) => response.arrayBuffer()) // é³å£°ããŒã¿ãArrayBufferãšããŠååŸ
.then((arrayBuffer) => audioContextRef.current!.decodeAudioData(arrayBuffer))
.then((audioBuffer) => {
audioBufferRef.current = audioBuffer;
})
ããŒãªãšå€æ
-
AnalyserNode
ã€ã³ã¿ãŒãã§ãŒã¹ã¯ãé³å£°ããŒã¿ã«å¯ŸããŠããŒãªãšå€æãè¡ããŸãã
analyserNodeRef.current!.connect(audioContextRef.current.destination);
- ãµã³ããªã³ã°ãããæ°ã®ååã«å¯Ÿå¿ããåšæ³¢æ°åž¯åã®æ°ãé
åã®ãµã€ãºãšãªãã
getByteFrequencyData
ã¡ãœããã䜿çšããŠåšæ³¢æ°æåãååŸããŸãã
bufferLength = analyserNode.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
analyserNode.getByteFrequencyData(dataArray);
4. éçºäžã®èª²é¡ãšè§£æ±ºç
p5.jsãšReactã®çµ±å
-
p5.js
ãReactã®ã³ã³ããŒãã³ãå ã§äœ¿çšããéã«ãDOM管çææ³ã®éãããæç»ãæ£ããè¡ãããªãåé¡ãçºçããŸããã - 解決çãšããŠã
react-p5-wrapper
ãå°å ¥ããããšã§ãp5.js
ãReactãšçµ±åããŸããã
p5.soundã®å°å ¥ãšã©ãŒ
-
@types/p5
ã«ãããp5.sound
ã®åå®çŸ©ã®åç §å ãp5.js
ãreacat-p5-wrapper
ãšå¹²æžããŠæ£åžžã«åäœããŸããã§ããã - 解決çãšããŠãä»åã¯WEB Audio APIã䜿ãããšã«æ±ºå®ããŸããããPomodifyã®éçºåœåã¯ãp5.jsã ãã§é³æ¥œãšã®é£æºãèããŠããã®ã§ããããã®åé¡ãããæ念ããŸããã
ããžã¥ã¢ã©ã€ã¶ãŒã®æç»ããã«ãã«ããåé¡
-
rotate
ã¡ãœãã䜿çšããããžã¥ã¢ã©ã€ã¶ãŒã®å転ã«ãã£ãŠãæç»ããã«ãã«ããåé¡ãçºçããã - 解決çãšããŠã
rotate
ã¡ãœããã®ä»£æ¿ãšããŠãäžè§é¢æ°ã¡ãœããã®åŒæ°ã«offset
ãè¿œå ããããšã§åº§æšäžã§å転ããã工倫ãè¡ãªããŸããã
5. ãŸãšã
ä»åã¯ããªãŒãã£ãªã»ããžã¥ã¢ã©ã€ã¶ãŒã䜿ã£ããã¢ããŒãã¿ã€ããŒãPomodifyããäœæããŸãããp5.jsã®é åã¯ãä»ã®GUIã©ã€ãã©ãªãšæ¯ã¹ãŠå¶çŽãå°ãªããæ軜ã«å®è£ ã§ããç¹ã§ãããã®ç¹ã«ã€ããŠã¯ãäžèšã®ã³ãŒããããç解ããŠããã ããããšæããŸããçããããã²ãããã³ããšã³ãã®å ¥éãšããŠp5.jsã䜿ã£ãã³ã³ãã³ãå¶äœã«ææŠããŠã¿ãŠã¯ãããã§ããããã
ä»åŸã®å±æ
Pomodifyã¯ãã誰ããæ°è»œã«é³æ¥œãèŽããªãããã¢ããŒãã¿ã€ããŒãå©çšã§ããããã¢ãããŒã«ãããžã¥ã¢ã©ã€ã¶ãŒã®ã«ã¹ã¿ãã€ãºæ©èœããåçã§ããé³æºã®æ¡å ãªã©ã®æ©èœæ¡åŒµãè¡ãäºå®ã§ãã
ãŸããä»åã®ãã£ããã§ããããªãŒãã£ãªã»ããžã¥ã¢ã©ã€ã¶ãŒãçæããããã®ããŒã«å¶äœã«ãçæããããšèããŠããŸãã次åã¯ãã¡ãã®å 容ã§ç¶ç·šã®èšäºãæžããã°ãšæã£ãŠããŸãã
ãããã«
æåŸãŸã§ãã芧ããã ãããããšãããããŸããã
以äžã«ããœãŒã¹ã³ãŒããšåèè³æãæ·»ä»ããŠãããŸãã®ã§ãããå®ãããã°ãäžèªãã ãããŸãã
6. ãœãŒã¹ã³ãŒããšåèè³æ
Github
åèè³æ
è£è©±
- é³æºã¯çæAIãšgarageBandãçšããŠäœæããŸããã
- å¶äœæéãçãã£ãããããœãŒã¹ã³ãŒãã®ãªãã¡ã¯ã¿ãªã³ã°ãè¿œãã€ããŠãããŸããããã容赊ãã ããã
Discussion