/******************************************************************************\
*                                    KeyBang                                   *
*                                    -------                                   *
*                                 by Mike Stark                                *
* Revisions:                                                                   *
*   1.0 30 Mar 92 Added patterns, removed floating point math, added intro     *
*                 window, improved input handler and added command options.    *
*   0.9 23 Feb 92 Added the input handler so that keybanging would get the key *
*                 events before intuition.                                     *
*   0.8           Made PAL/NTSC independent.                                   *
*   0.7           Added the ability to play random sounds.                     *
*   0.?           Original version (Shapes in response to intuition events)    *
* Credits:                                                                     *
*     Michael Balzer (balzer@heike.informatik.uni-dortmund.de), the author of  *
*	  "Bomber" for a C example of how to use the audio device.             *
*     Carolyn Scheppner of CATS for "OneKey" which is a concise example of how *
*	  to install and use an input handler.                                 *
\******************************************************************************/

#include "KeyBang.h"
#include "table.h"

char *version = "\0$VER: KeyBang 1.0 by Mike Stark (March 30, 1992)";

#define VECTORS 30
#define CLOCK 3579545
#define FORM 0x464f524d
#define BODY 0x424f4459
#define VHDR 0x56484452
#define EIGHT_SVX 0x38535658
#define SOUNDDIRNAME "Sounds:"

extern void BabyInputHandler();

SHORT ColorTable[16] =
{
  0x0000, 0x0eca, 0x0c00, 0x0f60, 0x0090, 0x03f1, 0x000f, 0x02cd, 0x0f0c,
  0x0a0f, 0x0950, 0x0fe0, 0x0fff, 0x0ccc, 0x0888, 0x0444
};

#define PATTERNS 6

USHORT PatternData[] =
{
/* Solid */
  0xffff,
/* Brick */
  0x0101, 0x0101, 0x0101, 0xff01, 0x0101, 0x0101, 0x0101, 0x01ff,
/* Grid */
  0xffff, 0x0303, 0x0303, 0x0303,
/* Diagonal lines */
  0xf0f0, 0x3c3c, 0x0f0f, 0xc3c3,
/* Other diagonal lines */
  0x0f0f, 0x3c3c, 0xf0f0, 0xc3c3,
/* Other brick pattern */
  0x0300, 0x0300, 0x0300, 0xffff, 0x0003, 0x0003, 0x0003, 0xffff
};

struct PatternInfo
{
  int Power;
  USHORT *Data;
} Patterns[PATTERNS] = 
{
  {0, &PatternData[0]},
  {3, &PatternData[1]},
  {2, &PatternData[9]},
  {2, &PatternData[13]},
  {2, &PatternData[17]},
  {3, &PatternData[21]}
};

#define MESSAGELINES 8

char *Message[] =
{
  "KeyBang 1.0",
  "by Mike Stark",
  "March 30, 1992",
  "",
  "--> Alt-Alt-F5 exits <--",
  "",
  "This program is shareware.",
  "Please read KeyBang.doc."
};

char *ScreenTitle = "KeyBang";
struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase *GfxBase = NULL;
struct Screen *BabyScreen = NULL;
struct Window *BabyWindow = NULL, *IntroWindow = NULL;
struct Remember *MemList = NULL;
BYTE *BabyTmpRaster = NULL;
APTR OldWindow;
struct IOAudio *AudioMsg = NULL;
struct MsgPort *IOReplyPort = NULL;
struct MsgPort *KeyPort = NULL;
struct IOStdReq *InputRequest = NULL;
struct Interrupt HandlerInfo;
struct SoundInfo **Sound;
int Depth = 4;
int Sounds = 0;
int HandlerData[2];
int LeftSoundPlaying = FALSE, RightSoundPlaying = FALSE;
int WinX = 640, WinY = 200, XDPM, YDPM;
int Unit[2];
BOOL Audio = FALSE, OneShape = FALSE;

main(argc, argv)
int argc;
char **argv;

