plugins: Moved the timecode recording functionality to a plugin.

This commit is contained in:
Vas Crabb 2021-11-02 15:31:03 +11:00
parent e4c0f2ddac
commit d775a2731d
27 changed files with 474 additions and 345 deletions

View file

@ -1484,22 +1484,6 @@ Core State/Playback Options
you should only record and playback with all configuration (.cfg),
NVRAM (.nv), and memory card files deleted.
.. _mame-commandline-recordtimecode:
**-record_timecode**
Tells MAME to create a timecode file. It contains a line with elapsed times
on each press of timecode shortcut key (default is **F12**). This option
works only when recording mode is enabled (**-record** option). The
timecode file is saved in the ``inp`` folder.
By default, no timecode file is saved.
Example:
.. code-block:: bash
mame pacman -record worldrecord -record_timecode
.. _mame-commandline-mngwrite:
**-mngwrite** *<filename>*

View file

@ -70,4 +70,5 @@ sample code that you can use as a starting point when writing your own plugins.
hiscore
inputmacro
layout
timecode
timer

View file

@ -0,0 +1,21 @@
.. _plugins-timecode:
Timecode Recorder Plugin
========================
The timecode recorder plugin logs time codes to a text file in conjunction with
creating an input recording file to assist people creating gameplay videos. The
time code log file is *only* created when making an input recording. The time
code log file has the same name as the input recording file with the extension
**.timecode** appended. Use the :ref:`record <mame-commandline-record>` and
:ref:`input_directory <mame-commandline-inputdirectory>` options to create an
input recording and specify the location for the output files.
By default, the plugin records a time code when you press the **F12** key on the
keyboard while not pressing either **Shift** key. You can change this setting
in the options menu for the plugin (choose **Plugin Options** from the main menu
during emulation, and then choose **Timecode Recorder**).
Settings for the plugin are stored in JSON format in the file **plugin.cfg** in
the **timecode** folder inside your plugin data folder (see the
:ref:`homepath option <mame-commandline-homepath>`).

View file

@ -1,3 +1,5 @@
.. _plugins-timer:
Timer Plugin
============

View file

@ -63,6 +63,83 @@ Core classes
Many of MAMEs core classes used to implement an emulation session are available
to Lua scripts.
.. _luareference-core-attotime:
Attotime
~~~~~~~~
Wraps MAMEs ``attotime`` class, which represents a high-precision time
interval. Attotime values support addition and subtraction with other attotime
values, and multiplication and division by integers.
Instantiation
^^^^^^^^^^^^^
emu.attotime()
Creates an attotime value representing zero (i.e. no elapsed time).
emu.attotime(seconds, attoseconds)
Creates an attotime with the specified whole and fractional parts.
emu.attotime(attotime)
Creates a copy of an existing attotime value.
emu.attotime.from_double(seconds)
Creates an attotime value representing the specified number of seconds.
emu.attotime.from_ticks(periods, frequency)
Creates an attotime representing the specified number of periods of the
specified frequency in Hertz.
emu.attotime.from_seconds(seconds)
Creates an attotime value representing the specified whole number of
seconds.
emu.attotime.from_msec(milliseconds)
Creates an attotime value representing the specified whole number of
milliseconds.
emu.attotime.from_usec(microseconds)
Creates an attotime value representing the specified whole number of
microseconds.
emu.attotime.from_nsec(nanoseconds)
Creates an attotime value representing the specified whole number of
nanoseconds.
Methods
^^^^^^^
t:as_double()
Returns the time interval in seconds as a floating-point value.
t:as_hz()
Interprets the interval as a period and returns the corresponding frequency
in Hertz as a floating-point value.
t:as_khz()
Interprets the interval as a period and returns the corresponding frequency
kilohertz as a floating-point value.
t:as_mhz()
Interprets the interval as a period and returns the corresponding frequency
megahertz as a floating-point value.
t:as_ticks(frequency)
Returns the interval as a whole number of periods at the specified
frequency. The frequency is specified in Hertz.
Properties
^^^^^^^^^^
t.is_zero (read-only)
Whether the value represents no elapsed time.
t.is_never (read-only)
Whether the value is greater than the maximum number of whole seconds that
can be represented (treated as an unreachable time in the future or
overflow).
t.attoseconds (read-only)
The fraction seconds portion of the interval in attoseconds.
t.seconds (read-only)
The number of whole seconds in the interval.
t.msec (read-only)
The number of whole milliseconds in the fractional seconds portion of the
interval.
t.usec (read-only)
The number of whole microseconds in the fractional seconds portion of the
interval.
t.nsec (read-only)
The number of whole nanoseconds in the fractional seconds portion of the
interval.
.. _luareference-core-mameman:
MAME machine manager
@ -147,6 +224,9 @@ machine:logerror(msg)
Properties
^^^^^^^^^^
machine.time (read-only)
The elapsed emulated time for the current session as an
:ref:`attotime <luareference-core-attotime>`.
machine.system (read-only)
The :ref:`driver metadata <luareference-core-driver>` for the current
system.

View file

@ -58,7 +58,7 @@ function lib:load_settings()
local loaded_settings = json.parse(file:read('a'))
file:close()
if not loaded_settings then
emu.print_error(string.format('Error loading autofire settings: error parsing file "%s" as JSON\n', filename))
emu.print_error(string.format('Error loading autofire settings: error parsing file "%s" as JSON', filename))
return buttons
end
for index, button_settings in ipairs(loaded_settings) do
@ -76,7 +76,7 @@ function lib:save_settings(buttons)
if not attr then
lfs.mkdir(path)
elseif attr.mode ~= 'directory' then
emu.print_error(string.format('Error saving autofire settings: "%s" is not a directory\n', path))
emu.print_error(string.format('Error saving autofire settings: "%s" is not a directory', path))
return
end
if #buttons == 0 then
@ -89,7 +89,7 @@ function lib:save_settings(buttons)
local filename = path .. get_settings_filename()
local file = io.open(filename, 'w')
if not file then
emu.print_error(string.format('Error saving autofire settings: error opening file "%s" for writing\n', filename))
emu.print_error(string.format('Error saving autofire settings: error opening file "%s" for writing', filename))
return
end
file:write(data)

