paulrosen / abcjs

javascript for rendering abc music notation
Other
1.91k stars 282 forks source link

SynthController Play() after Pause() not working #749

Open SebastianGroesswang opened 2 years ago

SebastianGroesswang commented 2 years ago

First of all great work and thank you for this public library..

I am currently working on an react application which displays and plays a specific music sheet. In other words a play along of abc strings. I was using the basic CreateSynth() until I had to transpose the audio.

So I changed to SynthController(). I used a custom bar to display the play, pause and restart button, because it was still present from the CreateSynth() job.

After I called synthController.play() the music started playing till i called the synthController.pause() method. Now I want to continue from the pause and due to that I called synthController.play() again. And nothing happened.

Am I using the wrong calls of methods or could this be a bigger bug?

paulrosen commented 2 years ago

I can't tell without seeing more of your code where the problem is.

But for transposing the audio you should be able to do that with no changes. If you add %%MIDI transpose -2 to the top of your ABC string it should play two half steps lower than written.

And I apologize for the state of the audio API. It grew and evolved. I'm currently working on a wrapper that will simplify the calling of all the audio functions. I'm not sure when it will be ready, though.

SebastianGroesswang commented 2 years ago

im sry for the late response...

import React, {Component} from 'react';
import 'abcjs/abcjs-midi.css';
import abcjs from 'abcjs/midi';
import { width } from '@mui/system';
import PropTypes from 'prop-types';
import { withStyles } from '@mui/styles';
import { Button, formLabelClasses, IconButton } from '@mui/material';
import { ArrowBack, FormatColorResetOutlined, Pause, PlayArrow, Replay } from '@mui/icons-material';

class ABCPlayer extends Component {

    constructor(props){
        super(props)
        this.state = {
            alreadyPlaying: false
        }
    }

    render(){
        const {classes} = this.props;

        return(
            <div className={classes.content}>

                <div className={classes.header}>
                    <IconButton color="primary" aria-label="go back to scan" onClick={() => {this.synthController.pause();this.timingCallbacks.stop();this.props.returnBack()}}>
                        <ArrowBack/>
                    </IconButton>
                </div>

                <div style={{backgroundColor: 'white'}}>
                    <div id="paper" ></div>
                </div>

                <div id="audio"/>
                <div className={classes.alignCenterBottom}>

                    <IconButton color="primary" aria-label="go back to scan" onClick={() => {
                        if(this.state.alreadyPlaying === false){
                            this.synthController.play()
                            this.timingCallbacks.start()
                            this.setState({alreadyPlaying: true})
                        }
                        }}>
                        <PlayArrow/>
                    </IconButton>

                    <IconButton color="primary" aria-label="go back to scan" onClick={() => {

                            this.synthController.pause()
                            this.timingCallbacks.pause()

                            this.setState({alreadyPlaying: false})
                        }}>
                        <Pause/>
                    </IconButton>

                    <IconButton color="primary" aria-label="go back to scan" onClick={() => {
                        this.synthController.restart()
                        this.timingCallbacks.reset()
                    }}>
                        <Replay/>
                    </IconButton>

                </div>
            </div>   
        )
    }

    componentDidMount() {
        this.synthController = new abcjs.synth.SynthController()

        this.synthController.load("#audio", {}, {            
            displayLoop: false, 
            displayRestart: false, 
            displayPlay: false, 
            displayProgress: false, 
            displayWarp: false
        });

        this.visualObj = abcjs.renderAbc(
            "paper",
            this.props.abc,
            {
                responsive : "resize", 
                staffwidth: 300,
                wrap: { 
                    minSpacing: 1.2, 
                    maxSpacing: 2.7, 
                    preferredMeasuresPerLine: 2
                }
            }
        )
        this.timingCallbacks = new abcjs.TimingCallbacks(this.visualObj[0], {eventCallback: (ev) => {console.log(ev)}});

        this.synth = new abcjs.synth.CreateSynth();

        var myContext = new AudioContext();

        this.synth.init({
            audioContext: myContext,
            visualObj: this.visualObj[0],
            options: {
                pan: [ -0.3, 0.3 ],

            }
        }).then((response) => {

            this.synthController.setTune(this.visualObj[0], true, 
                { 
                    onEnded: () => {console.log("stopped"); this.setState({alreadyPlaying: false})}, //onEnded does not work
                    midiTranspose: this.props.transpose, 
                    program: 0
                }
            ).then(
                () => console.log("audio reloaded")
            )

        }).catch(function (reason) {
            console.log(reason)
        });

    }
};
ABCPlayer.propTypes = {
    classes: PropTypes.object.isRequired,
  };

export default withStyles(styles)(ABCPlayer);

this is my component i have coded

The abc transpose parameter sounds like a good workaround but i would rather use the transpose option of the SynthController. My intention is to hold the abc string as short as it can be. And the midi parameter seems quite difficult to understand.

paulrosen commented 2 years ago

I might not be following this exactly but I see you setting the tune when the component is mounted, but I don't see it being recreated if this.props.transpose changes. Is that variable dynamic because if so setTune needs to be called again when it changes.

Other than that, your code matches the code in https://paulrosen.github.io/abcjs/examples/basic-transpose.html so I think it should work.

The first thing I'd do is put console.log(this.props.transpose) just above setTune to make sure the expected value is being passed. If so, I'd put a breakpoint at the top of synth/abc_midi_sequencer.js to make sure options.midiTranspose contains the correct value.

SebastianGroesswang commented 2 years ago

this.props.transpose will not be changed after this component has been mounted. The transposing workes with no problems at all. Cause after this.synthController.play() has been called the audio which will be played is in the perfect pitch. But the problem here is after I call this.synthController.pause() and then calling this.synthController.play() again. Nothing will be played. And yes I took the basic-transpose.html as example and coded after it. But one thing is different is I directly call the synthController.play/.pause methods and not load the buttons, progressbar etc in #audio.

paulrosen commented 2 years ago

Oh, I see. I didn't understand. I'll see if I can reproduce.

paulrosen commented 2 years ago

What version of abcjs did you specify In package.json? I suspect it is an old version because the import has changed from import abcjs from 'abcjs/midi'; to import abcjs from 'abcjs';

If you change it to 6.0.0-beta.36 do you still have the problem?

SebastianGroesswang commented 2 years ago

before i was using 5.12.0

Even if I adopt the new version and the other import, the same bug occurs.

I will try for a stable build the workaround with the %MIDI header parameter in the abc string.

dbq817 commented 2 years ago

I also ran into this problem with version 6.0.2, after pausing the music successfully, calling play() will not work, but if you carry on calling play() and pause(), and then play() again, it works :)

I am trying to find a workaround for this.

Best regards, Ben

dbq817 commented 2 years ago

just like I said, you can call pause-play-pause to implement the PAUSE operation as a workaround , and then play() works well.

zoschfrosch commented 5 months ago

I have the same problem with 6.3.0. The "pause play pause play" Workaround does it's job, but when I try to restart instead with "pause play pause restart play", the position slider starts at the beginning, but the playback contiunues at the position where it was paused. Is there any news about this issue?

Kind regards Thomas