Wave Runner Demo Details

Got a programming project in mind? Tell everyone about it!
Post Reply
VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:33 pm

Introduction

In a similar manner to Kieran's Twisted Brain write-up from last year, I'm going to describe how Wave Runner works under the hood, as well as providing some details about its genesis and how it all came together.

The demo itself can be found here: https://bitshifters.github.io/posts/pro ... unner.html

I'm hoping to get all the parts written over the next two weeks, time permitting. I'll block out a number of posts for the stuff I want to talk about, and fill them in as and when they're ready. Several demo effects (as well as a lot of the build system and all kinds of other things!) were done by Tom Seddon and with luck he'll have time to describe his work, so I'll block out parts for those as well.

Rough plan is to do a high-level introduction to the demo framework, talk a bit about Stable Raster, NOP Slides and Clockslides, then move onto a description of each of the effects, and finish up with some closing thoughts.

So, without further ado... Let's start with a Framework Overview.
Last edited by VectorEyes on Wed Jul 17, 2019 2:33 pm, edited 1 time in total.

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:34 pm

The Demo Framework

Wave Runner is heavily influenced by, and shares some code with, Twisted Brain (hereafter known as 'TB') There will be places in this write-up where it's easier to refer to TB and describe how Wave Runner is different than to describe how Wave Runner works in detail.

Similarities to TB include:

- Both demos have a 'Render' function which runs while the Video ULA is scanning out the visible portion of the frame, followed by a number of 'Update' functions which do music decompression and playback, run 'Update' code for the current effect, and do 'Scripting' (deciding what other code to run each frame).
- They music playback is very similar. Exomizer Decompression to decompress up to 11 bytes per frame, that are sent to the SN chip immediately after the 'Render' function completes.

Significant differences include:

- Wave Runner uses fully Stable Raster (cycle-accurate timing with respect to the Video ULA output). It achieves this by use of a NOP slide, of which more later.
- The 'Effect Render' function starts approximately 192 cycles (ie 1.5 scanlines) *before* the start of the visible frame. This is to allow the render function to do any 'preparation' necessary before the effects starts rendering.
- Wave Runner runs with interrupts enabled (although only the System Via Timer1 is enabled). This has some positive ramifications as described shortly.
- TB used Exomizer 'streaming' compression to decrunch the music stream, and PuCrunch to decompress images and data. WR uses two separate Exomizer decompressors, one in 'Streaming' mode (for the music) and one in 'Targetted' mode (for all other decompression).
- WR has the ability to run code 'in the background'. It is interrupted once per frame to run the entirety of the Render/Update loop, but then returns to a loop which can be doing useful stuff like Exomizer decompression or clearing the screen, that runs until the T1 interrupt triggers the next Render/Update loop.
- The music player was heavily optimised for the Master 128's 65C02 by HexWab.


An overview of the Framework

The demo is split into several systems:

- The main 'Render/Update loop': Triggered once per frame, just before the Video ULA starts scanning out the visible frame. Responsible for calling the current effect's Render and Update functions, as well as ticking all the other systems.

- The 'Background Processing' loop: Runs all the time except when interrupted by the Render/Update loop. Responsible for Targetted Exomizer decompression and screen clearing.

- The 'Effect System': Maintains a big table of render/update/startup/shutdown functions for each effect, and is responsible for calling them appropriately to transition between effects. Also manages Sideways RAM banks and Shadow/Main memory state for each effect.

- The 'Task System': Runs up to 6 additional functions per frame. Each task has access to a small block of data containing its arguments. The system can run tasks for a specified number of frames, or until the task function marks itself as complete.

- The 'Timeline'. This reads a stream of bytes in memory and interprets it as instructions such as 'Wait for 60 frames then spawn this task' or 'Wait until the current decrunch has completed and then kick off another decrunch', etc. Timeline points can be relative to the start of the demo, the start of the effect, the last timeline point, or can wait for various 'flags' to be set. Each Effect has its own timeline and some have several timelines used at different points.

- The 'VGM Player'. Decrunches bytes of music data and sends them to the sound chip.


Memory map

&0000 - &00FF : Zero page. All kinds of stuff that is referred to frequently by the code, e.g. timers tracking how long it's been since the start of the demo, the current effect, and the last 'timeline point', 32 bytes 'effect workspace' that each effect can use for whatever it likes, small buffers needed for the Exomizer decompressers, etc.