View file

@ -547,7 +547,7 @@ function env.basechar(bytes, base)
end
return newbytes
end
emu.print_verbose("data_hiscore: basechar " .. base .. " unimplemented\n")
emu.print_verbose("data_hiscore: basechar " .. base .. " unimplemented")
return bytes
end
@ -566,7 +566,7 @@ function env.charset_conv(bytes, charset, aoffset)
end
return bytes
end
emu.print_verbose("data_hiscore: charset " .. chartype .. " unimplemented\n")
emu.print_verbose("data_hiscore: charset " .. chartype .. " unimplemented")
return bytes
end
for num, char in ipairs(bytes) do

View file

@ -4,7 +4,7 @@ local db
local function check_db(msg)
if db:errcode() > sql.OK then
emu.print_error(string.format("Error: %s (%s - %s)\n", msg, db:errcode(), db:errmsg()))
emu.print_error(string.format("Error: %s (%s - %s)", msg, db:errcode(), db:errmsg()))
end
end
@ -15,7 +15,7 @@ do
lfs.mkdir(dbpath)
db = sql.open(dbpath .. "/history.db")
if not db then
emu.print_error("Unable to create history.db\n")
emu.print_error("Unable to create history.db")
return false
end
check_db("opening database")

View file

@ -83,7 +83,7 @@ function discord.startplugin()
data = data .. res
until #res == 0 and #data > 0 or time + 1 < os.time()
if #data == 0 then
emu.print_verbose("discord: timed out waiting for response, closing connection\n");
emu.print_verbose("discord: timed out waiting for response, closing connection");
pipe = nil
end
--print(data)

View file

@ -49,7 +49,7 @@ function hiscore.startplugin()
config_read = true
return true
else
emu.print_error(string.format('Error loading hiscore plugin settings: error parsing file "%s" as JSON\n', filename))
emu.print_error(string.format('Error loading hiscore plugin settings: error parsing file "%s" as JSON', filename))
end
end
return false
@ -62,7 +62,7 @@ function hiscore.startplugin()
if not attr then
lfs.mkdir(path)
elseif attr.mode ~= 'directory' then
emu.print_error(string.format('Error saving hiscore plugin settings: "%s" is not a directory\n', path))
emu.print_error(string.format('Error saving hiscore plugin settings: "%s" is not a directory', path))
return
end
local settings = { only_save_at_exit = not timed_save }
@ -72,7 +72,7 @@ function hiscore.startplugin()
local data = json.stringify(settings, { indent = true })
local file = io.open(filename, 'w')
if not file then
emu.print_error(string.format('Error saving hiscore plugin settings: error opening file "%s" for writing\n', filename))
emu.print_error(string.format('Error saving hiscore plugin settings: error opening file "%s" for writing', filename))
return
end
file:write(data)

View file

@ -104,7 +104,7 @@ function lib:load_settings()
local settings = json.parse(file:read('a'))
file:close()
if not settings then
emu.print_error(string.format('Error loading input macros: error parsing file "%s" as JSON\n', filename))
emu.print_error(string.format('Error loading input macros: error parsing file "%s" as JSON', filename))
return { }
end
@ -124,7 +124,7 @@ function lib:save_settings(macros)
if not stat then
lfs.mkdir(path)
elseif stat.mode ~= 'directory' then
emu.print_error(string.format('Error saving input macros: "%s" is not a directory\n', path))
emu.print_error(string.format('Error saving input macros: "%s" is not a directory', path))
return
end
filename = path .. settings_filename()
@ -139,7 +139,7 @@ function lib:save_settings(macros)
local text = json.stringify(settings, { indent = true })
local file = io.open(filename, 'w')
if not file then
emu.print_error(string.format('Error saving input macros: error opening file "%s" for writing\n', filename))
emu.print_error(string.format('Error saving input macros: error opening file "%s" for writing', filename))
return
end
file:write(text)

View file

@ -42,7 +42,7 @@ function portname.startplugin()
local function parse_names(ctable, depth)
if depth >= 5 then
emu.print_error("portname: max import depth exceeded\n")
emu.print_error("portname: max import depth exceeded")
return
end
if ctable.import then
@ -132,12 +132,12 @@ function portname.startplugin()
lfs.mkdir(path)
if not lfs.attributes(path) then
manager.machine:popmessage(_("Failed to save input name file"))
emu.print_verbose("portname: unable to create path " .. path .. "\n")
emu.print_verbose("portname: unable to create path " .. path)
return false
end
elseif attr.mode ~= "directory" then
manager.machine:popmessage(_("Failed to save input name file"))
emu.print_verbose("portname: path exists but isn't directory " .. path .. "\n")
emu.print_verbose("portname: path exists but isn't directory " .. path)
return false
end
return true
@ -151,7 +151,7 @@ function portname.startplugin()
local filename = get_filename()
local file = io.open(ctrlrpath .. "/portname/" .. filename, "r")
if file then
emu.print_verbose("portname: input name file exists " .. filename .. "\n")
emu.print_verbose("portname: input name file exists " .. filename)
manager.machine:popmessage(_("Failed to save input name file"))
file:close()
return false

300
plugins/timecode/init.lua Normal file
View file

