WEBVTT

00:00.000 --> 00:13.040
Okay, hi, I'm Banya.

00:13.040 --> 00:18.640
I'm the author of Blue O'Readyo and now Zigg Radio and this talk is just introducing

00:18.640 --> 00:25.920
this flow graph, STR framework and a little bit about the Zigg language because some

00:25.920 --> 00:30.080
of you may have heard of it but not seen too much of it.

00:30.080 --> 00:35.440
So just to give you a mental model of what this is, don't worry if you can't read the code

00:35.440 --> 00:43.120
just yet, basically a flow graph, softer to find radio framework is a framework in which

00:43.120 --> 00:48.480
you can express receivers or transmitters that look roughly like this where you have samples

00:48.480 --> 00:55.440
flowing in this case like left to right and down to the right and I guess I kind of consider

00:55.520 --> 01:04.000
this the hello world of STR which is just a FM broadcast receiver in this case starting

01:04.000 --> 01:10.160
with an STR source running through the demodulation and getting it ready to go out the audio

01:10.160 --> 01:17.920
and through a computer. So like new radio, Zigg Radio or Lua Radio with any of these frameworks

01:17.920 --> 01:27.040
you can express flow graphs like this as like a piece of code. So specifically Zigg Radio

01:27.760 --> 01:34.160
really has a dual purpose. One of them is to just make stand alone radio receivers so if you

01:34.160 --> 01:41.280
do want to or a transmitter. If you just want something that's running on its own continuously

01:41.280 --> 01:50.000
demodulating or decoding radio signals or transmitting you can do that as well as signal processing

01:50.000 --> 01:55.840
experience experiment. So if you just want to do like a little bit of scripting to work with some

01:55.840 --> 02:01.440
signals you can use a framework like this for that but you can also integrate Zigg Radio in your

02:01.440 --> 02:07.680
application natively so you can have an application that might be getting samples from wherever

02:07.680 --> 02:13.920
and then you can use the radio as just a signal processing engine and then get get whatever

02:13.920 --> 02:19.760
process data out of it. So the framework has a bunch of existing blocks like

02:19.760 --> 02:26.160
new radio but you can also create your own of course and it leverages external libraries

02:26.160 --> 02:33.920
for any acceleration so like new radio it it uses bulk which has a lot of SIMD processing routines

02:33.920 --> 02:44.240
for DSP but also it'll use liquid DSP if that's available and then also for any IO that has to

02:45.440 --> 02:52.960
that also comes from an external library so for the RTLSDR lib RTLSDR for air spy so on so forth

02:54.000 --> 03:01.040
it's written in Zigg open source MIT license and this is what I consider the spiritual

03:01.040 --> 03:07.360
successor to Lua Radio which I'll briefly talk about but I think not everyone is familiar with it but

03:09.680 --> 03:16.960
for those that are I'll do a comparing contrast so the explicit goals of Zigg Radio are threefold

03:17.760 --> 03:24.400
it needs to be high performance to do real-time SDR but should be easy to use a big one is zero

03:24.480 --> 03:33.520
dependencies so there's a lot of benefits of having a load dependency framework namely it's just

03:33.520 --> 03:38.960
easier to build the thing so if you've ever had a situation where you upgraded like the boost libraries

03:38.960 --> 03:45.280
on your system and then everything broke that's something you can avoid zero dependency stuff tends

03:45.280 --> 03:50.240
to be easy to deploy and then easy to integrate if you're going to use it in your own application

03:51.200 --> 03:57.600
in the cases Zigg Radio any flow graph or if you do integrate it into your own Zigg application

03:58.560 --> 04:04.080
it compiles into a single static executable which is kind of the gold standard of just being

04:04.080 --> 04:10.960
easy to deploy and the asterisk on the zero dependencies is of course that you know if you're going to

04:10.960 --> 04:17.760
leverage external libraries for IO or DSP those have to come from somewhere in the cases Zigg Radio those

04:17.840 --> 04:22.800
are all dynamically loaded so it's not it's never linked into the into the final product

04:24.240 --> 04:30.960
and then thirdly a goal of Zigg Radio which is really where Lilo Radio started was for rapid

04:30.960 --> 04:39.280
prototyping but specifically for embedded targets so one thing that's difficult is compiling like a

04:39.280 --> 04:46.320
flow graph on a Raspberry Pi so it'd be nice if you can you know tweak whatever SDR project you're

