Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zilong-L committed Aug 27, 2024
1 parent 46033c2 commit 621319c
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 79 deletions.
113 changes: 53 additions & 60 deletions src/Components/EarTrainers/DegreeTrainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,126 +10,121 @@ import SettingsIcon from '@mui/icons-material/Settings';
import Sidebar from '@components/Sidebar';
import DegreeTrainerSettings from '@components/EarTrainers/DegreeTrainerSettings';
import { getPianoInstance, getDroneInstance } from '@components/ToneInstance';
import IntroModal from '@components/EarTrainers/DegreeTrainerIntro'; // 新增导入

const apps = [{ name: 'Ear Trainer', path: '/ear-trainer' }, { name: 'Chord Trainer', path: '/chord-trainer' }];
const degrees = [
{ name: "I", distance: 0, enable: true },
{ name: "IIb", distance: 1, enable: false },
{ name: "II", distance: 2, enable: true },
{ name: "IIIb", distance: 3, enable: true },
{ name: "IIIb", distance: 3, enable: false },
{ name: "III", distance: 4, enable: true },
{ name: "IV", distance: 5, enable: true },
{ name: "IV", distance: 5, enable: false },
{ name: "Vb", distance: 6, enable: false },
{ name: "V", distance: 7, enable: true },
{ name: "V", distance: 7, enable: false },
{ name: "VIb", distance: 8, enable: false },
{ name: "VI", distance: 9, enable: true },
{ name: "VI", distance: 9, enable: false },
{ name: "VIIb", distance: 10, enable: false },
{ name: "VII", distance: 11, enable: true },
]
{ name: "VII", distance: 11, enable: false },
];

const EarTrainer = () => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isIntroOpen, setIsIntroOpen] = useState(true); // 控制 IntroModal 的显示状态

const [currentNote, setCurrentNote] = useState("");
const [disabledNotes, setDisabledNotes] = useState([]);
const [gameStarted, setGameStarted] = useState(false);


const [bpm, setBpm] = useState(60);
const [bpm, setBpm] = useState(40);
const [currentNotes, setCurrentNotes] = useState(degrees);
const [filteredNotes, setFilteredNotes] = useState(degrees);
const [possibleMidiList, setPossibleMidiList] = useState([]);

const [droneVolume, setDroneVolume] = useState(0.3);
const [pianoVolume, setPianoVolume] = useState(0.3);
const [rootNote, setRootNote] = useState(36);
const [range, setRange] = useState([Tone.Frequency('C3').toMidi(), Tone.Frequency('C6').toMidi()])
const [pianoVolume, setPianoVolume] = useState(1.0);
const [rootNote, setRootNote] = useState(Tone.Frequency('C3').toMidi());
const [range, setRange] = useState([Tone.Frequency('C3').toMidi(), Tone.Frequency('C4').toMidi()]);

const piano = getPianoInstance();
const drone = getDroneInstance();

const pianoSampler = piano.sampler;

useEffect(() => {
console.log('Degree Trainer mounted');
return () => {
console.log('drone stopped')
console.log('drone stopped');
drone.stop();
console.log('Release all scheduled notes');
Tone.getTransport().stop();
Tone.getTransport().cancel(); // Cancel all scheduled events
};
}, []);

useEffect(() => {
drone.updateRoot(rootNote);
drone.setVolume(droneVolume);
piano.setVolume(pianoVolume);
}
, [droneVolume, pianoVolume, rootNote]);
}, [droneVolume, pianoVolume, rootNote]);

useEffect(() => {
const newNotes = currentNotes.filter((obj) => obj.enable);
setFilteredNotes(newNotes);
}, [currentNotes]);

useEffect(() => {
const newNotes = currentNotes.filter((obj) => obj.enable)
setFilteredNotes(newNotes)
}, [currentNotes])

useEffect(()=>{
const newNote = generateRandomNoteBasedOnRoot();
setCurrentNote(newNote);
},[possibleMidiList])
}, [possibleMidiList]);

useEffect(() => {
console.log(range)
}, [range])
console.log(range);
}, [range]);

const startGame = () => {
// Reset transport for each game start
Tone.getTransport().stop();
Tone.getTransport().position = 0;
Tone.getTransport().cancel(); // Clear all previous scheduled events

setGameStarted(true);
setDisabledNotes([]);

// const endtime = playKeyEstablishMelody();
const note = generateRandomNoteBasedOnRoot();
setCurrentNote(note);
playNote(note,1);
playNote(note, 1);

// Schedule the random note to play after the melody
drone.start();

// Start the transport
Tone.getTransport().start();
};

