diff --git a/.gitattributes b/.gitattributes index 5ebde95f07..c077807abe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,6 +18,7 @@ *.deb binary *.zip binary *.pdf binary +*.wav binary # These should also not be modified by git. tests/basics/string_cr_conversion.py -text diff --git a/ports/raspberrypi/Makefile b/ports/raspberrypi/Makefile index e1169ce7d2..7ce5606f8b 100644 --- a/ports/raspberrypi/Makefile +++ b/ports/raspberrypi/Makefile @@ -196,6 +196,7 @@ SRC_C += \ bindings/rp2pio/__init__.c \ common-hal/rp2pio/StateMachine.c \ common-hal/rp2pio/__init__.c \ + audio_dma.c \ background.c \ peripherals/pins.c \ fatfs_port.c \ diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c new file mode 100644 index 0000000000..96317984df --- /dev/null +++ b/ports/raspberrypi/audio_dma.c @@ -0,0 +1,383 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "audio_dma.h" + +#include "shared-bindings/audiocore/RawSample.h" +#include "shared-bindings/audiocore/WaveFile.h" +#include "supervisor/background_callback.h" + +#include "py/mpstate.h" +#include "py/runtime.h" + +#include "src/rp2_common/hardware_irq/include/hardware/irq.h" + +#if CIRCUITPY_AUDIOPWMIO || CIRCUITPY_AUDIOBUSIO + +#define AUDIO_DMA_CHANNEL_COUNT NUM_DMA_CHANNELS + +void audio_dma_convert_signed(audio_dma_t* dma, uint8_t* buffer, uint32_t buffer_length, + uint8_t** output_buffer, uint32_t* output_buffer_length) { + if (dma->first_buffer_free) { + *output_buffer = dma->first_buffer; + } else { + *output_buffer = dma->second_buffer; + } + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" + if (dma->signed_to_unsigned || + dma->unsigned_to_signed || + dma->sample_spacing > 1 || + (dma->sample_resolution != dma->output_resolution)) { + *output_buffer_length = buffer_length / dma->sample_spacing; + uint32_t out_i = 0; + if (dma->sample_resolution <= 8 && dma->output_resolution > 8) { + size_t shift = dma->output_resolution - dma->sample_resolution; + + for (uint32_t i = 0; i < buffer_length; i += dma->sample_spacing) { + if (dma->signed_to_unsigned) { + ((uint16_t*) *output_buffer)[out_i] = ((uint16_t) ((int8_t*) buffer)[i] + 0x80) << shift; + } else if (dma->unsigned_to_signed) { + ((int16_t*) *output_buffer)[out_i] = ((int16_t) ((uint8_t*) buffer)[i] - 0x80) << shift; + } else { + ((uint16_t*) *output_buffer)[out_i] = ((uint16_t) ((uint8_t*) buffer)[i]) << shift; + } + out_i += 1; + } + } else if (dma->sample_resolution <= 8 && dma->output_resolution <= 8) { + for (uint32_t i = 0; i < buffer_length; i += dma->sample_spacing) { + if (dma->signed_to_unsigned) { + ((uint8_t*) *output_buffer)[out_i] = ((int8_t*) buffer)[i] + 0x80; + } else if (dma->unsigned_to_signed) { + ((int8_t*) *output_buffer)[out_i] = ((uint8_t*) buffer)[i] - 0x80; + } else { + ((uint8_t*) *output_buffer)[out_i] = ((uint8_t*) buffer)[i]; + } + out_i += 1; + } + } else if (dma->sample_resolution > 8 && dma->output_resolution > 8) { + size_t shift = 16 - dma->output_resolution; + for (uint32_t i = 0; i < buffer_length / 2; i += dma->sample_spacing) { + if (dma->signed_to_unsigned) { + ((uint16_t*) *output_buffer)[out_i] = ((int16_t*) buffer)[i] + 0x8000; + } else if (dma->unsigned_to_signed) { + ((int16_t*) *output_buffer)[out_i] = ((uint16_t*) buffer)[i] - 0x8000; + } else { + ((uint16_t*) *output_buffer)[out_i] = ((uint16_t*) buffer)[i]; + } + if (dma->output_resolution < 16) { + if (dma->output_signed) { + ((int16_t*) *output_buffer)[out_i] = ((int16_t*) *output_buffer)[out_i] >> shift; + } else { + ((uint16_t*) *output_buffer)[out_i] = ((uint16_t*) *output_buffer)[out_i] >> shift; + } + } + out_i += 1; + } + } + } else { + *output_buffer = buffer; + *output_buffer_length = buffer_length; + } + #pragma GCC diagnostic pop + dma->first_buffer_free = !dma->first_buffer_free; +} + +void audio_dma_load_next_block(audio_dma_t* dma) { + uint8_t dma_channel = dma->channel[1]; + if (dma->first_channel_free) { + dma_channel = dma->channel[0]; + } + dma->first_channel_free = !dma->first_channel_free; + + uint8_t* output_buffer; + uint32_t output_buffer_length; + audioio_get_buffer_result_t get_buffer_result; + uint8_t* buffer; + uint32_t buffer_length; + get_buffer_result = audiosample_get_buffer(dma->sample, + dma->single_channel, dma->audio_channel, &buffer, &buffer_length); + + if (get_buffer_result == GET_BUFFER_ERROR) { + audio_dma_stop(dma); + return; + } + + audio_dma_convert_signed(dma, buffer, buffer_length, &output_buffer, &output_buffer_length); + + // If we don't have an output buffer, save the pointer to first_buffer for use in the single + // buffer special case. + if (dma->first_buffer == NULL) { + dma->first_buffer = output_buffer; + } + + dma_channel_set_trans_count(dma_channel, output_buffer_length / dma->output_size, false /* trigger */); + dma_channel_set_read_addr(dma_channel, output_buffer, false /* trigger */); + if (get_buffer_result == GET_BUFFER_DONE) { + if (dma->loop) { + audiosample_reset_buffer(dma->sample, dma->single_channel, dma->audio_channel); + } else { + // Set channel trigger to ourselves so we don't keep going. + dma_channel_hw_t* c = &dma_hw->ch[dma_channel]; + c->al1_ctrl = (c->al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); + } + } +} + +// Playback should be shutdown before calling this. +audio_dma_result audio_dma_setup_playback(audio_dma_t* dma, + mp_obj_t sample, + bool loop, + bool single_channel, + uint8_t audio_channel, + bool output_signed, + uint8_t output_resolution, + uint32_t output_register_address, + uint8_t dma_trigger_source) { + // Use two DMA channels to because the DMA can't wrap to itself without the + // buffer being power of two aligned. + dma->channel[0] = dma_claim_unused_channel(false); + dma->channel[1] = dma_claim_unused_channel(false); + if (dma->channel[0] == NUM_DMA_CHANNELS || dma->channel[1] == NUM_DMA_CHANNELS) { + if (dma->channel[0] < NUM_DMA_CHANNELS) { + dma_channel_unclaim(dma->channel[0]); + } + return AUDIO_DMA_DMA_BUSY; + } + + dma->sample = sample; + dma->loop = loop; + dma->single_channel = single_channel; + dma->audio_channel = audio_channel; + dma->signed_to_unsigned = false; + dma->unsigned_to_signed = false; + dma->output_signed = output_signed; + dma->sample_spacing = 1; + dma->first_channel_free = true; + dma->output_resolution = output_resolution; + dma->sample_resolution = audiosample_bits_per_sample(sample); + audiosample_reset_buffer(sample, single_channel, audio_channel); + + bool single_buffer; + bool samples_signed; + uint32_t max_buffer_length; + audiosample_get_buffer_structure(sample, single_channel, &single_buffer, &samples_signed, + &max_buffer_length, &dma->sample_spacing); + + // Check to see if we have to scale the resolution up. + if (dma->sample_resolution <= 8 && dma->output_resolution > 8) { + max_buffer_length *= 2; + } + if (output_signed != samples_signed || + dma->sample_spacing > 1 || + (dma->sample_resolution != dma->output_resolution)) { + max_buffer_length /= dma->sample_spacing; + dma->first_buffer = (uint8_t*) m_realloc(dma->first_buffer, max_buffer_length); + if (dma->first_buffer == NULL) { + return AUDIO_DMA_MEMORY_ERROR; + } + + dma->first_buffer_free = true; + if (!single_buffer) { + dma->second_buffer = (uint8_t*) m_realloc(dma->second_buffer, max_buffer_length); + if (dma->second_buffer == NULL) { + return AUDIO_DMA_MEMORY_ERROR; + } + } + dma->signed_to_unsigned = !output_signed && samples_signed; + dma->unsigned_to_signed = output_signed && !samples_signed; + } + + if (output_resolution > 8) { + dma->output_size = 2; + } else { + dma->output_size = 1; + } + // Transfer both channels at once. + if (!single_channel && audiosample_channel_count(sample) == 2) { + dma->output_size *= 2; + } + + enum dma_channel_transfer_size dma_size = DMA_SIZE_8; + if (dma->output_size == 2) { + dma_size = DMA_SIZE_16; + } else if (dma->output_size == 4) { + dma_size = DMA_SIZE_32; + } + + for (size_t i = 0; i < 2; i++) { + dma_channel_config c = dma_channel_get_default_config(dma->channel[i]); + channel_config_set_transfer_data_size(&c, dma_size); + channel_config_set_dreq(&c, dma_trigger_source); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + // Chain to the other channel by default. + channel_config_set_chain_to(&c, dma->channel[(i + 1) % 2]); + dma_channel_set_config(dma->channel[i], &c, false /* trigger */); + dma_channel_set_write_addr(dma->channel[i], (void*) output_register_address, false /* trigger */); + } + + // We keep the audio_dma_t for internal use and the sample as a root pointer because it + // contains the audiodma structure. + MP_STATE_PORT(playing_audio)[dma->channel[0]] = dma; + MP_STATE_PORT(playing_audio)[dma->channel[1]] = dma; + + // Load the first two blocks up front. + audio_dma_load_next_block(dma); + if (!single_buffer) { + audio_dma_load_next_block(dma); + } + + // Special case the DMA for a single buffer. It's commonly used for a single wave length of sound + // and may be short. Therefore, we use DMA chaining to loop quickly without involving interrupts. + // On the RP2040 we chain by having a second DMA writing to the config registers of the first. + // Read and write addresses change with DMA so we need to reset the read address back to the + // start of the sample. + if (single_buffer) { + dma_channel_config c = dma_channel_get_default_config(dma->channel[1]); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); + channel_config_set_dreq(&c, 0x3f); // dma as fast as possible + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, false); + channel_config_set_chain_to(&c, dma->channel[1]); // Chain to ourselves so we stop. + dma_channel_configure(dma->channel[1], &c, + &dma_hw->ch[dma->channel[0]].al3_read_addr_trig, // write address + &dma->first_buffer, // read address + 1, // transaction count + false); // trigger + } else { + // Enable our DMA channels on DMA0 to the CPU. This will wake us up when + // we're WFI. + dma_hw->inte0 |= (1 << dma->channel[0]) | (1 << dma->channel[1]); + irq_set_mask_enabled(1 << DMA_IRQ_0, true); + } + + dma_channel_start(dma->channel[0]); + + return AUDIO_DMA_OK; +} + +void audio_dma_stop(audio_dma_t* dma) { + // Disable our interrupts. + dma_hw->inte0 &= ~((1 << dma->channel[0]) | (1 << dma->channel[1])); + irq_set_mask_enabled(1 << DMA_IRQ_0, false); + + // Run any remaining audio tasks because we remove ourselves from + // playing_audio. + RUN_BACKGROUND_TASKS; + + for (size_t i = 0; i < 2; i++) { + size_t channel = dma->channel[i]; + + if (dma_channel_is_busy(channel)) { + dma_channel_abort(channel); + } + dma_channel_unclaim(channel); + MP_STATE_PORT(playing_audio)[channel] = NULL; + dma->channel[i] = NUM_DMA_CHANNELS; + } + + // Hold onto our buffers. +} + +// To pause we simply stop the DMA. It is the responsibility of the output peripheral +// to hold the previous value. +void audio_dma_pause(audio_dma_t* dma) { + dma_hw->ch[dma->channel[0]].al1_ctrl &= ~DMA_CH0_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->channel[1]].al1_ctrl &= ~DMA_CH0_CTRL_TRIG_EN_BITS; +} + +void audio_dma_resume(audio_dma_t* dma) { + // Always re-enable the non-busy channel first so it's ready to continue when the busy channel + // finishes and chains to it. (An interrupt could make the time between enables long.) + size_t first = 0; + size_t second = 1; + if (dma_channel_is_busy(dma->channel[0])) { + first = 1; + second = 0; + } + dma_hw->ch[dma->channel[first]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->channel[second]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS; +} + +bool audio_dma_get_paused(audio_dma_t* dma) { + if (dma->channel[0] >= AUDIO_DMA_CHANNEL_COUNT) { + return false; + } + uint32_t control = dma_hw->ch[dma->channel[0]].ctrl_trig; + + return (control & DMA_CH0_CTRL_TRIG_EN_BITS) == 0; +} + +void audio_dma_init(audio_dma_t* dma) { + dma->first_buffer = NULL; + dma->second_buffer = NULL; +} + +void audio_dma_deinit(audio_dma_t* dma) { + m_free(dma->first_buffer); + dma->first_buffer = NULL; + + m_free(dma->second_buffer); + dma->second_buffer = NULL; +} + +bool audio_dma_get_playing(audio_dma_t* dma) { + if (dma->channel[0] == NUM_DMA_CHANNELS) { + return false; + } + if (!dma_channel_is_busy(dma->channel[0]) && + !dma_channel_is_busy(dma->channel[1])) { + audio_dma_stop(dma); + return false; + } + + return true; +} + +// WARN(tannewt): DO NOT print from here, or anything it calls. Printing calls +// background tasks such as this and causes a stack overflow. +STATIC void dma_callback_fun(void *arg) { + audio_dma_t* dma = arg; + if (dma == NULL) { + return; + } + + audio_dma_load_next_block(dma); +} + +void isr_dma_0(void) { + for (size_t i = 0; i < NUM_DMA_CHANNELS; i++) { + uint32_t mask = 1 << i; + if ((dma_hw->intr & mask) != 0 && MP_STATE_PORT(playing_audio)[i] != NULL) { + audio_dma_t* dma = MP_STATE_PORT(playing_audio)[i]; + background_callback_add(&dma->callback, dma_callback_fun, (void*)dma); + dma_hw->ints0 = mask; + } + } +} + +#endif diff --git a/ports/raspberrypi/audio_dma.h b/ports/raspberrypi/audio_dma.h new file mode 100644 index 0000000000..1f739ff737 --- /dev/null +++ b/ports/raspberrypi/audio_dma.h @@ -0,0 +1,91 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_RASPBERRYPI_AUDIO_DMA_OUT_H +#define MICROPY_INCLUDED_RASPBERRYPI_AUDIO_DMA_OUT_H + +#include "py/obj.h" +#include "supervisor/background_callback.h" + +#include "src/rp2_common/hardware_dma/include/hardware/dma.h" + +typedef struct { + mp_obj_t sample; + uint8_t channel[2]; + uint8_t audio_channel; + uint8_t output_size; + uint8_t sample_spacing; + bool loop; + bool single_channel; + bool signed_to_unsigned; + bool unsigned_to_signed; + bool output_signed; + bool first_channel_free; + bool first_buffer_free; + uint8_t output_resolution; // in bits + uint8_t sample_resolution; // in bits + uint8_t* first_buffer; + uint8_t* second_buffer; + background_callback_t callback; +} audio_dma_t; + +typedef enum { + AUDIO_DMA_OK, + AUDIO_DMA_DMA_BUSY, + AUDIO_DMA_MEMORY_ERROR, +} audio_dma_result; + + +void audio_dma_init(audio_dma_t* dma); +void audio_dma_deinit(audio_dma_t* dma); +void audio_dma_reset(void); + +// This sets everything up but doesn't start the timer. +// Sample is the python object for the sample to play. +// loop is true if we should loop the sample. +// single_channel is true if we only output a single channel. When false, all channels will be +// output. +// audio_channel is the index of the channel to dma. single_channel must be false in this case. +// output_signed is true if the dma'd data should be signed. False and it will be unsigned. +// output_register_address is the address to copy data to. +// dma_trigger_source is the DMA trigger source which cause another copy +audio_dma_result audio_dma_setup_playback(audio_dma_t* dma, + mp_obj_t sample, + bool loop, + bool single_channel, + uint8_t audio_channel, + bool output_signed, + uint8_t output_resolution, + uint32_t output_register_address, + uint8_t dma_trigger_source); + +void audio_dma_stop(audio_dma_t* dma); +bool audio_dma_get_playing(audio_dma_t* dma); +void audio_dma_pause(audio_dma_t* dma); +void audio_dma_resume(audio_dma_t* dma); +bool audio_dma_get_paused(audio_dma_t* dma); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_AUDIO_DMA_OUT_H diff --git a/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c b/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c new file mode 100644 index 0000000000..57605a1fb4 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c @@ -0,0 +1,235 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "common-hal/audiopwmio/PWMAudioOut.h" + +#include +#include + +#include "extmod/vfs_fat.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/runtime.h" +#include "shared-bindings/pwmio/PWMOut.h" +#include "shared-bindings/audiopwmio/PWMAudioOut.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/microcontroller/Processor.h" +#include "supervisor/shared/tick.h" +#include "supervisor/shared/translate.h" + +#include "src/rp2040/hardware_structs/include/hardware/structs/dma.h" +#include "src/rp2_common/hardware_pwm/include/hardware/pwm.h" + +#define NUM_DMA_TIMERS 4 + +void audiopwmout_reset() { + for (size_t i = 0; i < NUM_DMA_TIMERS; i++) { + dma_hw->timer[i] = 0; + } +} + +// Caller validates that pins are free. +void common_hal_audiopwmio_pwmaudioout_construct(audiopwmio_pwmaudioout_obj_t* self, + const mcu_pin_obj_t* left_channel, const mcu_pin_obj_t* right_channel, uint16_t quiescent_value) { + if (left_channel != NULL && right_channel != NULL) { + if (pwm_gpio_to_slice_num(left_channel->number) != pwm_gpio_to_slice_num(right_channel->number)) { + mp_raise_ValueError(translate("Pins must share PWM slice")); + } + if (pwm_gpio_to_channel(left_channel->number) != 0) { + mp_raise_ValueError(translate("Stereo left must be on PWM channel A")); + } + if (pwm_gpio_to_channel(right_channel->number) != 1) { + mp_raise_ValueError(translate("Stereo right must be on PWM channel B")); + } + } + + // Typically pwmout doesn't let us change frequency with two objects on the + // same PWM slice. However, we have private access to it so we can do what + // we want. ;-) We mark ourselves variable only if we're a mono output to + // prevent other PWM use on the other channel. If stereo, we say fixed + // frequency so we can allocate with ourselves. + bool mono = right_channel == NULL; + + // We don't actually know our frequency yet so just pick one that shouldn't + // match anyone else. (We'll only know the frequency once we play something + // back.) + uint32_t frequency = 12500; + + // Make sure the PWMOut's are "deinited" by default. + self->left_pwm.pin = NULL; + self->right_pwm.pin = NULL; + + pwmout_result_t result = common_hal_pwmio_pwmout_construct(&self->left_pwm, + left_channel, 0, frequency, mono); + if (result == PWMOUT_OK && right_channel != NULL) { + result = common_hal_pwmio_pwmout_construct(&self->right_pwm, + right_channel, 0, frequency, false); + if (result != PWMOUT_OK) { + common_hal_pwmio_pwmout_deinit(&self->left_pwm); + } + } + if (result != PWMOUT_OK) { + mp_raise_RuntimeError(translate("All timers in use")); + } + + claim_pin(left_channel); + if (right_channel != NULL) { + claim_pin(right_channel); + } + + audio_dma_init(&self->dma); + + self->quiescent_value = quiescent_value; +} + +bool common_hal_audiopwmio_pwmaudioout_deinited(audiopwmio_pwmaudioout_obj_t* self) { + return common_hal_pwmio_pwmout_deinited(&self->left_pwm); +} + +void common_hal_audiopwmio_pwmaudioout_deinit(audiopwmio_pwmaudioout_obj_t* self) { + if (common_hal_audiopwmio_pwmaudioout_deinited(self)) { + return; + } + if (common_hal_audiopwmio_pwmaudioout_get_playing(self)) { + common_hal_audiopwmio_pwmaudioout_stop(self); + } + + // TODO: ramp the pwm down from quiescent value to 0 + common_hal_pwmio_pwmout_deinit(&self->left_pwm); + common_hal_pwmio_pwmout_deinit(&self->right_pwm); + + audio_dma_deinit(&self->dma); +} + +void common_hal_audiopwmio_pwmaudioout_play(audiopwmio_pwmaudioout_obj_t* self, mp_obj_t sample, bool loop) { + if (common_hal_audiopwmio_pwmaudioout_get_playing(self)) { + common_hal_audiopwmio_pwmaudioout_stop(self); + } + + // TODO: Share pacing timers based on frequency. + size_t pacing_timer = NUM_DMA_TIMERS; + for (size_t i = 0; i < NUM_DMA_TIMERS; i++) { + if (dma_hw->timer[i] == 0) { + pacing_timer = i; + } + break; + } + if (pacing_timer == NUM_DMA_TIMERS) { + mp_raise_RuntimeError(translate("No DMA pacing timer found")); + } + uint32_t tx_register = (uint32_t) &pwm_hw->slice[self->left_pwm.slice].cc; + if (common_hal_pwmio_pwmout_deinited(&self->right_pwm)) { + // Shift the destination if we are outputting to the second PWM channel. + tx_register += self->left_pwm.channel * sizeof(uint16_t); + } + + pwmio_pwmout_set_top(&self->left_pwm, 1023); + + audio_dma_result result = audio_dma_setup_playback( + &self->dma, + sample, + loop, + false, // single channel + 0, // audio channel + false, // output signed + 10, + (uint32_t) tx_register, // output register + 0x3b + pacing_timer); // data request line + + if (result == AUDIO_DMA_DMA_BUSY) { + // common_hal_audiobusio_i2sout_stop(self); + mp_raise_RuntimeError(translate("No DMA channel found")); + } else if (result == AUDIO_DMA_MEMORY_ERROR) { + // common_hal_audiobusio_i2sout_stop(self); + mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion")); + } + + // OK! We got all of the resources we need and dma is ready. + self->pacing_timer = pacing_timer; + + // Playback with two independent clocks. One is the sample rate which + // determines when we push a new sample to the PWM slice. The second is the + // PWM frequency itself. + + // Determine the DMA divisor. The RP2040 has four pacing timers we can use + // to trigger the DMA. Each has a 16 bit fractional divisor system clock * X / Y where X and Y + // are 16-bit. + + uint32_t sample_rate = audiosample_sample_rate(sample); + uint32_t system_clock = common_hal_mcu_processor_get_frequency(); + uint32_t best_numerator = 0; + uint32_t best_denominator = 0; + uint32_t best_error = system_clock; + + for (uint32_t denominator = 0xffff; denominator > 0; denominator--) { + uint32_t numerator = (denominator * sample_rate) / system_clock; + uint32_t remainder = (denominator * sample_rate) % system_clock; + if (remainder > (system_clock / 2)) { + numerator += 1; + remainder = system_clock - remainder; + } + if (remainder < best_error) { + best_denominator = denominator; + best_numerator = numerator; + best_error = remainder; + // Stop early if we can't do better. + if (remainder == 0) { + break; + } + } + } + + dma_hw->timer[pacing_timer] = best_numerator << 16 | best_denominator; +} + +void common_hal_audiopwmio_pwmaudioout_stop(audiopwmio_pwmaudioout_obj_t* self) { + dma_hw->timer[self->pacing_timer] = 0; + self->pacing_timer = NUM_DMA_TIMERS; + + audio_dma_stop(&self->dma); +} + +bool common_hal_audiopwmio_pwmaudioout_get_playing(audiopwmio_pwmaudioout_obj_t* self) { + bool playing = audio_dma_get_playing(&self->dma); + if (!playing && self->pacing_timer < NUM_DMA_TIMERS) { + dma_hw->timer[self->pacing_timer] = 0; + self->pacing_timer = NUM_DMA_TIMERS; + } + return playing; +} + +void common_hal_audiopwmio_pwmaudioout_pause(audiopwmio_pwmaudioout_obj_t* self) { + audio_dma_pause(&self->dma); +} + +void common_hal_audiopwmio_pwmaudioout_resume(audiopwmio_pwmaudioout_obj_t* self) { + audio_dma_resume(&self->dma); +} + +bool common_hal_audiopwmio_pwmaudioout_get_paused(audiopwmio_pwmaudioout_obj_t* self) { + return audio_dma_get_paused(&self->dma); +} diff --git a/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.h b/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.h new file mode 100644 index 0000000000..2b43b8354d --- /dev/null +++ b/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.h @@ -0,0 +1,47 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOPWMIO_PWMAUDIOOUT_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOPWMIO_PWMAUDIOOUT_H + +#include "common-hal/pwmio/PWMOut.h" + +#include "audio_dma.h" + +typedef struct { + mp_obj_base_t base; + pwmio_pwmout_obj_t left_pwm; + pwmio_pwmout_obj_t right_pwm; + audio_dma_t dma; + uint16_t quiescent_value; + uint8_t pacing_timer; +} audiopwmio_pwmaudioout_obj_t; + +void audiopwmout_reset(void); + +void audiopwmout_background(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOPWMIO_PWMAUDIOOUT_H diff --git a/ports/raspberrypi/common-hal/audiopwmio/__init__.c b/ports/raspberrypi/common-hal/audiopwmio/__init__.c new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ports/raspberrypi/common-hal/pwmio/PWMOut.c b/ports/raspberrypi/common-hal/pwmio/PWMOut.c index 3d8979a036..e861bab5a9 100644 --- a/ports/raspberrypi/common-hal/pwmio/PWMOut.c +++ b/ports/raspberrypi/common-hal/pwmio/PWMOut.c @@ -39,7 +39,7 @@ #include "src/rp2_common/hardware_pwm/include/hardware/pwm.h" uint32_t target_slice_frequencies[NUM_PWM_SLICES]; -uint32_t slice_fixed_frequency; +uint32_t slice_variable_frequency; #define CHANNELS_PER_SLICE 2 static uint32_t channel_use; @@ -76,7 +76,7 @@ void pwmout_reset(void) { } pwm_set_enabled(slice, false); target_slice_frequencies[slice] = 0; - slice_fixed_frequency &= ~(1 << slice); + slice_variable_frequency &= ~(1 << slice); } } @@ -108,7 +108,7 @@ pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t* self, return PWMOUT_ALL_TIMERS_ON_PIN_IN_USE; } // If the other user wants a variable frequency then we can't share either. - if ((slice_fixed_frequency & (1 << slice)) != 0) { + if ((slice_variable_frequency & (1 << slice)) != 0) { return PWMOUT_ALL_TIMERS_ON_PIN_IN_USE; } // If we're both fixed frequency but we don't match target frequencies then we can't share. @@ -118,9 +118,10 @@ pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t* self, } self->slice = slice; self->channel = channel; + channel_use |= channel_use_mask; if (variable_frequency) { - slice_fixed_frequency |= 1 << slice; + slice_variable_frequency |= 1 << slice; } if (target_slice_frequencies[slice] != frequency) { @@ -153,7 +154,7 @@ void common_hal_pwmio_pwmout_deinit(pwmio_pwmout_obj_t* self) { uint32_t slice_mask = ((1 << CHANNELS_PER_SLICE) - 1) << (self->slice * CHANNELS_PER_SLICE + self->channel); if ((channel_use & slice_mask) == 0) { target_slice_frequencies[self->slice] = 0; - slice_fixed_frequency &= ~(1 << self->slice); + slice_variable_frequency &= ~(1 << self->slice); pwm_set_enabled(self->slice, false); } @@ -171,6 +172,13 @@ uint16_t common_hal_pwmio_pwmout_get_duty_cycle(pwmio_pwmout_obj_t* self) { return self->duty_cycle; } +void pwmio_pwmout_set_top(pwmio_pwmout_obj_t* self, uint32_t top) { + self->actual_frequency = common_hal_mcu_processor_get_frequency() / top; + self->top = top; + pwm_set_clkdiv_int_frac(self->slice, 1, 0); + pwm_set_wrap(self->slice, self->top - 1); +} + void common_hal_pwmio_pwmout_set_frequency(pwmio_pwmout_obj_t* self, uint32_t frequency) { if (frequency == 0 || frequency > (common_hal_mcu_processor_get_frequency() / 2)) { mp_raise_ValueError(translate("Invalid PWM frequency")); diff --git a/ports/raspberrypi/common-hal/pwmio/PWMOut.h b/ports/raspberrypi/common-hal/pwmio/PWMOut.h index 50f84777b5..cbb0b707a3 100644 --- a/ports/raspberrypi/common-hal/pwmio/PWMOut.h +++ b/ports/raspberrypi/common-hal/pwmio/PWMOut.h @@ -43,5 +43,7 @@ typedef struct { } pwmio_pwmout_obj_t; void pwmout_reset(void); +// Private API for AudioPWMOut. +void pwmio_pwmout_set_top(pwmio_pwmout_obj_t* self, uint32_t top); #endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_PWMIO_PWMOUT_H diff --git a/ports/raspberrypi/mpconfigport.h b/ports/raspberrypi/mpconfigport.h index 77d1ff0805..e0d98579dd 100644 --- a/ports/raspberrypi/mpconfigport.h +++ b/ports/raspberrypi/mpconfigport.h @@ -27,6 +27,8 @@ #ifndef __INCLUDED_MPCONFIGPORT_H #define __INCLUDED_MPCONFIGPORT_H +#include "src/rp2040/hardware_regs/include/hardware/platform_defs.h" + #define MICROPY_PY_SYS_PLATFORM "RP2040" #define CIRCUITPY_INTERNAL_NVM_SIZE 0 @@ -41,6 +43,7 @@ #include "py/circuitpy_mpconfig.h" #define MICROPY_PORT_ROOT_POINTERS \ + mp_obj_t playing_audio[NUM_DMA_CHANNELS]; \ CIRCUITPY_COMMON_ROOT_POINTERS; #endif // __INCLUDED_MPCONFIGPORT_H diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index 8681e29949..dac7231dac 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -27,8 +27,6 @@ CIRCUITPY_FULL_BUILD = 1 CIRCUITPY_PWMIO = 1 # Things that need to be implemented. -CIRCUITPY_AUDIOBUSIO = 0 # Use PIO interally for I2S -CIRCUITPY_AUDIOMP3 = 0 CIRCUITPY_COUNTIO = 0 # Use PWM interally CIRCUITPY_FREQUENCYIO = 0 # Use PWM interally CIRCUITPY_I2CPERIPHERAL = 0 @@ -37,8 +35,15 @@ CIRCUITPY_PULSEIO = 0 # Use PIO interally CIRCUITPY_ROTARYIO = 0 # Use PIO interally CIRCUITPY_WATCHDOG = 1 -# Things that are unsupported by the hardware. +# Audio via PWM CIRCUITPY_AUDIOIO = 0 +CIRCUITPY_AUDIOBUSIO ?= 0 # add this later +CIRCUITPY_AUDIOCORE ?= 1 +CIRCUITPY_AUDIOPWMIO ?= 1 + +# These libraries require Cortex M4+ for fancy math instructions. +CIRCUITPY_AUDIOMIXER ?= 0 +CIRCUITPY_AUDIOMP3 ?= 0 INTERNAL_LIBM = 1 diff --git a/ports/raspberrypi/supervisor/port.c b/ports/raspberrypi/supervisor/port.c index 7c503514ab..36d2e8275c 100644 --- a/ports/raspberrypi/supervisor/port.c +++ b/ports/raspberrypi/supervisor/port.c @@ -32,6 +32,7 @@ #include "bindings/rp2pio/StateMachine.h" #include "genhdr/mpversion.h" +#include "shared-bindings/audiopwmio/PWMAudioOut.h" #include "shared-bindings/busio/I2C.h" #include "shared-bindings/busio/SPI.h" #include "shared-bindings/microcontroller/__init__.h" @@ -108,6 +109,10 @@ void reset_port(void) { rtc_reset(); #endif + #if CIRCUITPY_AUDIOPWMIO + audiopwmout_reset(); + #endif + reset_all_pins(); } diff --git a/tests/manual/audiocore/jeplayer-splash-16000-16bit-mono-signed.wav b/tests/manual/audiocore/jeplayer-splash-16000-16bit-mono-signed.wav new file mode 100644 index 0000000000..cd3da395bd Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-16000-16bit-mono-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-16000-16bit-stereo-signed.wav b/tests/manual/audiocore/jeplayer-splash-16000-16bit-stereo-signed.wav new file mode 100644 index 0000000000..c1036475b8 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-16000-16bit-stereo-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-16000-24bit-mono-signed.wav b/tests/manual/audiocore/jeplayer-splash-16000-24bit-mono-signed.wav new file mode 100644 index 0000000000..6aa6afc7ab Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-16000-24bit-mono-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-16000-24bit-stereo-signed.wav b/tests/manual/audiocore/jeplayer-splash-16000-24bit-stereo-signed.wav new file mode 100644 index 0000000000..c175aaf612 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-16000-24bit-stereo-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-16000-8bit-mono-unsigned.wav b/tests/manual/audiocore/jeplayer-splash-16000-8bit-mono-unsigned.wav new file mode 100644 index 0000000000..ccf63b9c15 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-16000-8bit-mono-unsigned.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-16000-8bit-stereo-unsigned.wav b/tests/manual/audiocore/jeplayer-splash-16000-8bit-stereo-unsigned.wav new file mode 100644 index 0000000000..63aaa5b294 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-16000-8bit-stereo-unsigned.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-44100-16bit-mono-signed.wav b/tests/manual/audiocore/jeplayer-splash-44100-16bit-mono-signed.wav new file mode 100644 index 0000000000..147b14b169 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-44100-16bit-mono-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-44100-16bit-stereo-signed.wav b/tests/manual/audiocore/jeplayer-splash-44100-16bit-stereo-signed.wav new file mode 100644 index 0000000000..adfe84fb03 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-44100-16bit-stereo-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-44100-24bit-mono-signed.wav b/tests/manual/audiocore/jeplayer-splash-44100-24bit-mono-signed.wav new file mode 100644 index 0000000000..2c5337045d Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-44100-24bit-mono-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-44100-24bit-stereo-signed.wav b/tests/manual/audiocore/jeplayer-splash-44100-24bit-stereo-signed.wav new file mode 100644 index 0000000000..a290c6c379 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-44100-24bit-stereo-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-44100-8bit-mono-unsigned.wav b/tests/manual/audiocore/jeplayer-splash-44100-8bit-mono-unsigned.wav new file mode 100644 index 0000000000..cd1c79280a Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-44100-8bit-mono-unsigned.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-44100-8bit-stereo-unsigned.wav b/tests/manual/audiocore/jeplayer-splash-44100-8bit-stereo-unsigned.wav new file mode 100644 index 0000000000..12c48e2583 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-44100-8bit-stereo-unsigned.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-44100-stereo.mp3 b/tests/manual/audiocore/jeplayer-splash-44100-stereo.mp3 new file mode 100644 index 0000000000..fd820e69ac Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-44100-stereo.mp3 differ diff --git a/tests/manual/audiocore/jeplayer-splash-8000-16bit-mono-signed.wav b/tests/manual/audiocore/jeplayer-splash-8000-16bit-mono-signed.wav new file mode 100644 index 0000000000..a730c2b84c Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-8000-16bit-mono-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-8000-16bit-stereo-signed.wav b/tests/manual/audiocore/jeplayer-splash-8000-16bit-stereo-signed.wav new file mode 100644 index 0000000000..5038ae8ae5 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-8000-16bit-stereo-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-8000-24bit-mono-signed.wav b/tests/manual/audiocore/jeplayer-splash-8000-24bit-mono-signed.wav new file mode 100644 index 0000000000..ac25cddb37 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-8000-24bit-mono-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-8000-24bit-stereo-signed.wav b/tests/manual/audiocore/jeplayer-splash-8000-24bit-stereo-signed.wav new file mode 100644 index 0000000000..6fec674312 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-8000-24bit-stereo-signed.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-8000-8bit-mono-unsigned.wav b/tests/manual/audiocore/jeplayer-splash-8000-8bit-mono-unsigned.wav new file mode 100644 index 0000000000..d5e49b24b3 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-8000-8bit-mono-unsigned.wav differ diff --git a/tests/manual/audiocore/jeplayer-splash-8000-8bit-stereo-unsigned.wav b/tests/manual/audiocore/jeplayer-splash-8000-8bit-stereo-unsigned.wav new file mode 100644 index 0000000000..3df7945aa2 Binary files /dev/null and b/tests/manual/audiocore/jeplayer-splash-8000-8bit-stereo-unsigned.wav differ diff --git a/tests/manual/audiopwmio/single_buffer_loop.py b/tests/manual/audiopwmio/single_buffer_loop.py new file mode 100644 index 0000000000..dfac475b5b --- /dev/null +++ b/tests/manual/audiopwmio/single_buffer_loop.py @@ -0,0 +1,65 @@ +import audiocore +import audiopwmio +import board +import digitalio +import array +import time +import math + +trigger = digitalio.DigitalInOut(board.D4) +trigger.switch_to_output(True) + +# Generate one period of sine wav. +length = 8000 // 440 + +samples = [] +sample_names = [ +"unsigned 8 bit", +"signed 8 bit", +"unsigned 16 bit", +"signed 16 bit" +] + + +# unsigned 8 bit +u8 = array.array("B", [0] * length) +for i in range(length): + u8[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 7) + 2 ** 7) + +samples.append(audiocore.RawSample(u8, sample_rate=4000)) + +# signed 8 bit +s8 = array.array("b", [0] * length) +for i in range(length): + s8[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 7)) + +samples.append(audiocore.RawSample(s8, sample_rate=16000)) + +# unsigned 16 bit +u16 = array.array("H", [0] * length) +for i in range(length): + u16[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15) + 2 ** 15) + +samples.append(audiocore.RawSample(u16, sample_rate=8000)) + + +# signed 16 bit +s16 = array.array("h", [0] * length) +for i in range(length): + s16[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15)) + +samples.append(audiocore.RawSample(s16, sample_rate=8000)) + + +dac = audiopwmio.PWMAudioOut(board.D13) +for sample, name in zip(samples, sample_names): + print(name) + trigger.value = False + dac.play(sample, loop=True) + time.sleep(1) + dac.stop() + time.sleep(0.1) + trigger.value = True + print() + +print("done") diff --git a/tests/manual/audiopwmio/wavefile_pause_resume.py b/tests/manual/audiopwmio/wavefile_pause_resume.py new file mode 100644 index 0000000000..27912d3217 --- /dev/null +++ b/tests/manual/audiopwmio/wavefile_pause_resume.py @@ -0,0 +1,39 @@ +import audiocore +import audiopwmio +import board +import digitalio +import time +import math +import os + +trigger = digitalio.DigitalInOut(board.D4) +trigger.switch_to_output(True) + +sample_prefix = "jeplayer-splash" + +samples = [] +for fn in os.listdir("/"): + if fn.startswith(sample_prefix): + samples.append(fn) + +dac = audiopwmio.PWMAudioOut(left_channel=board.D12, right_channel=board.D13) +for filename in samples: + print("playing", filename) + with open(filename, "rb") as sample_file: + try: + sample = audiocore.WaveFile(sample_file) + except OSError as e: + print(e) + continue + trigger.value = False + dac.play(sample) + while dac.playing: + time.sleep(0.1) + dac.pause() + time.sleep(0.1) + dac.resume() + trigger.value = True + time.sleep(0.1) + print() + +print("done") diff --git a/tests/manual/audiopwmio/wavefile_playback.py b/tests/manual/audiopwmio/wavefile_playback.py new file mode 100644 index 0000000000..0adbf2b19c --- /dev/null +++ b/tests/manual/audiopwmio/wavefile_playback.py @@ -0,0 +1,36 @@ +import audiocore +import audiopwmio +import board +import digitalio +import time +import math +import os + +trigger = digitalio.DigitalInOut(board.D4) +trigger.switch_to_output(True) + +sample_prefix = "jeplayer-splash" + +samples = [] +for fn in os.listdir("/"): + if fn.startswith(sample_prefix): + samples.append(fn) + +dac = audiopwmio.PWMAudioOut(left_channel=board.D12, right_channel=board.D13) +for filename in samples: + print("playing", filename) + with open(filename, "rb") as sample_file: + try: + sample = audiocore.WaveFile(sample_file) + except OSError as e: + print(e) + continue + trigger.value = False + dac.play(sample) + while dac.playing: + time.sleep(1) + trigger.value = True + time.sleep(0.1) + print() + +print("done")