Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Audio recording and playback #163

Draft
wants to merge 50 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
cf08a1d
codal_port/modaudio: Extend AudioFrame constructor to take opt size.
dpgeorge Nov 14, 2023
2119cd9
codal_port/modaudio: Rework audio playing to play a long AudioFrame.
dpgeorge Nov 14, 2023
1dc4c12
codal_port/modaudio: Remove buffer expansion.
dpgeorge Nov 14, 2023
07190d5
codal_app: Add microphone recording interface.
dpgeorge Nov 14, 2023
edfa023
codal_port/microbit_microphone: Add methods to record.
dpgeorge Nov 14, 2023
7e3b102
codal_port/modaudio: Add audio.sound_level() method.
dpgeorge Nov 14, 2023
c487a38
src: Add test recording program.
dpgeorge Nov 14, 2023
30ef534
codal_port/modaudio: Rename "size" field to "alloc_size".
dpgeorge Jan 15, 2024
affdb0a
codal_port/modaudio: Make AudioFrame constructor public.
dpgeorge Jan 15, 2024
8ae75da
codal_app/microbithal_microphone: Store the current recording length.
dpgeorge Jan 15, 2024
1e4ae5b
codal_port/modaudio: Add a "used_size" field to AudioFrame.
dpgeorge Jan 15, 2024
0cf7f04
codal_port/modaudio: Add rate to AudioFrame.
dpgeorge Jan 15, 2024
ab6ea69
codal_port/microbit_microphone: Use rate from AudioFrame, or update it.
dpgeorge Jan 15, 2024
bd7905d
codal_port/modaudio: Use AudioFrame rate as rate when playing it.
dpgeorge Jan 15, 2024
2756929
src: Update test_record.py.
dpgeorge Jan 15, 2024
19dbe4b
codal_app/main: Set microphone gain to 0.2.
dpgeorge Jan 18, 2024
f1b03ee
codal_port/modaudio: Take the sqrt of sound_level.
dpgeorge Jan 18, 2024
4e3ac1d
codal_port/modaudio: Mostly use "alloc_size" instead of "used_size".
dpgeorge Jan 18, 2024
40979ac
codal_port/modaudio: Round up AudioFrame size to nearest 32.
dpgeorge Jan 18, 2024
4878157
codal_port/modaudio: Increas "used_size" when data is written.
dpgeorge Jan 18, 2024
5fe6307
src: Update test_record.py.
dpgeorge Jan 18, 2024
a528b1e
codal_port/modaudio: Update to build with latest micropython.
dpgeorge Jan 18, 2024
71d9430
codal_app/microbithal_microphone: Make input streaming use pullInto.
dpgeorge Feb 26, 2024
8528e89
codal_port/microbit_microphone: Implement wait argument to record_into.
dpgeorge Mar 25, 2024
5d1b3b5
codal_app/microbithal_microphone: Remove CODAL workaround.
dpgeorge Mar 25, 2024
7ba6c03
codal_app/microbithal_microphone: Add func to set the sensitivity.
dpgeorge Apr 22, 2024
b19fbde
codal_port/microbit_microphone: Add set_sensitivity method and consts.
dpgeorge Apr 22, 2024
8041d86
codal_port/modaudio: Ensure AudioFrame size is non-zero.
dpgeorge Apr 22, 2024
80cfac7
codal_port/microbit_microphone: Validate duration and rate args.
dpgeorge Apr 22, 2024
61aaf2a
codal_port/modaudio: Allow AudioFrame to be arbitrary length.
dpgeorge Apr 29, 2024
e8a5d61
codal_port/modaudio: Make None the default to AudioFrame().
dpgeorge Apr 29, 2024
0441aa2
codal_port/microbit_microphone: Require rate to be positive.
dpgeorge Apr 29, 2024
c7f64c1
codal_port/modaudio: Fix typecode of AudioFrame buffer to be unsigned.
dpgeorge Apr 30, 2024
c741736
codal_port/modaudio: Use mp_sched_schedule_node for audio fetcher.
dpgeorge May 20, 2024
26d077a
codal_port/modaudio: Separate default AudioFrame and output buffer size.
dpgeorge May 20, 2024
a4fb09b
codal_port/modaudio: Allow output buffer to be larger than 32 bytes.
dpgeorge May 20, 2024
a280e25
codal_port/modaudio: Stop streaming audio when data is exhausted.
dpgeorge May 27, 2024
9819ff8
codal_port: Implement AudioTrack and AudioRecording.
dpgeorge Aug 1, 2024
3fba8c9
src: Update test_record.py.
dpgeorge Aug 2, 2024
96a24ff
codal_port/modaudio: Remove used_size entry from AudioFrame type.
dpgeorge Aug 5, 2024
02e5289
codal_port/modaudio: Make AudioFrame add and mult helpers public.
dpgeorge Aug 21, 2024
30251c7
codal_port/microbit_audiotrack: Implement +,+=,-,-=,*,*= on AudioTrack.
dpgeorge Aug 21, 2024
ceb13c8
src: Update test_record.py to use mult for volume.
dpgeorge Aug 21, 2024
e337b94
src: In test_record.py, make sure my_track is defined.
dpgeorge Aug 22, 2024
d4c7b67
codal_port/microbit_audiorecording: Make track() args positional.
dpgeorge Aug 26, 2024
5cc0cb0
src: Use was_pressed in test_record.py.
dpgeorge Sep 9, 2024
5b36b01
codal_port/modaudio: Improve feeding of audio pipeline.
dpgeorge Sep 9, 2024
311dece
codal_app/microbithal_audio: Add microbit_hal_audio_is_playing() func.
dpgeorge Sep 9, 2024
4c957c4
codal_port/modaudio: Make audio waiting wait for audio to be silent.
dpgeorge Sep 9, 2024
df4cb05
codal_port/modaudio: Make sure final partial audio data is sent out.
dpgeorge Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/codal_app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ int main() {
uBit.audio.setSpeakerEnabled(true);
uBit.audio.setPinEnabled(false);

// Set the microphone gain to a reasonable value.
uBit.audio.processor->setGain(0.2);

mp_main();
return 0;
}
12 changes: 9 additions & 3 deletions src/codal_app/microbithal.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,13 @@ int microbit_hal_compass_get_field_strength(void);
int microbit_hal_compass_get_heading(void);

