In this tutorial, we will go through the actual setup of the demo. The source file mentioned in this text is located in “C64/DemoMaker/demomaker_part2_empty.ras“, and since it doesn’t include any scenes, it does absolutely nothing but playing music.
Note: The whole structure explained here is somewhat technical. The idea is simple, but can be hard to grasp at first. However, if you want to make a demo, you don’t really *need* to understand all the inner workings, that is the whole point. The main things you will have to understand are the following:
- That scenes are organized through the timeStamps array
- That every scene must contain a
- Scene raster that performs actions / calculates the effect etc
- Scene initialization routine that performs pre-scene stuff (decrunching, setting modes, clearing screens etc)
- That when you create a new scene, you must yourself add the scene raster to the “wait raster” routine, and the scene initialization to the “main routine”.
All the technical methods described in this tutorial part is hidden in a different file – and the only thing the “user” should be concerned with is the timeStamp list and initializing scene raster & data.
The main file we compile will contain all @preprocess definitions and variabels used throughout our demo. The idea is simple: Keep all variables that define the demo (like color, effects, timing, texts) together, so in the end you will only be “directing” the effects and stitching them together.
At the start, we have a couple of debug defines: turn music on/off, use kernal (should be 0) and a “debug_raster” flag for automatic debugging of raster times (by calling this procedure before / after an effect is calculated, you can see how much raster time has passed. Turn on/off by using the specified debug raster key)
Next, we have some user blocks (used for displaying blocks in the memory analyzer etc), and finally we include the main demo music, here courtesy of Uctumi.
program DemoMaker; var @define play_music 1 // Turn off kernalr @define useKernal 0 // Turn on/off raster debug @define debug_raster 1 // Key for turning on / off rasters @define rasterDebugKey "KEY_R" /* Pointers must be declared outside of blocks */ p1,zp1,p2,zp2,textPointer: pointer; @ifdef debug_raster show_raster: byte=0; @endif // Specify user data @userdata "$0400" "$0800" "Bank 1 video temp storage" @userdata "$4400" "$4800" "Bank 2 video" @userdata "$8400" "$8800" "Bank 3 video" // Main demo music (by Uctumi) music: incsid("music/courierb0.sid","2");
Since the demo might be large, and we wish to keep the code block as small as possible, we move all variables to another position in memory, here at $F000.
// Put all these variables at $F000 @startblock $F000 "Variables" // Standard variables a,b,c,i,j,k,l,m,n,o : byte; // Points to next scene (@scene1, @scene2 etc) nextScene: byte=0; // 0 if scene is not yet initialized, 1 if initialized initComplete:byte=0; // Current row in the timeStamps table currentPart: byte = 0; // 1 if transition mode is entered (and scene is nearing completion) transitionMode : byte=0; // keep01 contains the memory config, and is used when decrunching from $D000 etc // Currentconfig is a temp variable that is used for storing memory config $01 during wait // rater, as the raster might trigger in the middle of a decrunch keep01, currentConfig : byte; // time, 0.5*time and 0.25*time time, time2, time4 : byte=0; // Some standard color themes colorsBluePurple: array  of byte = (11,6,12,12,4,14,15,1,1,15,14,4,12,12,6,11); colorsGreen : array  of byte = (11,11,5,12,13,13,15,7,7,15,13,13,12,5,11,11); /* *** * ** * C E N E S *** */ // Set up scenes @define scene1 1 @define scene2 2 @define scene3 3 // Timestamps decide order of scenes (column 0) and how long they should last (column 1) // Column 2 and 3 are purely optional for extra data (like, displaying same scene multiple times with various // colors or character sets) timeStamps: array of byte = ( @scene1, $7, 0,0, @scene2, $10,0,0, @scene3, $23,0,0, @scene1, $40,1,0 ); @endblock
In this variable block, all the important variables for maintaining the demo is presented. Some explanations:
- Each scene in the the demo (we will have 3, even through they aren’t defined yet) are defined as constant values (@scene2 = 2 etc)
- The order and timing of the scene is defined by the timeStamps array, where the 4 column in each row are as follows: [scene index], [time when scene changes to next], [user data], [user data]
- We have three time variables that keep track of time. “time” and “time2” move rapidly, while time4 only ticks every 64th frame – and will be the main time that the demo follows (e.g the scenes will change when column 2 in timeStamps equals time4.
- keep01 and currentConfig makes sure that the current memory configuration (memory address $01) is always preserved during interrupts
- currentPart points to the current location in the timeStamps (that is, the current scene)
- nextScene points to the next scene
- initComplete is a variable that is set to 0 as long as the current scene is initializing, and to 1 when initialization is complete. This is to prevent the scene raster to start before the actual data is initialized.
- transitionMode is 0 when no transition, but when a scene is nearing completion (that is, the time4 variable is [column 1 of timeStamps]-1), this variable is set to 1. It is therefore possible to initiate transitions to next scene (like clearing the screen slowly, moving sprites out, fading text or images etc)
The main block
Let’s jump to the main routine, as defined from the flowchart in the previous tutorial. There should be very few surprises here: We set up the memory configuration (no basic, no IO, no kernal) and save the config to the keep01 variable. The raster interrupt is set up to point to our “wait raster” (see the flowchart in the previous part of this tutorial series). Afterwards, we enable rasters – and call the main loop update routine “UpdateMainRoutine” (called “Main routine” in the flowchart).
/* Main routine */ begin // Disable all interrupts PreventIRQ(); DisableCIAInterrupts(); // Empty NMI irq nmiirq(NMI()); // Set current memory config SetMemoryConfig(1,@useKernal,0); // Make sure we keep that config stored keep01:=peek(^$01,0); currentPart := 0; nextScene:=timeStamps[currentPart*4]; initsid(SIDFILE_1_INIT); initComplete:=0; RasterIRQ(RasterWaiter(),0,@useKernal); EnableRasterIRQ(); enableirq(); UpdateMainloop(); end.
The main routine
In the previous tutorial, we mentioned that the job of the main routine is to perform heavy initializations all while the “wait raster” just plays music and waits for the initialization of the current scene is done and “initComplete” is set to 1. Typical initializations include decrunching of images & data, calculating tables, setting screen modes, switching banks, clearing screens etc. Make sure that the screen either is off or whatever whenever you perform these initializations.
procedure UpdateMainloop(); begin while (1<2) do begin if initComplete=0 then begin // Make sure that everything is black SPRITE_BITMASK:=0; screenoff(); if (nextScene=@scene1) then InitScene1(); screenon(); initComplete:=1; end; end; end;
Note a couple of things:
- The main loop loops infinitely, but is only triggered to do something whenever the “initComplete” flag is set to zero (meaning that a new scene has been triggered, it is not yet initialized).
- Whenever a new scene is initialized, we always turn off the screen (black) and also turn off sprites.
- In this example, we only really have a single scenes yet – so the part that would check whether the next scene is “scene1” and initialize it, and give notice to the “wait raster” that initialization is complete (initComplete=1) and we can proceed to initialize the next raster interrupt (for scene 1).
The wait raster
The “wait raster” is designed to, well, wait for the main routine to finish initializations – all while playing that demo music without interruptions. The “wait raster” therefore interrupts all initializations each frame, and can cause havoc when decrunching from usually closed-off areas of the memory (such as $D000-$DFFF) etc. Therefore, at the start of the interrupt, we have a custom “StartIRQ” that makes sure that the current memory config of the main loop initialization stuff is preserved in the “currentConfig” variable. Finally, when the “initComplete” flag is set to 1, the “wait raster” raster just passes the flag to the raster of the next scene.
interrupt RasterWaiter(); begin // This replaces startirq by always turning ON the vic // in case we were in the middle of a scene initializing decrunch. Remember to restore // the original value of $01 at the end of the raster! Asm(" pha txa pha tya pha lda $01 sta currentConfig lda keep01 sta $01 asl $D019 "); // Only advance when main loop gives the flag that scene init is complete! if (initComplete=1) then begin if (nextScene=@scene1) then begin RasterIRQ(RasterScene1(),0,@useKernal); end; end; @ifdef play_music call(SIDFILE_1_PLAY); @endif // Set original config poke(^$01, 0, currentConfig); CloseIRQ(); end;
The Progress Tracker (the final ingredient)
Finally, if you recall from the flowchart of part 1 of this tutorial, we implement the Progress Tracker. This procedure is not being used in any of the parts we have currently described, but is the main procedure that must be called within the raster of every scene that we include in the demo.
The progress tracked is responsible for the following:
- Playing music
- Constantly following whether the current scene time (“time4” variable) is closing in on a new scene
- If a new scene is nearing, initializes “transitionMode” to 1, telling the scene to prepare for a transition
- When a new scene is activated, we proceed to the next part in the timeStamps array by increasing currentPart, calculating nextScene, telling the main loop that we need to start initializing the next scene (by setting initComplete to 0) and finally: moving the raster interrupt back to the “wait raster” while the next scene initializes.
- Increasing the time, time2 and time4 counters.
rocedure ProgressTracker(); var curTimeStamp : byte; begin // Play music, display music raster time @ifdef play_music @ifdef debug_raster DebugRaster(YELLOW); @endif call(SIDFILE_1_PLAY); @ifdef debug_raster DebugRaster(BLACK); @endif @endif transitionMode:=0; // Find timestamp of current scene curTimeStamp:=timeStamps[currentPart*4+1]; // Is it time to advance to the next scene? if (time4=curTimeStamp) then begin // Start pointing to the next part inc(currentPart); // Reset time and time2, but not time 4 (which is used for global timing) time:=0; time2:=0; // new scene is not yet initialized initComplete:=0; // Point to next scene nextScene:=timeStamps[currentPart*4]; // Load the raster waiter. RasterIRQ(RasterWaiter(),0,@useKernal); // Since initComplete is zero and nextScene is set, the main loop will instantly start initializing // the next scene. end; // However, if we are at the final timestep before a change, set transitioning flag to 1 if (time4=curTimeStamp-1) then transitionMode:=1; // Toggle raster debugging if raster is on and key is pressed @ifdef debug_raster if (keypressed(@rasterDebugKey)=1) then begin show_raster:=(show_raster+1)&1; end; @endif // Increase time, time2 and time4 IncTime(); end;
Simplifying stuff: restructuring the program
Finally, we need to recognize an importat fact: Everything up until now is somewhat redundant when creating a demo. If you intend to just *use* the DemoMaker tutorials as a basis for your own demo, you shouldn’t have to deal with all these methods all the time. We have therefore chosen to extract the main (re-usable) routines to a file called “demomaker_procedures.ras” with demomaker variables in “demomaker_vars.ras“. Finally, instead of having to set the raster and init methods directly in this method, it just calls another procedure. This means that your demo tutorial now should look like this ; a much cleaner verison indeed :
program DemoMaker_part2; var @define play_music 1 // Turn off kernalr @define useKernal 0 // Turn on/off raster debug @define debug_raster 1 // Key for turning on / off rasters @define rasterDebugKey "KEY_R" /* Pointers must be declared outside of blocks */ p1,zp1,p2,zp2,textPointer: pointer; @ifdef debug_raster show_raster: byte=0; @endif // Specify user data @userdata "$0400" "$0800" "Bank 1 video temp storage" @userdata "$4400" "$4800" "Bank 2 video" @userdata "$8400" "$8800" "Bank 3 video" // Main demo music (by Uctumi) music: incsid("music/courierb0.sid","2"); // Put all these variables at $F000 @startblock $F000 "Variables" // Standard variables a,b,c,i,j,k,l,m,n,o : byte; // Common .ras file for demomaker variables @include "demomaker_vars.ras" /* *** * ** * C E N E S *** */ // Set up scenes @define scene1 1 @define scene2 2 @define scene3 3 // Timestamps decide order of scenes (column 0) and how long they should last (column 1) // Column 2 and 3 are purely optional for extra data (like, displaying same scene multiple times with various // colors or character sets) timeStamps: array of byte = ( @scene1, $7, 0,0, @scene2, $10,0,0, @scene3, $23,0,0, @scene1, $40,1,0 ); @endblock procedure InitSceneRaster(); begin // This is where scene inits go end; procedure MainLoopInitScene(); begin // This is where mainloop inits go end; @include "demomaker_procedures.ras" begin DemoMakerMainLoop(); end.