04:46.400 --> 04:52.240
working on when it's already on the embedded system without needing to recompile on the embedded

04:52.240 --> 04:57.840
system so in the case of Lilo Radio this worked out because it was an interpreted language and so

04:57.840 --> 05:05.840
Lilo Jet you know can spawn up and just in time compile any changes that you've made Zigg Radio

05:05.840 --> 05:12.000
is technically a step backwards in that because it is compiled it's not interpreted but the nice

05:12.160 --> 05:18.400
thing about Zigg especially with the LVM back end and then the future back ends that they'll have

05:19.120 --> 05:27.280
is you can cross compile from anywhere that you're at any target they like and that means that you

05:27.280 --> 05:32.960
can do all of your development on a host platform but be targeting an embedded system like a Raspberry

05:32.960 --> 05:40.800
Pi or whatever single board computer so one day hopefully this will kind of come back to the

05:40.880 --> 05:49.520
rapid prototyping that Lilo Radio had so briefly Lilo Radio is looks similar to Zigg Radio

05:49.520 --> 05:56.720
or rather Zigg Radio looks similar to it it's a Lilo Jet based SDR framework it really has the same

05:56.720 --> 06:03.040
goals as Zigg Radio does that's why I call Zigg Radio the successor it looks very similar like a

06:03.040 --> 06:09.200
lot of the API is the same and you can integrate Lilo Radio into your application but it's a lot

06:09.280 --> 06:14.320
easier to do as Zigg Radio with Lilo Radio you kind of have to do some IPC you know it's a little

06:14.320 --> 06:23.200
ugly and you have to link in Lilo Jet so the difference is now our Zigg Radio is compiled instead

06:23.200 --> 06:29.520
of interpreted Zigg instead of Lilo Jet it's multi threaded with ring buffers kind of a standard

06:29.520 --> 06:36.080
architecture and a lot of SDR frameworks Lilo Radio had to be multi process which was not ideal

06:36.240 --> 06:45.120
let's just reality of using that ecosystem Zigg Radio supports many more targets as

06:45.120 --> 06:51.760
sports anything that LLVM can generate code for and Zigg Radio already has a synchronous

06:51.760 --> 06:57.200
block control which I'll cover later most importantly though there is just the long-term

06:57.200 --> 07:04.000
viability of this project because Lilo Radio being based on Lilo Jet has sort of an uncertain

07:04.000 --> 07:10.000
future since Lilo Jet has been in maintenance mode so for for like future targets future architectors

07:10.000 --> 07:17.760
it's not guaranteed that that would work but you know Zigg is a new and healthy modern project

07:19.440 --> 07:25.520
so the Zigg language raise your hand if you're familiar with Zigg just had a curiosity

07:26.720 --> 07:33.760
okay I just maybe like a quarter of the room so this is basically a modern systems program

07:33.760 --> 07:40.480
language it really is a better C I think is the best way of thinking of it it looks roughly like this

07:40.480 --> 07:48.320
but there'll be a few more examples really the guiding principle of the language or as advertised

07:48.320 --> 07:54.080
from the from the website is no hidden control flow no hidden memory allocations basically nothing is

07:54.080 --> 08:01.600
hidden behind what the language is doing so to spell that out a bit more there is no operator overloading

08:01.680 --> 08:09.760
there's no like implicit destructors there's no classes there's no inheritance there's no implicit

08:09.760 --> 08:14.560
heat allocation so there isn't like a new keyword that will allocate memory behind your back

08:16.080 --> 08:22.400
and an extension to its simplicity and probably one of the more novel things that's known for is

08:23.200 --> 08:29.440
there is meta programming but it's in the very same language so instead of in C needing like a preprocessor

08:29.520 --> 08:36.240
language or in C++ having like a special templating language in Zigg you're still using Zigg

08:36.240 --> 08:42.800
but it's a compiled time and I'll show an example of that and then as I alluded to before

08:42.800 --> 08:48.640
there's just excellent a target and platform support I think it really was designed from the beginning

08:48.640 --> 08:56.480
to to be strong enough front and then most importantly for achieving the lower radio goals

08:56.480 --> 09:03.840
is that it compiles fast not it'll be faster on more architectures in the future I think is

09:03.840 --> 09:10.800
is in the roadmap but on x86 in particular where they've replaced the LVM back end with their own