void microbit_hal_microphone_init(void);
void microbit_hal_microphone_set_sensitivity(float value);
void microbit_hal_microphone_set_threshold(int kind, int value);
int microbit_hal_microphone_get_level(void);
float microbit_hal_microphone_get_level_db(void);
void microbit_hal_microphone_start_recording(uint8_t *buf, size_t max_len, size_t *cur_len, int rate);
bool microbit_hal_microphone_is_recording(void);
void microbit_hal_microphone_stop_recording(void);

const uint8_t *microbit_hal_get_font_data(char c);

Expand All @@ -181,13 +185,15 @@ int microbit_hal_log_data(const char *key, const char *value);
void microbit_hal_audio_select_pin(int pin);
void microbit_hal_audio_select_speaker(bool enable);
void microbit_hal_audio_set_volume(int value);
bool microbit_hal_audio_is_playing(void);
bool microbit_hal_audio_is_expression_active(void);
void microbit_hal_audio_play_expression(const char *expr);
void microbit_hal_audio_stop_expression(void);

void microbit_hal_audio_init(uint32_t sample_rate);
void microbit_hal_audio_write_data(const uint8_t *buf, size_t num_samples);
void microbit_hal_audio_ready_callback(void);
void microbit_hal_audio_raw_init(uint32_t sample_rate);
void microbit_hal_audio_raw_set_rate(uint32_t sample_rate);
void microbit_hal_audio_raw_write_data(const uint8_t *buf, size_t num_samples);
void microbit_hal_audio_raw_ready_callback(void);

void microbit_hal_audio_speech_init(uint32_t sample_rate);
void microbit_hal_audio_speech_write_data(const uint8_t *buf, size_t num_samples);
Expand Down
32 changes: 20 additions & 12 deletions src/codal_app/microbithal_audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class AudioSource : public DataSource {
}
};

static AudioSource data_source;
static AudioSource raw_source;
static AudioSource speech_source;

extern "C" {
Expand Down Expand Up @@ -86,6 +86,10 @@ void microbit_hal_sound_synth_callback(int event) {
}
}

bool microbit_hal_audio_is_playing(void) {
return uBit.audio.isPlaying();
}

bool microbit_hal_audio_is_expression_active(void) {
return sound_synth_active_count > 0;
}
Expand All @@ -105,23 +109,27 @@ void microbit_hal_audio_stop_expression(void) {
uBit.audio.soundExpressions.stop();
}