@ -0,0 +1,300 @@
-- license:BSD-3-Clause
-- copyright-holders:Vas Crabb
local exports = {
name = 'inputmacro',
version = '0.0.1',
description = 'Timecode recorder plugin',
license = 'BSD-3-Clause',
author = { name = 'Vas Crabb' } }
local timecode = exports
function timecode.startplugin()
local file -- the timecode log file
local enabled -- whether timecode recording is enabled
local write -- whether to record a timecode on the next emulated frame
local text -- name of current part
local start_time -- start time for current part
local total_time -- total time of parts so far this session
local count -- current timecode number
local show_counter -- whether to show elapsed time since last timecode
local show_total -- whether to show the total time of parts
local hotkey_seq -- input sequence to record timecode
local hotkey_pressed -- whether the hotkey was pressed on the last frame update
local hotkey_cfg -- configuration string for the hotkey
local item_hotkey -- menu index of hotkey item
local commonui -- common UI helpers
local hotkey_poller -- helper for configuring hotkey
local function get_settings_path()
return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/timecode/'
end
local function process_frame()
if (not manager.machine.paused) and file and write then
write = false
count = count + 1
show_total = true
-- milliseconds from beginning of playback
local curtime = manager.machine.time
local cursec = curtime.seconds
local msec_start = (cursec * 1000) + curtime.msec
local msec_start_str = string.format('%015d', msec_start)
-- display the timecode
local curtime_str = string.format(
'%02d:%02d:%02d.%03d',
cursec // (60 * 60),
(cursec // 60) % 60,
cursec % 60,
msec_start % 1000)
-- milliseconds from previous timecode
local elapsed = curtime - start_time
local elapsedsec = elapsed.seconds
local msec_elapsed = (elapsedsec * 1000) + elapsed.msec
local msec_elapsed_str = string.format('%015d', msec_elapsed)
-- elapsed from previous timecode
start_time = curtime
local elapsed_str = string.format(
'%02d:%02d:%02d.%03d',
elapsedsec // (60 * 60),
(elapsedsec // 60) % 60,
elapsedsec % 60,
msec_elapsed % 1000)
-- number of frames from beginning of playback
-- TODO: should this account for actual frame rate rather than assuming 60fps?
local frame_start_str = string.format('%015d', msec_start * 60 // 1000)
-- number of frames from previous timecode
-- TODO: should this account for actual frame rate rather than assuming 60fps?
local frame_elapsed_str = string.format('%015d', msec_elapsed * 60 // 1000)
local message
local key
if count == 1 then
text = 'INTRO'
show_counter = true
message = string.format(_p('plugin-timecode', 'TIMECODE: Intro started at %s'), curtime_str)
key = 'INTRO_START'
elseif count == 2 then
total_time = total_time + elapsed
show_counter = false
message = string.format(_p('plugin-timecode', 'TIMECODE: Intro duration %s'), elapsed_str)
key = 'INTRO_STOP'
elseif count == 3 then
text = 'GAMEPLAY'
show_counter = true
message = string.format(_p('plugin-timecode', 'TIMECODE: Gameplay started at %s'), curtime_str)
key = 'GAMEPLAY_START'
elseif count == 4 then
total_time = total_time + elapsed
show_counter = false
message = string.format(_p('plugin-timecode', 'TIMECODE: Gameplay duration %s'), elapsed_str)
key = 'GAMEPLAY_STOP'
elseif (count % 2) == 1 then
local extrano = (count - 3) // 2
text = string.format('EXTRA %d', extrano)
show_counter = true
message = string.format(_p('plugin-timecode', 'TIMECODE: Extra %d started at %s'), extrano, curtime_str)
key = string.format('EXTRA_START_%03d', extrano)
else
local extrano = (count - 4) // 2
total_time = total_time + elapsed
show_counter = false
message = string.format(_p('plugin-timecode', 'TIMECODE: Extra %d duration %s'), extrano, elapsed_str)
key = string.format('EXTRA_STOP_%03d', extrano)
end
emu.print_info(message)
manager.machine:popmessage(message)
file:write(
string.format(
'%-19s %s %s %s %s %s %s\n',
key,
curtime_str, elapsed_str,
msec_start_str, msec_elapsed_str,
frame_start_str, frame_elapsed_str))
end
end
local function process_frame_done()
local machine = manager.machine
if show_counter then
-- show duration of current part
local counter = (machine.time - start_time).seconds
local counter_str = string.format(
machine.paused and _p('plugin-timecode', ' %s%s%02d:%02d [paused] ') or _p('plugin-timecode', ' %s%s%02d:%02d '),
text,
(#text > 0) and ' ' or '',
(counter // 60) % 60,
counter % 60)
machine.render.ui_container:draw_text('right', 0, counter_str, 0xf0f01010, 0xff000000)
end
if show_total then
-- show total time for all parts so far
local total = ((show_counter and (machine.time - start_time) or emu.attotime()) + total_time).seconds
total_str = string.format(_p('plugin-timecode', 'TOTAL %02d:%02d '), (total // 60) % 60, total % 60)
machine.render.ui_container:draw_text('left', 0, total_str, 0xf010f010, 0xff000000)
end
if enabled then
local pressed = machine.input:seq_pressed(hotkey_seq)
if (not hotkey_pressed) and pressed then
write = true
end
hotkey_pressed = pressed
end
end
local function start()
hotkey_seq = manager.machine.input:seq_from_tokens('KEYCODE_F12 NOT KEYCODE_LSHIFT NOT KEYCODE_RSHIFT')
-- try to load configuration
local cfgname = get_settings_path() .. 'plugin.cfg'
local cfgfile = io.open(cfgname, 'r')
if cfgfile then
local json = require('json')
local settings = json.parse(cfgfile:read('a'))
cfgfile:close()
if not settings then
emu.print_error(string.format('Error loading timecode recorder settings: error parsing file "%s" as JSON', cfgname))
else
hotkey_cfg = settings.hotkey
if hotkey_cfg then
local seq = manager.machine.input:seq_from_tokens(hotkey_cfg)
if seq then
hotkey_seq = seq
end
end
end
end
-- only do timecode recording if we're doing input recording
local options = manager.machine.options.entries
local filename = options.record:value()
enabled = #filename > 0
show_counter = false
show_total = false
if enabled then
filename = filename .. '.timecode'
emu.print_info(string.format('Record input timecode file: %s', filename))
file = emu.file(options.input_directory:value(), 0x0e) -- FIXME: magic number for flags
local openerr = file:open(filename)
if openerr then
-- TODO: this used to throw a fatal error and log the error description
emu.print_error('Failed to open file for input timecode recording')
enabled = false
else
write = false
text = ''
start_time = emu.attotime()
total_time = emu.attotime()
count = 0
show_counter = false
show_total = false
hotkey_pressed = false
file:write('# ==========================================\n')
file:write('# TIMECODE FILE FOR VIDEO PREVIEW GENERATION\n')
file:write('# ==========================================\n')
file:write('#\n')
file:write('# VIDEO_PART: code of video timecode\n')
file:write('# START: start time (hh:mm:ss.mmm)\n')
file:write('# ELAPSED: elapsed time (hh:mm:ss.mmm)\n')
file:write('# MSEC_START: start time (milliseconds)\n')
file:write('# MSEC_ELAPSED: elapsed time (milliseconds)\n')
file:write('# FRAME_START: start time (frames)\n')
file:write('# FRAME_ELAPSED: elapsed time (frames)\n')
file:write('#\n')
file:write('# VIDEO_PART======= START======= ELAPSED===== MSEC_START===== MSEC_ELAPSED=== FRAME_START==== FRAME_ELAPSED==\n')
end
end
end
local function stop()
-- close the file if we're recording
if file then
file:close()
file = nil
end
-- try to save settings
local path = get_settings_path()
local attr = lfs.attributes(path)
if not attr then
lfs.mkdir(path)
elseif attr.mode ~= 'directory' then
emu.print_error(string.format('Error saving timecode recorder settings: "%s" is not a directory', path))
return
end
if hotkey_cfg then
local json = require('json')
local settings = { hotkey = hotkey_cfg }
local data = json.stringify(settings, { indent = true })
local cfgname = path .. 'plugin.cfg'
local cfgfile = io.open(cfgname, 'w')
if not cfgfile then
emu.print_error(string.format('Error saving timecode recorder settings: error opening file "%s" for writing', cfgname))
return
end
cfgfile:write(data)
cfgfile:close()
end
end
local function menu_callback(index, event)
if hotkey_poller then
if hotkey_poller:poll() then
if hotkey_poller.sequence then
hotkey_seq = hotkey_poller.sequence
hotkey_cfg = manager.machine.input:seq_to_tokens(hotkey_seq)
end
hotkey_poller = nil
return true
end
elseif (index == item_hotkey) and (event == 'select') then
if not commonui then
commonui = require('commonui')
end
hotkey_poller = commonui.switch_polling_helper()
return true
end
return false
end
local function menu_populate()
local result = { }
table.insert(result, { _p('plugin-timecode', 'Timecode Recorder'), '', 'off' })
table.insert(result, { '---', '', '' })
table.insert(result, { _p('plugin-timecode', 'Hotkey'), manager.machine.input:seq_name(hotkey_seq), hotkey_poller and 'lr' or '' })
item_hotkey = #result
if hotkey_poller then
return hotkey_poller:overlay(result)
else
return result
end
end
emu.register_frame(process_frame)
emu.register_frame_done(process_frame_done)
emu.register_prestart(start)
emu.register_stop(stop)
emu.register_menu(menu_callback, menu_populate, _p('plugin-timecode', 'Timecode Recorder'))
end
return exports

View file

@ -0,0 +1,10 @@
{
"plugin": {
"name": "timecode",
"description": "Timecode recorder plugin",
"version": "0.0.1",
"author": "Vas Crabb",
"type": "plugin",
"start": "false"
}
}

View file

@ -84,7 +84,7 @@ template <typename T> inline constexpr attoseconds_t ATTOSECONDS_IN_NSEC(T &&x)
//**************************************************************************
// TYPE DEFINITIONS
//***************************************************************************/
//**************************************************************************
// the attotime structure itself
class attotime

View file

@ -69,7 +69,6 @@ const options_entry emu_options::s_option_entries[] =
{ OPTION_REWIND_CAPACITY "(1-2048)", "100", OPTION_INTEGER, "rewind buffer size in megabytes" },
{ OPTION_PLAYBACK ";pb", nullptr, OPTION_STRING, "playback an input file" },
{ OPTION_RECORD ";rec", nullptr, OPTION_STRING, "record an input file" },
{ OPTION_RECORD_TIMECODE, "0", OPTION_BOOLEAN, "record an input timecode file (requires -record option)" },
{ OPTION_EXIT_AFTER_PLAYBACK, "0", OPTION_BOOLEAN, "close the program at the end of playback" },
{ OPTION_MNGWRITE, nullptr, OPTION_STRING, "optional filename to write a MNG movie of the current session" },

View file

@ -56,7 +56,6 @@
#define OPTION_REWIND_CAPACITY "rewind_capacity"
#define OPTION_PLAYBACK "playback"
#define OPTION_RECORD "record"
#define OPTION_RECORD_TIMECODE "record_timecode"
#define OPTION_EXIT_AFTER_PLAYBACK "exit_after_playback"
#define OPTION_MNGWRITE "mngwrite"
#define OPTION_AVIWRITE "aviwrite"
@ -342,7 +341,6 @@ public:
int rewind_capacity() const { return int_value(OPTION_REWIND_CAPACITY); }
const char *playback() const { return value(OPTION_PLAYBACK); }
const char *record() const { return value(OPTION_RECORD); }
bool record_timecode() const { return bool_value(OPTION_RECORD_TIMECODE); }
bool exit_after_playback() const { return bool_value(OPTION_EXIT_AFTER_PLAYBACK); }
const char *mng_write() const { return value(OPTION_MNGWRITE); }
const char *avi_write() const { return value(OPTION_AVIWRITE); }

View file

@ -820,7 +820,6 @@ namespace {
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_FAST_FORWARD, N_p("input-name", "Fast Forward"), input_seq(KEYCODE_INSERT) ) \
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SHOW_FPS, N_p("input-name", "Show FPS"), input_seq(KEYCODE_F11, input_seq::not_code, KEYCODE_LSHIFT) ) \
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SNAPSHOT, N_p("input-name", "Save Snapshot"), input_seq(KEYCODE_F12, input_seq::not_code, KEYCODE_LSHIFT) ) \
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_TIMECODE, N_p("input-name", "Write current timecode"), input_seq(KEYCODE_F12, input_seq::not_code, KEYCODE_LSHIFT) ) \
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_RECORD_MNG, N_p("input-name", "Record MNG"), input_seq(KEYCODE_F12, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_LCONTROL) ) \
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_RECORD_AVI, N_p("input-name", "Record AVI"), input_seq(KEYCODE_F12, KEYCODE_LSHIFT, KEYCODE_LCONTROL) ) \
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_TOGGLE_CHEAT, N_p("input-name", "Toggle Cheat"), input_seq(KEYCODE_F6) ) \

View file

@ -1656,9 +1656,6 @@ ioport_manager::ioport_manager(running_machine &machine)
, m_playback_file(machine.options().input_directory(), OPEN_FLAG_READ)
, m_playback_accumulated_speed(0)
, m_playback_accumulated_frames(0)
, m_timecode_file(machine.options().input_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS)
, m_timecode_count(0)
, m_timecode_last_time(attotime::zero)
, m_deselected_card_config()
{
for (auto &entries : m_type_to_entry)
@ -1746,7 +1743,6 @@ time_t ioport_manager::initialize()
// open playback and record files if specified
time_t basetime = playback_init();
record_init();
timecode_init();
return basetime;
}
@ -1828,7 +1824,6 @@ void ioport_manager::exit()
// close any playback or recording files
playback_end();
record_end();
timecode_end();
}
@ -2855,29 +2850,6 @@ void ioport_manager::record_write<bool>(bool value)
record_write(byte);
}
template<typename Type>
void ioport_manager::timecode_write(Type value)
{
// protect against nullptr handles if previous reads fail
if (!m_timecode_file.is_open())
return;
// read the value; if we fail, end playback
if (m_timecode_file.write(&value, sizeof(value)) != sizeof(value))
timecode_end("Out of space");
}
/*template<>
void ioport_manager::timecode_write<bool>(bool value)
{
u8 byte = u8(value);
timecode_write(byte);
}*/
template<>
void ioport_manager::timecode_write<std::string>(std::string value) {
timecode_write(value.c_str());
}
//-------------------------------------------------
// record_init - initialize INP recording
@ -2915,48 +2887,6 @@ void ioport_manager::record_init()
}
void ioport_manager::timecode_init()
{
// check if option -record_timecode is enabled
if (!machine().options().record_timecode())
{
machine().video().set_timecode_enabled(false);
return;
}
// if no file, nothing to do
const char *record_filename = machine().options().record();
if (record_filename[0] == 0)
{
machine().video().set_timecode_enabled(false);
return;
}
machine().video().set_timecode_enabled(true);
// open the record file
std::string filename;
filename.append(record_filename).append(".timecode");
osd_printf_info("Record input timecode file: %s\n", record_filename);
std::error_condition const filerr = m_timecode_file.open(filename);
if (filerr)
throw emu_fatalerror("ioport_manager::timecode_init: Failed to open file for input timecode recording (%s:%d %s)", filerr.category().name(), filerr.value(), filerr.message());
m_timecode_file.puts("# ==========================================\n");
m_timecode_file.puts("# TIMECODE FILE FOR VIDEO PREVIEW GENERATION\n");
m_timecode_file.puts("# ==========================================\n");
m_timecode_file.puts("#\n");
m_timecode_file.puts("# VIDEO_PART: code of video timecode\n");
m_timecode_file.puts("# START: start time (hh:mm:ss.mmm)\n");
m_timecode_file.puts("# ELAPSED: elapsed time (hh:mm:ss.mmm)\n");
m_timecode_file.puts("# MSEC_START: start time (milliseconds)\n");
m_timecode_file.puts("# MSEC_ELAPSED: elapsed time (milliseconds)\n");
m_timecode_file.puts("# FRAME_START: start time (frames)\n");
m_timecode_file.puts("# FRAME_ELAPSED: elapsed time (frames)\n");
m_timecode_file.puts("#\n");
m_timecode_file.puts("# VIDEO_PART======= START======= ELAPSED===== MSEC_START===== MSEC_ELAPSED=== FRAME_START==== FRAME_ELAPSED==\n");
}
//-------------------------------------------------
// record_end - end INP recording
//-------------------------------------------------
@ -2977,19 +2907,6 @@ void ioport_manager::record_end(const char *message)
}
void ioport_manager::timecode_end(const char *message)
{
// only applies if we have a live file
if (m_timecode_file.is_open()) {
// close the file
m_timecode_file.close();
// pop a message
if (message != nullptr)
machine().popmessage("Recording Timecode Ended\nReason: %s", message);
}
}
//-------------------------------------------------
// record_frame - start of frame callback for
// recording
@ -3007,97 +2924,6 @@ void ioport_manager::record_frame(const attotime &curtime)
// then the current speed
record_write(u32(machine().video().speed_percent() * double(1 << 20)));
}
if (m_timecode_file.is_open() && machine().video().get_timecode_write())
{
// Display the timecode
m_timecode_count++;
std::string const current_time_str = string_format("%02d:%02d:%02d.%03d",
(int)curtime.seconds() / (60 * 60),
(curtime.seconds() / 60) % 60,
curtime.seconds() % 60,
(int)(curtime.attoseconds()/ATTOSECONDS_PER_MILLISECOND));
// Elapsed from previous timecode
attotime const elapsed_time = curtime - m_timecode_last_time;
m_timecode_last_time = curtime;
std::string const elapsed_time_str = string_format("%02d:%02d:%02d.%03d",
elapsed_time.seconds() / (60 * 60),
(elapsed_time.seconds() / 60) % 60,
elapsed_time.seconds() % 60,
int(elapsed_time.attoseconds()/ATTOSECONDS_PER_MILLISECOND));
// Number of ms from beginning of playback
int const mseconds_start = curtime.seconds()*1000 + curtime.attoseconds()/ATTOSECONDS_PER_MILLISECOND;
std::string const mseconds_start_str = string_format("%015d", mseconds_start);
// Number of ms from previous timecode
int mseconds_elapsed = elapsed_time.seconds()*1000 + elapsed_time.attoseconds()/ATTOSECONDS_PER_MILLISECOND;
std::string const mseconds_elapsed_str = string_format("%015d", mseconds_elapsed);
// Number of frames from beginning of playback
int const frame_start = mseconds_start * 60 / 1000;
std::string const frame_start_str = string_format("%015d", frame_start);
// Number of frames from previous timecode
int frame_elapsed = mseconds_elapsed * 60 / 1000;
std::string const frame_elapsed_str = string_format("%015d", frame_elapsed);
std::string message;
std::string timecode_text;
std::string timecode_key;
bool show_timecode_counter = false;
if (m_timecode_count==1) {
message = string_format("TIMECODE: Intro started at %s", current_time_str);
timecode_key = "INTRO_START";
timecode_text = "INTRO";
show_timecode_counter = true;
}
else if (m_timecode_count==2) {
machine().video().add_to_total_time(elapsed_time);
message = string_format("TIMECODE: Intro duration %s", elapsed_time_str);
timecode_key = "INTRO_STOP";
//timecode_text = "INTRO";
}
else if (m_timecode_count==3) {
message = string_format("TIMECODE: Gameplay started at %s", current_time_str);
timecode_key = "GAMEPLAY_START";
timecode_text = "GAMEPLAY";
show_timecode_counter = true;
}
else if (m_timecode_count==4) {
machine().video().add_to_total_time(elapsed_time);
message = string_format("TIMECODE: Gameplay duration %s", elapsed_time_str);
timecode_key = "GAMEPLAY_STOP";
//timecode_text = "GAMEPLAY";
}
else if (m_timecode_count % 2 == 1) {
message = string_format("TIMECODE: Extra %d started at %s", (m_timecode_count-3)/2, current_time_str);
timecode_key = string_format("EXTRA_START_%03d", (m_timecode_count-3)/2);
timecode_text = string_format("EXTRA %d", (m_timecode_count-3)/2);
show_timecode_counter = true;
}
else {
machine().video().add_to_total_time(elapsed_time);
message = string_format("TIMECODE: Extra %d duration %s", (m_timecode_count-4)/2, elapsed_time_str);
timecode_key = string_format("EXTRA_STOP_%03d", (m_timecode_count-4)/2);
}
osd_printf_info("%s \n", message);
machine().popmessage("%s \n", message);
m_timecode_file.printf(
"%-19s %s %s %s %s %s %s\n",
timecode_key,
current_time_str, elapsed_time_str,
mseconds_start_str, mseconds_elapsed_str,
frame_start_str, frame_elapsed_str);
machine().video().set_timecode_write(false);
machine().video().set_timecode_text(timecode_text);
machine().video().set_timecode_start(m_timecode_last_time);
machine().ui().set_show_timecode_counter(show_timecode_counter);
}
}