09:10.800 --> 09:18.640
custom back end I waste for debug builds it it compiles very quickly so here's some examples of

09:18.640 --> 09:25.040
just Zigg pure Zigg so you know it's a statically typed language so you've got

09:27.040 --> 09:32.000
you know you're standard integer types you can have to fix size arrays pointers you have these

09:32.000 --> 09:39.840
things called slices which is basically a pointer plus a length you got strings you have optional

09:39.840 --> 09:47.360
values built into the language functions can return errors so errors are like a first class

09:47.440 --> 09:55.440
concept in the language and there are keywords for handling errors they look kind of like this

09:55.440 --> 10:00.080
so you know you can catch an error explicitly and do something with it or you can use this

10:00.080 --> 10:08.880
syntactic sugar to propagate errors up the stack there's loops the loops especially like this

10:08.880 --> 10:14.480
syntax is kind of like the Python enumerate where you get both the element in each

10:14.560 --> 10:22.240
thing you're iterating over plus an index and probably very importantly there's structures and

10:22.240 --> 10:30.320
the structures can have methods and the methods can have a self so if you've seen this pattern

10:30.320 --> 10:37.680
in languages like Python or Lua this language also has the syntactic sugar where the

10:38.560 --> 10:46.880
dot method syntax will pass a reference of the thing you call it on so you can basically build

10:47.920 --> 10:58.080
objects with with this alone yeah so here's just an example of a point structure that can add

10:58.080 --> 11:07.600
itself to another and return another point so a couple more examples

11:08.480 --> 11:16.960
here's like a Fibonacci number um routine and really the thing I want to highlight here is

11:17.760 --> 11:23.920
you can write this ordinary function to compute the Fibonacci number of the index you pass in

11:24.800 --> 11:30.880
and you can run it out runtime and you didn't vote it as you'd think but if you want to run this

11:30.880 --> 11:37.120
at comp time you just stick comp time in front of it and this whole thing is happens at compile time

11:37.200 --> 11:43.760
so it's essentially just building this binary with the result of this function already having run

11:46.880 --> 11:53.200
and in addition you can do things with types at comp time so you have type information at comp time

11:53.760 --> 12:00.160
and that lets you do interesting things for example conditional compilation so you can have

12:00.160 --> 12:05.760
entire sections of code that r aren't compiled based on what that type was at compile time

12:06.400 --> 12:11.440
and you can even do loop unrolling so here's kind of a crazy example where we're actually computing

12:12.240 --> 12:18.560
you know the this Fibonacci number but then using it to generate an unrolled loop and this will

12:18.560 --> 12:24.240
actually generate you know code with all 12 of whatever is in here unrolled

12:26.320 --> 12:28.320
the other uh

12:28.800 --> 12:36.240
the other thing that comp time is used for is just doing parametric types in general

12:36.800 --> 12:42.640
and the way it's done in zig is you have a function that takes in a type and it returns a new

12:42.640 --> 12:49.920
type in this case like a struct that's parameterized by that type so this is just an example

12:49.920 --> 12:57.200
of a moving average calculation and it can take in a type t which is some integral type

12:58.080 --> 13:05.200
in this case flow 32 but it could be some integral type as well and we've got two methods

13:05.200 --> 13:12.160
one of them to basically push a value into the history and then one of them to compute the average

13:12.800 --> 13:18.640
and as you can see we're referencing t for not only the state but you know all the parameters and the

13:18.640 --> 13:29.200
returns so to jump back to zig radio that was just kind of a tour of the zig language

13:31.040 --> 13:37.200
zig radio is a flow graph SDR framework and in this example we'll take the FM broadcast receiver

13:37.200 --> 13:41.440
that you saw earlier and just break down how you would express this in zig radio

13:42.320 --> 13:48.080
so there's this flow graph here and it's basically doing some tuning some filtering

13:48.720 --> 13:56.240
the actual FM demod some more filtering applying the emphasis and then down sampling it and playing it

13:56.240 --> 14:04.640
out your audio in this case a pulse audio sync I won't go too much into the actual DSP details but

14:04.640 --> 14:09.920
you know you can think of this as sort of like channelizing or tuning the particular part of the

14:10.000 --> 14:16.000
spectrum you want the actual FM demod and then some audio clean up and decimation

14:17.360 --> 14:24.800
so in zig radio you basically do this in three phases you instantiate all the blocks that you need