void microbit_hal_audio_init(uint32_t sample_rate) {
if (!data_source.started) {
void microbit_hal_audio_raw_init(uint32_t sample_rate) {
if (!raw_source.started) {
MicroBitAudio::requestActivation();
data_source.started = true;
data_source.callback = microbit_hal_audio_ready_callback;
data_source.channel = uBit.audio.mixer.addChannel(data_source, sample_rate, 255);
raw_source.started = true;
raw_source.callback = microbit_hal_audio_raw_ready_callback;
raw_source.channel = uBit.audio.mixer.addChannel(raw_source, sample_rate, 255);
} else {
data_source.channel->setSampleRate(sample_rate);
raw_source.channel->setSampleRate(sample_rate);
}
}

void microbit_hal_audio_write_data(const uint8_t *buf, size_t num_samples) {
if ((size_t)data_source.buf.length() != num_samples) {
data_source.buf = ManagedBuffer(num_samples);
void microbit_hal_audio_raw_set_rate(uint32_t sample_rate) {
raw_source.channel->setSampleRate(sample_rate);
}

void microbit_hal_audio_raw_write_data(const uint8_t *buf, size_t num_samples) {
if ((size_t)raw_source.buf.length() != num_samples) {
raw_source.buf = ManagedBuffer(num_samples);
}
memcpy(data_source.buf.getBytes(), buf, num_samples);
data_source.sink->pullRequest();
memcpy(raw_source.buf.getBytes(), buf, num_samples);
raw_source.sink->pullRequest();
}

void microbit_hal_audio_speech_init(uint32_t sample_rate) {
Expand Down
94 changes: 94 additions & 0 deletions src/codal_app/microbithal_microphone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,65 @@
#include "microbithal.h"
#include "MicroBitDevice.h"

#define MIN(a, b) ((a) < (b) ? (a) : (b))

extern "C" void microbit_hal_level_detector_callback(int);

static void level_detector_event_handler(Event evt) {
microbit_hal_level_detector_callback(evt.value);
}

class MyStreamRecording : public DataSink
{
public:
SplitterChannel *upStream;

public:
uint8_t *dest;
size_t *dest_pos_ptr;
size_t dest_max;
bool request_stop;

MyStreamRecording(SplitterChannel *source);
virtual ~MyStreamRecording();

virtual int pullRequest();
};

MyStreamRecording::MyStreamRecording(SplitterChannel *source) : upStream(source)
{
}

MyStreamRecording::~MyStreamRecording()
{
}

int MyStreamRecording::pullRequest()
{
uint8_t *pull_buf = this->dest + *this->dest_pos_ptr;
size_t n = this->dest_max - *this->dest_pos_ptr;

if (n > 0) {
n = this->upStream->pullInto(pull_buf, n) - pull_buf;
}

if (n == 0 || this->request_stop) {
this->upStream->disconnect();
this->request_stop = false;
} else {
// Convert signed 8-bit to unsigned 8-bit data.
for (size_t i = 0; i < n; ++i) {
pull_buf[i] += 128;
}
*this->dest_pos_ptr += n;
}

return DEVICE_OK;
}

static MyStreamRecording *recording = NULL;
static SplitterChannel *splitterChannel = NULL;

extern "C" {

static bool microphone_init_done = false;
Expand All @@ -46,6 +99,10 @@ void microbit_hal_microphone_init(void) {
}
}

void microbit_hal_microphone_set_sensitivity(float value) {
uBit.audio.processor->setGain(value);
}

void microbit_hal_microphone_set_threshold(int kind, int value) {
if (kind == MICROBIT_HAL_MICROPHONE_SET_THRESHOLD_LOW) {
uBit.audio.levelSPL->setLowThreshold(value);
Expand All @@ -66,4 +123,41 @@ float microbit_hal_microphone_get_level_db(void) {
return value;
}

void microbit_hal_microphone_start_recording(uint8_t *buf, size_t max_len, size_t *cur_len, int rate) {
if (splitterChannel == NULL) {
splitterChannel = uBit.audio.splitter->createChannel();
splitterChannel->setFormat(DATASTREAM_FORMAT_8BIT_UNSIGNED);
}
splitterChannel->requestSampleRate(rate);

if (recording == NULL) {
recording = new MyStreamRecording(splitterChannel);
} else {
if (microbit_hal_microphone_is_recording()) {
microbit_hal_microphone_stop_recording();
while (microbit_hal_microphone_is_recording()) {
microbit_hal_idle();
}
}
}

recording->dest = buf;
recording->dest_pos_ptr = cur_len;
*recording->dest_pos_ptr = 0;
recording->dest_max = max_len;
recording->request_stop = false;

splitterChannel->connect(*recording);
}

bool microbit_hal_microphone_is_recording(void) {
return recording != NULL && splitterChannel->isConnected();
}

void microbit_hal_microphone_stop_recording(void) {
if (recording != NULL) {
recording->request_stop = true;
}
}

}
3 changes: 3 additions & 0 deletions src/codal_port/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ SRC_C += \
iters.c \
main.c \
microbit_accelerometer.c \
microbit_audiorecording.c \
microbit_audiotrack.c \
microbit_button.c \
microbit_compass.c \
microbit_display.c \
Expand Down Expand Up @@ -100,6 +102,7 @@ SRC_C += \
modspeech.c \
modthis.c \
mphalport.c \
utils.c \

SRC_C += \
shared/readline/readline.c \
Expand Down
128 changes: 128 additions & 0 deletions src/codal_port/microbit_audiorecording.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2024 Damien P. George
*
* 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 "py/mphal.h"
#include "drv_system.h"
#include "modmicrobit.h"
#include "modaudio.h"
#include "utils.h"

mp_obj_t microbit_audio_recording_new(size_t num_bytes, uint32_t rate) {
// Make sure size is non-zero.
if (num_bytes == 0) {
num_bytes = 1;
}

// Create and return the AudioRecording object.
uint8_t *data = m_new(uint8_t, num_bytes);
memset(data, 128, num_bytes);
return microbit_audio_track_new(MP_OBJ_NULL, num_bytes, data, rate);
}

static mp_obj_t microbit_audio_recording_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
(void)type_in;

enum { ARG_duration, ARG_rate };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_duration, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_OBJ_NULL} },
{ MP_QSTR_rate, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(AUDIO_TRACK_DEFAULT_SAMPLE_RATE)} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

mp_int_t rate = mp_obj_get_int_allow_float(args[ARG_rate].u_obj);
if (rate <= 0) {
mp_raise_ValueError(MP_ERROR_TEXT("rate out of bounds"));
}

mp_float_t duration_ms = mp_obj_get_float(args[ARG_duration].u_obj);
if (duration_ms <= 0) {
mp_raise_ValueError(MP_ERROR_TEXT("duration out of bounds"));
}
size_t num_bytes = duration_ms * rate / 1000;

return microbit_audio_recording_new(num_bytes, rate);
}

static mp_obj_t microbit_audio_recording_copy(mp_obj_t self_in) {
microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(self_in);
uint8_t *data = m_new(uint8_t, self->size);
memcpy(data, self->data, self->size);
return microbit_audio_track_new(MP_OBJ_NULL, self->size, data, self->rate);
}
static MP_DEFINE_CONST_FUN_OBJ_1(microbit_audio_recording_copy_obj, microbit_audio_recording_copy);

static mp_obj_t microbit_audio_recording_track(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_start_ms, ARG_end_ms };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_start_ms, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(0)} },
{ MP_QSTR_end_ms, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} },
};
// parse args
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

