Tidy up cassette code.

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
Andrea Odetti 2021-05-30 19:57:27 +01:00
parent a302227470
commit 4adde75a13
4 changed files with 140 additions and 97 deletions

View file

@ -619,26 +619,34 @@ namespace sa2
if (ImGui::BeginTabItem("Tape")) if (ImGui::BeginTabItem("Tape"))
{ {
CassetteTape & tape = CassetteTape::instance();
size_t size; size_t size;
size_t pos; size_t pos;
int frequency; int frequency;
uint8_t bit; uint8_t bit;
getTapeInfo(size, pos, frequency, bit); tape.getTapeInfo(size, pos, frequency, bit);
if (pos < size) if (size)
{ {
const float remaining = float(size - pos) / float(frequency); const float remaining = float(size - (pos + 1)) / float(frequency);
const float fraction = float(pos) / float(size); const float fraction = float(pos + 1) / float(size);
char buf[32]; char buf[32];
sprintf(buf, "-%.1f s", remaining); sprintf(buf, "-%.1f s", remaining);
const ImU32 color = bit ? IM_COL32(200, 0, 0, 100) : IM_COL32(0, 200, 0, 100); const ImU32 color = bit ? IM_COL32(200, 0, 0, 100) : IM_COL32(0, 200, 0, 100);
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, color); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, color);
ImGui::ProgressBar(fraction, ImVec2(-FLT_MIN, 0), buf); ImGui::ProgressBar(fraction, ImVec2(-FLT_MIN, 0), buf);
ImGui::LabelText("Frequency", "%d Hz", frequency); ImGui::LabelText("Frequency", "%d Hz", frequency);
ImGui::LabelText("Auto Play", "%s", "ON");
ImGui::PopStyleColor(); ImGui::PopStyleColor();
if (ImGui::Button("Eject tape")) if (ImGui::Button("Rewind"))
{ {
ejectTape(); tape.rewind();
}
ImGui::SameLine();
if (ImGui::Button("Eject"))
{
tape.eject();
} }
} }
else else

View file

@ -59,10 +59,10 @@ namespace
// tested with all formats from https://asciiexpress.net/ // tested with all formats from https://asciiexpress.net/
// 8 bit mono is just enough // 8 bit mono is just enough
// TAPEIN will interpolate so we do not need to resample at a higher frequency // TAPEIN will interpolate so we do not need to resample at a higher frequency
const SDL_AudioFormat format = sizeof(tape_data_t) == 1 ? AUDIO_S8 : AUDIO_S16SYS; const SDL_AudioFormat format = sizeof(CassetteTape::tape_data_t) == 1 ? AUDIO_S8 : AUDIO_S16SYS;
const int res = SDL_BuildAudioCVT(&cvt, wavSpec.format, wavSpec.channels, wavSpec.freq, format, 1, wavSpec.freq); const int res = SDL_BuildAudioCVT(&cvt, wavSpec.format, wavSpec.channels, wavSpec.freq, format, 1, wavSpec.freq);
cvt.len = wavLength; cvt.len = wavLength;
std::vector<tape_data_t> output(cvt.len_mult * cvt.len / sizeof(tape_data_t)); std::vector<CassetteTape::tape_data_t> output(cvt.len_mult * cvt.len / sizeof(CassetteTape::tape_data_t));
std::memcpy(output.data(), wavBuffer, cvt.len); std::memcpy(output.data(), wavBuffer, cvt.len);
SDL_FreeWAV(wavBuffer); SDL_FreeWAV(wavBuffer);
@ -70,10 +70,10 @@ namespace
{ {
cvt.buf = reinterpret_cast<Uint8 *>(output.data()); cvt.buf = reinterpret_cast<Uint8 *>(output.data());
SDL_ConvertAudio(&cvt); SDL_ConvertAudio(&cvt);
output.resize(cvt.len_cvt / sizeof(tape_data_t)); output.resize(cvt.len_cvt / sizeof(CassetteTape::tape_data_t));
} }
setCassetteTape(output, wavSpec.freq); CassetteTape::instance().setData(output, wavSpec.freq);
} }
} }

View file