14:24.800 --> 14:31.200
with all the parameters some of these blocks they look like actually if you can see that they're

14:31.200 --> 14:36.400
in purple then that's the syntax highlighter knowing that it's a function call because these are

14:36.480 --> 14:43.440
examples of parametric blocks that are parameterized by some type so there's low pass filters that

14:43.440 --> 14:49.920
can be complex or they can be real depending on what type is passed and but essentially we've got

14:49.920 --> 14:57.680
you know one block for every block that you see in the flow graph so you know instantiate all your

14:57.680 --> 15:03.360
blocks and then there's a top level object called the flow graph and this is where they're all connected

15:04.320 --> 15:10.080
this syntax is just a linear block connection so connecting one blocks output to another

15:10.080 --> 15:16.480
blockchain but there's another syntax for blocks that have multiple inputs and outputs but it's

15:16.480 --> 15:24.720
pretty straightforward for for the flow graph you actually pass in the allocator you want to use

15:24.720 --> 15:29.040
so depending on your platform if you don't want to use the general purpose allocator if you want to

15:29.040 --> 15:35.520
use just like one big arena allocator however whatever your memory allocation needs are you know you

15:35.520 --> 15:42.400
can pass it in the top level there and then finally you run the flow graph if this flow graph runs

15:42.400 --> 15:49.440
forever this will block forever if the flow graph runs to completion it will term it'll unblock

15:49.600 --> 15:57.040
one system so some of the features of ZIG radio these these come from lower radio as well

15:57.040 --> 16:05.440
ZIG radios aware of the sample rates of all the blocks and it propagates them um to each block

16:05.440 --> 16:10.720
so that when the block initializes itself it has access to its own sample rate the nice thing

16:10.720 --> 16:17.600
about this is you can basically work in hurts everywhere like you don't have to calculate normalized

16:17.680 --> 16:24.080
frequencies or angular frequencies everything is in hurts which is really convenient it's also

16:24.080 --> 16:29.120
convenient when you change something upstream everything will propagate as we'd like downstream

16:32.880 --> 16:38.560
but yeah in this little example the sample rates all the way the same until it hits this down

16:38.560 --> 16:44.160
sample error and then it falls off by four and anything downstream knows that it's at 240 kilohertz

16:44.160 --> 16:54.720
at that point so another feature or I guess kind of a requirement but ZIG radio works fine with

16:54.720 --> 17:02.720
finite streams so if you have just an IQ file that you want to process to completion you can

17:03.440 --> 17:09.840
you can build a flow graph as normal and like I said when you when you run it it will unblock

17:10.000 --> 17:15.920
once it's done and really the the takeaway from this is that you can write scripts with this if you

17:15.920 --> 17:22.640
just want to do some DSP on files and in this case um because the framework itself has zero

17:22.640 --> 17:29.680
dependencies and in this case you probably don't care about runtime performance you know you can you can

17:29.680 --> 17:37.200
build this with no external libraries no um any DSP acceleration and it'll run fine

17:37.680 --> 17:44.560
so another feature in ZIG radio which which was planned for Lua Radio but but was kind of always

17:44.560 --> 17:52.080
on the future road map but is supported in ZIG radio is asynchronous control APIs for blocks so if

17:52.080 --> 18:00.400
you want to change for example on an SDR source if you want to uh tune to a different frequency

18:01.120 --> 18:08.560
you can wall the flow graph is running you can call um like set frequency and and and change it

18:08.560 --> 18:13.920
if you want to change the filter cut off on like a low pass filter or an AGC preset

18:15.200 --> 18:20.880
basically any block can expose any uh functions that can modify at state at runtime

18:23.440 --> 18:29.040
so it's actually make a block in ZIG radio is uh super simple this is actually a totally

18:29.120 --> 18:35.760
valid ZIG radio block and really you only need two things you need this state called block

18:38.000 --> 18:45.120
in the in the block struct and then you need a process function technically you got also initialize

18:45.120 --> 18:53.840
that state here and what ZIG radio does is when you initialize your block with this syntax

18:53.920 --> 19:00.640
that actually introspects on the whole um structure and it finds this process function and it

19:00.640 --> 19:06.400
figures out what the inputs and outputs are based on this type signature so a process function can

19:06.400 --> 19:15.280
have multiple inputs and outputs and inputs are marked by constant slices and outputs are