View file

@ -337,7 +337,6 @@ enum ioport_type
IPT_UI_FAST_FORWARD,
IPT_UI_SHOW_FPS,
IPT_UI_SNAPSHOT,
IPT_UI_TIMECODE,
IPT_UI_RECORD_MNG,
IPT_UI_RECORD_AVI,
IPT_UI_TOGGLE_CHEAT,
@ -1428,10 +1427,6 @@ private:
void record_frame(const attotime &curtime);
void record_port(ioport_port &port);
template<typename Type> void timecode_write(Type value);
void timecode_init();
void timecode_end(const char *message = nullptr);
// internal state
running_machine & m_machine; // reference to owning machine
bool m_safe_to_read; // clear at start; set after state is loaded
@ -1455,9 +1450,6 @@ private:
util::read_stream::ptr m_playback_stream; // playback stream (nullptr if not recording)
u64 m_playback_accumulated_speed; // accumulated speed during playback
u32 m_playback_accumulated_frames; // accumulated frames during playback
emu_file m_timecode_file; // timecode/frames playback file (nullptr if not recording)
int m_timecode_count;
attotime m_timecode_last_time;
// storage for inactive configuration
std::unique_ptr<util::xml::file> m_deselected_card_config;

View file

@ -22,7 +22,7 @@ class ui_manager
{
public:
// construction/destruction
ui_manager(running_machine &machine) : m_machine(machine),m_show_timecode_counter(false),m_show_timecode_total(false) { }
ui_manager(running_machine &machine) : m_machine(machine) { }
virtual ~ui_manager() { }
@ -31,11 +31,6 @@ public:
// is a menuing system active? we want to disable certain keyboard/mouse inputs under such context
virtual bool is_menu_active() { return false; }
void set_show_timecode_counter(bool value) { m_show_timecode_counter = value; m_show_timecode_total = true; }
bool show_timecode_counter() const { return m_show_timecode_counter; }
bool show_timecode_total() const { return m_show_timecode_total; }
virtual void popup_time_string(int seconds, std::string message) { }
virtual void menu_reset() { }
@ -45,8 +40,6 @@ public:
protected:
// instance variables
running_machine & m_machine;
bool m_show_timecode_counter;
bool m_show_timecode_total;
};
/***************************************************************************

View file

@ -103,11 +103,6 @@ video_manager::video_manager(running_machine &machine)
, m_snap_native(true)
, m_snap_width(0)
, m_snap_height(0)
, m_timecode_enabled(false)
, m_timecode_write(false)
, m_timecode_text("")
, m_timecode_start(attotime::zero)
, m_timecode_total(attotime::zero)
{
// request a callback upon exiting
machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&video_manager::exit, this));
@ -376,45 +371,6 @@ void video_manager::save_active_screen_snapshots()
}
//-------------------------------------------------
// save_input_timecode - add a line of current
// timestamp to inp.timecode file
//-------------------------------------------------
void video_manager::save_input_timecode()
{
// if record timecode input is not active, do nothing
if (!m_timecode_enabled) {
return;
}
m_timecode_write = true;
}
std::string &video_manager::timecode_text(std::string &str)
{
attotime elapsed_time = machine().time() - m_timecode_start;
str = string_format(" %s%s%02d:%02d %s",
m_timecode_text,
m_timecode_text.empty() ? "" : " ",
(elapsed_time.m_seconds / 60) % 60,
elapsed_time.m_seconds % 60,
machine().paused() ? "[paused] " : "");
return str;
}
std::string &video_manager::timecode_total_text(std::string &str)
{
attotime elapsed_time = m_timecode_total;
if (machine().ui().show_timecode_counter()) {
elapsed_time += machine().time() - m_timecode_start;
}
str = string_format("TOTAL %02d:%02d ",
(elapsed_time.m_seconds / 60) % 60,
elapsed_time.m_seconds % 60);
return str;
}
//-------------------------------------------------
// begin_recording_screen - begin recording a
// movie for a specific screen

View file

@ -80,7 +80,6 @@ public:
render_target &snapshot_target() { return *m_snap_target; }
void save_snapshot(screen_device *screen, emu_file &file);
void save_active_screen_snapshots();
void save_input_timecode();
// movies
void begin_recording(const char *name, movie_recording::format format);
@ -88,16 +87,6 @@ public:
void add_sound_to_recording(const s16 *sound, int numsamples);
bool is_recording() const { return !m_movie_recordings.empty(); }
void set_timecode_enabled(bool value) { m_timecode_enabled = value; }
bool get_timecode_enabled() { return m_timecode_enabled; }
bool get_timecode_write() { return m_timecode_write; }
void set_timecode_write(bool value) { m_timecode_write = value; }
void set_timecode_text(std::string &str) { m_timecode_text = str; }
void set_timecode_start(attotime time) { m_timecode_start = time; }
void add_to_total_time(attotime time) { m_timecode_total += time; }
std::string &timecode_text(std::string &str);
std::string &timecode_total_text(std::string &str);
private:
// internal helpers
void exit();
@ -180,12 +169,6 @@ private:
static const attoseconds_t ATTOSECONDS_PER_SPEED_UPDATE = ATTOSECONDS_PER_SECOND / 4;
static const int PAUSED_REFRESH_RATE = 30;
bool m_timecode_enabled; // inp.timecode record enabled
bool m_timecode_write; // Show/hide timer at right (partial time)
std::string m_timecode_text; // Message for that video part (intro, gameplay, extra)
attotime m_timecode_start; // Starting timer for that video part (intro, gameplay, extra)
attotime m_timecode_total; // Show/hide timer at left (total elapsed on resulting video preview)
};
#endif // MAME_EMU_VIDEO_H

View file

@ -740,6 +740,34 @@ void lua_engine::initialize()
[] (device_t &dev, int maxdepth) { return devenum<slot_interface_enumerator>(dev, maxdepth); });
auto attotime_type = emu.new_usertype<attotime>(
"attotime",
sol::call_constructor, sol::constructors<attotime(), attotime(seconds_t, attoseconds_t), attotime(attotime const &)>());
attotime_type["from_double"] = &attotime::from_double;
attotime_type["from_ticks"] = static_cast<attotime (*)(u64, u32)>(&attotime::from_ticks);
attotime_type["from_seconds"] = &attotime::from_seconds;
attotime_type["from_msec"] = &attotime::from_msec;
attotime_type["from_usec"] = &attotime::from_usec;
attotime_type["from_nsec"] = &attotime::from_nsec;
attotime_type["as_double"] = &attotime::as_double;
attotime_type["as_hz"] = &attotime::as_hz;
attotime_type["as_khz"] = &attotime::as_khz;
attotime_type["as_mhz"] = &attotime::as_mhz;
attotime_type["as_ticks"] = static_cast<u64 (attotime::*)(u32) const>(&attotime::as_ticks);
attotime_type["is_zero"] = sol::property(&attotime::is_zero);
attotime_type["is_never"] = sol::property(&attotime::is_never);
attotime_type["attoseconds"] = sol::property(&attotime::attoseconds);
attotime_type["seconds"] = sol::property(&attotime::seconds);
attotime_type["msec"] = sol::property([] (attotime const &t) { return t.attoseconds() / ATTOSECONDS_PER_MILLISECOND; });
attotime_type["usec"] = sol::property([] (attotime const &t) { return t.attoseconds() / ATTOSECONDS_PER_MICROSECOND; });
attotime_type["nsec"] = sol::property([] (attotime const &t) { return t.attoseconds() / ATTOSECONDS_PER_NANOSECOND; });
attotime_type[sol::meta_function::to_string] = &attotime::to_string;
attotime_type[sol::meta_function::addition] = static_cast<attotime (*)(attotime const &, attotime const &)>(&operator+);
attotime_type[sol::meta_function::subtraction] = static_cast<attotime (*)(attotime const &, attotime const &)>(&operator-);
attotime_type[sol::meta_function::multiplication] = static_cast<attotime (*)(attotime const &, u32)>(&operator*);
attotime_type[sol::meta_function::division] = static_cast<attotime (*)(attotime const &, u32)>(&operator/);
/* emu_file library
*
* emu.file([opt] searchpath, flags) - flags can be as in osdcore "OPEN_FLAG_*" or lua style
@ -801,8 +829,10 @@ void lua_engine::initialize()
}));
file_type.set("read", [](emu_file &file, sol::buffer *buff) { buff->set_len(file.read(buff->get_ptr(), buff->get_len())); return buff; });
file_type.set("write", [](emu_file &file, const std::string &data) { return file.write(data.data(), data.size()); });
file_type.set("puts", &emu_file::puts);
file_type.set("open", static_cast<std::error_condition (emu_file::*)(std::string_view)>(&emu_file::open));
file_type.set("open_next", &emu_file::open_next);
file_type.set("close", &emu_file::close);
file_type.set("seek", sol::overload(
[](emu_file &file) { return file.tell(); },
[this] (emu_file &file, s64 offset, int whence) -> sol::object {
@ -1198,17 +1228,18 @@ void lua_engine::initialize()
m.popmessage();
};
machine_type["logerror"] = [] (running_machine &m, std::string const *str) { m.logerror("[luaengine] %s\n", str); };
machine_type["time"] = sol::property(&running_machine::time);
machine_type["system"] = sol::property(&running_machine::system);
machine_type["parameters"] = sol::property(&running_machine::parameters);
machine_type["video"] = sol::property(&running_machine::video);
machine_type["sound"] = sol::property(&running_machine::sound);
machine_type["render"] = sol::property(&running_machine::render);
machine_type["ioport"] = sol::property(&running_machine::ioport);
machine_type["parameters"] = sol::property(&running_machine::parameters);
machine_type["memory"] = sol::property(&running_machine::memory);
machine_type["options"] = sol::property(&running_machine::options);
machine_type["output"] = sol::property(&running_machine::output);
machine_type["memory"] = sol::property(&running_machine::memory);
machine_type["ioport"] = sol::property(&running_machine::ioport);
machine_type["input"] = sol::property(&running_machine::input);
machine_type["natkeyboard"] = sol::property(&running_machine::natkeyboard);
machine_type["uiinput"] = sol::property(&running_machine::ui_input);
machine_type["render"] = sol::property(&running_machine::render);
machine_type["debugger"] = sol::property(
[] (running_machine &m, sol::this_state s) -> sol::object
{
@ -1217,9 +1248,9 @@ void lua_engine::initialize()
else
return sol::lua_nil;
});
machine_type["natkeyboard"] = sol::property(&running_machine::natkeyboard);
machine_type["paused"] = sol::property(&running_machine::paused);
machine_type["options"] = sol::property(&running_machine::options);
machine_type["samplerate"] = sol::property(&running_machine::sample_rate);
machine_type["paused"] = sol::property(&running_machine::paused);
machine_type["exit_pending"] = sol::property(&running_machine::exit_pending);
machine_type["hard_reset_pending"] = sol::property(&running_machine::hard_reset_pending);
machine_type["devices"] = sol::property([] (running_machine &m) { return devenum<device_enumerator>(m.root_device()); });
@ -1561,7 +1592,7 @@ void lua_engine::initialize()
cass_type["length"] = sol::property([] (cassette_image_device &c) { return c.exists() ? c.get_length() : 0.0; });
auto image_type = sol().registry().new_usertype<device_image_interface>("image", "new", sol::no_constructor);
auto image_type = sol().registry().new_usertype<device_image_interface>("image", sol::no_constructor);
image_type["load"] = &device_image_interface::load;
image_type["load_software"] = static_cast<image_init_result (device_image_interface::*)(std::string_view)>(&device_image_interface::load_software);
image_type["unload"] = &device_image_interface::unload;

View file

@ -203,7 +203,7 @@ void lua_engine::initialize_input(sol::table &emu)
});
auto ioport_port_type = sol().registry().new_usertype<ioport_port>("ioport_port", "new", sol::no_constructor);
auto ioport_port_type = sol().registry().new_usertype<ioport_port>("ioport_port", sol::no_constructor);
ioport_port_type["read"] = &ioport_port::read;
ioport_port_type["write"] = &ioport_port::write;
ioport_port_type["field"] = &ioport_port::field;

View file

@ -1132,38 +1132,6 @@ void mame_ui_manager::draw_fps_counter(render_container &container)
}
//-------------------------------------------------
// draw_timecode_counter
//-------------------------------------------------
void mame_ui_manager::draw_timecode_counter(render_container &container)
{
std::string tempstring;
draw_text_full(
container,
machine().video().timecode_text(tempstring),
0.0f, 0.0f, 1.0f,
ui::text_layout::text_justify::RIGHT, ui::text_layout::word_wrapping::WORD,
OPAQUE_, rgb_t(0xf0, 0xf0, 0x10, 0x10), rgb_t::black(), nullptr, nullptr);
}
//-------------------------------------------------
// draw_timecode_total
//-------------------------------------------------
void mame_ui_manager::draw_timecode_total(render_container &container)
{
std::string tempstring;
draw_text_full(
container,
machine().video().timecode_total_text(tempstring),
0.0f, 0.0f, 1.0f,
ui::text_layout::text_justify::LEFT, ui::text_layout::word_wrapping::WORD,
OPAQUE_, rgb_t(0xf0, 0x10, 0xf0, 0x10), rgb_t::black(), nullptr, nullptr);
}
//-------------------------------------------------
// draw_profiler
//-------------------------------------------------
@ -1248,14 +1216,6 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container)
if (show_fps_counter())
draw_fps_counter(container);
// Show the duration of current part (intro or gameplay or extra)
if (show_timecode_counter())
draw_timecode_counter(container);
// Show the total time elapsed for the video preview (all parts intro, gameplay, extras)
if (show_timecode_total())
draw_timecode_total(container);
// draw the profiler if visible
if (show_profiler())
draw_profiler(container);
@ -1302,10 +1262,6 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container)
image_handler_ingame();
// handle a save input timecode request
if (machine().ui_input().pressed(IPT_UI_TIMECODE))
machine().video().save_input_timecode();
if (ui_disabled)
return ui_disabled;

View file

@ -186,8 +186,6 @@ public:
void decrease_frameskip();
void request_quit();
void draw_fps_counter(render_container &container);
void draw_timecode_counter(render_container &container);
void draw_timecode_total(render_container &container);
void draw_profiler(render_container &container);
void start_save_state();
void start_load_state();