// Function to replay the melody within the game
const playNote = (note = null,delay=0) => {
const playNote = (note = null, delay = 0) => {
Tone.getTransport().stop();
Tone.getTransport().position = 0;
Tone.getTransport().cancel(); // Clear all previous scheduled events
if (!note) {
note = currentNote;
}
pianoSampler.triggerAttackRelease(note, 60 / bpm,Tone.now()+delay);
pianoSampler.triggerAttackRelease(note, 60 / bpm, Tone.now() + delay);
};


const generateRandomNoteBasedOnRoot = () => {
if (possibleMidiList.length === 0) return null;
const nextNoteMidi = possibleMidiList[Math.floor(Math.random() * possibleMidiList.length)];
return Tone.Frequency(nextNoteMidi, 'midi').toNote();
};


useEffect(() => {
const expandedIntervals = [];

// 扩展每个音符到所有可能的八度范围内
filteredNotes.forEach((note) => {
for (let octaveShift = -4; octaveShift <= 4; octaveShift++) {
const midiValue = rootNote + note.distance + octaveShift * 12;
expandedIntervals.push(midiValue);
}
});

// 过滤掉不在范围内的音符
const newIntervalList = expandedIntervals.filter(
(midi) => midi >= range[0] && midi <= range[1]
);
Expand All @@ -141,28 +136,29 @@ const EarTrainer = () => {
const handleNoteGuess = (guessedNote) => {
const guessedNoteMidi = Tone.Frequency(guessedNote).toMidi();
const currentNoteMidi = Tone.Frequency(currentNote).toMidi();
console.log(guessedNote,currentNote)
// 检查两个音符是否在同一音阶内,即相差一个或多个八度
console.log(guessedNote, currentNote);
if (guessedNoteMidi % 12 === currentNoteMidi % 12) {
setDisabledNotes([]); // 重置禁用音符状态,准备下一轮
setDisabledNotes([]);
setCurrentNote(() => {
const nextNote = generateRandomNoteBasedOnRoot(rootNote, filteredNotes);
playNote(nextNote);
return nextNote;
});
} else {
setDisabledNotes((prev) => [...prev, guessedNote]);
playNote();
playNote();
}
};


const handleIntroClose = () => {
setIsIntroOpen(false);
startGame(); // 在关闭 IntroModal 后开始游戏
};

return (
<>
<AppBar position="static" sx={{ boxShadow: 0, paddingX: '0.5rem' }}>
<Toolbar sx={{ color: (theme) => theme.palette.text.primary, height: '64px' }}>

<Typography variant="h5" sx={{ flexGrow: 1, textAlign: 'left' }}>
<Link to="/ear-trainer" style={{ textDecoration: 'none', color: 'inherit' }}>
Ear Trainer
Expand All @@ -171,8 +167,8 @@ const EarTrainer = () => {
<Button
onClick={() => setIsSettingsOpen(!isSettingsOpen)}
variant="contained"
color="primary" // Make the button stand out with a primary color
sx={{ boxShadow: 'none', }}
color="primary"
sx={{ boxShadow: 'none' }}
>
<SettingsIcon />
</Button>
Expand All @@ -185,10 +181,16 @@ const EarTrainer = () => {
<MenuIcon />
</Button>
{apps.map((item) => (
<Button variant="contained" key={item.name} component={Link} to={item.path} sx={{
display: 'none',
'@media (min-width:600px)': { display: 'block', boxShadow: 'none', textTransform: 'none' }
}}>
<Button
variant="contained"
key={item.name}
component={Link}
to={item.path}
sx={{
display: 'none',
'@media (min-width:600px)': { display: 'block', boxShadow: 'none', textTransform: 'none' },
}}
>
{item.name}
</Button>
))}
Expand Down Expand Up @@ -224,18 +226,9 @@ const EarTrainer = () => {
setCurrentNotes={setCurrentNotes}
playNote={playNote}
/>
{!gameStarted ? (
<Button
variant="contained"
color="primary"
onClick={startGame}
sx={{ height: '30vh', marginTop: 'auto', marginBottom: '2rem' }}
fullWidth
>
<Typography variant='h2'>开始</Typography>
</Button>
) : (
<Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'flex-end', height: '100%',marginBottom:'2rem' }}>
<IntroModal isOpen={isIntroOpen} handleClose={handleIntroClose} />
{gameStarted && (
<Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'flex-end', height: '100%', marginBottom: '2rem' }}>
<Box sx={{ flexGrow: 1 }} /> {/* 这个空的 Box 会推动下面的内容到底部 */}
<Grid container spacing={2} sx={{ marginBottom: '1rem' }}>
{filteredNotes.map((note) => (
Expand Down
38 changes: 38 additions & 0 deletions src/Components/EarTrainers/DegreeTrainerIntro.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { Modal, Box, Typography, Button } from '@mui/material';

const IntroModal = ({ isOpen, handleClose }) => {
return (
<Modal
open={isOpen}
onClose={handleClose}
aria-labelledby="intro-modal-title"
aria-describedby="intro-modal-description"
>
<Box sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4
}}>
<Typography id="intro-modal-title" variant="h6" component="h2">
欢迎使用音程训练器
</Typography>
<Typography id="intro-modal-description" sx={{ mt: 2 }}>
您将会听到一个低音作为主音,随后会播放另一个音符。您需要根据听到的音符选择相应的音程级数。
在右上角的设置中,您可以调节音量和练习内容。
</Typography>
<Button onClick={handleClose} variant="contained" color="primary" sx={{ mt: 3 }}>
开始练习
</Button>
</Box>
</Modal>
);
};

export default IntroModal;
2 changes: 1 addition & 1 deletion src/Components/EarTrainers/DegreeTrainerSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ function DegreeTrainerSettings({

<Button
color='secondary'
onClick={() => setShowDegreeSettings(false)}
onClick={() => setShowVolumeSettings(false)}
sx={{ display: 'flex', justifyContent: 'flex-center', fontSize: '1.2rem', marginLeft: 'auto' }}
>
<HomeIcon/>
Expand Down
35 changes: 17 additions & 18 deletions src/Components/ToneInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,50 +41,49 @@ function getPianoInstance() {

function getDroneInstance() {
let masterGainNode = null;
let rootMax = Tone.Frequency("C5").toMidi()
let rootMin = Tone.Frequency("C2").toMidi()
let rootMax = Tone.Frequency("C5").toMidi();
let rootMin = Tone.Frequency("C2").toMidi();
if (!droneInstance) {
// Initialize the oscillators with updated names
const rootOscillator = new Tone.Oscillator("C2", "sine");
const octaveOscillator = new Tone.Oscillator("C3", "sine"); // First harmonic
const fifthOscillator = new Tone.Oscillator("G2", "sine"); // Second harmonic
const octaveOscillator = new Tone.Oscillator("C3", "sine");
const fifthOscillator = new Tone.Oscillator("G2", "sine");

// Initialize gain nodes for each part
const rootGain = new Tone.Gain(0.8); // Strongest component
const octaveGain = new Tone.Gain(0.2); // Quieter harmonic
const fifthGain = new Tone.Gain(0.1); // Even quieter harmonic
const rootGain = new Tone.Gain(0.8);
const octaveGain = new Tone.Gain(0.2);
const fifthGain = new Tone.Gain(0.1);

// Create a master gain node to control overall volume
masterGainNode = new Tone.Gain(0.075).toDestination(); // Default volume at midpoint
// Create a limiter to prevent distortion
const limiter = new Tone.Limiter(-10).toDestination();

// Adjust the master gain node as needed
masterGainNode = new Tone.Gain(0.35);

// Connect oscillators to their respective gain nodes
rootOscillator.connect(rootGain);
octaveOscillator.connect(octaveGain);
fifthOscillator.connect(fifthGain);

// Connect gain nodes to the master gain node
rootGain.connect(masterGainNode);
octaveGain.connect(masterGainNode);
fifthGain.connect(masterGainNode);

// Create a start function that starts all oscillators
// Connect the master gain to the limiter
masterGainNode.connect(limiter);

function start() {
rootOscillator.start();
octaveOscillator.start();
fifthOscillator.start();
}

// Create a stop function that stops all oscillators
function stop() {
rootOscillator.stop();
octaveOscillator.stop();
fifthOscillator.stop();
}

// Create a setVolume function that maps 0-1 to 0-0.15
function setVolume(value) {
const clampedValue = Math.min(1, Math.max(0, value)); // Clamp between 0 and 1
masterGainNode.gain.value = clampedValue * 0.35; // Map to 0-0.15
const clampedValue = Math.min(1, Math.max(0, value));
masterGainNode.gain.value = clampedValue * 0.35; // Adjusted volume
}

// Create an updateRoot function to change the root note
Expand Down

0 comments on commit 621319c

Please sign in to comment.