Rasters interrupts & SID

This tutorial will cover the following concepts:

  • Rasterline IRQ hooks and wait functions
  • Loading and playing SID files
  • Reading from the sine tab
  • How to create crappy raster bars (they don’t sort properly)

It is useful to hook routines to the raster IRQ to enable effects that are not possible otherwise. In this example, we will continuously set the background & foreground colors at specific y-coordinates in order to produce a rather limited effect of raster bars. Mind you, this is my first go on raster bars on the C64, and I haven’t even been drinking today. Alas, I discovered more than a handful of bugs in my compiler, which is a good thing I guess.

We start off with some definitions


program RasterInterruptsAndSid;
var
	time, mainloop,a,b,i,j, idx, color : byte;
	colorIndex, diff : byte;
	bar_s : array[4] of byte = (0, 5, 12, 20);
	currentBar, barCount : byte;
 	fade : array [16] of byte = 
(00,11,6,12,4,14,15,1,1,15,14,4,12,6,11,00,00); 
	sidfile: incsid("song.sid");

Some notes here:

  • Tons of ugly variables. Welcome to the c64! Unless I manage to implement types.
  • There were supposed to be 4 raster bars, but just two implemented. Because I am lazy and have better things to do.
  • SID files are included with the “IncSid” command. The SID file load, play and init address are automatically extracted, and the header is removed before being included.
  • You are on your own with regards to making sure the SID runs in a valid load/play address. Check out sidreloc for details.

This time, we’ll start with the main method:


begin
	InitSid(SIDFILE_1_INIT);

	poke(SCREEN_BG_COL, 0, BLACK);
	poke(SCREEN_FG_COL, 1, BLACK);

	ClearScreen($20, SCREEN_CHAR_LOC);

	moveto(8,15, $04);
	printstring("E P I C    H E L L O", 0);
	moveto(0,17, $04);
	printstring("M U S I C   F R O M   D E F L E M A S K", 0);

	time:=0;

	DisableInterrupts();
	RasterIRQ(UpdateRaster(), 199);
	EnableInterrupts();

	Loop();

end.

Key points here are

  • InitSid calls the sid initialization routine. For each SID included in the project, the SIDFILE_N constant is increased.
  • Printstring prints at location defined by MoveTo, where the final parameter is an offset shift (not used in this example)
  • RasterIRQ sets up a raster interrupt to the function “UpdateRaster()” when the scan line hits value #199. Interrupts are disabled and enabled before and after.
  • The Loop function just goes on 4evah, while the main code continues to be executed for each raster refresh (when the raster line hits the same position, roughly 60 times pr second)

procedure UpdateScreen();
begin
	Poke(SCREEN_BG_COL, 0, BLACK);
	Poke(SCREEN_FG_COL, 0, BLACK);

	Call(SIDFILE_1_PLAY);
	Fill(^$D800 + 40*15, fade[(time/4) & 15], 40);
	Fill(^$D800 + 40*17, fade[(time/4 + 5) & 15], 40);

end;

procedure Bars();

interrupt UpdateRaster();
begin
	UpdateScreen();

	time:=time+1;

	bar_s[0] := sine[time*2]/2 + 100;
	bar_s[1] := sine[time*2 + 30]/2 + 100;
	
	if bar_s[0]>bar_s[1] then 
		swap(bar_s[0], bar_s[1]);


	barCount:=15;
	currentBar:=0;	
	colorIndex:=0;
	Bars();
	
	kernelInterrupt();

end;

Next up are the update functions! UpdateScreen just sets the background and foreground color, while Call(SIDFILE_1_PLAY) command ensures continuous playback of the SID track. It is vital that this method is called once per screen refresh.

We also fill the color memory where the text is displayed with color valuefade[(time/#4) & #15] which ensures that the color value is limited between 0-15 of the fade (color) table.

Some vital points here:

  • The recursive procedure Bars will be used by subsqeuent procedures (UpdateRaster, Bars itself), so it needs to be declared empty before the other functions.
  • The two bars are set up for each frame via a simple sine lookup table + shift.¬†sine[time*#2]/#2 + #100;implies that the time is scaled with 2 (remember, just a bit shift), the amplitude divided by 2 (256/2=128) and then shifted to fit the screen.
  • the Swap(x,y) method just swaps/sort the bar positions if one is located before the other in y-space. Which is kinda obvious when taking how raster lines work into account.
  • After Bars is called, the function returns by calling the KernelInterrupt routine. This is necessary to let the system continue with its interrupts. Also note that the procedure is declared as an “interrupt” and not “procedure”, which ensures that the current interrupt is acknowledged before continuing.

 


procedure Bars();
begin
	WaitForRaster(bar_s[currentBar]);

	for i:=1 to barCount do begin
		val:=fade[colorIndex];
		Poke(SCREEN_BG_COL, 0, val);
		Poke(SCREEN_FG_COL, 0, val);
		colorIndex:=colorIndex+1;
		WaitNoRaster(1);
		
	end;
	Poke(SCREEN_BG_COL, 0, 0);
	Poke(SCREEN_FG_COL, 0, 0);

	a:=bar_s[currentBar];
	currentBar:=currentBar+1;
	b:=bar_s[currentBar];
	diff := (b - a);
	barCount:=15;
	colorIndex:=0;

	if diff<34 then begin
		peek(^$d012, 0, a);
		bar_s[currentBar]:=a+2;
		barCount:=diff/2 +2;
		colorIndex:=15-diff/2;
	end;

	if currentBar < 1 then begin
		Bars();
	end;
end;

Ahh, the beast part of the code.

  • First, we wait for the raster line to hit the position of the current bar
  • The bar is then processed by setting a fade color for a total of [barCount] lines. Note that we also wait 1 rasterline within the for loop.
  • Afterwards, the background is set to zero
  • The remaining part is an ugly technical brute-force-thingy that ensures that the bars won’t overlap, by shrinking the size of the subsequent bar & shifting the colors.
  • The method calls itself for the next bar in the list

And that’s it!