&0100 - &01FF : 6502 Stack, but also contains an 156-byte table used by Targetted Exo Decompressor.

&0300 - &0FFF : 3328 byte buffer used by streaming Exo3 decompressor (for music).

&1000 - &1FFF : All the demo framework code, plus several tables of sine values at various amplitudes.

&2000 - &2FFF : 'Effect workspace'. Each effect is free to put whatever code or data it wants here.

&3000 - &7FFF : Screen memory. (The demo runs in a mixture of MODE1 and MODE2, both of which require the full 20k). The demo will often display 'Main' memory while writing a new image to 'Shadow' or vice versa.

&8000 - &BFFF : Sideways RAM banks x 4. Three banks contain the code and data for all the effects, plus the Exo-compressed images. The fourth bank contains the first 16k of the compressed music.

&C000 - &DFFF : HAZEL, which contains the rest of the compressed music, and right at the end an another 156-byte workspace used by the Streaming Exo decompressor.

&E000 - &FFFF : OS ROM, interrupt handling routines etc.


Notes on memory map:

Exomizer provides a trade-off between the amount of 'workspace' needed at runtime and the compression ratio. By specifying a larger workspace during the compression step, you can reduce the size of the compressed data. For the music ("Synergy Main Menu" by Scavenger) we were lucky in that using a workspace size of 3328 bytes compresses the music data into 24411 bytes. This fits into one SWR Bank plus most of HAZEL, leaving space for an additional 156 bytes right at the end of HAZEL (used for another small Exo-based workspace) with just 9 bytes free! The 3328-byte workspace fits between &200 and the demo framework code at &1000.

ANDY is not used. It's reserved for future demos when we really start to run out of space. :)

Similarly to TB, we keep HAZEL active all the time (the demo never uses the OS VDU routines and keeps that part of the OS ROM paged out) and the streaming music decompressor runs down through SWR bank 3 and straight into HAZEL.


The Render/Update loop

Here's what happens in the IRQ Handler that's triggered by System Via Timer1. (Note many details omitted for clarity!):

- (Housekeeping code that caches X and Y so we can return from the IRQ properly. A is already cached in &FC.)
- Correct for interrupt jitter to achieve stable raster (see section on NOP slides).
- Set up SWR and main/shadow state for the current effect.
- Run 'Render' function for current effect.
- Run music player.
- Tick the Timeline System. (This may lead to a transition to the next effect, because all effect transitions are triggered by the effect timelines).
- Tick the Task System, which will tick all active Tasks.
- Run 'Update' function for current effect.
- Deliberately waste several scanline's worth of cycles. Reserving cycles gives us a crude measure of how close to 'CPU capacity' the demo is.
- (Update the various counters that increment once per frame).
- Restore Shadow/Main state and SWR bank to those needed for the Background Processing.
- Restore X, Y and A, and RTI.

The Background Processing loop continuously does the following:

- Check if the "Clear Screen Requested" flag is non-zero. If so, jump to the code that handles screen-clearing.
- Check if the "Exomizer Decrunch Requested" flag is non-zero. If so, jump to the code that does Exo decompression.

Overall, the system is designed to let you run timing-critical rendering code syncred to the raster beam, but to also run code 'once per frame at some point' or 'in the background as fast as possible'.

This diagram correlates when the different bits of the framework are running with the CRTC cycle:
Timings2.png
Last edited by VectorEyes on Wed Jul 17, 2019 2:34 pm, edited 3 times in total.

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:34 pm

NOP Slides and Clockslides