19:15.360 --> 19:23.200
mutable slices inside the actual process block you can do whatever DSP you'd like to do

19:24.240 --> 19:32.000
and then return it result that notifies the framework how many samples were consumed and produced

19:32.720 --> 19:40.640
so pretty straightforward um the framework will call process repeatedly uh in its own thread

19:41.440 --> 19:48.400
and it has some guarantees on this which are more in detail on the website but basically um

19:48.400 --> 19:53.040
you know little guarantee that some equal number of samples are available across all these vectors

19:55.520 --> 20:00.560
if you want to make this very same block if you want to make it completely parametric

20:01.200 --> 20:05.840
you can in typical ZIG fashion just wrap it in a function that takes in a type

20:06.720 --> 20:13.360
and then now we have a multiply block where instead of just being for float 32's it's

20:13.360 --> 20:20.240
for any type T um the only caveat here is that this type T has to support this multiply operator

20:20.960 --> 20:26.640
and as I mentioned before there is no operator overloading in ZIG so the only time you might run into

20:28.480 --> 20:33.920
some of its simplicity is in stuff like this but then again you can you know right code that will

20:34.000 --> 20:38.400
do different things based on different types or uh handle it however you'd like

20:41.840 --> 20:51.520
so in general blocks can use any ZIG type um the most common ones are the built-in complex

20:51.520 --> 20:57.840
float 32 for complex value samples float 32 for real that real value samples u-a's for

20:57.840 --> 21:05.280
bytes u-on for bits um you can also create your own types which will be in like two slides

21:07.280 --> 21:15.440
so blocks also have this concept of hooks which I'll just mentioned briefly um basically there's

21:15.440 --> 21:21.440
three main hooks right now initialize the initialize and set rate initialize and the initialize

21:21.520 --> 21:27.920
lifts a block do memory allocation the allocation or any kind of IO if you're going to open

21:27.920 --> 21:35.040
up an STR source or initialize an audio device you can also do sample rate dependent initialization

21:35.600 --> 21:41.840
so if you have a DSP block that needs to know it's sample rate um this is where you would do it

21:43.920 --> 21:50.160
and then blocks can basically specify the downstream sample rate so for a source block

21:50.640 --> 21:55.840
overloading this hook is what sets the initial rate for anything connected downstream

21:56.480 --> 22:02.400
and then for blocks that modify the sample rate like up samples down samples you would implement

22:02.400 --> 22:07.520
this hook and modify you get your upstream rate here and you return the the modified rate

22:10.880 --> 22:18.160
so custom data types you can make out of ZIG structure unions as pretty much what you'd expect

22:19.120 --> 22:24.640
um this is just a toy example of like a weather packet but you can use any types that you'd like

22:24.640 --> 22:30.160
in there um dynamically allocated types are also supported and they're kind of handled

22:31.840 --> 22:38.960
by the framework in that um they're allocated once and then the reference counted throughout the

22:38.960 --> 22:46.480
flow graph and then when the final usage of it is when it's after the final consumer of it

22:47.120 --> 22:52.800
it gets deallicated so you can have fairly efficient dynamically allocated types but you know it's

22:52.800 --> 23:00.800
generally not recommended for any high sample rate uh things just maybe slower packets and

23:00.800 --> 23:09.840
something of that nature so asynchronous control as I mentioned before um in ZIG radio you can

23:09.840 --> 23:15.920
at runtime change parameters in the block and the way you implement this in a block is it's just a

23:15.920 --> 23:21.440
normal function that modifies its state and the framework guarantees that it'll be run

23:21.440 --> 23:27.200
exclusively of the process function so from a blocks perspective it's always single threaded you

23:27.200 --> 23:34.080
don't have to think about locking or anything like that um and then from the from the top level

23:35.920 --> 23:40.880
from the owner of the flow graph you'd use this call API to do a thread safe call into into the

23:40.880 --> 23:47.680
block so here's an example uh this is a multiply constant block so it takes in a constant

23:48.480 --> 23:57.040
on construction and we just expose this uh new method here called set constant back and update it

23:57.040 --> 24:05.280
add at runtime these these methods can also return errors and they can also return data back so

24:06.160 --> 24:15.280
um this this call wrapper will also give you results from blocks are sorry from functions that

24:15.840 --> 24:19.840
you know might not return void as well as any error that's returned