{
  struct MyInputEvent *Message;
  struct IntuiMessage *IMessage;
  struct IOAudio *SoundMsg;
  int Type, XSize, YSize, X, Y, Signal, Sample, SampleUnit;
  BOOL Done, DoAction, Mouse;
  int Count = 0, Seed, Pattern, Colors;

  GetOptions(argc, argv);

  Initialize();

  Colors = 1 << Depth;

  Signal = 1 << KeyPort->mp_SigBit;
  Signal |= 1 << IOReplyPort->mp_SigBit;
  Signal |= 1 << BabyWindow->UserPort->mp_SigBit;

  Done = FALSE;
  while (!Done)
  {
    DoAction = FALSE;
    Wait(Signal);
    if (Audio)
      while (SoundMsg = (struct IOAudio *)GetMsg(IOReplyPort))
      {
	SampleUnit = (int)SoundMsg->ioa_Request.io_Unit;
	if (SampleUnit & 9)
	  RightSoundPlaying = FALSE;
	if (SampleUnit & 6)
	  LeftSoundPlaying = FALSE;
        FreeMem(SoundMsg, sizeof(struct IOAudio)); 
      }
    while (Message = (struct MyInputEvent *)GetMsg(KeyPort))
    {
      if (Message->IE.ie_Class == IECLASS_RAWKEY)
      {
	if (!((Message->IE.ie_Code & IECODE_UP_PREFIX) ||
	      (Message->IE.ie_Qualifier & IEQUALIFIER_REPEAT)))
	{
	  DoAction = TRUE;
	  Mouse = FALSE;
	}
	if (Message->IE.ie_Code == 0x45)
	{
	  Erase(BabyWindow->RPort);
	}
	if ((Message->IE.ie_Code == 0x54) &&
	    ((Message->IE.ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
	     == (IEQUALIFIER_LALT | IEQUALIFIER_RALT)))
	{
	  DoAction = FALSE;
	  Done = TRUE;
	}
      }
      FreeMem(Message, sizeof(struct MyInputEvent));
    }
    while (IMessage = (struct IntuiMessage *)GetMsg(BabyWindow->UserPort))
    {
      if (!(IMessage->Code & IECODE_UP_PREFIX))
      {
	Mouse = TRUE;
	X = IMessage->MouseX;  Y = IMessage->MouseY;
	YSize = Random(0) / 505 + 10;
	XSize = 2 * YSize * XDPM / YDPM;
	if (X < XSize) X = XSize;  if (Y < YSize) Y = YSize;
	if (X > WinX - XSize) X = WinX - XSize;
	if (Y > WinY - YSize) Y = WinY - YSize;
	DoAction = TRUE;
      }
      ReplyMsg(IMessage);
    }
    if (DoAction)
    {
      if (IntroWindow)
      {
	CloseWindow(IntroWindow);
	IntroWindow= NULL;
	(void)Random(Seed);
	SetDrMd(BabyWindow->RPort, JAM2);
      }
      if (OneShape) Erase(BabyWindow->RPort);
      Sample = Random(0) * Sounds / 32768;
      if (Audio) MakeSound(Sample);
      if (!Mouse)
      {
	YSize = Random(0) / 505 + 10;
	XSize = 2 * YSize * XDPM / YDPM;
	Y = Random(0) * (WinY - 2 * YSize) / 32768 + YSize;
	X = Random(0) * (WinX - 2 * XSize) / 32768 + XSize;
      }
      Pattern = Random(0) * PATTERNS / 32768;
      SetAfPt(BabyWindow->RPort, Patterns[Pattern].Data, Patterns[Pattern].Power);
      SetOPen(BabyWindow->RPort, Random(0) * (Colors - 1) / 32768 + 1);
      SetAPen(BabyWindow->RPort, Random(0) * (Colors - 1) / 32768 + 1);
      SetBPen(BabyWindow->RPort, Random(0) * (Colors - 1) / 32768 + 1);
      Type = Random(0) * 5 / 32768;
      switch (Type)
      {
	case 0:
	     Circle(BabyWindow->RPort, YSize, X, Y);
	     break;
	default:
	     Polygon(BabyWindow->RPort, Type + 2, YSize, X, Y);
	     break;
      }
    }
  }
  CleanUp(FALSE);
}

Erase(RPort)
struct RastPort *RPort;

/********************\
*       Erase        *
*       -----        *
*      Clear the KeyBang window.  I know that there are more efficient ways of
* doing this but this isn't too bad and that's how I coded it.
\********************/

{
  Move(RPort, 0, 0);
  SetDrMd(RPort, JAM1);
  SetAfPt(RPort, Patterns[0].Data, Patterns[0].Power);
  SetOPen(RPort, 0);
  SetAPen(RPort, 0);
  ClearScreen(RPort);
  SetDrMd(RPort, JAM2);
}

Polygon(RPort, Vertices, Size, X, Y)
struct RastPort *RPort;
int Size, X, Y;

/********************\
*      Polygon       *
*      -------       *
*      Draw a polygon with "Vertices" vertices and "Size" pixels high centered
* at "X", "Y".  Polygons are corrected for the aspect ratio of the screen.  The
* AreaInfo and TmpRas structures of the window into which the polygon is to
* be drawn must be properly initialized.  See Initialize.
\********************/

{
  int Phase, XPos, YPos, i;

  Phase = Random(0) * SINETABENTRIES / 32768;
  XPos = Size * SineTab[Phase] / 128;
  YPos = Size * SineTab[(Phase + SINETABENTRIES / 4) % SINETABENTRIES] / 128;
  AreaMove(RPort, X + 2 * XPos * XDPM / YDPM, Y + YPos);
  for (i = 1; i < Vertices; i++)
  {
    XPos = Size * SineTab[(i * SINETABENTRIES / Vertices + Phase)
			  % SINETABENTRIES] / 128;
    YPos = Size * SineTab[(i * SINETABENTRIES / Vertices + Phase +
			   SINETABENTRIES / 4) % SINETABENTRIES] / 128;
    AreaDraw(RPort, X + 2 * XDPM * XPos / YDPM, Y + YPos);
  }
  AreaEnd(RPort);
}

int Circle(RPort, Size, X, Y)
struct RastPort *RPort;
int Size, X, Y;

/********************\
*       Circle       *
*       ------       *
*      Draw a circle "Size" pixels high centered at "X", "Y".  Circles are
* corrected for the aspect ratio of the screen.  The AreaInfo and TmpRas
* structures of the window into which the circle is to be drawn must be
* properly initialized.  See Initialize.
\********************/

{
  AreaEllipse(RPort, X, Y, 2 * Size * XDPM / YDPM, Size);
  AreaEnd(RPort);
}

 
Initialize()

/********************\
*     Initialize     *
*     ----------     *
*      Allocates all of the resources that are used by the program.  If some
* of the resources are not available, this routine does not return.
\********************/

{
  struct NewWindow NewWindow;
  struct NewScreen NewScreen;
  SHORT *VectorTable;
  static UBYTE AudioChannels[] = {3, 5, 10, 12};
  extern struct Library *SysBase;
  int i, Width, Length;
  struct Process *ThisProcess;

  /* Open libraries */

  IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 29);
  if (IntuitionBase == NULL)
    CleanUp(FALSE);
  GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 29);
  if (GfxBase == NULL)
    CleanUp(FALSE);

  /* Get display size and aspect ratio from GfxBase */

  XDPM = GfxBase->NormalDPMX;
  YDPM = GfxBase->NormalDPMY;
  WinX = GfxBase->NormalDisplayColumns;
  WinY = GfxBase->NormalDisplayRows;

  /* Open the screen */

  NewScreen.LeftEdge = 0;
  NewScreen.TopEdge = 0;
  NewScreen.Width = WinX;
  NewScreen.Height = WinY;
  NewScreen.Depth = Depth;
  NewScreen.DetailPen = 1;
  NewScreen.BlockPen = 1;
  NewScreen.ViewModes = HIRES;
  NewScreen.Type = CUSTOMSCREEN;
  NewScreen.Font = NULL;
  NewScreen.DefaultTitle = (UBYTE *)ScreenTitle;
  NewScreen.Gadgets = NULL;
  NewScreen.CustomBitMap = NULL;
  if ((BabyScreen = (struct Screen *)OpenScreen(&NewScreen)) == NULL)
    CleanUp(FALSE);

  /* Open the window */

  NewWindow.LeftEdge = 0;
  NewWindow.TopEdge = 0;
  NewWindow.Width = WinX;
  NewWindow.Height = WinY;
  NewWindow.DetailPen = 1;
  NewWindow.BlockPen = 1;
  NewWindow.IDCMPFlags = MOUSEBUTTONS;
  NewWindow.Flags = SIMPLE_REFRESH | BORDERLESS | ACTIVATE | BACKDROP | RMBTRAP;
  NewWindow.FirstGadget = NULL;
  NewWindow.CheckMark = NULL;
  NewWindow.Title = NULL;
  NewWindow.Screen = BabyScreen;
  NewWindow.BitMap = NULL;
  NewWindow.MinWidth = WinX;
  NewWindow.MinHeight = WinY;
  NewWindow.MaxWidth = WinX;
  NewWindow.MaxHeight = WinY;
  NewWindow.Type = CUSTOMSCREEN;
  LoadRGB4(&BabyScreen->ViewPort, &ColorTable[0], 1 << Depth);
  if ((BabyWindow = (struct Window *)OpenWindow(&NewWindow)) == NULL)
    CleanUp(FALSE);

  /* Make system requexters come up on this screen */

  ThisProcess = (struct Process *)FindTask(0);
  OldWindow = ThisProcess->pr_WindowPtr;
  ThisProcess->pr_WindowPtr = (APTR)BabyWindow;

  /* Hide the screen title bar */

  ShowTitle(BabyScreen, FALSE);

  /* Open the message window */

  Width = 0;
  for (i = 0; i < MESSAGELINES; i++)
  {
    Length = TextLength(BabyWindow->RPort, Message[i], strlen(Message[i]));
    if (Length + 16 > Width) Width = Length + 16;
  } 
  NewWindow.LeftEdge = WinX / 2 - Width / 2;
  NewWindow.TopEdge = 30;
  NewWindow.Width = Width;
  NewWindow.Height = 100;
  NewWindow.IDCMPFlags = NULL;
  NewWindow.Flags = SIMPLE_REFRESH;
  NewWindow.Title = (UBYTE *)ScreenTitle;
  NewWindow.MinWidth = Width;
  NewWindow.MinHeight = 100;
  NewWindow.MaxWidth = Width;
  NewWindow.MaxHeight = 100;
  if (IntroWindow = (struct Window *)OpenWindow(&NewWindow))
  {
    SetAPen(IntroWindow->RPort, 1);
    for (i = 0; i < MESSAGELINES; i++)
    {
      Length = TextLength(IntroWindow->RPort, Message[i], strlen(Message[i]));
      Move(IntroWindow->RPort, Width / 2 - Length / 2, 20 + 10 * i);
      Text(IntroWindow->RPort, Message[i], strlen(Message[i]));
    }
  }
  /*  Open the AreaInfo and TmpRas structures used by the drawing routines */

  BabyWindow->RPort->TmpRas =
	      (struct TmpRas *)AllocRemember(&MemList, sizeof(struct TmpRas), NULL);
  if (!BabyWindow->RPort->TmpRas)
    CleanUp(FALSE);
  if (!(BabyTmpRaster = AllocRaster(WinX, WinY)))
    CleanUp(FALSE);
  InitTmpRas(BabyWindow->RPort->TmpRas, BabyTmpRaster, RASSIZE(WinX, WinY));
  BabyWindow->RPort->AreaInfo =
	  (struct AreaInfo *)AllocRemember(&MemList, sizeof(struct AreaInfo), NULL);
  if (!BabyWindow->RPort->AreaInfo)
    CleanUp(FALSE);
  if ((VectorTable = (SHORT *)AllocRemember(&MemList, VECTORS * 5, NULL))== NULL)
    CleanUp(FALSE);
  InitArea(BabyWindow->RPort->AreaInfo, VectorTable, VECTORS);

  /* Make the IO reply port for audio and input IO */

  if ((IOReplyPort = CreatePort("KeyBangIO", 0)) == NULL)
    CleanUp(FALSE);

  /* Make the port to receive messages from the input handler */

  if ((KeyPort = CreatePort("KeyBangKeys", 0)) == NULL)
    CleanUp(FALSE);  

  /* Open the audio device */

  LoadSounds();

  if (Sounds)
  {
    AudioMsg = (struct IOAudio *)AllocRemember(&MemList, sizeof(struct IOAudio),
					       MEMF_PUBLIC | MEMF_CLEAR);
    if (AudioMsg == NULL)
      CleanUp(FALSE);
    AudioMsg->ioa_Request.io_Message.mn_ReplyPort = IOReplyPort;
    AudioMsg->ioa_Request.io_Command = ADCMD_ALLOCATE;
    AudioMsg->ioa_Request.io_Flags = ADIOF_NOWAIT;
    AudioMsg->ioa_Data = AudioChannels;
    AudioMsg->ioa_Length = sizeof(AudioChannels);
    if (OpenDevice(AUDIONAME, 0 , AudioMsg, 0) == 0)
    {
      Audio = TRUE;
      Unit[0] = (int)AudioMsg->ioa_Request.io_Unit & 9;
      Unit[1] = (int)AudioMsg->ioa_Request.io_Unit & 6;
    }
  }

  /* Open the input device and install the input handler */

  if (!(InputRequest = CreateStdIO(IOReplyPort)))
    CleanUp(FALSE);
  if (OpenDevice("input.device", 0, InputRequest, 0))
    CleanUp(FALSE);
  HandlerData[0] = (int)KeyPort;
  HandlerData[1] = (int)SysBase;
  HandlerInfo.is_Data = (APTR)&HandlerData[0];
  HandlerInfo.is_Code = BabyInputHandler;
  HandlerInfo.is_Node.ln_Name = ScreenTitle;
  HandlerInfo.is_Node.ln_Pri = 75;
  InputRequest->io_Command = IND_ADDHANDLER;
  InputRequest->io_Data = (APTR)&HandlerInfo;
  DoIO(InputRequest);
}

