Zebra PC blog

Official support for: u-he.com
Post Reply New Topic
RELATED
PRODUCTS

Post

The long:
Urs wrote:Hi all,

just a short interruption of the silence, letting y'all know what's going on before I go back to work (after having a beer and some sleep, of course).

You know, weird things happen from time to time. About 10 days ago the scripting engine was finished and thus I think everything was finished. Just a small tidy up, some lines for VST2.4 support and a few tests and Z2 would've gone Release Candidate.

But then, so many great ideas came up while working on scripting. Although I never saw any big sense in scripting other than intelligent randomizers, the work and the discussions brought up some nice topics. What if one could script Midi filters, or even simple dsp modules? In short: I got obsessed by the idea to switch from *interpreting* a script to actually *compiling* it. Well, not necessarily compiling it into native code, but into some sort of "bytecode" that would be executed close to native speed (let's say, faster than JAVA in any case). So, that's where we are now...

Well, one could argue that I could've speed it up *after* releasing Z2 and leave it at interpretation for now. I was pretty sure that this would cause a lot of trouble. Maybe the language has to be changed in some ways for fast bytecode execution. This would've meant that there would either be two slightly different languages, or, the scripts made with the first incarnation wouldn't work in future versions. Both alternatives wouldn't have been an option. Instead, I voted for some extra delay and for a final implementation that is future proof.

Today I think I've brought it to the point where the interpreted language was, with a few object bindings left to code. And with the ability to benchmark the result. So much for now: The speed of the bytecode interpretation outpaces my wildest expectations :D

While creating a set of random wavetables for all four Z2 oscillators took a quarter second in the interpreted language, it takes, uhm, roughly 3350 microseconds in bytecode. That's 3.5 ms vs. 250 ms or a factor of 1:75 for bytecode vs. interpretation :shock:

This even is fast enough for small dsp algorithms, i.e. an ordinary 12dB lowpass filter would cost 3-5% cpu on my ancient G5 machine.

However, I had to indeed change the language a bit to get these speed results, which are necessary to implement real time features later. Best of all, in another step the bytecode could be translated into native code without too much hassle, for maybe another speed gain of factor 10 or so. But that's a project for autumn, maybe.

So, these maybe 2-3 extra weeks have opened up some great possibilities. I can think of scriptable oscillator effects and waveform generators. Script based modulators. Script based articulations and midi-controlled behaviours.

And of course, many feature requests could be delivered by writing a scripted preset, rather than shelling out an update.

That's something!

Cheers,

;) Urs
AND

the short:
kinda Urs wrote:Zebra2 is wicked hot. It will consume your very being, morphing you into a shell of the person you were. You will spend the rest of eternity drooling over the 30 kabillion presets that come with Z2 and the sonic power at your disposal.

Post

Urs wrote: So, these maybe 2-3 extra weeks have opened up some great possibilities. I can think of scriptable oscillator effects and waveform generators. Script based modulators. Script based articulations and midi-controlled behaviours.
:party:

For this I'm happy to wait.

Post

willb wrote:Urs, have you seen vmgen from Anton Ertl at TU Wien? It's pretty sweet.
That's interesting! I've just read the docs and think that it does pretty much the same thing I came up with, with some differences. For instance, vmgen seems to create self contained interpreters that implement all interfaces to the outside world into the language. My approach is different from that, as it has a simple universal execution unit and a dedicated back end interface to the context it runs in. For example, there are different objects known to the language when the script is executed as a SmartPreset or when it's executed as an event based realtime modifier within a single dsp module.

To illustrate this, in a SmartPreset you need object access like this:

for ( int i = 1; i <=4, i++ ) Oscillator[ i ].Tune = rand( Oscillator[ i ].Tune.min, Oscillator[ i ].Tune.max );

This would set a random value to each oscillator's Tune parameter, within the appropriate ranges of that parameter.

Now, in a script that creates an oscillator waveform in realtime, you can't access the plugin's parameters, or people could come up with rouge scripts that corrupt the plugin state. Instead, you'll have to implement predefined functions that will be called upon execution time:

