5
diff --git a/ComputeGraph/examples/example1/test.pdf b/ComputeGraph/examples/example1/test.pdf
index 82166212..e8c85243 100644
Binary files a/ComputeGraph/examples/example1/test.pdf and b/ComputeGraph/examples/example1/test.pdf differ
diff --git a/ComputeGraph/examples/example10/README.md b/ComputeGraph/examples/example10/README.md
index 63addf96..3b234eaa 100644
--- a/ComputeGraph/examples/example10/README.md
+++ b/ComputeGraph/examples/example10/README.md
@@ -1,5 +1,7 @@
# Example 10
+Please refer to the [simple example](../simple/README.md) to have an overview of how to define a graph and it nodes and how to generate the C++ code for the static scheduler. This document is only explaining additional details
+
This example is implementing a dynamic / asynchronous mode.
It is enabled in `graph.py` with:
@@ -22,6 +24,38 @@ The even source is generating a value only when the count is even.
The processing is adding its inputs. If no data is available on an input, 0 is used.
-In case of fifo overflow or underflow, any node will slip its execution.
+In case of fifo overflow or underflow, any node will skip its execution.
+
+All nodes are generating or consuming one sample but the FIFOs have a size of 2 because of the 100% increase requested in the configuration settings.
+
+## Expected outputs
+
+```
+Schedule length = 9
+Memory usage 34 bytes
+```
+
+```
+Start
+0
+0
+1
+1
+2
+2
+3
+3
+4
+4
+5
+5
+6
+6
+7
+7
+8
+8
+9
+9
+```
-All nodes are generating or consuming one sample but the FIFOs have a size of 2 because of the 100% increase requested in the configuration settings.
\ No newline at end of file
diff --git a/ComputeGraph/examples/example10/graph.py b/ComputeGraph/examples/example10/graph.py
index 550ce59d..95dee4d0 100644
--- a/ComputeGraph/examples/example10/graph.py
+++ b/ComputeGraph/examples/example10/graph.py
@@ -2,7 +2,7 @@ from cmsisdsp.cg.scheduler import *
### Define new types of Nodes
-
+
class SinkAsync(GenericSink):
def __init__(self,name,theType,inLength):
diff --git a/ComputeGraph/examples/example2/README.md b/ComputeGraph/examples/example2/README.md
index a178c26a..265bceda 100644
--- a/ComputeGraph/examples/example2/README.md
+++ b/ComputeGraph/examples/example2/README.md
@@ -1,12 +1,15 @@
# Example 2
-Please refer to [Example 1](example1.md) for the details about how to create a graph and the C++ support classes.
+Please refer to the [simple example](../simple/README.md) to have an overview of how to define a graph and it nodes and how to generate the C++ code for the static scheduler.
In this example. we are just analyzing a much more complex example to see some new features:
- Delay
-- CMSIS-DSP functions
-- Some default nodes : sliding buffer
+- CMSIS-DSP function
+- Constant node
+- SlidingBuffer
+
+This example is not really using a MFCC or a TensorFlow Lite node. It is just providing some wrappers to show how such a nodes could be included in a graph:
The graph is:
@@ -14,7 +17,7 @@ The graph is:
It is much more complex:
-- First we have a source delayed by 10 samples ;
+- First we have a stereo source delayed by 10 samples ;
- Then this stereo source is split into left/right samples using the default block Unzip
- The samples are divided by 2 using a CMSIS-DSP function
- The node HALF representing a constant is introduced (constant arrays are also supported)
@@ -24,18 +27,11 @@ It is much more complex:
- Another sliding buffer
- An a block representing TensorFlow Lite for Micro (a fake TFLite node)
-Note that those blocks (MFCC, TFLite) are doing nothing in this example. It is just to illustrate a more complex example that someone may want to experiment with for keyword spotting.
+Note that those blocks (MFCC, TFLite) are doing nothing in this example. It is just to illustrate a more complex example typical of keyword spotting applications.
Examples 5 and 6 are showing how to use the CMSIS-DSP MFCC.
-The new features compared to `example1` are:
-
-- Delay
-- CMSIS-DSP function
-- Constant node
-- SlidingBuffer
-
-Let's look at all of this:
+Let's look at the new features compared to example 1:
## Delay
@@ -43,9 +39,7 @@ Let's look at all of this:
g.connectWithDelay(src.o, toMono.i,10)
```
-
-
-To add a delay on a link between 2 nodes, you just use the `connectWithDelay` function. Delays can be useful for some graphs which are not schedulable. They are implemented by starting the schedule with a FIFO which is not empty but contain 0 samples.
+To add a delay on a link between 2 nodes, you just use the `connectWithDelay` function. Delays can be useful for some graphs which are not schedulable. They are implemented by starting the schedule with a FIFO which is not empty but contain some 0 samples.
## CMSIS-DSP function
@@ -59,16 +53,18 @@ sa=Dsp("scale",floatType,blockSize)
The corresponding CMSIS-DSP function will be named: `arm_scale_f32`
-The code generated in `sched.cpp` will not require any C++ class, It will look like:
+The code generated in `scheduler.cpp` will not require any C++ class, It will look like:
```C++
{
- float32_t* i0;
- float32_t* o2;
- i0=fifo2.getReadBuffer(160);
- o2=fifo4.getWriteBuffer(160);
- arm_scale_f32(i0,HALF,o2,160);
- cgStaticError = 0;
+ float32_t* i0;
+ float32_t* i1;
+ float32_t* o2;
+ i0=fifo3.getReadBuffer(160);
+ i1=fifo4.getReadBuffer(160);
+ o2=fifo5.getWriteBuffer(160);
+ arm_add_f32(i0,i1,o2,160);
+ cgStaticError = 0;
}
```
@@ -84,23 +80,21 @@ A constant node is defined as:
half=Constant("HALF")
```
+In the C++ code, `HALF` is expected to be a value defined in `custom.h`
-
-In the C++ code, HALF is expected to be a value defined in custom.h
-
-In the Python generated code, it would be in custom.py
-
-Constant values are not involved in the scheduling (they are ignored) and they have no io. So, to connect to a constant node we do:
+Constant values are not involved in the scheduling (they are ignored) and they have no IO. So, to connect to a constant node we do:
```python
g.connect(half,sa.ib)
```
-There is no "o", "oa" suffixes for the constant node half.
+There is no "o", "oa" suffixes for the constant node `half`.
+
+Constant nodes are just here to make it easier to use CMSIS-DSP functions.
## SlidingBuffer
-Sliding buffers and OverlapAndAdd are used a lot so they are provided by default.
+Sliding buffers and OverlapAndAdd are used a lot so they are provided in the `cg/nodes/cpp`folder of the `ComputeGraph` folder.
In Python, it can be used with:
@@ -114,3 +108,18 @@ There is no C++ class to write for this since it is provided by default by the f
It is named `SlidingBuffer` but not `SlidingWindow` because no multiplication with a window is done. It must be implemented with another block as will be demonstrated in the [example 3](example3.md)
+## Expected outputs
+
+```
+Schedule length = 302
+Memory usage 10720 bytes
+```
+
+And when executed:
+
+```
+Start
+Nb = 40
+```
+
+Execution is running for 40 iterations without errors.
diff --git a/ComputeGraph/examples/example3/README.md b/ComputeGraph/examples/example3/README.md
index fe3b7b79..0a78b2ac 100644
--- a/ComputeGraph/examples/example3/README.md
+++ b/ComputeGraph/examples/example3/README.md
@@ -1,5 +1,7 @@
# Example 3
+Please refer to the [simple example](../simple/README.md) to have an overview of how to define a graph and it nodes and how to generate the C++ code for the static scheduler. This document is only explaining additional details
+
This example is implementing a working example with FFT. The graph is:

