我有一个 next.js React 网站,它使用 api 生成 MIDI 文件的延续。 api 获取 MIDI 文件,对其进行处理并将处理后的 MIDI 文件上传到云端,并将 URL 返回到网站。
我已经构建了一个音频播放器组件(在克劳德的帮助下,因为我以前从未这样做过), 然而,它产生的声音低于最佳效果,几种乐器重叠,鼓声听起来像高音调的机械噪音。
我将附上下面的组件代码,但我有几个问题:
我是音频处理领域的新手,因此非常感谢任何帮助。
附件是完整的组件代码(减去自定义)
'use client';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import styled from 'styled-components';
import * as Tone from 'tone';
import { Midi } from '@tonejs/midi';
import { FaPlay, FaPause, FaStepBackward, FaStepForward } from 'react-icons/fa';
interface AudioPlayerProps {
midiUrl: string;
}
interface NoteData {
time: number;
note: string;
duration: number;
velocity: number;
}
const AudioPlayer: React.FC<AudioPlayerProps> = ({ midiUrl }) => {
const [isPlaying, setIsPlaying] = useState(false);
const [duration, setDuration] = useState(0);
const [currentTime, setCurrentTime] = useState(0);
const [error, setError] = useState<string | null>(null);
const [progress, setProgress] = useState(0);
const synthRef = useRef<Tone.PolySynth | null>(null);
const midiRef = useRef<Midi | null>(null);
const notesRef = useRef<NoteData[]>([]);
const initializeAudioContext = useCallback(async () => {
if (!synthRef.current) {
await Tone.start();
synthRef.current = new Tone.PolySynth(Tone.Synth, {
envelope: {
attack: 0.02,
decay: 0.1,
sustain: 0.3,
release: 0.8,
},
}).toDestination();
}
}, []);
useEffect(() => {
const loadMidi = async () => {
try {
await initializeAudioContext();
const midi = await Midi.fromUrl(midiUrl);
midiRef.current = midi;
setDuration(midi.duration);
notesRef.current = midi.tracks.flatMap(track =>
track.notes.map(note => ({
time: note.time,
note: note.name,
duration: note.duration,
velocity: note.velocity
}))
).sort((a, b) => a.time - b.time);
} catch (error) {
console.error('Error loading MIDI file:', error);
setError('Error loading MIDI file');
}
};
loadMidi();
}, [midiUrl, initializeAudioContext]);
const togglePlayPause = useCallback(async () => {
await initializeAudioContext();
if (isPlaying) {
Tone.getTransport().pause();
} else {
Tone.getTransport().cancel();
Tone.getTransport().seconds = currentTime;
notesRef.current.filter(note => note.time >= currentTime).forEach(note => {
Tone.getTransport().schedule((time) => {
synthRef.current?.triggerAttackRelease(note.note, note.duration, time, note.velocity);
}, note.time);
});
Tone.getTransport().start();
}
setIsPlaying(!isPlaying);
}, [isPlaying, currentTime, initializeAudioContext]);
const handleProgressChange = useCallback((newTime: number) => {
setCurrentTime(newTime);
setProgress((newTime / duration) * 100);
Tone.getTransport().seconds = newTime;
}, [duration]);
const handleProgressBarInteraction = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
const bounds = event.currentTarget.getBoundingClientRect();
const x = event.clientX - bounds.left;
const clickedValue = (x / bounds.width) * duration;
handleProgressChange(clickedValue);
}, [duration, handleProgressChange]);
const rewind = useCallback(() => {
handleProgressChange(Math.max(currentTime - 10, 0));
}, [currentTime, handleProgressChange]);
const fastForward = useCallback(() => {
handleProgressChange(Math.min(currentTime + 10, duration));
}, [currentTime, duration, handleProgressChange]);
useEffect(() => {
const interval = setInterval(() => {
if (isPlaying) {
const current = Tone.getTransport().seconds;
setCurrentTime(current);
setProgress((current / duration) * 100);
if (current >= duration) {
setIsPlaying(false);
Tone.getTransport().stop();
}
}
}, 100);
return () => clearInterval(interval);
}, [isPlaying, duration]);
const formatTime = (time: number) => {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
};
return (
<PlayerContainer>
<ProgressBarContainer
onClick={handleProgressBarInteraction}
onMouseDown={handleProgressBarInteraction}
>
<ProgressBar width={progress} />
<ProgressCircle left={progress} />
</ProgressBarContainer>
<Controls>
<Button onClick={rewind} disabled={!synthRef.current}>
<FaStepBackward />
</Button>
<PlayButton onClick={togglePlayPause}>
{isPlaying ? <FaPause /> : <FaPlay />}
</PlayButton>
<Button onClick={fastForward} disabled={!synthRef.current}>
<FaStepForward />
</Button>
</Controls>
<TimeInfo>
<span>{formatTime(currentTime)}</span>
<span>{formatTime(duration)}</span>
</TimeInfo>
{error && <div style={{ color: 'red', marginTop: '1rem' }}>{error}</div>}
</PlayerContainer>
);
};
export default AudioPlayer;
您可能需要为要使用的乐器分配一些样本,以获得更好的音质。
工具组合 -tone -tonejs/Midi(使用 midi 文件)也可能因生成的文件而出现问题。
MIDI 文件是二进制文件,并且在实现上存在很大差异,我遇到了许多格式奇怪的文件,其中许多文件导致 MIDI 文件库感到困惑。
对于您的情况,我会推荐不同的游戏解决方案。
https://github.com/fraigo/javascript-midi-player
也许值得一试。欢迎私信我。