Guide to develop 4k Intros for MorphOS (Part 1)

For this guide, we will need MorphOS SDK, and lzma_loader to crunch the executable output.

Develop a 4k intro can be divided on 4 tasks:

  • Statup code.
  • Graphics code.
  • Audio code.
  • Intro itself.

And we will add an extra task, a stack virtual machine.

 

Startup code

Some time ago I published some code to do this task, I have received help from morphzone forum to optimize this part, specially from Piru.

This code is based on that startup code published, I have made small changes but basically the tasks are:

  • Initialize OpenGL Context.
  • Initialize AHI device.
  • Calculate sinus and cosinus tables.

So here is the startup code:

Code:

/*
* 4Kb Intro startup by Pedro Gil (Balrog Soft)
*            www.amigaskool.net
*
*  TinyGL and AHI device startup code,
*  and music synth for 4k intros.
*  The song was ripped from howagen intro by tonic.
*
*  Special thanks to Piru for his small opts,
*  and Morphzone.org people!
*
*/

#include <stdio.h>
#include <limits.h>

#include <devices/ahi.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/tinygl.h>
#include <proto/ahi.h>

#define ubyte   unsigned char
#define uint    unsigned int

#define byte    signed char

#ifndef M_PI
#define M_PI  3.14159265
#define M_PI2 6.283185
#endif

// Sinus and cosinus arrays have 720 elements

#define TABLE_DEGREES 720
#define DEGREE_STEP     M_PI2 / TABLE_DEGREES

// Define our ahi device for 8 bits
// signed mono buffer with a frequency
// of 8000 samples

#define FREQUENCY    8000
#define TYPE         AHIST_M8S
#define BUFFERSIZE   24000

static void display(void);
static void keys(unsigned char c, int x, int y);
static void synth4k(void);

struct ExecBase    *SysBase;
struct Library     *TinyGLBase;

GLContext *__tglContext;

static struct MsgPort    *AHImp     = NULL;
static struct AHIRequest *AHIios1 = NULL;
static struct AHIRequest *AHIios2 = NULL;
static struct AHIRequest *link   = NULL;

static struct Message *wbstartupmsg = NULL;
static struct Process *self = NULL;

static void  *tmp;
static BOOL  init = TRUE;

static byte  AHIDevice;

// Here are defined two audio buffers,
// it is similar to a screen double buffer
static byte   buffer1[BUFFERSIZE], buffer2[BUFFERSIZE];
static byte  *p1=buffer1, *p2=buffer2;
static uint  signals;
static int   t, i, j, n;

// Our sinus and cosinus tables
static float  sinus[TABLE_DEGREES], cosinus[TABLE_DEGREES],
      a = 0, b = 1, c, d = DEGREE_STEP;

#define __TEXTSECTION__ __attribute__((section(".text")))