24:26.160 --> 24:31.840
so um you know one of the advantages of using ZIG is you get the rich target and architecture support

24:32.000 --> 24:38.880
that it has and as I mentioned before like one of the stated goals is to be able to deploy onto embedded

24:38.880 --> 24:46.000
systems very easily and this is just an example of that so if you clone the ZIG radio a repo

24:46.800 --> 24:52.640
then you can build all the examples on the system that you're running it on just by running ZIG

24:52.800 --> 25:00.800
build examples so in this case I've built it on X86 64 as you can see by the file I'll put

25:01.680 --> 25:12.640
but just by passing one flag to change the target for AR64 specifically for a Linux platform

25:13.760 --> 25:20.640
we can build all of the examples from the host for something that you can then just copy

25:20.720 --> 25:28.080
directly to a Raspberry Pi 4 and you can run it there's nothing else required partly because ZIG

25:28.080 --> 25:37.040
radio has no dependencies and it generates static executables um I guess I just to be like technically

25:37.040 --> 25:42.720
correct the only real dependency is it does use the C library so it can dynamically load

25:42.720 --> 25:50.320
anything that's needed at runtime but you don't need like a sister or anything to to make this work

25:50.400 --> 25:58.320
so this is uh super convenient for just developing on a host and then um and then being

25:58.320 --> 26:05.680
able to deploy it onto an embedded target so everything up till now has just been talking about

26:05.680 --> 26:12.240
creating flow graphs for standalone receivers or transmitters and for like creating your own blocks

26:12.240 --> 26:18.160
now let's say you actually want to use the radio in your own application how it should do it and

26:19.040 --> 26:24.080
there's two special primitives for that called the application source and the application sink

26:25.040 --> 26:32.480
and basically the source lets you inject samples into the flow graph and then the sink lets you

26:32.480 --> 26:42.000
pull them out or consume them from the flow graph so um these the source and sink then have their

26:42.000 --> 26:47.840
own little thread safe API that let you push and pull samples out of it so in this example we have

26:48.720 --> 26:54.000
this application source as you can see it's a parametric source so in this case it's type for

26:55.280 --> 27:02.720
for real samples flow 32s but this can be any zig type so you can you know maybe be producing

27:02.720 --> 27:08.080
IQ samples which are complex floats and then maybe your application sink is giving you fully

27:08.640 --> 27:15.520
decoded packets which are some zig struct and this egg toy example though we just have an

27:15.520 --> 27:22.720
application source and we're going to add the flow 32 sample to itself and then multiply it by

27:22.720 --> 27:31.680
itself and then throw it out the sink and if we were to start the flow graph this is kind of what

27:31.680 --> 27:37.120
the simple API looks like for interacting with the source in the sink so in this case we're going

27:37.120 --> 27:44.080
to we can push three samples to the source wait for three samples to be available for reading from

27:44.160 --> 27:51.200
the sink and then read them out and then finally when we're done with the flow graph assuming

27:51.200 --> 27:58.480
at some point this terminates you can set end of stream and the flow graph will collapse naturally

27:58.480 --> 28:03.680
as that end of stream propagates through all of it so then if we run the example you know we'll

28:03.680 --> 28:16.480
get for 1636 for the three numbers that got doubled and squared yeah so yeah this is the

28:17.360 --> 28:24.720
kind of the simple push and pop API there's more there's there's more expressive APIs for

28:24.720 --> 28:31.600
working with like slices and larger quantities of data and note that this is fully parametric

28:31.600 --> 28:38.800
for whatever type that you instantiated that source or sync with so it's expecting the same type

28:38.800 --> 28:48.560
that it was made with so as sort of a case study for a fully integrated application using

28:48.560 --> 28:55.280
ZIG radio there's another project I have called rat fly and this is basically a short way

28:55.360 --> 29:04.400
slash am radio receiver that runs in the in the web browser with a ZIG radio back end and

29:05.680 --> 29:12.560
it's a simple like am demodulator envelope demodulator with some power information and there's an

29:12.560 --> 29:22.560
AGC and some settings you can tweak all of these like settings that you see here are using that

29:22.560 --> 29:29.280
asynchronous control API so this is like a long running flow graph but as you tweak these settings it'll

29:29.280 --> 29:38.320
update the flow graph in real time the actual architecture of this is basically a ZIG app called

