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;
}