diff --git a/source/frontends/sdl/imgui/sdlsettings.cpp b/source/frontends/sdl/imgui/sdlsettings.cpp index 919fdfc1..8f91ec49 100644 --- a/source/frontends/sdl/imgui/sdlsettings.cpp +++ b/source/frontends/sdl/imgui/sdlsettings.cpp @@ -619,26 +619,34 @@ namespace sa2 if (ImGui::BeginTabItem("Tape")) { + CassetteTape & tape = CassetteTape::instance(); + size_t size; size_t pos; int frequency; 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 fraction = float(pos) / float(size); + const float remaining = float(size - (pos + 1)) / float(frequency); + const float fraction = float(pos + 1) / float(size); char buf[32]; sprintf(buf, "-%.1f s", remaining); const ImU32 color = bit ? IM_COL32(200, 0, 0, 100) : IM_COL32(0, 200, 0, 100); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, color); ImGui::ProgressBar(fraction, ImVec2(-FLT_MIN, 0), buf); ImGui::LabelText("Frequency", "%d Hz", frequency); + ImGui::LabelText("Auto Play", "%s", "ON"); ImGui::PopStyleColor(); - if (ImGui::Button("Eject tape")) + if (ImGui::Button("Rewind")) { - ejectTape(); + tape.rewind(); + } + ImGui::SameLine(); + if (ImGui::Button("Eject")) + { + tape.eject(); } } else diff --git a/source/frontends/sdl/processfile.cpp b/source/frontends/sdl/processfile.cpp index 51933b93..85c0f69c 100644 --- a/source/frontends/sdl/processfile.cpp +++ b/source/frontends/sdl/processfile.cpp @@ -59,10 +59,10 @@ namespace // tested with all formats from https://asciiexpress.net/ // 8 bit mono is just enough // 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); cvt.len = wavLength; - std::vector output(cvt.len_mult * cvt.len / sizeof(tape_data_t)); + std::vector output(cvt.len_mult * cvt.len / sizeof(CassetteTape::tape_data_t)); std::memcpy(output.data(), wavBuffer, cvt.len); SDL_FreeWAV(wavBuffer); @@ -70,10 +70,10 @@ namespace { cvt.buf = reinterpret_cast(output.data()); 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); } } diff --git a/source/linux/tape.cpp b/source/linux/tape.cpp index 6bf0ca2a..8787b592 100644 --- a/source/linux/tape.cpp +++ b/source/linux/tape.cpp @@ -10,78 +10,110 @@ #include -namespace +CassetteTape & CassetteTape::instance() { - std::vector cassetteTape; - uint64_t baseCycles; - 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(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::min(); - } + static CassetteTape tape; + return tape; } -//--------------------------------------------------------------------------- +void CassetteTape::setData(const std::vector & 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(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::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 { if (g_Apple2Type == A2TYPE_PRAVETS8A) return GetPravets().GetKeycode( MemReadFloatingBus(nExecutedCycles) ); - CpuCalcCycles(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); + const BYTE highBit = CassetteTape::instance().getValue(nExecutedCycles); return MemReadFloatingBus(highBit, nExecutedCycles); } @@ -90,23 +122,3 @@ BYTE __stdcall TapeWrite(WORD, WORD address, BYTE, BYTE, ULONG nExecutedCycles) { return 0; } - -void setCassetteTape(const std::vector & 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(); -} diff --git a/source/linux/tape.h b/source/linux/tape.h index 52a8a84c..0f58ae3f 100644 --- a/source/linux/tape.h +++ b/source/linux/tape.h @@ -3,8 +3,31 @@ #include #include -typedef int8_t tape_data_t; +class CassetteTape +{ +public: -void setCassetteTape(const std::vector & data, const int frequency); -void getTapeInfo(size_t & size, size_t & pos, int & frequency, uint8_t & bit); -void ejectTape(); + typedef int8_t tape_data_t; + + void setData(const std::vector & 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 myData; + + uint64_t myBaseCycles; + int myFrequency; + bool myIsPlaying = false; + BYTE myLastBit = 1; // negative wave + + static constexpr tape_data_t myThreshold = 5; +};