Fix MusicSlider.jsx
This commit is contained in:
parent
7996905f30
commit
f9447b3deb
@ -1,6 +1,21 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
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 }) => {
|
||||
const [audioContext, setAudioContext] = 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))}%`;
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className="otamatone-container">
|
||||
<div className="otamatone">
|
||||
@ -195,22 +229,32 @@ export const MusicSlider = ({ isReadOnly, onFrequencyChange, frequency: external
|
||||
<div className="eye left-eye"></div>
|
||||
<div className="eye right-eye"></div>
|
||||
</div>
|
||||
<div className="otamatone-mouth"></div>
|
||||
</div>
|
||||
<div
|
||||
ref={sliderRef}
|
||||
className={`otamatone-neck ${!isReadOnly ? 'interactive' : ''}`}
|
||||
style={{ cursor: isReadOnly ? 'default' : 'pointer' }}
|
||||
>
|
||||
<div
|
||||
className={`frequency-indicator ${isReadOnly ? 'read-only' : ''} ${composerIsPlaying ? 'active' : ''}`}
|
||||
style={{ left: getFrequencyPosition() }}
|
||||
className="otamatone-mouth"
|
||||
style={{
|
||||
width: `${getMouthSize()}px`,
|
||||
height: `${getMouthSize()}px`
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
{(!isReadOnly || composerIsPlaying) && (
|
||||
<div
|
||||
ref={sliderRef}
|
||||
className={`otamatone-neck ${!isReadOnly ? 'interactive' : ''}`}
|
||||
style={{ cursor: isReadOnly ? 'default' : 'pointer' }}
|
||||
>
|
||||
<div className="note-marker">
|
||||
{(!isReadOnly && isDragging.current) && Math.round(frequency.current)}
|
||||
<div
|
||||
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>
|
||||
);
|
||||
|
@ -104,10 +104,23 @@
|
||||
.note-marker
|
||||
position: absolute
|
||||
top: -50px
|
||||
font-size: 32pt
|
||||
font-size: 24pt
|
||||
color: $white
|
||||
text-shadow: 0 0 15px rgba(255, 255, 255, 0.7)
|
||||
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
|
||||
width: 140px
|
||||
@ -169,10 +182,11 @@
|
||||
border-radius: 50%
|
||||
position: absolute
|
||||
bottom: 30px
|
||||
width: 30px
|
||||
height: 30px
|
||||
transition: all 0.3s ease
|
||||
animation: mouth-pulse 5s infinite alternate ease-in-out
|
||||
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275)
|
||||
transform-origin: center center
|
||||
|
||||
&.active
|
||||
animation: mouth-pulse 0.5s infinite alternate ease-in-out
|
||||
|
||||
@keyframes blink
|
||||
0%, 90%, 100%
|
||||
@ -201,3 +215,13 @@
|
||||
100%
|
||||
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
|
Loading…
x
Reference in New Issue
Block a user