Code: Select all

int CreateWaveTable( float Table[ 2048 ], float WaveForm )
{
	float phase = 0.0;
	float phaseInc = 2.0 * 3.14/2047.0;
	float out = 0.0;
	float fm = 3.14 * WaveForm/15.0;

	for ( int i = 0; i < 2048; i++ )
	{
		out = sin( phase + fm * out );
		Table[ i ] = out;
		phase += phaseInc; 
	}
	return 1;
}
This is a simple example that simulates FM feedback for a single oscillator. The oscillator passes a wavetable to the function and the WaveForm parameter that is usually used to select the current wavetable/curve. The script itself does not need to know about anything in the outside world, except maybe some fft and interpolator objects.

The point is, I have *one* universal compiler & VM, and an interface layer that offers context-based objects at compile time and a very fast communication method during execution. This makes the VM reusable in arbitrary situations, without having to create multiple VMs for a single plugin.

Another intrigueing difference is that I've prepared the VM to work... without any VM. I've found a way to have each instruction find its follow-up instruction and branch to it without manipulating registers or stacks. It's not yet at the point, but it will involve adding some inline asm into the instruction callbacks. This will yield into bytecode that executes about twice as fast as normally, because there's no need for an instruction dispatcher anymore. However, it means that I have to write different code for Intel and PowerPC, which I'm not yet willing to. Autumn, maybe.

On the downside, creating your own VM means a lot of testing and bugfixing...

Cheers,

;) Urs

Post

Very cool stuff. I wish there were more domain-specific music and dsp languages like this. Every time I'm working in Reaktor or Max I can't help but feel that a text-based language would be much better than a graphical language for a lot of this kind of thing.

Post

Urs: thanks for the info, this looks extremely cool. Have you looked at "threaded code"? (not like "multiple threads," but where each bytecode is replaced by the address of the function that implements that bytecode) That is very fast on machines with indirect branch predictors (e.g. P4) and not so fast on PPC. We can talk about this stuff more offline if you're interested.

kuniklo: of course, there's SuperCollider, Chuck, and impromptu -- and more venerable options like Common Music, SAOL/SASL, and Csound.

Post

Could we have script based intelligent algorithmic effects sort of like Karma then? :)

Post

aMUSEd wrote:Could we have script based intelligent algorithmic effects sort of like Karma then? :)
At one time, yes.

The first implementation will just be SmartPresets, which can be viewed as a functional extension to presets. Like, randomizers, Waveform creation algorithms (not dynamic, they'd just write into the static wavetables of the oscs), MSEG/Arp pattern generators. Of course, a SmartPreset does not have to be a full preset. It can contain just the script, so that you can load these things into any patch you have open at the current time.

In the next step (after updating MFM, Filterscape and another goodie) I'll add event based scripts that can be triggered by Midi, the Arp, timers, whatever. That should do for Karma-like sequencing (if you mean Korg's Karma...) as well as for articulations like in the latest orchester libraries. In that go I'll probably also add dynamic waveform generators for the Oscs, along with scriptable OscFX.

And then... the big step... if I manage to get the scripts compile to native code, there's a good chance for script based modulators, audio modules and audio effects. That would be something, wouldn't it? 8)

Btw. Filterscape and the other plugs will automatically support SmartPresets, too. No idea if they'll be as useful in the context, but I'm sure that there are gonna be some nice applications, too.

Cheers,

;) Urs

Post

willb wrote:Urs: thanks for the info, this looks extremely cool. Have you looked at "threaded code"? (not like "multiple threads," but where each bytecode is replaced by the address of the function that implements that bytecode) That is very fast on machines with indirect branch predictors (e.g. P4) and not so fast on PPC. We can talk about this stuff more offline if you're interested.
Will, this is exactly what I'm doing. But I'm going to take it a bit further with a bit of inline assembly. Instead of letting each instruction pop back to the VM, I'm gonna let it branch to the next instruction directly, without need to store the return address on the instruction stack. I know how to achieve it on PPC, but I have no idea yet about x86. The advantage of the PPC thing is that the one and only function argument (a pointer to the machine state) is always in R0 and stays tehre until the end of the function. On x86 I will have to explicitely load it in EAX (I guess) first before the branch.

