/*
* 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
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
copy lzmaLoader_shared ram:intro
lzma e intro -so >> ram:intro
#define TONEFREQ 220.0f
#define TONEBASE 1.059463094359f
#define CHANNELS 2
#define PATTERNS 5
#define PATTERNLEN 16
#define SONGLEN 4
#define NOTELEN 2048
#define PATLEN 32768
#define SILENT 255
static char patterns[PATTERNS][PATTERNLEN]=
{
{13, 18, 20, 18, 25, 18,20,18,13, 18, 20, 18, 25, 18,20,18,},
{22, 0, 0, 0, 0, 0, 20,0, 23,22,0, 20, 0,0,22,18},
{0,0, 0,0,0,0, 18, 18, 18,17,0,15, 0, 0, 17, 18,},
{0, 0, 0,0, 0, 0, 0, 0, 0,13,15,13,20,18,13,20},
{18, 0, 0,0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0},
};
static char seq[CHANNELS][SONGLEN] =
{
{0, 0, 0, 0},
{1, 2, 3, 4}
};
static float env_chan[CHANNELS][8] =
{
{
0, 0, // a
256, 1, // d
2048, 0.2, // s
3072, 0.6, // r
},
{
0, 0, // a
500, 1, // d
7000, 0.8, // s
8000, 0.7 // r
}
};
static short pos[2];
static float freq;
static char note[2];
static short note_pos;
static byte ch;
static float env,hz;
static float *env_val;
static char pat_ptr;
// Two oscillators are availabe, sin and saw
// first parameter are hertzs and second parameter is time
static float osc_sin(float f, int tm)
{
int d = tm*720*f/8000;
return (sinus[(d%720)]+1.0)/2.0;
}
static float osc_saw(float f, int tm)
{
int r=(2*FREQUENCY / f);
return (tm % r) * (1.0/r);
}
static void synth4k(void)
{
// Fill the audio buffer
for (n = 0; n < BUFFERSIZE; n++)
{
// Set a pointer to the current buffer
// and set the current buffer to zero
byte *pb = &p1[n];
*pb = 0;
// Process our song to generate a wave for every channel
for (ch = 0; ch < CHANNELS; ch++)
{
// Set some pointers to save some bytes,
// note contains the current note played on this channel
// po is the current position of the wave note for filling our buffer
// pat_ptr contains the current pattern note to be played on this channel
char *not = ¬e[ch];
short *po = &pos[ch];
note_pos = (t>>11)%PATTERNLEN;
pat_ptr = patterns[seq[ch][(t>>15)%SONGLEN]][note_pos];
// If note is silence, don't process the song
if (pat_ptr < 255)
{
// Change the current note to be played,
// if it's zero the last note is preserved
if (pat_ptr > 0 && t%NOTELEN == 0)
{
*not = ((pat_ptr&63)-1);
*po = 0;
}
// Apply the envelope filter
env_val = env_chan[ch];
env = env_val[7];
for (i=0; i<8; i+=2)
{
if (env_val[i] > *po)
{
float pr = ((*po-env_val[i-2])/(env_val[i] - env_val[i-2]));
env = pr * (env_val[i+1] - env_val[i-1]) + env_val[i-1];
}
}
// Set base frequency
freq = TONEBASE;
// Get the frequency for the note to be played,
// it is a simple power function, but it is not available.
for (j = 1; j < *not; j ++)
freq *= TONEBASE;
// Get the herzs need to play the specific note
hz = TONEFREQ*freq;
// Here we define the instruments for the channels,
// Play with oscillators and apply more to the time,
// or hz parameters, add different oscillators, use
// your imagination to obtain different sounds!
if (ch == 0)
*pb+=env*(osc_saw(hz,t))*48;
else
*pb+=env*(osc_sin(hz,t+osc_sin(5,t)*10))*24;
}
// Increment the buffer position
*po+=1;
}
// Increment the time position
t++;
}
}
static byte mem[VM_MEMORY] =
{
PUSH, 8,
LWID,
PUSH, GL_LINE_LOOP,
SSHA,
PUSH, 0xff,
PUSH, 0x1f,
SCOL,
CALL, 0,
CALL, 0,
MOVE,
CALL, 0,
CALL, 0,
MOVE,
CLPA,
RPUSH, 0,
PUSH, 1,
ADD,
RPOP, 0,
RPUSH, 0,
PUSH, 127,
CMP,
BREQ, 3,
JMP, 0,11,
HALT,
};
#define VM_REGISTERS 16
#define VM_MEMORY 4096
#define HALT 0x00
#define PUSH 0x01
#define RPUSH 0x02
#define RPOP 0x03
#define ADD 0x04
#define SUB 0x05
#define MUL 0x06
#define DIV 0x07
#define CALL 0x08
#define JMP 0x09
#define RJMP 0x0A
#define CMP 0x0B
#define BRLE 0x0C
#define BREQ 0x0D
#define BRGR 0x0E
#define LDSP 0x0F
#define STSP 0x10
#define SCOL 0x60
#define LWID 0x61
#define SSHA 0x62
#define MOVE 0x63
#define CLPA 0x65
static void vm_exec(void);
static short pc;
static short sp;
static ubyte flags;
static short vertex[1024], shape=0, x = 0, y = 0, m=0;
static byte reg[VM_REGISTERS];
static byte mem[VM_MEMORY] =
{
PUSH, 8,
LWID,
PUSH, GL_LINE_LOOP,
SSHA,
PUSH, 0xff,
PUSH, 0x1f,
SCOL,
CALL, 0,
CALL, 0,
MOVE,
CALL, 0,
CALL, 0,
MOVE,
CLPA,
RPUSH, 0,
PUSH, 1,
ADD,
RPOP, 0,
RPUSH, 0,
PUSH, 127,
CMP,
BREQ, 3,
JMP, 0,11,
HALT,
};
static void push(byte value)
{
mem[sp--] = value;
}
static byte pop(void)
{
return mem[++sp];
}
static short dpop(void)
{
return (pop()<<8)+(pop()&0xff);
}
static byte pop_pc(void)
{
return mem[pc++];
}
static byte dpop_pc(void)
{
return (pop_pc()<<8)+(pop_pc()&0xff);
}
static void func1(void)
{
}
static void func2(void)
{
}
static void func3(void)
{
}
static void (*func_ptr[3])(void) = {func1, func2, func3};
static void vm_exec(void)
{
ubyte opCode;
byte a,b;
short c;
pc = 0;
sp = VM_MEMORY-1;
flags = 0;
do {
opCode = pop_pc();
switch (opCode)
{
case PUSH:
push(pop_pc());
break;
case RPUSH:
push(reg[pop_pc()]);
break;
case RPOP:
reg[pop_pc()]=pop();
break;
case ADD:
push(pop()+pop());
break;
case SUB:
push(pop()-pop());
break;
case MUL:
push(pop()*pop());
break;
case DIV:
push(pop()/pop());
break;
case CALL:
(*func_ptr[pop_pc()])();
break;
case JMP:
pc = dpop_pc();
break;
case RJMP:
pc += pop_pc();
break;
case CMP:
a = pop();
b = pop();
flags = 0;
flags |= ((a<b) << 0);
flags |= ((a==b)<< 1);
flags |= ((a>b) << 2);
break;
case BRLE:
a = pop_pc();
if (flags & (1 << 0))
pc += a;
break;
case BREQ:
a = pop_pc();
if (flags & (1 << 1))
pc += a;
break;
case BRGR:
a = pop_pc();
if (flags & (1 << 2))
pc += a;
break;
case LDSP:
sp = dpop();
break;
case STSP:
push(sp);
push(sp>>8);
break;
case SCOL:
c = dpop();
glColor4ub((c>>4)&0xf0,c&0xf0,(c<<4)&0xf0,(c>>8)&0xf0);
break;
case LWID:
glLineWidth(pop());
break;
case SSHA:
shape = pop();
break;
case MOVE:
x += pop();
y += pop();
vertex[m++] = x;
vertex[m++] = y;
break;
case CLPA:
glVertexPointer(2, GL_SHORT, 0, vertex);
glDrawArrays(shape, 0, m);
m = x = y = 0;
break;
}
} while (opCode != 0x00); // HALT
}