admin管理员组文章数量:1122832
I'm using Vue v3.5.12, tone v15.0.4 and @tonejs/midi. My requirement is to load a MIDI file and convert to a Midi
object, schedule it to playback with Tone.js and implement the following user controls: Play, Pause, Continue, Stop, Loop[from, to]. I also want Volume and Tempo controls which can be used during playback and later on this will be linked to a metronome track. So far I have got Play, Pause, Continue, Stop and Volume working to my satisfaction. However, when I try tp change the tempo up or down it appears to work successfully, but when I stop the playback and start again it either fails to restart or starts after a long pause at the original tempo, not the one that was set when playback was stopped. A further issue is that if I change the tempo and then pause it restarts at the wrong position. Further pauses and continues work OK and the tempo is correct. My code is based on the example in the github repository for [tonejs/midi][1]
. Here is the applicable code (Typescript):
const onSelectedMidiFileChange = async () => {
console.log('Selected example:', selectedMidiFile.value);
try {
if (isPlayingMidi.value) {
togglePlayMidi()
}
currentMidiFile = await Midi.fromUrl(selectedMidiFile.value[1])
console.log(currentMidiFile)
selectedMidiFileJSON.value = JSON.stringify(currentMidiFile, null, 2)
// console.log(selectedMidiFileJSON.value);
} catch(error) {
return `Invalid JSON ${error}`
}
}
// volume control
const gain = new Tone.Gain(1).toDestination(); // Initialize with volume at 0 dB
// Function to set global volume
const setGlobalVolume = (volumeLevel: number) => {
// Convert volumeLevel from a percentage (0-100) to dB
const volumeDb = volumeLevelToDb(volumeLevel);
gain.gain.setValueAtTime(Tone.dbToGain(volumeDb), Tone.now()); // Efficiently set the gain
// console.log(`Global volume set to ${volumeDb} dB, gain =`, Tone.dbToGain(volumeDb)); // Debug log
}
// Convert volume percentage to dB
const volumeLevelToDb = (percentage: number): number => {
return percentage === 0 ? -Infinity : (percentage - 100) * 0.25; // Fit your own scaling
}
// tempo control
const setTempo = (newTempo: number) => {
Tone.getTransport().bpm.value = newTempo; // Set the new tempo
console.log(`Transport tempo set to ${round(Tone.getTransport().bpm.value, 0)}`)
};
const synths: Tone.PolySynth<Tone.Synth<Tone.SynthOptions>>[] = []
// called when `play` button clicked
const togglePlayMidi = () => {
// Toggle state of isPlayingMidi
isPlayingMidi.value = !isPlayingMidi.value
if (isPlayingMidi.value) {
// create a PolySynth for each voice (track) and schedule to play starting in 0.5 seconds
if (Tone.getTransport().state !== 'started') {
Tone.getTransport().start(0)
console.log(`Transport started at ${round(Tone.getTransport().seconds)} seconds, tempo: ${Tone.getTransport().bpm.value}bpm`)
}
const now = Tone.now() + 0.5
if (currentMidiFile) {
currentMidiFile.tracks.forEach((track, trk) => {
//create a synth for each track
const synth = new Tone.PolySynth(Tone.Synth, {
envelope: {
attack: 0.02,
decay: 0.1,
sustain: 0.3,
release: 1
}
}).sync().connect(gain) /* added `.sync()` method which connects the `synth` to the `Transport` */
synths.push(synth)
//schedule all of the events
track.notes.forEach((note, idx) => {
synth.triggerAttackRelease(note.name, note.duration, note.time + now, note.velocity)
})
})
}
} else {
if (isPausedMidi.value) {
pauseContinueMidi()
}
console.log(`Transport stopped at ${round(Tone.getTransport().seconds)} seconds`);
Tone.getTransport().stop(0)
Tone.getTransport().cancel(); // Clear scheduled events and reset the timer
//empty the `synths` array and dispose each one
while (synths.length) {
const synth = synths.shift()
synth?.unsync()
synth?.dispose()
}
}
}
let pauseTime = 0; // Track the time when pausing
const pauseContinueMidi = () => {
const transport = Tone.getTransport();
// console.log("Current transport seconds:", transport.seconds);
if (!isPausedMidi.value) {
// When pausing, capture the current transport time
pauseTime = transport.seconds; // Capture current play time
transport.pause(); // Pause the transport
console.log("Transport paused at:", round(pauseTime), `, state: ${transport.state}`);
} else {
// If it's paused, restart at the `pauseTime`
transport.seconds = pauseTime
transport.start();
console.log("Transport re-started at:", round(transport.seconds), `, state: ${transport.state}`);
}
// Toggle the paused state
isPausedMidi.value = !isPausedMidi.value
}
const round = (n: number, decimals: number = 2) => Math.round(n * Math.pow(10, decimals)) / Math.pow(10, decimals)
And here is a log of significant events:
Start: Transport started at 23.51 seconds, tempo: 120bpm
Stop: Transport stopped at 30.86 seconds
Start: Transport started at 36.13 seconds, tempo: 120bpm
Pause: Transport paused at: 42.12 , state: paused
Continue: Transport re-started at: 42.12 , state: started
Stop: Transport stopped at 45.9 seconds
Start: Transport started at 51.48 seconds, tempo: 120bpm // everything worked fine up to here
Tempo: Transport tempo set to 159 // changed tempo here
Pause: Transport paused at: 62.13 , state: paused
Continue: Transport re-started at: 62.13 , state: started // continued from wrong position
Pause: Transport paused at: 67.85 , state: paused
Continue: Transport re-started at: 67.85 , state: started // continued from pause
Stop: Transport stopped at 71.94 seconds
Start: Transport started at 85.32 seconds, tempo: 159bpm // Didn't start playing for nearly 15 seconds and didn't play at revisrd tempo
Stop: Transport stopped at 100.21 seconds
Tone.js is a fantastic library for handling Web Audio but it's very difficult for a hobbyist like myself to understand how all the moving parts fit together. Can anyone help me get through this stumbling block? Thanks.
I'm using Vue v3.5.12, tone v15.0.4 and @tonejs/midi. My requirement is to load a MIDI file and convert to a Midi
object, schedule it to playback with Tone.js and implement the following user controls: Play, Pause, Continue, Stop, Loop[from, to]. I also want Volume and Tempo controls which can be used during playback and later on this will be linked to a metronome track. So far I have got Play, Pause, Continue, Stop and Volume working to my satisfaction. However, when I try tp change the tempo up or down it appears to work successfully, but when I stop the playback and start again it either fails to restart or starts after a long pause at the original tempo, not the one that was set when playback was stopped. A further issue is that if I change the tempo and then pause it restarts at the wrong position. Further pauses and continues work OK and the tempo is correct. My code is based on the example in the github repository for [tonejs/midi][1]
. Here is the applicable code (Typescript):
const onSelectedMidiFileChange = async () => {
console.log('Selected example:', selectedMidiFile.value);
try {
if (isPlayingMidi.value) {
togglePlayMidi()
}
currentMidiFile = await Midi.fromUrl(selectedMidiFile.value[1])
console.log(currentMidiFile)
selectedMidiFileJSON.value = JSON.stringify(currentMidiFile, null, 2)
// console.log(selectedMidiFileJSON.value);
} catch(error) {
return `Invalid JSON ${error}`
}
}
// volume control
const gain = new Tone.Gain(1).toDestination(); // Initialize with volume at 0 dB
// Function to set global volume
const setGlobalVolume = (volumeLevel: number) => {
// Convert volumeLevel from a percentage (0-100) to dB
const volumeDb = volumeLevelToDb(volumeLevel);
gain.gain.setValueAtTime(Tone.dbToGain(volumeDb), Tone.now()); // Efficiently set the gain
// console.log(`Global volume set to ${volumeDb} dB, gain =`, Tone.dbToGain(volumeDb)); // Debug log
}
// Convert volume percentage to dB
const volumeLevelToDb = (percentage: number): number => {
return percentage === 0 ? -Infinity : (percentage - 100) * 0.25; // Fit your own scaling
}
// tempo control
const setTempo = (newTempo: number) => {
Tone.getTransport().bpm.value = newTempo; // Set the new tempo
console.log(`Transport tempo set to ${round(Tone.getTransport().bpm.value, 0)}`)
};
const synths: Tone.PolySynth<Tone.Synth<Tone.SynthOptions>>[] = []
// called when `play` button clicked
const togglePlayMidi = () => {
// Toggle state of isPlayingMidi
isPlayingMidi.value = !isPlayingMidi.value
if (isPlayingMidi.value) {
// create a PolySynth for each voice (track) and schedule to play starting in 0.5 seconds
if (Tone.getTransport().state !== 'started') {
Tone.getTransport().start(0)
console.log(`Transport started at ${round(Tone.getTransport().seconds)} seconds, tempo: ${Tone.getTransport().bpm.value}bpm`)
}
const now = Tone.now() + 0.5
if (currentMidiFile) {
currentMidiFile.tracks.forEach((track, trk) => {
//create a synth for each track
const synth = new Tone.PolySynth(Tone.Synth, {
envelope: {
attack: 0.02,
decay: 0.1,
sustain: 0.3,
release: 1
}
}).sync().connect(gain) /* added `.sync()` method which connects the `synth` to the `Transport` */
synths.push(synth)
//schedule all of the events
track.notes.forEach((note, idx) => {
synth.triggerAttackRelease(note.name, note.duration, note.time + now, note.velocity)
})
})
}
} else {
if (isPausedMidi.value) {
pauseContinueMidi()
}
console.log(`Transport stopped at ${round(Tone.getTransport().seconds)} seconds`);
Tone.getTransport().stop(0)
Tone.getTransport().cancel(); // Clear scheduled events and reset the timer
//empty the `synths` array and dispose each one
while (synths.length) {
const synth = synths.shift()
synth?.unsync()
synth?.dispose()
}
}
}
let pauseTime = 0; // Track the time when pausing
const pauseContinueMidi = () => {
const transport = Tone.getTransport();
// console.log("Current transport seconds:", transport.seconds);
if (!isPausedMidi.value) {
// When pausing, capture the current transport time
pauseTime = transport.seconds; // Capture current play time
transport.pause(); // Pause the transport
console.log("Transport paused at:", round(pauseTime), `, state: ${transport.state}`);
} else {
// If it's paused, restart at the `pauseTime`
transport.seconds = pauseTime
transport.start();
console.log("Transport re-started at:", round(transport.seconds), `, state: ${transport.state}`);
}
// Toggle the paused state
isPausedMidi.value = !isPausedMidi.value
}
const round = (n: number, decimals: number = 2) => Math.round(n * Math.pow(10, decimals)) / Math.pow(10, decimals)
And here is a log of significant events:
Start: Transport started at 23.51 seconds, tempo: 120bpm
Stop: Transport stopped at 30.86 seconds
Start: Transport started at 36.13 seconds, tempo: 120bpm
Pause: Transport paused at: 42.12 , state: paused
Continue: Transport re-started at: 42.12 , state: started
Stop: Transport stopped at 45.9 seconds
Start: Transport started at 51.48 seconds, tempo: 120bpm // everything worked fine up to here
Tempo: Transport tempo set to 159 // changed tempo here
Pause: Transport paused at: 62.13 , state: paused
Continue: Transport re-started at: 62.13 , state: started // continued from wrong position
Pause: Transport paused at: 67.85 , state: paused
Continue: Transport re-started at: 67.85 , state: started // continued from pause
Stop: Transport stopped at 71.94 seconds
Start: Transport started at 85.32 seconds, tempo: 159bpm // Didn't start playing for nearly 15 seconds and didn't play at revisrd tempo
Stop: Transport stopped at 100.21 seconds
Tone.js is a fantastic library for handling Web Audio but it's very difficult for a hobbyist like myself to understand how all the moving parts fit together. Can anyone help me get through this stumbling block? Thanks.
Share Improve this question edited Nov 21, 2024 at 20:18 apps2go asked Nov 21, 2024 at 20:06 apps2goapps2go 1662 silver badges9 bronze badges2 Answers
Reset to default 1I've found a different approach to the issue I posted above. By follwing the example FatOscillator
in the Tonejs
github repository adapted to generate multiple parts from my MIDI file, I was able to control tempo in real time using Transport.bpm.value
property. All is well.
本文标签: typescriptTonejs v1504how do I change tempo during playback when playing a MIDI fileStack Overflow
版权声明:本文标题:typescript - Tone.js v15.0.4 - how do I change tempo during playback when playing a MIDI file - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736307472a1933323.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论