Skip to content

Commit 4f0892c

Browse files
Merge pull request #457 from lancaster-university/audio-refactor
Audio refactor
2 parents 9ad9ece + 70c4301 commit 4f0892c

File tree

13 files changed

+158
-34
lines changed

13 files changed

+158
-34
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ jobs:
4545
uses: actions/checkout@v4
4646
with:
4747
repository: 'lancaster-university/microbit-v2-samples'
48+
# FIXME: Temporarily check out the audio-refactor branch for testing
49+
ref: 'audio-refactor'
4850
# We need to use the checkout action (instead of build.py cloning the
4951
# repository) so that on PRs we can get the commit from the PR merge
5052
- name: Clone this repository in the libraries folder

.github/workflows/micropython.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@ jobs:
1616
with:
1717
repository: 'microbit-foundation/micropython-microbit-v2'
1818
submodules: 'true'
19+
# FIXME: Temporarily check out the audio-recording branch for testing
20+
ref: 'audio-recording'
1921
- name: Manually clone microbit-v2-samples in the lib/codal/libraries folder
2022
uses: actions/checkout@v4
2123
with:
2224
path: lib/codal/libraries/codal-microbit-v2
2325
fetch-depth: '0'
2426
submodules: 'recursive'
27+
# FIXME: Temporarily check out the audio-recording branch for testing
28+
ref: 'audio-refactor'
2529
- name: Setup arm-none-eabi-gcc v10.3
2630
uses: carlosperate/arm-none-eabi-gcc-action@v1
2731
with:

.github/workflows/size-diff.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ jobs:
4747
repository: 'lancaster-university/microbit-v2-samples'
4848
# Unless manually triggered via workflow_dispatch this will be an empty
4949
# string, checking out the default branch
50-
ref: ${{ github.event.inputs.samples-commit }}
50+
# FIXME: Temporarily check out the audio-refactor branch for testing
51+
# ref: ${{ github.event.inputs.samples-commit }}
52+
ref: 'audio-refactor'
5153
# We need to use the checkout action (instead of build.py cloning the
5254
# repository) so that on PRs we can get the commit from the PR merge
5355
- name: Clone this repository in the libraries folder

inc/MicroBitAudio.h

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ DEALINGS IN THE SOFTWARE.
2828
#include "NRF52PWM.h"
2929
#include "SoundEmojiSynthesizer.h"
3030
#include "SoundExpressions.h"
31+
#include "SampleSource.h"
3132
#include "Mixer2.h"
3233
#include "SoundOutputPin.h"
3334
#include "StreamNormalizer.h"
@@ -39,12 +40,19 @@ DEALINGS IN THE SOFTWARE.
3940
#define MICROBIT_AUDIO_STATUS_DEEPSLEEP 0x0001
4041
#define CONFIG_DEFAULT_MICROPHONE_GAIN 0.1f
4142

42-
4343
// Configurable options
4444
#ifndef CONFIG_AUDIO_MIXER_OUTPUT_LATENCY_US
4545
#define CONFIG_AUDIO_MIXER_OUTPUT_LATENCY_US (uint32_t) ((CONFIG_MIXER_BUFFER_SIZE/2) * (1000000.0f/44100.0f))
4646
#endif
4747

48+
#ifndef CONFIG_AUDIO_INPUT_CHANNELS
49+
#define CONFIG_AUDIO_INPUT_CHANNELS 4
50+
#endif
51+
52+
#ifndef CONFIG_AUDIO_DEFAULT_MICROPHONE_SAMPLERATE
53+
#define CONFIG_AUDIO_DEFAULT_MICROPHONE_SAMPLERATE 11000
54+
#endif
55+
4856
namespace codal
4957
{
5058
/**
@@ -55,12 +63,13 @@ namespace codal
5563
public:
5664
static MicroBitAudio *instance; // Primary instance of MicroBitAudio, on demand activated.
5765
Mixer2 mixer; // Multi channel audio mixer
58-
NRF52ADCChannel *mic; // Microphone ADC Channel from uBit.IO
66+
NRF52ADCChannel *mic; // Microphone ADC Channel from uBit.IO
5967
StreamNormalizer *processor; // Stream Normaliser instance
6068
StreamSplitter *splitter; // Stream Splitter instance (8bit normalized output)
6169
StreamSplitter *rawSplitter; // Stream Splitter instance (raw input)
6270
LevelDetectorSPL *levelSPL; // Level Detector SPL instance
6371
LowPassFilter *micFilter; // Low pass filter to remove high frequency noise on the mic
72+
SampleSource *sampleSource[CONFIG_AUDIO_INPUT_CHANNELS]; // multichannel sample playback capability
6473

6574
private:
6675
volatile bool micEnabled; // State of on board mic
@@ -97,12 +106,6 @@ namespace codal
97106
*/
98107
static void requestActivation();
99108

100-
/**
101-
* Catch events from the splitter
102-
* @param MicroBitEvent
103-
*/
104-
void onSplitterEvent(MicroBitEvent);
105-
106109
/**
107110
* Activate Mic
108111
*/
@@ -190,6 +193,8 @@ namespace codal
190193
* Puts the component in (or out of) sleep (low power) mode.
191194
*/
192195
virtual int setSleep(bool doSleep) override;
196+
197+
virtual void periodicCallback();
193198
};
194199
}
195200

