added sound to Time Machine playback

fixed playback speed
updated docs
This commit is contained in:
thrust26 2020-12-06 12:08:25 +01:00
parent d38593a821
commit ee8734ce14
10 changed files with 128 additions and 74 deletions

View File

@ -28,6 +28,8 @@
* Added dynamic tooltips to most debugger items.
* Added sound to Time Machine playback.
* Increased sample size for CDFJ+.
* Fixed autofire bug for trackball controllers.

View File

@ -1828,8 +1828,8 @@
</tr>
<tr>
<td>Playback the <a href="#TimeMachine"><b>Time Machine</b></a> from current state (without sound, from the TM dialog only)</td>
<td>Shift</td>
<td>Shift</td>
<td>Space</td>
<td>Space</td>
</tr>
<tr>
<td>Start/Stop playback (exist/enters the <a href="#TimeMachine"><b>Time Machine</b></a> dialog)</td>
@ -2259,10 +2259,10 @@
<tr><td>Current time</td><td>Shows the time of the currently selected state,
relative to the first one</td></tr>
<tr><td>'Start/Stop' button</td><td>Starts or stops the Time Machine</td></tr>
<tr><td>'Exit' button</td><td>Exits the dialog and continues emulation, starting at current state</td></tr>
<tr><td>'Exit' button</td><td>Exits the dialog and continues emulation from the current state</td></tr>
<tr><td>'Rewind All' button</td><td>Navigates back to the begin of the timeline</td></tr>
<tr><td>'Rewind One' button</td><td>Navigates back by one state</td></tr>
<tr><td>'Playback' button</td><td>Starts playback, starting at the current state (with sound disabled)</td></tr>
<tr><td>'Playback' button</td><td>Starts playback from the current state</td></tr>
<tr><td>'Unwind One' button</td><td>Navigates forward by one state</td></tr>
<tr><td>'Unwind All' button</td><td>Navigates forward to the end of the timeline</td></tr>
<tr><td>'Save All' button</td><td>Saves all Time Machine states to disk</td></tr>
@ -2288,43 +2288,50 @@
<a href="#HighScoreGames">High Scores - supported games</a></b>) this has
been done already.</p>
<p>To save a score, the High Score dialog can be opened by pressing 'Insert'
any time while a game is played. It will provide the current variation and
score and allow the user to add this as a new high score. Of course this
makes most sense when a game is over. Note: In multiplayer games, only the
score of the first player can be saved.</p>
<p><b>High Scores</b> dialog:</p>
<td><img src="graphics/highscores.png"></td>
<table border="5" cellpadding="2" frame="box" rules="none">
<tr>
<td><img src="graphics/highscores.png"></td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td valign="top">
<table border="1" cellpadding="4">
<tr><th>Item</th><th>Description</th></tr>
<tr><td>Top row</td><td>Displays the current game's name.</td></tr>
<tr><td>Variation</td><td>By default the current game's variation is
selected. By changing the variation, the high scores of other
variations can be reviewed.</td></tr>
<tr><td>High scores table</td><td>This table displays up to ten high scores
for the current game variation.
<ul>
<li>Besides 'Rank' and 'Score' an optional special value (e.g.
'Level', 'Wave' or 'Round') is displayed.</li>
<li>In the 'Name' column, the player's initials are displayed.
These can be entered when a new high score is added to the list.</li>
<li>'Date' and 'Time' record when the high score was added.</li>
<li>The buttons at the right allow deleting individual high
scores from the list.</li>
</ul>
</td></tr>
<tr><td>MD5/Props</td><td>Display the checksums of the current ROM and the
high score properties defined for it. This can be useful for
comparing and verifying high scores.</td></tr>
<tr><td>Reset</td><td>Resets all high scores of the currently selected
variation.</td></tr>
<tr><td>Save</td><td>Saves the updated high scores and closes the dialog.
</td></tr>
<tr><td>Cancel</td><td>Closes the dialog without saving.</td></tr>
</table>
<p>This dialog can be opened by pressing 'Insert' any time while a game is
played. It will provide the current variation and score and allow the user
to add this as a new high score. Of course this makes most sense when a game
is over. Note: In multiplayer games, only the score of the first player can be
saved.</p>
<p>The dialog items are explained in the following two tables.</p>
<table border="1" cellpadding="4">
<tr><th>Item</th><th>Description</th></tr>
<tr><td>Top row</td><td>Displays the current game's name.</td></tr>
<tr><td>Variation</td><td>By default the current game's variation is
selected. By changing the variation, the high scores of other
variations can be reviewed.</td></tr>
<tr><td>High scores table</td><td>This table displays up to ten high scores
for the current game variation.
<ul>
<li>Besides 'Rank' and 'Score' an optional special value (e.g.
'Level', 'Wave' or 'Round') is displayed.</li>
<li>In the 'Name' column, the player's initials are displayed.
These can be entered when a new high score is added to the list.</li>
<li>'Date' and 'Time' record when the high score was added.</li>
<li>The buttons at the right allow deleting individual high
scores from the list.</li>
</ul>
</td></tr>
<tr><td>MD5/Props</td><td>Display the checksums of the current ROM and the
high score properties defined for it. This can be useful for
comparing and verifying high scores.</td></tr>
<tr><td>Reset</td><td>Resets all high scores of the currently selected
variation.</td></tr>
<tr><td>Save</td><td>Saves the updated high scores and closes the dialog.
</td></tr>
<tr><td>Cancel</td><td>Closes the dialog without saving.</td></tr>
</td>
</tr>
</table>
<p>For details how to configure high scores definintions for a game see
<a href="#HighScoreProps"><b>High Scores Properties</b></a></h2>.</p>
</blockquote></br>
@ -3511,7 +3518,7 @@
<p><b>User Interface Settings</b> dialog (2 tabs):</p>
<table border="5" cellpadding="2" frame="box" rules="none">
<tr>
<td><img src="graphics/options_misc.png"><br><br></td>
<td><img src="graphics/options_misc.png"></td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td valign="top">
<table border="1" cellpadding="4">
@ -3530,12 +3537,13 @@
</td>
</tr>
<tr>
<td><img src="graphics/options_misc_classic.png"></td>
<td><img src="graphics/options_misc_dark.png"></td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td><img src="graphics/options_misc_light.png"></td>
<td><img src="graphics/options_misc_classic.png"></td>
</tr>
<tr>
<td><img src="graphics/options_misc_dark.png"></td>
<td colspan="3"><center><img src="graphics/options_misc_light.png"></center></td>
</tr>
</table>
<br>

View File

@ -422,33 +422,28 @@ string HighScoresManager::md5Props() const
bool HighScoresManager::scoreInvert() const
{
json jprops;
return scoreInvert(properties(jprops));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int32 HighScoresManager::special() const
{
if(!myOSystem.hasConsole())
return NO_VALUE;
json jprops;
uInt16 addr = specialAddress(properties(jprops));
if (addr == DEFAULT_ADDRESS)
return NO_VALUE;
return special(addr, specialBCD(jprops), specialZeroBased(jprops));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int32 HighScoresManager::special(uInt16 addr, bool varBCD, bool zeroBased) const
{
if (!myOSystem.hasConsole())
return NO_VALUE;
Int32 var = peek(addr);
if (varBCD)
if(specialBCD(jprops))
var = fromBCD(var);
var += zeroBased ? 1 : 0;
var += specialZeroBased(jprops) ? 1 : 0;
return var;
}

View File

@ -131,9 +131,6 @@ class HighScoresManager
*/
uInt32 numAddrBytes(Int32 digits, Int32 trailing) const;
Int32 special(uInt16 addr, bool varBCD, bool zeroBased) const;
// Retrieve current values (using game's properties)
Int32 numVariations() const;
const string specialLabel() const;

View File

@ -2388,7 +2388,6 @@ void EventHandler::enterPlayBackMode()
{
#ifdef GUI_SUPPORT
setState(EventHandlerState::PLAYBACK);
myOSystem.sound().mute(true); // sound does not work in playback mode
#endif
}

View File

@ -25,6 +25,7 @@
#include "Settings.hxx"
#include "TIA.hxx"
#include "Sound.hxx"
#include "AudioSettings.hxx"
#include "MediaFactory.hxx"
#include "FBSurface.hxx"
@ -440,23 +441,24 @@ void FrameBuffer::update(UpdateMode mode)
case EventHandlerState::PLAYBACK:
{
static Int32 frames = 0;
RewindManager& r = myOSystem.state().rewindManager();
bool success = true;
Int64 frameCycles = 76 * std::max<Int32>(myOSystem.console().tia().scanlinesLastFrame(), 240);
if(--frames <= 0)
{
r.unwindStates(1);
// get time between current and next state
uInt64 startCycles = r.getCurrentCycles();
RewindManager& r = myOSystem.state().rewindManager();
uInt64 prevCycles = r.getCurrentCycles();
success = r.unwindStates(1);
// display larger state gaps faster
frames = std::sqrt((myOSystem.console().tia().cycles() - startCycles) / frameCycles);
if(success)
r.rewindStates(1);
Int64 frameCycles = 76 * std::max<Int32>(myOSystem.console().tia().scanlinesLastFrame(), 240);
// Use time between previous and current state
// to playback larger state gaps faster
frames = std::sqrt((r.getCurrentCycles() - prevCycles) / frameCycles);
// TODO: test and verify with CS
//myOSystem.sound().mute(uInt32(frames) > myOSystem.audioSettings().headroom() / 2 + 1);
}
redraw |= success;
if(redraw)
myTIASurface->render();

View File

@ -839,7 +839,15 @@ void OSystem::mainLoop()
if (myEventHandler->state() == EventHandlerState::EMULATION)
// Dispatch emulation and render frame (if applicable)
timesliceSeconds = dispatchEmulation(emulationWorker);
else {
else if(myEventHandler->state() == EventHandlerState::PLAYBACK)
{
// Playback at emulation speed
timesliceSeconds = static_cast<double>(myConsole->tia().scanlinesLastFrame() * 76) /
static_cast<double>(myConsole->emulationTiming().cyclesPerSecond());
myFrameBuffer->update();
}
else
{
// Render the GUI with 60 Hz in all other modes
timesliceSeconds = 1. / 60.;
myFrameBuffer->update();

View File

@ -86,16 +86,26 @@ void Audio::phase1()
uInt8 sample0 = myChannel0.phase1();
uInt8 sample1 = myChannel1.phase1();
if (!myAudioQueue) return;
addSample(sample0, sample1);
#ifdef GUI_SUPPORT
mySamples.push_back(sample0 | (sample1 << 4));
#endif
}
if (myAudioQueue->isStereo()) {
myCurrentFragment[2*mySampleIndex] = myMixingTableIndividual[sample0];
myCurrentFragment[2*mySampleIndex + 1] = myMixingTableIndividual[sample1];
} else {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Audio::addSample(uInt8 sample0, uInt8 sample1)
{
if(!myAudioQueue) return;
if(myAudioQueue->isStereo()) {
myCurrentFragment[2 * mySampleIndex] = myMixingTableIndividual[sample0];
myCurrentFragment[2 * mySampleIndex + 1] = myMixingTableIndividual[sample1];
}
else {
myCurrentFragment[mySampleIndex] = myMixingTableSum[sample0 + sample1];
}
if (++mySampleIndex == myAudioQueue->fragmentSize()) {
if(++mySampleIndex == myAudioQueue->fragmentSize()) {
mySampleIndex = 0;
myCurrentFragment = myAudioQueue->enqueue(myCurrentFragment);
}
@ -125,6 +135,16 @@ bool Audio::save(Serializer& out) const
if (!myChannel0.save(out)) return false;
if (!myChannel1.save(out)) return false;
#ifdef GUI_SUPPORT
out.putLong(uInt64(mySamples.size()));
out.putByteArray(mySamples.data(), mySamples.size());
// TODO: check if this improves sound of playback for larger state gaps
//out.putInt(mySampleIndex);
//out.putShortArray((uInt16*)myCurrentFragment, myAudioQueue->fragmentSize());
mySamples.clear();
#endif
}
catch(...)
{
@ -144,6 +164,24 @@ bool Audio::load(Serializer& in)
if (!myChannel0.load(in)) return false;
if (!myChannel1.load(in)) return false;
#ifdef GUI_SUPPORT
uInt64 sampleSize = in.getLong();
unique_ptr<uInt8[]> samples = make_unique<uInt8[]>(sampleSize);
in.getByteArray(samples.get(), sampleSize);
//mySampleIndex = in.getInt();
//in.getShortArray((uInt16*)myCurrentFragment, myAudioQueue->fragmentSize());
// Feed all loaded samples into the audio queue
for(size_t i = 0; i < sampleSize; i++)
{
uInt8 sample = samples[i];
uInt8 sample0 = sample & 0x0f;
uInt8 sample1 = sample >> 4;
addSample(sample0, sample1);
}
#endif
}
catch(...)
{

View File

@ -47,6 +47,7 @@ class Audio : public Serializable
private:
void phase1();
void addSample(uInt8 sample0, uInt8 sample1);
private:
shared_ptr<AudioQueue> myAudioQueue;
@ -61,6 +62,9 @@ class Audio : public Serializable
Int16* myCurrentFragment{nullptr};
uInt32 mySampleIndex{0};
#ifdef GUI_SUPPORT
mutable ByteArray mySamples;
#endif
private:
Audio(const Audio&) = delete;

View File

@ -53,6 +53,7 @@ WhatsNewDialog::WhatsNewDialog(OSystem& osystem, DialogContainer& parent, const
add(ypos, "added wildcard support to launcher dialog filter");
add(ypos, "added option to search subdirectories in launcher");
add(ypos, "added tooltips to many UI items");
add(ypos, "added sound to Time Machine playback");
add(ypos, "increased sample size for CDFJ+");
add(ypos, ELLIPSIS + " (for a complete list see 'docs/Changes.txt')");
#endif