Fix MusicSlider.jsx

This commit is contained in:
Mathias Wagner 2025-03-01 17:34:24 +01:00
parent 7996905f30
commit f9447b3deb
2 changed files with 85 additions and 17 deletions

View File

@ -1,6 +1,21 @@
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react';
import "./styles.sass"; import "./styles.sass";
const frequencyToNote = (frequency) => {
const A4 = 440;
const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
const halfSteps = Math.round(12 * Math.log2(frequency / A4));
const A4Index = notes.indexOf('A');
let noteIndex = (A4Index + halfSteps) % 12;
if (noteIndex < 0) noteIndex += 12;
const octave = 4 + Math.floor((halfSteps + A4Index) / 12);
return `${notes[noteIndex]}${octave}`;
};
export const MusicSlider = ({ isReadOnly, onFrequencyChange, frequency: externalFrequency, composerIsPlaying }) => { export const MusicSlider = ({ isReadOnly, onFrequencyChange, frequency: externalFrequency, composerIsPlaying }) => {
const [audioContext, setAudioContext] = useState(null); const [audioContext, setAudioContext] = useState(null);
const [oscillator, setOscillator] = useState(null); const [oscillator, setOscillator] = useState(null);
@ -187,6 +202,25 @@ export const MusicSlider = ({ isReadOnly, onFrequencyChange, frequency: external
return `${Math.max(0, Math.min(pos, 100))}%`; return `${Math.max(0, Math.min(pos, 100))}%`;
}; };
const getMouthSize = () => {
const minSize = 30;
const maxSize = 60;
const freqRange = 880;
const sizeRange = maxSize - minSize;
const relativeFreq = frequency.current - 220;
const size = minSize + (relativeFreq / freqRange) * sizeRange;
return Math.max(minSize, Math.min(maxSize, size));
};
const shouldShowNoteMarker = () => {
if (!isReadOnly) return isDragging.current;
return composerIsPlaying;
};
if (isReadOnly && !composerIsPlaying) {
return null;
}
return ( return (
<div className="otamatone-container"> <div className="otamatone-container">
<div className="otamatone"> <div className="otamatone">
@ -195,22 +229,32 @@ export const MusicSlider = ({ isReadOnly, onFrequencyChange, frequency: external
<div className="eye left-eye"></div> <div className="eye left-eye"></div>
<div className="eye right-eye"></div> <div className="eye right-eye"></div>
</div> </div>
<div className="otamatone-mouth"></div> <div
className="otamatone-mouth"
style={{
width: `${getMouthSize()}px`,
height: `${getMouthSize()}px`
}}
></div>
</div> </div>
<div {(!isReadOnly || composerIsPlaying) && (
ref={sliderRef} <div
className={`otamatone-neck ${!isReadOnly ? 'interactive' : ''}`} ref={sliderRef}
style={{ cursor: isReadOnly ? 'default' : 'pointer' }} className={`otamatone-neck ${!isReadOnly ? 'interactive' : ''}`}
> style={{ cursor: isReadOnly ? 'default' : 'pointer' }}
<div
className={`frequency-indicator ${isReadOnly ? 'read-only' : ''} ${composerIsPlaying ? 'active' : ''}`}
style={{ left: getFrequencyPosition() }}
> >
<div className="note-marker"> <div
{(!isReadOnly && isDragging.current) && Math.round(frequency.current)} className={`frequency-indicator ${isReadOnly ? 'read-only' : ''} ${composerIsPlaying ? 'active' : ''}`}
style={{ left: getFrequencyPosition() }}
>
{shouldShowNoteMarker() && (
<div className="note-marker">
{frequencyToNote(frequency.current)}
</div>
)}
</div> </div>
</div> </div>
</div> )}
</div> </div>
</div> </div>
); );

View File

@ -104,10 +104,23 @@
.note-marker .note-marker
position: absolute position: absolute
top: -50px top: -50px
font-size: 32pt font-size: 24pt
color: $white color: $white
text-shadow: 0 0 15px rgba(255, 255, 255, 0.7) text-shadow: 0 0 15px rgba(255, 255, 255, 0.7)
font-weight: bold font-weight: bold
font-family: 'Arial', sans-serif
background: rgba(0, 0, 0, 0.6)
padding: 2px 8px
border-radius: 8px
backdrop-filter: blur(5px)
min-width: 60px
text-align: center
animation: note-pop 0.2s ease-out
opacity: 1
.read-only &
background: rgba(0, 0, 0, 0.8)
box-shadow: 0 0 20px rgba(0, 0, 0, 0.4)
.otamatone-face .otamatone-face
width: 140px width: 140px
@ -169,10 +182,11 @@
border-radius: 50% border-radius: 50%
position: absolute position: absolute
bottom: 30px bottom: 30px
width: 30px transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275)
height: 30px transform-origin: center center
transition: all 0.3s ease
animation: mouth-pulse 5s infinite alternate ease-in-out &.active
animation: mouth-pulse 0.5s infinite alternate ease-in-out
@keyframes blink @keyframes blink
0%, 90%, 100% 0%, 90%, 100%
@ -200,4 +214,14 @@
opacity: 0 opacity: 0
100% 100%
transform: translateY(0) transform: translateY(0)
opacity: 1
@keyframes note-pop
0%
transform: scale(0.8)
opacity: 0.5
50%
transform: scale(1.1)
100%
transform: scale(1)
opacity: 1 opacity: 1