#ifdef NOSTARTUPFILES
int entry(void)
#else
int main(void)
#endif
{
  SysBase    = *(struct ExecBase **) 4;
 
  wbstartupmsg = 0;
  self = (struct Process *) FindTask(0);
  if (self->pr_CLI == 0)
  {
WaitPort(&self->pr_MsgPort);
wbstartupmsg = GetMsg(&self->pr_MsgPort);
  }

  TinyGLBase = OpenLibrary("tinygl.library",   50);

  if (TinyGLBase)
  {

    // Initialize AHI device

AHImp=CreateMsgPort();
#ifndef REMOVE
if(AHImp != NULL)
{
#endif
  AHIios1 = (struct AHIRequest *) CreateIORequest(AHImp, sizeof(struct AHIRequest));
  AHIios1->ahir_Version = 4;
  AHIDevice = OpenDevice(AHINAME, 0, (struct IORequest *)AHIios1, 0);
#ifndef REMOVE
    }
#endif

// Make a copy of the request (for double buffering)

AHIios2 = AllocVec(sizeof(struct AHIRequest), MEMF_ANY);
CopyMem(AHIios1, AHIios2, sizeof(struct AHIRequest));

// Generate sinus and cosinus tables
i = TABLE_DEGREES;
while (i)
{
  c = a + b * d;
  b = b - c * d;
  sinus  [TABLE_DEGREES-i] = c;
  cosinus[TABLE_DEGREES-i] = b;
  a = c;
  i--;
}
c = 0;

__tglContext = GLInit();
if (__tglContext)
{
  // Initialize TinyGL
 
        glutInit(NULL,NULL);

  glutInitDisplayMode(GLUT_RGBA);

  #ifndef REMOVE
  glutInitWindowSize(640, 480);
  #endif

  glutFullScreen();
 
  glutCreateWindow(NULL);

  #ifndef REMOVE
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  #endif
 
  // Setting blending settings

  glEnable(GL_BLEND);  // Enable Blending
 
  glBlendFunc(GL_SRC_ALPHA, GL_ONE);
 
        glEnableClientState(GL_VERTEX_ARRAY);

  // Setting projection, P2D define a
  // 2D projection for 2d graphics.
  glMatrixMode(GL_PROJECTION);
 
  #ifdef P2D
  glOrtho(320.0f, -320.0f,
     240.0f, -240.0f,
      -1.0f, 1.0f);
  #else
  gluPerspective(80, 1.333333f, 1.0, 500.0);
  glTranslatef(0.0f, 0.0f, -240.0f);
  #endif

  glMatrixMode(GL_MODELVIEW);
 
  // Some unused stuff
 
  //glScalef(2.0f,2.0f,0.0f);
  //glEnable(GL_LINE_SMOOTH);

  //glHint(TGL_CORRECT_NORMALS_HINT, GL_FASTEST);
  //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
 
  // Define our main key and idle functions
 
        glutKeyboardFunc(keys);
 
        glutIdleFunc(display);

  // And enter to the main loop.
 
  glutMainLoop();

  // Close TinyGL Context
  GLClose(__tglContext);
}

   // Abort any pending AHI iorequests
AbortIO((struct IORequest *) AHIios1);
WaitIO((struct IORequest *) AHIios1);

if(link)  // Only if the second request was started
{
  AbortIO((struct IORequest *) AHIios2);
  WaitIO((struct IORequest *) AHIios2);

if(!AHIDevice)
  CloseDevice((struct IORequest *)AHIios1);

DeleteIORequest((struct IORequest *)AHIios1);
FreeVec(AHIios2);
 
// Close TinyGL lib
CloseLibrary(TinyGLBase);
  }
 
  if (wbstartupmsg)
  {
    Forbid();
    ReplyMsg(wbstartupmsg);
  }
 
  return 0;
}


static void keys(unsigned char c, int x, int y)
{
// Send a fake CTRL+C signal to exit
if (c == 0x1b)
{
  SetSignal(SIGBREAKF_CTRL_C, SIGBREAKF_CTRL_C);
}
}

static void display(void)

// Ahi device handle code

// Send io request to the device
if ((signals & (1L << AHImp->mp_SigBit) || init))
{
  if (init && link)
   init = FALSE;
  if (!link)
            synth4k();
//   AHIios1->ahir_Std.io_Message.mn_Node.ln_Pri = 0;
  AHIios1->ahir_Std.io_Command  = CMD_WRITE;
  AHIios1->ahir_Std.io_Data     = p1;
  AHIios1->ahir_Std.io_Length   = BUFFERSIZE;
//   AHIios1->ahir_Std.io_Offset   = 0;
  AHIios1->ahir_Frequency       = FREQUENCY;
  AHIios1->ahir_Type            = TYPE;
  AHIios1->ahir_Volume          = 0x10000;          // Full volume
  AHIios1->ahir_Position        = 0x8000;           // Centered
  AHIios1->ahir_Link            = link;
  SendIO((struct IORequest *) AHIios1);
}

// Check if there are any signals from ahi device
if(link)
{
  // Send a fake signal to not stop the loop
  SetSignal(SIGBREAKF_CTRL_D, SIGBREAKF_CTRL_D);
        signals = Wait(SIGBREAKF_CTRL_D | (1L << AHImp->mp_SigBit));
}

link = AHIios1;
  
// Swap ahi buffers
if ((signals & (1L << AHImp->mp_SigBit)) || init)
    {
  if (!init)
   WaitIO((struct IORequest *) AHIios2);
 
  // Swap buffer and request pointers, and restart
  tmp    = p1;
  p1     = p2;
  p2     = tmp;
 
        synth4k();

  tmp    = AHIios1;
  AHIios1 = AHIios2;
  AHIios2 = tmp;
}

// Here goes the drawing stuff!

glClear(GL_COLOR_BUFFER_BIT); // Clear Screen Buffer

glutSwapBuffers();

}

// This is the future 4k synth
static void synth4k(void)
{

}

#ifdef NOSTARTUPFILES
/* __abox__ symbol is required or else the binary is loaded as PowerUP app */
const int __abox__ __TEXTSECTION__ = 1;
#endif

 

Entry function does all the initizalization stuff and start the main loop.
keys function only check if escape key is pressed and send a signal to exit.
display function does all graphic stuff and also swaps ahi buffers if need.
synth4k function is reserved to fill the next audio buffer with our song.

To build this code, you will need a makefile, don't you?

 

Makefile:

CC   = gcc
CPP  = g++
LD   = ld
STRIP  = strip
CFLAGS  = -O1 -s  -fomit-frame-pointer -noixemul  -ISDK:tinygl-sdk/include

NOSTARTUPFILES  = 1
REMOVE    = 1
2D_PROJECTION = 1

ifdef NOSTARTUPFILES
CFLAGS+= -DNOSTARTUPFILES -nostartfiles
endif

ifdef REMOVE
CFLAGS+= -DREMOVE
endif

ifdef 2D_PROJECTION
CFLAGS+= -DP2D
endif

INTRO_OBJS = intro.o

all: intro

intro: $(INTRO_OBJS)
$(CC) $(CFLAGS) -o $@ $(INTRO_OBJS)
$(STRIP) -s -R .comment -R .gnu.version -R .gnu.version_d -R .gnu.version_r $@
chmod u+x $@

intro_map:
$(CC) $(CFLAGS) intro.c -Wl,-Map=foobar.map,--traditional-format

intro_asm:
$(CC) $(CFLAGS) -save-temps -o $@ $(INTRO_OBJS)

clean:
rm -f *.o intro

 

Ok, but this executable is sooo big! It's near 4k limit, what to do? lzma loader will save your butt, again Piru made an excellent job, and I obtain an executable of 1660 bytes, maybe too much for a startup code, but it's not so bad.

You can put lzma loader files inside your build directory and use this script to get your intro crunched on ram disk.

CLI:

copy lzmaLoader_shared ram:intro
lzma e intro -so >> ram:intro

 

You have it, a startup code for 4k intros, but where is the fun? a black screen and no sound? On the next episode, beloved childrens, we will add a 4k synth with oscillators, envelope, tracks and patterns support, and all of this for only 1kb! enough to make a sweeeet chip tune!

For lazy people like me, you can download all the files here:

4k Intro tutorial 1

 

The story continues in part 2...