Welcome/Apps/VividShaper/Getting started
One of the first Wavetable synthesizers was the PPG Wavecomputer, which was released in 1978 and had 30 different wavetables. It was followed a few years later by the Wave 2 (1981), Wave 2.2 (1982), and Wave 2.3 (1984). Each wave had the length of 256 samples (probably one byte per sample). A wavetable is a table of waves that can be looped through to give an evolving sound. The PPG Wave 2.3 had 30 different wavetables, each consisting of 64 waves (a total of 1920 waves). With 256 samples per wave, that required a memory of 480 kB.
PPG Wave 2.2. Image licensed under CC-BY-SA-2.0. Original source: https://www.flickr.com/photos/krunkwerke/sets/72157623332441453/with/4366765092/
In addition to these wavetables, the PPG had also an analogue low-pass filter, two ADSR envelopes, and one AD envelope generator. The envelopes could not only be used for the volume, but also to instruct the oscillator to cycle through a given wavetable. It had furthermore eight voices, each with two oscillators, where each oscillator could play a different wave.
In 1986, the US synth company Sequential released their Prophet VS synthesizer. This was marketed as a “vector synthesizer”, because it could mix between different waveforms using the built in waveform joystick. The actual synth engine core was however a wavetable synthesizer, consisting of 96 factory-preset 12-bit waveforms. These waves had the length of 128 samples. It wasn't really the wavetable part that made this synth unique, but its combination of digital waves and vector synthesis. Despite this novel approach, it wasn't very successful and the Sequential company was sold to Yamaha in 1987. Dave Smith who owned the company went on to start a new company known has Dave Smith Instruments.
One would think that Yamaha bought Sequential for its innovative idea of the Prophet VS, but it was Korg that actually did something with the “vector” synth idea. They borrowed the idea of vector synthesis and made their own Korg Wavestation synthesizer (released in 1990). The Korg Wavestation is more like an hybrid between a wavetable synthesizer and a rompler, that is a synth that can play back recorded samples. However, since it has the ability to smoothly walk through different waveforms by building sequences of sounds, it may be regarded as a wavetable synth and it can for sure operate as one if the waves are short enough,
Later, in 2002, Dave Smith released his first instrument after selling Sequential to Yamaha. It was called the Evolver and it was a real wavetable synthesizer. It had the same waves as the Prophet VS, plus 32 user waves that could be uploaded via MIDI. That allowed users to create their own waves and play them back.
It should be noted that there is some misconception around the term “wavetable”. Sometimes, it is referred to a set of waves that you can loop through, and sometimes it is just referred to a single wave. Sometimes, this is then referred to as a “single-cycle” wavetable. Today, a wavetable synthesizer is just the overall technique of using very short samples that either changes dynamically or are played statically where other effects are added like a subtractive synthesizer.
Today, there are many software synthesizers that have adapted the concept of wavetable synthesis, including Native Instrument's Massive, Reason Studios Europa, and Serum from xfer records. However, they are all built around the idea of predefined wavetables that you can manipulate using various effects.
VividShaper is quite different. It is a wavetable AUv3 plugin synthesizer for macOS and iOS that borrows the idea from the early wavetable synthesizers that had only 128 samples per wave and with a number of waves to cycle through. However, instead of having only a fixed number of waves to choose from, VividShaper lets you program and modify your waves over time using the built in Lua programming language.
When you start the plugin for the first time, you will see a window with the Lua coding editor on the left side and a waves view on the right side. In the waves view, you will see two waves next to each other, one in green colour and one in red colour. The green wave represents the left audio channel and the red wave represents the right audio channel. In this example, since we have not changed the panning of the current oscillator, it will output the wave with the same amplitude to both the left and right audio channel.
This is an oscilloscope view of the current output from the generator, so it shows the superposition of all oscillators being played.
In versions prior to v1.2, you would only be able to see the wave for one oscillator at a time. You could control which oscillator with the variable wavesview. With the current oscilloscope, you don't need to think about it since you get to see the output from all the waves combined. However, being able to view a given wave could still be interesting in the case where you want to debug your patch by looking at individual oscillators. This is done by setting the wavesview variable to the oscillator you want to investigate:
wavesview = 1
If you want to get back the oscilloscope view, you just set it to wavesview=0 (or remove the line).
In VividShaper, most of the things you do to control the synthesizer will be done through coding, so VividShaper does not have any knobs. However, it does have a few buttons. On the left side, there's a patch menu button that will allow you to load and save patches (either locally or to iCloud). The menu looks like this:
Here is an explanation of all the menu alternatives:
Next to the Patches menu button, you will find a few other buttons:
New: The New button will remove anything you have written and start from the simple patch you see in the image.
-- Patch: New wave[1] = VSSin(1,0) vol[1] = gate
This patch creates a sinewave and sets the volume to 1 if gate is on. The variable gate is either 1 if the key is pressed or 0 if the key is released, so in this very simple example the volume is either on or off.
Parse: The Parse button is pressed when you have coded something new and want to use the new code. You can also reset the synth using the parse button (even if it is the same code).
View: The View button will change the view of the editor and the graph (only editor, only graph view, editor left to graph, editor below graph).
Help: Finally, there is also a Help button. This button loads some text into the editor, telling which version you have and gives you a simple example. If you press help again, it will switch back to your original code that you worked on.
In the first version of VividShaper, all patches were stored in alphabetic order in one huge list. The patch organiser, introduced in v1.2, allows you to tag each patch by adding #tags to your patch name. For instance, the patch Super Moving Saw #pad #supersaw that you find among the factory patches is located both under the #pad category and the supersaw category. You can organise your patches in the same way. The patches you store in iCloud will have their own category system separate from locally stored patches. If you don't add a tag, they will be found under the Unlabelled menu item.
You don't have to create or delete the categories. This is done automatically. As soon as you save a patch with a new hashtag, e.g. Blip #8bit, the organiser will create the category #8bit for you. Once you delete all patches with a given hashtag, the category will be removed.
Let's take a look at a slightly more advanced example of a Lua program:
-- Patch: Triangle #retro wave[1] = VSTriangle(1,0) vol[1] = velocity*VSADSRE(1,1,0.8,3,0,gatetimeon,gatetimeoff) active = vol[1] > 0.000001
On the first row, you see a comment saying – Patch: Triangle #retro. You don't need to add this row, but it will always be added as the first row in the code when you save the patch anyway. The first row always tell the name of the patch (which is equal to the filename). In our example, the name of the patch is Triangle #retro. By giving it the hashtag #retro, we will save it under a new category that we call retro.
In VividShaper, each generator has eight oscillators, where each oscillator plays one wave. A wave consists of 128 samples and the wave[] array consists of eight such wave arrays, one for each oscillator. In other words, wave[1] corresponds to the wave of the first oscillator, wave[2] corresponds to the wave of the second oscillator, and so on. In this example, we will only use one oscillator.
On the second row, we will call the built in function VSTriangle(frequency,phase). This function will create a new wave of 128 samples (array) and store it to the first oscillator. The frequency is set to 1 Hz, meaning it will fill the 128 samples with one cycle of the triangle wave. If we change this to e.g. 2 Hz, we will fill the wave with 2 repeating triangle waves.
The term 'frequency' in this context refers to the number of cycles of the wave that fit into the 128-sample, not the pitch of the note that will be played. For example:
The phase is given in degrees (0-360) and is here set to zero. The 'phase' of 0 means the wave starts at its beginning, while adjusting the phase shifts the start point of the waveform cycle, which can create subtle changes int he sound when combined with other waves.
It is important to grasp these concepts. VSSin, VSSaw, VSTriangle all works the same, with one argument for frequency and one for phase. VSSquare has an additional argument for the width. Here is some additional examples:
On the third row, we set the output volume using the Attack, Decay, Sustain, Release (ADSR) envelope (VSADSRE). This is then multiplied by the velocity of the note. The E stands for Exponential release. If you don't want exponential release but a linear release, you can use VSADSR instead. They take the same arguments. The arguments are as follows:
attack = 1 -- This tells the time it takes to reach the peak volume (in seconds) decay = 1 -- This tells the time it takes to drop from the peak volume to a sustain volume (in seconds) sustain = 0.8 -- This sets the sustain volume (in the range from 0 to 1). release = 3 -- This set the time it takes for the volume to drop to zero after the user released the key. initlevel = 0 -- This tells the initial volume level when the envelope starts, i.e. when they key is pressed. Often set to zero. vol[1] = VSADSRE(attack,decay,sustain,release,initlevel,gatetimeon,gatetimeoff)
The variables gatetimeon and gatetimeoff tells how long time it has been since the key was pressed down (gatetimeon) and released (gatetimeoff). When the key is released, the gatetimeon time freezes at that time point. This means we can sum together gatetimeon+gatetimeoff to get the total time from when the key was pressed down.
The output from VSADSRE is stored to vol[1], which is the volume for the first oscillator. Each of the eight oscillators has its own volume, ranging from vol[1] to vol[8].
The final row will tell if the generator should be active or not:
active = vol[1] > 0.000001
The variable active is a boolean variable that is either true or false. Your generator gets activated when a note has been pressed. The variable active turns true at the very moment when the generator starts. The generator continues to run as long as active == true, but you can turn it off by setting active = false. It is a good idea to always turn off a generator when you know it won't play anything anymore.
In this example, we continue to let the generator be active as long as the volume for the oscillator is above 0.000001. Once the volume goes below, then active == false and the generator will turn itself off (until you press a note again).
When you play this simple patch, you will see the wave on the right side view:
Playing back just one oscillator gives a quite thin sound. If you want to get a more dynamic sound, you can add a second slightly detuned oscillator. Try out this code:
-- Patch: A simple example with two oscillators wave[1] = VSTriangle(1,0) wave[2] = wave[1] vol[1] = VSADSRE(1,1,0.8,3,0,gatetimeon,gatetimeoff) vol[2] = vol[1] note[1] = notein + 0.01 note[2] = notein - 0.01 panning[1] = 0.25 panning[2] = 0.75 gvol = 0.5 active = VSMax(vol) > 0.00001
Here, we have added another wave[2]. We have set it to the same wave form as wave[1] and we have chosen the volume (vol[2]) to be the same volume as for the first oscillator. Then, we can also set what note that should be played for each of the oscillators. The variable notein will tell the MIDI note number corresponds to the note being played. For instance, the MIDI note value 48 corresponds to C-4.
note[x] is an array corresponding to the notes being played for the eight oscillators (x is between 1-8). If we don't set note[x] explicitly, each oscillator will keep the default notein value. Hence, you don't have to set the note[] array if you don't want to change the note.
Given that notein=48, note[1] will be 47.99 and note[2] will be 48.01. Setting these two oscillators slightly detuned will create a beat effect, which you can read more about here:
https://en.wikipedia.org/wiki/Beat_(acoustics)
It is this technique that synthesizers use to create a more fatty sound. For instance, a supersaw sound is created by mixing several detuned sawtooth oscillators.
We can also set the panning of each oscillator, telling them if the sound should be played on the left speaker, the right speaker, or anything in between. This is done by setting the panning[x] array, which again is an array of eight values, one value for each oscillator. A panning value of 0 means left, a panning value of 1 means right, and consequently a panning value of 0.5 means in the middle.
Since we are using two oscillators, it is a good idea to change the global volume gvol variable to 0.5. This is the global volume that VividShaper sends to the DAW and the default value is 1. If we add more than one oscillator, it can be a good idea to lower the global volume. Otherwise, the total volume will be saturated.
Finally, we turn off the generator if both oscillators drop below 0.00001 in volume. The vol variable is an array of 8 elements and VSMax(vol) returns the highest value of the array. This way, we don't turn off the generator until all oscillators are below the chosen threshold.
You may want to play a bit with the threshold value. Setting it very low may seem to be a good idea, but if you use the exponential VSADSRE() envelope, the volume will mathematically speaking never reach zero and it may take a very long time for it to reach your threshold. Similarly, setting it too high (e.g. 0.1) will certainly give you an audible disruption in the sound. You want to turn it off when you really cannot hear it any longer.
This image does not do justice of what is going on, because the waves are changing shape over time. Still, you can tell from the image that the superposition of the two waves makes it look somewhat different from a triangle wave:
By default, the waves view always show the oscilloscope view. In the version prior to v1.2, it was always showing the first wave (wave[1]). You can change this by setting the wavesview variable. For instance, by setting wavesview = 1, you will tell VividShaper to show wave[1] instead of the oscilloscope view. If you set it to wavesview = 3, you will show wave[3].
If you set wavesview = 0, you will get back the oscilloscope view.
You can also change the y-axis scale of the waves view, by setting the scaleview variable. The default value is set to 1, but if you set scaleview to e.g. scaleview=2, you will magnify the wave being plotted without changing the volume.
Finally, there's a text variable that will display anything that you set it to at the bottom of the view. This is great for debugging. In this example, text=gatetimeoff, telling how many seconds it has been since the key was released.
The screenshot is from v1.1 where wave[1] was the default view:
We have so far used the VSTriangle waveform, but VividShaper also comes with a few other waveforms:
-- Wave generators wave[x] = VSSin(frequency,phase) -- Sine-wave. frequency=1 is base, phase is in degrees. wave[x] = VSTriangle(frequency,phase) -- Triangle-wave. wave[x] = VSSaw(frequency,phase) -- Sawtooth-wave. wave[x] = VSSquare(frequency,phase,width) -- Square-wave. Width is between 0 to 1. wave[x] = VSNoise(seed) -- Random generated wave, given the seed integer.
VSSin and VSSaw takes the same arguments as VSTriangle. VSSquare has one extra argument to set the width of the square wave (between 0 to 1). VSNoise generates a random wave given the seed variable, where seed is an integer, e.g. VSNoise(32). If you want the VSNoise to generate new waves every time you call it, you can write VSNoise(tick). The variable tick counts the number of times the Lua code has been called from when it was initiated. Since it is incrementing every time, you will make sure to generate a new random wave.
Version 1.2 introduced LFO functions:
lfo = VSLFOSin(frequency,phase,time) -- phase is in degrees lfo = VSLFOTriangle(frequency,phase,time) lfo = VSLFOSaw(frequency,phase,time) lfo = VSLFOSquare(frequency,phase,width,time) -- width is between 0 to 1
All these LFO functions will oscillate between -1 to 1, so if you want the range 0 to 1 you need to add the LFO by 1 and divide it by 2.
In contrast to the wave functions, the LFO functions outputs a single value (scalar value). The first function, VSLFOSin, is really just the same as writing math.sin(2*math.pi*frequency*time+2*math.pi*phase/360) in Lua, but VSLFOSin is more compact and aligned with the rest of the LFO functions.
If you want a pulsating sound that modulates the volume, you could do something like this
-- Patch: New wave[1] = VSTriangle(1,0) v = VSADSRE(0.3,0.2,0.8,1,0,gatetimeon,gatetimeoff) lfo = VSLFOSquare(tempo/60,0,0.5,gatetimeon+gatetimeoff) vol[1] = v*(lfo+1)/2 active = v > 0.000001
Notice that in this patch, we need to calculate the envelope volume separately first, then we calculate the lfo and multiply it with the volume (after fixing the range between 0 and 1). Finally, we test if the generator should be active based on the envelope volume *v*, not the final volume vol[1].
A Variational Autoencoder (VAE) is a neural network that has been trained to send out the input as output. However, since the network has a middle layer that only consists of a few neurons, all the data that is sent through the network need to pass through this middle layer and therefore the network needs to represent every single input using only a few variables.
The input part of this network (everything before the middle layer) is called the encoder and the output part of the network (everything after the middle layer) is called the decoder. The middle layer is often called the “latent space” and could for instance consists of only two neurons. In other words, this is a kind of a dimensionality reduction. Once the network has been trained, it can be used to generate new output by setting the latent neurons explicitly.
VividShaper has a variational autoencoder that has been trained with over 4000 different waves, where the middle layer consists of two neurons. This means it can represent all these different waves with just two variables. However, it won't be able to replicate the input waves perfectly.
There are two functions that you can use. First, you can use the VSAutoencoder2() function to obtain the latent space variables. The output of these two variables are between -1 to 1, so you can see the output as a 2D map.
saw = VSSaw(1,0) latent = VSAutoencoder2(saw)
Then you can feed in the latent variables to VSAutodecoder2() and generate a new wave given the coordinates in this 2D map.
In this example, we can see how the decoded sawtooth wave looks like and we also obtain the latent space coordinates by printing them. The volume has been set to vol=1 just to allow it to play constantly even when the note has been released. You may want to replace this vol with an ADSR-envelope instead, but it made it easier to make a screenshot to keep the volume on.
wave[1] = VSAutodecoder2(latent)
The screenshot is from v1.1 where wave[1] was the default view:
Using gatetimeon and gatetimeoff variables makes it possible to dynamically change the position of the latent space over time. For instance, you can let it spin around (like a record):
-- Circular movement in latent space local t = gatetimeon + gatetimeoff local radius = 1 -- Radius of circular motion local x = radius * math.cos(t/5) -- Divide time by a factor for slower movement local y = radius * math.sin(t/5) wave[1] = VSAutodecoder2({x,y}) -- Generate waveform from latent space
The functions are called VSAutoencoder2 and VSAutodecoder2 since the latent space is two variables. There may be other auto encoder networks with a latent space having more dimensions in a future update.
The lowpass/bandpass/highpass filters work a little bit different from other apps. Instead of filtering the output from the oscillators, you can filter the wave itself. The wave filter function in VividSynths is a biquad filter. A biquad filter consists of five different coefficients and depending on how you set them you can create either a lowpass, bandpass, or a highpass filter.
wave[x] = VSBiquad(wave[x],biquadcoeff)
It can be quite difficult to calculate how these coefficients should be set, so VividShaper comes with three functions for this purpose:
biquadcoeff = VSLowpass(cutoffFreq,resonance,notein) biquadcoeff = VSBandpass(cutoffFreq,resonance,notein) biquadcoeff = VSHighpass(cutoffFreq,resonance,notein)
A very important thing is that you need to provide each of these helper functions with the current note. If you play a note at a low frequency, you may actually play under the cutoffFreq so that no filter should be applied at all for a lowpass filter. That means the filter effect on the wave depends not only on the cutoff frequency and the resonance, but also on the playback frequency (i.e. the note).
A full example comes here:
-- Patch: Filter example wave[1] = VSSaw(1,0) vol[1] = VSADSRE(1,1,0.8,3,0,gatetimeon,gatetimeoff) biquadcoeff = VSLowpass(300,1,notein) wave[1] = VSBiquad(wave[1],biquadcoeff)
Setting the frequency as a function of time since gate on may give you some interesting sweeping filter effects.
VividShaper comes with two different sorts of waveform manipulators. The first one is a wavefold effect. If the amplitude is e.g. 1.2, it is going over the limit with 0.2. The wavefold effect will then fold the waveform down again with the amount it was exceeding the limit: 1.0-0.2 = 0.8. This can result in some interesting harmonics.
This is how the wavefold function is called:
wave[1] = VSWavefold(wave[1],1.5)
The 1.5 coefficient means the wave should be multiplied with 1.5 first, then folded.
Another important function is a wave normalisation function. If the wave reaches above a given threshold (that we define), we can normalise the wave to a new value:
threshold = 1.0 normvalue = 1.0 wave[1] = VSNorm(wave[1],threshold,normvalue)
If the max amplitude of the wave is 1.5, this will divide the wave with 1.5 so that the new max amplitude is 1.0. However, if the max amplitude is below 1.0, it won't do anything. If we always want the wave to normalise to 1.0, even if the amplitude is lower, we can set the threshold to 0:
threshold = 0.0 normvalue = 1.0 wave[1] = VSNorm(wave[1],threshold,normvalue)
Besides all the built in math functions that come with Lua, VividShaper also comes with some wave manipulating wave functions that you may find useful. These are:
-- Wave math operators - arguments can be either arrays or scalar factors wave[x] = VSMul(wave1,wave2) -- Multiply element wise wave1 with wave2 wave[x] = VSMul(wave1,1.5) -- Multiply element wise wave1 with 1.5 wave[x] = VSDiv(1,5,wave2) -- Divide element wise 1.5 with wave2 wave[x] = VSAdd(wave1,wave2) -- Add element wise wave1 with wave2 wave[x] = VSSub(wave1,wave2) -- Subtract element wise wave1 with wave2
With these functions, you can either e.g. multiply a wave with a scalar value or a wave with another wave. These are very useful functions if you want to create an additive synth:
wave1 = VSSin(1,0) wave1 = VSMul(wave1,0.7) wave2 = VSSin(2,0) wave2 = VSMul(wave2,0.3) wave[1] = VSAdd(wave1,wave2)
Introduced in v1.2, you can now let one oscillator either ring or sync modulate another oscillator:
ring[x]=y -- Oscillator x should be ring moduled by oscillator y sync[x]=y -- Oscillator x should be synced by oscillator y
Ring modulation means one oscillator is multiplied by another oscillator (the modulator) and the output from this oscillator is the multiplication of the two. Sync modulation means that one oscillator is restarting from zero whenever another oscillator (the modulator) restarts from zero.
An important rule here is that y<x for both. This means that oscillator 1 can ring or sync module oscillator 2, but not the other way around. The reason for this is because the generator calculates oscillator 1 first, then oscillator 2. Here is an example from the factory patches:
-- Patch: Ringing #drone #new wave[1] = VSSaw(1,0) wave[2] = VSTriangle(1,0) vol[2] = VSADSRE(1.0,0.5,0.8,2,0,gatetimeon,gatetimeoff) vol[1] = VSADSRE(0.2,0.5,0.3,1,0,gatetimeon,gatetimeoff) ring[2] = 1 note[2] = notein-24 note[1] = note[2]-1 gvol = 0.7 panning[1] = 0.3 panning[2] = 0.7 oscnote = note[1] active = VSMax(vol) > 0.00001
In this example, oscillator 2 is ring modulated by oscillator 1. Sometimes, you want to mute the modulating oscillator, but in this example we keep both oscillators playing: notice also that we change the frequency of the note for one of the oscillators. This is quite important, because otherwise it won't give you much of a time dependent modulation (then you could just multiply the two waves using VSMul()).
Running all the eight generators may be CPU intensive but there are ways to optimise it. You can either choose to decrease the number of generators in use, by setting the variable generators to a value between 1 and 8.
generators = 1 -- This sets VividShaper to become a mono synth
You can also tell VividSynths how often it should run the Lua code. The default value is that the synth engine runs the Lua code after 512 samples have been played back. If the playback frequency is 48000 Hz, the Lua code will be executed 48000/512=93.75 times per second. That is a lot and sometimes not necessary. You can decrease this to e.g. running the Lua code after 1024 samples instead, changing the number of times it is executed to 46.875 times per second. This is done by setting the updatefreq variable:
updatefreq = 1024
You can also increase the speed. This variable can be set to the following values: 128, 256, 512, 1024, 2048, and 4096.
However, setting it to 128 means the Lua code is called 48000/128=375 times per second. That will put some demands on your CPU so test this carefully.
Sometimes, it is very useful to obtain CC-messages so that you can manipulate the sound in realtime using an external MIDI controller. The array cc[x] gives you at each update the current values of all CC messages except CC message 0 which is not used. Hence, cc[1] gives you the message for CC 1, cc[20] gives you the message for CC 20, etc. The values you get are between 0 and 127. If you prefer to get them normalised between 0 and 1, you can use the array ncc[x] instead.
Version 1.2 introduces eight AUv3 parameters, that you read from the par[x] array where x is from 1 to 8. In your DAW, these parameters will be called P1 to P8 and have the range from 0 to 1 (just like ncc[x]).
The variable velocity tells the current velocity of the note being played (from 0 to 1). By multiplying the volume output with velocity, you can make your patch velocity sensitive.
The variable tempo tells you the current tempo from the DAW, e.g. 120. A corresponding variable is beatpos for beat position.
Finally, the array prevvol[x] tells you the volumes of each oscillator when the gate flipped from off to on again. This can be useful information if you are making a monophonic synthesizer (generators=1) and want to start the ADSR envelopes initial volumes to the volume when the gate was turned on:
generators = 1 wave[1] = VSTriangle(1,0) attack = 1 decay = 1 sustain = 0.8 release = 3 initlevel = prevvol[1] vol[1] = VSADSR(attack, decay, sustain, release, initlevel, gatetimeon, gatetimeoff)
MIDI output is a new experimental feature in v1.2. If you add an instance of VividShaper as MIDI effect plugin, only one generator will run (for now at least - this may change) and you will not be able to play any sound but you can instead send MIDI.
VSMIDI(command,data1,data2) -- Send MIDI data. (Next version) VSMIDINote(channel,note,velocity,length) -- Play a given note on the given channel. length=1/4 means a quarter note, (Next version) VSTick(prevbeatpos,beatpos,length) -- Returns true for every new time interval with the given length. (Next version)
The first function VSMIDI() is allowing you to send any kind of MIDI data, notes or CC messages. If you use it to play notes, you need to take of also turning them off. How MIDI messages are constructed is not covered here, but there are plenty of MIDI documentation that describe this.
If you prefer to let VividShaper handle this, you can use VSMIDINote() instead. Here, you will only need to tell that you want to play a certain note on a specific channel with a given velocity and length.
For instance, VSMIDINote(0,36,127,1/4) will play C-3 (note=36 corresponds to C-3) at maximum velocity (127) as a quarter note. Note that 1/4 is the gate time. Sometimes, you may want the gate time to be somewhat shorter, in which case you could write: VSMIDINote(0,36,127,1/4-1/4*0.1). This would give you 90% gate time of a quarter note (we subtracted 10%).
The final function is a pretty useful function not only for MIDI but for other things as well. It will return a true/false value for every time interval. You can think of it as a metronome returning “ticks”. For instance:
tick = VSTick(prevbeatpos,beatpos,1/4)
would give tick==true four times per bar (one time per quarter note).
These functions together makes it possible to make simple sequencers, where one VividShaper instance can control any AUv3 plugin or even real hardware.
Here is a longer example that you can try in Logic. Load a drum kit as instrument and VividShaper as MIDI effect.
-- Patch: MIDItest #midi tick = VSTick(prevbeatpos,beatpos,1/16) step = math.floor(beatpos*4) bd = {1,0,0,0,0,0,1,0} sn = {0,0,0,0,1,0,0,0} hh = {1} bdindex = step % #bd snindex = step % #sn hhindex = step % #hh if tick == true then if bd[bdindex+1] == 1 then VSMIDINote(0,36,127,1/16) end if sn[bdindex+1] == 1 then VSMIDINote(0,36+4,127,1/16) end if hh[hhindex+1] == 1 then VSMIDINote(0,36+6,127,1/16) end end
Here is a short explanation: