mirror of https://github.com/stella-emu/stella.git
added sound to Time Machine playback
fixed playback speed updated docs
This commit is contained in:
parent
d38593a821
commit
ee8734ce14
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
* Added dynamic tooltips to most debugger items.
|
* Added dynamic tooltips to most debugger items.
|
||||||
|
|
||||||
|
* Added sound to Time Machine playback.
|
||||||
|
|
||||||
* Increased sample size for CDFJ+.
|
* Increased sample size for CDFJ+.
|
||||||
|
|
||||||
* Fixed autofire bug for trackball controllers.
|
* Fixed autofire bug for trackball controllers.
|
||||||
|
|
|
@ -1828,8 +1828,8 @@
|
||||||
</tr>
|
</tr>
|
||||||
<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>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>Space</td>
|
||||||
<td>Shift</td>
|
<td>Space</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Start/Stop playback (exist/enters the <a href="#TimeMachine"><b>Time Machine</b></a> dialog)</td>
|
<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,
|
<tr><td>Current time</td><td>Shows the time of the currently selected state,
|
||||||
relative to the first one</td></tr>
|
relative to the first one</td></tr>
|
||||||
<tr><td>'Start/Stop' button</td><td>Starts or stops the Time Machine</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 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>'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 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>'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>
|
<tr><td>'Save All' button</td><td>Saves all Time Machine states to disk</td></tr>
|
||||||
|
@ -2288,16 +2288,18 @@
|
||||||
<a href="#HighScoreGames">High Scores - supported games</a></b>) this has
|
<a href="#HighScoreGames">High Scores - supported games</a></b>) this has
|
||||||
been done already.</p>
|
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>
|
<p><b>High Scores</b> dialog:</p>
|
||||||
|
<table border="5" cellpadding="2" frame="box" rules="none">
|
||||||
|
<tr>
|
||||||
<td><img src="graphics/highscores.png"></td>
|
<td><img src="graphics/highscores.png"></td>
|
||||||
|
<td> </td>
|
||||||
<p>This dialog can be opened by pressing 'Insert' any time while a game is
|
<td valign="top">
|
||||||
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">
|
<table border="1" cellpadding="4">
|
||||||
<tr><th>Item</th><th>Description</th></tr>
|
<tr><th>Item</th><th>Description</th></tr>
|
||||||
<tr><td>Top row</td><td>Displays the current game's name.</td></tr>
|
<tr><td>Top row</td><td>Displays the current game's name.</td></tr>
|
||||||
|
@ -2325,6 +2327,11 @@
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td>Cancel</td><td>Closes the dialog without saving.</td></tr>
|
<tr><td>Cancel</td><td>Closes the dialog without saving.</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<p>For details how to configure high scores definintions for a game see
|
<p>For details how to configure high scores definintions for a game see
|
||||||
<a href="#HighScoreProps"><b>High Scores Properties</b></a></h2>.</p>
|
<a href="#HighScoreProps"><b>High Scores Properties</b></a></h2>.</p>
|
||||||
</blockquote></br>
|
</blockquote></br>
|
||||||
|
@ -3511,7 +3518,7 @@
|
||||||
<p><b>User Interface Settings</b> dialog (2 tabs):</p>
|
<p><b>User Interface Settings</b> dialog (2 tabs):</p>
|
||||||
<table border="5" cellpadding="2" frame="box" rules="none">
|
<table border="5" cellpadding="2" frame="box" rules="none">
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="graphics/options_misc.png"><br><br></td>
|
<td><img src="graphics/options_misc.png"></td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td valign="top">
|
<td valign="top">
|
||||||
<table border="1" cellpadding="4">
|
<table border="1" cellpadding="4">
|
||||||
|
@ -3530,12 +3537,13 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="graphics/options_misc_classic.png"></td>
|
<td><img src="graphics/options_misc_dark.png"></td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td><img src="graphics/options_misc_light.png"></td>
|
<td><img src="graphics/options_misc_classic.png"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<br>
|
<br>
|
||||||
|
|
|
@ -422,33 +422,28 @@ string HighScoresManager::md5Props() const
|
||||||
bool HighScoresManager::scoreInvert() const
|
bool HighScoresManager::scoreInvert() const
|
||||||
{
|
{
|
||||||
json jprops;
|
json jprops;
|
||||||
|
|
||||||
return scoreInvert(properties(jprops));
|
return scoreInvert(properties(jprops));
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
Int32 HighScoresManager::special() const
|
Int32 HighScoresManager::special() const
|
||||||
{
|
{
|
||||||
|
if(!myOSystem.hasConsole())
|
||||||
|
return NO_VALUE;
|
||||||
|
|
||||||
json jprops;
|
json jprops;
|
||||||
uInt16 addr = specialAddress(properties(jprops));
|
uInt16 addr = specialAddress(properties(jprops));
|
||||||
|
|
||||||
if (addr == DEFAULT_ADDRESS)
|
if (addr == DEFAULT_ADDRESS)
|
||||||
return NO_VALUE;
|
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);
|
Int32 var = peek(addr);
|
||||||
|
|
||||||
if (varBCD)
|
if(specialBCD(jprops))
|
||||||
var = fromBCD(var);
|
var = fromBCD(var);
|
||||||
|
|
||||||
var += zeroBased ? 1 : 0;
|
var += specialZeroBased(jprops) ? 1 : 0;
|
||||||
|
|
||||||
return var;
|
return var;
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,9 +131,6 @@ class HighScoresManager
|
||||||
*/
|
*/
|
||||||
uInt32 numAddrBytes(Int32 digits, Int32 trailing) const;
|
uInt32 numAddrBytes(Int32 digits, Int32 trailing) const;
|
||||||
|
|
||||||
|
|
||||||
Int32 special(uInt16 addr, bool varBCD, bool zeroBased) const;
|
|
||||||
|
|
||||||
// Retrieve current values (using game's properties)
|
// Retrieve current values (using game's properties)
|
||||||
Int32 numVariations() const;
|
Int32 numVariations() const;
|
||||||
const string specialLabel() const;
|
const string specialLabel() const;
|
||||||
|
|
|
@ -2388,7 +2388,6 @@ void EventHandler::enterPlayBackMode()
|
||||||
{
|
{
|
||||||
#ifdef GUI_SUPPORT
|
#ifdef GUI_SUPPORT
|
||||||
setState(EventHandlerState::PLAYBACK);
|
setState(EventHandlerState::PLAYBACK);
|
||||||
myOSystem.sound().mute(true); // sound does not work in playback mode
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "Settings.hxx"
|
#include "Settings.hxx"
|
||||||
#include "TIA.hxx"
|
#include "TIA.hxx"
|
||||||
#include "Sound.hxx"
|
#include "Sound.hxx"
|
||||||
|
#include "AudioSettings.hxx"
|
||||||
#include "MediaFactory.hxx"
|
#include "MediaFactory.hxx"
|
||||||
|
|
||||||
#include "FBSurface.hxx"
|
#include "FBSurface.hxx"
|
||||||
|
@ -440,23 +441,24 @@ void FrameBuffer::update(UpdateMode mode)
|
||||||
case EventHandlerState::PLAYBACK:
|
case EventHandlerState::PLAYBACK:
|
||||||
{
|
{
|
||||||
static Int32 frames = 0;
|
static Int32 frames = 0;
|
||||||
RewindManager& r = myOSystem.state().rewindManager();
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
Int64 frameCycles = 76 * std::max<Int32>(myOSystem.console().tia().scanlinesLastFrame(), 240);
|
|
||||||
|
|
||||||
if(--frames <= 0)
|
if(--frames <= 0)
|
||||||
{
|
{
|
||||||
r.unwindStates(1);
|
RewindManager& r = myOSystem.state().rewindManager();
|
||||||
// get time between current and next state
|
uInt64 prevCycles = r.getCurrentCycles();
|
||||||
uInt64 startCycles = r.getCurrentCycles();
|
|
||||||
success = r.unwindStates(1);
|
success = r.unwindStates(1);
|
||||||
// display larger state gaps faster
|
|
||||||
frames = std::sqrt((myOSystem.console().tia().cycles() - startCycles) / frameCycles);
|
|
||||||
|
|
||||||
if(success)
|
Int64 frameCycles = 76 * std::max<Int32>(myOSystem.console().tia().scanlinesLastFrame(), 240);
|
||||||
r.rewindStates(1);
|
|
||||||
|
// 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;
|
redraw |= success;
|
||||||
if(redraw)
|
if(redraw)
|
||||||
myTIASurface->render();
|
myTIASurface->render();
|
||||||
|
|
|
@ -839,7 +839,15 @@ void OSystem::mainLoop()
|
||||||
if (myEventHandler->state() == EventHandlerState::EMULATION)
|
if (myEventHandler->state() == EventHandlerState::EMULATION)
|
||||||
// Dispatch emulation and render frame (if applicable)
|
// Dispatch emulation and render frame (if applicable)
|
||||||
timesliceSeconds = dispatchEmulation(emulationWorker);
|
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
|
// Render the GUI with 60 Hz in all other modes
|
||||||
timesliceSeconds = 1. / 60.;
|
timesliceSeconds = 1. / 60.;
|
||||||
myFrameBuffer->update();
|
myFrameBuffer->update();
|
||||||
|
|
|
@ -86,16 +86,26 @@ void Audio::phase1()
|
||||||
uInt8 sample0 = myChannel0.phase1();
|
uInt8 sample0 = myChannel0.phase1();
|
||||||
uInt8 sample1 = myChannel1.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];
|
void Audio::addSample(uInt8 sample0, uInt8 sample1)
|
||||||
myCurrentFragment[2*mySampleIndex + 1] = myMixingTableIndividual[sample1];
|
{
|
||||||
} else {
|
if(!myAudioQueue) return;
|
||||||
|
|
||||||
|
if(myAudioQueue->isStereo()) {
|
||||||
|
myCurrentFragment[2 * mySampleIndex] = myMixingTableIndividual[sample0];
|
||||||
|
myCurrentFragment[2 * mySampleIndex + 1] = myMixingTableIndividual[sample1];
|
||||||
|
}
|
||||||
|
else {
|
||||||
myCurrentFragment[mySampleIndex] = myMixingTableSum[sample0 + sample1];
|
myCurrentFragment[mySampleIndex] = myMixingTableSum[sample0 + sample1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (++mySampleIndex == myAudioQueue->fragmentSize()) {
|
if(++mySampleIndex == myAudioQueue->fragmentSize()) {
|
||||||
mySampleIndex = 0;
|
mySampleIndex = 0;
|
||||||
myCurrentFragment = myAudioQueue->enqueue(myCurrentFragment);
|
myCurrentFragment = myAudioQueue->enqueue(myCurrentFragment);
|
||||||
}
|
}
|
||||||
|
@ -125,6 +135,16 @@ bool Audio::save(Serializer& out) const
|
||||||
|
|
||||||
if (!myChannel0.save(out)) return false;
|
if (!myChannel0.save(out)) return false;
|
||||||
if (!myChannel1.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(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
|
@ -144,6 +164,24 @@ bool Audio::load(Serializer& in)
|
||||||
|
|
||||||
if (!myChannel0.load(in)) return false;
|
if (!myChannel0.load(in)) return false;
|
||||||
if (!myChannel1.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(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
|
|
|
@ -47,6 +47,7 @@ class Audio : public Serializable
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void phase1();
|
void phase1();
|
||||||
|
void addSample(uInt8 sample0, uInt8 sample1);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
shared_ptr<AudioQueue> myAudioQueue;
|
shared_ptr<AudioQueue> myAudioQueue;
|
||||||
|
@ -61,6 +62,9 @@ class Audio : public Serializable
|
||||||
|
|
||||||
Int16* myCurrentFragment{nullptr};
|
Int16* myCurrentFragment{nullptr};
|
||||||
uInt32 mySampleIndex{0};
|
uInt32 mySampleIndex{0};
|
||||||
|
#ifdef GUI_SUPPORT
|
||||||
|
mutable ByteArray mySamples;
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Audio(const Audio&) = delete;
|
Audio(const Audio&) = delete;
|
||||||
|
|
|
@ -53,6 +53,7 @@ WhatsNewDialog::WhatsNewDialog(OSystem& osystem, DialogContainer& parent, const
|
||||||
add(ypos, "added wildcard support to launcher dialog filter");
|
add(ypos, "added wildcard support to launcher dialog filter");
|
||||||
add(ypos, "added option to search subdirectories in launcher");
|
add(ypos, "added option to search subdirectories in launcher");
|
||||||
add(ypos, "added tooltips to many UI items");
|
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, "increased sample size for CDFJ+");
|
||||||
add(ypos, ELLIPSIS + " (for a complete list see 'docs/Changes.txt')");
|
add(ypos, ELLIPSIS + " (for a complete list see 'docs/Changes.txt')");
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue