diff --git a/video/agon.h b/video/agon.h index b17231b..79a6de1 100644 --- a/video/agon.h +++ b/video/agon.h @@ -68,6 +68,7 @@ #define PACKET_MOUSE 0x09 // Mouse data #define AUDIO_CHANNELS 3 // Default number of audio channels +#define AUDIO_DEFAULT_SAMPLE_RATE 16384 // Default sample rate #define MAX_AUDIO_CHANNELS 32 // Maximum number of audio channels #define PLAY_SOUND_PRIORITY 3 // Sound driver task priority with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest @@ -84,6 +85,9 @@ #define AUDIO_CMD_ENABLE 8 // Enables a channel #define AUDIO_CMD_DISABLE 9 // Disables (destroys) a channel #define AUDIO_CMD_RESET 10 // Reset audio channel +#define AUDIO_CMD_SEEK 11 // Seek to a position in a sample +#define AUDIO_CMD_DURATION 12 // Set the duration of a channel +#define AUDIO_CMD_SAMPLERATE 13 // Set the samplerate for channel or underlying audio system #define AUDIO_WAVE_DEFAULT 0 // Default waveform (Square wave) #define AUDIO_WAVE_SQUARE 0 // Square wave @@ -97,11 +101,22 @@ #define AUDIO_SAMPLE_LOAD 0 // Send a sample to the VDP #define AUDIO_SAMPLE_CLEAR 1 // Clear/delete a sample -#define AUDIO_SAMPLE_FROM_BUFFER 2 // Load a sample from a buffer +#define AUDIO_SAMPLE_FROM_BUFFER 2 // Load a sample from a buffer +#define AUDIO_SAMPLE_SET_FREQUENCY 3 // Set the base frequency of a sample +#define AUDIO_SAMPLE_BUFFER_SET_FREQUENCY 4 // Set the base frequency of a sample (using buffer ID) +#define AUDIO_SAMPLE_SET_REPEAT_START 5 // Set the repeat start point of a sample +#define AUDIO_SAMPLE_BUFFER_SET_REPEAT_START 6 // Set the repeat start point of a sample (using buffer ID) +#define AUDIO_SAMPLE_SET_REPEAT_LENGTH 7 // Set the repeat length of a sample +#define AUDIO_SAMPLE_BUFFER_SET_REPEAT_LENGTH 8 // Set the repeat length of a sample (using buffer ID) #define AUDIO_SAMPLE_DEBUG_INFO 0x10 // Get debug info about a sample +#define AUDIO_DEFAULT_FREQUENCY 523 // Default sample frequency (C5, or C above middle C) + #define AUDIO_FORMAT_8BIT_SIGNED 0 // 8-bit signed sample #define AUDIO_FORMAT_8BIT_UNSIGNED 1 // 8-bit unsigned sample +#define AUDIO_FORMAT_DATA_MASK 7 // data bit mask for format +#define AUDIO_FORMAT_WITH_RATE 8 // OR this with the format to indicate a sample rate follows +#define AUDIO_FORMAT_TUNEABLE 16 // OR this with the format to indicate sample can be tuned (frequency adjustable) #define AUDIO_ENVELOPE_NONE 0 // No envelope #define AUDIO_ENVELOPE_ADSR 1 // Simple ADSR volume envelope @@ -118,12 +133,14 @@ #define AUDIO_STATUS_HAS_VOLUME_ENVELOPE 0x08 // Channel has a volume envelope set #define AUDIO_STATUS_HAS_FREQUENCY_ENVELOPE 0x10 // Channel has a frequency envelope set -#define AUDIO_STATE_IDLE 0 // Channel is idle/silent -#define AUDIO_STATE_PENDING 1 // Channel is pending (note will be played next loop call) -#define AUDIO_STATE_PLAYING 2 // Channel is playing a note (passive) -#define AUDIO_STATE_PLAY_LOOP 3 // Channel is in active note playing loop -#define AUDIO_STATE_RELEASE 4 // Channel is releasing a note -#define AUDIO_STATE_ABORT 5 // Channel is aborting a note +enum AudioState : uint8_t { // Audio channel state + Idle = 0, // currently idle/silent + Pending, // note will be played next loop call + Playing, // playing (passive) + PlayLoop, // active playing loop (used when an envelope is active) + Release, // in "release" phase + Abort // aborting a note +}; // Mouse commands #define MOUSE_ENABLE 0 // Enable mouse diff --git a/video/agon_audio.h b/video/agon_audio.h index 2ea9deb..288abe7 100644 --- a/video/agon_audio.h +++ b/video/agon_audio.h @@ -20,27 +20,27 @@ #include "types.h" // audio channels and their associated tasks -std::unordered_map> audio_channels; +std::unordered_map> audioChannels; std::vector> audioHandlers; -std::unordered_map> samples; // Storage for the sample data +std::unordered_map> samples; // Storage for the sample data -fabgl::SoundGenerator SoundGenerator; // The audio class +std::unique_ptr soundGenerator; // audio handling sub-system // Audio channel driver task // -void audio_driver(void * parameters) { +void audioDriver(void * parameters) { uint8_t channel = *(uint8_t *)parameters; - audio_channels[channel] = make_shared_psram(channel); + audioChannels[channel] = make_shared_psram(channel); while (true) { - audio_channels[channel]->loop(); + audioChannels[channel]->loop(); vTaskDelay(1); } } -void init_audio_channel(uint8_t channel) { - xTaskCreatePinnedToCore(audio_driver, "audio_driver", +void initAudioChannel(uint8_t channel) { + xTaskCreatePinnedToCore(audioDriver, "audioDriver", 4096, // This stack size can be checked & adjusted by reading the Stack Highwater &channel, // Parameters PLAY_SOUND_PRIORITY, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest. @@ -59,35 +59,61 @@ void audioTaskKill(uint8_t channel) { if (audioHandlers[channel]) { vTaskDelete(audioHandlers[channel]); audioHandlers[channel] = nullptr; - audio_channels.erase(channel); + audioChannels.erase(channel); debug_log("audioTaskKill: channel %d killed\n\r", channel); } else { debug_log("audioTaskKill: channel %d not found\n\r", channel); } } +// Change the sample rate +// +void setSampleRate(uint16_t sampleRate) { + // make a new sound generator and re-attach all our active channels + if (sampleRate == 65535) { + sampleRate = AUDIO_DEFAULT_SAMPLE_RATE; + } + // detach the old sound generator + if (soundGenerator) { + soundGenerator->play(false); + for (auto channelPair : audioChannels) { + auto channel = channelPair.second; + channel->detachSoundGenerator(); + } + } + // delete the old sound generator + soundGenerator = nullptr; + soundGenerator = std::unique_ptr(new fabgl::SoundGenerator(sampleRate)); + for (auto channelPair : audioChannels) { + auto channel = channelPair.second; + channel->attachSoundGenerator(); + } + soundGenerator->play(true); +} + // Initialise the sound driver // -void init_audio() { +void initAudio() { + // make new sound generator + setSampleRate(AUDIO_DEFAULT_SAMPLE_RATE); audioHandlers.reserve(MAX_AUDIO_CHANNELS); - debug_log("init_audio: we have reserved %d channels\n\r", audioHandlers.capacity()); + debug_log("initAudio: we have reserved %d channels\n\r", audioHandlers.capacity()); for (uint8_t i = 0; i < AUDIO_CHANNELS; i++) { - init_audio_channel(i); + initAudioChannel(i); } - SoundGenerator.play(true); } // Channel enabled? // bool channelEnabled(uint8_t channel) { - return channel < MAX_AUDIO_CHANNELS && audio_channels[channel]; + return channel < MAX_AUDIO_CHANNELS && audioChannels[channel]; } // Play a note // -uint8_t play_note(uint8_t channel, uint8_t volume, uint16_t frequency, uint16_t duration) { +uint8_t playNote(uint8_t channel, uint8_t volume, uint16_t frequency, uint16_t duration) { if (channelEnabled(channel)) { - return audio_channels[channel]->play_note(volume, frequency, duration); + return audioChannels[channel]->playNote(volume, frequency, duration); } return 1; } @@ -96,7 +122,7 @@ uint8_t play_note(uint8_t channel, uint8_t volume, uint16_t frequency, uint16_t // uint8_t getChannelStatus(uint8_t channel) { if (channelEnabled(channel)) { - return audio_channels[channel]->getStatus(); + return audioChannels[channel]->getStatus(); } return -1; } @@ -105,7 +131,7 @@ uint8_t getChannelStatus(uint8_t channel) { // void setVolume(uint8_t channel, uint8_t volume) { if (channelEnabled(channel)) { - audio_channels[channel]->setVolume(volume); + audioChannels[channel]->setVolume(volume); } } @@ -113,7 +139,7 @@ void setVolume(uint8_t channel, uint8_t volume) { // void setFrequency(uint8_t channel, uint16_t frequency) { if (channelEnabled(channel)) { - audio_channels[channel]->setFrequency(frequency); + audioChannels[channel]->setFrequency(frequency); } } @@ -121,7 +147,7 @@ void setFrequency(uint8_t channel, uint16_t frequency) { // void setWaveform(uint8_t channel, int8_t waveformType, uint16_t sampleId) { if (channelEnabled(channel)) { - auto channelRef = audio_channels[channel]; + auto channelRef = audioChannels[channel]; channelRef->setWaveform(waveformType, channelRef, sampleId); } } @@ -134,7 +160,7 @@ uint8_t clearSample(uint16_t sampleId) { debug_log("clearSample: sample %d not found\n\r", sampleId); return 0; } - samples.erase(sampleId); + samples[sampleId] = nullptr; debug_log("reset sample\n\r"); return 1; } diff --git a/video/audio_channel.h b/video/audio_channel.h index 0e6a879..922ae5f 100644 --- a/video/audio_channel.h +++ b/video/audio_channel.h @@ -2,6 +2,7 @@ #define AUDIO_CHANNEL_H #include +#include #include #include @@ -10,77 +11,84 @@ #include "envelopes/volume.h" #include "envelopes/frequency.h" -extern fabgl::SoundGenerator SoundGenerator; // The audio class +extern std::unique_ptr soundGenerator; // audio handling sub-system extern void audioTaskAbortDelay(uint8_t channel); // The audio channel class // -class audio_channel { +class AudioChannel { public: - audio_channel(uint8_t channel); - ~audio_channel(); - uint8_t play_note(uint8_t volume, uint16_t frequency, int32_t duration); + AudioChannel(uint8_t channel); + ~AudioChannel(); + uint8_t playNote(uint8_t volume, uint16_t frequency, int32_t duration); uint8_t getStatus(); - std::unique_ptr getSampleWaveform(uint16_t sampleId, std::shared_ptr channelRef); - void setWaveform(int8_t waveformType, std::shared_ptr channelRef, uint16_t sampleId = 0); + void setWaveform(int8_t waveformType, std::shared_ptr channelRef, uint16_t sampleId = 0); void setVolume(uint8_t volume); void setFrequency(uint16_t frequency); + void setDuration(int32_t duration); void setVolumeEnvelope(std::unique_ptr envelope); void setFrequencyEnvelope(std::unique_ptr envelope); + void setSampleRate(uint16_t sampleRate); + WaveformGenerator * getWaveform() { return this->_waveform.get(); } + void attachSoundGenerator(); + void detachSoundGenerator(); + void seekTo(uint32_t position); void loop(); uint8_t channel() { return _channel; } private: + std::shared_ptr getSampleWaveform(uint16_t sampleId, std::shared_ptr 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 _state; uint8_t _channel; uint8_t _volume; uint16_t _frequency; int32_t _duration; uint32_t _startTime; uint8_t _waveformType; - std::unique_ptr _waveform; + std::atomic _state; + std::shared_ptr _waveform = nullptr; std::unique_ptr _volumeEnvelope; std::unique_ptr _frequencyEnvelope; }; #include "audio_sample.h" #include "enhanced_samples_generator.h" -extern std::unordered_map> samples; // Storage for the sample data - -audio_channel::audio_channel(uint8_t channel) { - this->_channel = channel; - this->_state = AUDIO_STATE_IDLE; - this->_volume = 0; - this->_frequency = 750; - this->_duration = -1; +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) { setWaveform(AUDIO_WAVE_DEFAULT, nullptr); - debug_log("audio_driver: init %d\n\r", this->_channel); + debug_log("AudioChannel: init %d\n\r", channel); debug_log("free mem: %d\n\r", heap_caps_get_free_size(MALLOC_CAP_8BIT)); } -audio_channel::~audio_channel() { - debug_log("audio_driver: deiniting %d\n\r", this->_channel); +AudioChannel::~AudioChannel() { + debug_log("AudioChannel: deiniting %d\n\r", channel()); if (this->_waveform) { this->_waveform->enable(false); - SoundGenerator.detach(this->_waveform.get()); + soundGenerator->detach(getWaveform()); } - debug_log("audio_driver: deinit %d\n\r", this->_channel); + debug_log("AudioChannel: deinit %d\n\r", channel()); } -uint8_t audio_channel::play_note(uint8_t volume, uint16_t frequency, int32_t duration) { +uint8_t AudioChannel::playNote(uint8_t volume, uint16_t frequency, int32_t duration) { + if (!this->_waveform) { + debug_log("AudioChannel: no waveform on channel %d\n\r", channel()); + return 0; + } switch (this->_state) { - case AUDIO_STATE_IDLE: - case AUDIO_STATE_RELEASE: + case AudioState::Idle: + case AudioState::Release: this->_volume = volume; this->_frequency = frequency; this->_duration = duration == 65535 ? -1 : duration; if (this->_duration == 0 && this->_waveformType == AUDIO_WAVE_SAMPLE) { // zero duration means play whole sample - this->_duration = ((EnhancedSamplesGenerator *)this->_waveform.get())->getDuration(); + // 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); if (this->_volumeEnvelope) { // subtract the "release" time from the duration this->_duration -= this->_volumeEnvelope->getRelease(); @@ -89,14 +97,14 @@ uint8_t audio_channel::play_note(uint8_t volume, uint16_t frequency, int32_t dur this->_duration = 1; } } - this->_state = AUDIO_STATE_PENDING; - debug_log("audio_driver: play_note %d,%d,%d,%d\n\r", this->_channel, this->_volume, this->_frequency, this->_duration); + this->_state = AudioState::Pending; + debug_log("AudioChannel: playNote %d,%d,%d,%d\n\r", channel(), volume, frequency, this->_duration); return 1; } return 0; } -uint8_t audio_channel::getStatus() { +uint8_t AudioChannel::getStatus() { uint8_t status = 0; if (this->_waveform && this->_waveform->enabled()) { status |= AUDIO_STATUS_ACTIVE; @@ -105,9 +113,9 @@ uint8_t audio_channel::getStatus() { } } switch (this->_state) { - case AUDIO_STATE_PENDING: - case AUDIO_STATE_PLAYING: - case AUDIO_STATE_PLAY_LOOP: + case AudioState::Pending: + case AudioState::Playing: + case AudioState::PlayLoop: status |= AUDIO_STATUS_PLAYING; break; } @@ -118,51 +126,58 @@ uint8_t audio_channel::getStatus() { status |= AUDIO_STATUS_HAS_FREQUENCY_ENVELOPE; } - debug_log("audio_driver: getStatus %d\n\r", status); + debug_log("AudioChannel: getStatus %d\n\r", status); return status; } -std::unique_ptr audio_channel::getSampleWaveform(uint16_t sampleId, std::shared_ptr channelRef) { +std::shared_ptr AudioChannel::getSampleWaveform(uint16_t sampleId, std::shared_ptr channelRef) { if (samples.find(sampleId) != samples.end()) { auto sample = samples.at(sampleId); + // if (sample->channels.find(_channel) != sample->channels.end()) { + // // this channel is already playing this sample, so do nothing + // debug_log("AudioChannel: already playing sample %d on channel %d\n\r", sampleId, channel()); + // return nullptr; + // } + + // TODO remove channel tracking?? // remove this channel from other samples - for (auto samplePair : samples) { - if (samplePair.second) { - samplePair.second->channels.erase(_channel); - } - } - sample->channels[_channel] = channelRef; + // for (auto samplePair : samples) { + // if (samplePair.second) { + // samplePair.second->channels.erase(_channel); + // } + // } + // sample->channels[_channel] = channelRef; - return make_unique_psram(sample); + return std::make_shared(sample); } return nullptr; } -void audio_channel::setWaveform(int8_t waveformType, std::shared_ptr channelRef, uint16_t sampleId) { - std::unique_ptr newWaveform = nullptr; +void AudioChannel::setWaveform(int8_t waveformType, std::shared_ptr channelRef, uint16_t sampleId) { + std::shared_ptr newWaveform = nullptr; switch (waveformType) { case AUDIO_WAVE_SAWTOOTH: - newWaveform = make_unique_psram(); + newWaveform = std::make_shared(); break; case AUDIO_WAVE_SQUARE: - newWaveform = make_unique_psram(); + newWaveform = std::make_shared(); break; case AUDIO_WAVE_SINE: - newWaveform = make_unique_psram(); + newWaveform = std::make_shared(); break; case AUDIO_WAVE_TRIANGLE: - newWaveform = make_unique_psram(); + newWaveform = std::make_shared(); break; case AUDIO_WAVE_NOISE: - newWaveform = make_unique_psram(); + newWaveform = std::make_shared(); break; case AUDIO_WAVE_VICNOISE: - newWaveform = make_unique_psram(); + newWaveform = std::make_shared(); break; case AUDIO_WAVE_SAMPLE: // Buffer-based sample playback - debug_log("audio_driver: using sample buffer %d for waveform\n\r", sampleId); + debug_log("AudioChannel: using sample buffer %d for waveform on channel %d\n\r", sampleId, channel()); newWaveform = getSampleWaveform(sampleId, channelRef); break; default: @@ -170,50 +185,50 @@ void audio_channel::setWaveform(int8_t waveformType, std::shared_ptr_state != AUDIO_STATE_IDLE) { - debug_log("audio_driver: aborting current playback\n\r"); + if (newWaveform) { + debug_log("AudioChannel: setWaveform %d on channel %d\n\r", waveformType, channel()); + 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 = AUDIO_STATE_ABORT; + this->_state = AudioState::Abort; audioTaskAbortDelay(this->_channel); waitForAbort(); } if (this->_waveform) { - debug_log("audio_driver: detaching old waveform\n\r"); - SoundGenerator.detach(this->_waveform.get()); + debug_log("AudioChannel: detaching old waveform\n\r"); + detachSoundGenerator(); } - this->_waveform = std::move(newWaveform); + this->_waveform = newWaveform; _waveformType = waveformType; - SoundGenerator.attach(this->_waveform.get()); - debug_log("audio_driver: setWaveform %d done\n\r", waveformType); + attachSoundGenerator(); + debug_log("AudioChannel: setWaveform %d done on channel %d\n\r", waveformType, channel()); } } -void audio_channel::setVolume(uint8_t volume) { - debug_log("audio_driver: setVolume %d\n\r", volume); +void AudioChannel::setVolume(uint8_t volume) { + debug_log("AudioChannel: setVolume %d on channel %d\n\r", volume, channel()); if (this->_waveform) { waitForAbort(); switch (this->_state) { - case AUDIO_STATE_IDLE: + case AudioState::Idle: if (volume > 0) { // new note playback this->_volume = volume; this->_duration = -1; // indefinite duration - this->_state = AUDIO_STATE_PENDING; + this->_state = AudioState::Pending; } break; - case AUDIO_STATE_PLAY_LOOP: + case AudioState::PlayLoop: // we are looping, so an envelope may be active if (volume == 0) { // silence whilst looping always stops playback - curtail duration @@ -227,8 +242,8 @@ void audio_channel::setVolume(uint8_t volume) { this->_volume = volume; } break; - case AUDIO_STATE_PENDING: - case AUDIO_STATE_RELEASE: + case AudioState::Pending: + case AudioState::Release: // Set level so next loop will pick up the new volume this->_volume = volume; break; @@ -238,7 +253,7 @@ void audio_channel::setVolume(uint8_t volume) { this->_waveform->setVolume(volume); if (volume == 0) { // we're going silent, so abort any current playback - this->_state = AUDIO_STATE_ABORT; + this->_state = AudioState::Abort; audioTaskAbortDelay(this->_channel); } break; @@ -246,16 +261,16 @@ void audio_channel::setVolume(uint8_t volume) { } } -void audio_channel::setFrequency(uint16_t frequency) { - debug_log("audio_driver: setFrequency %d\n\r", frequency); +void AudioChannel::setFrequency(uint16_t frequency) { + debug_log("AudioChannel: setFrequency %d on channel %d\n\r", frequency, channel()); this->_frequency = frequency; if (this->_waveform) { waitForAbort(); switch (this->_state) { - case AUDIO_STATE_PENDING: - case AUDIO_STATE_PLAY_LOOP: - case AUDIO_STATE_RELEASE: + case AudioState::Pending: + case AudioState::PlayLoop: + case AudioState::Release: // Do nothing as next loop will pick up the new frequency break; default: @@ -264,46 +279,94 @@ void audio_channel::setFrequency(uint16_t frequency) { } } -void audio_channel::setVolumeEnvelope(std::unique_ptr envelope) { +void AudioChannel::setDuration(int32_t duration) { + debug_log("AudioChannel: setDuration %d on channel %d\n\r", duration, channel()); + if (duration == 0xFFFFFF) { + duration = -1; + } + this->_duration = duration; + + if (this->_waveform) { + waitForAbort(); + switch (this->_state) { + case AudioState::Idle: + // kick off a new note playback + this->_state = AudioState::Pending; + break; + case AudioState::Playing: + audioTaskAbortDelay(this->_channel); + break; + default: + // any other state we should be looping so it will just get picked up + break; + } + } +} + +void AudioChannel::setVolumeEnvelope(std::unique_ptr envelope) { this->_volumeEnvelope = std::move(envelope); - if (envelope && this->_state == AUDIO_STATE_PLAYING) { + if (envelope && this->_state == AudioState::Playing) { // swap to looping - this->_state = AUDIO_STATE_PLAY_LOOP; + this->_state = AudioState::PlayLoop; audioTaskAbortDelay(this->_channel); } } -void audio_channel::setFrequencyEnvelope(std::unique_ptr envelope) { +void AudioChannel::setFrequencyEnvelope(std::unique_ptr envelope) { this->_frequencyEnvelope = std::move(envelope); - if (envelope && this->_state == AUDIO_STATE_PLAYING) { + if (envelope && this->_state == AudioState::Playing) { // swap to looping - this->_state = AUDIO_STATE_PLAY_LOOP; + this->_state = AudioState::PlayLoop; audioTaskAbortDelay(this->_channel); } } -void audio_channel::waitForAbort() { - while (this->_state == AUDIO_STATE_ABORT) { +void AudioChannel::setSampleRate(uint16_t sampleRate) { + if (this->_waveform) { + this->_waveform->setSampleRate(sampleRate); + } +} + +void AudioChannel::attachSoundGenerator() { + if (this->_waveform) { + soundGenerator->attach(getWaveform()); + } +} + +void AudioChannel::detachSoundGenerator() { + if (this->_waveform) { + soundGenerator->detach(getWaveform()); + } +} + +void AudioChannel::seekTo(uint32_t position) { + if (this->_waveformType == AUDIO_WAVE_SAMPLE) { + ((EnhancedSamplesGenerator *)getWaveform())->seekTo(position); + } +} + +void AudioChannel::waitForAbort() { + while (this->_state == AudioState::Abort) { // wait for abort to complete vTaskDelay(1); } } -uint8_t audio_channel::getVolume(uint32_t elapsed) { +uint8_t AudioChannel::getVolume(uint32_t elapsed) { if (this->_volumeEnvelope) { return this->_volumeEnvelope->getVolume(this->_volume, elapsed, this->_duration); } return this->_volume; } -uint16_t audio_channel::getFrequency(uint32_t elapsed) { +uint16_t AudioChannel::getFrequency(uint32_t elapsed) { if (this->_frequencyEnvelope) { return this->_frequencyEnvelope->getFrequency(this->_frequency, elapsed, this->_duration); } return this->_frequency; } -bool audio_channel::isReleasing(uint32_t elapsed) { +bool AudioChannel::isReleasing(uint32_t elapsed) { if (this->_volumeEnvelope) { return this->_volumeEnvelope->isReleasing(elapsed, this->_duration); } @@ -313,7 +376,7 @@ bool audio_channel::isReleasing(uint32_t elapsed) { return elapsed >= this->_duration; } -bool audio_channel::isFinished(uint32_t elapsed) { +bool AudioChannel::isFinished(uint32_t elapsed) { if (this->_volumeEnvelope) { return this->_volumeEnvelope->isFinished(elapsed, this->_duration); } @@ -323,55 +386,55 @@ bool audio_channel::isFinished(uint32_t elapsed) { return (elapsed >= this->_duration); } -void audio_channel::loop() { +void AudioChannel::loop() { + int delay = 0; + switch (this->_state) { - case AUDIO_STATE_PENDING: - debug_log("audio_driver: play %d,%d,%d,%d\n\r", this->_channel, this->_volume, this->_frequency, this->_duration); + 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(); // set our initial volume and frequency this->_waveform->setVolume(this->getVolume(0)); - if (this->_waveformType == AUDIO_WAVE_SAMPLE) { - // hack to ensure samples always start from beginning - this->_waveform->setFrequency(-1); - } else { - this->_waveform->setFrequency(this->getFrequency(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 = AUDIO_STATE_PLAY_LOOP; + this->_state = AudioState::PlayLoop; } else { - this->_state = AUDIO_STATE_PLAYING; + this->_state = AudioState::Playing; // if delay value is negative then this delays for a super long time - vTaskDelay(pdMS_TO_TICKS(this->_duration)); + delay = this->_duration; } break; - case AUDIO_STATE_PLAYING: + + case AudioState::Playing: if (this->_duration >= 0) { // simple playback - delay until we have reached our duration uint32_t elapsed = millis() - this->_startTime; - debug_log("audio_driver: elapsed %d\n\r", elapsed); + debug_log("AudioChannel: %d elapsed %d\n\r", channel(), elapsed); if (elapsed >= this->_duration) { this->_waveform->enable(false); - debug_log("audio_driver: end\n\r"); - this->_state = AUDIO_STATE_IDLE; + debug_log("AudioChannel: %d end\n\r", channel()); + this->_state = AudioState::Idle; } else { - debug_log("audio_driver: loop (%d)\n\r", this->_duration - elapsed); - vTaskDelay(pdMS_TO_TICKS(this->_duration - elapsed)); + debug_log("AudioChannel: %d loop (%d)\n\r", channel(), this->_duration - elapsed); + delay = this->_duration - elapsed; } } else { // our duration is indefinite, so delay for a long time - debug_log("audio_driver: loop (indefinite playback)\n\r"); - vTaskDelay(pdMS_TO_TICKS(-1)); + debug_log("AudioChannel: %d loop (indefinite playback)\n\r", channel()); + delay = -1; } break; + // loop and release states used for envelopes - case AUDIO_STATE_PLAY_LOOP: { + case AudioState::PlayLoop: { uint32_t elapsed = millis() - this->_startTime; if (isReleasing(elapsed)) { - debug_log("audio_driver: releasing...\n\r"); - this->_state = AUDIO_STATE_RELEASE; + debug_log("AudioChannel: releasing %d...\n\r", channel()); + this->_state = AudioState::Release; } // update volume and frequency as appropriate if (this->_volumeEnvelope) @@ -380,7 +443,8 @@ void audio_channel::loop() { this->_waveform->setFrequency(this->getFrequency(elapsed)); break; } - case AUDIO_STATE_RELEASE: { + + case AudioState::Release: { uint32_t elapsed = millis() - this->_startTime; // update volume and frequency as appropriate if (this->_volumeEnvelope) @@ -390,17 +454,22 @@ void audio_channel::loop() { if (isFinished(elapsed)) { this->_waveform->enable(false); - debug_log("audio_driver: end (released)\n\r"); - this->_state = AUDIO_STATE_IDLE; + debug_log("AudioChannel: end (released %d)\n\r", channel()); + this->_state = AudioState::Idle; } break; } - case AUDIO_STATE_ABORT: + + case AudioState::Abort: this->_waveform->enable(false); - debug_log("audio_driver: abort\n\r"); - this->_state = AUDIO_STATE_IDLE; + debug_log("AudioChannel: abort %d\n\r", channel()); + this->_state = AudioState::Idle; break; } + + if (delay != 0) { + vTaskDelay(pdMS_TO_TICKS(delay)); + } } #endif // AUDIO_CHANNEL_H diff --git a/video/audio_sample.h b/video/audio_sample.h index ec417d4..0d54eae 100644 --- a/video/audio_sample.h +++ b/video/audio_sample.h @@ -8,61 +8,49 @@ #include "audio_channel.h" #include "buffer_stream.h" -struct audio_sample { - audio_sample(std::vector>& streams, uint8_t format) : blocks(streams), format(format), index(0), blockIndex(0) {} - ~audio_sample(); - int8_t getSample(); - void rewind() { - index = 0; - blockIndex = 0; - } - void checkIndexes() { - if (blockIndex >= blocks.size()) { - blockIndex = 0; - index = 0; - } - if (index >= blocks[blockIndex]->size()) { - index = 0; - } - } - uint32_t getDuration() { - uint32_t duration = 0; - for (auto block : blocks) { - duration += block->size(); - } - return duration / 16; - } - std::vector>& blocks; - uint8_t format; // Format of the sample data - uint32_t index; // Current index inside the current sample block - uint32_t blockIndex; // Current index into the sample data blocks - std::unordered_map> channels; // Channels playing this sample +struct AudioSample { + AudioSample(std::vector> streams, uint8_t format, uint32_t sampleRate = AUDIO_DEFAULT_SAMPLE_RATE, uint16_t frequency = 0) : + blocks(streams), format(format), sampleRate(sampleRate), baseFrequency(frequency) {} + ~AudioSample(); + + int8_t getSample(uint32_t & index, uint32_t & blockIndex); + void seekTo(uint32_t position, uint32_t & index, uint32_t & blockIndex, int32_t & repeatCount); + uint32_t getSize(); + + std::vector> blocks; + uint8_t format; // Format of the sample data + uint32_t sampleRate; // Sample rate of the sample + uint16_t baseFrequency = 0; // Base frequency of the sample + int32_t repeatStart = 0; // Start offset for repeat, in samples + int32_t repeatLength = -1; // Length of the repeat section in samples, -1 means to end of sample + // std::unordered_map> channels; // Channels playing this sample }; -audio_sample::~audio_sample() { +AudioSample::~AudioSample() { // iterate over channels - for (auto channelPair : this->channels) { - auto channelRef = channelPair.second; - if (!channelRef.expired()) { - auto channel = channelRef.lock(); - debug_log("audio_sample: removing sample from channel %d\n\r", channel->channel()); - // Remove sample from channel - channel->setWaveform(AUDIO_WAVE_DEFAULT, nullptr); - } - } - debug_log("audio_sample cleared\n\r"); + // for (auto channelPair : this->channels) { + // auto channel = channelPair.second.lock(); + // if (channel) { + // // Remove sample from channel + // debug_log("AudioSample: removing sample from channel %d\n\r", channel->channel()); + // // TODO change so only removes if channel is definitely set to this sample + // channel->setWaveform(AUDIO_WAVE_DEFAULT, nullptr); + // } + // } } -int8_t audio_sample::getSample() { - // our blocks might have changed, so we need to check if we're still in range - checkIndexes(); +int8_t AudioSample::getSample(uint32_t & index, uint32_t & blockIndex) { + // get the next sample + if (blockIndex >= blocks.size()) { + // we've reached the end of the sample, and haven't looped, so return 0 (silence) + return 0; + } auto block = blocks[blockIndex]; int8_t sample = block->getBuffer()[index++]; - - // Insert looping magic here + if (index >= block->size()) { - // block reached end, move to next, or loop + // block reached end, move to next block index = 0; blockIndex++; } @@ -74,4 +62,34 @@ int8_t audio_sample::getSample() { return sample; } +void AudioSample::seekTo(uint32_t position, uint32_t & index, uint32_t & blockIndex, int32_t & repeatCount) { + // NB repeatCount calculation here can result in zero, or a negative number, + // or a number that's beyond the end of the sample, which is fine + // it just means that the sample will never loop + if (repeatLength < 0) { + // repeat to end of sample + repeatCount = getSize() - position; + } else if (repeatLength > 0) { + auto repeatEnd = repeatStart + repeatLength; + repeatCount = repeatEnd - position; + } else { + repeatCount = 0; + } + + blockIndex = 0; + index = position; + while (blockIndex < blocks.size() && index >= blocks[blockIndex]->size()) { + index -= blocks[blockIndex]->size(); + blockIndex++; + } +} + +uint32_t AudioSample::getSize() { + uint32_t samples = 0; + for (auto block : blocks) { + samples += block->size(); + } + return samples; +} + #endif // AUDIO_SAMPLE_H diff --git a/video/enhanced_samples_generator.h b/video/enhanced_samples_generator.h index a6cf2c2..2f2f1f7 100644 --- a/video/enhanced_samples_generator.h +++ b/video/enhanced_samples_generator.h @@ -13,41 +13,64 @@ // class EnhancedSamplesGenerator : public WaveformGenerator { public: - EnhancedSamplesGenerator(std::shared_ptr sample); + EnhancedSamplesGenerator(std::shared_ptr sample); void setFrequency(int value); + void setSampleRate(int value); int getSample(); - int getDuration(); - + int getDuration(uint16_t frequency); + + void seekTo(uint32_t position); private: - std::weak_ptr _sample; + std::shared_ptr _sample; + + uint32_t index; // Current index inside the current sample block + uint32_t blockIndex; // Current index into the sample data blocks + int32_t repeatCount; // Sample count when repeating + // TODO consider whether repeatStart and repeatLength may need to be here + // which would allow for per-channel repeat settings + + int frequency; + int previousSample; + int currentSample; + double samplesPerGet; + double fractionalSampleOffset; + + double calculateSamplerate(uint16_t frequency); + int8_t getNextSample(); }; -EnhancedSamplesGenerator::EnhancedSamplesGenerator(std::shared_ptr sample) - : _sample(sample) +EnhancedSamplesGenerator::EnhancedSamplesGenerator(std::shared_ptr sample) + : _sample(sample), repeatCount(0), index(0), blockIndex(0), frequency(0), previousSample(0), currentSample(0), samplesPerGet(1.0), fractionalSampleOffset(0.0) {} void EnhancedSamplesGenerator::setFrequency(int value) { - // usually this will do nothing... - // but we'll hijack this method to allow us to reset the sample index - // ideally we'd override the enable method, but C++ doesn't let us do that - if (value < 0) { - // rewind our sample if it's still valid - if (!_sample.expired()) { - auto samplePtr = _sample.lock(); - samplePtr->rewind(); - } - } + frequency = value; + samplesPerGet = calculateSamplerate(value); +} + +void EnhancedSamplesGenerator::setSampleRate(int value) { + WaveformGenerator::setSampleRate(value); + samplesPerGet = calculateSamplerate(frequency); } int EnhancedSamplesGenerator::getSample() { - if (duration() == 0 || _sample.expired()) { + if (duration() == 0) { return 0; } - auto samplePtr = _sample.lock(); - int sample = samplePtr->getSample(); + // if we've moved far enough along, read the next sample + while (fractionalSampleOffset >= 1.0) { + previousSample = currentSample; + currentSample = getNextSample(); + fractionalSampleOffset = fractionalSampleOffset - 1.0; + } + + // Interpolate between the samples to reduce aliasing + int sample = currentSample * fractionalSampleOffset + previousSample * (1.0-fractionalSampleOffset); + + fractionalSampleOffset = fractionalSampleOffset + samplesPerGet; // process volume sample = sample * volume() / 127; @@ -57,9 +80,39 @@ int EnhancedSamplesGenerator::getSample() { return sample; } -int EnhancedSamplesGenerator::getDuration() { - // NB this is hard-coded for a 16khz sample rate - return _sample.expired() ? 0 : _sample.lock()->getDuration(); +int EnhancedSamplesGenerator::getDuration(uint16_t frequency) { + // TODO this will produce an incorrect duration if the sample rate for the channel has been + // adjusted to differ from the underlying audio system sample rate + // At this point it's not clear how to resolve this, so we'll assume it hasn't been adjusted + return !_sample ? 0 : (_sample->getSize() * 1000 / sampleRate()) / calculateSamplerate(frequency); +} + +void EnhancedSamplesGenerator::seekTo(uint32_t position) { + _sample->seekTo(position, index, blockIndex, repeatCount); + + // prepare our fractional sample data for playback + fractionalSampleOffset = 0.0; + previousSample = _sample->getSample(index, blockIndex); + currentSample = _sample->getSample(index, blockIndex); +} + +double EnhancedSamplesGenerator::calculateSamplerate(uint16_t frequency) { + auto baseFrequency = _sample->baseFrequency; + auto frequencyAdjust = baseFrequency > 0 ? (double)frequency / (double)baseFrequency : 1.0; + return frequencyAdjust * ((double)_sample->sampleRate / (double)(sampleRate())); +} + +int8_t EnhancedSamplesGenerator::getNextSample() { + auto sample = _sample->getSample(index, blockIndex); + + // looping magic + repeatCount--; + if (repeatCount == 0) { + // we've reached the end of the repeat section, so loop back + seekTo(_sample->repeatStart); + } + + return sample; } #endif // ENHANCED_SAMPLES_GENERATOR_H diff --git a/video/envelopes/frequency.h b/video/envelopes/frequency.h index 0183eef..92e396b 100644 --- a/video/envelopes/frequency.h +++ b/video/envelopes/frequency.h @@ -50,8 +50,8 @@ SteppedFrequencyEnvelope::SteppedFrequencyEnvelope(std::shared_ptr_totalSteps, this->_totalAdjustment); - debug_log("audio_driver: SteppedFrequencyEnvelope: stepLength=%d, repeats=%d, restricts=%d, totalLength=%d\n\r", this->_stepLength, this->_repeats, this->_restrict, _totalLength); + debug_log("audioDriver: SteppedFrequencyEnvelope: totalSteps=%d, totalAdjustment=%d\n\r", this->_totalSteps, this->_totalAdjustment); + debug_log("audioDriver: SteppedFrequencyEnvelope: stepLength=%d, repeats=%d, restricts=%d, totalLength=%d\n\r", this->_stepLength, this->_repeats, this->_restrict, _totalLength); } uint16_t SteppedFrequencyEnvelope::getFrequency(uint16_t baseFrequency, uint32_t elapsed, int32_t duration) { diff --git a/video/envelopes/volume.h b/video/envelopes/volume.h index b386c59..da99dae 100644 --- a/video/envelopes/volume.h +++ b/video/envelopes/volume.h @@ -36,7 +36,7 @@ ADSRVolumeEnvelope::ADSRVolumeEnvelope(uint16_t attack, uint16_t decay, uint8_t { // attack, decay, release are time values in milliseconds // sustain is 0-255, centered on 127, and is the relative sustain level - debug_log("audio_driver: ADSRVolumeEnvelope: attack=%d, decay=%d, sustain=%d, release=%d\n\r", this->_attack, this->_decay, this->_sustain, this->_release); + debug_log("audioDriver: ADSRVolumeEnvelope: attack=%d, decay=%d, sustain=%d, release=%d\n\r", this->_attack, this->_decay, this->_sustain, this->_release); } uint8_t ADSRVolumeEnvelope::getVolume(uint8_t baseVolume, uint32_t elapsed, int32_t duration) { diff --git a/video/vdu.h b/video/vdu.h index c12f940..b95c566 100644 --- a/video/vdu.h +++ b/video/vdu.h @@ -33,7 +33,7 @@ void VDUStreamProcessor::vdu(uint8_t c) { setActiveViewport(VIEWPORT_GRAPHICS); break; case 0x07: // Bell - play_note(0, 100, 750, 125); + playNote(0, 100, 750, 125); break; case 0x08: // Cursor Left cursorLeft(); diff --git a/video/vdu_audio.h b/video/vdu_audio.h index 1ca35c1..aa4b266 100644 --- a/video/vdu_audio.h +++ b/video/vdu_audio.h @@ -14,6 +14,7 @@ #include "agon.h" #include "agon_audio.h" +#include "audio_sample.h" #include "buffers.h" #include "types.h" @@ -29,7 +30,7 @@ void VDUStreamProcessor::vdu_sys_audio() { auto frequency = readWord_t(); if (frequency == -1) return; auto duration = readWord_t(); if (duration == -1) return; - sendAudioStatus(channel, play_note(channel, volume, frequency, duration)); + sendAudioStatus(channel, playNote(channel, volume, frequency, duration)); } break; case AUDIO_CMD_STATUS: { @@ -84,8 +85,51 @@ void VDUStreamProcessor::vdu_sys_audio() { case AUDIO_SAMPLE_FROM_BUFFER: { auto bufferId = readWord_t(); if (bufferId == -1) return; auto format = readByte_t(); if (format == -1) return; + uint32_t sampleRate = AUDIO_DEFAULT_SAMPLE_RATE; + if (format & AUDIO_FORMAT_WITH_RATE) { + sampleRate = readWord_t(); if (sampleRate == -1) return; + } + + sendAudioStatus(channel, createSampleFromBuffer(bufferId, format, sampleRate)); + } break; + + case AUDIO_SAMPLE_SET_FREQUENCY: { + auto frequency = readWord_t(); if (frequency == -1) return; + + sendAudioStatus(channel, setSampleFrequency(sampleNum, frequency)); + } break; + + case AUDIO_SAMPLE_BUFFER_SET_FREQUENCY: { + auto bufferId = readWord_t(); if (bufferId == -1) return; + auto frequency = readWord_t(); if (frequency == -1) return; + + sendAudioStatus(channel, setSampleFrequency(bufferId, frequency)); + } break; + + case AUDIO_SAMPLE_SET_REPEAT_START: { + auto repeatStart = read24_t(); if (repeatStart == -1) return; + + sendAudioStatus(channel, setSampleRepeatStart(sampleNum, repeatStart)); + } break; - sendAudioStatus(channel, createSampleFromBuffer(bufferId, format)); + case AUDIO_SAMPLE_BUFFER_SET_REPEAT_START: { + auto bufferId = readWord_t(); if (bufferId == -1) return; + auto repeatStart = read24_t(); if (repeatStart == -1) return; + + sendAudioStatus(channel, setSampleRepeatStart(bufferId, repeatStart)); + } break; + + case AUDIO_SAMPLE_SET_REPEAT_LENGTH: { + auto repeatLength = read24_t(); if (repeatLength == -1) return; + + sendAudioStatus(channel, setSampleRepeatLength(sampleNum, repeatLength)); + } break; + + case AUDIO_SAMPLE_BUFFER_SET_REPEAT_LENGTH: { + auto bufferId = readWord_t(); if (bufferId == -1) return; + auto repeatLength = read24_t(); if (repeatLength == -1) return; + + sendAudioStatus(channel, setSampleRepeatLength(bufferId, repeatLength)); } break; case AUDIO_SAMPLE_DEBUG_INFO: { @@ -93,12 +137,18 @@ void VDUStreamProcessor::vdu_sys_audio() { debug_log(" samples count: %d\n\r", samples.size()); debug_log(" free mem: %d\n\r", heap_caps_get_free_size(MALLOC_CAP_8BIT)); auto sample = samples[sampleNum]; - if (sample == nullptr) { + if (!sample) { debug_log(" sample is null\n\r"); break; } auto buffer = sample->blocks; - debug_log(" length: %d\n\r", buffer.size()); + debug_log(" length: %d blocks\n\r", buffer.size()); + debug_log(" size: %d\n\r", sample->getSize()); + debug_log(" format: %d\n\r", sample->format); + debug_log(" sample rate: %d\n\r", sample->sampleRate); + debug_log(" base frequency: %d\n\r", sample->baseFrequency); + debug_log(" repeat start: %d\n\r", sample->repeatStart); + debug_log(" repeat length: %d\n\r", sample->repeatLength); if (buffer.size() > 0) { debug_log(" data first byte: %d\n\r", buffer[0]->getBuffer()[0]); } @@ -124,8 +174,8 @@ void VDUStreamProcessor::vdu_sys_audio() { } break; case AUDIO_CMD_ENABLE: { - if (channel >= 0 && channel < MAX_AUDIO_CHANNELS && audio_channels[channel] == nullptr) { - init_audio_channel(channel); + if (channel >= 0 && channel < MAX_AUDIO_CHANNELS && audioChannels[channel] == nullptr) { + initAudioChannel(channel); } } break; @@ -138,7 +188,34 @@ void VDUStreamProcessor::vdu_sys_audio() { case AUDIO_CMD_RESET: { if (channelEnabled(channel)) { audioTaskKill(channel); - init_audio_channel(channel); + initAudioChannel(channel); + } + } break; + + case AUDIO_CMD_SEEK: { + auto position = read24_t(); if (position == -1) return; + + if (channelEnabled(channel)) { + audioChannels[channel]->seekTo(position); + } + } break; + + case AUDIO_CMD_DURATION: { + auto duration = read24_t(); if (duration == -1) return; + + if (channelEnabled(channel)) { + audioChannels[channel]->setDuration(duration); + } + } break; + + case AUDIO_CMD_SAMPLERATE: { + auto sampleRate = readWord_t(); if (sampleRate == -1) return; + + if (channel == 255) { + // set underlying sample rate + setSampleRate(sampleRate); + } else if (channelEnabled(channel)) { + audioChannels[channel]->setSampleRate(sampleRate); } } break; } @@ -165,20 +242,24 @@ uint8_t VDUStreamProcessor::loadSample(uint16_t bufferId, uint32_t length) { // timed out, or couldn't allocate buffer - so abort return 0; } - return createSampleFromBuffer(bufferId, 0); + return createSampleFromBuffer(bufferId, 0, AUDIO_DEFAULT_SAMPLE_RATE); } // Create a sample from a buffer // -uint8_t VDUStreamProcessor::createSampleFromBuffer(uint16_t bufferId, uint8_t format) { +uint8_t VDUStreamProcessor::createSampleFromBuffer(uint16_t bufferId, uint8_t format, uint16_t sampleRate) { if (buffers.find(bufferId) == buffers.end()) { debug_log("vdu_sys_audio: buffer %d not found\n\r", bufferId); return 0; } clearSample(bufferId); - auto sample = make_shared_psram(buffers[bufferId], format); - // auto sample = make_shared_psram(bufferId, format); + auto sample = (format & AUDIO_FORMAT_WITH_RATE) ? + std::make_shared(buffers[bufferId], format & AUDIO_FORMAT_DATA_MASK, sampleRate) + : std::make_shared(buffers[bufferId], format & AUDIO_FORMAT_DATA_MASK); if (sample) { + if (format & AUDIO_FORMAT_TUNEABLE) { + sample->baseFrequency = AUDIO_DEFAULT_FREQUENCY; + } samples[bufferId] = sample; return 1; } @@ -191,7 +272,7 @@ void VDUStreamProcessor::setVolumeEnvelope(uint8_t channel, uint8_t type) { if (channelEnabled(channel)) { switch (type) { case AUDIO_ENVELOPE_NONE: - audio_channels[channel]->setVolumeEnvelope(nullptr); + audioChannels[channel]->setVolumeEnvelope(nullptr); debug_log("vdu_sys_audio: channel %d - volume envelope disabled\n\r", channel); break; case AUDIO_ENVELOPE_ADSR: @@ -200,7 +281,7 @@ void VDUStreamProcessor::setVolumeEnvelope(uint8_t channel, uint8_t type) { auto sustain = readByte_t(); if (sustain == -1) return; auto release = readWord_t(); if (release == -1) return; std::unique_ptr envelope = make_unique_psram(attack, decay, sustain, release); - audio_channels[channel]->setVolumeEnvelope(std::move(envelope)); + audioChannels[channel]->setVolumeEnvelope(std::move(envelope)); break; } } @@ -212,13 +293,13 @@ void VDUStreamProcessor::setFrequencyEnvelope(uint8_t channel, uint8_t type) { if (channelEnabled(channel)) { switch (type) { case AUDIO_ENVELOPE_NONE: - audio_channels[channel]->setFrequencyEnvelope(nullptr); + audioChannels[channel]->setFrequencyEnvelope(nullptr); debug_log("vdu_sys_audio: channel %d - frequency envelope disabled\n\r", channel); break; case AUDIO_FREQUENCY_ENVELOPE_STEPPED: - auto phaseCount = readByte_t(); if (phaseCount == -1) return; + auto phaseCount = readByte_t(); if (phaseCount == -1) return; auto control = readByte_t(); if (control == -1) return; - auto stepLength = readWord_t(); if (stepLength == -1) return; + auto stepLength = readWord_t(); if (stepLength == -1) return; auto phases = make_shared_psram>(); for (auto n = 0; n < phaseCount; n++) { auto adjustment = readWord_t(); if (adjustment == -1) return; @@ -229,10 +310,46 @@ void VDUStreamProcessor::setFrequencyEnvelope(uint8_t channel, uint8_t type) { bool cumulative = control & AUDIO_FREQUENCY_CUMULATIVE; bool restrict = control & AUDIO_FREQUENCY_RESTRICT; std::unique_ptr envelope = make_unique_psram(phases, stepLength, repeats, cumulative, restrict); - audio_channels[channel]->setFrequencyEnvelope(std::move(envelope)); + audioChannels[channel]->setFrequencyEnvelope(std::move(envelope)); break; } } } +// Set sample frequency +// +uint8_t VDUStreamProcessor::setSampleFrequency(uint16_t sampleId, uint16_t frequency) { + if (samples.find(sampleId) == samples.end()) { + debug_log("vdu_sys_audio: sample %d not found\n\r", sampleId); + return 0; + } + samples[sampleId]->baseFrequency = frequency; + return 1; +} + +// Set sample repeatStart +// +uint8_t VDUStreamProcessor::setSampleRepeatStart(uint16_t sampleId, uint32_t repeatStart) { + if (samples.find(sampleId) == samples.end()) { + debug_log("vdu_sys_audio: sample %d not found\n\r", sampleId); + return 0; + } + samples[sampleId]->repeatStart = repeatStart; + return 1; +} + +// Set sample repeatLength +// +uint8_t VDUStreamProcessor::setSampleRepeatLength(uint16_t sampleId, uint32_t repeatLength) { + if (samples.find(sampleId) == samples.end()) { + debug_log("vdu_sys_audio: sample %d not found\n\r", sampleId); + return 0; + } + if (repeatLength == 0xFFFFFF) { + repeatLength = -1; + } + samples[sampleId]->repeatLength = repeatLength; + return 1; +} + #endif // VDU_AUDIO_H diff --git a/video/vdu_buffered.h b/video/vdu_buffered.h index b06f5a3..3d13897 100644 --- a/video/vdu_buffered.h +++ b/video/vdu_buffered.h @@ -244,7 +244,10 @@ void VDUStreamProcessor::bufferCall(uint16_t callBufferId, uint32_t offset) { void VDUStreamProcessor::bufferClear(uint16_t bufferId) { debug_log("bufferClear: buffer %d\n\r", bufferId); if (bufferId == 65535) { - buffers.clear(); + // iterate thru our buffers and clear their vectors + for (auto bufferPair : buffers) { + bufferPair.second.clear(); + } resetBitmaps(); resetSamples(); return; @@ -253,7 +256,7 @@ void VDUStreamProcessor::bufferClear(uint16_t bufferId) { debug_log("bufferClear: buffer %d not found\n\r", bufferId); return; } - buffers.erase(bufferId); + buffers[bufferId].clear(); clearBitmap(bufferId); clearSample(bufferId); debug_log("bufferClear: cleared buffer %d\n\r", bufferId); @@ -732,7 +735,7 @@ void VDUStreamProcessor::bufferConsolidate(uint16_t bufferId) { void clearTargets(std::vector targets) { for (auto target : targets) { if (buffers.find(target) != buffers.end()) { - buffers.erase(target); + buffers[target].clear(); } clearBitmap(target); } diff --git a/video/vdu_stream_processor.h b/video/vdu_stream_processor.h index 6c29656..790cde7 100644 --- a/video/vdu_stream_processor.h +++ b/video/vdu_stream_processor.h @@ -53,9 +53,12 @@ class VDUStreamProcessor { void vdu_sys_audio(); void sendAudioStatus(uint8_t channel, uint8_t status); uint8_t loadSample(uint16_t bufferId, uint32_t length); - uint8_t createSampleFromBuffer(uint16_t bufferId, uint8_t format); + uint8_t createSampleFromBuffer(uint16_t bufferId, uint8_t format, uint16_t sampleRate); void setVolumeEnvelope(uint8_t channel, uint8_t type); void setFrequencyEnvelope(uint8_t channel, uint8_t type); + uint8_t setSampleFrequency(uint16_t bufferId, uint16_t frequency); + uint8_t setSampleRepeatStart(uint16_t bufferId, uint32_t offset); + uint8_t setSampleRepeatLength(uint16_t bufferId, uint32_t length); void vdu_sys_sprites(void); void receiveBitmap(uint8_t cmd, uint16_t width, uint16_t height); diff --git a/video/video.ino b/video/video.ino index af0b500..4c4b082 100644 --- a/video/video.ino +++ b/video/video.ino @@ -30,7 +30,7 @@ // 29/03/2023: + Typo in boot screen fixed // 01/04/2023: + Added resetPalette to MODE, timeouts to VDU commands // 08/04/2023: RC4 + Removed delay in readbyte_t, fixed VDP_SCRCHAR, VDP_SCRPIXEL -// 12/04/2023: + Fixed bug in play_note +// 12/04/2023: + Fixed bug in playNote // 13/04/2023: + Fixed bootup fail with no keyboard // 17/04/2023: RC5 + Moved wait_completion in vdu so that it only executes after graphical operations // 18/04/2023: + Minor tweaks to wait completion logic @@ -84,7 +84,7 @@ void setup() { processor = new VDUStreamProcessor(&VDPSerial); processor->wait_eZ80(); setupKeyboardAndMouse(); - init_audio(); + initAudio(); copy_font(); set_mode(1); processor->sendModeInformation();