LoadSounds()

/********************\
*     LoadSounds     *
*     ----------     *
*      Look into the sound directory and examine all the files.  Those which
* contain IFF 8SVX samples are loaded into an array so that they can be 
* played by MakeSound.
\********************/

{
  int SoundLength;
  struct FileLock *SoundLock;
  struct FileInfoBlock *FileInfoBlock;
  struct FileHandle *File;
  UBYTE *Buffer;
  char FileName[128];
  struct IFFHeader Header;
  struct SampleInfo SampleInfo;

  Sound = (struct SoundInfo **)AllocRemember(&MemList, 100 * 4, NULL);
  SoundLock = Lock(SOUNDDIRNAME, ACCESS_READ);
  if (!SoundLock)
    return;
  FileInfoBlock = (struct FileInfoBlock *)AllocMem(sizeof(struct FileInfoBlock), NULL);
  Examine(SoundLock, FileInfoBlock);
  while (ExNext(SoundLock, FileInfoBlock))
  {
    strcpy(FileName, SOUNDDIRNAME);
    strcat(FileName, FileInfoBlock->fib_FileName);
    File = Open(FileName, MODE_OLDFILE);
    if (File)
    {
      Read(File, &Header, 8);
      if (Header.Name == FORM)
      {
	Read(File, &Header, 4);
	if (Header.Name == EIGHT_SVX)
	{
	  while (Read(File, &Header, 8) > 0)
	  {
	    switch (Header.Name)
	    {
	      case VHDR:
		   Read(File, &SampleInfo, Header.Length);
		   break;
	      case BODY:
		   Buffer = (UBYTE *)AllocRemember(&MemList, Header.Length,
						   MEMF_CHIP | MEMF_PUBLIC);
		   if (!Buffer) return;
		   Read(File, Buffer, Header.Length);
		   SoundLength = Header.Length;
		   break;
	      default:
		   Seek(File, Header.Length, OFFSET_CURRENT);
		   break;
	    }
	  }
	  Sound[Sounds] =(struct SoundInfo *)AllocRemember(&MemList,
						sizeof(struct SoundInfo), NULL); 
	  if (!Sound[Sounds]) CleanUp(FALSE);
	  Sound[Sounds]->Buffer = Buffer;
	  Sound[Sounds]->SamplePeriod = CLOCK / SampleInfo.SampleRate;
	  Sound[Sounds]->Volume = SampleInfo.Volume / 1024;
	  Sound[Sounds]->Length = SoundLength;
	  Sounds++;
	}
      }
    }
    Close(File);
  }
  UnLock(SoundLock);
  FreeMem(FileInfoBlock, sizeof(struct FileInfoBlock));
}

