Fix MusicSlider.jsx
This commit is contained in:
parent
7996905f30
commit
f9447b3deb
@ -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,8 +229,15 @@ 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>
|
||||||
|
{(!isReadOnly || composerIsPlaying) && (
|
||||||
<div
|
<div
|
||||||
ref={sliderRef}
|
ref={sliderRef}
|
||||||
className={`otamatone-neck ${!isReadOnly ? 'interactive' : ''}`}
|
className={`otamatone-neck ${!isReadOnly ? 'interactive' : ''}`}
|
||||||
@ -206,11 +247,14 @@ export const MusicSlider = ({ isReadOnly, onFrequencyChange, frequency: external
|
|||||||
className={`frequency-indicator ${isReadOnly ? 'read-only' : ''} ${composerIsPlaying ? 'active' : ''}`}
|
className={`frequency-indicator ${isReadOnly ? 'read-only' : ''} ${composerIsPlaying ? 'active' : ''}`}
|
||||||
style={{ left: getFrequencyPosition() }}
|
style={{ left: getFrequencyPosition() }}
|
||||||
>
|
>
|
||||||
|
{shouldShowNoteMarker() && (
|
||||||
<div className="note-marker">
|
<div className="note-marker">
|
||||||
{(!isReadOnly && isDragging.current) && Math.round(frequency.current)}
|
{frequencyToNote(frequency.current)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -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%
|
||||||
@ -201,3 +215,13 @@
|
|||||||
100%
|
100%
|
||||||
transform: translateY(0)
|
transform: translateY(0)
|
||||||
opacity: 1
|
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