inc/MicroBitConfig.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
#ifdef SOFTDEVICE_PRESENT
6060
#define MICROBIT_STORAGE_PAGE ( MICROBIT_BOOTLOADER_ADDRESS - MICROBIT_CODEPAGESIZE * 3)
6161
#else
62-
#define MICROBIT_STORAGE_PAGE ( 0x7F000 )
62+
#define MICROBIT_STORAGE_PAGE ( 0x0007F000 )
6363
#endif
6464
#endif
6565

@@ -68,7 +68,11 @@
6868
#endif
6969

7070
#ifndef MICROBIT_DEFAULT_SCRATCH_PAGE
71-
#define MICROBIT_DEFAULT_SCRATCH_PAGE ( MICROBIT_BOOTLOADER_ADDRESS - MICROBIT_CODEPAGESIZE * 4)
71+
#ifdef SOFTDEVICE_PRESENT
72+
#define MICROBIT_DEFAULT_SCRATCH_PAGE ( MICROBIT_BOOTLOADER_ADDRESS - MICROBIT_CODEPAGESIZE * 4)
73+
#else
74+
#define MICROBIT_DEFAULT_SCRATCH_PAGE ( 0x0007E000 )
75+
#endif
7276
#endif
7377

7478
#ifndef MICROBIT_STORAGE_SCRATCH_PAGE
@@ -87,6 +91,13 @@
8791
#endif
8892
#endif
8993

94+
#ifndef MICROBIT_TOP_OF_FLASH
95+
#ifdef SOFTDEVICE_PRESENT
96+
#define MICROBIT_TOP_OF_FLASH ( 0x00073000 )
97+
#else
98+
#define MICROBIT_TOP_OF_FLASH ( 0x0007E000 )
99+
#endif
100+
#endif
90101

91102
#ifndef MICROBIT_APP_REGION_END
92103
#define MICROBIT_APP_REGION_END ( MICROBIT_DEFAULT_SCRATCH_PAGE)

inc/Mixer2.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class Mixer2 : public DataSource
165165
* @param sampleRate (samples per second) - if set to zero, defaults to the output sample rate of the Mixer
166166
* @param sampleRange (quantization levels) the difference between the maximum and minimum sample level on the input channel
167167
*/
168-
MixerChannel *addChannel(DataSource &stream, float sampleRate = CONFIG_MIXER_DEFAULT_CHANNEL_SAMPLERATE, int sampleRange = CONFIG_MIXER_INTERNAL_RANGE);
168+
MixerChannel *addChannel(DataSource &stream, float sampleRate = 0.0f, int sampleRange = CONFIG_MIXER_INTERNAL_RANGE);
169169

170170
/**
171171
* Removes a channel from the mixer

inc/SampleSource.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#ifndef SAMPLE_SOURCE_H
2+
#define SAMPLE_SOURCE_H
3+
4+
#include "MemorySource.h"
5+
#include "DataStream.h"
6+
#include "Mixer2.h"
7+
8+
namespace codal
9+
{
10+
class SampleSource : public MemorySource
11+
{
12+
private:
13+
Mixer2 &mixer;
14+
float sampleRate;
15+
float sampleRange;
16+
MixerChannel *channel;
17+
18+
public:
19+
/**
20+
* Constructor.
21+
* Creates an empty sample source with a Mixer.
22+
* = CONFIG_MIXER_DEFAULT_SAMPLERATE
23+
*/
24+
SampleSource(Mixer2 &mixer, float sampleRate, float sampleRange);
25+
26+
/**
27+
* Destructor
28+
* Removes all resources held by the instance
29+
*/
30+
~SampleSource();
31+
32+
void setSampleRate(float sampleRate);
33+
34+
void setVolume(float volume);
35+
};
36+
}
37+
#endif

model/MicroBit.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ int MicroBit::init()
201201
// which saves processor time, memeory and battery life.
202202
messageBus.listen(DEVICE_ID_MESSAGE_BUS_LISTENER, DEVICE_EVT_ANY, this, &MicroBit::onListenerRegisteredEvent);
203203
messageBus.listen(DEVICE_ID_MESSAGE_BUS_LISTENER, ID_PIN_P0, this, &MicroBit::onP0ListenerRegisteredEvent, MESSAGE_BUS_LISTENER_IMMEDIATE);
204+
messageBus.listen(DEVICE_ID_MESSAGE_BUS_IGNORED, DEVICE_EVT_ANY, this, &MicroBit::onListenerRemovedEvent);
204205

205206
#if CONFIG_ENABLED(DMESG_SERIAL_DEBUG) && DEVICE_DMESG_BUFFER_SIZE > 0
206207
codal_dmesg_set_flush_fn(microbit_dmesg_flush);
@@ -301,7 +302,6 @@ int MicroBit::init()
301302
// before any user code begins running.
302303

303304
sleep(10);
304-
305305
return DEVICE_OK;
306306
}
307307

@@ -365,7 +365,7 @@ void MicroBit::onListenerRegisteredEvent(Event evt)
365365
// The level detector uses lazy instantiation, we just need to read the data once to start it running.
366366
//audio.level->getValue();
367367
// The level detector requires that we enable constant listening, otherwise no events will be emitted.
368-
audio.levelSPL->activateForEvents( true );
368+
audio.levelSPL->listenerAdded();
369369
break;
370370

371371
case DEVICE_ID_MICROPHONE:
@@ -376,6 +376,22 @@ void MicroBit::onListenerRegisteredEvent(Event evt)
376376
}
377377
}
378378

379+
/**
380+
* A listener to perform actions as a result of Message Bus reflection.
381+
*
382+
* This callback is triggered upon a MessageBus listneer being removed.
383+
* We may wish to perform cleanup operations as a result, etc.
384+
*
385+
*/
386+
void MicroBit::onListenerRemovedEvent(Event evt)
387+
{
388+
switch(evt.value)
389+
{
390+
case DEVICE_ID_SYSTEM_LEVEL_DETECTOR:
391+
audio.levelSPL->listenerRemoved();
392+
break;
393+
}
394+
}
379395

380396
/**
381397
* Perfom scheduler idle
@@ -475,3 +491,8 @@ void microbit_dmesg_flush()
475491
#endif
476492
}
477493

494+
uint32_t *microbit_top_of_flash()
495+
{
496+
return (uint32_t *) MICROBIT_TOP_OF_FLASH;
497+
}
498+

model/MicroBit.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ DEALINGS IN THE SOFTWARE.
6767
#include "StreamNormalizer.h"
6868
#include "LevelDetector.h"
6969
#include "LevelDetectorSPL.h"
70+
#include "SampleSource.h"
7071
#include "PulseIn.h"
7172
#include "neopixel.h"
7273

@@ -121,6 +122,11 @@ namespace codal
121122
*/
122123
void onP0ListenerRegisteredEvent(Event evt);
123124

125+
/**
126+
* A callback listener to disable default audio streaming to P0 if an event handler is registered on that pin.
127+
*/
128+
void onListenerRemovedEvent(Event evt);
129+
124130
// Pin ranges used for LED matrix display.
125131

126132
public:
@@ -315,6 +321,7 @@ namespace codal
315321
}
316322

317323
void microbit_dmesg_flush();
324+
uint32_t *microbit_top_of_flash();
318325

319326
#if CONFIG_ENABLED(CODAL_USE_GLOBAL_NAMESPACE)
320327
using namespace codal;