MakeSound(Sample)
int Sample;

/********************\
*     MakeSound      *
*     ---------      *
*    Play the sound "Sample". "Sample" is an index into the array of
* sound samples loaded by LoadSounds.  If one of the two channels opened by
* KeyBang are busy, the sound is played in the other.  If both are busy,
* no sound is played.
\********************/

{
  BYTE *SoundData;
  struct IOAudio *SoundMsg;
  int i;
  static int UnitNum;

  if (LeftSoundPlaying)
  {
    if (RightSoundPlaying)
      return;
    else
      UnitNum = 0;
  }
  else
  {
    if (RightSoundPlaying)
      UnitNum = 1;
    else
      UnitNum = UnitNum - 2 * UnitNum + 1;
  }
  if (SoundMsg = (struct IOAudio *)AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC))
  {
    SoundMsg->ioa_Request.io_Message.mn_ReplyPort = IOReplyPort;
    SoundMsg->ioa_Request.io_Device = AudioMsg->ioa_Request.io_Device;
    SoundMsg->ioa_Request.io_Unit = (struct Unit *)Unit[UnitNum];
    SoundMsg->ioa_Request.io_Command = CMD_WRITE;
    SoundMsg->ioa_Request.io_Flags = ADIOF_PERVOL;
    SoundMsg->ioa_AllocKey = AudioMsg->ioa_AllocKey;
    SoundMsg->ioa_Length = Sound[Sample]->Length;
    SoundMsg->ioa_Period = Sound[Sample]->SamplePeriod;
    SoundMsg->ioa_Volume = 64;
    SoundMsg->ioa_Cycles = 1;
    SoundMsg->ioa_Data = Sound[Sample]->Buffer;
    BeginIO(SoundMsg);
    if (Unit[UnitNum] & 9)
      RightSoundPlaying = TRUE;
    if (Unit[UnitNum] & 6)
      LeftSoundPlaying = TRUE;
  }
}