Another approach would be an enhanced switch/case architecture, where one could exploit gcc's "Label by Value" thing and just put a goto &&label_of_next_instruction at the end of each piece. Dunno if this works for VC++ though...

The most confusing point currently is how to create functions. Unlike vmgen I'm using a mixed approach of a stack and register architecture. For array indices and method arguments etc. registers are used like a stack while operations are register based, which saves a lot of moves for out-of-order processing. But for custom functions I have to create some sort of stack that saves a minimum amount of registers, otherwise functions can't be called recursively. We'll see...

Later,

;) Urs

Post

Wow Urs! You don't know when to stop. Z2 is amazing already. :hail:

Oh well I guess transending perfection is good!

Best,
Gordon

Post

willb wrote: kuniklo: of course, there's SuperCollider, Chuck, and impromptu -- and more venerable options like Common Music, SAOL/SASL, and Csound.
And Nyquist, Faust, etc. It would be nice to have something scripted that just plugs in as a vst though (I know CSound can do this now but I'm not a fan).

Post

It would be nice to have something scripted that just plugs in as a vst though (I know CSound can do this now but I'm not a fan).
this is something i've hoped for for a long time, and maybe with all the scripting functionality that urs is adding zebra may end up as the shell for a huge scriptable dsp system. and then we could see a whole community of zebra scripts and extensions :) :)
btw i couldn't even get csound to work properly in vst mode so i gave up

Post

Hengy wrote:
It would be nice to have something scripted that just plugs in as a vst though (I know CSound can do this now but I'm not a fan).
this is something i've hoped for for a long time, and maybe with all the scripting functionality that urs is adding zebra may end up as the shell for a huge scriptable dsp system. and then we could see a whole community of zebra scripts and extensions :) :)
btw i couldn't even get csound to work properly in vst mode so i gave up
We'll see. Please don't expect too much. The scripting engine will be buggy as hell for quite some time. No way that all bugs can be revealed & swept away in its first incarnation. It will do what it's been meant to in the first place, but I also don't want to make it a lifetime enterprise.

Of course I have a deep interest in getting it going for dsp. But the whole support for this has to happen outside of my mailbox. If it's getting too much, there has to be a website/forum/mailing list. Hehehe, I don't want to spend the rest of my developer life in debugging other people's code :oops:

However, yesterday I managed to give it a real world test. The most complicated part naturally is dynamic object access. This is so over my head that I'll only allow for explicit objects, but no pointers/placeholders for them. Fortunately, similar objects already come as arrays, which is a good replacement for pointers (even easier for non-developers I think). This also has the advantage that there's no need to resolve types at runtime. So, I'm very happy that the following code does exactly what it should (in 400 Microseconds...):

Code: Select all

for ( int xy = 1; xy < 5; xy++ )
{
	for ( int slot = 1; slot < 9; slot++ )
	{		
		Core.XY[ xy ].Right[ slot ] = rand( Core.XY[ xy ].Right[ slot ].min, 
									Core.XY[ xy ].Right[ slot ].max );
												
		Core.XY[ xy ].Left[ slot ] = rand( Core.XY[ xy ].Left[ slot ].min,
									Core.XY[ xy ].Left[ slot ].max );
		
		Core.XY[ xy ].Up[ slot ] = rand( Core.XY[ xy ].Up[ slot ].min,
									Core.XY[ xy ].Up[ slot ].max );
		
		Core.XY[ xy ].Down[ slot ] = rand( Core.XY[ xy ].Down[ slot ].min,
									Core.XY[ xy ].Down[ slot ].max );
	}
}
It nicely animates the XY assign page in Z2 each time you load this script 8)

Later,

;) Urs

Post

cool, and don't mind me, i was only thinking out loud, i'll be happy with whatever level of scripting you add

Post

AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.
Cool that you're adding scripting... but...

Have to admit, here, I'm a bit surprised you wouldn't just use something like lua, or do bindings in SWIG or something. Spending lots of time writing a scripting language, a VM, and the rest just doesn't seem like the best use of anybody's time, even us as users.

Lua's used as the real-time UI engine for World of Warcraft, for example - and SWIG has bindings to a bunch of different languages. Binding in most of them is pretty straightforward, and you get the benefit of being able to have a toolchain for developing the scripts.

...matter of fact, there's even a JIT compiler.

http://luajit.luaforge.net/luajit_performance.html (http://luajit.luaforge.net/luajit_performance.html)

Post

lightyear wrote:Have to admit, here, I'm a bit surprised you wouldn't just use something like lua, or do bindings in SWIG or something. Spending lots of time writing a scripting language, a VM, and the rest just doesn't seem like the best use of anybody's time, even us as users.
It may seem like that. I have indeed looked at lua and at Spidermonkey. But I have abandoned the idea to use these for several reasons :-o

One drawback of the luaJIT for instance is its restriction to x86 processors. But more than 50% of my users still use PowerPC processors and will go on using these for quite some time.

The usage of scripts in my plugs is a very specialized application. It needs flexibility at some uncommon points, while it needs to be restricted at some points that are typically considered standard features:

- including external files into scripts must not be possible, because presets must be "standalone"
- the object binding must be dynamic (no static symbol table etc.), because my plugs have just too many objects
- the engine must fit in 5-10 kB of code that are reusable for different situations (no binding at compile time)
- registers and variables must be accessible from the outside
- features like class definitions are not required because they're already defined by the plugin structure. Any additional oop would just bloat the engine for no reason
- there must not be any memory allocation during execution

I could go in detail why this is and what it's all about, but that would go a bit beyond...

See, my problem with all these third party libraries is, they are all a bit super potent for what we need and it takes a lot of time to study & adapt them. It's not like you download the sources, compile and go. I'm already using several libs for the pc version, mostly to emulate Apple's CoreGraphics APIs. Call me a slow coder, but it took me months to get things working together in the way I wanted. With the exception of font rendering I have meanwhile replaced many, many things with my own dedicated code for the right job. As an essence of that experience, I'd say that it wouldn't necessarily be faster (for me) to incorporate something existent nor would I feel safe to say that it's the right thing for the specific task.

E.g. it was shocking that adding support for .aupreset on Win has more than doubled the size of the binary, using open source projects. So I left it (XML parser and some CoreFoundation stuff...) away until I have time to write my own 2kB converter, instead of 900kB of redundant instructions. Which cost me a couple of days to find out.

And honestly, I always wanted to write my own vm. Stuff like this is what makes the whole dev thing interesting for me. I have learned a lot within the last 3 weeks (writing an interpreter and then converting it into a vm), and the outcome is a simple, lightweight, extensible thing that won't get in my way for future ideas. It's only 5000 lines of code (2000 lines of not-so-readable geek code by C++ template/stdlib evangelists who don't put braces on extra lines). I honestly don't want to add any subdirectories with 20+ files to my project anymore. I want it clean, integrated, same style of coding, same design and so that I understand what's going on and when.

For instance, for a lot of things, especially dsp stuff, it's necessary to "patch" right into the engine. Stuff like that only works when you can be sure that nobody else changes anything. That is, when you've written it yourself. If you need to upgrade a 3rd party lib, i.e. because of platform issues (Intel Macs anyone? Or 64 bit architectures?) you can be sure that you have to spend a lot of time into redoing a lot of changes. Maybe I'm paranoid here, but that's just how I see things.

Last but not least, I had great fun writing the parser 8)

(Writing the VM was more like a bad joke... it's not much more than a file with 200 callbacks that consist of 3-8 lines each. It was done in 6 hours or so.)

Cheers,

;) Urs

Post Reply

Return to “u-he”