This commit is contained in:
entar 2025-05-25 14:21:29 +07:00
parent 4a0615971e
commit a48dead3cc
10 changed files with 4585 additions and 0 deletions

4180
charts/high/high-erect.json Normal file

File diff suppressed because it is too large Load Diff

53
main.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

BIN
songs/High Erect/Voices.ogg Normal file

Binary file not shown.