29:38.320 --> 29:45.600
rat fly this is a single static executable containing all of this so it's got the whole ZIG radio

29:45.600 --> 29:52.560
flow graph which is generating both audio samples and power samples and it's leveraging

29:52.560 --> 30:02.160
HTTP.ZIG which is a basically a web framework in ZIG that lets you create web servers with

30:02.880 --> 30:09.600
analyers there's also a companion library that gives you websocket support and

30:09.920 --> 30:17.040
this binary is hosting like not only the flow graph but the entire web server and even the

30:17.040 --> 30:23.680
the front end bundle is like statically in the executable so it's literally like one file was running

30:23.680 --> 30:32.000
the whole thing and that front end bundle is served over an HTTP handler and then this you know

30:32.000 --> 30:37.760
front end will connect back over websockets and that's where it's getting real time power samples,

30:37.760 --> 30:48.480
audio samples and control and it's using the browser's audio APIs to play audio out just as like

30:48.480 --> 30:56.880
an aside like I've been running this continuously for for months on on a Raspberry Pi 4 and

30:56.880 --> 31:03.120
one thing that I like about ZIG in general and I guess kind of a consequence of this framework is the

31:03.120 --> 31:09.600
memory footprint is like constant basically so this is appropriate for any like long running

31:11.840 --> 31:21.120
application you might have on an embedded system. So the website is thegradyo.org all the

31:21.120 --> 31:26.000
information in these slides is essentially on there as well also the slides are updated on the

31:26.000 --> 31:35.440
Boston website. In addition there is a reference manual that you can just click through the

31:35.440 --> 31:53.200
website on the reference manual link. This has all the blocks that basically it has a list of all the

31:53.200 --> 32:00.160
blocks in the framework and you can get kind of a general description, the type signatures and

32:00.160 --> 32:04.880
examples so if you go on the reference manual you can see everything available right now.

32:12.800 --> 32:18.880
So for road map in the short term just more blocks there's already some digital stuff in the

32:18.880 --> 32:27.760
current release but there'll be more in the next release including an X25 receiver, a CHU time

32:27.760 --> 32:36.880
station receiver and then more stuff down the line. But there's more acceleration blocks that

32:37.440 --> 32:43.360
could be added especially like decimating if our filter is a lot of that is missing right now

32:44.320 --> 32:51.120
that's kind of on the road map as well plotting sinks there's no plotting sinks so it's a little

32:52.160 --> 32:58.160
can be a little difficult to do real-time debugging. I usually am using a Jupyter notebook to plot

32:58.160 --> 33:08.000
things from IQ files. In the medium term platform support is also I guess like a medium priority

33:08.960 --> 33:15.760
surprisingly like it's actually pretty easy to target windows of sick not only because the

33:15.760 --> 33:23.520
compiler can support so many targets but also because the standard library is very target friendly as

33:23.520 --> 33:30.800
well and it actually has windows implementations of stuff normally I wouldn't really prioritize that

33:30.800 --> 33:37.520
but a bit of the amateur radio community uses windows so this seems to come up from time to time

33:39.760 --> 33:44.240
and then web assembly would also be cool to be able to take a whole flow graph and basically

33:44.240 --> 33:50.240
compile it down to something that could run on any wasom compatible execution environment

33:51.040 --> 33:56.960
and then long term is sort of TBD I think there's enough to keep busy in the short and medium term before

33:57.440 --> 34:06.400
really changing any big direction so if you're interested in contributing you can find it

34:06.400 --> 34:13.520
you can do those big radio and find the GitHub project all issues and PRs are appreciated and

34:13.520 --> 34:19.120
as I kind of just mentioned there's like a ton of low hanging fruit so there's single processing

34:19.120 --> 34:25.520
blocks that don't exist that are probably easy to implement and that's a good project and

34:26.320 --> 34:32.320
also for existing blocks there's there's a bunch of blocks that exist that don't have a synchronous

34:32.320 --> 34:38.320
APIs so APIs that can like do stuff like changing the cut off or changing some parameter that's

34:38.320 --> 34:48.320
pretty straightforward IO blocks of course that's sort of an endless list there right now the RTLSTR

34:48.400 --> 34:57.840
supported and the air spy hf is supported probably air spy will be next and for audio there's

34:57.840 --> 35:06.960
pulse audio sinks but yeah more can be added there if you'd like to try as a radio you can

