Implementing rhythm-based mechanics

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
Ulydev
Party member
Posts: 445
Joined: Mon Nov 10, 2014 10:46 pm
Location: Paris
Contact:

Implementing rhythm-based mechanics

Post by Ulydev »

Hello everyone,

I've been doing some research about rhythm games for my next project.

Unfortunately, I can't seem to understand how one would implement rhythm-based games with LÖVE, only with the Sound and Audio APIs.

Currently I'm hardcoding the length of each beat and it kind of seems to work; but what if I wanted to game to adapt to different tracks (given the BPM is pre-defined, no need to detect it)?

Ideally I would love to play the audio, then make a function call that would return the current position of the listener relative to the bpm of the song (e.g. "3.1" would mean it's slightly after the third beat).

Could someone please clear this up?
Thanks! :awesome:
Last edited by Ulydev on Mon May 02, 2016 9:20 pm, edited 1 time in total.
User avatar
pgimeno
Party member
Posts: 3550
Joined: Sun Oct 18, 2015 2:58 pm

Re: Implementing rhythm-based mechanics

Post by pgimeno »

Not Easy™.

To get the data with LÖVE, use [wiki]love.sound.newSoundData[/wiki] to load the sound, instead of [wiki]love.audio.newSource[/wiki]. You can then apply love.audio.newSource to the SoundData object you obtain, and also [wiki]Data:getString[/wiki] (or [wiki]Data:getPointer[/wiki] if you're willing to use FFI).

As for what to do with the data for the purpose of determining the tempo (BPM) of a song, a quick overview is here:

http://sound.stackexchange.com/question ... ually-work

The complete thing is in the GameDev article linked from there:

http://archive.gamedev.net/archive/refe ... index.html

Sounds complicated? That's because it is! There's even a contest on beat detection algorithms:

http://nema.lis.illinois.edu/nema_out/m ... s/abt/mck/


(Irrelevant after seeing your edit)
Last edited by pgimeno on Tue May 03, 2016 12:04 am, edited 1 time in total.
User avatar
zorg
Party member
Posts: 3444
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Implementing rhythm-based mechanics

Post by zorg »

Also, just to be pedantic a bit, since löve uses openAL's positional audio stuff, saying that you want "a function call that would return the current position of the listener relative to the bpm of the song" is wrong, since you want the position of the playback cursor, or in other words, the temporal / time position of the song itself, not the (spatial) position of any listener.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
User avatar
Kingdaro
Party member
Posts: 395
Joined: Sun Jul 18, 2010 3:08 am

Re: Implementing rhythm-based mechanics

Post by Kingdaro »

The formula for the current beat of a song given a song's playing position (in seconds) and a BPM: bpm / 60 * songTime

So if your BPM was 120 and the song is at 1 second, then the current beat is 2. This gets a lot more complicated with BPM changes, but if you're not dealing with that, then this should be good enough.

EDIT: For future reference, most of what you'd need to do involving song beats can be derived from two formulas:
Get the number of seconds in a beat: 60 / bpm
Get the number of beats in a second: bpm / 60

This makes up a good chunk of the math you're probably going to need.
User avatar
undef
Party member
Posts: 438
Joined: Mon Jun 10, 2013 3:09 pm
Location: Berlin
Contact:

Re: Implementing rhythm-based mechanics

Post by undef »

You're probably looking for [url=Source:tell]https://love2d.org/wiki/Source:tell[/url].
Divide this by your beat length in seconds (which is your bpm/60).

Don't forget that there might be an offset at the beginning of the audio file, because of silence etc.
So make sure to take that into account so your function is beat aligned.
twitter | steam | indieDB

Check out quadrant on Steam!
User avatar
Sulunia
Party member
Posts: 203
Joined: Tue Mar 22, 2016 1:10 pm
Location: SRS, Brazil

Re: Implementing rhythm-based mechanics

Post by Sulunia »

Also make sure you're interpolating the audio time value. (interpolating values you get from the Source:tell function).
These two functions may prove useful:

Code: Select all

--Taken from beatfever source code
function playInterpolated() --Runs the music, but enables interpolated timer reporting. Used ingame as an interpolated timer.
   previousFrameTime = love.timer.getTime()*1000
   lastReportedPlaytime = 0
   songTime = 0
   songPlay:play()
end

function getInterpolatedTimer()
   songTime = songTime + (love.timer.getTime()*1000) - previousFrameTime
   previousFrameTime = love.timer.getTime()*1000
   if songPlay:tell("seconds")*1000 ~= lastReportedPlaytime then --Updates music time, but with easing
      songTime = (songTime + (songPlay:tell("seconds")*1000))/2
      lastReportedPlaytime = songPlay:tell("seconds")*1000
      eased = true
   end
   return songTime
--for more info about this, take a look
	--https://www.reddit.com/r/gamedev/comments/13y26t/how_do_rhythm_games_stay_in_sync_with_the_music/
end
Now you said you need to work with the beat to beat calculation.. hmm :monocle:

First, i'd get the current milliseconds between every beat of the song (you can calculate this based on the BPM).
Then, starting from 0 i'd generate a list with every beat time in milliseconds..

Code: Select all

Say we have 300 milliseconds per beat for a given song. The list on it's first position would be 300, on the second position 600, on the third position 900... and so on until the BPM changes or the music reaches it's end.
Finally, to figure out how far are we from the next beat, we just get the ((time from the next beat)-(time from last beat))/(milliseconds per beat). And concatenate this with the current index of the beat on the table, hence, resulting in something like 3.4 or something of the sort. :ultrahappy:

@edit
I'm stupid, so the function was very very wrong. But it's fixed now! :P
Last edited by Sulunia on Wed Jun 08, 2016 1:11 pm, edited 4 times in total.
Don't check my github! It contains thousands of lines of spaghetti code in many different languages cool software! :neko:
https://github.com/Sulunia
User avatar
Ulydev
Party member
Posts: 445
Joined: Mon Nov 10, 2014 10:46 pm
Location: Paris
Contact:

Re: Implementing rhythm-based mechanics

Post by Ulydev »

Looks like I'm going to end up using Source:tell() a lot.

But at least, I know where I'm going now: thanks a lot for your help, everyone! :ultrahappy:
User avatar
undef
Party member
Posts: 438
Joined: Mon Jun 10, 2013 3:09 pm
Location: Berlin
Contact:

Re: Implementing rhythm-based mechanics

Post by undef »

I don't get it.
Sulunia wrote:

Code: Select all

function getInterpolatedTimer(dt)
	songTime = songTime + dt - previousFrameTime -- this should be ~0 for the first frame


		songTime = (songTime + (songPlay:tell("seconds")*1000))/2 -- which means this should be half of the song time after the first frame?
end
The way I see it your song time approaches the actual song time asymptotically.
But why are you doing this?
twitter | steam | indieDB

Check out quadrant on Steam!
User avatar
Sulunia
Party member
Posts: 203
Joined: Tue Mar 22, 2016 1:10 pm
Location: SRS, Brazil

Re: Implementing rhythm-based mechanics

Post by Sulunia »

undef wrote: The way I see it your song time approaches the actual song time asymptotically.
But why are you doing this?
It makes sure that whilst no new Source:tell() value is reported, we ease the last value forward a bit using the DT as a base value for such easing.
This is useful pretty much if you're using the song time to calculate any note or game element position, since it'll make it "slide" more smoothly, as the value is always coming in a steady stream.
Backstory: on the earlier implementation of my rhythm game on python in the raspberry pi, if we didn't ease the value this way the notes would jitter quite a bit since the songTimer didn't really refresh that often. In love2d the difference is actually quite small, but it's there.

There is a link on the source code that explains this a bit better.
Don't check my github! It contains thousands of lines of spaghetti code in many different languages cool software! :neko:
https://github.com/Sulunia
User avatar
undef
Party member
Posts: 438
Joined: Mon Jun 10, 2013 3:09 pm
Location: Berlin
Contact:

Re: Implementing rhythm-based mechanics

Post by undef »

Ah, I shouldn't have looked at that reddit post earlier.
Great resource! Thanks for pointing it out! :)
twitter | steam | indieDB

Check out quadrant on Steam!
Post Reply

Who is online

Users browsing this forum: Google [Bot] and 84 guests