Conductor = {
timingWindow = 0.1,
created = function(self, folderName)
local songfolder = 'songs/' .. folderName
local songfile = songfolder .. '/song.lua'
if not love.filesystem.exists(songfile) then
error('"' .. songfolder .. '" does not have a song.lua.')
end
-- TODO: make song loading a little safer
local songdata = love.filesystem.load(songfile)()
self.songdata = songdata
self.time = 0
self.beat = -songdata.offset * self:beatsPerSecond()
self.playing = false
self.beatsPerGroup = songdata.beatsPerGroup
-- sort the notes table by beat
table.sort(songdata.notes, function(a, b)
return a[1] < b[1]
end)
-- convert the note tables into a more usable format
-- ex: { 1, 'click' }
-- note.beat = first element
-- note.type = second element
-- note.info = array of whatever else is in the table
-- note.time = time of the note in seconds
-- note.next = the note that comes after this one (nil if it's the last)
for i,note in pairs(songdata.notes) do
note.beat = table.remove(note, 1)
note.type = table.remove(note, 1)
note.info = { unpack(note) }
note.time = songdata.offset + note.beat * self:secondsPerBeat()
note.next = songdata.notes[i + 1]
end
self.beatTimer = create(Timer, self:secondsPerBeat())
self.beatTimer.time = -(self.songdata.offset % self:secondsPerBeat()) + self:secondsPerBeat()
local sourcepath = songfolder .. '/' .. songdata.source
self.source = love.audio.newSource(sourcepath, 'stream')
end,
onBeat = function(self, fn)
self.beatTimer:addCallback(fn)
end,
play = function(self)
self.playing = true
self.source:play()
self.source:seek(self.time)
end,
pause = function(self)
self.playing = false
self.source:pause()
end,
stop = function(self)
self.playing = false
self.source:stop()
self.time = 0
end,
update = function(self, dt)
if self.playing then
self.time = self.time + dt
self.beat = self.beat + self:beatsPerSecond() * dt
end
local bpmChanges = self.songdata.bpmChanges
if bpmChanges then
for i=#bpmChanges, 1, -1 do
local change = bpmChanges[i]
if self.beat > change[1] then
self.songdata.bpm = change[2]
break
end
end
end
self.beatTimer:update(dt)
end,
secondsPerBeat = function(self)
return 60 / self.songdata.bpm
end,
beatsPerSecond = function(self)
return self.songdata.bpm / 60
end,
}
The conductor class in my rhythm game. Not sure how long is too long, though.