In several places the demo needs to delay by an exact number of cycles, but the cycle count is continually changing and only known at runtime. The techniques necessary to do this are out there on the Web (e.g. https://www.pagetable.com/?p=669) but for those who are interested and/or haven't seen them before I'll go over them briefly.

NOP Slides: When you need to delay by 2N cycles.

The Wave Runner code synchronises itself to the vertical sync interrupt using techniques already described in the Twisted Brain writeup. This gets you an IRQ handler that is called every frame at a known offset from the vsync, but with a few cycles of jitter. (This jitter is caused by several things, most notably the fact that when an interrupt fires, the CPU must wait for the current instruction to finish -- which could take between 1 and 7 cycles -- before servicing the interrupt. Combined with other effects such as cycle stretching when reading the VIAs, in effect you can have up to 8 cycles of jitter.)

To correct for this, you do the following:
  • Read Timer1 Low.
  • Extract the lowest 3 bits and invert them. (This gives a value from 0 to 7 where 0 means 'Timer value was large, so correct with a long delay' and 7 means 'Timer value was small, so correct with a short delay'. Remember the counter is counting down, not up!).
  • Write the value into the second byte of a Branch instruction, ie the branch offset.
  • Branch into a series of repeating NOPs.
The code that does this in Wave Runner looks like this:

Code: Select all

.aboutToReadT1
lda sysViaStart + viaReg_T1CounterLow \read T1L, clear interrupt, also sync to 1MHz due to cycle stretching
.t1lInAReadyToSlide
; Extract lowest 3 bits, use result to control a NOP slide. This corrects for timer jitter and provides stable raster.
and #7
eor #7
sta branch+1
.branch
bpl branch \always
.slide
; Note: this slide delays (CPU cycles) by TWICE the 'input' to the slide, which is
; what we want because the T1 counter is 1MHz, but the CPU runs at 2MHz.
nop:nop:nop:nop
nop:nop:cmp &3
Because the 1MHz VIA timers operate at half the speed of the CPU, and NOPS take two cycles, this has the effect of introducing a delay which exactly counteracts the jitter.

Credit goes to Hexwab for detailing this technique (in much more detail!) here.

(At this point I have to admit that I have no idea why I put a CMP &3 at the end. It's an easy way to use 3 cycles instead of two, and I suspect it was because at some point I needed to delay for an extra cycle. It might look like I've missed one NOP -- there are only 6 NOPS, but the branch values range from 0 to 7 -- so the code might branch to the "&3" byte of the final CMP, and treat it as an instruction. But on the 65C02, opcode 03 is a one-cycle NOP, which means the jitter correction still works!)

One detail that the original article mentions, but which took me ages to appreciate the importance of: the number of cycles between the interrupt firing and reading Timer1 Low is crucial. You need to carefully set up the code so that the Timer1 read is at just the right point within an 8-cycle repeating loop.

So when you want to delay by 2N cycles, use a NOP slide. But what if you want to delay in 1-cycle increments, instead of two?

ClockSlides: When you need to delay by N (+ constant)

The concept of a clockslide is similar to a NOP slide, but by changing the 'control' value you can change how many cycles to waste at one-cycle granularity.

Here's a clockslide that expects a value between 0 and 13 in A, and introduces a delay of between 15 and 2 cycles (not including the cycles for the STA and the BRA):

Code: Select all

STA slide+1
.slide
BRA slide
cmp #&C9 : cmp #&C9 : cmp #&C9 : cmp #&C9 : cmp #&C9 : cmp #&C9 : cmp &EA
The way this works is as follows:
  • If A is 0, it executes 6 x "CMP #&C9" (CMP immediate, 12 cycles) plus one "CMP &EA" (CMP zero-page, 3 cycles), total: 15
  • If A is 1, it branches to the second (comparison value) byte of the first CMP... which is &C9... which is the opcode for CMP immediate! So it executes 6 x "CMP #&C9" again (12 cycles), but this time at the end, it treats the "&EA" as an instruction which is... NOP (2 cycles). Total: 14.
  • If A is 2, it branches two bytes forward, executes 5 x "CMP #&C9" (10 cycles) plus the final "CMP &EA" (3 cycles). Total: 13.
    ... and the pattern repeats all the way down to:
  • If A is 13, it branches straight to the final &EA (NOP) : 2 cycles.
By changing the number of NOPS, you can introduce variable delays up to the limit of the branch instruction.

Interestingly, I started using these techniques before I became aware of the 1-cycle NOPS provided by the 65C02. I think there may be some interesting possibilities for using NOP1s in these 'slide' techniques that have yet to be explored.
Last edited by VectorEyes on Fri Jul 12, 2019 12:13 am, edited 1 time in total.

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:34 pm

Double Sine Wave Effect

Introduction

This effect uses stable raster to render a superposition of two sine waves. Each wave can have its left/right movement speed and vertical scale adjusted independently, and by choosing values carefully a variety of interesting patterns can be created.

When the effect starts, the whole screen is filled with the value &F, and the ULA is set to MODE1. This means that by changing the palette register for just one entry (the one that maps logical colour %1111 to a physical colour) you can change the black 'background' colour. As the effect progresses, various images are decrunched to the screen, but the images are all set up so the right hand side (where the wave effect takes place) side stays filled with &F, and all of the palette changes that alter the look of the images (which only appear on the left) leave logical colour &F set to black.

The upshot is that you can draw an animated wave using all 8 colours on the right of the screen, while displaying any MODE1 image on the left (as long as the image has a black background!)

The effect uses a 256-entry sine table whose values vary between 0 and 14. During frame update, 16-bit additions are performed to step two pointers 'through' the table, to provide new 'start values' for the two waves. To draw the 'final' wave at frame render, the two waves start at the 'start value' and for each scanline, they step through the sine table (16-bit addition again) and take the high byte of the result as an index into the table. Two sine values (varying between the values 0 and 14) are thus retrieved from the table, and summed together (giving a possible range of 0-28).

This value between 0 and 28 is used to select one of 29 hand-crafted functions. Each of these functions essentially does:
  • Wait(first)
  • Write to palette register to change logical colour &F to a colour. (See below for how the colour is chosen!)
  • Wait(second)
  • Write to palette to change colour &F back to black.
... where wait(first) and wait(second) always sum to the same value.

For example, the firs pattern in the effect is composed of this sine wave...
Vid1Scaled.gif
Vid1Scaled.gif (95.02 KiB) Viewed 92 times
... added to this sine wave...
Vid2Scaled.gif
Vid2Scaled.gif (236.99 KiB) Viewed 92 times
... to give this result:
Vid12CombScaled.gif
Vid12CombScaled.gif (343.44 KiB) Viewed 92 times

Adding Colour

However, there is an additional complication. The effect was originally monochrome (black/white). This meant that to achieve the "wait(delay)/write palette/wait(inversed delay)/write palette" behaviour, all you needed was two clockslides with the 'set to white' in between and the 'set to black' at the end.

But when I added colour, I used the tried-and-tested '16-bit add, then use high byte as an index' technique to grab colour values from another 256-entry table. By choosing different step speeds, it is possible to create different colour movement patterns. All of the moving colours in the sine wave are generated from the same colour table, arranged something like this:
CopperColours.PNG
This meant that in addition to the above, the code is also doing (per line):
  • 16-bit add to step through the colour table.
  • Use high byte to index into colour table and retrieve palette entry.
... and the code that does this is interleaved among the 'wait' and 'write palette' instructions. That is why there are 29 different functions. Each one does the same thing, but the order and timing of operations changes for each one to ensure the two palette writes are at the right time.

For instance, here's the function that swaps the palette as early as possible, ie '0 cycles of delay':

Code: Select all

.delay_0
    NOP
    ; First part of a 16-bit add: low byte of (colour index per line + colour scale)
    LDA sineEffects_ColourIndexPerLineLow ; 3
    ADC sineEffects_ColourScaleLow ; 3
    STA sineEffects_ColourIndexPerLineLow ; 3

    ; At this point, we've added the low byte, we have carry flag set appropriately... so we can load the 'current'
    ; high byte, store it to palette reg, and then get on with adding the high addend to it.
    LDX sineEffects_ColourIndexPerLineHigh ; 3
    LDA colourTable,X    ;4

    ; Additional wait before store to palette register.
    WAIT_16
    STA &FE21

    ; then need another 17 cycles before the store of black colour (ie 15 before the LDA #im (black colour))
    TXA ; 2 -- put index-per-line-high back into A
    ADC sineEffects_ColourScaleHigh ; 3
    STA sineEffects_ColourIndexPerLineHigh ; 3
    WAIT_3
    lda #mainColToBlack \ 2
    sta &FE21 \ 4
    JMP thinSinReturn


And here's the one that swaps as late as possible (28 cycles later, compared to delay_0):

Code: Select all

.delay_28
    LDX sineEffects_ColourIndexPerLineHigh ; 3
    LDA colourTable,X    ;4
    STA &FE21

    NOP ; 2

    LDA sineEffects_ColourIndexPerLineLow ; 3
    ADC sineEffects_ColourScaleLow ; 3
    STA sineEffects_ColourIndexPerLineLow ; 3

    TXA ; 2 -- put index-per-line-low back into A
    ADC sineEffects_ColourScaleHigh ; 3
    STA sineEffects_ColourIndexPerLineHigh ; 3
    WAIT_19
    lda #mainColToBlack \ 2
    sta &FE21 \ 4
    JMP thinSinReturn
(The WAIT_XX macros insert a series of NOPs plus possibly an additional 1-cycle NOP to achieve the desired wait time).

(As an aside... I think it would be interesting to explore dynamically generating this sort of code at runtime instead of creating it by hand!)

Here's another example of how adding two simple sine waves gives an interesting effect, this time with added colour. This wave (note it's moving, just very slowly):...
Vid5Scaled.gif
Vid5Scaled.gif (742.32 KiB) Viewed 92 times
... plus this one (which is almost the same, just a bit faster and with a very slightly different scale):...
Vid6Scaled.gif
Vid6Scaled.gif (552.75 KiB) Viewed 92 times
... combines to form this result:
Vid56CombScaled.gif
Vid56CombScaled.gif (803.04 KiB) Viewed 92 times

All of this, of course, has to run in exactly 128 cycles per scanline! In actual fact there are some cycles spare, because the WAIT_XX macros are 'dead' cycles that could be put to use somehow. I considered various possibilities but didn't have time to try them out.

Fading up and down

The 'fade waves up down' effect (which is used to change between patterns) is done by patching the code that loads
from the sine table to refer to a variety of different tables which were pre-generated for different amplitudes. Essentially the effect render code is redirected to a variety of different sine tables over the course of a few seconds, to fade the amplitude down from 14 to 0, then swap the values that control the wave pattern to new values, then interpolate the amplitude back from 0 to 14.

Fading colours in/out

The initial fade from white to coloured, and the final fade from coloured to black, is done by spawning tasks which copy values from predefined tables of colours (palette entries) to the 'actual' colour table. The indices to copy each frame are chosen from a table of random numbers (the numbers 0-255 in random order) which is how we get the nice random-looking 'fade in' and the 'fade out' at the end.
Last edited by VectorEyes on Tue Jul 16, 2019 11:21 pm, edited 1 time in total.

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:35 pm

Logo Dissolve Effect

Overview

This effect -- the first one in the demo -- is based on vertical rupture, not stable raster. The effect reprograms the CRTC each scanline to choose which line of the logo to render (and also changes the palette to control the logo colour) but it does not 'draw' images by palette-swapping.

As is common with vrup-based techniques, the 'source' image is very different to the image rendered on-screen. In this case, the source image data consists of each unique line from the Bitshifters logo, repeated eight times. The eight-line offset is necessary because the CRTC can only address lines whose addresses start at an eight-byte alignment. (When setting CRTC addresses, you divide the 'actual' address by eight). In actual fact, because we reset the CRTC start address each scanline, only the first line of every eight is ever displayed on screen, and seven out of every eight lines could be set to anything at all without the effect looking different.

The 'unique lines' image was generated from the original Bitshifters logo, using a C# command-line tool written specifically for the task. I extracted one 'Bitshifters' from the four in the original image, and ran it through the tool.

The original logo looks like this:
bslogo_single.png
bslogo_single.png (1.25 KiB) Viewed 5 times

And the new image looks like this:
bslogo_unique_annotated.png
bslogo_unique_annotated.png (1.97 KiB) Viewed 5 times

(I added the green lines to delineate each unique line. As you can see there are only 13 different lines, including the blank line).

The tool also emits a list of line indices. For each of the 56 lines in the single logo image, it lists the corresponding index in the 'unique lines' image, in a format easily ingestible by BeebAsm, specifically something like this (I added the comments manually!):

Code: Select all

EQUB 2		; First line of logo -- top of b, i, t,     h, i, f, t -- maps to line 2 in the unique lines image
EQUB 2		; Ditto
EQUB 0		; Third line of logo is totally blank
EQUB 2		; Another three lines like the first and second...
EQUB 2		; ...
EQUB 2		; ...
EQUB 0		; And another blank line
EQUB 1		; Now we're onto a different line. Top of b,    t,    h,       f, t -- maps to line 1 in the unique lines image
EQUB 1		; etc
EQUB 0
EQUB 11
EQUB 11
EQUB 0
(And so on for 56 entries!)
This file is used to create a 256-entry table where each entry is the 'unique line index' (between 0 and 12) to use to render that line. This is done by including the file four times, with some 'EQUB 0s' (blank lines) in between and at the top and bottom.

Another 256-entry table contains the colour to use for each line.

Effect rendering

It's interesting to compare TB's effect to this one. Both of them use one-line vertical rupture to choose, per-scanline, which line from an image to draw. (One-line vertical rupture is covered extensively in the Twisted Brain write-up). However Wave Runner 'thins out' the logo vertically, as compared to TB's horizontal movement. The TB version stores two copies of the whole logo, one with a two-pixel offset, and it uses these to move the effect horizontally in two-pixel increments. WR on the other hand stores one 'processed' copy of the logo (each unique line appears only once) and 'moves' them vertically.

This vertical 'splitting' is achieved relatively simply. Before the first visible scanline, we initialise a 16-bit variable (the 'current line pointer') with an initial value. Each scanline, another 16-bit value, the 'per-line offset', is added to the 'current line pointer'. The following logic then happens:
  • If the addition involved a carry from the low to high byte, then draw a line from the logo:
    - Take high byte of 'current line pointer' and use it as index into the 256-entry table of unique line indices.
    - Take that unique line index and use it to look up into another table of CRTC start addresses. This table contains the start address of each line in the image.
    - Set CRTC start address to that address.
    - Also use the high-byte of the 'current line pointer' to look up the colour from the 256-entry table of colours.
    - Set the palette (by writing to the ULA palette register four times).
  • If, however, the addition did NOT involve a carry from low to high byte, draw a blank line:
    - Exactly the same logic as above, but force the unique line index to 0, which is the 'blank' line. (Note how the top row of the processed image is a totally empty line).
Essentially, what this is doing is stepping 'through' the logo by a fractional number of lines for each scanline, but only drawing a logo line when you step to a 'new' line.

Animating the logo to split up is then a simple matter of spawning tasks that interpolate the 'per-line offset' between different values to make the logo expand, contract and then expand again.

There are many ways this effect could be extended but, as with just about everything else, there wasn't time to try them all out! My biggest regret is that the logo expands downwards instead of in both directions. All the infrastructure is in place to do it (all you need is to interpolate the 'starting value' as you change the per-line offset) but, once again, not time to try it out! Perhaps next year...
Last edited by VectorEyes on Wed Jul 17, 2019 11:46 pm, edited 1 time in total.

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:35 pm

(Placeholder: Intro and Outro Images)

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:35 pm

(Placeholder: Vertical Scolltext Effect)

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:35 pm

(Placeholder: 'Blobs' Effect)

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:36 pm

(Placeholder: Chequerboard Effect)

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:36 pm

(Placeholder: Closing thoughts and Miscellany)

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 09, 2019 11:37 pm

(Placeholder: Anything else I forgot!)

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Tue Jul 16, 2019 11:26 pm

Just a note to say that I have added the sections on 'NOP Slides and Clockslides' and the 'Double Sine Wave' effect. Work commitments mean I can't write these up as quickly as I'd like, but I'm very happy to answer questions or discuss areas where people want more detail, and I'm sure everyone else who worked on it would be happy to chip in with replies as well!

Phlamethrower
Posts: 103
Joined: Fri Nov 24, 2017 1:35 pm
Contact:

Re: Wave Runner Demo Details

Post by Phlamethrower » Wed Jul 17, 2019 10:23 am

A link to the demo would be useful.

Nice use of animated GIFs :D

VectorEyes
Posts: 231
Joined: Fri Apr 13, 2018 1:48 pm
Contact:

Re: Wave Runner Demo Details

Post by VectorEyes » Wed Jul 17, 2019 2:38 pm

Phlamethrower wrote:
Wed Jul 17, 2019 10:23 am
A link to the demo would be useful.

Nice use of animated GIFs :D
Thanks, and good point! I'm sure my notes for the first section had a link but apparently not. I've amended the first section and added the link.

The animated GIFs were created by exporting a video from B2 and then running it through FFMPEG. It was a surprisingly easy process.

Post Reply