@ -10,78 +10,110 @@
#include <limits> #include <limits>
namespace CassetteTape & CassetteTape::instance()
{ {
std::vector<tape_data_t> cassetteTape; static CassetteTape tape;
uint64_t baseCycles; return tape;
int tapeFrequency;
bool playing = false;
BYTE lastBit = 1;
// threshold not needed for https://asciiexpress.net
// but probably necessary for a real audio file
constexpr tape_data_t threshold = 5;
BYTE getBitValue(const tape_data_t val)
{
// this has been tested on all formats from https://asciiexpress.net
//
// we are extracting the sign bit (set for negative numbers)
// this is really important for the asymmetric wave in https://asciiexpress.net/diskserver/
// not so much for the other cases
if (val > threshold)
{
lastBit = 0;
}
else if (val < -threshold)
{
lastBit = 1;
}
// else leave it unchanged to the previous value
return lastBit;
}
tape_data_t getCurrentWave(size_t & pos)
{
if (playing && !cassetteTape.empty())
{
const double delta = g_nCumulativeCycles - baseCycles;
const double position = delta / g_fCurrentCLK6502 * tapeFrequency;
pos = static_cast<size_t>(position);
if (pos < cassetteTape.size() - 1)
{
// linear interpolation
const double reminder = position - pos;
const double value = (1.0 - reminder) * cassetteTape[pos] + reminder * cassetteTape[pos + 1];
return lround(value);
}
cassetteTape.clear();
}
pos = 0;
return std::numeric_limits<tape_data_t>::min();
}
} }
//--------------------------------------------------------------------------- void CassetteTape::setData(const std::vector<tape_data_t> & data, const int frequency)
{
myData = data;
myFrequency = frequency;
myIsPlaying = false;
}
void CassetteTape::eject()
{
myData.clear();
}
void CassetteTape::rewind()
{
myIsPlaying = false;
}
BYTE CassetteTape::getBitValue(const tape_data_t val)
{
// threshold not needed for https://asciiexpress.net
// but probably necessary for a real audio file
//
// this has been tested on all formats from https://asciiexpress.net
//
// we are extracting the sign bit (set for negative numbers)
// this is really important for the asymmetric wave in https://asciiexpress.net/diskserver/
// not so much for the other cases
if (val > myThreshold)
{
myLastBit = 0;
}
else if (val < -myThreshold)
{
myLastBit = 1;
}
// else leave it unchanged to the previous value
return myLastBit;
}
CassetteTape::tape_data_t CassetteTape::getCurrentWave(size_t & pos) const
{
if (myIsPlaying)
{
const double delta = g_nCumulativeCycles - myBaseCycles;
const double position = delta / g_fCurrentCLK6502 * myFrequency;
pos = static_cast<size_t>(position);
if (pos + 1 < myData.size())
{
// linear interpolation
const double reminder = position - pos;
const double value = (1.0 - reminder) * myData[pos] + reminder * myData[pos + 1];
return lround(value);
}
else
{
pos = myData.size() - 1;
}
}
else
{
pos = 0;
}
return std::numeric_limits<tape_data_t>::min();
}
BYTE CassetteTape::getValue(const ULONG nExecutedCycles)
{
CpuCalcCycles(nExecutedCycles);
if (!myIsPlaying)
{
// start play as soon as TAPEIN is read
myIsPlaying = true;
myBaseCycles = g_nCumulativeCycles;
}
size_t pos;
const tape_data_t val = getCurrentWave(pos);
const BYTE highBit = getBitValue(val);
return highBit;
}
void CassetteTape::getTapeInfo(size_t & size, size_t & pos, int & frequency, uint8_t & bit) const
{
const tape_data_t val = getCurrentWave(pos);
bit = myLastBit;
size = myData.size();
frequency = myFrequency;
}
BYTE __stdcall TapeRead(WORD pc, WORD address, BYTE, BYTE, ULONG nExecutedCycles) // $C060 TAPEIN BYTE __stdcall TapeRead(WORD pc, WORD address, BYTE, BYTE, ULONG nExecutedCycles) // $C060 TAPEIN
{ {
if (g_Apple2Type == A2TYPE_PRAVETS8A) if (g_Apple2Type == A2TYPE_PRAVETS8A)
return GetPravets().GetKeycode( MemReadFloatingBus(nExecutedCycles) ); return GetPravets().GetKeycode( MemReadFloatingBus(nExecutedCycles) );
CpuCalcCycles(nExecutedCycles); const BYTE highBit = CassetteTape::instance().getValue(nExecutedCycles);
if (!playing)
{
// start play as soon as TAPEIN is read
playing = true;
baseCycles = g_nCumulativeCycles;
}
size_t pos;
const tape_data_t val = getCurrentWave(pos); // this has side effects
const BYTE highBit = getBitValue(val);
return MemReadFloatingBus(highBit, nExecutedCycles); return MemReadFloatingBus(highBit, nExecutedCycles);
} }
@ -90,23 +122,3 @@ BYTE __stdcall TapeWrite(WORD, WORD address, BYTE, BYTE, ULONG nExecutedCycles)
{ {
return 0; return 0;
} }
void setCassetteTape(const std::vector<tape_data_t> & data, const int frequency)
{
playing = false;
cassetteTape = data;
tapeFrequency = frequency;
}
void getTapeInfo(size_t & size, size_t & pos, int & frequency, uint8_t & bit)
{
const tape_data_t val = getCurrentWave(pos); // this has side effects
bit = lastBit;
size = cassetteTape.size();
frequency = tapeFrequency;
}
void ejectTape()
{
cassetteTape.clear();
}

View file

@ -3,8 +3,31 @@
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
typedef int8_t tape_data_t; class CassetteTape
{
public:
void setCassetteTape(const std::vector<tape_data_t> & data, const int frequency); typedef int8_t tape_data_t;
void getTapeInfo(size_t & size, size_t & pos, int & frequency, uint8_t & bit);
void ejectTape(); void setData(const std::vector<tape_data_t> & data, const int frequency);
BYTE getValue(const ULONG nExecutedCycles);
void getTapeInfo(size_t & size, size_t & pos, int & frequency, uint8_t & bit) const ;
void eject();
void rewind();
static CassetteTape & instance();
private:
BYTE getBitValue(const tape_data_t val);
tape_data_t getCurrentWave(size_t & pos) const;
std::vector<tape_data_t> myData;
uint64_t myBaseCycles;
int myFrequency;
bool myIsPlaying = false;
BYTE myLastBit = 1; // negative wave
static constexpr tape_data_t myThreshold = 5;
};