Initial
This commit is contained in:
parent
4a0615971e
commit
a48dead3cc
4180
charts/high/high-erect.json
Normal file
4180
charts/high/high-erect.json
Normal file
File diff suppressed because it is too large
Load Diff
53
main.lua
Normal file
53
main.lua
Normal file
@ -0,0 +1,53 @@
|
||||
local Height = 0
|
||||
local Width = 0
|
||||
|
||||
local myMath = require("modules.math")
|
||||
local myTypes = require("modules.types")
|
||||
local conductor = require("modules.conductor")
|
||||
local json = require("modules.json")
|
||||
local files = require("modules.files")
|
||||
local socket = require("socket")
|
||||
|
||||
local newVector2 = myTypes.Vector2(10, 10)
|
||||
|
||||
local startTime = socket.gettime()
|
||||
|
||||
local songName = "high"
|
||||
local songDifficulty = "erect"
|
||||
|
||||
local chartString = files.read_file(string.format("charts/%s/%s-%s.json", songName, songName, songDifficulty))
|
||||
|
||||
print(chartString)
|
||||
local chart = json.parse(chartString).song
|
||||
|
||||
local inst = love.audio.newSource(string.format("songs/%s/Inst.ogg", chart.song), "stream")
|
||||
local voices = love.audio.newSource(string.format("songs/%s/Voices.ogg", chart.song), "stream")
|
||||
|
||||
conductor:setBpm(chart.bpm)
|
||||
conductor:mapBpmChanges(chart)
|
||||
|
||||
inst:play()
|
||||
voices:play()
|
||||
|
||||
local step = 0
|
||||
local beat = 0
|
||||
|
||||
|
||||
function love.draw()
|
||||
local currentTime = socket.gettime()
|
||||
local elapsed = math.floor((currentTime - startTime) * 1000)
|
||||
|
||||
local oldStep = step
|
||||
local oldBeat = beat
|
||||
|
||||
step = conductor:getStepRounded(elapsed)
|
||||
beat = conductor:getBeatRounded(elapsed)
|
||||
|
||||
if step ~= oldStep then
|
||||
print("Step", step)
|
||||
end
|
||||
|
||||
if beat ~= oldBeat then
|
||||
print("Beat", beat)
|
||||
end
|
||||
end
|
98
modules/conductor.lua
Normal file
98
modules/conductor.lua
Normal file
@ -0,0 +1,98 @@
|
||||
local myMath = require("modules.math")
|
||||
local logging = require("modules.logging")
|
||||
|
||||
local conductor = {}
|
||||
conductor.bpm = 120
|
||||
conductor.crochet = 1000
|
||||
conductor.stepCrochet = conductor.crochet/4
|
||||
conductor.songPosition = 0
|
||||
|
||||
conductor.bpmChangeMap = {}
|
||||
|
||||
function conductor:getBPMFromSeconds(time)
|
||||
local lastChange = {
|
||||
stepTime = 0,
|
||||
songTime = 0,
|
||||
bpm = self.bpm,
|
||||
stepCrochet = self.stepCrochet
|
||||
}
|
||||
|
||||
for _, change in next, self.bpmChangeMap do
|
||||
if change.songTime > lastChange.songTime then
|
||||
lastChange = change
|
||||
end
|
||||
end
|
||||
|
||||
return lastChange
|
||||
end
|
||||
|
||||
function conductor:calculateCrochet(bpm)
|
||||
return (60/bpm) * 1000
|
||||
end
|
||||
|
||||
function conductor:setBpm(newBpm)
|
||||
self.bpm = newBpm
|
||||
self.crochet = self:calculateCrochet(newBpm)
|
||||
self.stepCrochet = self.crochet / 4
|
||||
end
|
||||
|
||||
function conductor:getStep(time)
|
||||
local lastChange = self:getBPMFromSeconds(time)
|
||||
|
||||
return lastChange.stepTime + (time - lastChange.songTime) / lastChange.stepCrochet
|
||||
end
|
||||
|
||||
function conductor:getStepRounded(time)
|
||||
local lastChange = self:getBPMFromSeconds(time)
|
||||
|
||||
return math.floor(lastChange.stepTime + math.floor(time - lastChange.songTime) / lastChange.stepCrochet)
|
||||
end
|
||||
|
||||
function conductor:getBeat(time)
|
||||
return self:getStep(time) / 4
|
||||
end
|
||||
|
||||
function conductor:getBeatRounded(time)
|
||||
return math.floor(self:getStep(time) / 4)
|
||||
end
|
||||
|
||||
function conductor:getSectionBeats(song, section)
|
||||
local beats = nil
|
||||
if song["notes"][section] then
|
||||
beats = song["notes"][section]["sectionBeats"]
|
||||
end
|
||||
return beats or 4
|
||||
end
|
||||
|
||||
function conductor:mapBpmChanges(song)
|
||||
print(logging.dump(song))
|
||||
|
||||
local curBPM = song["bpm"]
|
||||
local totalSteps = 0
|
||||
local totalPos = 0
|
||||
|
||||
|
||||
|
||||
for index, section in next, song["notes"] do
|
||||
if section["changeBPM"] then
|
||||
curBPM = section["bpm"]
|
||||
|
||||
local change = {
|
||||
stepTime = totalSteps,
|
||||
songTime = totalPos,
|
||||
bpm = curBPM,
|
||||
stepCrochet = self:calculateCrochet(curBPM)/4
|
||||
}
|
||||
|
||||
table.insert(self.bpmChangeMap, change)
|
||||
end
|
||||
|
||||
local deltaSteps = myMath.round(self:getSectionBeats(song, index) * 4)
|
||||
totalSteps = totalSteps + deltaSteps
|
||||
totalPos = (totalPos + (60/curBPM) * 1000 / 4) * deltaSteps
|
||||
end
|
||||
|
||||
print("Mapped the song BPM changes.")
|
||||
end
|
||||
|
||||
return conductor
|
13
modules/files.lua
Normal file
13
modules/files.lua
Normal file
@ -0,0 +1,13 @@
|
||||
local files = {}
|
||||
|
||||
local open = io.open
|
||||
|
||||
function files.read_file(path)
|
||||
local file = open(path, "rb") -- r read mode and b binary mode
|
||||
if not file then return nil end
|
||||
local content = file:read "*a" -- *a or *all reads the whole file
|
||||
file:close()
|
||||
return content
|
||||
end
|
||||
|
||||
return files
|
194
modules/json.lua
Normal file
194
modules/json.lua
Normal file
@ -0,0 +1,194 @@
|
||||
--[[ json.lua
|
||||
|
||||
A compact pure-Lua JSON library.
|
||||
The main functions are: json.stringify, json.parse.
|
||||
|
||||
## json.stringify:
|
||||
|
||||
This expects the following to be true of any tables being encoded:
|
||||
* They only have string or number keys. Number keys must be represented as
|
||||
strings in json; this is part of the json spec.
|
||||
* They are not recursive. Such a structure cannot be specified in json.
|
||||
|
||||
A Lua table is considered to be an array if and only if its set of keys is a
|
||||
consecutive sequence of positive integers starting at 1. Arrays are encoded like
|
||||
so: `[2, 3, false, "hi"]`. Any other type of Lua table is encoded as a json
|
||||
object, encoded like so: `{"key1": 2, "key2": false}`.
|
||||
|
||||
Because the Lua nil value cannot be a key, and as a table value is considerd
|
||||
equivalent to a missing key, there is no way to express the json "null" value in
|
||||
a Lua table. The only way this will output "null" is if your entire input obj is
|
||||
nil itself.
|
||||
|
||||
An empty Lua table, {}, could be considered either a json object or array -
|
||||
it's an ambiguous edge case. We choose to treat this as an object as it is the
|
||||
more general type.
|
||||
|
||||
To be clear, none of the above considerations is a limitation of this code.
|
||||
Rather, it is what we get when we completely observe the json specification for
|
||||
as arbitrary a Lua object as json is capable of expressing.
|
||||
|
||||
## json.parse:
|
||||
|
||||
This function parses json, with the exception that it does not pay attention to
|
||||
\u-escaped unicode code points in strings.
|
||||
|
||||
It is difficult for Lua to return null as a value. In order to prevent the loss
|
||||
of keys with a null value in a json string, this function uses the one-off
|
||||
table value json.null (which is just an empty table) to indicate null values.
|
||||
This way you can check if a value is null with the conditional
|
||||
`val == json.null`.
|
||||
|
||||
If you have control over the data and are using Lua, I would recommend just
|
||||
avoiding null values in your data to begin with.
|
||||
|
||||
--]]
|
||||
|
||||
|
||||
local json = {}
|
||||
|
||||
|
||||
-- Internal functions.
|
||||
|
||||
local function kind_of(obj)
|
||||
if type(obj) ~= 'table' then return type(obj) end
|
||||
local i = 1
|
||||
for _ in pairs(obj) do
|
||||
if obj[i] ~= nil then i = i + 1 else return 'table' end
|
||||
end
|
||||
if i == 1 then return 'table' else return 'array' end
|
||||
end
|
||||
|
||||
local function escape_str(s)
|
||||
local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
|
||||
local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'}
|
||||
for i, c in ipairs(in_char) do
|
||||
s = s:gsub(c, '\\' .. out_char[i])
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
-- Returns pos, did_find; there are two cases:
|
||||
-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
|
||||
-- 2. Delimiter not found: pos = pos after leading space; did_find = false.
|
||||
-- This throws an error if err_if_missing is true and the delim is not found.
|
||||
local function skip_delim(str, pos, delim, err_if_missing)
|
||||
pos = pos + #str:match('^%s*', pos)
|
||||
if str:sub(pos, pos) ~= delim then
|
||||
if err_if_missing then
|
||||
error('Expected ' .. delim .. ' near position ' .. pos)
|
||||
end
|
||||
return pos, false
|
||||
end
|
||||
return pos + 1, true
|
||||
end
|
||||
|
||||
-- Expects the given pos to be the first character after the opening quote.
|
||||
-- Returns val, pos; the returned pos is after the closing quote character.
|
||||
local function parse_str_val(str, pos, val)
|
||||
val = val or ''
|
||||
local early_end_error = 'End of input found while parsing string.'
|
||||
if pos > #str then error(early_end_error) end
|
||||
local c = str:sub(pos, pos)
|
||||
if c == '"' then return val, pos + 1 end
|
||||
if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
|
||||
-- We must have a \ character.
|
||||
local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
|
||||
local nextc = str:sub(pos + 1, pos + 1)
|
||||
if not nextc then error(early_end_error) end
|
||||
return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
|
||||
end
|
||||
|
||||
-- Returns val, pos; the returned pos is after the number's final character.
|
||||
local function parse_num_val(str, pos)
|
||||
local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
|
||||
local val = tonumber(num_str)
|
||||
if not val then error('Error parsing number at position ' .. pos .. '.') end
|
||||
return val, pos + #num_str
|
||||
end
|
||||
|
||||
|
||||
-- Public values and functions.
|
||||
|
||||
function json.stringify(obj, as_key)
|
||||
local s = {} -- We'll build the string as an array of strings to be concatenated.
|
||||
local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise.
|
||||
if kind == 'array' then
|
||||
if as_key then error('Can\'t encode array as key.') end
|
||||
s[#s + 1] = '['
|
||||
for i, val in ipairs(obj) do
|
||||
if i > 1 then s[#s + 1] = ', ' end
|
||||
s[#s + 1] = json.stringify(val)
|
||||
end
|
||||
s[#s + 1] = ']'
|
||||
elseif kind == 'table' then
|
||||
if as_key then error('Can\'t encode table as key.') end
|
||||
s[#s + 1] = '{'
|
||||
for k, v in pairs(obj) do
|
||||
if #s > 1 then s[#s + 1] = ', ' end
|
||||
s[#s + 1] = json.stringify(k, true)
|
||||
s[#s + 1] = ':'
|
||||
s[#s + 1] = json.stringify(v)
|
||||
end
|
||||
s[#s + 1] = '}'
|
||||
elseif kind == 'string' then
|
||||
return '"' .. escape_str(obj) .. '"'
|
||||
elseif kind == 'number' then
|
||||
if as_key then return '"' .. tostring(obj) .. '"' end
|
||||
return tostring(obj)
|
||||
elseif kind == 'boolean' then
|
||||
return tostring(obj)
|
||||
elseif kind == 'nil' then
|
||||
return 'null'
|
||||
else
|
||||
error('Unjsonifiable type: ' .. kind .. '.')
|
||||
end
|
||||
return table.concat(s)
|
||||
end
|
||||
|
||||
json.null = {} -- This is a one-off table to represent the null value.
|
||||
|
||||
function json.parse(str, pos, end_delim)
|
||||
pos = pos or 1
|
||||
if pos > #str then error('Reached unexpected end of input.') end
|
||||
local pos = pos + #str:match('^%s*', pos) -- Skip whitespace.
|
||||
local first = str:sub(pos, pos)
|
||||
if first == '{' then -- Parse an object.
|
||||
local obj, key, delim_found = {}, true, true
|
||||
pos = pos + 1
|
||||
while true do
|
||||
key, pos = json.parse(str, pos, '}')
|
||||
if key == nil then return obj, pos end
|
||||
if not delim_found then error('Comma missing between object items.') end
|
||||
pos = skip_delim(str, pos, ':', true) -- true -> error if missing.
|
||||
obj[key], pos = json.parse(str, pos)
|
||||
pos, delim_found = skip_delim(str, pos, ',')
|
||||
end
|
||||
elseif first == '[' then -- Parse an array.
|
||||
local arr, val, delim_found = {}, true, true
|
||||
pos = pos + 1
|
||||
while true do
|
||||
val, pos = json.parse(str, pos, ']')
|
||||
if val == nil then return arr, pos end
|
||||
if not delim_found then error('Comma missing between array items.') end
|
||||
arr[#arr + 1] = val
|
||||
pos, delim_found = skip_delim(str, pos, ',')
|
||||
end
|
||||
elseif first == '"' then -- Parse a string.
|
||||
return parse_str_val(str, pos + 1)
|
||||
elseif first == '-' or first:match('%d') then -- Parse a number.
|
||||
return parse_num_val(str, pos)
|
||||
elseif first == end_delim then -- End of an object or array.
|
||||
return nil, pos + 1
|
||||
else -- Parse true, false, or null.
|
||||
local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}
|
||||
for lit_str, lit_val in pairs(literals) do
|
||||
local lit_end = pos + #lit_str - 1
|
||||
if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
|
||||
end
|
||||
local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
|
||||
error('Invalid json syntax starting at ' .. pos_info_str)
|
||||
end
|
||||
end
|
||||
|
||||
return json
|
16
modules/logging.lua
Normal file
16
modules/logging.lua
Normal file
@ -0,0 +1,16 @@
|
||||
local logging = {}
|
||||
|
||||
function logging.dump(o)
|
||||
if type(o) == 'table' then
|
||||
local s = '{ '
|
||||
for k,v in pairs(o) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. logging.dump(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(o)
|
||||
end
|
||||
end
|
||||
|
||||
return logging
|
11
modules/math.lua
Normal file
11
modules/math.lua
Normal file
@ -0,0 +1,11 @@
|
||||
local module = {}
|
||||
|
||||
function module.lerp(a, b,c)
|
||||
return a + (b - a) * c
|
||||
end
|
||||
|
||||
function module.round(a)
|
||||
return math.floor(a + .5)
|
||||
end
|
||||
|
||||
return module
|
20
modules/types.lua
Normal file
20
modules/types.lua
Normal file
@ -0,0 +1,20 @@
|
||||
local module = {}
|
||||
|
||||
local myMath = require("modules.math")
|
||||
|
||||
local Vector2 = {}
|
||||
Vector2.__index = Vector2
|
||||
|
||||
function Vector2:Lerp(newVector2, position)
|
||||
return module.Vector2(myMath.lerp(self.x, newVector2.x, position), myMath.lerp(self.x, newVector2.x, position))
|
||||
end
|
||||
|
||||
function Vector2:Get()
|
||||
return self.x, self.y
|
||||
end
|
||||
|
||||
function module.Vector2(x, y)
|
||||
return setmetatable({x = x, y = y}, Vector2)
|
||||
end
|
||||
|
||||
return module
|
BIN
songs/High Erect/Inst.ogg
Normal file
BIN
songs/High Erect/Inst.ogg
Normal file
Binary file not shown.
BIN
songs/High Erect/Voices.ogg
Normal file
BIN
songs/High Erect/Voices.ogg
Normal file
Binary file not shown.
Reference in New Issue
Block a user