source/MicroBitAudio.cpp

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,17 @@ MicroBitAudio::MicroBitAudio(NRF52Pin &pin, NRF52Pin &speaker, NRF52ADC &adc, NR
5555
if (MicroBitAudio::instance == NULL)
5656
MicroBitAudio::instance = this;
5757

58+
// Request a periodic callback
59+
status |= DEVICE_COMPONENT_STATUS_SYSTEM_TICK;
60+
5861
synth.allowEmptyBuffers(true);
5962

6063
mic = adc.getChannel(microphone, false);
61-
adc.setSamplePeriod( 1e6 / CONFIG_MIXER_DEFAULT_CHANNEL_SAMPLERATE );
64+
mic->setSampleRate(CONFIG_AUDIO_DEFAULT_MICROPHONE_SAMPLERATE);
6265
mic->setGain(7, 0);
6366

67+
activateMic();
68+
6469
// Implementers note: The order that the pipeline comes up here is quite sensitive. If we connect up to splitters after starting to
6570
// listen for events from them, we'll see channel startup events (which are valid!) that we don't want. So roughly always follow
6671
// the following schema:
@@ -73,46 +78,48 @@ MicroBitAudio::MicroBitAudio(NRF52Pin &pin, NRF52Pin &speaker, NRF52ADC &adc, NR
7378

7479
//Initilise input splitter
7580
rawSplitter = new StreamSplitter(mic->output);
81+
rawSplitter->filterOn(&micEnabled);
7682

7783
//Initilise stream normalizer
7884
processor = new StreamNormalizer(*rawSplitter->createChannel(), 0.08f, true, DATASTREAM_FORMAT_8BIT_SIGNED, 10);
7985

8086
//Initilise level detector SPL and attach to splitter
8187
//levelSPL = new LevelDetectorSPL(*rawSplitter->createChannel(), 85.0, 65.0, 16.0, 0, DEVICE_ID_MICROPHONE, false);
82-
levelSPL = new LevelDetectorSPL(*rawSplitter->createChannel(), 85.0, 65.0, 16.0, 52.0, DEVICE_ID_SYSTEM_LEVEL_DETECTOR, false);
83-
84-
// Connect to the rawSplitter. This must come AFTER the processor, to prevent the processor's channel activation starting the microphone
85-
if(EventModel::defaultEventBus)
86-
EventModel::defaultEventBus->listen(rawSplitter->id, DEVICE_EVT_ANY, this, &MicroBitAudio::onSplitterEvent, MESSAGE_BUS_LISTENER_IMMEDIATE);
88+
levelSPL = new LevelDetectorSPL(*rawSplitter->createChannel(), 85.0, 65.0, 16.0, 35.0f, DEVICE_ID_SYSTEM_LEVEL_DETECTOR);
8789

8890
//Initilise stream splitter
8991
splitter = new StreamSplitter(processor->output, DEVICE_ID_SPLITTER);
9092

91-
// Connect to the splitter - this COULD come after we create it, before we add any stages, as these are dynamic and will only connect on-demand, but just in case
92-
// we're going to follow the schema set out above, to be 100% sure.
93-
if(EventModel::defaultEventBus) {
94-
EventModel::defaultEventBus->listen(DEVICE_ID_SPLITTER, DEVICE_EVT_ANY, this, &MicroBitAudio::onSplitterEvent, MESSAGE_BUS_LISTENER_IMMEDIATE);
95-
EventModel::defaultEventBus->listen(DEVICE_ID_NOTIFY, mic->output.emitFlowEvents(), this, &MicroBitAudio::onSplitterEvent, MESSAGE_BUS_LISTENER_IMMEDIATE);
96-
}
93+
// Create audio input channels
94+
for (int i=0; i<CONFIG_AUDIO_INPUT_CHANNELS; i++)
95+
sampleSource[i] = new SampleSource(mixer, 11000, 255);
9796
}
9897

99-
void MicroBitAudio::onSplitterEvent(MicroBitEvent e){
100-
if( mic->output.isFlowing() || (e.value == SPLITTER_ACTIVATE || e.value == SPLITTER_CHANNEL_CONNECT) )
98+
void MicroBitAudio::periodicCallback()
99+
{
100+
if (mic->isEnabled() && !micEnabled)
101+
{
102+
//DMESG("MicroBitAudio::periodicCallback: activateMic()...");
101103
activateMic();
102-
else
104+
}
105+
106+
if (!mic->isEnabled() && micEnabled)
107+
{
108+
//DMESG("MicroBitAudio::periodicCallback: deactivateMic()...");
103109
deactivateMic();
110+
}
104111
}
105112

106113
void MicroBitAudio::activateMic(){
107114
runmic.setDigitalValue(1);
108115
runmic.setHighDrive(true);
109116
adc.activateChannel(mic);
117+
adc.getChannel(microphone, false)->setStartDelay(1);
110118
this->micEnabled = true;
111119
}
112120

113121
void MicroBitAudio::deactivateMic(){
114122
this->micEnabled = false;
115-
//mic->disable();
116123
runmic.setDigitalValue(0);
117124
runmic.setHighDrive(false);
118125
}

0 commit comments

Comments
 (0)