35:07.920 --> 35:13.360
clone it build the examples and run one of the examples if you have an RTLSTR and if you're on

35:13.360 --> 35:20.560
Linux because this will use pulse audio and then optionally you can install vol quick with DSP

35:20.560 --> 35:27.360
fftw for runtime acceleration if you're on a powerful enough host you might not need it for

35:27.520 --> 35:36.160
some of the examples but it doesn't hurt yeah that's it any questions

35:49.440 --> 35:56.560
yeah thank you for the talk I have a question what I did the the whole world of

35:56.720 --> 36:03.440
the R myself I've noticed that when playing the FM radio there's drift

36:03.440 --> 36:10.560
both buffer off over on the underruns that's the ingredient handle this so the question is does

36:10.560 --> 36:16.480
the framework mitigate like buffer overruns or underruns I mean I think some of that will just be

36:16.480 --> 36:25.440
dependent on your processing situation but the ZIG radio does have I forgot how big they are there

36:25.440 --> 36:33.360
might be like a quarter of a megabyte buffer in between every in between every block so there is

36:34.320 --> 36:40.800
buffering in the framework and there is back pressure but yeah I mean if you're still overrunning or

36:40.800 --> 36:46.720
underrunning that that could be either the SDR source isn't keeping up or your computer isn't keeping

36:46.800 --> 37:04.000
up with the processing yeah yeah it plays in real time I mean honestly just just

37:04.000 --> 37:09.600
decimating like after you already isolated however much you need of the FM that that already

37:09.600 --> 37:14.880
brings the sample rate low enough that you should be able to keep up on most modern and if you

37:14.880 --> 37:19.920
can't then you probably need some acceleration like a library

37:33.280 --> 37:38.000
so the question is if you have multiple R TLSDRs and you want to just do

37:38.240 --> 37:46.720
you can so multiple R TLSDRs are supported you can specify which one you want by a

37:46.720 --> 37:53.600
device index or serial number technically the sample rates match as far as the frameworks

37:53.600 --> 38:00.000
concerned it'll it'll combine samples but but yeah I guess that's sort of a front here there

38:00.000 --> 38:05.280
that you'll have to explore the I haven't thought about what would happen exactly but yeah

38:05.280 --> 38:20.960
in theory that the samples will move so the question is is there any way to visualize the

38:20.960 --> 38:26.480
flow graph and the short answer is there isn't anything like new radio companion so there isn't

38:27.680 --> 38:33.920
like a dragon drop UI for it I'm not sure if there ever will be to be honest I mean it's not a

38:33.920 --> 38:39.920
hard thing to build it's just sort of a a time thing it could be like a third-party project at

38:39.920 --> 38:47.200
some point I think I am planning at some point to be able to export just a graph of the flow

38:47.200 --> 38:54.560
of the flow graph like and I think in the past I was looking at a diet but any sort of

38:55.600 --> 39:02.560
format that can draw nodes and connections and stuff so that might be in the future

39:04.240 --> 39:10.480
so have you done any kind of benchmarking comparing let's say a yeah the little or the sample

39:10.480 --> 39:18.560
in a new radio and it's a radio team right as yeah the question is has there been benchmarking

39:18.560 --> 39:25.040
to compare the framework so there is a built-in benchmarking suite if you instead of running

39:25.040 --> 39:31.040
zig build examples if you run zig build benchmark it'll to benchmark across like 20 different

39:31.280 --> 39:37.840
examples and those early on I did compare those with low radio to make sure there was parity

39:38.640 --> 39:45.440
I think generally what I found is like if you really are pressing for the DSP at that point it's

39:45.440 --> 39:52.400
all like whether or not you have bulk and then the framework kind of isn't as much of a bottleneck anymore

39:52.960 --> 40:00.080
but yeah it's something that should probably be revisited or I was thinking of having some sort of

40:00.080 --> 40:05.120
regression test just to make sure it doesn't fall behind but yeah for the most part it's like

40:06.400 --> 40:12.240
anything very DSP heavy it the framework hasn't been too much of a bottleneck yeah

40:18.240 --> 40:24.400
right yeah yeah I'll have to revisit I'm not sure if it's like

40:24.800 --> 40:31.440
better by any order of magnitude for example I mean if it fits better or worse is probably on

40:32.480 --> 40:35.040
10% of all but yeah

40:36.800 --> 40:40.480
Thank you very much for your attention

