More tools added to the menu for assisting with setup.
This commit is contained in:
parent
e7b24ea7b3
commit
421ef75bdc
14 changed files with 1512 additions and 191 deletions
37
CHANGELOG
37
CHANGELOG
|
|
@ -587,7 +587,7 @@ API Changes
|
||||||
delay (DAC, receiver, Bluetooth, display). Press the service key on
|
delay (DAC, receiver, Bluetooth, display). Press the service key on
|
||||||
the game list; a click plays once a second and the screen flashes.
|
the game list; a click plays once a second and the screen flashes.
|
||||||
Adjust with the stick until the flash and the click coincide, then
|
Adjust with the stick until the flash and the click coincide, then
|
||||||
press button 1 to save. The value is kept in audio.cfg in the data
|
press button 1 to save. The value is kept in machine.cfg in the data
|
||||||
root and applied to every game on the machine, from the menu or the
|
root and applied to every game on the machine, from the menu or the
|
||||||
command line, on top of any per-game AUDIO_DELAY. Scripts can read and
|
command line, on top of any per-game AUDIO_DELAY. Scripts can read and
|
||||||
set it with singeGetAudioCalibration() / singeSetAudioCalibration(),
|
set it with singeGetAudioCalibration() / singeSetAudioCalibration(),
|
||||||
|
|
@ -862,6 +862,41 @@ API Changes
|
||||||
things that must not pass through each other -- which is what the
|
things that must not pass through each other -- which is what the
|
||||||
collide calls deliberately do not do.
|
collide calls deliberately do not do.
|
||||||
|
|
||||||
|
- The service tools. Pressing the SERVICE key on the bundled menu now
|
||||||
|
opens a list of ten screens rather than the audio delay screen alone:
|
||||||
|
Audio Delay, Input Test, System Information, Light Gun, Sound Test,
|
||||||
|
Display, Disc Test, Saved Data, MIDI Ports and Frame Statistics.
|
||||||
|
Button 2 backs out of a tool, the service key leaves. They live in
|
||||||
|
Singe/Tools.singe, which the engine extracts beside Menu.singe, and
|
||||||
|
each is a page of Singe/Menu.rml, so a cabinet builder can add one.
|
||||||
|
MenuClassic keeps the audio delay screen and nothing else.
|
||||||
|
|
||||||
|
Four calls were added for them, and are worth having on their own:
|
||||||
|
|
||||||
|
singeGetSystemInfo() returns everything the trace header knows in one
|
||||||
|
table -- version, operating system, processor, renderer, 3D device,
|
||||||
|
video decoders, audio formats, SoundFont, MIDI, window and canvas
|
||||||
|
sizes, this game's data directory and the data root every game writes
|
||||||
|
under -- so a bug report can be a photograph.
|
||||||
|
|
||||||
|
vldpGetShift() and vldpSetShift(x, y) move the picture within the
|
||||||
|
window while the game runs, the running equivalent of --shiftx and
|
||||||
|
--shifty, beside vldpGetScale and vldpSetScale.
|
||||||
|
|
||||||
|
singeSaveGeometry() keeps the scale, shift and rotation for this
|
||||||
|
machine, in the same machine.cfg the audio delay uses, and the engine
|
||||||
|
applies them to every game it starts afterwards unless the command
|
||||||
|
line names its own. A monitor is squared up once, not once per game.
|
||||||
|
The file is called machine.cfg rather than audio.cfg, because the
|
||||||
|
audio delay is no longer all it holds.
|
||||||
|
|
||||||
|
discGetFrameCount() answers how many frames the disc has. A framefile
|
||||||
|
answers for the whole disc rather than the segment playing.
|
||||||
|
|
||||||
|
SINGE_CONTROLLER_AXES and SINGE_CONTROLLER_BUTTONS join the input
|
||||||
|
layout globals, so a service screen can walk a pad without knowing the
|
||||||
|
numbers.
|
||||||
|
|
||||||
- Subtitles carried inside the video file: discGetSubtitleTracks(),
|
- Subtitles carried inside the video file: discGetSubtitleTracks(),
|
||||||
discGetSubtitleLanguage(track) and srtLoadTrack(track), which reads
|
discGetSubtitleLanguage(track) and srtLoadTrack(track), which reads
|
||||||
one track out of the disc's own container and loads it exactly as
|
one track out of the disc's own container and loads it exactly as
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,7 @@ singeEmbed(${CMAKE_SOURCE_DIR}/assets/Framework.singe ${GENERATED_DIR}/Framework
|
||||||
singeEmbed(${CMAKE_SOURCE_DIR}/assets/controls.cfg ${GENERATED_DIR}/controls_cfg.h "")
|
singeEmbed(${CMAKE_SOURCE_DIR}/assets/controls.cfg ${GENERATED_DIR}/controls_cfg.h "")
|
||||||
singeEmbed(${CMAKE_SOURCE_DIR}/assets/settings.cfg ${GENERATED_DIR}/settings_cfg.h "")
|
singeEmbed(${CMAKE_SOURCE_DIR}/assets/settings.cfg ${GENERATED_DIR}/settings_cfg.h "")
|
||||||
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Menu.singe ${GENERATED_DIR}/Menu_singe.h "")
|
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Menu.singe ${GENERATED_DIR}/Menu_singe.h "")
|
||||||
|
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Tools.singe ${GENERATED_DIR}/Tools_singe.h "")
|
||||||
singeEmbed(${CMAKE_SOURCE_DIR}/assets/MenuClassic.singe ${GENERATED_DIR}/MenuClassic_singe.h "")
|
singeEmbed(${CMAKE_SOURCE_DIR}/assets/MenuClassic.singe ${GENERATED_DIR}/MenuClassic_singe.h "")
|
||||||
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Menu.rml ${GENERATED_DIR}/Menu_rml.h "")
|
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Menu.rml ${GENERATED_DIR}/Menu_rml.h "")
|
||||||
singeEmbed(${CMAKE_SOURCE_DIR}/assets/menu.rcss ${GENERATED_DIR}/menu_rcss.h "")
|
singeEmbed(${CMAKE_SOURCE_DIR}/assets/menu.rcss ${GENERATED_DIR}/menu_rcss.h "")
|
||||||
|
|
|
||||||
|
|
@ -28,17 +28,72 @@
|
||||||
<div id="description"></div>
|
<div id="description"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div id="hints">Up / Down: select Left / Right: page Start or button: play Service: audio delay</div>
|
<div id="hints">Up / Down: select Left / Right: page Start or button: play Service: tools</div>
|
||||||
<button id="start">Start</button>
|
<button id="start">Start</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="calibration" class="hidden">
|
<div id="tools" class="tool hidden">
|
||||||
<h1>Audio Delay Calibration</h1>
|
<h1>Service Tools</h1>
|
||||||
|
<div id="toolList"></div>
|
||||||
|
<p class="value" id="toolHelp"></p>
|
||||||
|
<p class="keys">Up / Down: choose Button 1: open Service: back to the games</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolAudio" class="tool hidden">
|
||||||
|
<h1>Audio Delay</h1>
|
||||||
<p>Adjust until the flash and the click happen together.</p>
|
<p>Adjust until the flash and the click happen together.</p>
|
||||||
<p class="value">Delay: <span id="calValue"></span> ms</p>
|
<p class="value">Delay: <span id="calValue"></span> ms</p>
|
||||||
<p class="value">Measured device queue: <span id="calLatency"></span> ms</p>
|
<p class="value">Measured device queue: <span id="calLatency"></span> ms</p>
|
||||||
<p class="keys">Left / Right: 10 ms Up / Down: 1 ms Start: reset to 0</p>
|
<p class="keys">Left / Right: 10 ms Up / Down: 1 ms Start: reset to 0</p>
|
||||||
<p class="keys">Button 1: save Button 2: cancel</p>
|
<p class="keys">Button 1: save Button 2: back</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolInput" class="tool hidden">
|
||||||
|
<h1>Input Test</h1>
|
||||||
|
<div id="inputRows"></div>
|
||||||
|
<p class="keys">Press anything. Button 2: back</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolSystem" class="tool hidden">
|
||||||
|
<h1>System Information</h1>
|
||||||
|
<div id="systemRows"></div>
|
||||||
|
<p class="keys">Button 2: back</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolGun" class="tool clear hidden">
|
||||||
|
<h1>Light Gun</h1>
|
||||||
|
<p class="value">Last shot: <span id="gunLast"></span> shots shown: <span id="gunCount"></span></p>
|
||||||
|
<p class="keys">Button 1: fire Start: clear Button 2: back</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolSound" class="tool hidden">
|
||||||
|
<h1>Sound Test</h1>
|
||||||
|
<p class="value">Playing: <span id="soundWhich"></span></p>
|
||||||
|
<p class="value">Disc audio tracks: <span id="soundTracks"></span></p>
|
||||||
|
<p class="keys">Button 1: left, right, both Left / Right: disc track Button 2: back</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolDisplay" class="tool clear hidden">
|
||||||
|
<h1>Display</h1>
|
||||||
|
<p class="value" id="dispValues"></p>
|
||||||
|
<p class="value" id="dispSaved"></p>
|
||||||
|
<p class="keys">Arrows: move Button 3 / 4: size Coin 1: rotate Start: reset</p>
|
||||||
|
<p class="keys">Button 1: keep for this machine Button 2: back</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolDisc" class="tool hidden">
|
||||||
|
<h1>Disc Test</h1>
|
||||||
|
<div id="discRows"></div>
|
||||||
|
<p class="keys">Button 1: run Button 2: back</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolSaved" class="tool hidden">
|
||||||
|
<h1>Saved Data</h1>
|
||||||
|
<div id="savedRows"></div>
|
||||||
|
<p class="keys">Up / Down: choose Button 3: delete this game's save Button 2: back</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolMidi" class="tool hidden">
|
||||||
|
<h1>MIDI Ports</h1>
|
||||||
|
<div id="midiRows"></div>
|
||||||
|
<p class="keys">Button 1: send a note Button 3: look again Button 2: back</p>
|
||||||
|
</div>
|
||||||
|
<div id="toolStats" class="tool hidden">
|
||||||
|
<h1>Frame Statistics</h1>
|
||||||
|
<p class="value">The overlay is <span id="statsState"></span>.</p>
|
||||||
|
<p>It stays on while you play, in the top left corner.</p>
|
||||||
|
<p class="keys">Button 1: turn it on or off Button 2: back</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</rml>
|
</rml>
|
||||||
|
|
|
||||||
|
|
@ -29,102 +29,11 @@
|
||||||
-- Singe/MenuClassic.singe is the previous, sprite drawn menu, kept for one release.
|
-- Singe/MenuClassic.singe is the previous, sprite drawn menu, kept for one release.
|
||||||
|
|
||||||
dofile("Singe/Framework.singe")
|
dofile("Singe/Framework.singe")
|
||||||
|
dofile("Singe/Tools.singe")
|
||||||
|
|
||||||
local lfs = require("lfs")
|
local lfs = require("lfs")
|
||||||
|
|
||||||
|
|
||||||
-- Audio delay calibration: a click every second and a flash scheduled the engine's measured
|
|
||||||
-- device queue plus the candidate value after it. The click and the disc audio share one mixer
|
|
||||||
-- device, so when the player sees and hears them as one event the candidate is the machine's delay.
|
|
||||||
function calibrationBegin()
|
|
||||||
CALIBRATING = true
|
|
||||||
CAL_ORIGINAL = singeGetAudioCalibration()
|
|
||||||
CAL_VALUE = CAL_ORIGINAL
|
|
||||||
CAL_LATENCY = singeGetAudioLatency()
|
|
||||||
CAL_BEAT = singeGetTicks() - CAL_PERIOD
|
|
||||||
CAL_CLICK_DONE = true
|
|
||||||
CAL_FLASH_DONE = true
|
|
||||||
if VIDEO_ATTRACT then
|
|
||||||
videoPause(VIDEO_ATTRACT)
|
|
||||||
end
|
|
||||||
guiSetValue(GUI, DOCUMENT, "calLatency", tostring(CAL_LATENCY))
|
|
||||||
calibrationShowValue()
|
|
||||||
menuShowPage(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function calibrationEnd(save)
|
|
||||||
if save then
|
|
||||||
singeSetAudioCalibration(CAL_VALUE)
|
|
||||||
end
|
|
||||||
CALIBRATING = false
|
|
||||||
menuElement("calibration"):SetClass("flash", false)
|
|
||||||
menuShowPage(false)
|
|
||||||
if VIDEO_ATTRACT then
|
|
||||||
videoPlay(VIDEO_ATTRACT)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function calibrationInput(what)
|
|
||||||
local delta = 0
|
|
||||||
|
|
||||||
if what == SWITCH_LEFT then
|
|
||||||
delta = -CAL_STEP_COARSE
|
|
||||||
elseif what == SWITCH_RIGHT then
|
|
||||||
delta = CAL_STEP_COARSE
|
|
||||||
elseif what == SWITCH_UP then
|
|
||||||
delta = CAL_STEP_FINE
|
|
||||||
elseif what == SWITCH_DOWN then
|
|
||||||
delta = -CAL_STEP_FINE
|
|
||||||
elseif what == SWITCH_START1 or what == SWITCH_START2 then
|
|
||||||
CAL_VALUE = 0
|
|
||||||
calibrationShowValue()
|
|
||||||
elseif what == SWITCH_BUTTON1 then
|
|
||||||
calibrationEnd(true)
|
|
||||||
elseif what == SWITCH_BUTTON2 or what == SWITCH_SERVICE then
|
|
||||||
calibrationEnd(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
if delta ~= 0 then
|
|
||||||
CAL_VALUE = math.max(-CAL_LIMIT, math.min(CAL_LIMIT, CAL_VALUE + delta))
|
|
||||||
calibrationShowValue()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function calibrationShowValue()
|
|
||||||
guiSetValue(GUI, DOCUMENT, "calValue", tostring(CAL_VALUE))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function calibrationUpdate()
|
|
||||||
local now = singeGetTicks()
|
|
||||||
local gap = CAL_LATENCY + CAL_VALUE -- flash this long after the click; negative means before
|
|
||||||
local flash = false
|
|
||||||
|
|
||||||
-- Start a new beat? Whichever of click and flash comes first goes on the beat.
|
|
||||||
if now - CAL_BEAT >= CAL_PERIOD then
|
|
||||||
CAL_BEAT = now
|
|
||||||
CAL_CLICK_AT = now + math.max(0, -gap)
|
|
||||||
CAL_FLASH_AT = now + math.max(0, gap)
|
|
||||||
CAL_CLICK_DONE = false
|
|
||||||
CAL_FLASH_DONE = false
|
|
||||||
end
|
|
||||||
if not CAL_CLICK_DONE and now >= CAL_CLICK_AT then
|
|
||||||
soundPlay(SND_CLICK)
|
|
||||||
CAL_CLICK_DONE = true
|
|
||||||
end
|
|
||||||
if not CAL_FLASH_DONE and now >= CAL_FLASH_AT then
|
|
||||||
flash = true
|
|
||||||
CAL_FLASH_DONE = true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- The page is white for the one frame of the flash and black otherwise.
|
|
||||||
menuElement("calibration"):SetClass("flash", flash)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function cleanTitle(a)
|
function cleanTitle(a)
|
||||||
local output = string.lower(a)
|
local output = string.lower(a)
|
||||||
output = string.gsub(output, '%p', ' ')
|
output = string.gsub(output, '%p', ' ')
|
||||||
|
|
@ -189,7 +98,7 @@ end
|
||||||
|
|
||||||
-- The game page takes input once the disc has reached the grid behind it.
|
-- The game page takes input once the disc has reached the grid behind it.
|
||||||
function menuActive()
|
function menuActive()
|
||||||
return (not CALIBRATING) and (discGetFrame() >= DISC_GRID_START)
|
return (not toolsActive()) and (discGetFrame() >= DISC_GRID_START)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -332,13 +241,6 @@ function menuScrollToSelection()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- The game page or the calibration page; never both.
|
|
||||||
function menuShowPage(calibrating)
|
|
||||||
menuElement("games"):SetClass("hidden", calibrating)
|
|
||||||
menuElement("calibration"):SetClass("hidden", not calibrating)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function menuStartClick(gui, document, id, event, value)
|
function menuStartClick(gui, document, id, event, value)
|
||||||
if menuActive() then
|
if menuActive() then
|
||||||
gameStart()
|
gameStart()
|
||||||
|
|
@ -348,8 +250,9 @@ end
|
||||||
|
|
||||||
function onInputPressed(what)
|
function onInputPressed(what)
|
||||||
|
|
||||||
if CALIBRATING then
|
-- The service tools take every switch while they are up, including the one that closes them.
|
||||||
calibrationInput(what)
|
if toolsActive() then
|
||||||
|
toolsInput(what)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -357,7 +260,7 @@ function onInputPressed(what)
|
||||||
if menuActive() then
|
if menuActive() then
|
||||||
|
|
||||||
if what == SWITCH_SERVICE then
|
if what == SWITCH_SERVICE then
|
||||||
calibrationBegin()
|
toolsBegin()
|
||||||
elseif what == SWITCH_UP then
|
elseif what == SWITCH_UP then
|
||||||
gameSelect(GAME_SELECTED - 1)
|
gameSelect(GAME_SELECTED - 1)
|
||||||
elseif what == SWITCH_DOWN then
|
elseif what == SWITCH_DOWN then
|
||||||
|
|
@ -398,8 +301,8 @@ function onOverlayUpdate()
|
||||||
colorBackground(0, 0, 0, 0)
|
colorBackground(0, 0, 0, 0)
|
||||||
overlayClear()
|
overlayClear()
|
||||||
|
|
||||||
if CALIBRATING then
|
if toolsActive() then
|
||||||
calibrationUpdate()
|
toolsUpdate()
|
||||||
guiDraw(GUI)
|
guiDraw(GUI)
|
||||||
return(OVERLAY_UPDATED)
|
return(OVERLAY_UPDATED)
|
||||||
end
|
end
|
||||||
|
|
@ -530,13 +433,8 @@ else
|
||||||
LIST_SCROLL_PENDING = false
|
LIST_SCROLL_PENDING = false
|
||||||
SHUTDOWN_FROM_PUSH = false
|
SHUTDOWN_FROM_PUSH = false
|
||||||
|
|
||||||
-- Audio delay calibration screen (SWITCH_SERVICE)
|
-- The service tools (SWITCH_SERVICE) share this click with the audio delay screen.
|
||||||
CALIBRATING = false
|
SND_CLICK = soundLoad("Singe/click.wav")
|
||||||
CAL_PERIOD = 1000
|
|
||||||
CAL_STEP_COARSE = 10
|
|
||||||
CAL_STEP_FINE = 1
|
|
||||||
CAL_LIMIT = 1000
|
|
||||||
SND_CLICK = soundLoad("Singe/click.wav")
|
|
||||||
|
|
||||||
-- Load configuration
|
-- Load configuration
|
||||||
SHOW_INTRO = true
|
SHOW_INTRO = true
|
||||||
|
|
|
||||||
810
assets/Tools.singe
Normal file
810
assets/Tools.singe
Normal file
|
|
@ -0,0 +1,810 @@
|
||||||
|
--[[
|
||||||
|
*
|
||||||
|
* Singe 3
|
||||||
|
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 3
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
--]]
|
||||||
|
|
||||||
|
|
||||||
|
-- The service tools: everything an operator or a developer needs to check a cabinet, reached by
|
||||||
|
-- pressing SERVICE in the menu. Each tool is a page of Singe/Menu.rml and an entry in TOOLS with
|
||||||
|
-- up to four functions: begin when it opens, input for a switch, update once a frame, and finish
|
||||||
|
-- when it closes. Anything a tool leaves out simply does not happen.
|
||||||
|
--
|
||||||
|
-- Only the RmlUi menu has these. Singe/MenuClassic.singe exists for a machine with no GPU device,
|
||||||
|
-- where there is no document to put a page in; it keeps its own audio delay screen and nothing
|
||||||
|
-- else.
|
||||||
|
|
||||||
|
|
||||||
|
local lfs = require("lfs")
|
||||||
|
|
||||||
|
|
||||||
|
TOOL_SELECTED = 1
|
||||||
|
TOOL_OPEN = nil -- The tool being used, or nil while the list itself is showing
|
||||||
|
TOOLS = {}
|
||||||
|
|
||||||
|
|
||||||
|
-- Writing into a page is the one thing every tool does, and the document and the GUI are the same
|
||||||
|
-- ones every time, so they are named here rather than in twenty-two places.
|
||||||
|
local function set(id, text)
|
||||||
|
guiSetValue(GUI, DOCUMENT, id, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== The list =====
|
||||||
|
|
||||||
|
function toolsBegin()
|
||||||
|
TOOL_OPEN = nil
|
||||||
|
toolsShowList()
|
||||||
|
toolsShowPage("tools")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function toolsEnd()
|
||||||
|
if TOOL_OPEN and TOOL_OPEN.finish then
|
||||||
|
TOOL_OPEN.finish()
|
||||||
|
end
|
||||||
|
TOOL_OPEN = nil
|
||||||
|
toolsShowPage(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- One page of the document at a time, and the game list when nothing is named.
|
||||||
|
function toolsShowPage(id)
|
||||||
|
menuElement("games"):SetClass("hidden", id ~= nil)
|
||||||
|
menuElement("tools"):SetClass("hidden", id ~= "tools")
|
||||||
|
for _, tool in ipairs(TOOLS) do
|
||||||
|
menuElement(tool.id):SetClass("hidden", id ~= tool.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function toolsShowList()
|
||||||
|
local rows = {}
|
||||||
|
for i, tool in ipairs(TOOLS) do
|
||||||
|
local class = (i == TOOL_SELECTED) and ' class="selected"' or ""
|
||||||
|
rows[#rows + 1] = string.format("<div%s>%s</div>", class, tool.name)
|
||||||
|
end
|
||||||
|
set("toolList", table.concat(rows))
|
||||||
|
set("toolHelp", TOOLS[TOOL_SELECTED].help or "")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function toolsOpen(index)
|
||||||
|
TOOL_OPEN = TOOLS[index]
|
||||||
|
if TOOL_OPEN.begin then
|
||||||
|
TOOL_OPEN.begin()
|
||||||
|
end
|
||||||
|
toolsShowPage(TOOL_OPEN.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function toolsClose()
|
||||||
|
if TOOL_OPEN and TOOL_OPEN.finish then
|
||||||
|
TOOL_OPEN.finish()
|
||||||
|
end
|
||||||
|
TOOL_OPEN = nil
|
||||||
|
toolsShowList()
|
||||||
|
toolsShowPage("tools")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Every switch while the tools are up. A tool that wants a switch for itself says so by returning
|
||||||
|
-- true from its input function; everything else falls through to the rules here, so Button 2 always
|
||||||
|
-- backs out and SERVICE always leaves.
|
||||||
|
function toolsInput(what)
|
||||||
|
if TOOL_OPEN then
|
||||||
|
if TOOL_OPEN.input and TOOL_OPEN.input(what) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if what == SWITCH_BUTTON2 or what == SWITCH_SERVICE then
|
||||||
|
toolsClose()
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if what == SWITCH_UP then
|
||||||
|
TOOL_SELECTED = (TOOL_SELECTED == 1) and #TOOLS or (TOOL_SELECTED - 1)
|
||||||
|
toolsShowList()
|
||||||
|
elseif what == SWITCH_DOWN then
|
||||||
|
TOOL_SELECTED = (TOOL_SELECTED == #TOOLS) and 1 or (TOOL_SELECTED + 1)
|
||||||
|
toolsShowList()
|
||||||
|
elseif what == SWITCH_BUTTON1 or what == SWITCH_START1 or what == SWITCH_START2 then
|
||||||
|
toolsOpen(TOOL_SELECTED)
|
||||||
|
elseif what == SWITCH_BUTTON2 or what == SWITCH_SERVICE then
|
||||||
|
toolsEnd()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function toolsUpdate()
|
||||||
|
if TOOL_OPEN and TOOL_OPEN.update then
|
||||||
|
TOOL_OPEN.update()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Whether the tools have the screen, which is what the menu asks before doing anything of its own.
|
||||||
|
function toolsActive()
|
||||||
|
return TOOL_OPEN ~= nil or not menuElement("tools"):IsClassSet("hidden")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 1. Audio delay =====
|
||||||
|
|
||||||
|
-- A click every second and a flash scheduled the engine's measured device queue plus the candidate
|
||||||
|
-- value after it. The click and the disc audio share one mixer device, so when the player sees and
|
||||||
|
-- hears them as one event the candidate is this machine's delay.
|
||||||
|
local CAL_PERIOD = 1000
|
||||||
|
local CAL_STEP_COARSE = 10
|
||||||
|
local CAL_STEP_FINE = 1
|
||||||
|
local CAL_LIMIT = 1000
|
||||||
|
local calValue, calBeat, calClickDone, calFlashDone
|
||||||
|
|
||||||
|
|
||||||
|
local function audioShow()
|
||||||
|
set("calValue", tostring(calValue))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolAudio",
|
||||||
|
name = "Audio Delay",
|
||||||
|
help = "Line the click up with the flash so sound and picture agree.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
calValue = singeGetAudioCalibration()
|
||||||
|
calBeat = singeGetTicks() - CAL_PERIOD
|
||||||
|
calClickDone = true
|
||||||
|
calFlashDone = true
|
||||||
|
set("calLatency", tostring(singeGetAudioLatency()))
|
||||||
|
audioShow()
|
||||||
|
end,
|
||||||
|
|
||||||
|
input = function(what)
|
||||||
|
local delta = 0
|
||||||
|
if what == SWITCH_LEFT then
|
||||||
|
delta = -CAL_STEP_COARSE
|
||||||
|
elseif what == SWITCH_RIGHT then
|
||||||
|
delta = CAL_STEP_COARSE
|
||||||
|
elseif what == SWITCH_UP then
|
||||||
|
delta = CAL_STEP_FINE
|
||||||
|
elseif what == SWITCH_DOWN then
|
||||||
|
delta = -CAL_STEP_FINE
|
||||||
|
elseif what == SWITCH_START1 or what == SWITCH_START2 then
|
||||||
|
calValue = 0
|
||||||
|
audioShow()
|
||||||
|
return true
|
||||||
|
elseif what == SWITCH_BUTTON1 then
|
||||||
|
singeSetAudioCalibration(calValue)
|
||||||
|
toolsClose()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if delta ~= 0 then
|
||||||
|
calValue = math.max(-CAL_LIMIT, math.min(CAL_LIMIT, calValue + delta))
|
||||||
|
audioShow()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
|
||||||
|
update = function()
|
||||||
|
local now = singeGetTicks()
|
||||||
|
if now - calBeat >= CAL_PERIOD then
|
||||||
|
calBeat = now
|
||||||
|
calClickDone = false
|
||||||
|
calFlashDone = false
|
||||||
|
end
|
||||||
|
-- The click goes first; the flash waits out the device queue and the candidate delay, so
|
||||||
|
-- the two land together only when the candidate matches the machine.
|
||||||
|
if not calClickDone then
|
||||||
|
calClickDone = true
|
||||||
|
soundPlay(SND_CLICK)
|
||||||
|
end
|
||||||
|
if not calFlashDone and (now - calBeat >= singeGetAudioLatency() + calValue) then
|
||||||
|
calFlashDone = true
|
||||||
|
menuElement("toolAudio"):SetClass("flash", true)
|
||||||
|
elseif calFlashDone then
|
||||||
|
menuElement("toolAudio"):SetClass("flash", false)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
finish = function()
|
||||||
|
menuElement("toolAudio"):SetClass("flash", false)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 2. Input test =====
|
||||||
|
|
||||||
|
-- Every device the engine can see and what it is doing right now. Singe opens every controller as
|
||||||
|
-- a gamepad, an unrecognised one through a mapping it writes itself, so there is one family to
|
||||||
|
-- report rather than two. The switch line shows what the game would actually receive, which is the
|
||||||
|
-- question being asked when a button "does nothing".
|
||||||
|
local inputLast = "none yet"
|
||||||
|
local inputCount = 0
|
||||||
|
|
||||||
|
|
||||||
|
-- The engine sets every switch as a SWITCH_ global, so the names come from there rather than from a
|
||||||
|
-- second list here that could fall behind it.
|
||||||
|
local SWITCH_NAMES = {}
|
||||||
|
for name, value in pairs(_G) do
|
||||||
|
if type(name) == "string" and type(value) == "number" and name:sub(1, 7) == "SWITCH_" then
|
||||||
|
SWITCH_NAMES[value] = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolInput",
|
||||||
|
name = "Input Test",
|
||||||
|
help = "Controllers, keyboard, mice and light guns, live.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
inputLast = "none yet"
|
||||||
|
inputCount = 0
|
||||||
|
end,
|
||||||
|
|
||||||
|
input = function(what)
|
||||||
|
inputCount = inputCount + 1
|
||||||
|
inputLast = string.format("%s (%d)", SWITCH_NAMES[what] or "unnamed", what)
|
||||||
|
-- Button 2 is how every other tool backs out, so it is reported and then allowed through.
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
|
||||||
|
update = function()
|
||||||
|
local rows = {}
|
||||||
|
rows[#rows + 1] = string.format("<div class='row'>Last switch: <b>%s</b> count: %d</div>", inputLast, inputCount)
|
||||||
|
rows[#rows + 1] = string.format("<div class='row'>Gamepads: %d Mice: %d Stick drives mouse: %s</div>",
|
||||||
|
controllerHowMany(), mouseHowMany(), tostring(joyMouseIsEnabled()))
|
||||||
|
for slot = 0, SINGE_MAX_CONTROLLERS - 1 do
|
||||||
|
if controllerIsValid(slot) then
|
||||||
|
local axes = {}
|
||||||
|
for a = 0, SINGE_CONTROLLER_AXES - 1 do
|
||||||
|
axes[#axes + 1] = string.format("%d", controllerGetAxis(slot, a))
|
||||||
|
end
|
||||||
|
local buttons = {}
|
||||||
|
-- controllerGetButton wants the framework's own code for the button, not SDL's
|
||||||
|
-- index, so it is built here the way Framework.singe builds GAMEPAD_0.
|
||||||
|
for b = 0, SINGE_CONTROLLER_BUTTONS - 1 do
|
||||||
|
if controllerGetButton(slot, SINGE_GAMEPAD_BASE + slot * SINGE_GAMEPAD_STRIDE + SINGE_GAMEPAD_BUTTON_OFFSET + b) then
|
||||||
|
buttons[#buttons + 1] = tostring(b)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rows[#rows + 1] = string.format("<div class='row'>%d: <b>%s</b></div>", slot, controllerGetName(slot))
|
||||||
|
rows[#rows + 1] = string.format("<div class='sub'>axes %s buttons %s</div>",
|
||||||
|
table.concat(axes, ", "), (#buttons > 0) and table.concat(buttons, " ") or "-")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for m = 0, mouseHowMany() - 1 do
|
||||||
|
local x, y = mouseGetPosition(m)
|
||||||
|
rows[#rows + 1] = string.format("<div class='sub'>mouse %d: %s at %d, %d</div>", m, mouseGetName(m), x, y)
|
||||||
|
end
|
||||||
|
set("inputRows", table.concat(rows))
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 3. System information =====
|
||||||
|
|
||||||
|
-- Everything the trace header knows, on one screen. A photograph of this answers most of a bug
|
||||||
|
-- report without anyone having to find trace.txt, which is the point of it.
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolSystem",
|
||||||
|
name = "System Information",
|
||||||
|
help = "What this machine is and what the engine chose to run on.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
local i = singeGetSystemInfo()
|
||||||
|
local rows = {}
|
||||||
|
local function line(label, value)
|
||||||
|
rows[#rows + 1] = string.format("<div class='pair'><span class='label'>%s</span><span class='detail'>%s</span></div>", label, tostring(value))
|
||||||
|
end
|
||||||
|
line("Singe", i.version)
|
||||||
|
line("System", i.os)
|
||||||
|
line("Processor", i.cpu)
|
||||||
|
line("Renderer", i.renderer)
|
||||||
|
line("3D device", i.gpu)
|
||||||
|
line("Video", i.decoder)
|
||||||
|
line("Audio", i.audio)
|
||||||
|
line("SoundFont", i.soundFont)
|
||||||
|
line("MIDI", i.midi)
|
||||||
|
line("Window", string.format("%d x %d, canvas %d x %d, overlay %d x %d",
|
||||||
|
i.windowWidth, i.windowHeight, i.canvasWidth, i.canvasHeight, overlayGetWidth(), overlayGetHeight()))
|
||||||
|
line("Data", i.dataPath)
|
||||||
|
set("systemRows", table.concat(rows))
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 4. Light gun =====
|
||||||
|
|
||||||
|
-- Where the gun is pointing, against the picture it is pointing at. A cabinet needs this every
|
||||||
|
-- time the gun or the monitor moves, and nothing else in the engine shows the mapping from the
|
||||||
|
-- gun's own coordinates to the canvas.
|
||||||
|
-- The targets: one in the middle and one in each corner, set in far enough that a gun that is
|
||||||
|
-- slightly out still puts its shot on the screen beside them.
|
||||||
|
local GUN_SHOTS_KEPT = 12
|
||||||
|
local GUN_INSET = 24
|
||||||
|
local GUN_RING = 14
|
||||||
|
local GUN_CENTRE = 2
|
||||||
|
local GUN_CROSS = 8
|
||||||
|
local gunShots = {}
|
||||||
|
local gunLast = "none yet"
|
||||||
|
|
||||||
|
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolGun",
|
||||||
|
name = "Light Gun",
|
||||||
|
help = "Aim and fire: see where the shot actually lands.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
gunShots = {}
|
||||||
|
gunLast = "none yet"
|
||||||
|
end,
|
||||||
|
|
||||||
|
input = function(what)
|
||||||
|
if what == SWITCH_BUTTON1 then
|
||||||
|
local x, y = mouseGetPosition(0)
|
||||||
|
gunShots[#gunShots + 1] = { x = x, y = y }
|
||||||
|
while #gunShots > GUN_SHOTS_KEPT do
|
||||||
|
table.remove(gunShots, 1)
|
||||||
|
end
|
||||||
|
gunLast = string.format("%d, %d", x, y)
|
||||||
|
return true
|
||||||
|
elseif what == SWITCH_START1 or what == SWITCH_START2 then
|
||||||
|
gunShots = {}
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
|
||||||
|
update = function()
|
||||||
|
local w, h = overlayGetWidth(), overlayGetHeight()
|
||||||
|
-- A target in the middle and one in each corner: a gun that is square on the picture puts
|
||||||
|
-- its shots on them, and one that is not shows which way it is out.
|
||||||
|
overlayClear()
|
||||||
|
colorForeground(90, 90, 90)
|
||||||
|
overlayBox(1, 1, w - 2, h - 2)
|
||||||
|
overlayLine(w / 2, 0, w / 2, h)
|
||||||
|
overlayLine(0, h / 2, w, h / 2)
|
||||||
|
colorForeground(255, 255, 0)
|
||||||
|
for _, p in ipairs({ { w / 2, h / 2 }, { GUN_INSET, GUN_INSET }, { w - GUN_INSET, GUN_INSET },
|
||||||
|
{ GUN_INSET, h - GUN_INSET }, { w - GUN_INSET, h - GUN_INSET } }) do
|
||||||
|
overlayCircle(p[1], p[2], GUN_RING)
|
||||||
|
overlayCircle(p[1], p[2], GUN_CENTRE)
|
||||||
|
end
|
||||||
|
colorForeground(255, 60, 60)
|
||||||
|
for _, shot in ipairs(gunShots) do
|
||||||
|
overlayLine(shot.x - GUN_CROSS, shot.y, shot.x + GUN_CROSS, shot.y)
|
||||||
|
overlayLine(shot.x, shot.y - GUN_CROSS, shot.x, shot.y + GUN_CROSS)
|
||||||
|
end
|
||||||
|
set("gunLast", gunLast)
|
||||||
|
set("gunCount", tostring(#gunShots))
|
||||||
|
end,
|
||||||
|
|
||||||
|
finish = function()
|
||||||
|
overlayClear()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 5. Sound test =====
|
||||||
|
|
||||||
|
-- Which speaker is which, and how loud each kind of sound is. A cabinet with its wires crossed
|
||||||
|
-- sounds perfectly fine until something is meant to come from one side.
|
||||||
|
local soundStep = 0
|
||||||
|
local soundNames = { "left speaker", "right speaker", "both speakers" }
|
||||||
|
local soundPans = { -1.0, 1.0, 0.0 }
|
||||||
|
|
||||||
|
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolSound",
|
||||||
|
name = "Sound Test",
|
||||||
|
help = "Left, right, both, and the disc's own audio tracks.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
soundStep = 0
|
||||||
|
set("soundWhich", "press Button 1")
|
||||||
|
local tracks = {}
|
||||||
|
for t = 0, discGetAudioTracks() - 1 do
|
||||||
|
local code = discGetLanguage(t)
|
||||||
|
tracks[#tracks + 1] = string.format("%d:%s", t, (code ~= "" and code ~= "unk") and code or "unnamed")
|
||||||
|
end
|
||||||
|
set("soundTracks", (#tracks > 0) and table.concat(tracks, " ") or "the disc has none")
|
||||||
|
end,
|
||||||
|
|
||||||
|
input = function(what)
|
||||||
|
if what == SWITCH_BUTTON1 then
|
||||||
|
soundStep = (soundStep % #soundNames) + 1
|
||||||
|
soundSetPan(SND_CLICK, soundPans[soundStep])
|
||||||
|
soundPlay(SND_CLICK)
|
||||||
|
set("soundWhich", soundNames[soundStep])
|
||||||
|
return true
|
||||||
|
elseif what == SWITCH_LEFT or what == SWITCH_RIGHT then
|
||||||
|
-- Walk the disc's audio tracks, which is how a multi language release is checked.
|
||||||
|
local count = discGetAudioTracks()
|
||||||
|
if count > 1 then
|
||||||
|
local next = (discGetAudioTrack() + ((what == SWITCH_RIGHT) and 1 or (count - 1))) % count
|
||||||
|
discSetAudioTrack(next)
|
||||||
|
set("soundWhich", string.format("disc track %d (%s)", next, discGetLanguage(next)))
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
|
||||||
|
finish = function()
|
||||||
|
soundSetPan(SND_CLICK, 0.0)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 6. Display =====
|
||||||
|
|
||||||
|
-- The monitor, and the picture's place on it. Scale, shift and rotation are live, so the operator
|
||||||
|
-- sees what they are doing, and singeSaveGeometry keeps them in machine.cfg beside the audio delay
|
||||||
|
-- -- once for the cabinet rather than once for every game.
|
||||||
|
--
|
||||||
|
-- The test pattern: a 16 by 12 grid, which is square on a 4:3 monitor and on a 16:9 one when the
|
||||||
|
-- picture is letterboxed into it, and an eight step greyscale wedge a thirty-second of the width
|
||||||
|
-- per step.
|
||||||
|
local FULL_TURN = 360
|
||||||
|
local DISP_COLUMNS = 16
|
||||||
|
local DISP_ROWS = 12
|
||||||
|
local DISP_STEPS = 8
|
||||||
|
local DISP_STEP_WIDE = 32
|
||||||
|
local DISP_WEDGE = 16
|
||||||
|
|
||||||
|
|
||||||
|
local function displayShow()
|
||||||
|
local x, y = vldpGetShift()
|
||||||
|
set("dispValues", string.format("scale %d%% shift %d, %d rotation %d",
|
||||||
|
vldpGetScale(), x, y, vldpGetRotate()))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolDisplay",
|
||||||
|
name = "Display",
|
||||||
|
help = "Size, position and rotation of the picture, and a test pattern.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
displayShow()
|
||||||
|
set("dispSaved", "")
|
||||||
|
end,
|
||||||
|
|
||||||
|
input = function(what)
|
||||||
|
local x, y = vldpGetShift()
|
||||||
|
if what == SWITCH_LEFT then
|
||||||
|
vldpSetShift(math.max(SINGE_SHIFT_MIN, x - 1), y)
|
||||||
|
elseif what == SWITCH_RIGHT then
|
||||||
|
vldpSetShift(math.min(SINGE_SHIFT_MAX, x + 1), y)
|
||||||
|
elseif what == SWITCH_UP then
|
||||||
|
vldpSetShift(x, math.max(SINGE_SHIFT_MIN, y - 1))
|
||||||
|
elseif what == SWITCH_DOWN then
|
||||||
|
vldpSetShift(x, math.min(SINGE_SHIFT_MAX, y + 1))
|
||||||
|
elseif what == SWITCH_BUTTON3 then
|
||||||
|
vldpSetScale(math.max(SINGE_SCALE_MIN, vldpGetScale() - 1))
|
||||||
|
elseif what == SWITCH_BUTTON4 then
|
||||||
|
vldpSetScale(math.min(SINGE_SCALE_MAX, vldpGetScale() + 1))
|
||||||
|
elseif what == SWITCH_COIN1 then
|
||||||
|
vldpSetRotate((vldpGetRotate() + SINGE_ROTATE_STEP) % FULL_TURN)
|
||||||
|
elseif what == SWITCH_START1 or what == SWITCH_START2 then
|
||||||
|
vldpSetScale(SINGE_SCALE_MAX)
|
||||||
|
vldpSetShift(0, 0)
|
||||||
|
vldpSetRotate(0)
|
||||||
|
elseif what == SWITCH_BUTTON1 then
|
||||||
|
singeSaveGeometry()
|
||||||
|
set("dispSaved", "kept for this machine")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
displayShow()
|
||||||
|
set("dispSaved", "")
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
|
||||||
|
update = function()
|
||||||
|
-- A grid to square the picture up with, a bar of greys to set brightness by, and a frame
|
||||||
|
-- one pixel inside the edge: if any of the frame is missing, the monitor is overscanning.
|
||||||
|
local w, h = overlayGetWidth(), overlayGetHeight()
|
||||||
|
overlayClear()
|
||||||
|
colorForeground(60, 60, 60)
|
||||||
|
for gx = 0, w, math.floor(w / DISP_COLUMNS) do
|
||||||
|
overlayLine(gx, 0, gx, h)
|
||||||
|
end
|
||||||
|
for gy = 0, h, math.floor(h / DISP_ROWS) do
|
||||||
|
overlayLine(0, gy, w, gy)
|
||||||
|
end
|
||||||
|
-- The greyscale wedge. overlayBox draws an outline, so each step is filled by its own
|
||||||
|
-- columns; a monitor with its brightness wrong loses the dark end or the light one.
|
||||||
|
local step = math.floor(w / DISP_STEP_WIDE)
|
||||||
|
local first = math.floor((w - DISP_STEPS * step) / 2)
|
||||||
|
local top = math.floor(h / 2) - DISP_WEDGE
|
||||||
|
local bottom = math.floor(h / 2) + DISP_WEDGE
|
||||||
|
for i = 0, DISP_STEPS - 1 do
|
||||||
|
local level = math.floor(i * 255 / (DISP_STEPS - 1))
|
||||||
|
local left = first + i * step
|
||||||
|
colorForeground(level, level, level)
|
||||||
|
for x = left, left + step - 1 do
|
||||||
|
overlayLine(x, top, x, bottom)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Two frames, one inside the other: a monitor that overscans eats the outer one first.
|
||||||
|
colorForeground(255, 255, 255)
|
||||||
|
overlayBox(0, 0, w - 1, h - 1)
|
||||||
|
overlayBox(1, 1, w - 2, h - 2)
|
||||||
|
end,
|
||||||
|
|
||||||
|
finish = function()
|
||||||
|
overlayClear()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 7. Disc test =====
|
||||||
|
|
||||||
|
-- How long the disc takes to find a frame. A rip with its keyframes far apart seeks slowly, and
|
||||||
|
-- the only symptom in a game is that everything feels heavy; this measures it directly.
|
||||||
|
-- Twelve seeks spread over the disc, and a seek that has not landed in three seconds has failed
|
||||||
|
-- rather than been slow.
|
||||||
|
local DISC_SEEKS = 12
|
||||||
|
local DISC_SEEK_LIMIT = 3000
|
||||||
|
local discResults = {}
|
||||||
|
local discPending = nil
|
||||||
|
local discWorst = 0
|
||||||
|
|
||||||
|
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolDisc",
|
||||||
|
name = "Disc Test",
|
||||||
|
help = "Seek timing: how quickly the disc finds a frame.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
discResults = {}
|
||||||
|
discPending = nil
|
||||||
|
discWorst = 0
|
||||||
|
set("discRows", "<div class='row'>Button 1 runs the test.</div>")
|
||||||
|
end,
|
||||||
|
|
||||||
|
input = function(what)
|
||||||
|
if what == SWITCH_BUTTON1 and not discPending then
|
||||||
|
discResults = {}
|
||||||
|
discWorst = 0
|
||||||
|
discPending = { step = 0, started = 0 }
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
|
||||||
|
update = function()
|
||||||
|
if not discPending then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if discPending.started == 0 then
|
||||||
|
-- Spread the targets over the whole disc rather than clustering, so a framefile's
|
||||||
|
-- segment changes are included in what is measured.
|
||||||
|
local total = discGetFrameCount()
|
||||||
|
discPending.step = discPending.step + 1
|
||||||
|
discPending.target = math.floor((discPending.step / DISC_SEEKS) * math.max(1, total - 1))
|
||||||
|
discPending.started = singeGetTicks()
|
||||||
|
discSearch(discPending.target)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if discGetFrame() == discPending.target or singeGetTicks() - discPending.started > DISC_SEEK_LIMIT then
|
||||||
|
local took = singeGetTicks() - discPending.started
|
||||||
|
discResults[#discResults + 1] = string.format("<div class='sub'>frame %d took %d ms</div>", discPending.target, took)
|
||||||
|
discWorst = math.max(discWorst, took)
|
||||||
|
discPending.started = 0
|
||||||
|
if discPending.step >= DISC_SEEKS then
|
||||||
|
discResults[#discResults + 1] = string.format("<div class='row'>worst <b>%d ms</b> over %d seeks</div>", discWorst, discPending.step)
|
||||||
|
discPending = nil
|
||||||
|
end
|
||||||
|
set("discRows", table.concat(discResults))
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
finish = function()
|
||||||
|
discPending = nil
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 8. Saved data =====
|
||||||
|
|
||||||
|
-- What each game has kept, and a way to throw it away. High scores are the usual reason: an
|
||||||
|
-- operator wants the board cleared without deleting the game. Each game saves into its own data
|
||||||
|
-- directory, so they are found by looking rather than by being told.
|
||||||
|
local saveDirs = {}
|
||||||
|
local saveSelected = 1
|
||||||
|
|
||||||
|
|
||||||
|
local function savedShow()
|
||||||
|
local rows = {}
|
||||||
|
if #saveDirs == 0 then
|
||||||
|
rows[#rows + 1] = "<div class='row'>No game has saved anything yet.</div>"
|
||||||
|
end
|
||||||
|
for i, entry in ipairs(saveDirs) do
|
||||||
|
local class = (i == saveSelected) and " class='selected'" or ""
|
||||||
|
rows[#rows + 1] = string.format("<div%s>%s <span class='sub'>%d bytes</span></div>", class, entry.name, entry.size)
|
||||||
|
end
|
||||||
|
set("savedRows", table.concat(rows))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolSaved",
|
||||||
|
name = "Saved Data",
|
||||||
|
help = "What each game has kept, and clearing it.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
saveDirs = {}
|
||||||
|
saveSelected = 1
|
||||||
|
-- Every game's data directory is one below the root the engine was given.
|
||||||
|
-- lfs.dir hands back its iterator and the directory it walks; the iterator is useless
|
||||||
|
-- without it, so both are kept. pcall because the root need not exist yet.
|
||||||
|
local root = singeGetSystemInfo().dataRoot
|
||||||
|
local ok, iter, state = pcall(lfs.dir, root)
|
||||||
|
if ok then
|
||||||
|
for name in iter, state do
|
||||||
|
if name ~= "." and name ~= ".." then
|
||||||
|
local file = root .. name .. "/save.json"
|
||||||
|
local attr = lfs.attributes(file)
|
||||||
|
if attr and attr.mode == "file" then
|
||||||
|
saveDirs[#saveDirs + 1] = { name = name, path = file, size = attr.size }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(saveDirs, function(a, b) return a.name < b.name end)
|
||||||
|
savedShow()
|
||||||
|
end,
|
||||||
|
|
||||||
|
input = function(what)
|
||||||
|
if #saveDirs == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if what == SWITCH_UP then
|
||||||
|
saveSelected = (saveSelected == 1) and #saveDirs or (saveSelected - 1)
|
||||||
|
savedShow()
|
||||||
|
return true
|
||||||
|
elseif what == SWITCH_DOWN then
|
||||||
|
saveSelected = (saveSelected == #saveDirs) and 1 or (saveSelected + 1)
|
||||||
|
savedShow()
|
||||||
|
return true
|
||||||
|
elseif what == SWITCH_BUTTON3 then
|
||||||
|
-- Button 3 rather than Button 1: Button 1 is "choose" everywhere else, and this
|
||||||
|
-- throws away somebody's high scores.
|
||||||
|
os.remove(saveDirs[saveSelected].path)
|
||||||
|
table.remove(saveDirs, saveSelected)
|
||||||
|
saveSelected = math.max(1, math.min(saveSelected, #saveDirs))
|
||||||
|
savedShow()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 9. MIDI =====
|
||||||
|
|
||||||
|
-- The ports this machine has, and whether anything is coming in or going out. Nothing is opened
|
||||||
|
-- until this tool asks, so a cabinet that never touches MIDI never pays for it.
|
||||||
|
-- Middle C on channel 1 at a plain mezzo-forte: loud enough to hear on any instrument, and on
|
||||||
|
-- the channel a synthesiser is most likely to have something assigned to.
|
||||||
|
local MIDI_TEST_CHANNEL = 1
|
||||||
|
local MIDI_TEST_NOTE = 60
|
||||||
|
local MIDI_TEST_VELOCITY = 100
|
||||||
|
local MIDI_SEEN_MAX = 8
|
||||||
|
local midiIn, midiOut = -1, -1
|
||||||
|
local midiSeen = {}
|
||||||
|
local midiDirty = false
|
||||||
|
|
||||||
|
|
||||||
|
local function midiShow()
|
||||||
|
local rows = {}
|
||||||
|
rows[#rows + 1] = string.format("<div class='row'>%d in, %d out</div>", midiInputCount(), midiOutputCount())
|
||||||
|
for i = 0, midiOutputCount() - 1 do
|
||||||
|
rows[#rows + 1] = string.format("<div class='sub'>out %d%s %s</div>", i, (i == midiOut) and " *" or "", midiOutputName(i))
|
||||||
|
end
|
||||||
|
for i = 0, midiInputCount() - 1 do
|
||||||
|
rows[#rows + 1] = string.format("<div class='sub'>in %d%s %s</div>", i, (i == midiIn) and " *" or "", midiInputName(i))
|
||||||
|
end
|
||||||
|
for _, m in ipairs(midiSeen) do
|
||||||
|
rows[#rows + 1] = string.format("<div class='sub'>received %s</div>", m)
|
||||||
|
end
|
||||||
|
set("midiRows", table.concat(rows))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function onMidiMessage(status, data1, data2, bytes)
|
||||||
|
midiSeen[#midiSeen + 1] = string.format("%02X %02X %02X", status, data1, data2)
|
||||||
|
while #midiSeen > MIDI_SEEN_MAX do
|
||||||
|
table.remove(midiSeen, 1)
|
||||||
|
end
|
||||||
|
midiDirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolMidi",
|
||||||
|
name = "MIDI Ports",
|
||||||
|
help = "List the ports, send a note, watch what arrives.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
midiSeen = {}
|
||||||
|
midiDirty = false
|
||||||
|
if midiOutputCount() > 0 and not midiIsOutputOpen() then
|
||||||
|
midiOut = midiOpenOutput(0) and 0 or -1
|
||||||
|
end
|
||||||
|
if midiInputCount() > 0 and not midiIsInputOpen() then
|
||||||
|
midiIn = midiOpenInput(0) and 0 or -1
|
||||||
|
end
|
||||||
|
midiShow()
|
||||||
|
end,
|
||||||
|
|
||||||
|
input = function(what)
|
||||||
|
if what == SWITCH_BUTTON1 and midiIsOutputOpen() then
|
||||||
|
midiNoteOn(MIDI_TEST_CHANNEL, MIDI_TEST_NOTE, MIDI_TEST_VELOCITY)
|
||||||
|
midiNoteOff(MIDI_TEST_CHANNEL, MIDI_TEST_NOTE)
|
||||||
|
return true
|
||||||
|
elseif what == SWITCH_BUTTON3 then
|
||||||
|
midiRescan()
|
||||||
|
midiShow()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
|
||||||
|
update = function()
|
||||||
|
if midiDirty then
|
||||||
|
midiDirty = false
|
||||||
|
midiShow()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
finish = function()
|
||||||
|
midiCloseInput()
|
||||||
|
midiCloseOutput()
|
||||||
|
midiIn, midiOut = -1, -1
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== 10. Frame statistics =====
|
||||||
|
|
||||||
|
TOOLS[#TOOLS + 1] = {
|
||||||
|
id = "toolStats",
|
||||||
|
name = "Frame Statistics",
|
||||||
|
help = "The developer's overlay: frame time, what is alive, what the disc is doing.",
|
||||||
|
|
||||||
|
begin = function()
|
||||||
|
set("statsState", statsIsEnabled() and "on" or "off")
|
||||||
|
end,
|
||||||
|
|
||||||
|
input = function(what)
|
||||||
|
if what == SWITCH_BUTTON1 then
|
||||||
|
statsEnable(not statsIsEnabled())
|
||||||
|
set("statsState", statsIsEnabled() and "on" or "off")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
@ -124,8 +124,16 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Audio delay calibration: an opaque page over everything, white for the frame of the flash. */
|
/* .hidden and .tool are one class each, and the pages that let the overlay through wear two, so a
|
||||||
#calibration {
|
page taken out of view has to be said again here or its display wins. */
|
||||||
|
.tool.hidden,
|
||||||
|
.tool.clear.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The service tools: one opaque page over everything, the same for all of them. The audio delay
|
||||||
|
tool alone turns the page white for the frame of its flash, so the colours are named twice. */
|
||||||
|
.tool {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -136,35 +144,107 @@ body {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calibration.flash {
|
.tool.flash {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calibration h1 {
|
.tool h1 {
|
||||||
font-size: 24dp;
|
font-size: 24dp;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
margin-bottom: 20dp;
|
margin-bottom: 20dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calibration.flash h1 {
|
.tool.flash h1 {
|
||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calibration p {
|
.tool p {
|
||||||
margin-bottom: 20dp;
|
margin-bottom: 20dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calibration p.value {
|
.tool p.value {
|
||||||
margin-bottom: 4dp;
|
margin-bottom: 4dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calibration p.value + p.keys {
|
/* The key hints stand away from whatever the tool put above them, and close together among
|
||||||
|
themselves when a tool needs more than one line of them. */
|
||||||
|
.tool p.keys {
|
||||||
margin-top: 20dp;
|
margin-top: 20dp;
|
||||||
|
margin-bottom: 4dp;
|
||||||
|
color: #8fa2c4;
|
||||||
}
|
}
|
||||||
|
|
||||||
#calibration p.keys {
|
.tool p.keys + p.keys {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The rows a tool fills in for itself: a heading row and an indented detail row under it. Text
|
||||||
|
with nowhere to break -- a long path -- wraps mid-word rather than running off the edge, but an
|
||||||
|
ordinary word is left whole. */
|
||||||
|
.tool .row {
|
||||||
|
margin-bottom: 2dp;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool .sub {
|
||||||
|
margin-left: 20dp;
|
||||||
|
margin-bottom: 2dp;
|
||||||
|
font-size: 12dp;
|
||||||
|
color: #c8d2e6;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A label beside its value. The two are columns rather than one run of text, so a value too long
|
||||||
|
for the line wraps under itself instead of under the label. */
|
||||||
|
.tool .pair {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 2dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool .pair .label {
|
||||||
|
flex: 0 0 110dp;
|
||||||
|
color: #8fa2c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool .pair .detail {
|
||||||
|
flex: 1 1 0;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The chosen line of a list the tool draws, in the game list's gold. */
|
||||||
|
.tool .selected {
|
||||||
|
color: #ffd35a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Two tools draw their own picture into the overlay, which is underneath the document: the light
|
||||||
|
gun's targets and the display's test pattern. Those pages let it through and gather their text
|
||||||
|
into a strip along the bottom, between the two lower targets, so nothing worth aiming at is
|
||||||
|
covered. */
|
||||||
|
.tool.clear {
|
||||||
|
background-color: transparent;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool.clear h1 {
|
||||||
|
font-size: 16dp;
|
||||||
margin-bottom: 4dp;
|
margin-bottom: 4dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool.clear h1,
|
||||||
|
.tool.clear p {
|
||||||
|
background-color: #000000c0;
|
||||||
|
padding: 1dp 8dp;
|
||||||
|
margin-bottom: 4dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The help under the list of tools, away from the list itself. */
|
||||||
|
#toolHelp {
|
||||||
|
margin-top: 12dp;
|
||||||
|
}
|
||||||
|
|
|
||||||
292
docs/Manual.adoc
292
docs/Manual.adoc
|
|
@ -88,8 +88,8 @@ a line of key hints along the bottom.
|
||||||
Up and down move the selection one game at a time and left and right move it
|
Up and down move the selection one game at a time and left and right move it
|
||||||
a page at a time; both wrap from either end of the list to the other. Start
|
a page at a time; both wrap from either end of the list to the other. Start
|
||||||
or any action button launches the selected game, and the key mapped to
|
or any action button launches the selected game, and the key mapped to
|
||||||
`INPUT_SERVICE` (the `9` key by default) opens the audio delay calibration
|
`INPUT_SERVICE` (the `9` key by default) opens the service tools (see
|
||||||
screen (see <<menucalibration,Calibrating from the menu>>). With a mouse, a
|
<<servicetools,The Service Tools>>). With a mouse, a
|
||||||
click on a row selects that game and a second click on the same row, or a
|
click on a row selects that game and a second click on the same row, or a
|
||||||
click on the Start button in the footer, launches it; the mouse wheel
|
click on the Start button in the footer, launches it; the mouse wheel
|
||||||
scrolls a description too long for its panel. The menu keeps its selection
|
scrolls a description too long for its panel. The menu keeps its selection
|
||||||
|
|
@ -100,7 +100,79 @@ A GUI needs the GPU device the 3D scene uses, so on a machine without one
|
||||||
`guiNew` fails and the menu cannot start. `Singe/MenuClassic.singe` is the
|
`guiNew` fails and the menu cannot start. `Singe/MenuClassic.singe` is the
|
||||||
previous menu, drawn with sprites and `fontPrint` straight into the overlay,
|
previous menu, drawn with sprites and `fontPrint` straight into the overlay,
|
||||||
kept for one release for exactly that machine: edit `Menu.sh` (or `Menu.bat`)
|
kept for one release for exactly that machine: edit `Menu.sh` (or `Menu.bat`)
|
||||||
to name it in place of `Singe/Menu.singe`.
|
to name it in place of `Singe/Menu.singe`. It has the audio delay screen and
|
||||||
|
nothing else; the rest of the service tools need the document.
|
||||||
|
|
||||||
|
[#servicetools]
|
||||||
|
==== The Service Tools
|
||||||
|
|
||||||
|
The key mapped to `INPUT_SERVICE` opens a list of tools over the game list.
|
||||||
|
Up and down choose one and button 1 opens it; button 2 backs out of a tool to
|
||||||
|
the list, and the service key again leaves the tools altogether. Everything
|
||||||
|
lives in `Singe/Tools.singe`, which the engine extracts beside `Menu.singe`,
|
||||||
|
and each tool is a page of `Singe/Menu.rml`. Nothing here needs a game to be
|
||||||
|
running, and nothing changes a game's own files.
|
||||||
|
|
||||||
|
Audio Delay::
|
||||||
|
The downstream audio delay, measured against a click and a flash. See
|
||||||
|
<<menucalibration,Calibrating from the menu>> for what it is doing and why it
|
||||||
|
works.
|
||||||
|
|
||||||
|
Input Test::
|
||||||
|
Every controller, mouse and gun the engine can see, live: the last switch the
|
||||||
|
game would have received and how many have arrived, each pad's name with its
|
||||||
|
six axes and the buttons held down, and each mouse's name and position. This
|
||||||
|
is the screen that answers "the button does nothing" -- either the switch
|
||||||
|
arrives and the game ignores it, or it never arrives at all.
|
||||||
|
|
||||||
|
System Information::
|
||||||
|
The engine's version, the operating system, the processor and memory, the
|
||||||
|
renderer and the 3D device, which video decoders are in play, the audio
|
||||||
|
formats built in, the SoundFont, the MIDI state, the window, canvas and
|
||||||
|
overlay sizes, and the data directory. A photograph of this page answers most
|
||||||
|
of a bug report.
|
||||||
|
|
||||||
|
Light Gun::
|
||||||
|
Targets in the centre and at each corner of the overlay, over whatever the
|
||||||
|
disc is showing. Button 1 marks where the gun actually pointed, keeping the
|
||||||
|
last twelve shots; start clears them. A gun that is square on the picture puts
|
||||||
|
its crosses on the targets, and one that is not shows which way it is out.
|
||||||
|
|
||||||
|
Sound Test::
|
||||||
|
Button 1 plays the click through the left speaker, then the right, then both,
|
||||||
|
which is the whole of a crossed-wires check. It also lists the disc's own
|
||||||
|
audio tracks, and left and right switch between them.
|
||||||
|
|
||||||
|
Display::
|
||||||
|
The picture's size, position and rotation, changed live with the arrows,
|
||||||
|
buttons 3 and 4, and coin 1, over a test pattern: a 16 by 12 grid to square
|
||||||
|
the picture up with, an eight step greyscale wedge to set brightness by, and
|
||||||
|
two frames one pixel apart at the very edge -- a monitor that overscans eats
|
||||||
|
the outer one first. Button 1 keeps the values for this machine, in the same
|
||||||
|
`machine.cfg` the audio delay uses, so they apply to every game rather than
|
||||||
|
being set once per game. Start puts everything back.
|
||||||
|
|
||||||
|
Disc Test::
|
||||||
|
Twelve seeks spread over the whole disc, each timed. A rip whose keyframes are
|
||||||
|
far apart seeks slowly, and the only symptom in a game is that everything
|
||||||
|
feels heavy; this measures it directly and reports the worst of the twelve.
|
||||||
|
A seek that has not landed in three seconds is counted as that.
|
||||||
|
|
||||||
|
Saved Data::
|
||||||
|
What each game has kept, found by looking through the data root rather than by
|
||||||
|
being told, with the size of each. Button 3 -- not button 1, which is "choose"
|
||||||
|
everywhere else -- throws away the selected game's save. Clearing a high score
|
||||||
|
table without deleting the game is the usual reason.
|
||||||
|
|
||||||
|
MIDI Ports::
|
||||||
|
The input and output ports this machine has, which of them Singe has opened,
|
||||||
|
and the last few messages that arrived. Button 1 sends a note, button 3 looks
|
||||||
|
for ports again after something has been plugged in. Nothing is opened until
|
||||||
|
this tool asks, so a cabinet that never touches MIDI never pays for it.
|
||||||
|
|
||||||
|
Frame Statistics::
|
||||||
|
Turns the developer's overlay on and off. It stays on into the game, in the
|
||||||
|
top left corner. See <<statsenable,statsEnable>>.
|
||||||
|
|
||||||
[[controls]]
|
[[controls]]
|
||||||
=== Customizing the Controls
|
=== Customizing the Controls
|
||||||
|
|
@ -334,12 +406,18 @@ names one, and `deterministic = false` leaves the option off.
|
||||||
. The built in default.
|
. The built in default.
|
||||||
. The settings file.
|
. The settings file.
|
||||||
. The game's `games.dat` entry, for the handful of settings it carries (see <<gamesdat,games.dat>>).
|
. The game's `games.dat` entry, for the handful of settings it carries (see <<gamesdat,games.dat>>).
|
||||||
|
. `machine.cfg`, for the audio delay and the picture geometry the service tools
|
||||||
|
save (see <<servicetools,The Service Tools>>).
|
||||||
. The command line, which always wins.
|
. The command line, which always wins.
|
||||||
|
|
||||||
A settings file is a set of defaults, in other words, not something you typed:
|
A settings file is a set of defaults, in other words, not something you typed:
|
||||||
if a `games.dat` entry gives a game a resolution or a Sinden border, that entry
|
if a `games.dat` entry gives a game a resolution or a Sinden border, that entry
|
||||||
still describes the game better than a machine-wide file does. Anything actually
|
still describes the game better than a machine-wide file does. Anything actually
|
||||||
typed on the command line beats both.
|
typed on the command line beats both. `machine.cfg` sits just under the command
|
||||||
|
line because somebody adjusted the picture while looking at this monitor, which
|
||||||
|
is better evidence than any file written elsewhere; the audio delay is the
|
||||||
|
exception to the ordering entirely, being added to the game's delay rather than
|
||||||
|
replacing it.
|
||||||
|
|
||||||
Options that name the game (`--framefile`, `--disc`, `--entry`), name a
|
Options that name the game (`--framefile`, `--disc`, `--entry`), name a
|
||||||
directory (`--gamedir`, `--datadir`), or have to act before the file could be
|
directory (`--gamedir`, `--datadir`), or have to act before the file could be
|
||||||
|
|
@ -871,10 +949,11 @@ Singe/ Support files extracted by the engine
|
||||||
Menu.singe The bundled game menu
|
Menu.singe The bundled game menu
|
||||||
Menu.rml The menu's RmlUi document
|
Menu.rml The menu's RmlUi document
|
||||||
menu.rcss The menu's style sheet, on top of gui.rcss
|
menu.rcss The menu's style sheet, on top of gui.rcss
|
||||||
|
Tools.singe The menu's service tools (see The Service Tools)
|
||||||
MenuClassic.singe The previous, overlay-drawn menu (see The Bundled Menu)
|
MenuClassic.singe The previous, overlay-drawn menu (see The Bundled Menu)
|
||||||
gui.rcss The shipped GUI theme
|
gui.rcss The shipped GUI theme
|
||||||
controls.cfg.example Template for input mappings
|
controls.cfg.example Template for input mappings
|
||||||
click.wav Used by the menu's audio delay calibration
|
click.wav Used by the menu's audio delay and sound tests
|
||||||
Manual.pdf This manual
|
Manual.pdf This manual
|
||||||
ActionMax/ One game
|
ActionMax/ One game
|
||||||
games.dat Menu entries for the games in this directory
|
games.dat Menu entries for the games in this directory
|
||||||
|
|
@ -883,6 +962,7 @@ ActionMax/ One game
|
||||||
sprite_*.png, sound_*.wav, font_*.ttf
|
sprite_*.png, sound_*.wav, font_*.ttf
|
||||||
DLe.game A game packed into one file (see Single-File Games)
|
DLe.game A game packed into one file (see Single-File Games)
|
||||||
data/
|
data/
|
||||||
|
machine.cfg This machine's audio delay and picture geometry
|
||||||
ActionMax/ Indexes, trace.txt, screenshots, saves for that game
|
ActionMax/ Indexes, trace.txt, screenshots, saves for that game
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
@ -3107,7 +3187,9 @@ available to `controls.cfg` and to `Framework.singe` alike:
|
||||||
| `SINGE_TRIGGER_THRESHOLD` | The analogue trigger threshold in those same raw units; `0` when the triggers are on `SINGE_DEAD_ZONE`, which is the default.
|
| `SINGE_TRIGGER_THRESHOLD` | The analogue trigger threshold in those same raw units; `0` when the triggers are on `SINGE_DEAD_ZONE`, which is the default.
|
||||||
| `SINGE_LEGACY_SPRITE_ARGS` | True when the game asked for the 2.10 sprite argument order.
|
| `SINGE_LEGACY_SPRITE_ARGS` | True when the game asked for the 2.10 sprite argument order.
|
||||||
| `SINGE_DISC` | True when the game has a laserdisc; false when the canvas is the world.
|
| `SINGE_DISC` | True when the game has a laserdisc; false when the canvas is the world.
|
||||||
| `SINGE_GAMEPAD_BASE`, `SINGE_GAMEPAD_STRIDE`, `SINGE_AXIS_STRIDE`, `SINGE_GAMEPAD_BUTTON_OFFSET`, `SINGE_MOUSE_BASE`, `SINGE_MOUSE_STRIDE`, `SINGE_MAX_CONTROLLERS`, `SINGE_MAX_MICE` | Layout of the controller and mouse input codes; `Framework.singe` builds the `GAMEPAD_N` and `MOUSE_N` tables from them.
|
| `SINGE_GAMEPAD_BASE`, `SINGE_GAMEPAD_STRIDE`, `SINGE_AXIS_STRIDE`, `SINGE_GAMEPAD_BUTTON_OFFSET`, `SINGE_MOUSE_BASE`, `SINGE_MOUSE_STRIDE`, `SINGE_MAX_CONTROLLERS`, `SINGE_MAX_MICE`, `SINGE_CONTROLLER_AXES`, `SINGE_CONTROLLER_BUTTONS` | Layout of the controller and mouse input codes, and how many axes and buttons a pad has; `Framework.singe` builds the `GAMEPAD_N` and `MOUSE_N` tables from them, and a service screen walks a pad with them.
|
||||||
|
|
||||||
|
| `SINGE_SCALE_MIN`, `SINGE_SCALE_MAX`, `SINGE_SHIFT_MIN`, `SINGE_SHIFT_MAX`, `SINGE_ROTATE_STEP` | What `vldpSetScale`, `vldpSetShift` and `vldpSetRotate` will take. Clamp to these rather than to your own copies: a shift out of range ends the script.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
`Framework.singe` adds the `SCANCODE` and `MODIFIER` tables (SDL's key and
|
`Framework.singe` adds the `SCANCODE` and `MODIFIER` tables (SDL's key and
|
||||||
|
|
@ -3172,16 +3254,17 @@ of audio arriving early.
|
||||||
==== Calibrating from the menu
|
==== Calibrating from the menu
|
||||||
|
|
||||||
The bundled menu has a calibration screen for that downstream delay. Press the
|
The bundled menu has a calibration screen for that downstream delay. Press the
|
||||||
key mapped to `INPUT_SERVICE` (the `9` key by default) on the game list. The
|
key mapped to `INPUT_SERVICE` (the `9` key by default) on the game list and
|
||||||
screen is a page of the menu's document: it covers the menu in black, shows
|
choose Audio Delay from the service tools (see <<servicetools,The Service
|
||||||
the delay being tried and the device queue the engine measured, plays a click
|
Tools>>). The screen is a page of the menu's document: it covers the menu in
|
||||||
once a second and turns white for one frame at each flash. Adjust with left
|
black, shows the delay being tried and the device queue the engine measured,
|
||||||
and right (10 ms) and up and down (1 ms) until the flash and the click happen
|
plays a click once a second and turns white for one frame at each flash.
|
||||||
together, then press button 1 to save. Start resets to zero and button 2 (or
|
Adjust with left and right (10 ms) and up and down (1 ms) until the flash and
|
||||||
the service key again) cancels. The value is stored in `audio.cfg` in the
|
the click happen together, then press button 1 to save. Start resets to zero
|
||||||
data root and applied by the engine to every game on that machine, whether
|
and button 2 (or the service key again) cancels. The value is stored in
|
||||||
launched from the menu or from the command line; it is added to any per-game
|
`machine.cfg` in the data root and applied by the engine to every game on that
|
||||||
`AUDIO_DELAY`.
|
machine, whether launched from the menu or from the command line; it is added
|
||||||
|
to any per-game `AUDIO_DELAY`.
|
||||||
Recalibrate after changing speakers, headphones, or displays.
|
Recalibrate after changing speakers, headphones, or displays.
|
||||||
|
|
||||||
The screen works because the click and the disc audio share one mixer device
|
The screen works because the click and the disc audio share one mixer device
|
||||||
|
|
@ -5312,7 +5395,7 @@ for track = 0, discGetAudioTracks() - 1 do
|
||||||
end
|
end
|
||||||
----
|
----
|
||||||
|
|
||||||
[#discgetframe]
|
[#discgetsubtitlelanguage]
|
||||||
==== discGetSubtitleLanguage
|
==== discGetSubtitleLanguage
|
||||||
|
|
||||||
[source,text]
|
[source,text]
|
||||||
|
|
@ -5331,6 +5414,7 @@ The language a subtitle track inside the disc's own container is labelled with,
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<discgetsubtitletracks,discGetSubtitleTracks>>, <<srtloadtrack,srtLoadTrack>>
|
*See also:* <<discgetsubtitletracks,discGetSubtitleTracks>>, <<srtloadtrack,srtLoadTrack>>
|
||||||
|
|
||||||
|
[#discgetsubtitletracks]
|
||||||
==== discGetSubtitleTracks
|
==== discGetSubtitleTracks
|
||||||
|
|
||||||
[source,text]
|
[source,text]
|
||||||
|
|
@ -5360,6 +5444,7 @@ for track = 0, discGetSubtitleTracks() - 1 do
|
||||||
end
|
end
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[#discgetframe]
|
||||||
==== discGetFrame
|
==== discGetFrame
|
||||||
|
|
||||||
[source,text]
|
[source,text]
|
||||||
|
|
@ -5389,6 +5474,31 @@ function onOverlayUpdate()
|
||||||
end
|
end
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[#discgetframecount]
|
||||||
|
==== discGetFrameCount
|
||||||
|
|
||||||
|
[source,text]
|
||||||
|
----
|
||||||
|
frames = discGetFrameCount()
|
||||||
|
----
|
||||||
|
|
||||||
|
The number of frames on the disc, which is one past the highest frame
|
||||||
|
<<discsearch,discSearch>> will find. A framefile answers for the whole disc
|
||||||
|
rather than for the segment playing, so the count does not change as segments
|
||||||
|
come and go. Without a disc it answers `0`.
|
||||||
|
|
||||||
|
*Returns:* integer frame count, `0` without a disc.
|
||||||
|
|
||||||
|
*Since:* 3.00.
|
||||||
|
*See also:* <<discgetframe,discGetFrame>>, <<discsearch,discSearch>>
|
||||||
|
|
||||||
|
.Example
|
||||||
|
[source,lua]
|
||||||
|
----
|
||||||
|
-- Seek to the middle of the disc, wherever that is.
|
||||||
|
discSearch(math.floor(discGetFrameCount() / 2))
|
||||||
|
----
|
||||||
|
|
||||||
[#discgetheight]
|
[#discgetheight]
|
||||||
==== discGetHeight
|
==== discGetHeight
|
||||||
|
|
||||||
|
|
@ -13837,7 +13947,7 @@ end
|
||||||
milliseconds = singeGetAudioCalibration()
|
milliseconds = singeGetAudioCalibration()
|
||||||
----
|
----
|
||||||
|
|
||||||
Returns the per-machine audio delay in effect, in milliseconds: the value the menu's calibration screen saved to `audio.cfg` in the data root, or one set since by `singeSetAudioCalibration`. Zero when the machine has never been calibrated. It applies to every game on the machine, on top of the per-game delay from `singeGetAudioDelay`.
|
Returns the per-machine audio delay in effect, in milliseconds: the value the menu's calibration screen saved to `machine.cfg` in the data root, or one set since by `singeSetAudioCalibration`. Zero when the machine has never been calibrated. It applies to every game on the machine, on top of the per-game delay from `singeGetAudioDelay`.
|
||||||
|
|
||||||
*Returns:* integer milliseconds.
|
*Returns:* integer milliseconds.
|
||||||
|
|
||||||
|
|
@ -14020,6 +14130,49 @@ DIR = singeGetScriptPath():match("(.*[/\\])") or "./"
|
||||||
titleSprite = spriteLoad(DIR .. "images/title.png")
|
titleSprite = spriteLoad(DIR .. "images/title.png")
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[#singegetsysteminfo]
|
||||||
|
==== singeGetSystemInfo
|
||||||
|
|
||||||
|
[source,text]
|
||||||
|
----
|
||||||
|
info = singeGetSystemInfo()
|
||||||
|
----
|
||||||
|
|
||||||
|
Everything the trace header knows about this machine and this run, in one
|
||||||
|
table. It is meant for a service screen and for bug reports: the answer to
|
||||||
|
"what was it running on" without asking the player to find a log.
|
||||||
|
|
||||||
|
*Returns:* a table with these fields, all strings except the four sizes:
|
||||||
|
|
||||||
|
* `version` -- the engine version, as `SINGE_VERSION_STRING`.
|
||||||
|
* `os` -- the operating system name and release, and the architecture.
|
||||||
|
* `cpu` -- the processor, its core count and the memory in megabytes.
|
||||||
|
* `renderer` -- the name of SDL's 2D renderer in use.
|
||||||
|
* `gpu` -- the SDL_GPU driver behind the 3D scene, or a note that 3D is
|
||||||
|
unavailable.
|
||||||
|
* `decoder` -- which hardware video decoders the build will try, in words.
|
||||||
|
* `audio` -- the audio formats compiled in, comma separated.
|
||||||
|
* `soundFont` -- the SoundFont MIDI playback is using, or why there is none.
|
||||||
|
* `midi` -- the MIDI ports open, or a note that none have been asked for.
|
||||||
|
* `dataPath` -- this game's data directory.
|
||||||
|
* `dataRoot` -- the data directory the engine was given, one above `dataPath`,
|
||||||
|
which every game on the machine writes under.
|
||||||
|
* `windowWidth`, `windowHeight` -- integers, the window in pixels.
|
||||||
|
* `canvasWidth`, `canvasHeight` -- integers, the canvas in pixels.
|
||||||
|
|
||||||
|
*Since:* 3.00.
|
||||||
|
*See also:* <<singegetdatapath,singeGetDataPath>>, <<servicetools,The Service Tools>>
|
||||||
|
|
||||||
|
.Example
|
||||||
|
[source,lua]
|
||||||
|
----
|
||||||
|
-- A one-key diagnostic screen.
|
||||||
|
local info = singeGetSystemInfo()
|
||||||
|
fontPrint(10, 10, "Singe " .. info.version .. " on " .. info.os)
|
||||||
|
fontPrint(10, 30, info.cpu)
|
||||||
|
fontPrint(10, 50, info.renderer .. " / " .. info.gpu)
|
||||||
|
----
|
||||||
|
|
||||||
[#singegetticks]
|
[#singegetticks]
|
||||||
==== singeGetTicks
|
==== singeGetTicks
|
||||||
|
|
||||||
|
|
@ -14124,6 +14277,42 @@ function onKeyPressed(keysym, scancode)
|
||||||
end
|
end
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[#singesavegeometry]
|
||||||
|
==== singeSaveGeometry
|
||||||
|
|
||||||
|
[source,text]
|
||||||
|
----
|
||||||
|
singeSaveGeometry()
|
||||||
|
----
|
||||||
|
|
||||||
|
Keeps the picture's current scale, shift and rotation for this machine. They
|
||||||
|
go into `machine.cfg` in the data root, beside the audio delay, and the engine
|
||||||
|
applies them to every game it starts afterwards, from the menu or from the
|
||||||
|
command line, and across `singeReload`. An option actually typed on the command
|
||||||
|
line wins: `--scalefactor` overrides the saved scale, `--shiftx` or `--shifty`
|
||||||
|
the saved shift, `--rotate` the saved rotation. The file is written whole, so
|
||||||
|
the audio delay is kept as it stands.
|
||||||
|
|
||||||
|
This is a cabinet setting, not a game setting: it is how a monitor is squared
|
||||||
|
up once rather than once per game. The Display tool in the bundled menu is
|
||||||
|
exactly this call behind button 1.
|
||||||
|
|
||||||
|
*Returns:* nothing.
|
||||||
|
|
||||||
|
*Since:* 3.00.
|
||||||
|
*See also:* <<vldpsetscale,vldpSetScale>>, <<vldpsetshift,vldpSetShift>>, <<vldpsetrotate,vldpSetRotate>>, <<singesetaudiocalibration,singeSetAudioCalibration>>
|
||||||
|
|
||||||
|
.Example
|
||||||
|
[source,lua]
|
||||||
|
----
|
||||||
|
-- The operator is happy with the picture: keep it for the cabinet.
|
||||||
|
function onInputPressed(input)
|
||||||
|
if input == SWITCH_BUTTON1 then
|
||||||
|
singeSaveGeometry()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
----
|
||||||
|
|
||||||
[#singescreenshot]
|
[#singescreenshot]
|
||||||
==== singeScreenshot
|
==== singeScreenshot
|
||||||
|
|
||||||
|
|
@ -14160,7 +14349,7 @@ end
|
||||||
singeSetAudioCalibration(milliseconds)
|
singeSetAudioCalibration(milliseconds)
|
||||||
----
|
----
|
||||||
|
|
||||||
Sets the per-machine audio delay, applies it at once, and writes it to `audio.cfg` in the data root so every game on the machine picks it up. The menu's calibration screen calls it; a game with its own service menu may too. The value is positive when the audio is heard later than the device reports; the engine delays video presentation by that much. Values outside `-1000` to `1000` abort the script. If `audio.cfg` cannot be written the value still applies for this run and a message goes to the console.
|
Sets the per-machine audio delay, applies it at once, and writes it to `machine.cfg` in the data root so every game on the machine picks it up. The menu's calibration screen calls it; a game with its own service menu may too. The value is positive when the audio is heard later than the device reports; the engine delays video presentation by that much. Values outside `-1000` to `1000` abort the script. If `machine.cfg` cannot be written the value still applies for this run and a message goes to the console.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -16098,6 +16287,7 @@ so it never lands in a screenshot.
|
||||||
Leave it off in a shipped game. Nothing stops you putting it behind a key in
|
Leave it off in a shipped game. Nothing stops you putting it behind a key in
|
||||||
your own service menu.
|
your own service menu.
|
||||||
|
|
||||||
|
[#statsenable]
|
||||||
==== statsEnable
|
==== statsEnable
|
||||||
|
|
||||||
[source,text]
|
[source,text]
|
||||||
|
|
@ -17934,6 +18124,32 @@ else
|
||||||
end
|
end
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[#vldpgetshift]
|
||||||
|
==== vldpGetShift
|
||||||
|
|
||||||
|
[source,text]
|
||||||
|
----
|
||||||
|
x, y = vldpGetShift()
|
||||||
|
----
|
||||||
|
|
||||||
|
The picture's offset within the window as percentages, the `--shiftx` and
|
||||||
|
`--shifty` options or whatever <<vldpsetshift,vldpSetShift>> last applied, and
|
||||||
|
`0, 0` when neither has been given. At the extreme a shift fills exactly the
|
||||||
|
room `--scalefactor` left, so `100` with the scale at `100` moves nothing.
|
||||||
|
|
||||||
|
*Returns:* two integers, each `-100` to `100`.
|
||||||
|
|
||||||
|
*Since:* 3.00.
|
||||||
|
*See also:* <<vldpsetshift,vldpSetShift>>, <<vldpgetscale,vldpGetScale>>
|
||||||
|
|
||||||
|
.Example
|
||||||
|
[source,lua]
|
||||||
|
----
|
||||||
|
-- Report where the operator has put the picture.
|
||||||
|
local x, y = vldpGetShift()
|
||||||
|
fontPrint(10, 10, string.format("shift %d, %d", x, y))
|
||||||
|
----
|
||||||
|
|
||||||
[#vldpgetwidth]
|
[#vldpgetwidth]
|
||||||
==== vldpGetWidth
|
==== vldpGetWidth
|
||||||
|
|
||||||
|
|
@ -18164,6 +18380,44 @@ function onInputPressed(input)
|
||||||
end
|
end
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[#vldpsetshift]
|
||||||
|
==== vldpSetShift
|
||||||
|
|
||||||
|
[source,text]
|
||||||
|
----
|
||||||
|
vldpSetShift(x, y)
|
||||||
|
----
|
||||||
|
|
||||||
|
Moves the picture within the window, the running equivalent of `--shiftx` and
|
||||||
|
`--shifty`, and rebuilds the video rectangle so the disc, the overlay, the
|
||||||
|
GUIs, the 3D scene, the particles, any Sinden border and the mouse mapping all
|
||||||
|
follow it. The values are percentages of the room `--scalefactor` left, so
|
||||||
|
with the scale at `100` there is nowhere to move to. A value outside `-100` to
|
||||||
|
`100` ends the script, unlike <<vldpsetscale,vldpSetScale>>, which answers
|
||||||
|
`false`.
|
||||||
|
|
||||||
|
*Parameters:*
|
||||||
|
|
||||||
|
* `x` -- integer, `-100` to `100`.
|
||||||
|
* `y` -- integer, `-100` to `100`.
|
||||||
|
|
||||||
|
*Returns:* nothing.
|
||||||
|
|
||||||
|
*Since:* 3.00.
|
||||||
|
*See also:* <<vldpgetshift,vldpGetShift>>, <<vldpsetscale,vldpSetScale>>, <<singesavegeometry,singeSaveGeometry>>
|
||||||
|
|
||||||
|
.Example
|
||||||
|
[source,lua]
|
||||||
|
----
|
||||||
|
-- Nudge the picture left while the operator holds the key.
|
||||||
|
function onInputPressed(input)
|
||||||
|
if input == SWITCH_LEFT then
|
||||||
|
local x, y = vldpGetShift()
|
||||||
|
vldpSetShift(math.max(-100, x - 1), y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
----
|
||||||
|
|
||||||
[#vldpsetverbose]
|
[#vldpsetverbose]
|
||||||
==== vldpSetVerbose
|
==== vldpSetVerbose
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
#include "generated/controls_cfg.h"
|
#include "generated/controls_cfg.h"
|
||||||
#include "generated/settings_cfg.h"
|
#include "generated/settings_cfg.h"
|
||||||
#include "generated/Menu_singe.h"
|
#include "generated/Menu_singe.h"
|
||||||
|
#include "generated/Tools_singe.h"
|
||||||
#include "generated/MenuClassic_singe.h"
|
#include "generated/MenuClassic_singe.h"
|
||||||
#include "generated/Menu_rml.h"
|
#include "generated/Menu_rml.h"
|
||||||
#include "generated/menu_rcss.h"
|
#include "generated/menu_rcss.h"
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,17 @@ int64_t frameFileGetFrame(int32_t frameFileHandle, int32_t videoHandle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int64_t frameFileGetFrameCount(int32_t frameFileHandle) {
|
||||||
|
FrameFileT *f = _getFrameFile(frameFileHandle, "frameFileGetFrameCount");
|
||||||
|
|
||||||
|
if (f->count <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return f->files[f->count - 1].frame + videoGetFrameCount(_openSegment(f, f->count - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer *renderer, bool showCalculated) {
|
int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer *renderer, bool showCalculated) {
|
||||||
int32_t count = 0;
|
int32_t count = 0;
|
||||||
int64_t frame = 0;
|
int64_t frame = 0;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@
|
||||||
|
|
||||||
char *frameFileAudioName(const char *filename, const char *suffix);
|
char *frameFileAudioName(const char *filename, const char *suffix);
|
||||||
int64_t frameFileGetFrame(int32_t frameFileHandle, int32_t videoHandle);
|
int64_t frameFileGetFrame(int32_t frameFileHandle, int32_t videoHandle);
|
||||||
|
|
||||||
|
// Frames in the whole disc: where the last segment starts, plus how long it is. Opening that
|
||||||
|
// segment is the price of the answer, which is why it is asked for rather than kept.
|
||||||
|
int64_t frameFileGetFrameCount(int32_t frameFileHandle);
|
||||||
int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer *renderer, bool showCalculated);
|
int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer *renderer, bool showCalculated);
|
||||||
void frameFileQuit(void);
|
void frameFileQuit(void);
|
||||||
void frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHandle, int64_t *actualFrame);
|
void frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHandle, int64_t *actualFrame);
|
||||||
|
|
|
||||||
28
src/main.c
28
src/main.c
|
|
@ -276,9 +276,6 @@ static char *_cloneString(const char *string);
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
static void _crashHandler(int signalNumber);
|
static void _crashHandler(int signalNumber);
|
||||||
#endif
|
#endif
|
||||||
static char *_describeAudioDecoders(void);
|
|
||||||
static char *_describeCpu(void);
|
|
||||||
static char *_describeOs(void);
|
|
||||||
static bool _extractFile(const char *filename, const uint8_t *data, size_t length);
|
static bool _extractFile(const char *filename, const uint8_t *data, size_t length);
|
||||||
static char *_findVideoFile(const char *baseName);
|
static char *_findVideoFile(const char *baseName);
|
||||||
static void _launcher(const char *exeName, ConfigT *conf);
|
static void _launcher(const char *exeName, ConfigT *conf);
|
||||||
|
|
@ -381,7 +378,8 @@ static void _applyOptions(const char *exeName, ConfigT *conf, int32_t argc, cons
|
||||||
|
|
||||||
// Overscan Zoom
|
// Overscan Zoom
|
||||||
case 'b':
|
case 'b':
|
||||||
target = &conf->scaleFactor;
|
conf->given |= GIVEN_SCALE;
|
||||||
|
target = &conf->scaleFactor;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Show Calculated Frame File Values
|
// Show Calculated Frame File Values
|
||||||
|
|
@ -510,7 +508,8 @@ static void _applyOptions(const char *exeName, ConfigT *conf, int32_t argc, cons
|
||||||
|
|
||||||
// Presentation Rotation
|
// Presentation Rotation
|
||||||
case 'r':
|
case 'r':
|
||||||
target = &conf->rotate;
|
conf->given |= GIVEN_ROTATE;
|
||||||
|
target = &conf->rotate;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Where The Sinden Border Sits
|
// Where The Sinden Border Sits
|
||||||
|
|
@ -550,7 +549,8 @@ static void _applyOptions(const char *exeName, ConfigT *conf, int32_t argc, cons
|
||||||
|
|
||||||
// Horizontal Shift
|
// Horizontal Shift
|
||||||
case 'X':
|
case 'X':
|
||||||
target = &conf->shiftX;
|
conf->given |= GIVEN_SHIFT;
|
||||||
|
target = &conf->shiftX;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// X Resolution
|
// X Resolution
|
||||||
|
|
@ -562,7 +562,8 @@ static void _applyOptions(const char *exeName, ConfigT *conf, int32_t argc, cons
|
||||||
|
|
||||||
// Vertical Shift
|
// Vertical Shift
|
||||||
case 'Y':
|
case 'Y':
|
||||||
target = &conf->shiftY;
|
conf->given |= GIVEN_SHIFT;
|
||||||
|
target = &conf->shiftY;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Y Resolution
|
// Y Resolution
|
||||||
|
|
@ -949,7 +950,7 @@ static void _crashHandler(int signalNumber) {
|
||||||
|
|
||||||
// Which audio formats this build can decode, for a bug report and so a user whose music is silent
|
// Which audio formats this build can decode, for a bug report and so a user whose music is silent
|
||||||
// can see at a glance whether its format was ever compiled in. The mixer keeps the list.
|
// can see at a glance whether its format was ever compiled in. The mixer keeps the list.
|
||||||
static char *_describeAudioDecoders(void) {
|
char *mainDescribeAudioDecoders(void) {
|
||||||
char *list = strdup("");
|
char *list = strdup("");
|
||||||
char *grown = NULL;
|
char *grown = NULL;
|
||||||
int32_t count = MIX_GetNumAudioDecoders();
|
int32_t count = MIX_GetNumAudioDecoders();
|
||||||
|
|
@ -967,7 +968,7 @@ static char *_describeAudioDecoders(void) {
|
||||||
|
|
||||||
// The processor, for a bug report. Every platform keeps the name somewhere different, and none of
|
// The processor, for a bug report. Every platform keeps the name somewhere different, and none of
|
||||||
// them is worth failing over: the core count is always there as a fallback.
|
// them is worth failing over: the core count is always there as a fallback.
|
||||||
static char *_describeCpu(void) {
|
char *mainDescribeCpu(void) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
char name[128];
|
char name[128];
|
||||||
DWORD bytes = sizeof(name);
|
DWORD bytes = sizeof(name);
|
||||||
|
|
@ -1019,7 +1020,7 @@ static char *_describeCpu(void) {
|
||||||
|
|
||||||
|
|
||||||
// The operating system and its version, for a bug report.
|
// The operating system and its version, for a bug report.
|
||||||
static char *_describeOs(void) {
|
char *mainDescribeOs(void) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
char release[64];
|
char release[64];
|
||||||
DWORD bytes = sizeof(release);
|
DWORD bytes = sizeof(release);
|
||||||
|
|
@ -1749,9 +1750,9 @@ static void _stopSDL(void) {
|
||||||
// started it, the machine, and what the engine picked to run on. main.c's own trace lines wait
|
// started it, the machine, and what the engine picked to run on. main.c's own trace lines wait
|
||||||
// behind it so the block is always first and can be pasted whole.
|
// behind it so the block is always first and can be pasted whole.
|
||||||
static void _traceHeader(const ConfigT *conf, SDL_Renderer *renderer, SDL_GPUDevice *device) {
|
static void _traceHeader(const ConfigT *conf, SDL_Renderer *renderer, SDL_GPUDevice *device) {
|
||||||
char *os = _describeOs();
|
char *os = mainDescribeOs();
|
||||||
char *cpu = _describeCpu();
|
char *cpu = mainDescribeCpu();
|
||||||
char *audio = _describeAudioDecoders();
|
char *audio = mainDescribeAudioDecoders();
|
||||||
int32_t built = SDL_VERSION;
|
int32_t built = SDL_VERSION;
|
||||||
int32_t linked = SDL_GetVersion();
|
int32_t linked = SDL_GetVersion();
|
||||||
|
|
||||||
|
|
@ -1789,6 +1790,7 @@ static void _unpackData(const char *exePath, bool absolute) {
|
||||||
{ "controls.cfg.example", controls_cfg, controls_cfg_len },
|
{ "controls.cfg.example", controls_cfg, controls_cfg_len },
|
||||||
{ "settings.cfg.example", settings_cfg, settings_cfg_len },
|
{ "settings.cfg.example", settings_cfg, settings_cfg_len },
|
||||||
{ "Menu.singe", Menu_singe, Menu_singe_len },
|
{ "Menu.singe", Menu_singe, Menu_singe_len },
|
||||||
|
{ "Tools.singe", Tools_singe, Tools_singe_len },
|
||||||
{ "MenuClassic.singe", MenuClassic_singe, MenuClassic_singe_len },
|
{ "MenuClassic.singe", MenuClassic_singe, MenuClassic_singe_len },
|
||||||
{ "Menu.rml", Menu_rml, Menu_rml_len },
|
{ "Menu.rml", Menu_rml, Menu_rml_len },
|
||||||
{ "menu.rcss", menu_rcss, menu_rcss_len },
|
{ "menu.rcss", menu_rcss, menu_rcss_len },
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,11 @@ char *createDataDir(const char *dataDirBase, const char *filename);
|
||||||
char *createDataDirFor(const ConfigT *conf);
|
char *createDataDirFor(const ConfigT *conf);
|
||||||
void destroyConf(ConfigT **confPointer);
|
void destroyConf(ConfigT **confPointer);
|
||||||
bool isFrameFileName(const char *filename);
|
bool isFrameFileName(const char *filename);
|
||||||
|
// What the trace header says about this machine, for the service tools to show as well. Each one
|
||||||
|
// allocates; free() what comes back.
|
||||||
|
char *mainDescribeAudioDecoders(void);
|
||||||
|
char *mainDescribeCpu(void);
|
||||||
|
char *mainDescribeOs(void);
|
||||||
bool parseSindenString(const char *sindenString, ConfigT *conf);
|
bool parseSindenString(const char *sindenString, ConfigT *conf);
|
||||||
void queueScript(const ConfigT *conf);
|
void queueScript(const ConfigT *conf);
|
||||||
char *resolveDataDir(const ConfigT *conf);
|
char *resolveDataDir(const ConfigT *conf);
|
||||||
|
|
|
||||||
226
src/singe.c
226
src/singe.c
|
|
@ -61,6 +61,7 @@ LSEC_API int luaopen_ssl_config(lua_State *L);
|
||||||
#include "collide.h"
|
#include "collide.h"
|
||||||
#include "decode.h"
|
#include "decode.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "midi.h"
|
||||||
#include "midiIo.h"
|
#include "midiIo.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "frameFile.h"
|
#include "frameFile.h"
|
||||||
|
|
@ -136,7 +137,6 @@ SDL_COMPILE_TIME_ASSERT(codeGamepadBase, CODE_GAMEPAD_BASE >= SDL_SCANCODE_RESER
|
||||||
#define ANIMATION_MIN_DELAY_MS 10 // GIFs often carry a zero delay
|
#define ANIMATION_MIN_DELAY_MS 10 // GIFs often carry a zero delay
|
||||||
#define SCREENSHOT_MAX 10000
|
#define SCREENSHOT_MAX 10000
|
||||||
#define VIDEO_SCALE_THROTTLE_MS 15 // vldpSetScale accepts one change this often, as Hypseus does
|
#define VIDEO_SCALE_THROTTLE_MS 15 // vldpSetScale accepts one change this often, as Hypseus does
|
||||||
#define VIDEO_SHIFT_MAX 100 // --shiftx and --shifty at their extreme fill the room --scalefactor leaves
|
|
||||||
#define BEZEL_DIRECTORY "bezels" // Where --bezel looks for its artwork unless --bezeldir names another
|
#define BEZEL_DIRECTORY "bezels" // Where --bezel looks for its artwork unless --bezeldir names another
|
||||||
#define BEZEL_SIDECAR_EXTENSION ".cfg" // The cutout beside the artwork, named after it
|
#define BEZEL_SIDECAR_EXTENSION ".cfg" // The cutout beside the artwork, named after it
|
||||||
#define BEZEL_CUTOUT_TABLE "CUTOUT" // The one table the sidecar sets
|
#define BEZEL_CUTOUT_TABLE "CUTOUT" // The one table the sidecar sets
|
||||||
|
|
@ -215,7 +215,7 @@ SDL_COMPILE_TIME_ASSERT(codeGamepadBase, CODE_GAMEPAD_BASE >= SDL_SCANCODE_RESER
|
||||||
#define SOUND_DIRECTION_EPSILON 0.0001f // Closer than this to the listener has no direction
|
#define SOUND_DIRECTION_EPSILON 0.0001f // Closer than this to the listener has no direction
|
||||||
#define HEIGHTMAP_MAX 1025 // Pixels per side of a heightmap image
|
#define HEIGHTMAP_MAX 1025 // Pixels per side of a heightmap image
|
||||||
#define WATCH_INTERVAL_MS 1000 // How often --reload checks the script files
|
#define WATCH_INTERVAL_MS 1000 // How often --reload checks the script files
|
||||||
#define AUDIO_CALIBRATION_FILE "audio.cfg" // Per-machine audio delay, in the data root
|
#define MACHINE_FILE "machine.cfg" // Per-machine audio delay and picture geometry, in the data root
|
||||||
#define CONTROLS_FILE "controls.cfg" // Input mappings, built in and overridden per game
|
#define CONTROLS_FILE "controls.cfg" // Input mappings, built in and overridden per game
|
||||||
#define SETTINGS_FILE "settings.cfg" // User options, found the same four ways controls.cfg is
|
#define SETTINGS_FILE "settings.cfg" // User options, found the same four ways controls.cfg is
|
||||||
#define SETTINGS_PLACES 4 // The four places both of those are looked for
|
#define SETTINGS_PLACES 4 // The four places both of those are looked for
|
||||||
|
|
@ -781,12 +781,12 @@ static int32_t _lfsDirIterator(lua_State *L);
|
||||||
static void _lfsFillAttributes(lua_State *L, bool directory, int64_t size, int64_t modified);
|
static void _lfsFillAttributes(lua_State *L, bool directory, int64_t size, int64_t modified);
|
||||||
static int32_t _lfsMkdir(lua_State *L);
|
static int32_t _lfsMkdir(lua_State *L);
|
||||||
static int32_t _lfsRmdir(lua_State *L);
|
static int32_t _lfsRmdir(lua_State *L);
|
||||||
static int32_t _loadAudioCalibration(void);
|
|
||||||
static void _loadControlMappings(void);
|
static void _loadControlMappings(void);
|
||||||
static void _loadControlsFile(const char *path);
|
static void _loadControlsFile(const char *path);
|
||||||
static SDL_Surface *_loadEmbeddedPng(const uint8_t *data, size_t length);
|
static SDL_Surface *_loadEmbeddedPng(const uint8_t *data, size_t length);
|
||||||
static SDL_Texture *_loadEmbeddedTexture(const uint8_t *data, size_t length, SDL_Surface **surface);
|
static SDL_Texture *_loadEmbeddedTexture(const uint8_t *data, size_t length, SDL_Surface **surface);
|
||||||
static void _loadGamepadDatabase(void);
|
static void _loadGamepadDatabase(void);
|
||||||
|
static void _loadMachineSettings(void);
|
||||||
static void _logicalRect(SDL_FRect *rect);
|
static void _logicalRect(SDL_FRect *rect);
|
||||||
static int32_t _luaCallOriginal(lua_State *L);
|
static int32_t _luaCallOriginal(lua_State *L);
|
||||||
static void _luaDie(lua_State *L, const char *method, const char *fmt, ...) __attribute__((format(printf, 3, 4))) __attribute__((noreturn));
|
static void _luaDie(lua_State *L, const char *method, const char *fmt, ...) __attribute__((format(printf, 3, 4))) __attribute__((noreturn));
|
||||||
|
|
@ -801,6 +801,7 @@ static int32_t _luaPanic(lua_State *L);
|
||||||
static int32_t _luaSearcher(lua_State *L);
|
static int32_t _luaSearcher(lua_State *L);
|
||||||
static void _luaTrace(lua_State *L, const char *method, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
|
static void _luaTrace(lua_State *L, const char *method, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
|
||||||
static int32_t _luaTraceback(lua_State *L);
|
static int32_t _luaTraceback(lua_State *L);
|
||||||
|
static char *_machineFilePath(void);
|
||||||
static void _mapPointer(float px, float py, int32_t *x, int32_t *y);
|
static void _mapPointer(float px, float py, int32_t *x, int32_t *y);
|
||||||
static void _mapPointerDelta(float dx, float dy, int32_t *xr, int32_t *yr);
|
static void _mapPointerDelta(float dx, float dy, int32_t *xr, int32_t *yr);
|
||||||
static void _mapUnknownJoysticks(void);
|
static void _mapUnknownJoysticks(void);
|
||||||
|
|
@ -809,6 +810,7 @@ static float _mixerGain(int32_t volume, int32_t maximum);
|
||||||
static int32_t _mouseCode(int32_t device, int32_t button);
|
static int32_t _mouseCode(int32_t device, int32_t button);
|
||||||
static void _musicDestroy(MusicT *music);
|
static void _musicDestroy(MusicT *music);
|
||||||
static bool _saveCjson(lua_State *L, const char *name);
|
static bool _saveCjson(lua_State *L, const char *name);
|
||||||
|
static void _saveMachineSettings(int32_t milliseconds);
|
||||||
static void _saveCopy(lua_State *L, int32_t index, const char *method, int32_t depth);
|
static void _saveCopy(lua_State *L, int32_t index, const char *method, int32_t depth);
|
||||||
static void _saveNormalise(lua_State *L, int32_t depth);
|
static void _saveNormalise(lua_State *L, int32_t depth);
|
||||||
static void _saveCheckValue(lua_State *L, const char *method, int32_t index);
|
static void _saveCheckValue(lua_State *L, const char *method, int32_t index);
|
||||||
|
|
@ -852,7 +854,6 @@ static void _resetScriptState(void);
|
||||||
static void _rmlEscape(char *out, size_t size, const char *text);
|
static void _rmlEscape(char *out, size_t size, const char *text);
|
||||||
static void _runScript(bool fatal);
|
static void _runScript(bool fatal);
|
||||||
static bool _sameLoosePath(const char *a, const char *b);
|
static bool _sameLoosePath(const char *a, const char *b);
|
||||||
static void _saveAudioCalibration(int32_t milliseconds);
|
|
||||||
static SDL_Texture *_sceneVideoSource(int32_t player);
|
static SDL_Texture *_sceneVideoSource(int32_t player);
|
||||||
static void _scorePanelDigits(char *text, size_t size, int32_t value, int32_t digits, bool blank);
|
static void _scorePanelDigits(char *text, size_t size, int32_t value, int32_t digits, bool blank);
|
||||||
static bool _scorePanelEnable(bool enabled);
|
static bool _scorePanelEnable(bool enabled);
|
||||||
|
|
@ -968,6 +969,7 @@ static int32_t apiDiscGetAudioTracks(lua_State *L);
|
||||||
static int32_t apiDiscGetSubtitleLanguage(lua_State *L);
|
static int32_t apiDiscGetSubtitleLanguage(lua_State *L);
|
||||||
static int32_t apiDiscGetSubtitleTracks(lua_State *L);
|
static int32_t apiDiscGetSubtitleTracks(lua_State *L);
|
||||||
static int32_t apiDiscGetFrame(lua_State *L);
|
static int32_t apiDiscGetFrame(lua_State *L);
|
||||||
|
static int32_t apiDiscGetFrameCount(lua_State *L);
|
||||||
static int32_t apiDiscGetHeight(lua_State *L);
|
static int32_t apiDiscGetHeight(lua_State *L);
|
||||||
static int32_t apiDiscGetLanguage(lua_State *L);
|
static int32_t apiDiscGetLanguage(lua_State *L);
|
||||||
static int32_t apiDiscGetState(lua_State *L);
|
static int32_t apiDiscGetState(lua_State *L);
|
||||||
|
|
@ -1266,6 +1268,8 @@ static int32_t apiSingeGetAudioCalibration(lua_State *L);
|
||||||
static int32_t apiSingeGetAudioDelay(lua_State *L);
|
static int32_t apiSingeGetAudioDelay(lua_State *L);
|
||||||
static int32_t apiSingeGetAudioLatency(lua_State *L);
|
static int32_t apiSingeGetAudioLatency(lua_State *L);
|
||||||
static int32_t apiSingeGetDataPath(lua_State *L);
|
static int32_t apiSingeGetDataPath(lua_State *L);
|
||||||
|
static int32_t apiSingeGetSystemInfo(lua_State *L);
|
||||||
|
static int32_t apiSingeSaveGeometry(lua_State *L);
|
||||||
static int32_t apiSingeGetHeight(lua_State *L);
|
static int32_t apiSingeGetHeight(lua_State *L);
|
||||||
static int32_t apiSingeGetPauseFlag(lua_State *L);
|
static int32_t apiSingeGetPauseFlag(lua_State *L);
|
||||||
static int32_t apiSingeGetScriptPath(lua_State *L);
|
static int32_t apiSingeGetScriptPath(lua_State *L);
|
||||||
|
|
@ -1402,7 +1406,9 @@ static int32_t apiVldpSetBlend(lua_State *L);
|
||||||
static int32_t apiVldpSetLuma(lua_State *L);
|
static int32_t apiVldpSetLuma(lua_State *L);
|
||||||
static int32_t apiVldpSetMonochrome(lua_State *L);
|
static int32_t apiVldpSetMonochrome(lua_State *L);
|
||||||
static int32_t apiVldpSetRotate(lua_State *L);
|
static int32_t apiVldpSetRotate(lua_State *L);
|
||||||
|
static int32_t apiVldpGetShift(lua_State *L);
|
||||||
static int32_t apiVldpSetScale(lua_State *L);
|
static int32_t apiVldpSetScale(lua_State *L);
|
||||||
|
static int32_t apiVldpSetShift(lua_State *L);
|
||||||
static int32_t apiVldpSetVerbose(lua_State *L);
|
static int32_t apiVldpSetVerbose(lua_State *L);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2354,8 +2360,8 @@ static void _computeVideoRect(void) {
|
||||||
scaledH = rect.h * (float)_global.videoScale / (float)SCALE_FACTOR_MAX;
|
scaledH = rect.h * (float)_global.videoScale / (float)SCALE_FACTOR_MAX;
|
||||||
roomX = (rect.w - scaledW) / 2.0f;
|
roomX = (rect.w - scaledW) / 2.0f;
|
||||||
roomY = (rect.h - scaledH) / 2.0f;
|
roomY = (rect.h - scaledH) / 2.0f;
|
||||||
rect.x += roomX + roomX * (float)_global.videoShiftX / (float)VIDEO_SHIFT_MAX;
|
rect.x += roomX + roomX * (float)_global.videoShiftX / (float)SHIFT_MAX;
|
||||||
rect.y += roomY + roomY * (float)_global.videoShiftY / (float)VIDEO_SHIFT_MAX;
|
rect.y += roomY + roomY * (float)_global.videoShiftY / (float)SHIFT_MAX;
|
||||||
rect.w = scaledW;
|
rect.w = scaledW;
|
||||||
rect.h = scaledH;
|
rect.h = scaledH;
|
||||||
|
|
||||||
|
|
@ -3776,28 +3782,6 @@ static int32_t _lfsRmdir(lua_State *L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The per-machine audio delay lives beside the data directories, since it is not a property of any game.
|
|
||||||
static int32_t _loadAudioCalibration(void) {
|
|
||||||
char *path = utilCreateString("%s%s", _global.conf->dataDirBase, AUDIO_CALIBRATION_FILE);
|
|
||||||
size_t bytes = 0;
|
|
||||||
char *data = utilReadFile(path, &bytes);
|
|
||||||
int32_t value = 0;
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
if (sscanf(data, "delay = %d", &value) != 1) {
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
free(data);
|
|
||||||
}
|
|
||||||
free(path);
|
|
||||||
if ((value < -VIDEO_AUDIO_DELAY_MAX) || (value > VIDEO_AUDIO_DELAY_MAX)) {
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// The built-in controls.cfg, then every override in turn (the working directory, above and inside
|
// The built-in controls.cfg, then every override in turn (the working directory, above and inside
|
||||||
// the data directory, beside the script), each place only once, in a throwaway Lua state that has
|
// the data directory, beside the script), each place only once, in a throwaway Lua state that has
|
||||||
// the framework but not the API. Leaves the dead zone and the switch mappings behind.
|
// the framework but not the API. Leaves the dead zone and the switch mappings behind.
|
||||||
|
|
@ -4000,6 +3984,50 @@ static void _loadGamepadDatabase(void) {
|
||||||
|
|
||||||
|
|
||||||
// The whole logical rectangle: the window, in the coordinates everything is drawn in.
|
// The whole logical rectangle: the window, in the coordinates everything is drawn in.
|
||||||
|
// Everything the service tools keep for this machine, read in one pass: the audio delay and the
|
||||||
|
// picture geometry. A file missing a key leaves that setting alone, which is why each is looked
|
||||||
|
// for on its own rather than with one scan of the whole file.
|
||||||
|
//
|
||||||
|
// The geometry goes into the configuration rather than straight into the live values, because the
|
||||||
|
// live ones are set back to the configuration whenever a script starts or reloads; writing there
|
||||||
|
// makes "start from the command line" mean "start from the command line as this cabinet amends it".
|
||||||
|
// An option actually typed wins, which is what the GIVEN_ flags are for.
|
||||||
|
static void _loadMachineSettings(void) {
|
||||||
|
char *path = _machineFilePath();
|
||||||
|
size_t bytes = 0;
|
||||||
|
char *data = utilReadFile(path, &bytes);
|
||||||
|
const char *at = NULL;
|
||||||
|
int32_t delay = 0;
|
||||||
|
int32_t value = 0;
|
||||||
|
|
||||||
|
free(path);
|
||||||
|
if (data != NULL) {
|
||||||
|
at = strstr(data, "delay = ");
|
||||||
|
if ((at != NULL) && (sscanf(at, "delay = %d", &value) == 1) && (value >= -VIDEO_AUDIO_DELAY_MAX) && (value <= VIDEO_AUDIO_DELAY_MAX)) {
|
||||||
|
delay = value;
|
||||||
|
}
|
||||||
|
at = strstr(data, "scale = ");
|
||||||
|
if ((at != NULL) && !(_global.conf->given & GIVEN_SCALE) && (sscanf(at, "scale = %d", &value) == 1) && (value >= SCALE_FACTOR_MIN) && (value <= SCALE_FACTOR_MAX)) {
|
||||||
|
_global.conf->scaleFactor = value;
|
||||||
|
}
|
||||||
|
at = strstr(data, "shiftx = ");
|
||||||
|
if ((at != NULL) && !(_global.conf->given & GIVEN_SHIFT) && (sscanf(at, "shiftx = %d", &value) == 1) && (value >= SHIFT_MIN) && (value <= SHIFT_MAX)) {
|
||||||
|
_global.conf->shiftX = value;
|
||||||
|
}
|
||||||
|
at = strstr(data, "shifty = ");
|
||||||
|
if ((at != NULL) && !(_global.conf->given & GIVEN_SHIFT) && (sscanf(at, "shifty = %d", &value) == 1) && (value >= SHIFT_MIN) && (value <= SHIFT_MAX)) {
|
||||||
|
_global.conf->shiftY = value;
|
||||||
|
}
|
||||||
|
at = strstr(data, "rotate = ");
|
||||||
|
if ((at != NULL) && !(_global.conf->given & GIVEN_ROTATE) && (sscanf(at, "rotate = %d", &value) == 1) && ((value % ROTATE_STEP) == 0)) {
|
||||||
|
_global.conf->rotate = value;
|
||||||
|
}
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
videoSetAudioCalibration(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void _logicalRect(SDL_FRect *rect) {
|
static void _logicalRect(SDL_FRect *rect) {
|
||||||
rect->x = 0.0f;
|
rect->x = 0.0f;
|
||||||
rect->y = 0.0f;
|
rect->y = 0.0f;
|
||||||
|
|
@ -4302,6 +4330,13 @@ static int32_t _luaTraceback(lua_State *L) {
|
||||||
// A pointer position in presentation coordinates to the overlay coordinates the game works in:
|
// A pointer position in presentation coordinates to the overlay coordinates the game works in:
|
||||||
// the rotation comes off first, then the position maps into whatever the shift, the scale factor
|
// the rotation comes off first, then the position maps into whatever the shift, the scale factor
|
||||||
// and the Sinden border left of the picture.
|
// and the Sinden border left of the picture.
|
||||||
|
// The machine file lives beside the data directories, since what it holds belongs to the cabinet
|
||||||
|
// rather than to any game.
|
||||||
|
static char *_machineFilePath(void) {
|
||||||
|
return utilCreateString("%s%s", _global.conf->dataDirBase, MACHINE_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void _mapPointer(float px, float py, int32_t *x, int32_t *y) {
|
static void _mapPointer(float px, float py, int32_t *x, int32_t *y) {
|
||||||
float vx = 0;
|
float vx = 0;
|
||||||
float vy = 0;
|
float vy = 0;
|
||||||
|
|
@ -4954,6 +4989,24 @@ static void _pushConstants(lua_State *L) {
|
||||||
lua_setglobal(L, "SINGE_MOUSE_STRIDE");
|
lua_setglobal(L, "SINGE_MOUSE_STRIDE");
|
||||||
lua_pushinteger(L, MAX_CONTROLLERS);
|
lua_pushinteger(L, MAX_CONTROLLERS);
|
||||||
lua_setglobal(L, "SINGE_MAX_CONTROLLERS");
|
lua_setglobal(L, "SINGE_MAX_CONTROLLERS");
|
||||||
|
lua_pushinteger(L, CONTROLLER_AXIS_COUNT);
|
||||||
|
lua_setglobal(L, "SINGE_CONTROLLER_AXES");
|
||||||
|
lua_pushinteger(L, CONTROLLER_BUTTON_COUNT);
|
||||||
|
lua_setglobal(L, "SINGE_CONTROLLER_BUTTONS");
|
||||||
|
|
||||||
|
// What vldpSetScale, vldpSetShift and vldpSetRotate will take, so a service screen clamps to
|
||||||
|
// the engine's limits rather than to its own copy of them: a shift out of range ends the
|
||||||
|
// script, which makes a stale copy fatal rather than merely wrong.
|
||||||
|
lua_pushinteger(L, SCALE_FACTOR_MIN);
|
||||||
|
lua_setglobal(L, "SINGE_SCALE_MIN");
|
||||||
|
lua_pushinteger(L, SCALE_FACTOR_MAX);
|
||||||
|
lua_setglobal(L, "SINGE_SCALE_MAX");
|
||||||
|
lua_pushinteger(L, SHIFT_MIN);
|
||||||
|
lua_setglobal(L, "SINGE_SHIFT_MIN");
|
||||||
|
lua_pushinteger(L, SHIFT_MAX);
|
||||||
|
lua_setglobal(L, "SINGE_SHIFT_MAX");
|
||||||
|
lua_pushinteger(L, ROTATE_STEP);
|
||||||
|
lua_setglobal(L, "SINGE_ROTATE_STEP");
|
||||||
lua_pushinteger(L, MAX_MICE);
|
lua_pushinteger(L, MAX_MICE);
|
||||||
lua_setglobal(L, "SINGE_MAX_MICE");
|
lua_setglobal(L, "SINGE_MAX_MICE");
|
||||||
|
|
||||||
|
|
@ -5114,6 +5167,7 @@ static void _registerApi(lua_State *L) {
|
||||||
lua_register(L, "discGetSubtitleLanguage", apiDiscGetSubtitleLanguage); // 3.00
|
lua_register(L, "discGetSubtitleLanguage", apiDiscGetSubtitleLanguage); // 3.00
|
||||||
lua_register(L, "discGetSubtitleTracks", apiDiscGetSubtitleTracks); // 3.00
|
lua_register(L, "discGetSubtitleTracks", apiDiscGetSubtitleTracks); // 3.00
|
||||||
lua_register(L, "discGetFrame", apiDiscGetFrame); // 1.xx
|
lua_register(L, "discGetFrame", apiDiscGetFrame); // 1.xx
|
||||||
|
lua_register(L, "discGetFrameCount", apiDiscGetFrameCount); // 3.00
|
||||||
lua_register(L, "discGetHeight", apiDiscGetHeight); // 2.00
|
lua_register(L, "discGetHeight", apiDiscGetHeight); // 2.00
|
||||||
lua_register(L, "discGetLanguage", apiDiscGetLanguage); // 2.10
|
lua_register(L, "discGetLanguage", apiDiscGetLanguage); // 2.10
|
||||||
lua_register(L, "discGetState", apiDiscGetState); // 1.xx RDG
|
lua_register(L, "discGetState", apiDiscGetState); // 1.xx RDG
|
||||||
|
|
@ -5419,12 +5473,14 @@ static void _registerApi(lua_State *L) {
|
||||||
lua_register(L, "singeGetAudioLatency", apiSingeGetAudioLatency); // 3.00
|
lua_register(L, "singeGetAudioLatency", apiSingeGetAudioLatency); // 3.00
|
||||||
lua_register(L, "singeGetDataPath", apiSingeGetDataPath); // 2.00
|
lua_register(L, "singeGetDataPath", apiSingeGetDataPath); // 2.00
|
||||||
lua_register(L, "singeGetHeight", apiSingeGetHeight); // 1.xx
|
lua_register(L, "singeGetHeight", apiSingeGetHeight); // 1.xx
|
||||||
|
lua_register(L, "singeGetSystemInfo", apiSingeGetSystemInfo); // 3.00
|
||||||
lua_register(L, "singeGetPauseFlag", apiSingeGetPauseFlag); // 1.xx RDG
|
lua_register(L, "singeGetPauseFlag", apiSingeGetPauseFlag); // 1.xx RDG
|
||||||
lua_register(L, "singeGetScriptPath", apiSingeGetScriptPath); // 1.15 RDG
|
lua_register(L, "singeGetScriptPath", apiSingeGetScriptPath); // 1.15 RDG
|
||||||
lua_register(L, "singeGetTicks", apiSingeGetTicks); // 3.00
|
lua_register(L, "singeGetTicks", apiSingeGetTicks); // 3.00
|
||||||
lua_register(L, "singeGetWidth", apiSingeGetWidth); // 1.xx
|
lua_register(L, "singeGetWidth", apiSingeGetWidth); // 1.xx
|
||||||
lua_register(L, "singeQuit", apiSingeQuit); // 1.xx RDG
|
lua_register(L, "singeQuit", apiSingeQuit); // 1.xx RDG
|
||||||
lua_register(L, "singeReload", apiSingeReload); // 3.00
|
lua_register(L, "singeReload", apiSingeReload); // 3.00
|
||||||
|
lua_register(L, "singeSaveGeometry", apiSingeSaveGeometry); // 3.00
|
||||||
lua_register(L, "singeScreenshot", apiSingeScreenshot); // 1.xx
|
lua_register(L, "singeScreenshot", apiSingeScreenshot); // 1.xx
|
||||||
lua_register(L, "singeSetAudioCalibration", apiSingeSetAudioCalibration); // 3.00
|
lua_register(L, "singeSetAudioCalibration", apiSingeSetAudioCalibration); // 3.00
|
||||||
lua_register(L, "singeSetAudioDelay", apiSingeSetAudioDelay); // 3.00
|
lua_register(L, "singeSetAudioDelay", apiSingeSetAudioDelay); // 3.00
|
||||||
|
|
@ -5554,6 +5610,7 @@ static void _registerApi(lua_State *L) {
|
||||||
lua_register(L, "vldpGetPixel", apiVldpGetPixel); // 1.xx
|
lua_register(L, "vldpGetPixel", apiVldpGetPixel); // 1.xx
|
||||||
lua_register(L, "vldpGetRotate", apiVldpGetRotate); // Hypseus
|
lua_register(L, "vldpGetRotate", apiVldpGetRotate); // Hypseus
|
||||||
lua_register(L, "vldpGetScale", apiVldpGetScale); // Hypseus
|
lua_register(L, "vldpGetScale", apiVldpGetScale); // Hypseus
|
||||||
|
lua_register(L, "vldpGetShift", apiVldpGetShift); // 3.00
|
||||||
lua_register(L, "vldpGetWidth", apiDiscGetWidth); // 1.xx Same as discGetWidth.
|
lua_register(L, "vldpGetWidth", apiDiscGetWidth); // 1.xx Same as discGetWidth.
|
||||||
lua_register(L, "vldpGetYUVPixel", apiVldpGetYUVPixel); // Hypseus
|
lua_register(L, "vldpGetYUVPixel", apiVldpGetYUVPixel); // Hypseus
|
||||||
lua_register(L, "vldpResetFocus", apiVldpResetFocus); // Hypseus
|
lua_register(L, "vldpResetFocus", apiVldpResetFocus); // Hypseus
|
||||||
|
|
@ -5562,6 +5619,7 @@ static void _registerApi(lua_State *L) {
|
||||||
lua_register(L, "vldpSetMonochrome", apiVldpSetMonochrome); // Hypseus
|
lua_register(L, "vldpSetMonochrome", apiVldpSetMonochrome); // Hypseus
|
||||||
lua_register(L, "vldpSetRotate", apiVldpSetRotate); // Hypseus
|
lua_register(L, "vldpSetRotate", apiVldpSetRotate); // Hypseus
|
||||||
lua_register(L, "vldpSetScale", apiVldpSetScale); // Hypseus
|
lua_register(L, "vldpSetScale", apiVldpSetScale); // Hypseus
|
||||||
|
lua_register(L, "vldpSetShift", apiVldpSetShift); // 3.00
|
||||||
lua_register(L, "vldpSetVerbose", apiVldpSetVerbose); // 1.xx
|
lua_register(L, "vldpSetVerbose", apiVldpSetVerbose); // 1.xx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5815,12 +5873,18 @@ static bool _sameLoosePath(const char *a, const char *b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void _saveAudioCalibration(int32_t milliseconds) {
|
static void _saveMachineSettings(int32_t milliseconds) {
|
||||||
char *path = utilCreateString("%s%s", _global.conf->dataDirBase, AUDIO_CALIBRATION_FILE);
|
char *path = _machineFilePath();
|
||||||
FILE *out = fopen(path, "w");
|
FILE *out = fopen(path, "w");
|
||||||
|
|
||||||
|
// Everything the service tools can set for this machine goes in one file, and it is written
|
||||||
|
// whole: the geometry comes from what is live now, which is what the operator just adjusted.
|
||||||
if (out) {
|
if (out) {
|
||||||
fprintf(out, "delay = %d\n", milliseconds);
|
fprintf(out, "delay = %d\n", milliseconds);
|
||||||
|
fprintf(out, "scale = %d\n", _global.videoScale);
|
||||||
|
fprintf(out, "shiftx = %d\n", _global.videoShiftX);
|
||||||
|
fprintf(out, "shifty = %d\n", _global.videoShiftY);
|
||||||
|
fprintf(out, "rotate = %d\n", _global.videoRotate);
|
||||||
fclose(out);
|
fclose(out);
|
||||||
} else {
|
} else {
|
||||||
utilSay("Unable to write %s", path);
|
utilSay("Unable to write %s", path);
|
||||||
|
|
@ -8328,6 +8392,21 @@ static int32_t apiDiscGetSubtitleTracks(lua_State *L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// count = discGetFrameCount() How many frames the disc holds, which is the last frame plus one.
|
||||||
|
// With a frame file it is the whole disc rather than the segment playing.
|
||||||
|
static int32_t apiDiscGetFrameCount(lua_State *L) {
|
||||||
|
int64_t count = 0;
|
||||||
|
|
||||||
|
_argCheck(L, "discGetFrameCount", 0, 0);
|
||||||
|
if (_global.videoHandle >= 0) {
|
||||||
|
count = _global.conf->isFrameFile ? frameFileGetFrameCount(_global.frameFileHandle) : videoGetFrameCount(_global.videoHandle);
|
||||||
|
}
|
||||||
|
lua_pushinteger(L, (lua_Integer)count);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// frame = discGetFrame()
|
// frame = discGetFrame()
|
||||||
static int32_t apiDiscGetFrame(lua_State *L) {
|
static int32_t apiDiscGetFrame(lua_State *L) {
|
||||||
int64_t frame = 0;
|
int64_t frame = 0;
|
||||||
|
|
@ -12533,6 +12612,55 @@ static int32_t apiSingeGetAudioLatency(lua_State *L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// info = singeGetSystemInfo()
|
||||||
|
// What the trace header says about this machine, as a table, so a service screen can put it on
|
||||||
|
// screen and a photograph of it can answer most of a bug report. The same strings, from the same
|
||||||
|
// place, rather than a second description that could drift from the first.
|
||||||
|
static int32_t apiSingeGetSystemInfo(lua_State *L) {
|
||||||
|
char *os = mainDescribeOs();
|
||||||
|
char *cpu = mainDescribeCpu();
|
||||||
|
char *audio = mainDescribeAudioDecoders();
|
||||||
|
int32_t width = 0;
|
||||||
|
int32_t height = 0;
|
||||||
|
|
||||||
|
_argCheck(L, "singeGetSystemInfo", 0, 0);
|
||||||
|
lua_newtable(L);
|
||||||
|
lua_pushstring(L, VERSION_STRING); lua_setfield(L, -2, "version");
|
||||||
|
lua_pushstring(L, (os != NULL) ? os : ""); lua_setfield(L, -2, "os");
|
||||||
|
lua_pushstring(L, (cpu != NULL) ? cpu : ""); lua_setfield(L, -2, "cpu");
|
||||||
|
lua_pushstring(L, SDL_GetRendererName(_global.renderer)); lua_setfield(L, -2, "renderer");
|
||||||
|
lua_pushstring(L, (_global.device != NULL) ? SDL_GetGPUDeviceDriver(_global.device) : "none (3D unavailable)"); lua_setfield(L, -2, "gpu");
|
||||||
|
lua_pushstring(L, videoGetDecoderDescription()); lua_setfield(L, -2, "decoder");
|
||||||
|
lua_pushstring(L, (audio != NULL) ? audio : ""); lua_setfield(L, -2, "audio");
|
||||||
|
lua_pushstring(L, midiSoundfont()); lua_setfield(L, -2, "soundFont");
|
||||||
|
lua_pushstring(L, midiIoDescription()); lua_setfield(L, -2, "midi");
|
||||||
|
lua_pushstring(L, _global.conf->dataDir); lua_setfield(L, -2, "dataPath");
|
||||||
|
lua_pushstring(L, _global.conf->dataDirBase); lua_setfield(L, -2, "dataRoot");
|
||||||
|
lua_pushinteger(L, _global.canvasWidth); lua_setfield(L, -2, "canvasWidth");
|
||||||
|
lua_pushinteger(L, _global.canvasHeight); lua_setfield(L, -2, "canvasHeight");
|
||||||
|
SDL_GetWindowSize(_global.window, &width, &height);
|
||||||
|
lua_pushinteger(L, width); lua_setfield(L, -2, "windowWidth");
|
||||||
|
lua_pushinteger(L, height); lua_setfield(L, -2, "windowHeight");
|
||||||
|
free(os);
|
||||||
|
free(cpu);
|
||||||
|
free(audio);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// singeSaveGeometry() Keeps the picture's scale, shift and rotation for this machine, beside the
|
||||||
|
// audio delay, so an operator sets a monitor up once rather than once per game. The live values
|
||||||
|
// are what is written, which is what the service menu has just been adjusting.
|
||||||
|
static int32_t apiSingeSaveGeometry(lua_State *L) {
|
||||||
|
_argCheck(L, "singeSaveGeometry", 0, 0);
|
||||||
|
_saveMachineSettings(videoGetAudioCalibration());
|
||||||
|
_luaTrace(L, "singeSaveGeometry", "%d %d %d %d", _global.videoScale, _global.videoShiftX, _global.videoShiftY, _global.videoRotate);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// path = singeGetDataPath()
|
// path = singeGetDataPath()
|
||||||
static int32_t apiSingeGetDataPath(lua_State *L) {
|
static int32_t apiSingeGetDataPath(lua_State *L) {
|
||||||
_luaTrace(L, "singeGetDataPath", "%s", _global.conf->dataDir);
|
_luaTrace(L, "singeGetDataPath", "%s", _global.conf->dataDir);
|
||||||
|
|
@ -12632,7 +12760,7 @@ static int32_t apiSingeSetAudioCalibration(lua_State *L) {
|
||||||
_luaDie(L, "singeSetAudioCalibration", "Audio calibration must be between %d and %d milliseconds: %d", -VIDEO_AUDIO_DELAY_MAX, VIDEO_AUDIO_DELAY_MAX, value);
|
_luaDie(L, "singeSetAudioCalibration", "Audio calibration must be between %d and %d milliseconds: %d", -VIDEO_AUDIO_DELAY_MAX, VIDEO_AUDIO_DELAY_MAX, value);
|
||||||
}
|
}
|
||||||
videoSetAudioCalibration(value);
|
videoSetAudioCalibration(value);
|
||||||
_saveAudioCalibration(value);
|
_saveMachineSettings(value);
|
||||||
_luaTrace(L, "singeSetAudioCalibration", "%d", value);
|
_luaTrace(L, "singeSetAudioCalibration", "%d", value);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -14878,6 +15006,39 @@ static int32_t apiVldpSetRotate(lua_State *L) {
|
||||||
// applied = vldpSetScale(percent) Hypseus extension. Shrinks the picture about its centre, 25 to
|
// applied = vldpSetScale(percent) Hypseus extension. Shrinks the picture about its centre, 25 to
|
||||||
// 100 per cent; anything else answers false, as does a second change within 15 milliseconds, which
|
// 100 per cent; anything else answers false, as does a second change within 15 milliseconds, which
|
||||||
// is the rate Hypseus limits its own held-key zoom to.
|
// is the rate Hypseus limits its own held-key zoom to.
|
||||||
|
// x, y = vldpGetShift() Where the picture sits inside the room the scale factor leaves, as
|
||||||
|
// --shiftx and --shifty set it, -100 to 100.
|
||||||
|
static int32_t apiVldpGetShift(lua_State *L) {
|
||||||
|
_argCheck(L, "vldpGetShift", 0, 0);
|
||||||
|
lua_pushinteger(L, _global.videoShiftX);
|
||||||
|
lua_pushinteger(L, _global.videoShiftY);
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// vldpSetShift(x, y) Moves the picture inside that room, live. vldpGetScale and vldpSetScale
|
||||||
|
// decide how much room there is.
|
||||||
|
static int32_t apiVldpSetShift(lua_State *L) {
|
||||||
|
int32_t x = 0;
|
||||||
|
int32_t y = 0;
|
||||||
|
|
||||||
|
_argCheck(L, "vldpSetShift", 2, 2);
|
||||||
|
x = _argInteger(L, "vldpSetShift", 1);
|
||||||
|
y = _argInteger(L, "vldpSetShift", 2);
|
||||||
|
if ((x < SHIFT_MIN) || (x > SHIFT_MAX) || (y < SHIFT_MIN) || (y > SHIFT_MAX)) {
|
||||||
|
_luaDie(L, "vldpSetShift", "A shift is %d to %d per cent: %d, %d", SHIFT_MIN, SHIFT_MAX, x, y);
|
||||||
|
}
|
||||||
|
_global.videoShiftX = x;
|
||||||
|
_global.videoShiftY = y;
|
||||||
|
_computeVideoRect();
|
||||||
|
_global.refreshDisplay = true;
|
||||||
|
_luaTrace(L, "vldpSetShift", "%d %d", x, y);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t apiVldpSetScale(lua_State *L) {
|
static int32_t apiVldpSetScale(lua_State *L) {
|
||||||
int32_t percent = 0;
|
int32_t percent = 0;
|
||||||
uint64_t now = utilTicks();
|
uint64_t now = utilTicks();
|
||||||
|
|
@ -15185,7 +15346,8 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, SDL_GPUDevice *device, Co
|
||||||
// packed game carries none of it inside the .game.
|
// packed game carries none of it inside the .game.
|
||||||
persistOpen(_global.conf->dataDir);
|
persistOpen(_global.conf->dataDir);
|
||||||
videoSetAudioDelay(_global.conf->audioDelayMs);
|
videoSetAudioDelay(_global.conf->audioDelayMs);
|
||||||
videoSetAudioCalibration(_loadAudioCalibration());
|
// What the service tools left this machine set to, over whatever the command line asked for.
|
||||||
|
_loadMachineSettings();
|
||||||
utilTrace("Audio delay: device queue %d ms, calibration %d ms, game %d ms", videoGetAudioLatency(), videoGetAudioCalibration(), videoGetAudioDelay());
|
utilTrace("Audio delay: device queue %d ms, calibration %d ms, game %d ms", videoGetAudioLatency(), videoGetAudioCalibration(), videoGetAudioDelay());
|
||||||
|
|
||||||
_loadControlMappings();
|
_loadControlMappings();
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,10 @@ typedef enum GivenE {
|
||||||
GIVEN_AUDIO_TRACK = 1 << 5,
|
GIVEN_AUDIO_TRACK = 1 << 5,
|
||||||
GIVEN_AUDIO_DELAY = 1 << 6,
|
GIVEN_AUDIO_DELAY = 1 << 6,
|
||||||
GIVEN_CANVAS = 1 << 7,
|
GIVEN_CANVAS = 1 << 7,
|
||||||
GIVEN_AUDIO_SUFFIX = 1 << 8
|
GIVEN_AUDIO_SUFFIX = 1 << 8,
|
||||||
|
GIVEN_SCALE = 1 << 9,
|
||||||
|
GIVEN_SHIFT = 1 << 10, // -X or -Y
|
||||||
|
GIVEN_ROTATE = 1 << 11
|
||||||
} GivenE;
|
} GivenE;
|
||||||
|
|
||||||
typedef struct ConfigS {
|
typedef struct ConfigS {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue