diff --git a/.gitignore b/.gitignore index 654d1b9b..39fd005f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *~ *.swp .vscode +.DS_Store diff --git a/inc/core/CodalComponent.h b/inc/core/CodalComponent.h index c9e0b0fc..ec1755d5 100644 --- a/inc/core/CodalComponent.h +++ b/inc/core/CodalComponent.h @@ -68,6 +68,21 @@ DEALINGS IN THE SOFTWARE. #define DEVICE_ID_SYSTEM_ADC 34 #define DEVICE_ID_PULSE_IN 35 #define DEVICE_ID_USB 36 +#define DEVICE_ID_SPLITTER 37 +#define DEVICE_ID_AUDIO_PROCESSOR 38 +#define DEVICE_ID_TAP 39 +#define DEVICE_ID_POWER_MANAGER 40 + +// Slightly dubious IDs, moved here for now to remove conflicts +#define DEVICE_ID_PARTIAL_FLASHING 41 +#define DEVICE_ID_USB_FLASH_MANAGER 42 +#define DEVICE_ID_VIRTUAL_SPEAKER_PIN 43 +#define DEVICE_ID_LOG 44 + +// Suggested range for device-specific IDs: 50-79 +// NOTE - not final, just suggested currently. + +// Range 80-99 - RESERVED (deprecated space, do not use!) #define DEVICE_ID_IO_P0 100 // IDs 100-227 are reserved for I/O Pin IDs. @@ -83,6 +98,9 @@ DEALINGS IN THE SOFTWARE. // jacadac reserved from 3000 - 4000 #define DEVICE_ID_JD_DYNAMIC_ID 3000 +// Range 64000-65000 RESERVED (dynamic ID space) +#define DEVICE_ID_DYNAMIC_MIN 64000 +#define DEVICE_ID_DYNAMIC_MAX 65000 // Universal flags used as part of the status field #define DEVICE_COMPONENT_RUNNING 0x1000 @@ -124,6 +142,24 @@ namespace codal static uint8_t configuration; public: + /** + * @brief Generates a new component ID from the dynamic block. + * + * There are some cases where multiple instances of the same component need unique IDs, this + * static method allows application code to request a safe (unused) globally unique ID for + * this purpose. + * + * This currently creates monotonically incrementing IDs, with no reuse. When they're gone + * they're gone! + * + * @note While currently monotonic, this may change in the future for more complex schemes. + * + * @note The dynamic block is a finite resource, and may eventually be exhausted causing a PANIC. + * + * @return uint16_t A new, unique component ID, until the ID space is exhausted. + */ + static uint16_t generateDynamicID(); + /** * Adds the current CodalComponent instance to our array of components. */ diff --git a/inc/core/CodalConfig.h b/inc/core/CodalConfig.h index 9d23c90e..0d718249 100644 --- a/inc/core/CodalConfig.h +++ b/inc/core/CodalConfig.h @@ -153,15 +153,28 @@ DEALINGS IN THE SOFTWARE. // // Debug options // -#ifndef DEVICE_DMESG -#define DEVICE_DMESG 0 +#ifndef DMESG_SERIAL_DEBUG + #define DMESG_SERIAL_DEBUG 0 +#else + // Automatically enable DMESG_ENABLE if DMESG_SERIAL_DEBUG is set + #if DMESG_SERIAL_DEBUG > 0 + #define DMESG_ENABLE 1 + #endif +#endif + +#ifndef DMESG_ENABLE +#define DMESG_ENABLE 0 #endif // When non-zero internal debug messages (DMESG() macro) go to a in-memory buffer of this size (in bytes). // It can be inspected from GDB (with 'print codalLogStore'), or accessed by the application. // Typical size range between 512 and 4096. Set to 0 to disable. #ifndef DEVICE_DMESG_BUFFER_SIZE -#define DEVICE_DMESG_BUFFER_SIZE 1024 + #if DMESG_ENABLE > 0 + #define DEVICE_DMESG_BUFFER_SIZE 1024 + #else + #define DEVICE_DMESG_BUFFER_SIZE 0 + #endif #endif #ifndef CODAL_DEBUG diff --git a/inc/core/CodalFiber.h b/inc/core/CodalFiber.h index 822e0332..0251e543 100644 --- a/inc/core/CodalFiber.h +++ b/inc/core/CodalFiber.h @@ -368,6 +368,13 @@ namespace codal */ FiberLock(); + /** + * Create a new semaphore-mode lock that can be used for mutual exclusion and condition synchronisation. + * + * @param initial The number of access requests to grant before blocking + */ + FiberLock( int initial ); + /** * Block the calling fiber until the lock is available **/ @@ -380,8 +387,10 @@ namespace codal /** * Release the lock, and signal to all waiting fibers to continue + * + * @param reset The number of slots to reinitialise the FiberLock to, for semaphore duty */ - void notifyAll(); + void notifyAll( int reset = 0 ); /** * Determine the number of fibers currently blocked on this lock diff --git a/inc/core/ErrorNo.h b/inc/core/ErrorNo.h index 63840341..a5a73a75 100644 --- a/inc/core/ErrorNo.h +++ b/inc/core/ErrorNo.h @@ -85,14 +85,18 @@ enum PanicCode{ // Out out memory error. Heap storage was requested, but is not available. DEVICE_OOM = 20, + // Device has run out of a finite resource. Dynamic IDs for example. + DEVICE_RESORUCES_EXHAUSTED = 21, + // Corruption detected in the codal device heap space DEVICE_HEAP_ERROR = 30, // Dereference of a NULL pointer through the ManagedType class, DEVICE_NULL_DEREFERENCE = 40, - // Non-recoverable error in USB driver - DEVICE_USB_ERROR = 50, + // All ob-board peripheral failures should report from the 50-59 range, but may be device specific + // DO NOT USE ANYTHING BETWEEN 50 AND 60 HERE - used by implementing boards/libraries + DEVICE_PERIPHERAL_ERROR = 50, // Non-recoverable error in the JACDAC stack DEVICE_JACDAC_ERROR = 60, diff --git a/inc/driver-models/CodalUSB.h b/inc/driver-models/CodalUSB.h index 22e3adf2..47ca2ce7 100644 --- a/inc/driver-models/CodalUSB.h +++ b/inc/driver-models/CodalUSB.h @@ -210,6 +210,9 @@ class UsbEndpointIn int clearStall(); int reset(); int write(const void *buf, int length); +#ifdef USB_EP_FLAG_ASYNC + bool canWrite(); +#endif UsbEndpointIn(uint8_t idx, uint8_t type, uint8_t size = USB_MAX_PKT_SIZE); }; diff --git a/inc/driver-models/I2C.h b/inc/driver-models/I2C.h index 0b136d5d..542412f5 100644 --- a/inc/driver-models/I2C.h +++ b/inc/driver-models/I2C.h @@ -37,7 +37,7 @@ namespace codal enum AcknowledgeType {ACK, NACK}; -class I2C +class I2C : public PinPeripheral { public: I2C(Pin &sda, Pin &scl); @@ -80,6 +80,15 @@ class I2C virtual int read(AcknowledgeType ack = ACK); public: + /** + * Change the pins used by this I2C peripheral to those provided. + * + * @param sda the Pin to use for the I2C SDA line. + * @param scl the Pin to use for the I2C SCL line. + * @return DEVICE_OK on success, or DEVICE_NOT_IMPLEMENTED / DEVICE_NOT_SUPPORTED if the request cannot be performed. + */ + virtual int redirect(Pin &sda, Pin &scl); + /** * Issues a standard, 2 byte I2C command write to the I2C bus. * This consists of: diff --git a/inc/driver-models/LowLevelTimer.h b/inc/driver-models/LowLevelTimer.h index bd758d0d..dacf7b1e 100644 --- a/inc/driver-models/LowLevelTimer.h +++ b/inc/driver-models/LowLevelTimer.h @@ -71,8 +71,8 @@ class LowLevelTimer : public CodalComponent **/ virtual int setIRQPriority(int) { - target_panic(DEVICE_NOT_IMPLEMENTED); - return DEVICE_NOT_IMPLEMENTED; + target_panic(DEVICE_HARDWARE_CONFIGURATION_ERROR); + return DEVICE_HARDWARE_CONFIGURATION_ERROR; } /** diff --git a/inc/driver-models/Pin.h b/inc/driver-models/Pin.h index fbc25c04..16840a82 100644 --- a/inc/driver-models/Pin.h +++ b/inc/driver-models/Pin.h @@ -27,6 +27,7 @@ DEALINGS IN THE SOFTWARE. #include "CodalConfig.h" #include "CodalComponent.h" +#include "PinPeripheral.h" // Status Field flags... #define IO_STATUS_DIGITAL_IN 0x0001 // Pin is configured as a digital input, with no pull up. #define IO_STATUS_DIGITAL_OUT 0x0002 // Pin is configured as a digital output @@ -38,6 +39,7 @@ DEALINGS IN THE SOFTWARE. #define IO_STATUS_INTERRUPT_ON_EDGE 0x0080 // Pin will generate events on pin change #define IO_STATUS_ACTIVE_HI 0x0100 // Pin is ACTIVE_HI if set, or ACTIVE_LO if clear #define IO_STATUS_WAKE_ON_ACTIVE 0x0200 // Pin should trigger power manager wake-up +#define IO_STATUS_DISCONNECTING 0x0400 // Pin is currently in the process of disconnecting from a peripheral #define DEVICE_PIN_MAX_OUTPUT 1023 @@ -59,6 +61,7 @@ DEALINGS IN THE SOFTWARE. namespace codal { using namespace codal; + /** * Pin capabilities enum. * Used to determine the capabilities of each Pin as some can only be digital, or can be both digital and analogue. @@ -269,6 +272,17 @@ namespace codal return (status & (IO_STATUS_ANALOG_IN | IO_STATUS_ANALOG_OUT)) == 0 ? 0 : 1; } + /** + * Determines if this IO pin is currently in the processing of being disconnected from a peripheral. + * + * @return 1 if pin is disconnecting, 0 otherwise. + */ + virtual int isDisconnecting() + { + return (status & IO_STATUS_DISCONNECTING) == 0 ? 0 : 1; + } + + /** * Configures this IO pin as a "makey makey" style touch sensor (if necessary) * and tests its current debounced state. @@ -531,6 +545,13 @@ namespace codal return (status & IO_STATUS_WAKE_ON_ACTIVE) ? 1 : 0; } + /** + * Record that a given peripheral has been connected to this pin. + */ + virtual void connect(PinPeripheral &p, bool deleteOnRelease = false) + { + p.deleteOnRelease = deleteOnRelease; + } /** * Disconnect any attached peripherals from this pin. */ diff --git a/inc/driver-models/PinPeripheral.h b/inc/driver-models/PinPeripheral.h new file mode 100644 index 00000000..a65b951a --- /dev/null +++ b/inc/driver-models/PinPeripheral.h @@ -0,0 +1,87 @@ +/* +The MIT License (MIT) + +Copyright (c) 2022 Lancaster University. + +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 PIN_PERIPHERAL_H +#define PIN_PERIPHERAL_H + +#include "CodalConfig.h" +#include "CodalComponent.h" + +namespace codal +{ + /** + * Class definition for PinPeripheral. + * + * Serves as an abstract base class for any device driver that directly interacts with a Pin. + * Provides the necessary function to enable safe, dynamic rebinding of pins to peripherals at runtime + */ + class Pin; + class PinPeripheral + { + public: + bool deleteOnRelease = false; + bool pinLock = false; + + /** + * Method to release the given pin from a peripheral, if already bound. + * Device drivers should override this method to disconnect themselves from the give pin + * to allow it to be used by a different peripheral. + * + * @param pin the Pin to be released + */ + virtual int releasePin(Pin &pin); + + /** + * Determines if this peripheral has locked any attached pins to this peripheral. + * During a locked period, any attempts to release or reassign those pins to a differnet peripheral are ignored. + * This mechanism is primarily useful to use functions such as Pin::setDigitalValue() within a peripheral driver, + * but without releasing the pin's binding to that peripheral. + * + * @return true if this peripherals pin bindings are locked, false otherwise. + */ + bool isPinLocked(); + + /** + * Controls if this peripheral has locked any attached pins to this peripheral. + * During a locked period, any attempts to release or reassign those pins to a differnet peripheral are ignored. + * This mechanism is primarily useful to use functions such as Pin::setDigitalValue() within a peripheral driver, + * but without releasing the pin's binding to that peripheral. + * + * @param true if this peripherals pin bindings are to be locked, false otherwise. + */ + void setPinLock(bool locked); + + /** + * Utility function, to assist in redirect() operations and consistent use of disconnect()/connect() by peripherals. + * Safely disconnects pin from any attached peripherals, upfates pin to the new pin, and attaches to the given peripheral. + * Also validates out NULL cases. + * + * @param pin Typically a mutable instance variable, holding the current pin used by a given peripheral. + * @param newPin The pin which is replacing the value of pin. + */ + int reassignPin(void *pin, Pin *newPin); + }; +} + +#endif diff --git a/inc/driver-models/SPI.h b/inc/driver-models/SPI.h index 558469b3..c9b0ce06 100644 --- a/inc/driver-models/SPI.h +++ b/inc/driver-models/SPI.h @@ -39,9 +39,20 @@ typedef void (*PVoidCallback)(void *); /** * Class definition for an SPI interface. */ -class SPI +class SPI : public PinPeripheral { public: + + /** + * Change the pins used by this I2C peripheral to those provided. + * + * @param mosi the Pin to use for the SPI input line. + * @param miso the Pin to use for the SPI output line. + * @param sclk the Pin to use for the SPI clock line. + * @return DEVICE_OK on success, or DEVICE_NOT_IMPLEMENTED / DEVICE_NOT_SUPPORTED if the request cannot be performed. + */ + virtual int redirect(Pin &mosi, Pin &miso, Pin &sclk); + /** Set the frequency of the SPI interface * * @param frequency The bus frequency in hertz diff --git a/inc/driver-models/Serial.h b/inc/driver-models/Serial.h index 807689d8..5569f123 100644 --- a/inc/driver-models/Serial.h +++ b/inc/driver-models/Serial.h @@ -65,12 +65,12 @@ namespace codal * * Represents an instance of RawSerial which accepts codal device specific data types. */ - class Serial : public CodalComponent + class Serial : public PinPeripheral, public CodalComponent { protected: - Pin& tx; - Pin& rx; + Pin* tx; + Pin* rx; //delimeters used for matching on receive. ManagedString delimeters; @@ -182,6 +182,11 @@ namespace codal * gives a different behaviour: * * ASYNC - bytes are copied into the txBuff and returns immediately. + * If there is insufficient space in txBuff, then only the first 'n' + * bytes will actually be sent, refer to the return value to get + * how many bytes were actually put in the buffer. + * To ensure all bytes are sent on an ASYNC call, set an appropriate + * buffer length beforehand using setTxBufferSize. * * SYNC_SPINWAIT - bytes are copied into the txBuff and this method * will spin (lock up the processor) until all bytes @@ -210,6 +215,11 @@ namespace codal * gives a different behaviour: * * ASYNC - bytes are copied into the txBuff and returns immediately. + * If there is insufficient space in txBuff, then only the first 'n' + * bytes will actually be sent, refer to the return value to get + * how many bytes were actually put in the buffer. + * To ensure all bytes are sent on an ASYNC call, set an appropriate + * buffer length beforehand using setTxBufferSize. * * SYNC_SPINWAIT - bytes are copied into the txBuff and this method * will spin (lock up the processor) until all bytes diff --git a/inc/driver-models/SingleWireSerial.h b/inc/driver-models/SingleWireSerial.h index 9e232811..3dc2c3e8 100644 --- a/inc/driver-models/SingleWireSerial.h +++ b/inc/driver-models/SingleWireSerial.h @@ -22,7 +22,7 @@ namespace codal SingleWireDisconnected }; - class SingleWireSerial : public CodalComponent + class SingleWireSerial : public PinPeripheral, public CodalComponent { protected: virtual void configureRxInterrupt(int enable) = 0; @@ -39,6 +39,7 @@ namespace codal { this->id = id; this->cb = NULL; + p.connect(*this); } /** diff --git a/inc/drivers/Button.h b/inc/drivers/Button.h index 7ef0a61c..91b611d9 100644 --- a/inc/drivers/Button.h +++ b/inc/drivers/Button.h @@ -38,7 +38,7 @@ namespace codal * * Represents a single, generic button on the device. */ - class Button : public AbstractButton + class Button : public AbstractButton, public PinPeripheral { unsigned long downStartTime; // used to store the current system clock when a button down event occurs uint8_t sigma; // integration of samples over time. We use this for debouncing, and noise tolerance for touch sensing @@ -125,6 +125,15 @@ namespace codal return _pin.isWakeOnActive(); } + /** + * Method to release the given pin from a peripheral, if already bound. + * Device drivers should override this method to disconnect themselves from the give pin + * to allow it to be used by a different peripheral. + * + * @param pin the Pin to be released + */ + virtual int releasePin(Pin &pin) override; + /** * Destructor for Button, where we deregister this instance from the array of fiber components. */ diff --git a/inc/drivers/PulseIn.h b/inc/drivers/PulseIn.h index ac6e4546..58554880 100644 --- a/inc/drivers/PulseIn.h +++ b/inc/drivers/PulseIn.h @@ -34,7 +34,7 @@ DEALINGS IN THE SOFTWARE. namespace codal { -class PulseIn +class PulseIn : public PinPeripheral { Pin &pin; uint32_t lastPeriod; @@ -74,10 +74,22 @@ class PulseIn void onTimeout(Event e); + /** + * Method to release the given pin from a peripheral, if already bound. + * Device drivers should override this method to disconnect themselves from the give pin + * to allow it to be used by a different peripheral. + * + * @param pin the Pin to be released + */ + virtual int releasePin(Pin &pin) override; + /** * Destructor */ - ~PulseIn(); + virtual ~PulseIn(); + + private: + void disable(); }; } diff --git a/inc/drivers/TouchButton.h b/inc/drivers/TouchButton.h index e2398a03..85b990a8 100644 --- a/inc/drivers/TouchButton.h +++ b/inc/drivers/TouchButton.h @@ -119,6 +119,20 @@ namespace codal */ int buttonActive(); + /** + * Determines the instantneous digital value of the pin associated with this TouchButton + * + * @return true if a digital read of the attached pin is a logic 1, false otherwise. + */ + int getPinValue(); + + /** + * Drives a given digital value to the pin associated with this TouchButton + * + * @param v the digital value to write to the pin associated with this TouchButton. + */ + void setPinValue(int v); + /** * Destructor for Button, where we deregister this instance from the array of fiber components. */ diff --git a/inc/streams/DataStream.h b/inc/streams/DataStream.h index b1027ea3..3348c786 100644 --- a/inc/streams/DataStream.h +++ b/inc/streams/DataStream.h @@ -44,6 +44,8 @@ DEALINGS IN THE SOFTWARE. #define DATASTREAM_FORMAT_BYTES_PER_SAMPLE(x) ((x+1)/2) +#define DATASTREAM_SAMPLE_RATE_UNKNOWN 0.0f + namespace codal { /** @@ -53,7 +55,7 @@ namespace codal { public: - virtual int pullRequest(); + virtual int pullRequest(); }; /** @@ -62,12 +64,13 @@ namespace codal class DataSource { public: - - virtual ManagedBuffer pull(); - virtual void connect(DataSink &sink); - virtual void disconnect(); - virtual int getFormat(); - virtual int setFormat(int format); + virtual ManagedBuffer pull(); + virtual void connect(DataSink &sink); + virtual void disconnect(); + virtual int getFormat(); + virtual int setFormat(int format); + virtual float getSampleRate(); + virtual float requestSampleRate(float sampleRate); }; /** @@ -78,12 +81,13 @@ namespace codal class DataStream : public DataSource, public DataSink { ManagedBuffer stream[DATASTREAM_MAXIMUM_BUFFERS]; - int bufferCount; - int bufferLength; - int preferredBufferSize; - int writers; - uint16_t spaceAvailableEventCode; - uint16_t pullRequestEventCode; + //int bufferCount; + //int bufferLength; + //int preferredBufferSize; + //int writers; + //uint16_t spaceAvailableEventCode; + //uint16_t pullRequestEventCode; + ManagedBuffer * nextBuffer; bool isBlocking; DataSink *downStream; @@ -91,118 +95,122 @@ namespace codal public: - /** - * Default Constructor. - * Creates an empty DataStream. - * - * @param upstream the component that will normally feed this datastream with data. - */ - DataStream(DataSource &upstream); - - /** - * Destructor. - * Removes all resources held by the instance. - */ - ~DataStream(); - - /** - * Determines the value of the given byte in the buffer. - * - * @param position The index of the byte to read. - * @return The value of the byte at the given position, or DEVICE_INVALID_PARAMETER. - */ - int get(int position); - - /** - * Sets the byte at the given index to value provided. - * @param position The index of the byte to change. - * @param value The new value of the byte (0-255). - * @return DEVICE_OK, or DEVICE_INVALID_PARAMETER. - * - */ - int set(int position, uint8_t value); - - /** - * Gets number of bytes that are ready to be consumed in this data stream. - * @return The size in bytes. - */ - int length(); - - /** - * Determines if any of the data currently flowing through this stream is held in non-volatile (FLASH) memory. - * @return true if one or more of the ManagedBuffers in this stream reside in FLASH memory, false otherwise. - */ - bool isReadOnly(); - - /** - * Define a downstream component for data stream. - * - * @sink The component that data will be delivered to, when it is available - */ - virtual void connect(DataSink &sink) override; - - /** - * Define a downstream component for data stream. - * - * @sink The component that data will be delivered to, when it is available - */ - virtual void disconnect() override; - - /** - * Determine the data format of the buffers streamed out of this component. - */ - virtual int getFormat() override; - - /** - * Determine the number of bytes that are currnetly buffered before blocking subsequent push() operations. - * @return the current preferred buffer size for this DataStream - */ - int getPreferredBufferSize(); - - /** - * Define the number of bytes that should be buffered before blocking subsequent push() operations. - * @param size The number of bytes to buffer. - */ - void setPreferredBufferSize(int size); - - /** - * Determines if this stream acts in a synchronous, blocking mode or asynchronous mode. In blocking mode, writes to a full buffer - * will result int he calling fiber being blocked until space is available. Downstream DataSinks will also attempt to process data - * immediately as it becomes available. In non-blocking asynchronpus mode, writes to a full buffer are dropped and downstream Datasinks will - * be processed in a new fiber. - */ - void setBlocking(bool isBlocking); - - /** - * Determines if a buffer of the given size can be added to the buffer. - * - * @param size The number of bytes to add to the buffer. - * @return true if there is space for "size" bytes in the buffer. false otherwise. - */ - bool canPull(int size = 0); - - /** - * Determines if the DataStream can accept any more data. - * - * @return true if there if the buffer is ful, and can accept no more data at this time. False otherwise. - */ - bool full(); - - /** - * Provide the next available ManagedBuffer to our downstream caller, if available. - */ - virtual ManagedBuffer pull(); - - /** - * Deliver the next available ManagedBuffer to our downstream caller. - */ - virtual int pullRequest(); + /** + * Default Constructor. + * Creates an empty DataStream. + * + * @param upstream the component that will normally feed this datastream with data. + */ + DataStream(DataSource &upstream); + + /** + * Destructor. + * Removes all resources held by the instance. + */ + ~DataStream(); + + /** + * Determines the value of the given byte in the buffer. + * + * @param position The index of the byte to read. + * @return The value of the byte at the given position, or DEVICE_INVALID_PARAMETER. + */ + //int get(int position); + + /** + * Sets the byte at the given index to value provided. + * @param position The index of the byte to change. + * @param value The new value of the byte (0-255). + * @return DEVICE_OK, or DEVICE_INVALID_PARAMETER. + * + */ + //int set(int position, uint8_t value); + + /** + * Gets number of bytes that are ready to be consumed in this data stream. + * @return The size in bytes. + */ + //int length(); + + /** + * Determines if any of the data currently flowing through this stream is held in non-volatile (FLASH) memory. + * @return true if one or more of the ManagedBuffers in this stream reside in FLASH memory, false otherwise. + */ + bool isReadOnly(); + + /** + * Define a downstream component for data stream. + * + * @sink The component that data will be delivered to, when it is available + */ + virtual void connect(DataSink &sink) override; + + /** + * Define a downstream component for data stream. + * + * @sink The component that data will be delivered to, when it is available + */ + virtual void disconnect() override; + + /** + * Determine the data format of the buffers streamed out of this component. + */ + virtual int getFormat() override; + + /** + * Determine the number of bytes that are currnetly buffered before blocking subsequent push() operations. + * @return the current preferred buffer size for this DataStream + */ + //int getPreferredBufferSize(); + + /** + * Define the number of bytes that should be buffered before blocking subsequent push() operations. + * @param size The number of bytes to buffer. + */ + //void setPreferredBufferSize(int size); + + /** + * Determines if this stream acts in a synchronous, blocking mode or asynchronous mode. In blocking mode, writes to a full buffer + * will result int he calling fiber being blocked until space is available. Downstream DataSinks will also attempt to process data + * immediately as it becomes available. In non-blocking asynchronpus mode, writes to a full buffer are dropped and downstream Datasinks will + * be processed in a new fiber. + */ + void setBlocking(bool isBlocking); + + /** + * Determines if a buffer of the given size can be added to the buffer. + * + * @param size The number of bytes to add to the buffer. + * @return true if there is space for "size" bytes in the buffer. false otherwise. + */ + bool canPull(int size = 0); + + /** + * Determines if the DataStream can accept any more data. + * + * @return true if there if the buffer is ful, and can accept no more data at this time. False otherwise. + */ + ///bool full(); + + /** + * Provide the next available ManagedBuffer to our downstream caller, if available. + */ + virtual ManagedBuffer pull(); + + /** + * Deliver the next available ManagedBuffer to our downstream caller. + */ + virtual int pullRequest(); + + virtual float getSampleRate() override; + + virtual float requestSampleRate(float sampleRate) override; private: - /** - * Issue a deferred pull request to our downstream component, if one has been registered. - */ - void onDeferredPullRequest(Event); + /** + * Issue a deferred pull request to our downstream component, if one has been registered. + */ + //void onDeferredPullRequest(Event); }; } diff --git a/inc/streams/EffectFilter.h b/inc/streams/EffectFilter.h new file mode 100644 index 00000000..ef37c0d8 --- /dev/null +++ b/inc/streams/EffectFilter.h @@ -0,0 +1,51 @@ +#include "ManagedBuffer.h" +#include "DataStream.h" +#include "StreamNormalizer.h" + +#ifndef EFFECT_FILTER_H +#define EFFECT_FILTER_H + +namespace codal +{ + class EffectFilter : public DataSource, public DataSink + { + protected: + + DataSink *downStream; + DataSource &upStream; + bool deepCopy; + + public: + + EffectFilter(DataSource &source, bool deepCopy = true); + ~EffectFilter(); + + virtual ManagedBuffer pull(); + virtual int pullRequest(); + virtual void connect( DataSink &sink ); + virtual void disconnect(); + virtual int getFormat(); + virtual int setFormat( int format ); + + virtual float getSampleRate(); + virtual float requestSampleRate(float sampleRate); + + /** + * Defines if this filter should perform a deep copy of incoming data, or update data in place. + * + * @param deepCopy Set to true to copy incoming data into a freshly allocated buffer, or false to change data in place. + */ + void setDeepCopy(bool deepCopy); + + /** + * Default effect - a simple pass through filter. Override this method in subclasses to create specialist effects/filters. + * + * @param inputBuffer the buffer containing data to process. + * @param outputBuffer the buffer in which to store the filtered data. n.b. MAY be the same memory as the input buffer. + * @param format the format of the data (word size and signed/unsigned representation) + */ + virtual void applyEffect(ManagedBuffer inputBuffer, ManagedBuffer outputBuffer, int format); + }; +} + +#endif \ No newline at end of file diff --git a/inc/streams/FIFOStream.h b/inc/streams/FIFOStream.h new file mode 100644 index 00000000..c7aa8394 --- /dev/null +++ b/inc/streams/FIFOStream.h @@ -0,0 +1,51 @@ +#ifndef FIFO_STREAM_H +#define FIFO_STREAM_H + +#include "ManagedBuffer.h" +#include "DataStream.h" + + +#define FIFO_MAXIMUM_BUFFERS 256 + +namespace codal { + + class FIFOStream : public DataSource, public DataSink + { + private: + + ManagedBuffer buffer[FIFO_MAXIMUM_BUFFERS]; + int bufferCount; + int bufferLength; + + bool allowInput; + bool allowOutput; + + DataSink *downStream; + DataSource &upStream; + + public: + + FIFOStream( DataSource &source ); + ~FIFOStream(); + + virtual ManagedBuffer pull(); + virtual int pullRequest(); + virtual void connect( DataSink &sink ); + virtual void disconnect(); + virtual int getFormat(); + virtual int setFormat( int format ); + int length(); + void dumpState(); + + bool canPull(); + bool isFull(); + + void setInputEnable( bool state ); + void setOutputEnable( bool state ); + + + }; + +} + +#endif \ No newline at end of file diff --git a/inc/streams/LevelDetector.h b/inc/streams/LevelDetector.h index 4a4857f8..49de01e1 100644 --- a/inc/streams/LevelDetector.h +++ b/inc/streams/LevelDetector.h @@ -60,6 +60,8 @@ namespace codal{ int windowPosition; // The number of samples used so far in the calculation of a window. int level; // The current, instantaneous level. int sigma; // Running total of the samples in the current window. + bool activated; // Has this component been connected yet. + int ttl; /** @@ -69,8 +71,9 @@ namespace codal{ * @param highThreshold the HIGH threshold at which a LEVEL_THRESHOLD_HIGH event will be generated * @param lowThreshold the HIGH threshold at which a LEVEL_THRESHOLD_LOW event will be generated * @param id The id to use for the message bus when transmitting events. + * @param connectImmediately Should this component connect to upstream splitter when started */ - LevelDetector(DataSource &source, int highThreshold, int lowThreshold, uint16_t id = DEVICE_ID_SYSTEM_LEVEL_DETECTOR); + LevelDetector(DataSource &source, int highThreshold, int lowThreshold, uint16_t id = DEVICE_ID_SYSTEM_LEVEL_DETECTOR, bool connectImmediately = true); /** * Callback provided when data is ready. @@ -84,6 +87,8 @@ namespace codal{ */ int getValue(); + void activateForEvents( bool state ); + /** * Set threshold to the given value. Events will be generated when these thresholds are crossed. * diff --git a/inc/streams/LevelDetectorSPL.h b/inc/streams/LevelDetectorSPL.h index 4829c3c1..4df27d63 100644 --- a/inc/streams/LevelDetectorSPL.h +++ b/inc/streams/LevelDetectorSPL.h @@ -34,12 +34,48 @@ DEALINGS IN THE SOFTWARE. #define LEVEL_DETECTOR_SPL_INITIALISED 0x01 #define LEVEL_DETECTOR_SPL_HIGH_THRESHOLD_PASSED 0x02 #define LEVEL_DETECTOR_SPL_LOW_THRESHOLD_PASSED 0x04 +#define LEVEL_DETECTOR_SPL_CLAP 0x05 /** * Default configuration values */ #define LEVEL_DETECTOR_SPL_DEFAULT_WINDOW_SIZE 128 +#ifndef LEVEL_DETECTOR_SPL_NORMALIZE +#define LEVEL_DETECTOR_SPL_NORMALIZE 1 +#endif + +/** + * Define the parameters for the dB->8bit translation function. + */ + +// The level (in dB) that corresponds to an 8bit value of 0. +#ifndef LEVEL_DETECTOR_SPL_8BIT_000_POINT +#define LEVEL_DETECTOR_SPL_8BIT_000_POINT 35.0f +#endif + +// The level (in dB) that corresponds to an 8bit value of 255. +#ifndef LEVEL_DETECTOR_SPL_8BIT_255_POINT +#define LEVEL_DETECTOR_SPL_8BIT_255_POINT 100.0f +#endif + +#define LEVEL_DETECTOR_SPL_8BIT_CONVERSION (255.0f/(LEVEL_DETECTOR_SPL_8BIT_255_POINT-LEVEL_DETECTOR_SPL_8BIT_000_POINT)) + +/** + * Level detetor unit enumeration. + */ +#define LEVEL_DETECTOR_SPL_DB 1 +#define LEVEL_DETECTOR_SPL_8BIT 2 + +// Clap detection constants +#define LEVEL_DETECTOR_SPL_BEGIN_POSS_CLAP_RMS 200 // threshold to start considering clap - rms value +#define LEVEL_DETECTOR_SPL_MIN_IN_CLAP_RMS 300 // minimum amount to be within a clap once considering +#define LEVEL_DETECTOR_SPL_CLAP_OVER_RMS 100 // threshold once in clap to consider noise over +#define LEVEL_DETECTOR_SPL_CLAP_MAX_LOUD_BLOCKS 13 // ensure noise not too long to be a clap +#define LEVEL_DETECTOR_SPL_CLAP_MIN_LOUD_BLOCKS 2 // ensure noise not too short to be a clap +#define LEVEL_DETECTOR_SPL_CLAP_MIN_QUIET_BLOCKS 20 // prevent very fast taps being registered as clap + + namespace codal{ class LevelDetectorSPL : public CodalComponent, public DataSink { @@ -54,7 +90,14 @@ namespace codal{ int sigma; // Running total of the samples in the current window. float gain; float minValue; - + bool activated; // Has this component been connected yet + bool enabled; // Is the component currently running + int unit; // The units to be returned from this level detector (e.g. dB or linear 8bit) + uint64_t timeout; + int quietBlockCount; // number of quiet blocks consecutively - used for clap detection + int noisyBlockCount; // number of noisy blocks consecutively - used for clap detection + bool inNoisyBlock; // if had noisy and waiting to lower beyond lower threshold + float maxRms; // maximum rms within a noisy block /** * Creates a component capable of measuring and thresholding stream data @@ -63,10 +106,12 @@ namespace codal{ * @param highThreshold the HIGH threshold at which a SPL_LEVEL_THRESHOLD_HIGH event will be generated * @param lowThreshold the HIGH threshold at which a SPL_LEVEL_THRESHOLD_LOW event will be generated * @param id The id to use for the message bus when transmitting events. + * @param connectImmediately Should this component connect to upstream splitter when started */ LevelDetectorSPL(DataSource &source, float highThreshold, float lowThreshold, float gain, float minValue = 52, - uint16_t id = DEVICE_ID_SYSTEM_LEVEL_DETECTOR_SPL); + uint16_t id = DEVICE_ID_SYSTEM_LEVEL_DETECTOR_SPL, + bool connectImmediately = true); /** * Callback provided when data is ready. @@ -80,6 +125,13 @@ namespace codal{ */ float getValue(); + void activateForEvents( bool state ); + + /** + * Disable component + */ + void disable(); + /** * Set threshold to the given value. Events will be generated when these thresholds are crossed. * @@ -125,11 +177,22 @@ namespace codal{ int setGain(float gain); + /** + * Defines the units that will be returned by the getValue() function. + * + * @param unit Either LEVEL_DETECTOR_SPL_DB or LEVEL_DETECTOR_SPL_8BIT. + * @return DEVICE_OK or DEVICE_INVALID_PARAMETER. + */ + int setUnit(int unit); + /** * Destructor. */ ~LevelDetectorSPL(); + private: + float splToUnit(float f); + float unitToSpl(float f); }; } diff --git a/inc/streams/LowPassFilter.h b/inc/streams/LowPassFilter.h new file mode 100644 index 00000000..4eabc439 --- /dev/null +++ b/inc/streams/LowPassFilter.h @@ -0,0 +1,36 @@ +#include "ManagedBuffer.h" +#include "DataStream.h" +#include "EffectFilter.h" + +#ifndef LOW_PASS_FILTER_H +#define LOW_PASS_FILTER_H + +namespace codal +{ + class LowPassFilter : public EffectFilter + { + private: + + float lpf_value; + float lpf_beta; + + public: + + LowPassFilter( DataSource &source, float beta = 0.003f, bool deepCopy = true); + ~LowPassFilter(); + + /** + * Apply a simple low pass filter on the give buffer of data. + * Y(n) = (1-ß)*Y(n-1) + (ß*X(n))) = Y(n-1) - (ß*(Y(n-1)-X(n))); + * + * @param inputBuffer the buffer containing data to process. + * @param outputBuffer the buffer in which to store the filtered data. n.b. MAY be the same memory as the input buffer. + * @param format the format of the data (word size and signed/unsigned representation) + */ + virtual void applyEffect(ManagedBuffer inputBuffer, ManagedBuffer outputBuffer, int format) override; + + void setBeta( float beta ); + }; +} + +#endif \ No newline at end of file diff --git a/inc/streams/StreamFlowTrigger.h b/inc/streams/StreamFlowTrigger.h new file mode 100644 index 00000000..8ce7843f --- /dev/null +++ b/inc/streams/StreamFlowTrigger.h @@ -0,0 +1,36 @@ +#include "ManagedBuffer.h" +#include "DataStream.h" + +#ifndef STREAM_FLOW_TRIGGER_H +#define STREAM_FLOW_TRIGGER_H + +#define TRIGGER_PULL 1 +#define TRIGGER_REQUEST 2 + +namespace codal { + + class StreamFlowTrigger : public DataSource, public DataSink { + private: + + DataSink *downStream; + DataSource &upStream; + + void (*eventHandler)(int); + + public: + + StreamFlowTrigger( DataSource &source ); + ~StreamFlowTrigger(); + + void setDataHandler( void (*handler)(int) ); + + virtual ManagedBuffer pull(); + virtual int pullRequest(); + virtual void connect( DataSink &sink ); + virtual void disconnect(); + virtual int getFormat(); + virtual int setFormat( int format ); + }; +} + +#endif \ No newline at end of file diff --git a/inc/streams/StreamNormalizer.h b/inc/streams/StreamNormalizer.h index 6ce01a03..259153be 100644 --- a/inc/streams/StreamNormalizer.h +++ b/inc/streams/StreamNormalizer.h @@ -51,10 +51,10 @@ namespace codal{ uint32_t orMask; // post processing step - or'd with each sample. bool normalize; // If set, will recalculate a zero offset. bool zeroOffsetValid; // Set to true after the first buffer has been processed. - bool outputEnabled; // When set any buffer processed will be forwarded downstream. + bool outputEnabled; // When set any bxuffer processed will be forwarded downstream. DataSource &upstream; // The upstream component of this StreamNormalizer. DataStream output; // The downstream output stream of this StreamNormalizer. - ManagedBuffer buffer; // The buffer being processed. + //ManagedBuffer buffer; // The buffer being processed. static SampleReadFn readSample[9]; static SampleWriteFn writeSample[9]; @@ -137,6 +137,10 @@ namespace codal{ */ int setOrMask(uint32_t mask); + virtual float getSampleRate(); + + virtual float requestSampleRate(float sampleRate); + /** * Destructor. */ diff --git a/inc/streams/StreamRecording.h b/inc/streams/StreamRecording.h new file mode 100644 index 00000000..ec9fc3c9 --- /dev/null +++ b/inc/streams/StreamRecording.h @@ -0,0 +1,158 @@ +#ifndef RECORDING_STREAM_H +#define RECORDING_STREAM_H + +#include "ManagedBuffer.h" +#include "DataStream.h" + +// Pretty much the largest sensible number we can have on a v2 +#define REC_MAX_BUFFERS 200 + +#define REC_STATE_STOPPED 0 +#define REC_STATE_PLAYING 1 +#define REC_STATE_RECORDING 2 + +namespace codal +{ + + class StreamRecording : public DataSource, public DataSink + { + private: + + ManagedBuffer buffer[REC_MAX_BUFFERS]; + unsigned int lastBuffer; + unsigned int bufferLength; + unsigned int readWriteHead; + int state; + + DataSink *downStream; + DataSource &upStream; + + public: + + /** + * @brief Construct a new Stream Recording object + * + * @param source An upstream DataSource to connect to + */ + StreamRecording( DataSource &source ); + + /** + * @brief Destroy the Stream Recording object + */ + ~StreamRecording(); + + virtual ManagedBuffer pull(); + virtual int pullRequest(); + virtual void connect( DataSink &sink ); + virtual void disconnect(); + virtual int getFormat(); + virtual int setFormat( int format ); + + /** + * @brief Calculate and return the length in bytes that this StreamRecording represents + * + * @return int The length, in bytes. + */ + int length(); + + /** + * @brief Calculate the recorded duration for this StreamRecording. + * + * As this cannot be known by this class (as the sample rate may change during playback or recording) the expected rate must be supplied. + * + * @param sampleRate The sample rate to calculate the duration for, in samples per second. + * @return long The total duration of this StreamRecording, based on the supplied sample rate, in seconds. + */ + float duration( unsigned int sampleRate ); + + /** + * @brief Prints information about the internal state of this object, for debugging. + */ + void dumpState(); + + /** + * @brief Downstream classes should use this to determing if there is data to pull from this StreamRecording object. + * + * @return true If data is available + * @return false If the object is completely empty + */ + bool canPull(); + + /** + * @brief Checks if this object can store any further ManagedBuffers from the upstream components. + * + * @note This does not mean that RAM is completely full, but simply that there is now more internal storage for ManagedBuffer references. + * + * @return true If there are no more slots available to track more ManagedBuffers. + * @return false If there is remaining internal storage capacity for more data + */ + bool isFull(); + + /** + * @brief Begin recording data from the connected upstream + * + * The StreamRecording object will, if already playing; stop playback, erase its buffer, and start recording. + * + * Non-blocking, will return immediately. + * + * @return Do not use this value, return semantics are changing. + */ + bool record(); + + /** + * @brief Begin playing data from the connected upstream + * + * The StreamRecording object will, if already recording; stop recording, rewind to the start of its buffer, and start playing. + * + * Non-blocking, will return immediately. + * + * @return Do not use this value, return semantics are changing. + */ + bool play(); + + /** + * @brief Stop recording or playing the data stored in this StreamRecording object. + * + * Repeated calls to this will do nothing if the object is not in a recording or playback state. + * + * @return Do not use this value, return semantics are changing. + */ + void stop(); + + /** + * @brief Erase the internal buffer. + * + * Will also stop playback or recording, if either are active. + * + * @return Do not use this value, return semantics are changing. + */ + void erase(); + + /** + * @brief Checks if the object is playing back recorded data. + * + * @return True if playing back, else false if stopped or recording. + */ + bool isPlaying(); + + /** + * @brief Checks if the object is recording new data. + * + * @return True if recording, else false if stopped or playing back. + */ + bool isRecording(); + + /** + * @brief Checks if the object is stopped + * + * @return True if stopped, else false if recording or playing back. + */ + bool isStopped(); + + virtual float getSampleRate(); + + }; + +} + +#endif \ No newline at end of file diff --git a/inc/streams/StreamSplitter.h b/inc/streams/StreamSplitter.h new file mode 100644 index 00000000..90c3cb25 --- /dev/null +++ b/inc/streams/StreamSplitter.h @@ -0,0 +1,131 @@ +/* +The MIT License (MIT) + +Copyright (c) 2021 Lancaster University. + +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 "CodalConfig.h" +#include "DataStream.h" + +#ifndef STREAM_SPLITTER_H +#define STREAM_SPLITTER_H + +#ifndef CONFIG_MAX_CHANNELS +#define CONFIG_MAX_CHANNELS 10 +#endif + +#ifndef CONFIG_BLOCKING_THRESHOLD +#define CONFIG_BLOCKING_THRESHOLD 100 +#endif + +/** + * Splitter events + */ +#define SPLITTER_ACTIVATE_CHANNEL 1 +#define SPLITTER_DEACTIVATE_CHANNEL 2 +#define SPLITTER_CHANNEL_CONNECT 3 +#define SPLITTER_CHANNEL_DISCONNECT 4 +#define SPLITTER_ACTIVATE 5 +#define SPLITTER_DEACTIVATE 6 + + +/** + * Default configuration values + */ + +namespace codal{ + + class StreamSplitter; + + class SplitterChannel : public DataSource, public DataSink { + private: + StreamSplitter * parent; + float sampleRate; + + public: + int pullAttempts; // Number of failed pull request attempts + DataSink * output; + + /** + * @brief Construct a new Splitter Channel object. + * + * This should not normally be done manually; StreamSplitter objects will create these + * on-demand via createChannel() + * + * @param parent The StreamSplitter this channel is part of + * @param output An output DataSink to send data to. Can be NULL for a disconnected channel. + */ + SplitterChannel( StreamSplitter *parent, DataSink *output ); + virtual ~SplitterChannel(); + + virtual int pullRequest(); + virtual ManagedBuffer pull(); + virtual void connect(DataSink &sink); + virtual void disconnect(); + virtual int getFormat(); + virtual int setFormat(int format); + virtual float getSampleRate(); + virtual float requestSampleRate(float sampleRate); + }; + + class StreamSplitter : public DataSink, public CodalComponent + { + private: + ManagedBuffer lastBuffer; // Buffer being processed + uint64_t __cycle; + + public: + bool isActive; // Track if we need to emit activate/deactivate messages + int channels; // Current number of channels Splitter is serving + volatile int activeChannels; // Current number of /active/ channels this Splitter is serving + DataSource &upstream; // The upstream component of this Splitter + SplitterChannel * outputChannels[CONFIG_MAX_CHANNELS]; // Array of SplitterChannels the Splitter is serving + + /** + * Creates a component that distributes a single upstream datasource to many downstream datasinks + * + * @param source a DataSource to receive data from + */ + StreamSplitter(DataSource &source, uint16_t id = CodalComponent::generateDynamicID()); + + void periodicCallback(); + + /** + * Callback provided when data is ready. + */ + virtual int pullRequest(); + + virtual ManagedBuffer getBuffer(); + virtual SplitterChannel * createChannel(); + virtual bool destroyChannel( SplitterChannel * channel ); + virtual SplitterChannel * getChannel( DataSink * output ); + + /** + * Destructor. + */ + virtual ~StreamSplitter(); + + friend SplitterChannel; + + }; +} + +#endif \ No newline at end of file diff --git a/inc/streams/Synthesizer.h b/inc/streams/Synthesizer.h index 9c0b941b..f17dc97d 100644 --- a/inc/streams/Synthesizer.h +++ b/inc/streams/Synthesizer.h @@ -34,11 +34,6 @@ namespace codal { typedef uint16_t (*SynthesizerGetSample)(void *arg, int position); - /** - * Class definition for DataStream. - * A Datastream holds a number of ManagedBuffer references, provides basic flow control through a push/pull mechanism - * and byte level access to the datastream, even if it spans different buffers. - */ class Synthesizer : public DataSource, public CodalComponent { int samplePeriodNs; // The length of a single sample, in nanoseconds. @@ -115,7 +110,7 @@ namespace codal * Determine the sample rate currently in use by this Synthesizer. * @return the current sample rate, in Hz. */ - int getSampleRate(); + float getSampleRate(); /** * Change the sample rate used by this Synthesizer, diff --git a/inc/types/ManagedString.h b/inc/types/ManagedString.h index 1fbffc70..b7658ad9 100644 --- a/inc/types/ManagedString.h +++ b/inc/types/ManagedString.h @@ -341,7 +341,9 @@ namespace codal * display.scroll(s + p) // scrolls "abcdefgh" * @endcode */ + #ifndef DOXYGEN_SHOULD_SKIP_THIS // Friend members as 'const ... &' currently break breathe/exhale doc generation, so we MUST skip this for now. friend ManagedString (codal::operator+) (const ManagedString& lhs, const ManagedString& rhs); + #endif /** * Provides a character value at a given position in the string, indexed from zero. diff --git a/library.json b/library.json new file mode 100644 index 00000000..26fa0ba2 --- /dev/null +++ b/library.json @@ -0,0 +1,8 @@ +{ + "docs": { + "INPUT": [ + "source", + "inc" + ] + } +} diff --git a/source/core/CodalComponent.cpp b/source/core/CodalComponent.cpp index 8e4d8c47..59124de3 100644 --- a/source/core/CodalComponent.cpp +++ b/source/core/CodalComponent.cpp @@ -34,9 +34,19 @@ CodalComponent* CodalComponent::components[DEVICE_COMPONENT_COUNT]; uint8_t CodalComponent::configuration = 0; #if DEVICE_COMPONENT_COUNT > 255 -#error "DEVICE_COMPONENT_COUNT has to fit in uint8_t" + #error "DEVICE_COMPONENT_COUNT has to fit in uint8_t" #endif +uint16_t CodalComponent::generateDynamicID() { + static uint16_t __nextDynamicID = DEVICE_ID_DYNAMIC_MIN; + + // Have we blown off the end of our dynamic space? + if( __nextDynamicID > DEVICE_ID_DYNAMIC_MAX ) + target_panic( PanicCode::DEVICE_RESORUCES_EXHAUSTED ); + + return __nextDynamicID++; +} + /** * The periodic callback for all components. */ diff --git a/source/core/CodalFiber.cpp b/source/core/CodalFiber.cpp index 7e0fe033..58064de3 100644 --- a/source/core/CodalFiber.cpp +++ b/source/core/CodalFiber.cpp @@ -22,14 +22,6 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/** - * Functionality definitions for the Device Fiber scheduler. - * - * This lightweight, non-preemptive scheduler provides a simple threading mechanism for two main purposes: - * - * 1) To provide a clean abstraction for application languages to use when building async behaviour (callbacks). - * 2) To provide ISR decoupling for EventModel events generated in an ISR context. - */ #include "CodalConfig.h" #include "CodalFiber.h" #include "Timer.h" @@ -70,18 +62,6 @@ static EventModel *messageBus = NULL; using namespace codal; -/** - * Utility function to add the currenty running fiber to the given queue. - * - * Perform a simple add at the head, to avoid complexity, - * - * Queues are normally very short, so maintaining a doubly linked, sorted list typically outweighs the cost of - * brute force searching. - * - * @param f The fiber to add to the queue - * - * @param queue The run queue to add the fiber to. - */ REAL_TIME_FUNC void codal::queue_fiber(Fiber *f, Fiber **queue) { @@ -115,11 +95,6 @@ void codal::queue_fiber(Fiber *f, Fiber **queue) target_enable_irq(); } -/** - * Utility function to the given fiber from whichever queue it is currently stored on. - * - * @param f the fiber to remove. - */ REAL_TIME_FUNC void codal::dequeue_fiber(Fiber *f) { @@ -145,19 +120,11 @@ void codal::dequeue_fiber(Fiber *f) target_enable_irq(); } -/** - * Provides a list of all active fibers. - * - * @return A pointer to the head of the list of all active fibers. - */ Fiber * codal::get_fiber_list() { return fiberList; } -/** - * Allocates a fiber from the fiber pool if available. Otherwise, allocates a new one from the heap. - */ REAL_TIME_FUNC Fiber *getFiberContext() { @@ -205,15 +172,6 @@ Fiber *getFiberContext() return f; } - -/** - * Initialises the Fiber scheduler. - * Creates a Fiber context around the calling thread, and adds it to the run queue as the current thread. - * - * This function must be called once only from the main thread, and before any other Fiber operation. - * - * @param _messageBus An event model, used to direct the priorities of the scheduler. - */ void codal::scheduler_init(EventModel &_messageBus) { // If we're already initialised, then nothing to do. @@ -250,11 +208,6 @@ void codal::scheduler_init(EventModel &_messageBus) fiber_flags |= DEVICE_SCHEDULER_RUNNING; } -/** - * Determines if the fiber scheduler is operational. - * - * @return 1 if the fber scheduler is running, 0 otherwise. - */ REAL_TIME_FUNC int codal::fiber_scheduler_running() { @@ -264,11 +217,6 @@ int codal::fiber_scheduler_running() return 0; } -/** - * The timer callback, called from interrupt context once every SYSTEM_TICK_PERIOD_MS milliseconds. - * This function checks to determine if any fibers blocked on the sleep queue need to be woken up - * and made runnable. - */ void codal::scheduler_tick(Event evt) { Fiber *f = sleepQueue; @@ -294,14 +242,6 @@ void codal::scheduler_tick(Event evt) } } -/** - * Event callback. Called from an instance of DeviceMessageBus whenever an event is raised. - * - * This function checks to determine if any fibers blocked on the wait queue need to be woken up - * and made runnable due to the event. - * - * @param evt the event that has just been raised on an instance of DeviceMessageBus. - */ void codal::scheduler_event(Event evt) { Fiber *f = waitQueue; @@ -378,16 +318,6 @@ static Fiber* handle_fob() return f; } -/** - * Blocks the calling thread for the given period of time. - * The calling thread will be immediateley descheduled, and placed onto a - * wait queue until the requested amount of time has elapsed. - * - * @param t The period of time to sleep, in milliseconds. - * - * @note the fiber will not be be made runnable until after the elapsed time, but there - * are no guarantees precisely when the fiber will next be scheduled. - */ void codal::fiber_sleep(unsigned long t) { // If the scheduler is not running, then simply perform a spin wait and exit. @@ -412,24 +342,6 @@ void codal::fiber_sleep(unsigned long t) schedule(); } -/** - * Blocks the calling thread until the specified event is raised. - * The calling thread will be immediateley descheduled, and placed onto a - * wait queue until the requested event is received. - * - * @param id The ID field of the event to listen for (e.g. DEVICE_ID_BUTTON_A) - * - * @param value The value of the event to listen for (e.g. DEVICE_BUTTON_EVT_CLICK) - * - * @return DEVICE_OK, or DEVICE_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. - * - * @code - * fiber_wait_for_event(DEVICE_ID_BUTTON_A, DEVICE_BUTTON_EVT_CLICK); - * @endcode - * - * @note the fiber will not be be made runnable until after the event is raised, but there - * are no guarantees precisely when the fiber will next be scheduled. - */ int codal::fiber_wait_for_event(uint16_t id, uint16_t value) { int ret = fiber_wake_on_event(id, value); @@ -440,25 +352,6 @@ int codal::fiber_wait_for_event(uint16_t id, uint16_t value) return ret; } -/** - * Configures the fiber context for the current fiber to block on an event ID - * and value, but does not deschedule the fiber. - * - * @param id The ID field of the event to listen for (e.g. DEVICE_ID_BUTTON_A) - * - * @param value The value of the event to listen for (e.g. DEVICE_BUTTON_EVT_CLICK) - * - * @return DEVICE_OK, or DEVICE_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. - * - * @code - * fiber_wake_on_event(DEVICE_ID_BUTTON_A, DEVICE_BUTTON_EVT_CLICK); - * - * //perform some time critical operation. - * - * //deschedule the current fiber manually, waiting for the previously configured event. - * schedule(); - * @endcode - */ int codal::fiber_wake_on_event(uint16_t id, uint16_t value) { if (messageBus == NULL || !fiber_scheduler_running()) @@ -489,19 +382,6 @@ int codal::fiber_wake_on_event(uint16_t id, uint16_t value) #define HAS_THREAD_USER_DATA false #endif -/** - * Executes the given function asynchronously if necessary. - * - * Fibers are often used to run event handlers, however many of these event handlers are very simple functions - * that complete very quickly, bringing unecessary RAM overhead. - * - * This function takes a snapshot of the current processor context, then attempts to optimistically call the given function directly. - * We only create an additional fiber if that function performs a block operation. - * - * @param entry_fn The function to execute. - * - * @return DEVICE_OK, or DEVICE_INVALID_PARAMETER. - */ int codal::invoke(void (*entry_fn)(void)) { // Validate our parameters. @@ -553,21 +433,6 @@ int codal::invoke(void (*entry_fn)(void)) return DEVICE_OK; } -/** - * Executes the given function asynchronously if necessary, and offers the ability to provide a parameter. - * - * Fibers are often used to run event handlers, however many of these event handlers are very simple functions - * that complete very quickly, bringing unecessary RAM. overhead - * - * This function takes a snapshot of the current fiber context, then attempt to optimistically call the given function directly. - * We only create an additional fiber if that function performs a block operation. - * - * @param entry_fn The function to execute. - * - * @param param an untyped parameter passed into the entry_fn and completion_fn. - * - * @return DEVICE_OK, or DEVICE_INVALID_PARAMETER. - */ int codal::invoke(void (*entry_fn)(void *), void *param) { // Validate our parameters. @@ -619,13 +484,6 @@ int codal::invoke(void (*entry_fn)(void *), void *param) return DEVICE_OK; } -/** - * Launches a fiber. - * - * @param ep the entry point for the fiber. - * - * @param cp the completion routine after ep has finished execution - */ void codal::launch_new_fiber(void (*ep)(void), void (*cp)(void)) { // Execute the thread's entrypoint @@ -638,15 +496,6 @@ void codal::launch_new_fiber(void (*ep)(void), void (*cp)(void)) release_fiber(); } -/** - * Launches a fiber with a parameter - * - * @param ep the entry point for the fiber. - * - * @param cp the completion routine after ep has finished execution - * - * @param pm the parameter to provide to ep and cp. - */ void codal::launch_new_fiber_param(void (*ep)(void *), void (*cp)(void *), void *pm) { // Execute the thread's entrypoint. @@ -684,16 +533,6 @@ Fiber *__create_fiber(uint32_t ep, uint32_t cp, uint32_t pm, int parameterised) return newFiber; } -/** - * Creates a new Fiber, and launches it. - * - * @param entry_fn The function the new Fiber will begin execution in. - * - * @param completion_fn The function called when the thread completes execution of entry_fn. - * Defaults to release_fiber. - * - * @return The new Fiber, or NULL if the operation could not be completed. - */ Fiber *codal::create_fiber(void (*entry_fn)(void), void (*completion_fn)(void)) { if (!fiber_scheduler_running()) @@ -702,19 +541,6 @@ Fiber *codal::create_fiber(void (*entry_fn)(void), void (*completion_fn)(void)) return __create_fiber((uint32_t) entry_fn, (uint32_t)completion_fn, 0, 0); } - -/** - * Creates a new parameterised Fiber, and launches it. - * - * @param entry_fn The function the new Fiber will begin execution in. - * - * @param param an untyped parameter passed into the entry_fn and completion_fn. - * - * @param completion_fn The function called when the thread completes execution of entry_fn. - * Defaults to release_fiber. - * - * @return The new Fiber, or NULL if the operation could not be completed. - */ Fiber *codal::create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *)) { if (!fiber_scheduler_running()) @@ -723,11 +549,6 @@ Fiber *codal::create_fiber(void (*entry_fn)(void *), void *param, void (*complet return __create_fiber((uint32_t) entry_fn, (uint32_t)completion_fn, (uint32_t) param, 1); } -/** - * Exit point for all fibers. - * - * Any fiber reaching the end of its entry function will return here for recycling. - */ void codal::release_fiber(void *) { if (!fiber_scheduler_running()) @@ -736,11 +557,6 @@ void codal::release_fiber(void *) release_fiber(); } -/** - * Exit point for all fibers. - * - * Any fiber reaching the end of its entry function will return here for recycling. - */ REAL_TIME_FUNC void codal::release_fiber(void) { @@ -798,16 +614,6 @@ void codal::release_fiber(void) schedule(); } -/** - * Resizes the stack allocation of the current fiber if necessary to hold the system stack. - * - * If the stack allocation is large enough to hold the current system stack, then this function does nothing. - * Otherwise, the the current allocation of the fiber is freed, and a larger block is allocated. - * - * @param f The fiber context to verify. - * - * @return The stack depth of the given fiber. - */ void codal::verify_stack_size(Fiber *f) { // Ensure the stack buffer is large enough to hold the stack Reallocate if necessary. @@ -829,6 +635,12 @@ void codal::verify_stack_size(Fiber *f) Fiber *prevCurrFiber = currentFiber; currentFiber = f; + // GCC would normally assume malloc() and free() can't access currentFiber variable + // and thus skip emitting the store above. + // We invoke an external function that GCC knows nothing about (any function will do) + // to force GCC to emit the store. + get_current_sp(); + // To ease heap churn, we choose the next largest multple of 32 bytes. bufferSize = (stackDepth + 32) & 0xffffffe0; @@ -846,31 +658,16 @@ void codal::verify_stack_size(Fiber *f) } } -/** - * Determines if any fibers are waiting to be scheduled. - * - * @return The number of fibers currently on the run queue - */ int codal::scheduler_runqueue_empty() { return (runQueue == NULL); } -/** - * Determines if any fibers are waiting for events. - * - * @return 1 if there are no fibers currently waiting for events; otherwise 0 - */ int codal::scheduler_waitqueue_empty() { return (waitQueue == NULL); } -/** - * Calls the Fiber scheduler. - * The calling Fiber will likely be blocked, and control given to another waiting fiber. - * Call this function to yield control of the processor when you have nothing more to do. - */ void codal::schedule() { if (!fiber_scheduler_running()) @@ -974,10 +771,6 @@ void codal::schedule() } } -/** - * Set of tasks to perform when idle. - * Service any background tasks that are required, and attempt a power efficient sleep. - */ void codal::idle() { // Prevent an idle loop of death: @@ -1001,11 +794,6 @@ void codal::idle() } } -/** - * The idle task, which is called when the runtime has no fibers that require execution. - * - * This function typically calls idle(). - */ void codal::idle_task() { while(1) @@ -1015,21 +803,11 @@ void codal::idle_task() } } -/** - * Determines if deep sleep is pending. - * - * @return 1 if deep sleep is pending, 0 otherwise. - */ int codal::fiber_scheduler_get_deepsleep_pending() { return fiber_flags & DEVICE_SCHEDULER_DEEPSLEEP ? 1 : 0; } -/** - * Flag if deep sleep is pending. - * - * @param penfing 1 if deep sleep is pending, 0 otherwise. - */ void codal::fiber_scheduler_set_deepsleep_pending( int pending) { if ( pending) @@ -1038,18 +816,14 @@ void codal::fiber_scheduler_set_deepsleep_pending( int pending) fiber_flags &= ~DEVICE_SCHEDULER_DEEPSLEEP; } -/** - * Create a new lock that can be used for mutual exclusion and condition synchronisation. - */ -FiberLock::FiberLock() +FiberLock::FiberLock( int initial ) { queue = NULL; - locked = false; + locked = 0-initial; } -/** - * Block the calling fiber until the lock is available - **/ +FiberLock::FiberLock() : FiberLock( 0 ) {} + REAL_TIME_FUNC void FiberLock::wait() { @@ -1093,9 +867,6 @@ void FiberLock::wait() } } -/** - * Release the lock, and signal to one waiting fiber to continue - */ void FiberLock::notify() { Fiber *f = queue; @@ -1106,14 +877,12 @@ void FiberLock::notify() queue_fiber(f, &runQueue); } - if (locked > 0) - locked--; + // This allows the lock to reach into the negative, to allow limited access to an + // access constrained resource + locked--; } -/** - * Release the lock, and signal to all waiting fibers to continue - */ -void FiberLock::notifyAll() +void FiberLock::notifyAll( int reset ) { Fiber *f = queue; @@ -1124,13 +893,9 @@ void FiberLock::notifyAll() f = queue; } - locked = 0; + locked = reset; } - -/** - * Determine the number of fibers currently blocked on this lock - */ int FiberLock::getWaitCount() { Fiber *f = queue; diff --git a/source/driver-models/I2C.cpp b/source/driver-models/I2C.cpp index 4acd924a..f4441e8a 100644 --- a/source/driver-models/I2C.cpp +++ b/source/driver-models/I2C.cpp @@ -34,6 +34,18 @@ namespace codal { } + /** + * Change the pins used by this I2C peripheral to those provided. + * + * @param sda the Pin to use for the I2C SDA line. + * @param scl the Pin to use for the I2C SCL line. + * @return DEVICE_OK on success, or DEVICE_NOT_IMPLEMENTED / DEVICE_NOT_SUPPORTED if the request cannot be performed. + */ + int I2C::redirect(Pin &sda, Pin &scl) + { + return DEVICE_NOT_IMPLEMENTED; + } + /** * Set the frequency of the I2C interface * diff --git a/source/driver-models/PinPeripheral.cpp b/source/driver-models/PinPeripheral.cpp new file mode 100644 index 00000000..930bcd96 --- /dev/null +++ b/source/driver-models/PinPeripheral.cpp @@ -0,0 +1,98 @@ +/* +The MIT License (MIT) + +Copyright (c) 2022 Lancaster University. + +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 "Pin.h" +#include "CodalDmesg.h" + +using namespace codal; + +/** + * Method to release the given pin from a peripheral, if already bound. + * Device drivers should override this method to disconnect themselves from the give pin + * to allow it to be used by a different peripheral. + * + * @param pin the Pin to be released. + * @return DEVICE_OK on success, or DEVICE_NOT_IMPLEMENTED if unsupported, or DEVICE_INVALID_PARAMETER if the pin is not bound to this peripheral. + */ +int PinPeripheral::releasePin(Pin &pin) +{ + return DEVICE_NOT_IMPLEMENTED; +} + +/** + * Determines if this peripheral has locked any attached pins to this peripheral. + * During a locked period, any attempts to release or reassign those pins to a differnet peripheral are ignored. + * This mechanism is primarily useful to use functions such as Pin::setDigitalValue() within a peripheral driver, + * but without releasing the pin's binding to that peripheral. + * + * @return true if this peripherals pin bindings are locked, false otherwise. + */ +bool PinPeripheral::isPinLocked() +{ + return pinLock; +} + +/** + * Controls if this peripheral has locked any attached pins to this peripheral. + * During a locked period, any attempts to release or reassign those pins to a differnet peripheral are ignored. + * This mechanism is primarily useful to use functions such as Pin::setDigitalValue() within a peripheral driver, + * but without releasing the pin's binding to that peripheral. + * + * @param true if this peripherals pin bindings are to be locked, false otherwise. + */ +void PinPeripheral::setPinLock(bool locked) +{ + pinLock = locked; +} + +/** + * Utility function, to assist in redirect() operations and consistent use of disconnect()/connect() by peripherals. + * Safely disconnects pin from any attached peripherals, upfates pin to the new pin, and attaches to the given peripheral. + * Also validates out NULL cases. + * + * @param p Typically a mutable instance variable, holding the current pin used by a given peripheral. + * @param newPin The pin which is replacing the value of pin. + */ +int PinPeripheral::reassignPin(void *p, Pin *newPin) +{ + Pin **pin = (Pin **)p; + + if (pin == NULL) + return DEVICE_INVALID_PARAMETER; + + // If the pin is changing state, reelase any old peripherals and attach the new one. + if (*pin != newPin) + { + if (*pin) + (*pin)->disconnect(); + + if (newPin) + newPin->connect(*this); + + *pin = newPin; + } + + return DEVICE_OK; +} + diff --git a/source/driver-models/SPI.cpp b/source/driver-models/SPI.cpp index 1f34b99a..62d16620 100644 --- a/source/driver-models/SPI.cpp +++ b/source/driver-models/SPI.cpp @@ -29,6 +29,19 @@ DEALINGS IN THE SOFTWARE. namespace codal { +/** + * Change the pins used by this I2C peripheral to those provided. + * + * @param mosi the Pin to use for the SPI input line. + * @param miso the Pin to use for the SPI output line. + * @param sclk the Pin to use for the SPI clock line. + * @return DEVICE_OK on success, or DEVICE_NOT_IMPLEMENTED / DEVICE_NOT_SUPPORTED if the request cannot be performed. + */ +int SPI::redirect(Pin &mosi, Pin &miso, Pin &sclk) +{ + return DEVICE_NOT_IMPLEMENTED; +} + /** * Writes and reads from the SPI bus concurrently. Waits (possibly un-scheduled) for transfer to * finish. diff --git a/source/driver-models/Serial.cpp b/source/driver-models/Serial.cpp index d72d6bcc..7d956491 100644 --- a/source/driver-models/Serial.cpp +++ b/source/driver-models/Serial.cpp @@ -256,7 +256,7 @@ void Serial::circularCopy(uint8_t *circularBuff, uint8_t circularBuffSize, uint8 * * Buffers aren't allocated until the first send or receive respectively. */ -Serial::Serial(Pin& tx, Pin& rx, uint8_t rxBufferSize, uint8_t txBufferSize, uint16_t id) : tx(tx), rx(rx) +Serial::Serial(Pin& tx, Pin& rx, uint8_t rxBufferSize, uint8_t txBufferSize, uint16_t id) : tx(&tx), rx(&rx) { this->id = id; @@ -275,6 +275,9 @@ Serial::Serial(Pin& tx, Pin& rx, uint8_t rxBufferSize, uint8_t txBufferSize, uin this->rxBuffHeadMatch = -1; + reassignPin(&this->tx, &tx); + reassignPin(&this->rx, &rx); + this->status |= DEVICE_COMPONENT_STATUS_IDLE_TICK; } @@ -841,11 +844,17 @@ int Serial::redirect(Pin& tx, Pin& rx) lockTx(); lockRx(); + reassignPin(&this->tx, &tx); + reassignPin(&this->rx, &rx); + if(txBufferedSize() > 0) disableInterrupt(TxInterrupt); disableInterrupt(RxInterrupt); + // To be compatible with V1 behaviour + rx.setPull( PullMode::Up ); + configurePins(tx, rx); enableInterrupt(RxInterrupt); diff --git a/source/driver-models/Timer.cpp b/source/driver-models/Timer.cpp index 8537bc5f..23500ebc 100644 --- a/source/driver-models/Timer.cpp +++ b/source/driver-models/Timer.cpp @@ -100,6 +100,7 @@ Timer::Timer(LowLevelTimer& t, uint8_t ccPeriodChannel, uint8_t ccEventChannel) { // Register ourselves as the defualt timer - most recent timer wins. system_timer = this; + this->ccPeriodChannel = ccPeriodChannel; this->ccEventChannel = ccEventChannel; @@ -374,6 +375,8 @@ void Timer::trigger(bool isFallback) uint16_t id = e->id; uint16_t value = e->value; + + // Release before triggering event. Otherwise, an immediate event handler // can cancel this event, another event might be put in its place // and we end up releasing (or repeating) a completely different event. @@ -607,6 +610,7 @@ CODAL_TIMESTAMP codal::system_timer_current_time() * * @return the current time since power on in microseconds */ +REAL_TIME_FUNC CODAL_TIMESTAMP codal::system_timer_current_time_us() { if(system_timer == NULL) diff --git a/source/drivers/AnimatedDisplay.cpp b/source/drivers/AnimatedDisplay.cpp index 46cffe10..b520ec3d 100644 --- a/source/drivers/AnimatedDisplay.cpp +++ b/source/drivers/AnimatedDisplay.cpp @@ -274,7 +274,7 @@ void AnimatedDisplay::stopAnimation() void AnimatedDisplay::waitForFreeDisplay() { // If there's an ongoing animation, wait for our turn to display. - if (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED) + while (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED) fiber_wait_for_event(DEVICE_ID_NOTIFY, DISPLAY_EVT_FREE); } diff --git a/source/drivers/Button.cpp b/source/drivers/Button.cpp index fb573918..e756a5d9 100644 --- a/source/drivers/Button.cpp +++ b/source/drivers/Button.cpp @@ -86,7 +86,13 @@ void Button::setEventConfiguration(ButtonEventConfiguration config) */ int Button::buttonActive() { - return _pin.getDigitalValue() == polarity; + bool active; + + setPinLock(true); + active = _pin.getDigitalValue() == polarity; + setPinLock(false); + + return active; } /** @@ -172,6 +178,26 @@ int Button::isPressed() return status & DEVICE_BUTTON_STATE ? 1 : 0; } +/** + * Method to release the given pin from a peripheral, if already bound. + * Device drivers should override this method to disconnect themselves from the give pin + * to allow it to be used by a different peripheral. + * + * @param pin the Pin to be released. + * @return DEVICE_OK on success, or DEVICE_NOT_IMPLEMENTED if unsupported, or DEVICE_INVALID_PARAMETER if the pin is not bound to this peripheral. + */ +int Button::releasePin(Pin &pin) +{ + // We've been asked to disconnect from the given pin. + // Stop requesting periodic callbacks from the scheduler. + this->status &= ~DEVICE_COMPONENT_STATUS_SYSTEM_TICK; + + if (deleteOnRelease) + delete this; + + return DEVICE_OK; +} + /** * Destructor for Button, where we deregister this instance from the array of fiber components. */ diff --git a/source/drivers/HIDJoystick.cpp b/source/drivers/HIDJoystick.cpp index 3954c2e5..8a7e76c6 100644 --- a/source/drivers/HIDJoystick.cpp +++ b/source/drivers/HIDJoystick.cpp @@ -88,8 +88,8 @@ static const InterfaceInfo ifaceInfo = { { 1, // numEndpoints 0x03, /// class code - HID - 0x01, // subclass (boot interface) - 0x02, // protocol (joystick) + 0x00, // subclass - none + 0x00, // protocol - none (there isn't a protocol defined for gamepad/joystick) 0x00, // 0x00, // }, diff --git a/source/drivers/MessageBus.cpp b/source/drivers/MessageBus.cpp index ef86550d..465941ad 100644 --- a/source/drivers/MessageBus.cpp +++ b/source/drivers/MessageBus.cpp @@ -49,6 +49,7 @@ DEALINGS IN THE SOFTWARE. #include "CodalConfig.h" #include "MessageBus.h" #include "CodalFiber.h" +#include "CodalDmesg.h" #include "ErrorNo.h" #include "NotifyEvents.h" #include "codal_target_hal.h" @@ -172,7 +173,11 @@ void MessageBus::queueEvent(Event &evt) // If we need to queue, but there is no space, then there's nothg we can do. if (queueLength >= MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH) + { + // Note that this can lead to strange lockups, where we await an event that never arrives. + DMESG("evt %d/%d: overflow!", evt.source, evt.value); return; + } // Otherwise, we need to queue this event for later processing... // We queue this event at the tail of the queue at the point where we entered queueEvent() diff --git a/source/drivers/PulseIn.cpp b/source/drivers/PulseIn.cpp index 3d2b06fe..8eae3ed2 100644 --- a/source/drivers/PulseIn.cpp +++ b/source/drivers/PulseIn.cpp @@ -118,9 +118,26 @@ PulseIn::onTimeout(Event e) } /** - * Destructor + * Method to release the given pin from a peripheral, if already bound. + * Device drivers should override this method to disconnect themselves from the give pin + * to allow it to be used by a different peripheral. + * + * @param pin the Pin to be released. + * @return DEVICE_OK on success, or DEVICE_NOT_IMPLEMENTED if unsupported, or DEVICE_INVALID_PARAMETER if the pin is not bound to this peripheral. */ -PulseIn::~PulseIn() +int PulseIn::releasePin(Pin &pin) +{ + // We've been asked to disconnect from the given pin. + // As we do nothing else, simply disable ourselves. + disable(); + + if (deleteOnRelease) + delete this; + + return DEVICE_OK; +} + +void PulseIn::disable() { if (enabled) { @@ -132,4 +149,12 @@ PulseIn::~PulseIn() lastPeriod = 0; lock.notifyAll(); } +} + +/** + * Destructor + */ +PulseIn::~PulseIn() +{ + disable(); } \ No newline at end of file diff --git a/source/drivers/TouchButton.cpp b/source/drivers/TouchButton.cpp index fe9e0108..728a735f 100644 --- a/source/drivers/TouchButton.cpp +++ b/source/drivers/TouchButton.cpp @@ -103,6 +103,34 @@ int TouchButton::getThreshold() return this->threshold; } +/** + * Determines the instantneous digital value of the pin associated with this TouchButton + * + * @return true if a digital read of the attached pin is a logic 1, false otherwise. + */ +int TouchButton::getPinValue() +{ + int result; + + setPinLock(true); + result = _pin.getDigitalValue(); + setPinLock(false); + + return result; +} + +/** + * Drives a given digital value to the pin associated with this TouchButton + * + * @param v the digital value to write to the pin associated with this TouchButton. + */ +void TouchButton::setPinValue(int v) +{ + setPinLock(true); + _pin.setDigitalValue(v); + setPinLock(false); +} + /** * Determine the last reading taken from this button. * diff --git a/source/drivers/TouchSensor.cpp b/source/drivers/TouchSensor.cpp index c011c133..48f33733 100644 --- a/source/drivers/TouchSensor.cpp +++ b/source/drivers/TouchSensor.cpp @@ -92,7 +92,7 @@ int TouchSensor::addTouchButton(TouchButton *button) numberOfButtons++; // Put the button into input mode. - button->_pin.getDigitalValue(); + button->getPinValue(); return DEVICE_OK; } @@ -149,7 +149,7 @@ void TouchSensor::onSampleEvent(Event) { if (buttons[i]->active) { - if(buttons[i]->_pin.getDigitalValue() == 1 || cycles >= (buttons[i]->threshold)) + if(buttons[i]->getPinValue() == 1 || cycles >= (buttons[i]->threshold)) { buttons[i]->active = false; buttons[i]->setValue(cycles); diff --git a/source/streams/DataStream.cpp b/source/streams/DataStream.cpp index 4647307c..b3cd123e 100644 --- a/source/streams/DataStream.cpp +++ b/source/streams/DataStream.cpp @@ -26,6 +26,7 @@ DEALINGS IN THE SOFTWARE. #include "CodalComponent.h" #include "CodalFiber.h" #include "ErrorNo.h" +#include "CodalDmesg.h" using namespace codal; @@ -55,6 +56,15 @@ int DataSource::setFormat(int format) return DEVICE_NOT_SUPPORTED; } +float DataSource::getSampleRate() { + return DATASTREAM_SAMPLE_RATE_UNKNOWN; +} + +float DataSource::requestSampleRate(float sampleRate) { + // Just consume this by default, we don't _have_ to honour requests for specific rates. + return DATASTREAM_SAMPLE_RATE_UNKNOWN; +} + int DataSink::pullRequest() { return DEVICE_NOT_SUPPORTED; @@ -68,17 +78,17 @@ int DataSink::pullRequest() */ DataStream::DataStream(DataSource &upstream) { - this->bufferCount = 0; - this->bufferLength = 0; - this->preferredBufferSize = 0; - this->pullRequestEventCode = 0; - this->spaceAvailableEventCode = allocateNotifyEvent(); + //this->bufferCount = 0; + //this->bufferLength = 0; + //this->preferredBufferSize = 0; + //this->pullRequestEventCode = 0; + //this->spaceAvailableEventCode = allocateNotifyEvent(); + this->nextBuffer = NULL; this->isBlocking = true; - this->writers = 0; + //this->writers = 0; this->downStream = NULL; this->upStream = &upstream; - } /** @@ -95,7 +105,7 @@ DataStream::~DataStream() * @param position The index of the byte to read. * @return The value of the byte at the given position, or DEVICE_INVALID_PARAMETER. */ -int DataStream::get(int position) +/*int DataStream::get(int position) { for (int i = 0; i < bufferCount; i++) { @@ -106,7 +116,7 @@ int DataStream::get(int position) } return DEVICE_INVALID_PARAMETER; -} +}*/ /** * Sets the byte at the given index to value provided. @@ -115,7 +125,7 @@ int DataStream::get(int position) * @return DEVICE_OK, or DEVICE_INVALID_PARAMETER. * */ -int DataStream::set(int position, uint8_t value) +/*int DataStream::set(int position, uint8_t value) { for (int i = 0; i < bufferCount; i++) { @@ -129,16 +139,16 @@ int DataStream::set(int position, uint8_t value) } return DEVICE_INVALID_PARAMETER; -} +}*/ /** * Gets number of bytes that are ready to be consumed in this data stream. * @return The size in bytes. */ -int DataStream::length() +/*int DataStream::length() { return this->bufferLength; -} +}*/ /** * Determines if any of the data currently flowing through this stream is held in non-volatile (FLASH) memory. @@ -146,13 +156,17 @@ int DataStream::length() */ bool DataStream::isReadOnly() { - bool r = true; + /*bool r = true; for (int i=0; inextBuffer != NULL ) + return nextBuffer->isReadOnly(); + return true; } /** @@ -188,19 +202,19 @@ void DataStream::disconnect() * Determine the number of bytes that are currnetly buffered before blocking subsequent push() operations. * @return the current preferred buffer size for this DataStream */ -int DataStream::getPreferredBufferSize() +/*int DataStream::getPreferredBufferSize() { return preferredBufferSize; -} +}*/ /** * Define the number of bytes that should be buffered before blocking subsequent push() operations. * @param size The number of bytes to buffer. */ -void DataStream::setPreferredBufferSize(int size) +/*void DataStream::setPreferredBufferSize(int size) { this->preferredBufferSize = size; -} +}*/ /** * Determines if this stream acts in a synchronous, blocking mode or asynchronous mode. In blocking mode, writes to a full buffer @@ -213,13 +227,13 @@ void DataStream::setBlocking(bool isBlocking) this->isBlocking = isBlocking; // If this is the first time async mode has been used on this stream, allocate the necessary resources. - if (!isBlocking && this->pullRequestEventCode == 0) + /*if (!isBlocking && this->pullRequestEventCode == 0) { this->pullRequestEventCode = allocateNotifyEvent(); if(EventModel::defaultEventBus) EventModel::defaultEventBus->listen(DEVICE_ID_NOTIFY, pullRequestEventCode, this, &DataStream::onDeferredPullRequest); - } + }*/ } /** @@ -227,7 +241,7 @@ void DataStream::setBlocking(bool isBlocking) */ ManagedBuffer DataStream::pull() { - ManagedBuffer out = stream[0]; + /*ManagedBuffer out = stream[0]; // // A simplistic FIFO for now. Copy cost is actually pretty low because ManagedBuffer is a managed type, @@ -246,17 +260,24 @@ ManagedBuffer DataStream::pull() Event(DEVICE_ID_NOTIFY_ONE, spaceAvailableEventCode); - return out; + return out;*/ + + /*if( this->nextBuffer != NULL ) + return *this->nextBuffer; + + return ManagedBuffer();*/ + + return this->upStream->pull(); } /** * Issue a pull request to our downstream component, if one has been registered. */ -void DataStream::onDeferredPullRequest(Event) +/*void DataStream::onDeferredPullRequest(Event) { if (downStream != NULL) downStream->pullRequest(); -} +}*/ /** * Determines if a buffer of the given size can be added to the buffer. @@ -266,13 +287,16 @@ void DataStream::onDeferredPullRequest(Event) */ bool DataStream::canPull(int size) { - if(bufferCount + writers >= DATASTREAM_MAXIMUM_BUFFERS) + DMESG( "DataStream::canPull()" ); + /*if(bufferCount + writers >= DATASTREAM_MAXIMUM_BUFFERS) return false; if(preferredBufferSize > 0 && (bufferLength + size > preferredBufferSize)) return false; - return true; + return true;*/ + + return this->nextBuffer != NULL; } /** @@ -280,16 +304,17 @@ bool DataStream::canPull(int size) * * @return true if there if the buffer is ful, and can accept no more data at this time. False otherwise. */ -bool DataStream::full() +/*bool DataStream::full() { return !canPull(); -} +}*/ /** * Store the given buffer in our stream, possibly also causing a push operation on our downstream component. */ int DataStream::pullRequest() { + /* // If we're defined as non-blocking and no space is available, then there's nothing we can do. if (full() && this->isBlocking == false) return DEVICE_NO_RESOURCES; @@ -318,12 +343,49 @@ int DataStream::pullRequest() if (downStream != NULL) { - if (this->isBlocking) + DMESG( "DS(blocking = %d) -> PR?", this->isBlocking ); + if (this->isBlocking) { + DMESG( "DS -> Direct PR" ); downStream->pullRequest(); - else + } + else { + DMESG( "DS -> Defer PR" ); Event(DEVICE_ID_NOTIFY, pullRequestEventCode); + } } - return DEVICE_OK; + return DEVICE_OK;*/ + + if( this->downStream != NULL ) { + this->downStream->pullRequest(); + } + + return DEVICE_OK; +} + +/** + * Gets the sample rate for the stream that this component is part of. + * + * This will query 'up stream' towards the stream source to determine the current sample rate. + * + * @return float The sample rate, in samples-per-second, or DATASTREAM_SAMPLE_RATE_UNKNOWN if not known or unconfigured + */ +float DataStream::getSampleRate() { + if( this->upStream != NULL ) + return this->upStream->getSampleRate(); + return DATASTREAM_SAMPLE_RATE_UNKNOWN; } + +/** + * Sents a request 'up stream' towards the stream source for a higher sample rate, if possible. + * + * @note Components are not required to respect this request, and this mechanism is best-effort, so if the caller needs a specific rate, it should check via getSampleRate after making this request. + * + * @param sampleRate The sample rate requestd, in samples-per-second + */ +float DataStream::requestSampleRate(float sampleRate) { + if( this->upStream != NULL ) + return this->upStream->requestSampleRate( sampleRate ); + return DATASTREAM_SAMPLE_RATE_UNKNOWN; +} \ No newline at end of file diff --git a/source/streams/EffectFilter.cpp b/source/streams/EffectFilter.cpp new file mode 100644 index 00000000..6b9cbfe8 --- /dev/null +++ b/source/streams/EffectFilter.cpp @@ -0,0 +1,88 @@ +#include "EffectFilter.h" +#include "ManagedBuffer.h" +#include "DataStream.h" +#include "StreamNormalizer.h" +#include "CodalDmesg.h" + +using namespace codal; + +EffectFilter::EffectFilter(DataSource &source, bool deepCopy) : upStream( source ) +{ + this->downStream = NULL; + this->deepCopy = deepCopy; + source.connect( *this ); +} + +EffectFilter::~EffectFilter() +{ +} + +ManagedBuffer EffectFilter::pull() +{ + ManagedBuffer input = this->upStream.pull(); + ManagedBuffer output = deepCopy ? ManagedBuffer(input.length()) : input; + + applyEffect(input, output, this->upStream.getFormat()); + return output; +} + +int EffectFilter::pullRequest() +{ + if( this->downStream != NULL ) + this->downStream->pullRequest(); + + return 0; +} + +void EffectFilter::connect(DataSink &sink) +{ + this->downStream = &sink; +} + +void EffectFilter::disconnect() +{ + this->downStream = NULL; +} + +int EffectFilter::getFormat() +{ + return this->upStream.getFormat(); +} + +int EffectFilter::setFormat( int format ) +{ + return this->upStream.setFormat( format ); +} + +float EffectFilter::getSampleRate() +{ + return this->upStream.getSampleRate(); +} + +float EffectFilter::requestSampleRate(float sampleRate) +{ + return this->upStream.requestSampleRate( sampleRate ); +} + +/** + * Defines if this filter should perform a deep copy of incoming data, or update data in place. + * + * @param deepCopy Set to true to copy incoming data into a freshly allocated buffer, or false to change data in place. + */ +void EffectFilter::setDeepCopy( bool deepCopy ) +{ + this->deepCopy = deepCopy; +} + +/** + * Default effect - a simple pass through filter. + * + * @param inputBuffer the buffer containing data to process. + * @param outputBuffer the buffer in which to store the filtered data. n.b. MAY be the same memory as the input buffer. + * @param format the format of the data (word size and signed/unsigned representation) + */ +void EffectFilter::applyEffect(ManagedBuffer inputBuffer, ManagedBuffer outputBuffer, int format) +{ + if (inputBuffer.getBytes() != outputBuffer.getBytes()) + memcpy(outputBuffer.getBytes(), inputBuffer.getBytes(), inputBuffer.length()); +} \ No newline at end of file diff --git a/source/streams/FIFOStream.cpp b/source/streams/FIFOStream.cpp new file mode 100644 index 00000000..0499aecd --- /dev/null +++ b/source/streams/FIFOStream.cpp @@ -0,0 +1,132 @@ +#include "FIFOStream.h" +#include "ErrorNo.h" +#include "DataStream.h" +#include "ManagedBuffer.h" +#include "CodalDmesg.h" +#include "MessageBus.h" + +using namespace codal; + +FIFOStream::FIFOStream( DataSource &source ) : upStream( source ) +{ + this->bufferCount = 0; + this->bufferLength = 0; + + this->downStream = NULL; + source.connect( *this ); + + this->allowInput = false; + this->allowOutput = false; + +} + +FIFOStream::~FIFOStream() +{ + // +} + +bool FIFOStream::canPull() +{ + return (this->bufferLength > 0) && this->allowOutput; +} + +ManagedBuffer FIFOStream::pull() +{ + if( (this->bufferLength > 0) && this->allowOutput ) + { + ManagedBuffer out = buffer[0]; + + for (int i = 0; i < FIFO_MAXIMUM_BUFFERS-1; i++) + buffer[i] = buffer[i + 1]; + + buffer[FIFO_MAXIMUM_BUFFERS-1] = ManagedBuffer(); + + this->bufferLength -= out.length(); + this->bufferCount--; + + if (this->bufferCount > 0 && downStream != NULL) + downStream->pullRequest(); + + return out; + } + + return ManagedBuffer(); +} + +int FIFOStream::length() +{ + return this->bufferLength; +} + +bool FIFOStream::isFull() { + return this->bufferCount < FIFO_MAXIMUM_BUFFERS; +} + +void FIFOStream::dumpState() +{ + DMESG( + "TapeDeck { bufferCount = %d/%d, bufferLength = %dB }", + this->bufferCount, + FIFO_MAXIMUM_BUFFERS, + this->bufferLength + ); +} + +int FIFOStream::pullRequest() +{ + ManagedBuffer inBuffer = this->upStream.pull(); + + if( this->bufferCount >= FIFO_MAXIMUM_BUFFERS ) + return DEVICE_NO_RESOURCES; + + if( this->allowInput && inBuffer.length() > 0 ) + { + this->buffer[ this->bufferCount++ ] = inBuffer; + this->bufferLength += inBuffer.length(); + + // If we've just received a buffer after being idle, issue a downstream pullrequest to notify that data is ready. + if (bufferCount == 1 && this->allowOutput && downStream != NULL) + downStream->pullRequest(); + } + + return DEVICE_OK; +} + +void FIFOStream::connect( DataSink &sink ) +{ + this->downStream = &sink; +} + +void FIFOStream::disconnect() +{ + this->downStream = NULL; +} + +int FIFOStream::getFormat() +{ + return this->upStream.getFormat(); +} + +int FIFOStream::setFormat( int format ) +{ + return this->upStream.setFormat( format ); +} + +void FIFOStream::setInputEnable( bool state ) +{ + this->allowInput = state; +} +void FIFOStream::setOutputEnable( bool state ) +{ + bool enabling = false; + DMESG("FIFO:setOutputEnable %d", state ); + + if (this->allowOutput == false && state) + enabling = true; + + this->allowOutput = state; + + // If we've just been enabled and have data to send, issue a pullrequest to ensure our downstream is aware of this + if (enabling && bufferCount > 0 && downStream != NULL) + downStream->pullRequest(); +} \ No newline at end of file diff --git a/source/streams/LevelDetector.cpp b/source/streams/LevelDetector.cpp index b928d134..b5b0e127 100644 --- a/source/streams/LevelDetector.cpp +++ b/source/streams/LevelDetector.cpp @@ -28,10 +28,11 @@ DEALINGS IN THE SOFTWARE. #include "Timer.h" #include "LevelDetector.h" #include "ErrorNo.h" +#include "CodalDmesg.h" using namespace codal; -LevelDetector::LevelDetector(DataSource &source, int highThreshold, int lowThreshold, uint16_t id) : upstream(source) +LevelDetector::LevelDetector(DataSource &source, int highThreshold, int lowThreshold, uint16_t id, bool connectImmediately) : upstream(source) { this->id = id; this->level = 0; @@ -41,9 +42,10 @@ LevelDetector::LevelDetector(DataSource &source, int highThreshold, int lowThres this->lowThreshold = lowThreshold; this->highThreshold = highThreshold; this->status |= LEVEL_DETECTOR_INITIALISED; - - // Register with our upstream component - source.connect(*this); + this->activated = false; + if(connectImmediately){ + upstream.connect(*this); + } } /** @@ -51,22 +53,37 @@ LevelDetector::LevelDetector(DataSource &source, int highThreshold, int lowThres */ int LevelDetector::pullRequest() { + if( ttl < 1 && !activated ) + return DEVICE_OK; + ManagedBuffer b = upstream.pull(); + int16_t *data = (int16_t *) &b[0]; int samples = b.length() / 2; for (int i=0; i < samples; i++) { - sigma += abs(*data); + if(upstream.getFormat() == DATASTREAM_FORMAT_8BIT_SIGNED){ + sigma += abs((int8_t) *data); + } + else + sigma += abs(*data); + windowPosition++; if (windowPosition == windowSize) { level = sigma / windowSize; + sigma = 0; windowPosition = 0; + // If 8 bit - then multiply by 8 to upscale result. High 8 bit ~20, High 16 bit ~150 so roughly 8 times higher + if(upstream.getFormat() == DATASTREAM_FORMAT_8BIT_SIGNED){ + level = level*8; + } + if ((!(status & LEVEL_DETECTOR_HIGH_THRESHOLD_PASSED)) && level > highThreshold) { Event(id, LEVEL_THRESHOLD_HIGH); @@ -85,6 +102,10 @@ int LevelDetector::pullRequest() data++; } + // Disconnect when our TTL is less than 1, if we're not set to always active + if( !activated && --ttl < 1 ) + upstream.disconnect(); + return DEVICE_OK; } @@ -95,9 +116,18 @@ int LevelDetector::pullRequest() */ int LevelDetector::getValue() { + this->ttl = 100; // In buffers + upstream.connect(*this); + target_wait( 100 ); return level; } +void LevelDetector::activateForEvents( bool state ) +{ + this->activated = state; + if( this->activated ) + upstream.connect(*this); +} /** * Set threshold to the given value. Events will be generated when these thresholds are crossed. diff --git a/source/streams/LevelDetectorSPL.cpp b/source/streams/LevelDetectorSPL.cpp index 3816603a..b93f0fd5 100644 --- a/source/streams/LevelDetectorSPL.cpp +++ b/source/streams/LevelDetectorSPL.cpp @@ -29,21 +29,38 @@ DEALINGS IN THE SOFTWARE. #include "LevelDetector.h" #include "LevelDetectorSPL.h" #include "ErrorNo.h" +#include "StreamNormalizer.h" +#include "CodalDmesg.h" + +#define CODAL_STREAM_IDLE_TIMEOUT_MS 250 +#define CODAL_STREAM_MIC_STABLE_MS 5 using namespace codal; -LevelDetectorSPL::LevelDetectorSPL(DataSource &source, float highThreshold, float lowThreshold, float gain, float minValue, uint16_t id) : upstream(source) +LevelDetectorSPL::LevelDetectorSPL(DataSource &source, float highThreshold, float lowThreshold, float gain, float minValue, uint16_t id, bool connectImmediately) : upstream(source) { this->id = id; this->level = 0; this->windowSize = LEVEL_DETECTOR_SPL_DEFAULT_WINDOW_SIZE; this->lowThreshold = lowThreshold; this->highThreshold = highThreshold; + this->minValue = minValue; this->gain = gain; this->status |= LEVEL_DETECTOR_SPL_INITIALISED; - - // Register with our upstream component - source.connect(*this); + this->unit = LEVEL_DETECTOR_SPL_DB; + enabled = true; + if(connectImmediately){ + upstream.connect(*this); + this->activated = true; + } + else{ + this->activated = false; + } + + this->quietBlockCount = 0; + this->noisyBlockCount = 0; + this->inNoisyBlock = false; + this->maxRms = 0; } /** @@ -51,55 +68,92 @@ LevelDetectorSPL::LevelDetectorSPL(DataSource &source, float highThreshold, floa */ int LevelDetectorSPL::pullRequest() { + if( this->timeout - system_timer_current_time() > CODAL_STREAM_IDLE_TIMEOUT_MS && !activated ) { + upstream.disconnect(); + return DEVICE_OK; + } + ManagedBuffer b = upstream.pull(); - int16_t *data = (int16_t *) &b[0]; - int samples = b.length() / 2; + uint8_t *data = &b[0]; + + int format = upstream.getFormat(); + int skip = 1; + float multiplier = 256; + windowSize = 256; + + if (format == DATASTREAM_FORMAT_16BIT_SIGNED || format == DATASTREAM_FORMAT_UNKNOWN){ + skip = 2; + multiplier = 1; + windowSize = 128; + } + else if (format == DATASTREAM_FORMAT_32BIT_SIGNED){ + skip = 4; + windowSize = 64; + multiplier = (1/65536); + } + + int samples = b.length() / skip; while(samples){ - - //ensure we use at least windowSize number of samples - int16_t *end, *ptr; + // ensure we use at least windowSize number of samples (128) if(samples < windowSize) break; + uint8_t *ptr, *end; + + ptr = data; end = data + windowSize; float pref = 0.00002; /******************************* - * REMOVE DC OFFSET + * GET MAX VALUE ******************************/ - int32_t avg = 0; - ptr = data; - while(ptr < end) avg += *ptr++; - avg = avg/windowSize; - + int16_t maxVal = 0; + int16_t minVal = 32766; + int32_t v; ptr = data; - while(ptr < end) *ptr++ -= avg; + while (ptr < end) { + v = (int32_t) StreamNormalizer::readSample[format](ptr); + if (v > maxVal) maxVal = v; + if (v < minVal) minVal = v; + ptr += skip; + } + maxVal = (maxVal - minVal) / 2; /******************************* - * GET MAX VALUE + * GET RMS AMPLITUDE FOR CLAP DETECTION ******************************/ - - int16_t maxVal = 0; + int sumSquares = 0; + int count = 0; ptr = data; - while(ptr < end){ - int32_t v = abs(*ptr++); - if(v > maxVal) maxVal = v; + while (ptr < end) { + count++; + v = (int32_t) StreamNormalizer::readSample[format](ptr) - minVal; // need to sub minVal to avoid overflow + sumSquares += v * v; + ptr += skip; } - - float conv = ((float)maxVal)/((1 << 15)-1) * gain; + float rms = sqrt(sumSquares / count); /******************************* * CALCULATE SPL ******************************/ - conv = 20 * log10(conv/pref); + float conv = ((float) maxVal * multiplier) / ((1 << 15) - 1) * gain; + conv = 20 * log10(conv / pref); - if(isfinite(conv)) level = conv; + if (conv < minValue) level = minValue; + else if (isfinite(conv)) level = conv; else level = minValue; samples -= windowSize; + data += windowSize; + + /******************************* + * EMIT EVENTS + ******************************/ + + // HIGH THRESHOLD if ((!(status & LEVEL_DETECTOR_SPL_HIGH_THRESHOLD_PASSED)) && level > highThreshold) { Event(id, LEVEL_THRESHOLD_HIGH); @@ -107,15 +161,50 @@ int LevelDetectorSPL::pullRequest() status &= ~LEVEL_DETECTOR_SPL_LOW_THRESHOLD_PASSED; } + // LOW THRESHOLD if ((!(status & LEVEL_DETECTOR_SPL_LOW_THRESHOLD_PASSED)) && level < lowThreshold) { Event(id, LEVEL_THRESHOLD_LOW); status |= LEVEL_DETECTOR_SPL_LOW_THRESHOLD_PASSED; status &= ~LEVEL_DETECTOR_SPL_HIGH_THRESHOLD_PASSED; } - } - return DEVICE_OK; + // CLAP DETECTION HANDLING + if (this->inNoisyBlock && rms > this->maxRms) this->maxRms = rms; + + if ( + ( // if start of clap + !this->inNoisyBlock && + rms > LEVEL_DETECTOR_SPL_BEGIN_POSS_CLAP_RMS && + this->quietBlockCount >= LEVEL_DETECTOR_SPL_CLAP_MIN_QUIET_BLOCKS + ) || + ( // or if continuing a clap + this->inNoisyBlock && + rms > LEVEL_DETECTOR_SPL_CLAP_OVER_RMS + )) { + // noisy block + if (!this->inNoisyBlock) this->maxRms = rms; + this->quietBlockCount = 0; + this->noisyBlockCount += 1; + this->inNoisyBlock = true; + + } else { + // quiet block + if ( // if not too long, not too short, and loud enough + this->noisyBlockCount <= LEVEL_DETECTOR_SPL_CLAP_MAX_LOUD_BLOCKS && + this->noisyBlockCount >= LEVEL_DETECTOR_SPL_CLAP_MIN_LOUD_BLOCKS && + this->maxRms >= LEVEL_DETECTOR_SPL_MIN_IN_CLAP_RMS + ) { + Event(id, LEVEL_DETECTOR_SPL_CLAP); + } + this->inNoisyBlock = false; + this->noisyBlockCount = 0; + this->quietBlockCount += 1; + this->maxRms = 0; + } + } + + return DEVICE_OK; } /* @@ -125,7 +214,27 @@ int LevelDetectorSPL::pullRequest() */ float LevelDetectorSPL::getValue() { - return level; + bool wasAwake = this->activated || system_timer_current_time() - this->timeout ; + this->timeout = system_timer_current_time() + CODAL_STREAM_IDLE_TIMEOUT_MS; + upstream.connect(*this); + if( !wasAwake ) + target_wait( CODAL_STREAM_MIC_STABLE_MS ); + return splToUnit(level); +} + +void LevelDetectorSPL::activateForEvents( bool state ) +{ + this->activated = state; + if( this->activated ) + upstream.connect(*this); +} + +/* + * Disable / turn off this level detector + * + */ +void LevelDetectorSPL::disable(){ + enabled = false; } @@ -138,6 +247,9 @@ float LevelDetectorSPL::getValue() */ int LevelDetectorSPL::setLowThreshold(float value) { + // Convert specified unit into db if necessary + value = unitToSpl(value); + // Protect against churn if the same threshold is set repeatedly. if (lowThreshold == value) return DEVICE_OK; @@ -164,6 +276,9 @@ int LevelDetectorSPL::setLowThreshold(float value) */ int LevelDetectorSPL::setHighThreshold(float value) { + // Convert specified unit into db if necessary + value = unitToSpl(value); + // Protect against churn if the same threshold is set repeatedly. if (highThreshold == value) return DEVICE_OK; @@ -188,7 +303,7 @@ int LevelDetectorSPL::setHighThreshold(float value) */ float LevelDetectorSPL::getLowThreshold() { - return lowThreshold; + return splToUnit(lowThreshold); } /** @@ -198,7 +313,7 @@ float LevelDetectorSPL::getLowThreshold() */ float LevelDetectorSPL::getHighThreshold() { - return highThreshold; + return splToUnit(highThreshold); } /** @@ -225,6 +340,50 @@ int LevelDetectorSPL::setGain(float gain) return DEVICE_OK; } +/** + * Defines the units that will be returned by the getValue() function. + * + * @param unit Either LEVEL_DETECTOR_SPL_DB or LEVEL_DETECTOR_SPL_8BIT. + * @return DEVICE_OK or DEVICE_INVALID_PARAMETER. + */ +int LevelDetectorSPL::setUnit(int unit) +{ + if (unit == LEVEL_DETECTOR_SPL_DB || unit == LEVEL_DETECTOR_SPL_8BIT) + { + this->unit = unit; + return DEVICE_OK; + } + + return DEVICE_INVALID_PARAMETER; +} + + +float LevelDetectorSPL::splToUnit(float level) +{ + if (unit == LEVEL_DETECTOR_SPL_8BIT) + { + level = (level - LEVEL_DETECTOR_SPL_8BIT_000_POINT) * LEVEL_DETECTOR_SPL_8BIT_CONVERSION; + + // Ensure the result is clamped into the expected range. + if (level < 0.0f) + level = 0.0f; + + if (level > 255.0f) + level = 255.0f; + } + + return level; +} + + +float LevelDetectorSPL::unitToSpl(float level) +{ + if (unit == LEVEL_DETECTOR_SPL_8BIT) + level = LEVEL_DETECTOR_SPL_8BIT_000_POINT + level / LEVEL_DETECTOR_SPL_8BIT_CONVERSION; + + return level; +} + /** * Destructor. */ diff --git a/source/streams/LowPassFilter.cpp b/source/streams/LowPassFilter.cpp new file mode 100644 index 00000000..d9ae410e --- /dev/null +++ b/source/streams/LowPassFilter.cpp @@ -0,0 +1,53 @@ +#include "LowPassFilter.h" +#include "CodalDmesg.h" + +using namespace codal; + +LowPassFilter::LowPassFilter( DataSource &source, float beta, bool deepCopy) : EffectFilter( source, deepCopy ) +{ + this->lpf_value = 1.0; + this->lpf_beta = 0.0; + setBeta(beta); +} + +LowPassFilter::~LowPassFilter() +{ +} + +/** + * Apply a simple low pass filter on the give buffer of data. + * Y(n) = (1-ß)*Y(n-1) + (ß*X(n))) = Y(n-1) - (ß*(Y(n-1)-X(n))); + * + * @param inputBuffer the buffer containing data to process. + * @param outputBuffer the buffer in which to store the filtered data. n.b. MAY be the same memory as the input buffer. + * @param format the format of the data (word size and signed/unsigned representation) + */ +void LowPassFilter::applyEffect(ManagedBuffer inputBuffer, ManagedBuffer outputBuffer, int format) +{ + int bytesPerSample = DATASTREAM_FORMAT_BYTES_PER_SAMPLE(format); + int sampleCount = inputBuffer.length() / bytesPerSample; + uint8_t *in = inputBuffer.getBytes(); + uint8_t *out = outputBuffer.getBytes(); + + for( int i=0; ilpf_beta = beta; +} \ No newline at end of file diff --git a/source/streams/StreamFlowTrigger.cpp b/source/streams/StreamFlowTrigger.cpp new file mode 100644 index 00000000..d8a0de0b --- /dev/null +++ b/source/streams/StreamFlowTrigger.cpp @@ -0,0 +1,58 @@ +#include "StreamFlowTrigger.h" +#include "ManagedBuffer.h" +#include "DataStream.h" +#include "CodalDmesg.h" + +using namespace codal; + +StreamFlowTrigger::StreamFlowTrigger( DataSource &source ) : upStream( source ) +{ + this->eventHandler = NULL; + this->downStream = NULL; + source.connect( *this ); +} + +StreamFlowTrigger::~StreamFlowTrigger() +{ + // NOP +} + +void StreamFlowTrigger::setDataHandler( void (*handler)(int) ) +{ + this->eventHandler = handler; +} + +ManagedBuffer StreamFlowTrigger::pull() +{ + (*this->eventHandler)( TRIGGER_PULL ); + return this->upStream.pull(); +} + +int StreamFlowTrigger::pullRequest() +{ + (*this->eventHandler)( TRIGGER_REQUEST ); + if( this->downStream != NULL ) + this->downStream->pullRequest(); + + return 0; +} + +void StreamFlowTrigger::connect( DataSink &sink ) +{ + this->downStream = &sink; +} + +void StreamFlowTrigger::disconnect() +{ + this->downStream = NULL; +} + +int StreamFlowTrigger::getFormat() +{ + return this->upStream.getFormat(); +} + +int StreamFlowTrigger::setFormat( int format ) +{ + return this->upStream.setFormat( format ); +} \ No newline at end of file diff --git a/source/streams/StreamNormalizer.cpp b/source/streams/StreamNormalizer.cpp index eb9eda66..05e881aa 100644 --- a/source/streams/StreamNormalizer.cpp +++ b/source/streams/StreamNormalizer.cpp @@ -24,7 +24,6 @@ DEALINGS IN THE SOFTWARE. #include "StreamNormalizer.h" #include "ErrorNo.h" -#include "CodalDmesg.h" using namespace codal; @@ -144,14 +143,6 @@ StreamNormalizer::StreamNormalizer(DataSource &source, float gain, bool normaliz * Provide the next available ManagedBuffer to our downstream caller, if available. */ ManagedBuffer StreamNormalizer::pull() -{ - return buffer; -} - -/** - * Callback provided when data is ready. - */ -int StreamNormalizer::pullRequest() { int samples; // Number of samples in the input buffer. int s; // The sample being processed, encpasulated inside a 32 bit number. @@ -161,7 +152,8 @@ int StreamNormalizer::pullRequest() int bytesPerSampleIn; // number of bit per sample of the input buffer. int bytesPerSampleOut; // number of bit per sample of the input buffer. int z = 0; // normalized zero point calculated from this buffer. - int zo = (int) zeroOffset; // Snapshot of our previously calculate zero point + int zo = (int) zeroOffset; // Snapshot of our previously calculate zero point. + ManagedBuffer buffer; // The buffer being processed. // Determine the input format. inputFormat = upstream.getFormat(); @@ -226,11 +218,15 @@ int StreamNormalizer::pullRequest() // Ensure output buffer is the correct size; buffer.truncate(samples * bytesPerSampleOut); - // Signal downstream component that a buffer is ready. - if (outputEnabled) - output.pullRequest(); + return buffer; +} - return DEVICE_OK; +/** + * Callback provided when data is ready. + */ +int StreamNormalizer::pullRequest() +{ + return output.pullRequest(); } /** @@ -319,3 +315,11 @@ int StreamNormalizer::setOrMask(uint32_t mask) StreamNormalizer::~StreamNormalizer() { } + +float StreamNormalizer::getSampleRate() { + return this->upstream.getSampleRate(); +} + +float StreamNormalizer::requestSampleRate(float sampleRate) { + return this->upstream.requestSampleRate( sampleRate ); +} \ No newline at end of file diff --git a/source/streams/StreamRecording.cpp b/source/streams/StreamRecording.cpp new file mode 100644 index 00000000..d85d92da --- /dev/null +++ b/source/streams/StreamRecording.cpp @@ -0,0 +1,198 @@ +#include "StreamRecording.h" +#include "ErrorNo.h" +#include "DataStream.h" +#include "ManagedBuffer.h" +#include "CodalDmesg.h" +#include "MessageBus.h" + +using namespace codal; + +StreamRecording::StreamRecording( DataSource &source ) : upStream( source ) +{ + this->state = REC_STATE_STOPPED; + this->bufferLength = 0; + this->lastBuffer = 0; + this->readWriteHead = 0; + + this->downStream = NULL; + //upStream.connect( *this ); +} + +StreamRecording::~StreamRecording() +{ + // +} + +bool StreamRecording::canPull() +{ + return this->lastBuffer > this->readWriteHead; +} + +ManagedBuffer StreamRecording::pull() +{ + // Are we playing back? + if( this->state != REC_STATE_PLAYING ) + return ManagedBuffer(); + + // Do we have data to send? + if( this->readWriteHead >= this->lastBuffer ) { + stop(); + return ManagedBuffer(); + } + + //DMESGF( "Output: %d of %d", this->readWriteHead, REC_MAX_BUFFERS ); + + // Grab the next block + ManagedBuffer out = this->buffer[this->readWriteHead++]; + this->bufferLength -= out.length(); + + // Ping the downstream that we're good to go + if( downStream != NULL ) + downStream->pullRequest(); + + // Return the block + return out; +} + +int StreamRecording::length() +{ + return this->bufferLength; +} + +float StreamRecording::duration( unsigned int sampleRate ) +{ + return ((float)this->length() / DATASTREAM_FORMAT_BYTES_PER_SAMPLE((float)this->getFormat()) ) / (float)sampleRate; +} + +bool StreamRecording::isFull() { + return this->lastBuffer < REC_MAX_BUFFERS; +} + +void StreamRecording::dumpState() +{ + DMESG( + "TapeDeck { bufferCount = %d/%d, RWHead = %d }", + this->lastBuffer, + REC_MAX_BUFFERS, + this->readWriteHead + ); +} + +int StreamRecording::pullRequest() +{ + // Are we recording? + if( this->state != REC_STATE_RECORDING ) + return DEVICE_OK; + + ManagedBuffer data = this->upStream.pull(); + + DMESGN( "Input: %d of %d (%d B)\r", this->readWriteHead, REC_MAX_BUFFERS, this->bufferLength ); + + // Are we getting empty buffers (probably because we're out of RAM!) + if( data.length() == 0 ) + return DEVICE_NO_DATA; + + // Can we record any more? + if( this->readWriteHead < REC_MAX_BUFFERS ) + { + // Ok, so pull and retain, updating counts + this->buffer[this->readWriteHead++] = data; + this->lastBuffer = this->readWriteHead - 1; + this->bufferLength += data.length(); + return DEVICE_OK; + } + + this->stop(); + return DEVICE_NO_RESOURCES; +} + +void StreamRecording::connect( DataSink &sink ) +{ + this->downStream = &sink; +} + +void StreamRecording::disconnect() +{ + this->downStream = NULL; +} + +int StreamRecording::getFormat() +{ + return this->upStream.getFormat(); +} + +int StreamRecording::setFormat( int format ) +{ + return this->upStream.setFormat( format ); +} + +bool StreamRecording::record() +{ + // Duplicate check from within erase(), but here for safety in case of later code edits... + if( this->state != REC_STATE_STOPPED ) + this->stop(); + + erase(); + + bool changed = this->state != REC_STATE_RECORDING; + if( changed ) + upStream.connect( *this ); + + this->state = REC_STATE_RECORDING; + + return changed; +} + +void StreamRecording::erase() +{ + if( this->state != REC_STATE_STOPPED ) + this->stop(); + + for( int i=0; ibuffer[i] = ManagedBuffer(); + this->lastBuffer = 0; + this->readWriteHead = 0; +} + +bool StreamRecording::play() +{ + if( this->state != REC_STATE_STOPPED ) + this->stop(); + bool changed = this->state != REC_STATE_PLAYING; + + this->state = REC_STATE_PLAYING; + if( this->downStream != NULL ) + this->downStream->pullRequest(); + + return changed; +} + +void StreamRecording::stop() +{ + bool changed = this->state != REC_STATE_STOPPED; + if( changed ) + upStream.disconnect(); + + this->state = REC_STATE_STOPPED; + this->readWriteHead = 0; // Snap to the start +} + +bool StreamRecording::isPlaying() +{ + return this->state == REC_STATE_PLAYING; +} + +bool StreamRecording::isRecording() +{ + return this->state == REC_STATE_RECORDING; +} + +bool StreamRecording::isStopped() +{ + return this->state == REC_STATE_STOPPED; +} + +float StreamRecording::getSampleRate() +{ + return this->upStream.getSampleRate(); +} \ No newline at end of file diff --git a/source/streams/StreamSplitter.cpp b/source/streams/StreamSplitter.cpp new file mode 100644 index 00000000..9a72852d --- /dev/null +++ b/source/streams/StreamSplitter.cpp @@ -0,0 +1,272 @@ +/* +The MIT License (MIT) + +Copyright (c) 2021 Lancaster University. + +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 "CodalConfig.h" +#include "StreamSplitter.h" +#include "StreamNormalizer.h" +#include "ErrorNo.h" +#include "Event.h" +#include "CodalDmesg.h" + +using namespace codal; + +SplitterChannel::SplitterChannel( StreamSplitter * parent, DataSink * output = NULL ) +{ + this->sampleRate = DATASTREAM_SAMPLE_RATE_UNKNOWN; + this->parent = parent; + this->output = output; +} + +SplitterChannel::~SplitterChannel() +{ + // +} + +int SplitterChannel::pullRequest() { + pullAttempts++; + if( output != NULL ) + return output->pullRequest(); + return -1; +} + +ManagedBuffer SplitterChannel::pull() +{ + pullAttempts = 0; + ManagedBuffer inData = parent->getBuffer(); + + // Shortcuts - we can't fabricate samples, so just pass on what we can if we don't know or can't keep up. + if( sampleRate == DATASTREAM_SAMPLE_RATE_UNKNOWN || sampleRate >= parent->upstream.getSampleRate() ) + return inData; + + // Going the long way around - drop any extra samples... + float inRate = parent->upstream.getSampleRate(); + int inFmt = parent->upstream.getFormat(); + int bytesPerSample = DATASTREAM_FORMAT_BYTES_PER_SAMPLE( inFmt ); + int inSamples = inData.length() / bytesPerSample; + int step = (inRate / sampleRate); + + ManagedBuffer outData = ManagedBuffer( (inSamples/step) * bytesPerSample ); + + uint8_t *inPtr = &inData[0]; + uint8_t *outPtr = &outData[0]; + while( outPtr - &outData[0] < outData.length() ) + { + int s = StreamNormalizer::readSample[inFmt](inPtr); + inPtr += bytesPerSample * step; + + StreamNormalizer::writeSample[inFmt](outPtr, s); + outPtr += bytesPerSample; + } + + return outData; +} + +void SplitterChannel::connect(DataSink &sink) +{ + // Prevent repeated events on calling connect multiple times (so consumers can blindly connect!) + if( output != &sink ) { + output = &sink; + parent->activeChannels++; // Notify that we have might _at least_ one sink available + Event e( parent->id, SPLITTER_CHANNEL_CONNECT ); + } +} + +void SplitterChannel::disconnect() +{ + // Prevent repeated events on calling disconnect multiple times (so consumers can blindly disconnect!) + if( output != NULL ) { + output = NULL; + Event e( parent->id, SPLITTER_CHANNEL_DISCONNECT ); + } +} + +int SplitterChannel::getFormat() +{ + return parent->upstream.getFormat(); +} + +int SplitterChannel::setFormat(int format) +{ + return parent->upstream.setFormat( format ); +} + +float SplitterChannel::getSampleRate() +{ + if( sampleRate != DATASTREAM_SAMPLE_RATE_UNKNOWN ) + return sampleRate; + return parent->upstream.getSampleRate(); +} + +float SplitterChannel::requestSampleRate( float sampleRate ) +{ + this->sampleRate = sampleRate; + + // Do we need to request a higher rate upstream? + if( parent->upstream.getSampleRate() < sampleRate ) { + + // Request it, and if we got less that we expected, report that rate + if( parent->upstream.requestSampleRate( sampleRate ) < sampleRate ) + return parent->upstream.getSampleRate(); + } + + // Otherwise, report our own rate (we're matching or altering it ourselves) + return sampleRate; +} + + + + + +/** + * Creates a component that distributes a single upstream datasource to many downstream datasinks + * + * @param source a DataSource to receive data from + */ +StreamSplitter::StreamSplitter(DataSource &source, uint16_t id) : upstream(source) +{ + this->id = id; + this->channels = 0; + // init array to NULL. + for (int i = 0; i < CONFIG_MAX_CHANNELS; i++) + outputChannels[i] = NULL; + + source.connect(*this); + + this->__cycle = 0; + //this->status |= DEVICE_COMPONENT_STATUS_SYSTEM_TICK; +} + +StreamSplitter::~StreamSplitter() +{ + // Nop. +} + +ManagedBuffer StreamSplitter::getBuffer() +{ + activeChannels++; + return lastBuffer; +} + +void StreamSplitter::periodicCallback() { + if( this->__cycle++ % 50 != 0 ) + return; + + if( this->id == 64000 ) { + char const CLEAR_SRC[] = {0x1B, 0x5B, 0x32, 0x4A }; + DMESG( CLEAR_SRC ); + } + + DMESG( "%d - Active Channels: %d (active = %d, sampleRate = %d)", this->id, this->activeChannels, this->isActive, (int)this->upstream.getSampleRate() ); + for( int i=0; ioutputChannels[i] != NULL ) { + if( this->outputChannels[i]->output != NULL ) + DMESG( "\t- %d [CONN] failed = %d (sampleRate = %d)", i, this->outputChannels[i]->pullAttempts, (int)this->outputChannels[i]->getSampleRate() ); + else + DMESG( "\t- %d [DISC] failed = %d (sampleRate = %d)", i, this->outputChannels[i]->pullAttempts, (int)this->outputChannels[i]->getSampleRate() ); + } else + DMESG( "\t- %d [----]", i ); + } +} + +/** + * Callback provided when data is ready. + */ +int StreamSplitter::pullRequest() +{ + if( activeChannels > 0 ) + { + if( !isActive ) + Event e( id, SPLITTER_ACTIVATE ); + isActive = true; + lastBuffer = upstream.pull(); + } + else + { + if( isActive ) + Event e( id, SPLITTER_DEACTIVATE ); + isActive = false; + lastBuffer = ManagedBuffer(); + } + + activeChannels = 0; + + // For each downstream channel that exists in array outputChannels - make a pullRequest + for (int i = 0; i < CONFIG_MAX_CHANNELS; i++) + { + if (outputChannels[i] != NULL) + outputChannels[i]->pullRequest(); + } + + return DEVICE_OK; +} + + +SplitterChannel * StreamSplitter::createChannel() +{ + int placed = -1; + for (int i = 0; i < CONFIG_MAX_CHANNELS; i++) + { + // Add downstream as one of the splitters datasinks that will be served + if (outputChannels[i] == NULL){ + outputChannels[i] = new SplitterChannel( this, NULL ); + placed = i; + break; + } + } + if(placed != -1) { + channels++; + Event e( id, SPLITTER_ACTIVATE_CHANNEL ); + return outputChannels[placed]; + } + + return NULL; +} + +bool StreamSplitter::destroyChannel( SplitterChannel * channel ) { + for( int i=0; ioutput == output ) { + return outputChannels[i]; + } + } + } + + return NULL; +} \ No newline at end of file diff --git a/source/streams/Synthesizer.cpp b/source/streams/Synthesizer.cpp index 7aab1a2f..6497287e 100644 --- a/source/streams/Synthesizer.cpp +++ b/source/streams/Synthesizer.cpp @@ -316,7 +316,7 @@ ManagedBuffer Synthesizer::pull() * Determine the sample rate currently in use by this Synthesizer. * @return the current sample rate, in Hz. */ -int Synthesizer::getSampleRate() +float Synthesizer::getSampleRate() { return 1000000000 / samplePeriodNs; } diff --git a/source/types/ManagedBuffer.cpp b/source/types/ManagedBuffer.cpp index 28a9f92a..c4920750 100644 --- a/source/types/ManagedBuffer.cpp +++ b/source/types/ManagedBuffer.cpp @@ -304,7 +304,7 @@ void ManagedBuffer::shift(int offset, int start, int len) if (start < 0 || start + len > (int)ptr->length || start + len < start || len == 0 || offset == 0 || offset == INT_MIN) return; if (offset <= -len || offset >= len) { - fill(0); + fill(0, start, len); return; }