From 128d0ee5d1221c20024675b82b9d5612b5c3b93c Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Tue, 31 Oct 2023 21:13:43 +0000 Subject: [PATCH 01/25] Prevent irritating invisible cursor while navigating. Just 1 pet peeve. Just 1. --- video/video.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/video/video.ino b/video/video.ino index 6747a037..9d86f3c7 100644 --- a/video/video.ino +++ b/video/video.ino @@ -109,6 +109,7 @@ void loop() { if (processor->byteAvailable()) { if (drawCursor) { drawCursor = false; + cursorTime = CURSOR_PHASE; do_cursor(); } processor->processNext(); From 8133415f4a9c390ee42b873f2d3c9c09de47ab18 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Tue, 31 Oct 2023 21:27:14 +0000 Subject: [PATCH 02/25] Sleep sometimes in main loop, to avoid eating 100% cpu --- video/video.ino | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/video/video.ino b/video/video.ino index 9d86f3c7..fe1e7a91 100644 --- a/video/video.ino +++ b/video/video.ino @@ -87,10 +87,14 @@ void setup() { // The main loop // void loop() { + uint32_t count = 0; // Generic counter, incremented every loop iteration bool drawCursor = false; auto cursorTime = millis(); while (true) { + if ((count & 0x7f) == 0) delay(1 /* -TM- ms */); + count++; + if (terminalMode) { do_keyboard_terminal(); continue; From 79f32d9b8db76ad5481045f80bd1c78f573b5f0a Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Fri, 15 Dec 2023 00:32:12 +0000 Subject: [PATCH 03/25] Comment out zdi.h --- video/video.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/video/video.ino b/video/video.ino index c92d0160..6a007bad 100644 --- a/video/video.ino +++ b/video/video.ino @@ -72,7 +72,9 @@ bool consoleMode = false; // Serial console mode (0 = off, 1 = console enabl std::unique_ptr Terminal; // Used for CP/M mode VDUStreamProcessor * processor; // VDU Stream Processor +#ifndef USERSPACE #include "zdi.h" // ZDI debugging console +#endif /* !USERSPACE */ void setup() { disableCore0WDT(); delay(200); // Disable the watchdog timers From 1210d69c41096072838a464727ef462f6107528d Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Fri, 15 Dec 2023 00:57:32 +0000 Subject: [PATCH 04/25] More zdi.h fixes --- video/agon_ps2.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/video/agon_ps2.h b/video/agon_ps2.h index e2eabd86..2325a5e6 100644 --- a/video/agon_ps2.h +++ b/video/agon_ps2.h @@ -24,9 +24,15 @@ uint32_t mWheelAcc = MOUSE_DEFAULT_WHEELACC; // Mouse wheel acceleration // Forward declarations // +#ifdef USERSPACE +bool zdi_mode () { return false; } +void zdi_enter () {} +void zdi_process_cmd (uint8_t key) {} +#else bool zdi_mode (); void zdi_enter (); void zdi_process_cmd (uint8_t key); +#endif // Get keyboard instance // From aba335876c8d18b68ca9279a83318cd0d9df79f9 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Fri, 15 Dec 2023 22:25:18 +0000 Subject: [PATCH 05/25] Use raw pointer for soundGenerator to avoid de-init crash. Guard its re-init with a mutex --- video/agon_audio.h | 9 ++++++--- video/audio_channel.h | 2 +- video/video.ino | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/video/agon_audio.h b/video/agon_audio.h index e5557fb0..3d303ef0 100644 --- a/video/agon_audio.h +++ b/video/agon_audio.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "audio_channel.h" @@ -25,7 +26,8 @@ std::vector> audioHandlers; std::unordered_map> samples; // Storage for the sample data -std::unique_ptr soundGenerator; // audio handling sub-system +fabgl::SoundGenerator *soundGenerator; // audio handling sub-system +std::mutex soundGeneratorMutex; // Audio channel driver task // @@ -73,6 +75,7 @@ void setSampleRate(uint16_t sampleRate) { if (sampleRate == 65535) { sampleRate = AUDIO_DEFAULT_SAMPLE_RATE; } + auto lock = std::unique_lock(soundGeneratorMutex); // detatch the old sound generator if (soundGenerator) { soundGenerator->play(false); @@ -80,10 +83,10 @@ void setSampleRate(uint16_t sampleRate) { auto channel = channelPair.second; soundGenerator->detach(channel->getWaveform()); } + delete soundGenerator; } // delete the old sound generator - soundGenerator = nullptr; - soundGenerator = std::unique_ptr(new fabgl::SoundGenerator(sampleRate)); + soundGenerator = new fabgl::SoundGenerator(sampleRate); for (auto channelPair : audioChannels) { auto channel = channelPair.second; channel->attachSoundGenerator(); diff --git a/video/audio_channel.h b/video/audio_channel.h index c9dbb957..2f3e2746 100644 --- a/video/audio_channel.h +++ b/video/audio_channel.h @@ -10,7 +10,7 @@ #include "envelopes/volume.h" #include "envelopes/frequency.h" -extern std::unique_ptr soundGenerator; // audio handling sub-system +extern fabgl::SoundGenerator *soundGenerator; // audio handling sub-system extern void audioTaskAbortDelay(uint8_t channel); // The audio channel class diff --git a/video/video.ino b/video/video.ino index 6a007bad..eb5b8593 100644 --- a/video/video.ino +++ b/video/video.ino @@ -119,9 +119,9 @@ void loop() { if (processor->byteAvailable()) { if (drawCursor) { drawCursor = false; - cursorTime = CURSOR_PHASE; do_cursor(); } + cursorTime = 0; processor->processNext(); } } From df1ee2c1fc114c3840a3ca4bd596c85c977a4eee Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Mon, 15 Jan 2024 21:20:46 +0000 Subject: [PATCH 06/25] Compile fix for clang --- video/audio_channel.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/video/audio_channel.h b/video/audio_channel.h index dc60a8b8..73fbf663 100644 --- a/video/audio_channel.h +++ b/video/audio_channel.h @@ -78,7 +78,7 @@ uint8_t AudioChannel::playNote(uint8_t volume, uint16_t frequency, int32_t durat debug_log("AudioChannel: no waveform on channel %d\n\r", channel()); return 0; } - switch (this->_state) { + switch (this->_state.load()) { case AudioState::Idle: case AudioState::Release: this->_volume = volume; @@ -112,7 +112,7 @@ uint8_t AudioChannel::getStatus() { status |= AUDIO_STATUS_INDEFINITE; } } - switch (this->_state) { + switch (this->_state.load()) { case AudioState::Pending: case AudioState::Playing: case AudioState::PlayLoop: @@ -219,7 +219,7 @@ void AudioChannel::setVolume(uint8_t volume) { if (this->_waveform) { waitForAbort(); - switch (this->_state) { + switch (this->_state.load()) { case AudioState::Idle: if (volume > 0) { // new note playback @@ -267,7 +267,7 @@ void AudioChannel::setFrequency(uint16_t frequency) { if (this->_waveform) { waitForAbort(); - switch (this->_state) { + switch (this->_state.load()) { case AudioState::Pending: case AudioState::PlayLoop: case AudioState::Release: @@ -288,7 +288,7 @@ void AudioChannel::setDuration(int32_t duration) { if (this->_waveform) { waitForAbort(); - switch (this->_state) { + switch (this->_state.load()) { case AudioState::Idle: // kick off a new note playback this->_state = AudioState::Pending; @@ -389,7 +389,7 @@ bool AudioChannel::isFinished(uint32_t elapsed) { void AudioChannel::loop() { int delay = 0; - switch (this->_state) { + switch (this->_state.load()) { case AudioState::Pending: debug_log("AudioChannel: play %d,%d,%d,%d\n\r", channel(), this->_volume, this->_frequency, this->_duration); // we have a new note to play From 4c4cf36dc3e0d070396e826ab7c0ab9a5728ae17 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Sun, 28 Jan 2024 02:47:03 +0000 Subject: [PATCH 07/25] =?UTF-8?q?playmod=20fix:=20workaround=20for=20havin?= =?UTF-8?q?g=20no=20=EF=BB=BF=EF=BB=BFreal=20xTaskAbortDelay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- video/audio_channel.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/video/audio_channel.h b/video/audio_channel.h index 99d639f7..e9360352 100644 --- a/video/audio_channel.h +++ b/video/audio_channel.h @@ -526,9 +526,15 @@ void AudioChannel::loop() { break; } - if (delay != 0) { + /* + * in USERSPACE, xTaskAbortDelay is not implemented, so audioTaskAbortDelay + * won't work, and waitForAbort() will freeze vdu stream processor for the + * whole delay duration. So don't delay here, allowing 'abort' state to be + * transitioned rapidly (at some small CPU cost). + if (delay > 0) { + // -TM- vTaskDelay(pdMS_TO_TICKS(delay)); - } + }*/ } #endif // AUDIO_CHANNEL_H From 533a15716ae7ef523479c35f765e863fc0d013f4 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Fri, 23 Feb 2024 21:01:13 +0000 Subject: [PATCH 08/25] Support for VDP debug logging in emulator --- video/video.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/video/video.ino b/video/video.ino index 248e3996..9a4910c2 100644 --- a/video/video.ino +++ b/video/video.ino @@ -49,7 +49,7 @@ #include #include -#define DEBUG 0 // Serial Debug Mode: 1 = enable +#define DEBUG 1 // Serial Debug Mode: 1 = enable #define SERIALBAUDRATE 115200 HardwareSerial DBGSerial(0); @@ -207,6 +207,7 @@ void boot_screen() { // Debug printf to PC // +#ifndef USERSPACE void debug_log(const char *format, ...) { #if DEBUG == 1 va_list ap; @@ -222,6 +223,7 @@ void debug_log(const char *format, ...) { va_end(ap); #endif } +#endif // Set console mode // Parameters: From ed77f4853238008421fc7ff9a63797891b04736a Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Fri, 22 Mar 2024 23:01:23 +0000 Subject: [PATCH 09/25] Build fixes for the emulator --- video/cursor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/video/cursor.h b/video/cursor.h index 86fd0c77..5826007e 100644 --- a/video/cursor.h +++ b/video/cursor.h @@ -226,7 +226,7 @@ void cursorAutoNewline() { // Advance the active cursor right one character // -void cursorRight(bool scrollProtect = false) { +void cursorRight(bool scrollProtect) { // deal with any pending newline that we may have cursorAutoNewline(); @@ -242,7 +242,7 @@ void cursorRight(bool scrollProtect = false) { // Move the active cursor to the top-left position in the viewport // -void cursorHome(Point * cursor = activeCursor, Rect * viewport = activeViewport) { +void cursorHome(Point * cursor, Rect * viewport) { if (cursorBehaviour.flipXY) { cursor->X = cursorBehaviour.invertHorizontal ? (viewport->X2 + 1 - fontW) : viewport->X1; cursor->Y = cursorBehaviour.invertVertical ? (viewport->Y2 + 1 - fontH) : viewport->Y1; From a4a9ff07cfca65a97c471b1fb0449da73cc286a5 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Sat, 30 Mar 2024 19:30:28 +0000 Subject: [PATCH 10/25] Support booting into different video mode --- video/video.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/video/video.ino b/video/video.ino index 50908926..8c728b18 100644 --- a/video/video.ino +++ b/video/video.ino @@ -52,6 +52,8 @@ #define DEBUG 1 // Serial Debug Mode: 1 = enable #define SERIALBAUDRATE 115200 +extern uint32_t startup_screen_mode; /* in rust_glue.cpp */ + HardwareSerial DBGSerial(0); #include "agon.h" // Configuration file @@ -83,7 +85,7 @@ void setup() { disableCore1WDT(); delay(200); DBGSerial.begin(SERIALBAUDRATE, SERIAL_8N1, 3, 1); copy_font(); - set_mode(1); + set_mode(startup_screen_mode); setupVDPProtocol(); processor = new VDUStreamProcessor(&VDPSerial); initAudio(); From 94a7051a6eb8e4698c456ceabc584f469b6d95d9 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Fri, 5 Apr 2024 01:48:53 +0100 Subject: [PATCH 11/25] Bring back sane cursor behaviour while making cursor movements (ie make it visible) --- video/video.ino | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/video/video.ino b/video/video.ino index 389262b0..d9cdbefa 100644 --- a/video/video.ino +++ b/video/video.ino @@ -100,9 +100,8 @@ void setup() { // void loop() { uint32_t count = 0; // Generic counter, incremented every loop iteration - bool cursorShowing = false; - bool cursorTemporarilyHidden = false; - auto cursorTime = millis(); + bool cursorVisible = false; + bool cursorState = false; while (true) { if ((count & 0x7f) == 0) delay(1 /* -TM- ms */); @@ -111,29 +110,23 @@ void loop() { if (processTerminal()) { continue; } - if (!cursorTemporarilyHidden && cursorFlashing && (millis() - cursorTime > cursorFlashRate)) { - cursorTime = millis(); - cursorShowing = !cursorShowing; - if (ttxtMode) { - ttxt_instance.flash(cursorShowing); - } + cursorVisible = ((count & 0xFFFF) == 0); + if (cursorVisible) { + if (!cursorState && ttxtMode) ttxt_instance.flash(true); + cursorState = !cursorState; do_cursor(); + if (!cursorState && ttxtMode) ttxt_instance.flash(false); } do_keyboard(); do_mouse(); if (processor->byteAvailable()) { - if (!cursorTemporarilyHidden && cursorShowing) { - cursorTemporarilyHidden = true; + if (cursorState) { + cursorState = false; do_cursor(); } - cursorTime = 0; + count = -1; processor->processNext(); - if (!processor->byteAvailable() && (cursorTemporarilyHidden || !cursorFlashing)) { - cursorShowing = true; - cursorTemporarilyHidden = false; - do_cursor(); - } } } } From 132787e1082d52ead9f458254b48acd9558e0319 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Mon, 10 Jun 2024 22:17:23 +0100 Subject: [PATCH 12/25] Eliminate some VDP-C8 audio crashes --- video/agon_audio.h | 30 ++++++++++++++---------------- video/audio_channel.h | 17 +++++++++-------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/video/agon_audio.h b/video/agon_audio.h index 2fce0a44..57e350d9 100644 --- a/video/agon_audio.h +++ b/video/agon_audio.h @@ -21,7 +21,7 @@ #include "types.h" // audio channels and their associated tasks -std::unordered_map> audioChannels; +AudioChannel *audioChannels[MAX_AUDIO_CHANNELS]; std::vector> audioHandlers; std::unordered_map> samples; // Storage for the sample data @@ -52,8 +52,9 @@ void audioDriver(void * parameters) { BaseType_t initAudioChannel(uint8_t channelNum) { debug_log("initAudioChannel: channel %d\n\r", channelNum); - auto channel = make_shared_psram(channelNum); - audioChannels[channelNum] = channel; + if (audioChannels[channelNum] == nullptr) { + audioChannels[channelNum] = new AudioChannel(channelNum); + } return xTaskCreatePinnedToCore(audioDriver, "audioDriver", 1792, // Checked & adjusted by reading the Stack Highwater @@ -75,7 +76,6 @@ void audioTaskKill(uint8_t channel) { vTaskDelete(audioHandlers[channel]); audioChannels[channel]->detachSoundGenerator(); audioHandlers[channel] = nullptr; - audioChannels.erase(channel); debug_log("audioTaskKill: channel %d killed\n\r", channel); } else { debug_log("audioTaskKill: channel %d not found\n\r", channel); @@ -93,20 +93,19 @@ void setSampleRate(uint16_t sampleRate) { // detatch the old sound generator if (soundGenerator) { soundGenerator->play(false); - for (const auto &channelPair : audioChannels) { - if (!channelPair.second) { - debug_log("duff channel pair for channel %d, skipping\n\r", channelPair.first); - audioChannels.erase(channelPair.first); - continue; + for (int chan=0; chandetachSoundGenerator(); } - channelPair.second->detachSoundGenerator(); } delete soundGenerator; } // delete the old sound generator soundGenerator = new fabgl::SoundGenerator(sampleRate); - for (const auto &channelPair : audioChannels) { - channelPair.second->attachSoundGenerator(); + for (int chan=0; chanattachSoundGenerator(); + } } soundGenerator->play(true); } @@ -127,13 +126,13 @@ void initAudio() { // Channel enabled? // bool channelEnabled(uint8_t channel) { - return channel < MAX_AUDIO_CHANNELS && audioChannels.find(channel) != audioChannels.end(); + return channel < MAX_AUDIO_CHANNELS && audioChannels[channel] != nullptr; } // Play a note // uint8_t playNote(uint8_t channel, uint8_t volume, uint16_t frequency, uint16_t duration) { - if (channelEnabled(channel)) { + if (audioChannels[channel]) { return audioChannels[channel]->playNote(volume, frequency, duration); } return 1; @@ -176,8 +175,7 @@ uint8_t setFrequency(uint8_t channel, uint16_t frequency) { // uint8_t setWaveform(uint8_t channel, int8_t waveformType, uint16_t sampleId) { if (channelEnabled(channel)) { - auto channelRef = audioChannels[channel]; - return channelRef->setWaveform(waveformType, channelRef, sampleId); + return audioChannels[channel]->setWaveform(waveformType, sampleId); } return 0; } diff --git a/video/audio_channel.h b/video/audio_channel.h index 2697dc31..e5d32f9a 100644 --- a/video/audio_channel.h +++ b/video/audio_channel.h @@ -30,7 +30,7 @@ class AudioChannel { ~AudioChannel(); uint8_t playNote(uint8_t volume, uint16_t frequency, int32_t duration); uint8_t getStatus(); - uint8_t setWaveform(int8_t waveformType, std::shared_ptr channelRef, uint16_t sampleId = 0); + uint8_t setWaveform(int8_t waveformType, uint16_t sampleId = 0); uint8_t setVolume(uint8_t volume); uint8_t setFrequency(uint16_t frequency); uint8_t setDuration(int32_t duration); @@ -39,14 +39,14 @@ class AudioChannel { uint8_t setSampleRate(uint16_t sampleRate); uint8_t setDutyCycle(uint8_t dutyCycle); uint8_t setParameter(uint8_t parameter, uint16_t value); - WaveformGenerator * getWaveform() { return this->_waveform.get(); } void attachSoundGenerator(); void detachSoundGenerator(); uint8_t seekTo(uint32_t position); void loop(); uint8_t channel() { return _channel; } private: - std::shared_ptr getSampleWaveform(uint16_t sampleId, std::shared_ptr channelRef); + WaveformGenerator * getWaveform() { return this->_waveform.get(); } + std::shared_ptr getSampleWaveform(uint16_t sampleId, AudioChannel *channelRef); void waitForAbort(); uint8_t getVolume(uint32_t elapsed); uint16_t getFrequency(uint32_t elapsed); @@ -70,7 +70,7 @@ extern std::unordered_map> samples; // St AudioChannel::AudioChannel(uint8_t channel) : _channel(channel), _state(AudioState::Idle), _volume(64), _frequency(750), _duration(-1) { debug_log("AudioChannel: init %d\n\r", channel); - setWaveform(AUDIO_WAVE_DEFAULT, nullptr); + setWaveform(AUDIO_WAVE_DEFAULT); debug_log("free mem: %d\n\r", heap_caps_get_free_size(MALLOC_CAP_8BIT)); } @@ -146,7 +146,7 @@ uint8_t AudioChannel::getStatus() { return status; } -std::shared_ptr AudioChannel::getSampleWaveform(uint16_t sampleId, std::shared_ptr channelRef) { +std::shared_ptr AudioChannel::getSampleWaveform(uint16_t sampleId, AudioChannel *channelRef) { if (samples.find(sampleId) != samples.end()) { auto sample = samples.at(sampleId); // if (sample->channels.find(_channel) != sample->channels.end()) { @@ -170,7 +170,7 @@ std::shared_ptr AudioChannel::getSampleWaveform(uint16_t samp return nullptr; } -uint8_t AudioChannel::setWaveform(int8_t waveformType, std::shared_ptr channelRef, uint16_t sampleId) { +uint8_t AudioChannel::setWaveform(int8_t waveformType, uint16_t sampleId) { std::shared_ptr newWaveform = nullptr; switch (waveformType) { @@ -195,7 +195,7 @@ uint8_t AudioChannel::setWaveform(int8_t waveformType, std::shared_ptr_waveform) { soundGenerator->detach(getWaveform()); } + this->_state.store(AudioState::Idle); } uint8_t AudioChannel::seekTo(uint32_t position) { From a97da699e2e6acc0e792c7ff9ee1aecd3f9d159b Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Mon, 10 Jun 2024 22:18:17 +0100 Subject: [PATCH 13/25] Fix off-by-one error in charToBitmap, and bad indexing by signed char --- video/context.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/video/context.h b/video/context.h index 34d9bf0e..9c30beff 100644 --- a/video/context.h +++ b/video/context.h @@ -93,7 +93,7 @@ class Context { uint16_t currentBitmap = BUFFERED_BITMAP_BASEID; // Current bitmap ID fabgl::LinePattern linePattern = fabgl::LinePattern(); // Dotted line pattern uint8_t linePatternLength = 8; // Dotted line pattern length - std::vector charToBitmap = std::vector(255, 65535); // character to bitmap mapping + std::vector charToBitmap = std::vector(256, 65535); // character to bitmap mapping bool logicalCoords = true; // Use BBC BASIC logical coordinates @@ -140,7 +140,7 @@ class Context { bool cmpChar(uint8_t * c1, uint8_t *c2, uint8_t len); char getScreenChar(Point p); inline void setCharacterOverwrite(bool overwrite); // TODO integrate into setActiveCursor? - inline std::shared_ptr getBitmapFromChar(char c) { + inline std::shared_ptr getBitmapFromChar(uint8_t c) { return getBitmap(charToBitmap[c]); } @@ -368,4 +368,4 @@ class Context { #include "context/graphics.h" #include "context/viewport.h" -#endif // CONTEXT_H \ No newline at end of file +#endif // CONTEXT_H From 365fc5b903e2a72a79c76a9664c5c43571089b4e Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Thu, 13 Jun 2024 12:12:06 +0100 Subject: [PATCH 14/25] Fix memory corruption due to out-of-bounds vdu_palette calls --- video/vdu.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/video/vdu.h b/video/vdu.h index c6b5a3a8..59309729 100644 --- a/video/vdu.h +++ b/video/vdu.h @@ -250,6 +250,8 @@ void VDUStreamProcessor::vdu_palette() { auto g = readByte_t(); if (g == -1) return; // The green component auto b = readByte_t(); if (b == -1) return; // The blue component + // keep logical colour index in bounds + l &= 63; auto index = setLogicalPalette(l, p, r, g, b); if (index != -1) { From 4b719d8b6fbc28f19c2c605e27b6a24514c7c1ab Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Thu, 13 Jun 2024 12:12:23 +0100 Subject: [PATCH 15/25] Disable hexload on the emulator --- video/hexload.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/video/hexload.h b/video/hexload.h index 230958d9..2666c2d3 100644 --- a/video/hexload.h +++ b/video/hexload.h @@ -42,6 +42,10 @@ void echo_checksum(uint8_t ihexchecksum, uint8_t ez80checksum) { } void VDUStreamProcessor::vdu_sys_hexload(void) { +#ifdef USERSPACE + // no hexload for emulators :) + return; +#endif /* USERSPACE */ uint32_t segment_address; uint8_t u,h,l,tmp; uint8_t bytecount; @@ -163,4 +167,4 @@ void VDUStreamProcessor::vdu_sys_hexload(void) { printFmt("VDP done\r\n"); } -#endif // HEXLOAD_H \ No newline at end of file +#endif // HEXLOAD_H From d2a152fc2fcfbbe7dc4ddad400091f8e34d98540 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Thu, 13 Jun 2024 12:19:29 +0100 Subject: [PATCH 16/25] Audio subsystem redesign * Use a single task for channel sequencing * Locking on SoundGenerator and AudioChannel access, as those structures are manipulated from multiple tasks --- video/agon_audio.h | 100 +++++++++----------- video/audio_channel.h | 211 +++++++++++++++++++++++------------------- 2 files changed, 156 insertions(+), 155 deletions(-) diff --git a/video/agon_audio.h b/video/agon_audio.h index 57e350d9..7755cb29 100644 --- a/video/agon_audio.h +++ b/video/agon_audio.h @@ -13,73 +13,55 @@ #include #include #include -#include #include +#include + +std::mutex soundGeneratorMutex; +#include "agon.h" #include "audio_channel.h" #include "audio_sample.h" #include "types.h" // audio channels and their associated tasks AudioChannel *audioChannels[MAX_AUDIO_CHANNELS]; -std::vector> audioHandlers; - +TaskHandle_t audioTask; std::unordered_map> samples; // Storage for the sample data - -fabgl::SoundGenerator *soundGenerator; // audio handling sub-system -std::mutex soundGeneratorMutex; +fabgl::SoundGenerator *soundGenerator; // audio handling sub-system extern void force_debug_log(const char *format, ...); +bool channelEnabled(uint8_t channel); // Audio channel driver task // void audioDriver(void * parameters) { - uint8_t channelNum = (uint8_t)(uintptr_t)parameters; - // auto counter = 0; - auto channel = audioChannels[channelNum]; - while (true) { - channel->loop(); - vTaskDelay(1); - // counter++; - // if (counter > 3000) { - // force_debug_log("audioDriver: channel %d stack highwater %d\n\r", channelNum, uxTaskGetStackHighWaterMark(nullptr)); - // counter = 0; - // } + auto now = millis(); + for (int i=0; iloop(now); + } + } + vTaskDelay(pdMS_TO_TICKS(1)); } } -BaseType_t initAudioChannel(uint8_t channelNum) { - debug_log("initAudioChannel: channel %d\n\r", channelNum); - - if (audioChannels[channelNum] == nullptr) { - audioChannels[channelNum] = new AudioChannel(channelNum); - } - - return xTaskCreatePinnedToCore(audioDriver, "audioDriver", - 1792, // Checked & adjusted by reading the Stack Highwater - (void*)(uintptr_t) channelNum, // Parameters +BaseType_t initAudioTask() { + return xTaskCreatePinnedToCore(audioDriver, "audioDriver", + 2048, + nullptr, AUDIO_CHANNEL_PRIORITY, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest. - &audioHandlers[channelNum], // Task handle + &audioTask, AUDIO_CORE ); } -void audioTaskAbortDelay(uint8_t channel) { - if (audioHandlers[channel]) { - xTaskAbortDelay(audioHandlers[channel]); - } -} - -void audioTaskKill(uint8_t channel) { - if (audioHandlers[channel]) { - vTaskDelete(audioHandlers[channel]); - audioChannels[channel]->detachSoundGenerator(); - audioHandlers[channel] = nullptr; - debug_log("audioTaskKill: channel %d killed\n\r", channel); - } else { - debug_log("audioTaskKill: channel %d not found\n\r", channel); +BaseType_t initAudioChannel(int channel) { + if (!channelEnabled(channel)) { + audioChannels[channel] = new AudioChannel(channel); + return pdPASS; } + return 0; } // Change the sample rate @@ -89,21 +71,19 @@ void setSampleRate(uint16_t sampleRate) { if (sampleRate == 65535) { sampleRate = AUDIO_DEFAULT_SAMPLE_RATE; } - auto lock = std::unique_lock(soundGeneratorMutex); - // detatch the old sound generator - if (soundGenerator) { - soundGenerator->play(false); - for (int chan=0; chandetachSoundGenerator(); - } + { + // detach the old sound generator + auto lock = std::unique_lock(soundGeneratorMutex); + // delete the old sound generator + if (soundGenerator) { + soundGenerator->clear(); + delete soundGenerator; } - delete soundGenerator; + soundGenerator = new fabgl::SoundGenerator(sampleRate); } - // delete the old sound generator - soundGenerator = new fabgl::SoundGenerator(sampleRate); for (int chan=0; chanlock(); audioChannels[chan]->attachSoundGenerator(); } } @@ -113,14 +93,14 @@ void setSampleRate(uint16_t sampleRate) { // Initialise the sound driver // void initAudio() { - // make new sound generator + for (int i=0; igoIdle(); return 1; } return 0; } +void audioTaskKill(int channel) { + disableChannel(channel); +} + // Clear a sample // uint8_t clearSample(uint16_t sampleId) { diff --git a/video/audio_channel.h b/video/audio_channel.h index e5d32f9a..ebe2c056 100644 --- a/video/audio_channel.h +++ b/video/audio_channel.h @@ -2,16 +2,15 @@ #define AUDIO_CHANNEL_H #include -#include #include +#include #include #include "agon.h" #include "types.h" #include "envelopes/types.h" -extern fabgl::SoundGenerator *soundGenerator; // audio handling sub-system -extern void audioTaskAbortDelay(uint8_t channel); +extern fabgl::SoundGenerator *soundGenerator; // audio handling sub-system enum AudioState : uint8_t { // Audio channel state Idle = 0, // currently idle/silent @@ -42,24 +41,27 @@ class AudioChannel { void attachSoundGenerator(); void detachSoundGenerator(); uint8_t seekTo(uint32_t position); - void loop(); + void loop(uint64_t now); uint8_t channel() { return _channel; } + void goIdle(); + std::unique_lock lock() { return std::unique_lock(_channelMutex); } private: - WaveformGenerator * getWaveform() { return this->_waveform.get(); } - std::shared_ptr getSampleWaveform(uint16_t sampleId, AudioChannel *channelRef); - void waitForAbort(); - uint8_t getVolume(uint32_t elapsed); - uint16_t getFrequency(uint32_t elapsed); - bool isReleasing(uint32_t elapsed); - bool isFinished(uint32_t elapsed); + uint8_t _seekTo(uint32_t position); + void _goIdle(); + WaveformGenerator *getSampleWaveform(uint16_t sampleId, AudioChannel *channelRef); + uint8_t _getVolume(uint32_t elapsed); + uint16_t _getFrequency(uint32_t elapsed); + bool _isReleasing(uint32_t elapsed); + bool _isFinished(uint32_t elapsed); uint8_t _channel; uint8_t _volume; uint16_t _frequency; int32_t _duration; - uint32_t _startTime; + uint64_t _startTime; uint8_t _waveformType; - std::atomic _state; - std::shared_ptr _waveform = nullptr; + AudioState _state; + std::unique_ptr _waveform; + std::mutex _channelMutex; std::unique_ptr _volumeEnvelope; std::unique_ptr _frequencyEnvelope; }; @@ -68,7 +70,7 @@ class AudioChannel { #include "enhanced_samples_generator.h" extern std::unordered_map> samples; // Storage for the sample data -AudioChannel::AudioChannel(uint8_t channel) : _channel(channel), _state(AudioState::Idle), _volume(64), _frequency(750), _duration(-1) { +AudioChannel::AudioChannel(uint8_t channel) : _waveform(nullptr), _channel(channel), _state(AudioState::Idle), _volume(64), _frequency(750), _duration(-1) { debug_log("AudioChannel: init %d\n\r", channel); setWaveform(AUDIO_WAVE_DEFAULT); debug_log("free mem: %d\n\r", heap_caps_get_free_size(MALLOC_CAP_8BIT)); @@ -76,25 +78,40 @@ AudioChannel::AudioChannel(uint8_t channel) : _channel(channel), _state(AudioSta AudioChannel::~AudioChannel() { debug_log("AudioChannel: deiniting %d\n\r", channel()); - if (this->_waveform != nullptr) { + auto lock = std::unique_lock(_channelMutex); + detachSoundGenerator(); + debug_log("AudioChannel: deinit %d\n\r", channel()); +} + +void AudioChannel::goIdle() { + debug_log("AudioChannel: abort %d\n\r", channel()); + auto lock = std::unique_lock(_channelMutex); + if (this->_waveform) { this->_waveform->enable(false); - soundGenerator->detach(getWaveform()); } - debug_log("AudioChannel: deinit %d\n\r", channel()); + this->_state = AudioState::Idle; +} + +// expects the lock to already be held +void AudioChannel::_goIdle() { + debug_log("AudioChannel: abort %d\n\r", channel()); + if (this->_waveform) { + this->_waveform->enable(false); + } + this->_state = AudioState::Idle; } uint8_t AudioChannel::playNote(uint8_t volume, uint16_t frequency, int32_t duration) { + auto lock = std::unique_lock(_channelMutex); if (!this->_waveform) { debug_log("AudioChannel: no waveform on channel %d\n\r", channel()); return 0; } if (this->_waveformType == AUDIO_WAVE_SAMPLE && this->_volume == 0 && this->_state != AudioState::Idle) { // we're playing a silenced sample, so we're free to play a new note, so abort - this->_state = AudioState::Abort; - audioTaskAbortDelay(this->_channel); - waitForAbort(); + this->_goIdle(); } - switch (this->_state.load()) { + switch (this->_state) { case AudioState::Idle: case AudioState::Release: this->_volume = volume; @@ -104,7 +121,7 @@ uint8_t AudioChannel::playNote(uint8_t volume, uint16_t frequency, int32_t durat // zero duration means play whole sample // NB this can only work out sample duration based on sample provided // so if sample data is streaming in an explicit length should be used instead - this->_duration = ((EnhancedSamplesGenerator *)getWaveform())->getDuration(frequency); + this->_duration = ((EnhancedSamplesGenerator *)&*_waveform)->getDuration(frequency); if (this->_volumeEnvelope) { // subtract the "release" time from the duration this->_duration -= this->_volumeEnvelope->getRelease(); @@ -121,6 +138,7 @@ uint8_t AudioChannel::playNote(uint8_t volume, uint16_t frequency, int32_t durat } uint8_t AudioChannel::getStatus() { + auto lock = std::unique_lock(_channelMutex); uint8_t status = 0; if (this->_waveform && this->_waveform->enabled()) { status |= AUDIO_STATUS_ACTIVE; @@ -128,7 +146,7 @@ uint8_t AudioChannel::getStatus() { status |= AUDIO_STATUS_INDEFINITE; } } - switch (this->_state.load()) { + switch (this->_state) { case AudioState::Pending: case AudioState::Playing: case AudioState::PlayLoop: @@ -146,7 +164,7 @@ uint8_t AudioChannel::getStatus() { return status; } -std::shared_ptr AudioChannel::getSampleWaveform(uint16_t sampleId, AudioChannel *channelRef) { +WaveformGenerator *AudioChannel::getSampleWaveform(uint16_t sampleId, AudioChannel *channelRef) { if (samples.find(sampleId) != samples.end()) { auto sample = samples.at(sampleId); // if (sample->channels.find(_channel) != sample->channels.end()) { @@ -164,33 +182,34 @@ std::shared_ptr AudioChannel::getSampleWaveform(uint16_t samp // } // sample->channels[_channel] = channelRef; - return std::make_shared(sample); + return new EnhancedSamplesGenerator(sample); } debug_log("sample %d not found\n\r", sampleId); return nullptr; } uint8_t AudioChannel::setWaveform(int8_t waveformType, uint16_t sampleId) { - std::shared_ptr newWaveform = nullptr; + auto lock = std::unique_lock(_channelMutex); + WaveformGenerator *newWaveform = nullptr; switch (waveformType) { case AUDIO_WAVE_SAWTOOTH: - newWaveform = std::make_shared(); + newWaveform = new SawtoothWaveformGenerator(); break; case AUDIO_WAVE_SQUARE: - newWaveform = std::make_shared(); + newWaveform = new SquareWaveformGenerator(); break; case AUDIO_WAVE_SINE: - newWaveform = std::make_shared(); + newWaveform = new SineWaveformGenerator(); break; case AUDIO_WAVE_TRIANGLE: - newWaveform = std::make_shared(); + newWaveform = new TriangleWaveformGenerator(); break; case AUDIO_WAVE_NOISE: - newWaveform = std::make_shared(); + newWaveform = new NoiseWaveformGenerator(); break; case AUDIO_WAVE_VICNOISE: - newWaveform = std::make_shared(); + newWaveform = new VICNoiseGenerator(); break; case AUDIO_WAVE_SAMPLE: // Buffer-based sample playback @@ -216,15 +235,13 @@ uint8_t AudioChannel::setWaveform(int8_t waveformType, uint16_t sampleId) { if (this->_state != AudioState::Idle) { debug_log("AudioChannel: aborting current playback\n\r"); // some kind of playback is happening, so abort any current task delay to allow playback to end - this->_state = AudioState::Abort; - audioTaskAbortDelay(this->_channel); - waitForAbort(); + this->_goIdle(); } if (this->_waveform != nullptr) { debug_log("AudioChannel: detaching old waveform\n\r"); detachSoundGenerator(); } - this->_waveform = newWaveform; + this->_waveform.reset(newWaveform); _waveformType = waveformType; attachSoundGenerator(); debug_log("AudioChannel: setWaveform %d done on channel %d\n\r", waveformType, channel()); @@ -235,6 +252,7 @@ uint8_t AudioChannel::setWaveform(int8_t waveformType, uint16_t sampleId) { } uint8_t AudioChannel::setVolume(uint8_t volume) { + auto lock = std::unique_lock(_channelMutex); debug_log("AudioChannel: setVolume %d on channel %d\n\r", volume, channel()); if (volume == 255) { return this->_volume; @@ -244,8 +262,7 @@ uint8_t AudioChannel::setVolume(uint8_t volume) { } if (this->_waveform) { - waitForAbort(); - switch (this->_state.load()) { + switch (this->_state) { case AudioState::Idle: if (volume > 0) { // new note playback @@ -279,8 +296,7 @@ uint8_t AudioChannel::setVolume(uint8_t volume) { this->_waveform->setVolume(volume); if (volume == 0 && this->_waveformType != AUDIO_WAVE_SAMPLE) { // we're going silent, so abort any current playback - this->_state = AudioState::Abort; - audioTaskAbortDelay(this->_channel); + this->_goIdle(); } break; } @@ -290,12 +306,12 @@ uint8_t AudioChannel::setVolume(uint8_t volume) { } uint8_t AudioChannel::setFrequency(uint16_t frequency) { + auto lock = std::unique_lock(_channelMutex); debug_log("AudioChannel: setFrequency %d on channel %d\n\r", frequency, channel()); this->_frequency = frequency; if (this->_waveform) { - waitForAbort(); - switch (this->_state.load()) { + switch (this->_state) { case AudioState::Pending: case AudioState::PlayLoop: case AudioState::Release: @@ -310,6 +326,7 @@ uint8_t AudioChannel::setFrequency(uint16_t frequency) { } uint8_t AudioChannel::setDuration(int32_t duration) { + auto lock = std::unique_lock(_channelMutex); debug_log("AudioChannel: setDuration %d on channel %d\n\r", duration, channel()); if (duration == 0xFFFFFF) { duration = -1; @@ -317,14 +334,13 @@ uint8_t AudioChannel::setDuration(int32_t duration) { this->_duration = duration; if (this->_waveform) { - waitForAbort(); - switch (this->_state.load()) { + switch (this->_state) { case AudioState::Idle: // kick off a new note playback this->_state = AudioState::Pending; break; case AudioState::Playing: - audioTaskAbortDelay(this->_channel); + this->_goIdle(); break; default: // any other state we should be looping so it will just get picked up @@ -336,26 +352,29 @@ uint8_t AudioChannel::setDuration(int32_t duration) { } uint8_t AudioChannel::setVolumeEnvelope(std::unique_ptr envelope) { + auto lock = std::unique_lock(_channelMutex); this->_volumeEnvelope = std::move(envelope); if (envelope && this->_state == AudioState::Playing) { // swap to looping + this->_goIdle(); this->_state = AudioState::PlayLoop; - audioTaskAbortDelay(this->_channel); } return 1; } uint8_t AudioChannel::setFrequencyEnvelope(std::unique_ptr envelope) { + auto lock = std::unique_lock(_channelMutex); this->_frequencyEnvelope = std::move(envelope); if (envelope && this->_state == AudioState::Playing) { // swap to looping + this->_goIdle(); this->_state = AudioState::PlayLoop; - audioTaskAbortDelay(this->_channel); } return 1; } uint8_t AudioChannel::setSampleRate(uint16_t sampleRate) { + auto lock = std::unique_lock(_channelMutex); if (this->_waveform) { this->_waveform->setSampleRate(sampleRate); return 1; @@ -364,14 +383,16 @@ uint8_t AudioChannel::setSampleRate(uint16_t sampleRate) { } uint8_t AudioChannel::setDutyCycle(uint8_t dutyCycle) { + auto lock = std::unique_lock(_channelMutex); if (this->_waveform && this->_waveformType == AUDIO_WAVE_SQUARE) { - ((SquareWaveformGenerator *)getWaveform())->setDutyCycle(dutyCycle); + ((SquareWaveformGenerator *)&*_waveform)->setDutyCycle(dutyCycle); return 1; } return 0; } uint8_t AudioChannel::setParameter(uint8_t parameter, uint16_t value) { + auto lock = std::unique_lock(_channelMutex); if (this->_waveform) { bool use16Bit = parameter & AUDIO_PARAM_16BIT; auto param = parameter & AUDIO_PARAM_MASK; @@ -393,49 +414,55 @@ uint8_t AudioChannel::setParameter(uint8_t parameter, uint16_t value) { return 0; } +// caller must hold channel lock void AudioChannel::attachSoundGenerator() { if (this->_waveform) { - soundGenerator->attach(getWaveform()); + auto lock = std::unique_lock(soundGeneratorMutex); + soundGenerator->attach(&*_waveform); } } +// caller must hold channel lock void AudioChannel::detachSoundGenerator() { if (this->_waveform) { - soundGenerator->detach(getWaveform()); + auto lock = std::unique_lock(soundGeneratorMutex); + soundGenerator->detach(&*_waveform); } - this->_state.store(AudioState::Idle); + this->_state = AudioState::Idle; } -uint8_t AudioChannel::seekTo(uint32_t position) { +// caller must hold channel lock +uint8_t AudioChannel::_seekTo(uint32_t position) { if (this->_waveformType == AUDIO_WAVE_SAMPLE) { - ((EnhancedSamplesGenerator *)getWaveform())->seekTo(position); + ((EnhancedSamplesGenerator *)&*_waveform)->seekTo(position); return 1; } return 0; } -void AudioChannel::waitForAbort() { - while (this->_state == AudioState::Abort) { - // wait for abort to complete - vTaskDelay(1); - } +uint8_t AudioChannel::seekTo(uint32_t position) { + auto lock = std::unique_lock(_channelMutex); + return _seekTo(position); } -uint8_t AudioChannel::getVolume(uint32_t elapsed) { +// caller must hold channel lock +uint8_t AudioChannel::_getVolume(uint32_t elapsed) { if (this->_volumeEnvelope) { return this->_volumeEnvelope->getVolume(this->_volume, elapsed, this->_duration); } return this->_volume; } -uint16_t AudioChannel::getFrequency(uint32_t elapsed) { +// caller must hold channel lock +uint16_t AudioChannel::_getFrequency(uint32_t elapsed) { if (this->_frequencyEnvelope) { return this->_frequencyEnvelope->getFrequency(this->_frequency, elapsed, this->_duration); } return this->_frequency; } -bool AudioChannel::isReleasing(uint32_t elapsed) { +// caller must hold channel lock +bool AudioChannel::_isReleasing(uint32_t elapsed) { if (this->_volumeEnvelope) { return this->_volumeEnvelope->isReleasing(elapsed, this->_duration); } @@ -445,7 +472,8 @@ bool AudioChannel::isReleasing(uint32_t elapsed) { return elapsed >= this->_duration; } -bool AudioChannel::isFinished(uint32_t elapsed) { +// caller must hold channel lock +bool AudioChannel::_isFinished(uint32_t elapsed) { if (this->_volumeEnvelope) { return this->_volumeEnvelope->isFinished(elapsed, this->_duration); } @@ -455,73 +483,69 @@ bool AudioChannel::isFinished(uint32_t elapsed) { return (elapsed >= this->_duration); } -void AudioChannel::loop() { - int delay = 0; +void AudioChannel::loop(uint64_t now) { + auto lock = std::unique_lock(_channelMutex); - switch (this->_state.load()) { + switch (this->_state) { case AudioState::Pending: debug_log("AudioChannel: play %d,%d,%d,%d\n\r", channel(), this->_volume, this->_frequency, this->_duration); // we have a new note to play - this->_startTime = millis(); + this->_startTime = now; // set our initial volume and frequency - this->_waveform->setVolume(this->getVolume(0)); - this->seekTo(0); - this->_waveform->setFrequency(this->getFrequency(0)); + this->_waveform->setVolume(this->_getVolume(0)); + this->_seekTo(0); + this->_waveform->setFrequency(this->_getFrequency(0)); this->_waveform->enable(true); // if we have an envelope then we loop, otherwise just delay for duration if (this->_volumeEnvelope || this->_frequencyEnvelope) { this->_state = AudioState::PlayLoop; } else { this->_state = AudioState::Playing; - // if delay value is negative then this delays for a super long time - delay = this->_duration; } break; case AudioState::Playing: if (this->_duration >= 0) { // simple playback - delay until we have reached our duration - uint32_t elapsed = millis() - this->_startTime; - debug_log("AudioChannel: %d elapsed %d\n\r", channel(), elapsed); + uint32_t elapsed = now - this->_startTime; + //debug_log("AudioChannel: %d elapsed %d\n\r", channel(), elapsed); if (elapsed >= this->_duration) { this->_waveform->enable(false); - debug_log("AudioChannel: %d end\n\r", channel()); + //debug_log("AudioChannel: %d end\n\r", channel()); this->_state = AudioState::Idle; } else { - debug_log("AudioChannel: %d loop (%d)\n\r", channel(), this->_duration - elapsed); - delay = this->_duration - elapsed; + //debug_log("AudioChannel: %d loop (%d)\n\r", channel(), this->_duration - elapsed); } } else { // our duration is indefinite, so delay for a long time - debug_log("AudioChannel: %d loop (indefinite playback)\n\r", channel()); - delay = -1; + //debug_log("AudioChannel: %d loop (indefinite playback)\n\r", channel()); } break; // loop and release states used for envelopes case AudioState::PlayLoop: { - uint32_t elapsed = millis() - this->_startTime; - if (isReleasing(elapsed)) { + uint32_t elapsed = now - this->_startTime; + if (_isReleasing(elapsed)) { debug_log("AudioChannel: releasing %d...\n\r", channel()); this->_state = AudioState::Release; } // update volume and frequency as appropriate if (this->_volumeEnvelope) - this->_waveform->setVolume(this->getVolume(elapsed)); + this->_waveform->setVolume(this->_getVolume(elapsed)); if (this->_frequencyEnvelope) - this->_waveform->setFrequency(this->getFrequency(elapsed)); + this->_waveform->setFrequency(this->_getFrequency(elapsed)); break; } case AudioState::Release: { - uint32_t elapsed = millis() - this->_startTime; + uint32_t elapsed = now - this->_startTime; // update volume and frequency as appropriate if (this->_volumeEnvelope) - this->_waveform->setVolume(this->getVolume(elapsed)); + this->_waveform->setVolume(this->_getVolume(elapsed)); if (this->_frequencyEnvelope) - this->_waveform->setFrequency(this->getFrequency(elapsed)); + this->_waveform->setFrequency(this->_getFrequency(elapsed)); - if (isFinished(elapsed)) { + if (_isFinished(elapsed)) { this->_waveform->enable(false); debug_log("AudioChannel: end (released %d)\n\r", channel()); this->_state = AudioState::Idle; @@ -534,17 +558,10 @@ void AudioChannel::loop() { debug_log("AudioChannel: abort %d\n\r", channel()); this->_state = AudioState::Idle; break; - } - /* - * in USERSPACE, xTaskAbortDelay is not implemented, so audioTaskAbortDelay - * won't work, and waitForAbort() will freeze vdu stream processor for the - * whole delay duration. So don't delay here, allowing 'abort' state to be - * transitioned rapidly (at some small CPU cost). - if (delay > 0) { - // -TM- - vTaskDelay(pdMS_TO_TICKS(delay)); - }*/ + case AudioState::Idle: + break; + } } #endif // AUDIO_CHANNEL_H From 9a87197bde64d29d73b945950bc272fe2ca3e12d Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Thu, 18 Jul 2024 21:42:32 +0100 Subject: [PATCH 17/25] Fix crash when starting plasma.bin, https://github.com/jblang/agon-demos --- video/context.h | 2 +- video/context/fonts.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/video/context.h b/video/context.h index fd9bce7d..529394ac 100644 --- a/video/context.h +++ b/video/context.h @@ -239,7 +239,7 @@ class Context { bool usingSystemFont(); char getScreenChar(uint8_t x, uint8_t y); char getScreenCharAt(uint16_t px, uint16_t py); - void mapCharToBitmap(char c, uint16_t bitmapId); + void mapCharToBitmap(uint8_t c, uint16_t bitmapId); void unmapBitmapFromChars(uint16_t bitmapId); void resetCharToBitmap(); diff --git a/video/context/fonts.h b/video/context/fonts.h index 265c29c5..0f66f4b3 100644 --- a/video/context/fonts.h +++ b/video/context/fonts.h @@ -195,7 +195,7 @@ char Context::getScreenCharAt(uint16_t px, uint16_t py) { return getScreenChar(toScreenCoordinates(px, py)); } -void Context::mapCharToBitmap(char c, uint16_t bitmapId) { +void Context::mapCharToBitmap(uint8_t c, uint16_t bitmapId) { auto bitmap = getBitmap(bitmapId); if (bitmap) { charToBitmap[c] = bitmapId; From a95cc701642da5c6000fa0c47446d8ee51a2e2d1 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Sun, 25 Aug 2024 12:34:57 +0100 Subject: [PATCH 18/25] Build fix post-merge of c8 2.10.0 --- video/types.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/video/types.h b/video/types.h index 769365fb..239d6d7e 100644 --- a/video/types.h +++ b/video/types.h @@ -115,6 +115,12 @@ class psram_allocator } }; +template +bool operator==(const psram_allocator&, const psram_allocator&) { return true; } + +template +bool operator!=(const psram_allocator&a, const psram_allocator&b) { return !(a == b); } + // Typically we do not need a deleter because the regular one can handle PSRAM deallocations just fine, // but for completeness, here it is. From 80b40869f9b97c9abdd9f20b7b9b63a1f51616ba Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Wed, 28 Aug 2024 19:45:49 +0100 Subject: [PATCH 19/25] Clang (macos) build fixes post-2.10.0 merge --- video/vdu_buffered.h | 36 ++++++++++++++++++------------------ video/vdu_sys.h | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/video/vdu_buffered.h b/video/vdu_buffered.h index fccdda32..6fccfc02 100644 --- a/video/vdu_buffered.h +++ b/video/vdu_buffered.h @@ -253,7 +253,7 @@ void IRAM_ATTR VDUStreamProcessor::vdu_sys_buffered() { } auto matrixSize = getMatrixSize(bufferId); if (matrixSize.value != 0) { - float transform[matrixSize.size()] = {0.0f}; + float transform[matrixSize.size()]; if (getMatrixFromBuffer(bufferId, transform, matrixSize)) { force_debug_log("buffer contains a %d x %d matrix with contents:\n\r", matrixSize.rows, matrixSize.columns); for (int i = 0; i < matrixSize.rows; i++) { @@ -1580,7 +1580,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman MatrixSize size; size.rows = dimensions + 1; size.columns = size.rows; - float transform[size.size()] = {0.0f}; + float transform[size.size()]; for (int i = 0; i < size.rows; i++) { transform[i * size.rows + i] = 1.0f; } @@ -1657,7 +1657,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman } break; case AFFINE_SCALE: { // scale by a given factor in each dimension - float scales[dimensions] = {0.0f}; + float scales[dimensions]; if (!readFloatArguments(scales, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1667,7 +1667,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman } break; case AFFINE_TRANSLATE: { // translate by a given amount - float translateXY[dimensions] = {0.0f}; + float translateXY[dimensions]; if (!readFloatArguments(translateXY, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1677,7 +1677,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman } break; case AFFINE_TRANSLATE_OS_COORDS: { // translate by a given amount of pixels where x and y match current coordinate system scaling - float translateXY[dimensions] = {0.0f}; + float translateXY[dimensions]; if (!readFloatArguments(translateXY, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1690,7 +1690,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman } break; case AFFINE_SHEAR: { // shear by a given amount - float shearXY[dimensions] = {0.0f}; + float shearXY[dimensions]; if (!readFloatArguments(shearXY, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1702,7 +1702,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman case AFFINE_SKEW_RAD: { // skew by a given amount (angle) bool conversion = op == AFFINE_SKEW ? DEG_TO_RAD : 1.0f; - float skewXY[dimensions] = {0.0f}; + float skewXY[dimensions]; if (!readFloatArguments(skewXY, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1744,10 +1744,10 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman if (!replace) { // we are combining - for now, only if the existing matrix is the same size // TODO consider handling different size matrices - could combine at larger size, and then truncate - float existing[size.size()] = {0.0f}; + float existing[size.size()]; if (getMatrixFromBuffer(bufferId, existing, size, false)) { // combine the two matrices together - float newTransform[size.size()] = {0.0f}; + float newTransform[size.size()]; dspm_mult_f32(transform, existing, newTransform, size.rows, size.columns, size.columns); // copy data from matrix back to our working transform matrix memcpy(transform, newTransform, size.sizeBytes()); @@ -1775,7 +1775,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma const bool useAdvancedOffsets = command & MATRIX_OP_ADVANCED_OFFSETS; const bool useBufferValue = command & MATRIX_OP_BUFFER_VALUE; - float matrix[size.size()] = {0.0f}; + float matrix[size.size()]; switch (op) { case MATRIX_SET: { @@ -1817,7 +1817,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma case MATRIX_DIAGONAL: { // diagonal matrix with given values auto argCount = fabgl::imin(size.rows, size.columns); - float args[argCount] = {0.0f}; + float args[argCount]; if (!readFloatArguments(args, argCount, useBufferValue, useAdvancedOffsets, false)) { return; } @@ -1830,7 +1830,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma auto sourceId1 = readWord_t(); if (sourceId1 == -1) return; auto sourceId2 = readWord_t(); if (sourceId2 == -1) return; // Get the matrixes, for our target size, padding or truncating as necessary - float source[size.size()] = {0.0f}; + float source[size.size()]; if (!getMatrixFromBuffer(sourceId1, matrix, size) || !getMatrixFromBuffer(sourceId2, source, size)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d or %d\n\r", sourceId1, sourceId2); return; @@ -1845,7 +1845,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma auto sourceId1 = readWord_t(); if (sourceId1 == -1) return; auto sourceId2 = readWord_t(); if (sourceId2 == -1) return; // Get the matrixes, for our target size, padding or truncating as necessary - float source[size.size()] = {0.0f}; + float source[size.size()]; if (!getMatrixFromBuffer(sourceId1, matrix, size) || !getMatrixFromBuffer(sourceId2, source, size)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d or %d\n\r", sourceId1, sourceId2); return; @@ -1871,14 +1871,14 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma MatrixSize resultSize; resultSize.rows = dimensions; resultSize.columns = dimensions; - float source1[resultSize.size()] = {0.0f}; - float source2[resultSize.size()] = {0.0f}; + float source1[resultSize.size()]; + float source2[resultSize.size()]; if (!getMatrixFromBuffer(sourceId1, source1, resultSize) || !getMatrixFromBuffer(sourceId2, source2, resultSize)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d or %d\n\r", sourceId1, sourceId2); return; } // multiply values in source1 and source2 - float result[resultSize.size()] = {0.0f}; + float result[resultSize.size()]; dspm_mult_f32(source1, source2, result, resultSize.rows, resultSize.columns, resultSize.columns); for (int row = 0; row < size.rows; row++) { for (int column = 0; column < size.columns; column++) { @@ -1913,7 +1913,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma debug_log("bufferMatrixManipulate: source matrix %d not found\n\r", sourceId); return; } - float source[sourceSize.size()] = {0.0f}; + float source[sourceSize.size()]; if (!getMatrixFromBuffer(sourceId, source, sourceSize)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d\n\r", sourceId); return; @@ -1940,7 +1940,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma return; } // read source matrix - float source[sourceSize.size()] = {0.0f}; + float source[sourceSize.size()]; if (!getMatrixFromBuffer(sourceId, source, sourceSize)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d\n\r", sourceId); return; diff --git a/video/vdu_sys.h b/video/vdu_sys.h index d755b45f..5865ca62 100644 --- a/video/vdu_sys.h +++ b/video/vdu_sys.h @@ -382,7 +382,7 @@ void VDUStreamProcessor::sendCursorPosition() { // void VDUStreamProcessor::sendScreenChar(char c) { uint8_t packet[] = { - c, + (uint8_t)c, }; send_packet(PACKET_SCRCHAR, sizeof packet, packet); } From f1941313a8c6d81f4869577a54b03d5113d21db2 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Tue, 17 Sep 2024 08:45:19 +0100 Subject: [PATCH 20/25] Conditional compilation of some emulator-specific code --- video/video.ino | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/video/video.ino b/video/video.ino index 6eb30082..67674c92 100644 --- a/video/video.ino +++ b/video/video.ino @@ -52,7 +52,11 @@ #define DEBUG 1 // Serial Debug Mode: 1 = enable #define SERIALBAUDRATE 115200 +#ifdef USERSPACE extern uint32_t startup_screen_mode; /* in rust_glue.cpp */ +#else /* !USERSPACE */ +#define startup_screen_mode 0 +#endif /* !USERSPACE */ HardwareSerial DBGSerial(0); @@ -117,11 +121,15 @@ void loop() { } void processLoop(void * parameter) { +#ifdef USERSPACE uint32_t count = 0; +#endif /* USERSPACE */ while (true) { +#ifdef USERSPACE if ((count & 0x7f) == 0) delay(1 /* -TM- ms */); count++; +#endif /* USERSPACE */ #ifdef VDP_USE_WDT esp_task_wdt_reset(); From ba2646c6cbb6dbeda5d636b46fb8f4f9c9b04c92 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Fri, 11 Oct 2024 09:56:05 +0100 Subject: [PATCH 21/25] Conditional compilation for USERSPACE around DEBUG define --- video/video.ino | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/video/video.ino b/video/video.ino index 67674c92..70f07b43 100644 --- a/video/video.ino +++ b/video/video.ino @@ -49,7 +49,14 @@ #include #include -#define DEBUG 1 // Serial Debug Mode: 1 = enable +// Serial Debug Mode: 1 = enable +// Always enabled on the emulator, to support --verbose mode +#ifdef USERSPACE +# define DEBUG 1 +#else /* !USERSPACE */ +# define DEBUG 0 +#endif /* USERSPACE */ + #define SERIALBAUDRATE 115200 #ifdef USERSPACE From ca49625ca661397074ff9cbc4737215617a64079 Mon Sep 17 00:00:00 2001 From: Tom Morton Date: Mon, 23 Dec 2024 08:52:47 +0000 Subject: [PATCH 22/25] Disable custom terminal keyboard handler. fabgl does this --- video/video.ino | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/video/video.ino b/video/video.ino index 70f07b43..fb764212 100644 --- a/video/video.ino +++ b/video/video.ino @@ -196,16 +196,6 @@ void do_keyboard() { } } -// Handle the keyboard: CP/M Terminal Mode -// -void do_keyboard_terminal() { - uint8_t ascii; - if (getKeyboardKey(&ascii)) { - // send raw byte straight to z80 - processor->writeByte(ascii); - } -} - // Handle the mouse // void do_mouse() { @@ -331,8 +321,6 @@ bool processTerminal() { } break; case TerminalState::Suspended: { // Terminal temporarily deactivated, so pass on to VDU system - // but keep processing keyboard input - do_keyboard_terminal(); return false; } break; case TerminalState::Enabling: { @@ -365,7 +353,6 @@ bool processTerminal() { terminalState = TerminalState::Enabled; } break; case TerminalState::Enabled: { - do_keyboard_terminal(); // Write anything read from z80 to the screen // but do this a byte at a time, as VDU commands after a "suspend" will get lost if (processor->byteAvailable()) { From e7fc7d6a5ee3c571b79dd4c8da19f56e54e4fa65 Mon Sep 17 00:00:00 2001 From: Steve Sims Date: Thu, 16 Jan 2025 16:22:24 +0000 Subject: [PATCH 23/25] add calls to clear variable size float arrays --- video/vdu_buffered.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/video/vdu_buffered.h b/video/vdu_buffered.h index 7bb10e7a..09d92566 100644 --- a/video/vdu_buffered.h +++ b/video/vdu_buffered.h @@ -1581,6 +1581,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman size.rows = dimensions + 1; size.columns = size.rows; float transform[size.size()]; + memset(transform, 0, sizeof(transform)); for (int i = 0; i < size.rows; i++) { transform[i * size.rows + i] = 1.0f; } @@ -1658,6 +1659,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman case AFFINE_SCALE: { // scale by a given factor in each dimension float scales[dimensions]; + memset(scales, 0, sizeof(scales)); if (!readFloatArguments(scales, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1668,6 +1670,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman case AFFINE_TRANSLATE: { // translate by a given amount float translateXY[dimensions]; + memset(translateXY, 0, sizeof(translateXY)); if (!readFloatArguments(translateXY, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1678,6 +1681,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman case AFFINE_TRANSLATE_OS_COORDS: { // translate by a given amount of pixels where x and y match current coordinate system scaling float translateXY[dimensions]; + memset(translateXY, 0, sizeof(translateXY)); if (!readFloatArguments(translateXY, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1691,6 +1695,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman case AFFINE_SHEAR: { // shear by a given amount float shearXY[dimensions]; + memset(shearXY, 0, sizeof(shearXY)); if (!readFloatArguments(shearXY, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1703,6 +1708,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman // skew by a given amount (angle) bool conversion = op == AFFINE_SKEW ? DEG_TO_RAD : 1.0f; float skewXY[dimensions]; + memset(skewXY, 0, sizeof(skewXY)); if (!readFloatArguments(skewXY, dimensions, useBufferValue, useAdvancedOffsets, useMultiFormat)) { return; } @@ -1745,6 +1751,7 @@ void VDUStreamProcessor::bufferAffineTransform(uint16_t bufferId, uint8_t comman // we are combining - for now, only if the existing matrix is the same size // TODO consider handling different size matrices - could combine at larger size, and then truncate float existing[size.size()]; + memset(existing, 0, sizeof(existing)); if (getMatrixFromBuffer(bufferId, existing, size, false)) { // combine the two matrices together float newTransform[size.size()]; @@ -1776,6 +1783,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma const bool useBufferValue = command & MATRIX_OP_BUFFER_VALUE; float matrix[size.size()]; + memset(matrix, 0, sizeof(matrix)); switch (op) { case MATRIX_SET: { @@ -1818,6 +1826,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma // diagonal matrix with given values auto argCount = fabgl::imin(size.rows, size.columns); float args[argCount]; + memset(args, 0, sizeof(args)); if (!readFloatArguments(args, argCount, useBufferValue, useAdvancedOffsets, false)) { return; } @@ -1831,6 +1840,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma auto sourceId2 = readWord_t(); if (sourceId2 == -1) return; // Get the matrixes, for our target size, padding or truncating as necessary float source[size.size()]; + memset(source, 0, sizeof(source)); if (!getMatrixFromBuffer(sourceId1, matrix, size) || !getMatrixFromBuffer(sourceId2, source, size)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d or %d\n\r", sourceId1, sourceId2); return; @@ -1846,6 +1856,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma auto sourceId2 = readWord_t(); if (sourceId2 == -1) return; // Get the matrixes, for our target size, padding or truncating as necessary float source[size.size()]; + memset(source, 0, sizeof(source)); if (!getMatrixFromBuffer(sourceId1, matrix, size) || !getMatrixFromBuffer(sourceId2, source, size)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d or %d\n\r", sourceId1, sourceId2); return; @@ -1873,12 +1884,15 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma resultSize.columns = dimensions; float source1[resultSize.size()]; float source2[resultSize.size()]; + memset(source1, 0, sizeof(source1)); + memset(source2, 0, sizeof(source2)); if (!getMatrixFromBuffer(sourceId1, source1, resultSize) || !getMatrixFromBuffer(sourceId2, source2, resultSize)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d or %d\n\r", sourceId1, sourceId2); return; } // multiply values in source1 and source2 float result[resultSize.size()]; + memset(result, 0, sizeof(result)); dspm_mult_f32(source1, source2, result, resultSize.rows, resultSize.columns, resultSize.columns); for (int row = 0; row < size.rows; row++) { for (int column = 0; column < size.columns; column++) { @@ -1914,6 +1928,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma return; } float source[sourceSize.size()]; + memset(source, 0, sizeof(source)); if (!getMatrixFromBuffer(sourceId, source, sourceSize)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d\n\r", sourceId); return; @@ -1941,6 +1956,7 @@ void VDUStreamProcessor::bufferMatrixManipulate(uint16_t bufferId, uint8_t comma } // read source matrix float source[sourceSize.size()]; + memset(source, 0, sizeof(source)); if (!getMatrixFromBuffer(sourceId, source, sourceSize)) { debug_log("bufferMatrixManipulate: failed to read matrix from buffer %d\n\r", sourceId); return; From f3846020dfa172f059eb540788b49e22b0b1beb0 Mon Sep 17 00:00:00 2001 From: Steve Sims Date: Thu, 16 Jan 2025 16:30:55 +0000 Subject: [PATCH 24/25] fix compiler warning --- video/video.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/video/video.ino b/video/video.ino index eb088322..98a1757c 100644 --- a/video/video.ino +++ b/video/video.ino @@ -52,8 +52,10 @@ // Serial Debug Mode: 1 = enable // Always enabled on the emulator, to support --verbose mode #ifdef USERSPACE +#undef DEBUG # define DEBUG 1 #else /* !USERSPACE */ +#undef DEBUG # define DEBUG 0 #endif /* USERSPACE */ From 1e5fa083188efae5a151f1422e2f2b2bb0e3b415 Mon Sep 17 00:00:00 2001 From: Steve Sims Date: Thu, 16 Jan 2025 16:45:01 +0000 Subject: [PATCH 25/25] minor code formatting tweaks --- video/vdu_buffered.h | 6 +++--- video/video.ino | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/video/vdu_buffered.h b/video/vdu_buffered.h index 09d92566..1fd06cda 100644 --- a/video/vdu_buffered.h +++ b/video/vdu_buffered.h @@ -2117,8 +2117,8 @@ void VDUStreamProcessor::bufferTransformBitmap(uint16_t bufferId, uint8_t option debug_log("bufferTransformBitmap: width %d, height %d, xOffset %d, yOffset %d\n\r", width, height, xOffset, yOffset); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { // calculate the source pixel // NB we will need to adjust x,y here if we are auto-translating pos[0] = (float)x + xOffset; @@ -2130,7 +2130,7 @@ void VDUStreamProcessor::bufferTransformBitmap(uint16_t bufferId, uint8_t option } destination[(int)y * width + (int)x] = srcPixel; } - } + } // save new bitmap data to target buffer bufferClear(bufferId); diff --git a/video/video.ino b/video/video.ino index 98a1757c..e13d1b43 100644 --- a/video/video.ino +++ b/video/video.ino @@ -136,7 +136,9 @@ void processLoop(void * parameter) { while (true) { #ifdef USERSPACE - if ((count & 0x7f) == 0) delay(1 /* -TM- ms */); + if ((count & 0x7f) == 0) { + delay(1 /* -TM- ms */); + } count++; #endif /* USERSPACE */ @@ -353,8 +355,8 @@ bool processTerminal() { if (seq[0] == 'F') { uint32_t fontnum = textToWord(seq + 1); if (fontnum >= 0) { - auto font = fonts[fontnum]; // get shared_ptr to font -- was fonts[bufferID] - if (font != nullptr && font->chptr == nullptr) { // check it's defined + auto font = fonts[fontnum]; // get shared_ptr to font -- was fonts[bufferID] + if (font != nullptr && font->chptr == nullptr) { // check it's defined Terminal->loadFont(font.get()); } }