YoYoGames / GameMaker-Bugs

Public tracking for GameMaker bugs
15 stars 8 forks source link

Native BPM Tracking (for dynamic music & other beat-related implementation) #2683

Open kuraine opened 6 months ago

kuraine commented 6 months ago

Is your feature request related to a problem?

When working on dynamic music in Game Maker projects, many features need to be created from scratch to enable things like beat tracking for dramatic effects syncing up to the game world, changing tracks on the beat for transitions, etc. These all tend to revolve around knowing what tempo the music is working at, and can be simple if using a time signature that fits into your framerate neatly (120bpm fits into 60fps well & has decent maths to figure out). However, doing all of this for uneven time signatures requires some pretty advanced maths or external tools that is beyond many programmers (myself included).

While in other engines, middleware such as FMOD & Wwise can be used to aid this process, Game Maker doesn't have integration for these tools and therefore internal effects such as the ones introduced recently like reverb & filters can now be used to help bring Game Maker's native support in line with that middleware. BPM tracking would be a huge step towards feature parity and allow dynamic music to be handled completely within Game Maker's native toolset.

Describe the solution you'd like

I would love to be able to have an action that that defines a sound object's bpm (beats per minute) & time signature, as well as supporting events for objects that can be called on beat, or check what beat it is (from 1 to however many beats per measure there are in the time signature assigned). This would possibly call into conflict if multiple music sounds were played simultaneously, so there would need to be some way to define what is the "primary" music track that the bpm track is looking for.

As an example: I call in my script a piece of music as a sound object, defined as 128 bpm with a time signature of 4 beats per measure. Then, on an object that is changing its sprite_index every beat, it is calling an event on beat (rather than every frame such as a Step handler) which advances its sprite_index. Now the sprite is changing on the beat! On a separate object that is a music handler, a parameter calling for the music to change is set to True. In its bpm tracking event, it checks each beat to see if the beat is "1" (the downbeat), and if so, begins a new music track while fading out the one playing. Now the music is transitioning perfectly on the beat!

Describe alternatives you've considered

Alternatively, there could be simply a global setting for the game's bpm & time signature that can be changed manually. This would allow scripts that change the music (sound object) to also change the bpm/time signature so that the definitions are exact to the frame. Not tying it to a sound object would also allow bpm tracking to simply be used as a timing method for actions/events rather than for music, expanding its use cases. For example, someone who simply wanted to trigger things every 10 seconds could set the global bpm to 6 & then the bpm event would call every 10 seconds.

Additional context

For clarity's sake, some terminology definitions: BPM = Beats Per Minute, which is usually used in music to define an exact pace at which its tempo (speed) is played back.

In the example of 120 beats per minute, that means that 120 beats fit into 60 seconds... so there's 2 beats every second, or once every 30 frames (if no frames are dropped). If I was to track this roughly, I would create a script that sets a parameter to 30, subtract it by 1 each frame, and then when it hits 0, trigger a 'beat' event that then registers that a beat has been hit.

Time signature defines how many 'beats' are set per measure & what note value is counted as a beat. So 4/4 is 4 beats per measure, with the quarter note getting the beat. Though for implementation's sake, it could be reduced to "4", since what note value gets the beat doesn't matter. Often when tracking music, you want something to happen on the "downbeat", which is every time a measure hits "1" out of its total beats. So if the time signature was set to 4/4, the "downbeat" is hit every 4th beat.

In the example above of 120bpm at 4/4, I would set a parameter to 30 x 4 (120), subtract by 1 each frame, until it hits 0, which would be the downbeat of the next measure.

Where this gets tricky (as mentioned in the problem) is when you have a BPM that doesn't fit neatly into 60fps. So let's say it's 133bpm, which means there's 2.216666... beats per second. You could roughly call this 1 beat every 27.067 frames, but if the fraction isn't precise it can lead to beat shift & cause things to get out of sync very easily. While the result of (133/60) can be stored as its own variable, handling all of this in code can be a nightmare. Delta time can help, but as you can see, it's a tricky setup that could be made way more user-friendly by adding actions for users to take advantage of.

gnysek commented 6 months ago

Isn't it something that FMOD extension could offer? https://github.com/YoYoGames/GameMaker-Bugs/issues/3133

kuraine commented 6 months ago

Isn't it something that FMOD extension could offer? #3133

Yep! If FMOD was supported then it would solve a lot of issues. If the extension is on its way then I'd be very happy.