microsoft / pxt-microbit

A Blocks / JavaScript code editor for the micro:bit built on Microsoft MakeCode
https://makecode.microbit.org
Other
727 stars 638 forks source link

Considerations for updating to CODAL v0.2.50 #5002

Closed microbit-carlos closed 1 year ago

microbit-carlos commented 1 year ago
abchatra commented 1 year ago

PR to fix the first 2

JohnVidler commented 1 year ago

This will be added to the CODAL documentation when I return from leave, but as its pressing now, here are some relevant sections on the uBit stream implementation, dynamic IDs and how Demand Activation works inside CODAL

uBit Stream Implementation

The uBit object implements the following pipeline by default, and will be started up with the recommended setting for all components. These should be used for all higher-level interfaces to the microphone unless you really have good reasons to not do it this way, as it will likely break other parts of the audio interface.

Untitled Diagram drawio (uBit member names are in parenthesis, in the uBit.audio. namespace. Note that the Low Pass Filter is temporarily disabled in 0.2.50 but is expected to be reintroduced for later tags)

Its recommended that most downstream classes should use the splitter object via splitter->createChannel() or similar calls to rawSplitter if non-normalized data streams are required.

Note that data rate changes are implemented in the splitters, and that calling float requestSampleRate(float sampleRate); will popuplate up the stream towards the source until it reaches a component that is able to change data rate (usually a splitter, occasionally the data source, like the mic) - if the stream is unable to service this rate, it will return the rate its actually emitting as a return to this call - so extensions/implementations should check the return value here if they're particularly sensitive to the data rate.

Dynamic Component IDs

A small final note on the implementation in uBit - if user code listens to the DEVICE_ID_SPLITTER identifier, they will recieve events for only the splitter object, and not the rawSplitter. This will usually suffice, but if events from the rawSplitter are also required its ID can be accessed via rawSplitter->id. The MicroBitAudio class does this internally like so:

EventModel::defaultEventBus->listen(rawSplitter->id, DEVICE_EVT_ANY, this, &MicroBitAudio::onSplitterEvent, MESSAGE_BUS_LISTENER_IMMEDIATE);

This is surfaced this way as the ID is dynamic, and will be allocated by CODAL on startup, rather than a global identifier, as we have a single way of accessing it directly anyway and we don't want to pollute or confuse the ID space with any semi-useful IDs.

uBit.audio.rawSplitter->id (a single line to get the rawSplitter ID)

All component IDs can be checked this way, and its advisable, if there is a path to do so, to use the IDs provided via lookups as above, rather than relying on global identifiers. There are no plans to deprecate the existing ones, but as we have more components added/removed especially through technologies such as JacDac, the ID space will quickly have clashes if we don't move to more dynamic IDs.

Implementations of new dynamic components can get a runtime ID through the static method CodalComponent::generateDynamicID(); and some newer components, such as the StreamSplitter have their default IDs set to be automatically in the dynamic ID space:

StreamSplitter(DataSource &source, uint16_t id = CodalComponent::generateDynamicID());

(From StreamSplitter.h in codal-core)

Microphone (Demand Activation and Level Detector changes)

As the microphone is now 'demand activated' rather than needing to be requested by each user, its no longer nessicary to manually turn the mic on and off for float LevelDetectorSPL::getValue() calls, as simply making the call will fire up the stream and give you a sample.

However, if you need continuous readings, as is the case for onLoud and friends, there is now a void LevelDetectorSPL::activateForEvents( bool state ) method which when set to true will keep the level detector consuming data so it can emit events.

This has been implemented as a seperate call rather than part of simply listening to the event so that languages above CODAL can elect to connect to the events without being forced to enable the mic constantly, then can independently enable/disable events through the call above as/when needed.

Note here that 'demand activation' works on components consuming pull() calls only when the data is required, rather than continuously pull()ing. If a component sits mid-stream (ie. both pull()s an upstream and can be pull()ed from later downstream) then it should always forward pullRequest() calls towards its downstream even if the component itself does no work with the data during a pullRequest, otherwise components later in the pipeline will not be notified of pending data. A common pattern for this is to simply do the following:

int StreamNormalizer::pullRequest()
{
    return output.pullRequest();
}

(Copied from the StreamNormalizer class)

The return value should also be preserved, as it indicates that the pipeline is working correctly and components sinking data should return DEVICE_OK to indicate no error has occurred.

This all together means that pullRequest calls exhert a kind of pressure on downstream components which is measureable by upstream components (detecting if pull calls are made) to establish if data is being consumed, thus we can detect if the pipeline should continue to 1. supply data, and 2. keep any source devices involved activated (like the mic).