@@ -29,9 +31,7 @@ Now, the constant is an array:
hann=Constant("HANN")
```
-
-
-In custom.h, this array is defined as:
+In `custom.h`, this array is defined as:
```C++
extern const float32_t HANN[256];
@@ -41,47 +41,53 @@ extern const float32_t HANN[256];
## CMSIS-DSP FFT
-The FFT node cannot be created using a `Dsp` node in Python because FFT is requiring specific initializations. So, a Python class and C++ class must be created :
-
-
+The FFT node cannot be created using a `Dsp` node in Python because FFT is requiring specific initializations. So, a Python class and C++ class must be created. They are provided by default in the ffamework butg let's look at how they are implemented:
```python
class CFFT(GenericNode):
- def __init__(self,name,inLength):
+ def __init__(self,name,theType,inLength):
GenericNode.__init__(self,name)
- self.addInput("i",floatType,2*inLength)
- self.addOutput("o",floatType,2*inLength)
+ self.addInput("i",theType,2*inLength)
+ self.addOutput("o",theType,2*inLength)
@property
def typeName(self):
return "CFFT"
```
-Look at the definition of the inputs and outputs : The FFT is using complex number so the ports have twice the number of float samples. The argument of the constructor is the FFT length in complex sample.
+Look at the definition of the inputs and outputs : The FFT is using complex number so the ports have twice the number of float samples. The argument of the constructor is the FFT length in **complex** sample but `addInput` and `addOutput` require the number of samples of the base type : here float.
We suggest to use as arguments of the blocks a number of samples which is meaningful for the blocks and use the lengths in standard data type (f32, q31 ...) when defining the IO.
-So here, the number of complex samples is used as arguments. But the IO are using the number of floats required to encode those complex numbers.
+So here, the number of complex samples is used as arguments. But the IO are using the number of floats required to encode those complex numbers hence a factor of 2.
-The corresponding C++ class is:
+The C++ template is:
```C++
template
-class CFFT: public GenericNode
+class CFFT;
+```
+
+There are only specific implementations for specific datatype. No generic implementation is provided.
+
+For, float we have:
+
+```C++
+template
+class CFFT: public GenericNode
{
public:
- CFFT(FIFOBase &src,FIFOBase &dst):
- GenericNode(src,dst){
+ CFFT(FIFOBase &src,FIFOBase &dst):GenericNode(src,dst)
+ {
arm_status status;
status=arm_cfft_init_f32(&sfft,inputSize>>1);
};
-
+
int prepareForRunning() override
{
if (this->willOverflow() ||
- this->willUnderflow()
- )
+ this->willUnderflow())
{
return(CG_SKIP_EXECUTION_ID_CODE); // Skip execution
}
@@ -89,10 +95,11 @@ public:
return(0);
};
- int run() override {
- IN *a=this->getReadBuffer();
- OUT *b=this->getWriteBuffer();
- memcpy((void*)b,(void*)a,outputSize*sizeof(IN));
+ int run() override
+ {
+ float32_t *a=this->getReadBuffer();
+ float32_t *b=this->getWriteBuffer();
+ memcpy((void*)b,(void*)a,inputSize*sizeof(float32_t));
arm_cfft_f32(&sfft,b,0,1);
return(0);
};
@@ -104,11 +111,9 @@ public:
It is verbose but not difficult. The constructor is initializing the CMSIS-DSP FFT instance and connecting to the FIFO (through GenericNode).
-
-
The run function is applying the `arm_cfft_f32`. Since this function is modifying the input buffer, there is a `memcpy`. It is not really needed here. The read buffer can be modified by the CFFT. It will just make it more difficult to debug if you'd like to inspect the content of the FIFOs.
-
+THe function `prepareForRunning` is only used in asynchronous mode. Please refer to the documentation for the asynchronous mode.
This node is provided in `cg/nodes/cpp` so no need to define it. You can just use it by including the right headers.
@@ -125,3 +130,43 @@ from cmsisdsp.cg.scheduler import *
```
The scheduler module is automatically including the default nodes.
+
+## Expected output
+
+Output of Python script:
+
+```
+Schedule length = 25
+Memory usage 11264 bytes
+```
+
+Output of execution:
+
+```
+Start
+Nb = 40
+```
+
+It is running for 40 iterations of the scheduler without errors.
+
+The python script `debug.py` can be used to display the content of `input_example3.txt` and `../build/output_example3.txt`
+
+It should display the same sinusoid but it is delayed in `output_example3.txt` by a few samples because of the sliding buffer. The sliding buffer will generate 256 samples in output each time 128 samples are received in input. As consequence, at start, 256 samples with the half set to zero are generated.
+
+We can check it in the debug script by comparing a delayed version of the original to the output.
+
+You should get something like:
+
+
+
+We have 40 execution of the schedule iteration. In each schedule iteration we have two sinks. A sink is producing 192 samples.
+
+So, the execution is producing `40 * 2 * 192 == 15360` so a bit less than the `16000` samples in input.
+
+If we compare the input and output taking into account this length difference and the delay of 128 samples, we get (by running `debug.py`):
+
+```
+Comparison of input and output : max absolute error
+6.59404862823898e-07
+```
+
diff --git a/ComputeGraph/examples/example3/debug.py b/ComputeGraph/examples/example3/debug.py
new file mode 100644
index 00000000..5cd13e05
--- /dev/null
+++ b/ComputeGraph/examples/example3/debug.py
@@ -0,0 +1,20 @@
+import numpy as np
+from pylab import figure, clf, plot, xlabel, ylabel, xlim, ylim, title, grid, axes, show,semilogx, semilogy
+from numpy import genfromtxt
+ref_data = genfromtxt('input_example3.txt', delimiter=',')
+
+figure()
+plot(ref_data)
+
+output_data = genfromtxt('../build/output_example3.txt', delimiter=',')
+
+plot(output_data)
+show()
+
+print(ref_data.shape)
+print(output_data.shape)
+nb = output_data.shape[0] - 128
+
+print("Comparison of input and output : max absolute error")
+diff = output_data[128:] - ref_data[:nb]
+print(np.max(np.abs(diff)))
diff --git a/ComputeGraph/examples/example3/docassets/sine.png b/ComputeGraph/examples/example3/docassets/sine.png
new file mode 100644
index 00000000..5f2b5d72
Binary files /dev/null and b/ComputeGraph/examples/example3/docassets/sine.png differ
diff --git a/ComputeGraph/examples/example4/README.md b/ComputeGraph/examples/example4/README.md
index 91bde327..0921aee8 100644
--- a/ComputeGraph/examples/example4/README.md
+++ b/ComputeGraph/examples/example4/README.md
@@ -2,6 +2,8 @@
It is exactly the same example as example 3 but the code generation is generating Python code instead of C++.
+
+
The Python code is generated with:
```python
@@ -12,6 +14,12 @@ and it will generate a `sched.py` file.
A file `custom.py` and `appnodes.py` are also required.
+The example can be run with:
+
+`python main.py`
+
+Do not confuse `graph.py,` which is used to describe the graph, with the other Python files that are used to execute the graph.
+
## custom.py
```python
@@ -25,7 +33,7 @@ An array HANN is defined for the Hann window.
## appnodes.py
-This file is defining the new nodes which were used in `graph.py`. In `graph.py` which are just defining new kind of nodes for scheduling purpose : type and sizes.
+This file is defining the new nodes which were used in `graph.py`.
In `appnodes.py` we including new kind of nodes for simulation purpose:
@@ -33,8 +41,6 @@ In `appnodes.py` we including new kind of nodes for simulation purpose:
from cmsisdsp.cg.scheduler import *
```
-
-
The CFFT is very similar to the C++ version of example 3. But there is no `prepareForRunning`. Dynamic / asynchronous mode is not implemented for Python.
```python
@@ -110,3 +116,22 @@ DISPBUF = np.zeros(16000)
nb,error = s.scheduler(DISPBUF)
```
+The example can be run with:
+
+`python main.py`
+
+
+
+## Expected outputs
+
+```
+Generate graphviz and code
+Schedule length = 25
+Memory usage 11264 bytes
+```
+
+And when executed:
+
+
+
+As you can see at the beginning, there is a small delay during which the output signal is zero.
diff --git a/ComputeGraph/examples/example4/debug.py b/ComputeGraph/examples/example4/debug.py
deleted file mode 100644
index becd1195..00000000
--- a/ComputeGraph/examples/example4/debug.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import numpy as np
-from cmsisdsp.cg.static.nodes.simu import *
-
-a=np.zeros(10)
-f=FIFO(10,a)
-
-f.dump()
-
-nb = 1
-for i in range(4):
- w=f.getWriteBuffer(2)
- w[0:2]=nb*np.ones(2)
- nb = nb + 1
- f.dump()
-
-print(a)
-
-for i in range(4):
- w=f.getReadBuffer(2)
- print(w)
\ No newline at end of file
diff --git a/ComputeGraph/examples/example4/docassets/graph4.png b/ComputeGraph/examples/example4/docassets/graph4.png
new file mode 100644
index 00000000..815ad3f1
Binary files /dev/null and b/ComputeGraph/examples/example4/docassets/graph4.png differ
diff --git a/ComputeGraph/examples/example4/docassets/sine.png b/ComputeGraph/examples/example4/docassets/sine.png
new file mode 100644
index 00000000..6b2bc8bc
Binary files /dev/null and b/ComputeGraph/examples/example4/docassets/sine.png differ
diff --git a/ComputeGraph/examples/example5/README.md b/ComputeGraph/examples/example5/README.md
new file mode 100644
index 00000000..979bd9c4
--- /dev/null
+++ b/ComputeGraph/examples/example5/README.md
@@ -0,0 +1,25 @@
+# Example 5
+
+This is a pure python example. It is computing a sequence of MFCC with an overlap of 0.5 s and it is creating an animation.
+
+It can be run with:
+
+`python main.py`
+
+The `NumPy` sink at the end is just recording all the MFCC outputs as a list of buffers. This list is used to create an animation.
+
+
+
+## Expected output
+
+```
+Generate graphviz and code
+Schedule length = 292
+Memory usage 6614 bytes
+```
+
+And when executed you should get an animation looking like this:
+
+
+
+The Python `main.py` contains a line which can be uncommented to record the animation as a `.mp4` video.
\ No newline at end of file
diff --git a/ComputeGraph/examples/example5/docassets/graph5.png b/ComputeGraph/examples/example5/docassets/graph5.png
new file mode 100644
index 00000000..fd0eb769
Binary files /dev/null and b/ComputeGraph/examples/example5/docassets/graph5.png differ
diff --git a/ComputeGraph/examples/example5/docassets/mfcc.png b/ComputeGraph/examples/example5/docassets/mfcc.png
new file mode 100644
index 00000000..f1890194
Binary files /dev/null and b/ComputeGraph/examples/example5/docassets/mfcc.png differ
diff --git a/ComputeGraph/examples/example6/README.md b/ComputeGraph/examples/example6/README.md
new file mode 100644
index 00000000..74b4fb82
--- /dev/null
+++ b/ComputeGraph/examples/example6/README.md
@@ -0,0 +1,15 @@
+# Example 6
+
+This example is similar to example 5 but with C code generation instead of Python.
+
+
+
+## Expected output
+
+```
+nbMFCCOutputs = 126
+Generate graphviz and code
+Schedule length = 17
+Memory usage 2204 bytes
+```
+
diff --git a/ComputeGraph/examples/example6/docassets/graph6.png b/ComputeGraph/examples/example6/docassets/graph6.png
new file mode 100644
index 00000000..caf7ea9c
Binary files /dev/null and b/ComputeGraph/examples/example6/docassets/graph6.png differ
diff --git a/ComputeGraph/examples/example7/PythonTest.mo b/ComputeGraph/examples/example7/PythonTest.mo
index 778cdf66..0611783b 100644
--- a/ComputeGraph/examples/example7/PythonTest.mo
+++ b/ComputeGraph/examples/example7/PythonTest.mo
@@ -13,7 +13,7 @@ model PythonTest
Placement(visible = true, transformation(origin = {-82, 8}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
inner Modelica.Blocks.Noise.GlobalSeed globalSeed annotation(
Placement(visible = true, transformation(origin = {-86, -28}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
- ARM.Sound.WaveOutput waveOutput annotation(
+ ARM.Sound.WaveOutput waveOutput(path = "C:\\benchresults\\cmsis\\CMSIS-DSP\\ComputeGraph\\examples\\example7\\output.wav") annotation(
Placement(visible = true, transformation(origin = {24, -32}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
equation
connect(vht.y, transferFunction.u) annotation(
diff --git a/ComputeGraph/examples/example7/README.md b/ComputeGraph/examples/example7/README.md
new file mode 100644
index 00000000..574046c2
--- /dev/null
+++ b/ComputeGraph/examples/example7/README.md
@@ -0,0 +1,62 @@
+# Example 7
+
+This is an example showing how a graph in in Python (not C) can interact with an [OpenModelica](https://openmodelica.org/) model.
+
+
+
+First you need to get the project [AVH-SystemModeling](https://github.com/ARM-software/AVH-SystemModeling) from our ARM-Software repository.
+
+Then, you need launch `OpenModelica` and choose `Open Model`.
+
+Select `AVH-SystemModeling/VHTModelicaBlock/ARM/package.mo`
+
+Then choose `Open Model` again and select `PythonTest.mo`.
+
+You should see something like that in `Open Modelica`:
+
+
+
+Customize the output path in the `Wave` node.
+
+Refer to the `Open Modelica` documentation to know who to build and run this simulation. Once it is started in Modelica, launch the Python script in `example7`:
+
+`python main.py`
+
+You should see :
+
+```
+Connecting as INPUT
+Connecting as OUTPUT
+```
+
+In Modelica window, the simulation should continue to `100%`.
+
+In the simulation window, you should be able to plot the output wav and get something like:
+
+
+
+A `.wav` should have been generated so that you can listen to the result : A Larsen effect !
+
+The `Processing` node in the compute graph is implemented in `custom.py` and is a gain computed with `CMSIS-DSP` Python wrapper
+
+```python
+class Processing(GenericNode):
+ def __init__(self,inputSize,outputSize,fifoin,fifoout):
+ GenericNode.__init__(self,inputSize,outputSize,fifoin,fifoout)
+
+ def run(self):
+
+ i=self.getReadBuffer()
+ o=self.getWriteBuffer()
+
+ b=dsp.arm_scale_q15(i,0x6000,1)
+
+ o[:]=b[:]
+
+ return(0)
+```
+
+
+
+The gain has been chosen to create an instability.
+
diff --git a/ComputeGraph/examples/example7/docassets/graph7.png b/ComputeGraph/examples/example7/docassets/graph7.png
new file mode 100644
index 00000000..83ee74c5
Binary files /dev/null and b/ComputeGraph/examples/example7/docassets/graph7.png differ
diff --git a/ComputeGraph/examples/example7/docassets/modelica.png b/ComputeGraph/examples/example7/docassets/modelica.png
new file mode 100644
index 00000000..616707c4
Binary files /dev/null and b/ComputeGraph/examples/example7/docassets/modelica.png differ
diff --git a/ComputeGraph/examples/example7/docassets/waveoutput.png b/ComputeGraph/examples/example7/docassets/waveoutput.png
new file mode 100644
index 00000000..5b59e5dd
Binary files /dev/null and b/ComputeGraph/examples/example7/docassets/waveoutput.png differ
diff --git a/ComputeGraph/examples/example7/graph.py b/ComputeGraph/examples/example7/graph.py
index 93f45b9b..f546a684 100644
--- a/ComputeGraph/examples/example7/graph.py
+++ b/ComputeGraph/examples/example7/graph.py
@@ -33,18 +33,11 @@ print("Generate graphviz and code")
conf=Configuration()
-#conf.dumpSchedule = True
sched = g.computeSchedule(conf)
-#print(sched.schedule)
print("Schedule length = %d" % sched.scheduleLength)
print("Memory usage %d bytes" % sched.memory)
-#
-# Pass the source and sink objects used to communicate with the VHT Modelica block
-#conf.pyOptionalArgs=""
-conf.pathToSDFModule="C:\\\\benchresults\\\\cmsis_docker\\\\CMSIS\\\\DSP\\\\SDFTools"
-#conf.dumpFIFO=True
-#conf.prefix="sched1"
+
sched.pythoncode(".",config=conf)
with open("test.dot","w") as f:
diff --git a/ComputeGraph/examples/example7/output.wav b/ComputeGraph/examples/example7/output.wav
new file mode 100644
index 00000000..1485d01b
Binary files /dev/null and b/ComputeGraph/examples/example7/output.wav differ
diff --git a/ComputeGraph/examples/example8/README.md b/ComputeGraph/examples/example8/README.md
new file mode 100644
index 00000000..4b995a64
--- /dev/null
+++ b/ComputeGraph/examples/example8/README.md
@@ -0,0 +1,54 @@
+# Example 8
+
+This example is illustrating :
+
+* The `Duplicate` node to have a one-to-many connection at an output
+* A structured datatype for the samples in the connections
+
+
+
+## Structured datatype
+
+It is possible to use a custom datatype:
+
+```python
+complexType=CStructType("complex","MyComplex",8)
+```
+
+This is defining a new datatype that is mapped to the type `complex` in C/C++ and the class `MyComplex` in Python. The last argument is the size in bytes of the struct in C.
+
+The type complex may be defined with:
+
+```c
+typedef struct {
+ float re;
+ float im;
+} complex;
+```
+
+**Note that:**
+
+- The value **must have** value semantic in C/C++. So avoid classes
+- In Python, the classes have reference semantic which implies some constraints:
+ - You should never modify an object from the read buffer
+ - You should change the field of an object in the write buffer but not the object itself
+ - If you need a new object : copy or create a new object. Never use an object from the read buffer as it is if you intend to customize it
+
+The size of the C structure should take into account the padding that may be added to the struct.
+
+When no buffer sharing is used, the size of buffers is always expressed in number of samples.
+
+But in case of buffer sharing, the datatype of the buffer is `int8_t` and the size of the buffer must be computed by the Compute Graph taking into account ay padding that may exist.
+
+## Duplicate node
+
+In case of a one-to-many connections, the Python code will automatically add `Duplicate` nodes in the graph. Those `Duplicate` nodes do not appear directly in the graphviz but only as a stylized way : a dot.
+
+Currently it is limited to 3. If you need more that 3 outputs on an IO you'll have to insert the `Duplicate` nodes explicitly in the graph.
+
+In the generated code, you'll see the `Duplicate` nodes. For instance, in this example:
+
+```C++
+Duplicate3 dup0(fifo2,fifo3,fifo4,fifo5);
+```
+
diff --git a/ComputeGraph/examples/example8/docassets/graph8.png b/ComputeGraph/examples/example8/docassets/graph8.png
new file mode 100644
index 00000000..db3dd4cd
Binary files /dev/null and b/ComputeGraph/examples/example8/docassets/graph8.png differ
diff --git a/ComputeGraph/examples/example9/README.md b/ComputeGraph/examples/example9/README.md
new file mode 100644
index 00000000..0e9ae908
--- /dev/null
+++ b/ComputeGraph/examples/example9/README.md
@@ -0,0 +1,7 @@
+# Example 9
+
+Thsi example is just checking that duplicate node insertion and delay on a connection are working well together.
+
+The Python script is able to schedule the graph.
+
+
\ No newline at end of file
diff --git a/ComputeGraph/examples/example9/docassets/graph9.png b/ComputeGraph/examples/example9/docassets/graph9.png
new file mode 100644
index 00000000..56f92fb0
Binary files /dev/null and b/ComputeGraph/examples/example9/docassets/graph9.png differ
diff --git a/ComputeGraph/examples/simple/README.md b/ComputeGraph/examples/simple/README.md
index fdb0d85a..d9489949 100644
--- a/ComputeGraph/examples/simple/README.md
+++ b/ComputeGraph/examples/simple/README.md
@@ -383,13 +383,20 @@ The first line is the important one:
OUT *b=this->getWriteBuffer();
```
-We get a pointer to be able to write in the output FIFO. This pointer has the datatype OUT coming from the template so can be anything.
+We get a pointer to be able to write in the output FIFO. This pointer has the datatype OUT coming from the template so can be anything. **Those functions (`getWriteBuffer` and/or `getReadBuffer`) must always be used even if the node is doing nothing because FIFOs are only updated when those functions are used.**
The code in the loop is casting an `int` (the loop index) into the `OUT` datatype. If it is not possible it won't typecheck and build.
+```C++
+for(int i=0;i |