Add PWM based audio playback
See https://learn.adafruit.com/circuitpython-essentials/circuitpython-audio-out to get started. Fixes #4037
This commit is contained in:
parent
b19c700d4a
commit
191b143e7b
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -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
|
||||
|
@ -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 \
|
||||
|
383
ports/raspberrypi/audio_dma.c
Normal file
383
ports/raspberrypi/audio_dma.c
Normal file
@ -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
|
91
ports/raspberrypi/audio_dma.h
Normal file
91
ports/raspberrypi/audio_dma.h
Normal file
@ -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
|
235
ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c
Normal file
235
ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c
Normal file
@ -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 <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
47
ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.h
Normal file
47
ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.h
Normal file
@ -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
|
0
ports/raspberrypi/common-hal/audiopwmio/__init__.c
Normal file
0
ports/raspberrypi/common-hal/audiopwmio/__init__.c
Normal file
@ -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"));
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/manual/audiocore/jeplayer-splash-44100-stereo.mp3
Normal file
BIN
tests/manual/audiocore/jeplayer-splash-44100-stereo.mp3
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
65
tests/manual/audiopwmio/single_buffer_loop.py
Normal file
65
tests/manual/audiopwmio/single_buffer_loop.py
Normal file
@ -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")
|
39
tests/manual/audiopwmio/wavefile_pause_resume.py
Normal file
39
tests/manual/audiopwmio/wavefile_pause_resume.py
Normal file
@ -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")
|
36
tests/manual/audiopwmio/wavefile_playback.py
Normal file
36
tests/manual/audiopwmio/wavefile_playback.py
Normal file
@ -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")
|
Loading…
Reference in New Issue
Block a user