CleanUp(Flag)
BOOL Flag;

/********************\
*      CleanUp       *
*      -------       *
*      Deallocate all resources.  I have tried to reply to all messages
* which may be waiting on ports before deleting them.
\********************/
{
  struct Message *TempPointer;
  struct Process *ThisProcess;

  if (Flag)
    puts("Usage : KeyBang [-colors {2|4|8|16}] [-oneshape]");
  if (InputRequest)
  {
    InputRequest->io_Command = IND_REMHANDLER;
    InputRequest->io_Data = (APTR)&HandlerInfo;
    DoIO(InputRequest);
    CloseDevice(InputRequest);
    DeleteStdIO(InputRequest);
  }
  if (Audio) CloseDevice(AudioMsg);
  if (IOReplyPort)
    DeletePort(IOReplyPort);
  if (KeyPort)
  {
    while (TempPointer = GetMsg(KeyPort))
      FreeMem(TempPointer, sizeof(struct MyInputEvent));
    DeletePort(KeyPort);
  }
  if (BabyTmpRaster) FreeRaster(BabyTmpRaster, WinX, WinY);
  if (IntroWindow) CloseWindow(IntroWindow);
  if (BabyWindow)
  {
    ThisProcess = (struct Process *)FindTask(0);
    ThisProcess->pr_WindowPtr = OldWindow;
    CloseWindow(BabyWindow);
  }
  if (BabyScreen) CloseScreen(BabyScreen);
  if (MemList) FreeRemember(&MemList, TRUE);
  if (GfxBase) CloseLibrary(GfxBase);
  if (IntuitionBase) CloseLibrary(IntuitionBase);
  exit(0);
}

int Random(UserSeed)
unsigned long int UserSeed;

{
  static unsigned long int Seed;

  if (UserSeed) Seed = UserSeed;
  Seed = Seed * 1103515245 + 12345;
  return (unsigned int)(Seed/65536) % 32768;
}