microbit_audio_track_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
mp_int_t start_byte = mp_obj_get_float(args[ARG_start_ms].u_obj) * self->rate / 1000;
mp_int_t end_byte;
if (args[ARG_end_ms].u_obj == MP_OBJ_NEW_SMALL_INT(-1)) {
end_byte = self->size;
} else {
end_byte = mp_obj_get_float(args[ARG_end_ms].u_obj) * self->rate / 1000;
}

// Truncate start_byte to fit in valid range.
start_byte = MAX(0, start_byte);
start_byte = MIN(start_byte, self->size);

// Truncate end_byte to fit in valid range.
end_byte = MAX(0, end_byte);
end_byte = MIN(end_byte, self->size);

// Calculate length of track, truncating negative lengths to 0.
size_t len = MAX(0, end_byte - start_byte);

// Create and return new track.
return microbit_audio_track_new(pos_args[0], len, self->data + start_byte, self->rate);
}
static MP_DEFINE_CONST_FUN_OBJ_KW(microbit_audio_recording_track_obj, 1, microbit_audio_recording_track);

static const mp_rom_map_elem_t microbit_audio_recording_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_get_rate), MP_ROM_PTR(&microbit_audio_track_get_rate_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_rate), MP_ROM_PTR(&microbit_audio_track_set_rate_obj) },
{ MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&microbit_audio_recording_copy_obj) },
{ MP_ROM_QSTR(MP_QSTR_track), MP_ROM_PTR(&microbit_audio_recording_track_obj) },
};
static MP_DEFINE_CONST_DICT(microbit_audio_recording_locals_dict, microbit_audio_recording_locals_dict_table);

MP_DEFINE_CONST_OBJ_TYPE(
microbit_audio_recording_type,
MP_QSTR_AudioRecording,
MP_TYPE_FLAG_NONE,
make_new, microbit_audio_recording_make_new,
buffer, microbit_audio_track_get_buffer,
locals_dict, &microbit_audio_recording_locals_dict
);
Loading
Loading