I_like_foxes
I_like_foxes

Reputation: 1249

Dispatching actions via a callback in Redux

I'm working on building an step sequencer in Redux to help me learning. What I need is some universal clock that functions as "tick" to provide musical timing. So I planed to use the Tone.js library, which builds on top of the Web Audio API. There's the following function:

Tone.Transport.scheduleRepeat(function(time){
//do something with the time
}, "8n");

You provide a callback function which gets called everytime the transport reaches a certain position. My naive approach was to let the callback dispatch an action which increments a tick count in my store. This doesn't work because actions have to be plain objects. What are the possibilities to get this working?

I'm still working on getting the right understanding of the basic underlying principles of Redux so I'm unsure about that but could I somehow catch the callback using middleware and just let it through when it is actually invoked?

Would this be the right direction? What are some libraries, examples or concepts I could look into to get some idea how this could be done?

Is this even the right direction or should I approach this differently? If so, how?

Or do you maybe have any idea what's the best way to provide global timing for different components in a Redux app?

Upvotes: 0

Views: 617

Answers (4)

David Sherman
David Sherman

Reputation: 440

I'm working on an audio related application and have run into the issue of managing the web audio API part along with redux as well.

The way I've solved it is only storing representation of the audio state in the redux store (plain JS objects; what you would store in the database and use to initialize the app). This stored information is used to render the UI.

I have a service 'engine' class which listens to all changes in the store, this is where all the web audio stuff is created and stored. It contains basically a copy of the reducers from the redux store but applies the changes to the web audio nodes.

For example I dispatch an action:

{type:"set-gain", payload:{trackid:3, value:0.7} }

The redux store will simply update a plain JS track object to the new gain value, the engine will find the associated gain node (or create with add etc.) and set the value on it.

In your case you would dispatch an action to set the timing, in the redux store save it as plain JS object, in the engine part you use web audio scheduling to set it.

Upvotes: 0

oftenfrequent
oftenfrequent

Reputation: 64

Basically cwilso responded correctly. If you want to be scheduling JS functions for musical timing, you should not be using callbacks.

If there is Tone.js functionality that you want to implement based on this timing, avoid Redux and call these Tone.js functions directly, either within the callback functions or Tone.Transport.schedule function.

If you are building a sequencer I would recommend looping the Tone.Transport based on the length you want and scheduling the notes to be hit at certain points on the timeline (if this is what you are looking for). Check out the loopStart and loopEnd in the docs for help (http://tonejs.github.io/docs/#Transport).

If this functionality is necessary for visual references, which may be why you want a Redux callback, I can provide an example of how you might do that below:

function incrementTick() {
  return { type: 'INCREMENT_TICK' }
}

// inside your component once the increment function has been connected
Tone.Transport.scheduleRepeat((time) => {
  this.props.incrementTick()
}, "8n");

Hope this helps.

Upvotes: 1

cwilso
cwilso

Reputation: 13918

I went into a lot more detail in https://www.html5rocks.com/en/tutorials/audio/scheduling/, but in short - you shouldn't use Javascript callbacks for musical timing. It's not accurate enough. That's why we have web audio scheduling.

Upvotes: 2

CharlieBrown
CharlieBrown

Reputation: 4163

Very interesting question, that's a pet project I've been also wanting to tackle but haven't written a single LOC yet. :)

For the timing part, you could use a middleware for that, or even a <Clock /> component that launches the scheduler itself and dispatches an action on every tick (probably with the time as payload).

The tricky part however is the overall design of your application. Having researched Tone.js a little bit, it was clear to me that you'd have to separate the audio part from the visuals. Which means your Redux state should only be concerned about representing your step sequencer (I'm visualizing something like a list of lanes (channels/instruments) and your audio logic should be kept outside of it.

I would keep an array of lanes, each of which is itself an array of "steps" that define whether they're "active" or not. Again this is only UI related. Clicking on a step to activate it should modify your state via action creator and also setup anything you'll later need to play with Tone.js.

When playing back your song, you'll need to dispatch that clock tick to advance the current active "step" so you can highlight it in the UI.

Here's a mouth-watering Codepen emulating a Roland TR-808 to grab ideas:

http://codepen.io/pixelass/details/adyLPR

And here's the relevant section on the Tone.js wiki on sync'ing audio and UI:

https://github.com/Tonejs/Tone.js/wiki/Performance#syncing-visuals

Sorry I can't help you further, perhaps you're ahead of me and already have some working code you could share.

Upvotes: 1

Related Questions