Jump to content
IGNORED

The OS7 Journey, let's use it!


tschak909

Recommended Posts

Yup, I had disassembled that, as well:

        ram:8288 cd fa 97        CALL       FUN_ram_97fa                                     undefined FUN_ram_97fa()
        ram:828b 3e 00           LD         A,0x0
        ram:828d 11 00 00        LD         DE,0x0
        ram:8290 fd 21 12 00     LD         IY,0x12
        ram:8294 cd be 1f        CALL       PUT_VRAM                                         void PUT_VRAM(uchar table, ushor
        ram:8297 3e 10           LD         A,0x10
        ram:8299 cd c1 1f        CALL       INIT_SPR_ORDER                                   void INIT_SPR_ORDER(byte sprite_
        ram:829c cd 6c 85        CALL       RESET_GAME_STATE                                 undefined RESET_GAME_STATE()
        ram:829f 01 02 00        LD         BC,0x2
        ram:82a2 cd d9 1f        CALL       WRITE_REGISTER                                   void WRITE_REGISTER(uchar regist
        ram:82a5 01 00 07        LD         BC,0x700
        ram:82a8 cd d9 1f        CALL       WRITE_REGISTER                                   void WRITE_REGISTER(uchar regist
        ram:82ab 3e 00           LD         A,0x0
        ram:82ad 21 00 1c        LD         HL,0x1c00
        ram:82b0 cd b8 1f        CALL       INIT_TABLE                                       void INIT_TABLE(uchar table, ush
        ram:82b3 3e 01           LD         A,SPRITE_GENERATOR_TABLE
        ram:82b5 21 00 38        LD         HL,0x3800
        ram:82b8 cd b8 1f        CALL       INIT_TABLE                                       void INIT_TABLE(uchar table, ush
        ram:82bb 3e 02           LD         A,PATTERN_NAME_TABLE
        ram:82bd 21 00 18        LD         HL,0x1800
        ram:82c0 cd b8 1f        CALL       INIT_TABLE                                       void INIT_TABLE(uchar table, ush
        ram:82c3 3e 03           LD         A,PATTERN_GENERATOR_TABLE
        ram:82c5 21 00 20        LD         HL,0x2000
        ram:82c8 cd b8 1f        CALL       INIT_TABLE                                       void INIT_TABLE(uchar table, ush
        ram:82cb 3e 04           LD         A,PATTERN_COLOR_TABLE
        ram:82cd 21 00 00        LD         HL,0x0
        ram:82d0 cd b8 1f        CALL       INIT_TABLE                                       void INIT_TABLE(uchar table, ush
        ram:82d3 c3 35 97        JP         TURN_ON_DISPLAY_WITH_SIZE1_SPRITES               undefined TURN_ON_DISPLAY_WITH_S
                             -- Flow Override: CALL_RETURN (CALL_TERMINATOR)

 

Which turns into:


void DKONG_INIT_VDP(void)

{
  undefined *data;
  
  SET_UP_SOUND();
  data = &SPRITE_ORDER_TABLE_;
  FUN_ram_97fa(0xd0);
  PUT_VRAM('\0',0,data,0x12);
  INIT_SPR_ORDER(0x10);
  RESET_GAME_STATE();
  WRITE_REGISTER('\0','\x02');
  WRITE_REGISTER('\a','\0');
  INIT_TABLE(SPRITE_ATTRIBUTE_TABLE,0x1c00);
  INIT_TABLE(SPRITE_GENERATOR_TABLE,0x3800);
  INIT_TABLE(PATTERN_NAME_TABLE,0x1800);
  INIT_TABLE(PATTERN_GENERATOR_TABLE,0x2000);
  INIT_TABLE(PATTERN_COLOR_TABLE,0);
  TURN_ON_DISPLAY_WITH_SIZE1_SPRITES();
  return;
}

 

-Thom

Edited by tschak909
Link to comment
Share on other sites

9 minutes ago, Kiwi said:

The pattern table are usually loaded to 0x0000, name table at 0x1800, sprite attribute at 0x1b00, color table at 0x2000. And sprite pattern table at 0x3800.  0x1c00 is usually used for 2nd name table when you want to double buffer graphics.

that's how I have it set up in the test program, this is different than Donkey Kong itself.

-Thom

 

  • Like 1
Link to comment
Share on other sites

Complex objects are now in OS7LIB. This allows you to create composite objects out of semi mobile and sprite objects very easily, and position them relative to each other on a per-frame basis.

(for some  reason, donkey kong's color generators aren't lining up, I had to do a quick hack. will investigate why.)

 

The data tables that I am talking about below, are here:

https://github.com/tschak909/os7lib/blob/main/examples/complex_object/src/donkey_kong_complex_object.h

 

To implement this is exactly the same pattern as the other objects. We need FRAME, GRAPHICS, and STATUS objects, while the complex object also adds an OFFSET object to specify where on the metaplane the sprite needs to be, relative to its top-left most point.

 

Donkey Kong is a complex object, made up of two components, his head (face), which is component 0, and his body, which is component 1. I snipped pictures of those earlier in the thread.

 

The frame objects:

typedef unsigned char COMFrame;

const COMFrame donkey_kong_frame0[2] =
  {
    0x00, // Component 0 uses frame 0 of child object
    0x00, // Component 1 uses frame 0 of child object
  };

const COMFrame donkey_kong_frame1[2] =
  {
    0x01, // Component 0 uses frame 0 of child object
    0x01, // Component 1 uses frame 0 of child object
  };

const COMFrame donkey_kong_frame2[2] =
  {
    0x02, // Component 0 uses frame 0 of child object
    0x02, // Component 1 uses frame 0 of child object
  };

There is a frame object for each frame, like all the other objects, and each one contains a frame number for each component in the compound object.

 

Then there are the offset objects:

// These are the OFFSET objects, which specify where to place each child component on the metaplane
// RELATIVE to the position requested by PUTOBJ

/**
 * @brief A Complex offset. X,Y coordinate pairs
 */
typedef struct
{
  unsigned short x;
  unsigned short y;
} COMOffset;

const COMOffset donkey_kong_offset0[2] =
  {
    {
      16, // frame 0 component 0 rX position
      0   // frame 0 component 0 rY position
    },
    {
      0,  // frame 0 component 1 rX position
      0   // frame 0 component 1 rY position
    }
  };

const COMOffset donkey_kong_offset1[2] =
  {
    {
      0,  // frame 1 component 0 rX position
      0   // frame 1 component 0 rY position
    },
    {
      0,  // frame 1 component 1 rX position
      0   // frame 1 component 1 rY position
    }
  };

const COMOffset donkey_kong_offset2[2] =
  {
    {
      32, // frame 2 component 0 rX position
      0   // frame 2 component 0 rY position
    },
    {
      0,  // frame 2 component 1 rX position
      0   // frame 2 component 1 rY position
    }
  };

 

These offsets are mostly 0, so they are placed at the top most extent of where PUT_OBJ is placing its object, but you can see on frames 0 and 2 that Donkey Kong's head is shifted to the right 16, and 32 pixels respectively, so that his face lines up with the body.

 

Once we have these, we can populate our GRAPHICS object, specifying which frames and offsets pair up for each frame, and how many graphic components are in the complex object:

 

/**
 * @brief A Complex graphics object with n number components
 */
#define COMGraphics(n)                                                  \
  typedef struct                                                        \
  {                                                                     \
    unsigned char obj_type;                                             \
    struct                                                              \
    {                                                                   \
      void *frame_obj;                                                  \
      void *offset_obj;                                                 \
    } ptrs[n];                                                          \
  }                                                                     \

COMGraphics(3) DonkeyKongGraphics;
const DonkeyKongGraphics donkey_kong_graphics =
  {
    0x24,  // This is a complex object (4) with two (2) components.
    {
      {
        donkey_kong_frame0,
        donkey_kong_offset0
      },
      {
        donkey_kong_frame1,
        donkey_kong_offset1
      },
      {
        donkey_kong_frame2,
        donkey_kong_offset2
      }
    }
  };

 

Of course, we need a STATUS object so that we can move and specify the frames for PUT_OBJ to display. Since this object's internal state is only to render other objects, no other bits of info are needed except for frame, x, and y.

 

/**
 * @brief a status object for COMPLEX objects
 */
typedef struct
{
  unsigned char frame;
  unsigned short x;
  unsigned short y;
} COMStatus;

COMStatus donkey_kong_status;

 

And finally, a COMObj top level object to tie everything together that PUT_OBJ can use:

/**
 * @brief a Complex top-level object with n number components
 */
#define COMObj(n)                               \
  typedef struct                                \
  {                                             \
    void *graphics;                             \
    void *status;                               \
    void *components[n];                        \
  }                                             \

  
COMObj(2) DonkeyKongObj;
const DonkeyKongObj donkey_kong_obj =
  {
    donkey_kong_graphics,
    donkey_kong_status,
    {
      donkey_kong_head_obj,
      donkey_kong_body_obj
    }
  };

 

This points to the SprObj (donkey_kong_head_obj), and the SMO (donkey_kong_body_obj), and thus tie the whole thing together.

 

Once this is defined, you can ACTIVATE Donkey kong (move all his tiles into both the sprite and background tables) by doing:

  activate(donkey_kong_obj,false);

set where you want him to appear, and which frame:

  donkey_kong_status.x=0xb8;
  donkey_kong_status.y=0x10;
  donkey_kong_status.frame=0; // facing front

and finally put him onto the name table and sprite attributes table:

      put_obj(donkey_kong_obj,false);

 

While building the data tables can seem daunting, it pays off in ease of use and manipulation, and I applaud Coleco for trying to make a decent object oriented graphics package in an 8K ROM.

 

-Thom 

complex_object.rom

Edited by tschak909
Link to comment
Share on other sites

Current status, pouring over Coleco's sound package documentation. Goal here is to create an example which can play two different songs

 

image.thumb.png.a68214b73b41fd538949845647b2a2be.png

 

Bach's Invention 4 in D minor 

and Bach's Invention 8 in F Major

Both of which can be done quite nicely in 2 sound channels.

 

I did the Invention in D previously as a demonstration for the Atari Music System (AMS) composition process:

 

-Thom

Edited by tschak909
Link to comment
Share on other sites

Now that I've moved onto the sound generator package, I need to make some tools to help convert "notes" into the sound register data that the sound chip can understand. 

 

The sound player can support up to four different tone note types, and two white noise note types, and for this first demo, I will be using type 0, which has notes of a fixed pitch, and duration for the entire note.

 

According to the OS7 manual appendix C, type zero notes have the following format:

image.thumb.png.bd6cd3b2c7f26efa9bb152c01020a083.png

image.thumb.png.6b4ffa552175dc2d6fcf4b270a88b436.png

This means that for each note (that is NOT a rest, that needs another note type, and I need to do a tool for that type), there are 4 bytes that need to be defined. Rather than break out a calculator and shift the values into their correct places, I bootstrapped a little tool in C to help generate the correct values. It looks like this:

 

/**
 * @brief Simple tool to generate a type 0 note (fixed duration, fixed attenuation)
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see LICENSE for details.
 */

#include <stdlib.h>
#include <stdio.h>

#define REQUIRED_ARGC 4

typedef struct _type0_note_channel
{
  unsigned char channel_reserved : 6;
  unsigned char channel : 2;
} Type0NoteChannel;

typedef struct _type0_note_freq_atn
{
  unsigned short frequency : 10;  
  unsigned char frequency_reserved : 2;
  unsigned char atn : 4;
} Type0NoteFreqAtn;

Type0NoteChannel note_channel;
Type0NoteFreqAtn note_freq_atn;
unsigned char len;

int main(int argc, char* argv[])
{
  unsigned char *fp=NULL, *cp=NULL; // pointers to the bit packed fields
  
  if (argc < REQUIRED_ARGC) // display message if not enough params, and exit
    {
      printf("%s <channel-0-to-3> <freq-in-hz> <atn> <duration-in-frames>\n",argv[0]);
      exit(1);
    }

  // Fill out the fields
  note_channel.channel    = atoi(argv[1]);
  note_freq_atn.frequency = atoi(argv[2]);
  note_freq_atn.atn       = atoi(argv[3]);
  len                     = atoi(argv[4]);

  // Set byte sized pointers to note_channel and note_freq_atn
  // so we can easily output them as bytes in little endian order
  cp = (unsigned char *)&note_channel;
  fp = (unsigned char *)&note_freq_atn;

  // Print the result
  printf("0x%02x, 0x%02x, 0x%02x, 0x%02x\n",cp[0], fp[0], fp[1], len);

  // and exit
  return 0;
}

 

It can be fetched from here:

https://github.com/tschak909/os7lib/blob/main/tools/note_type_0.c

 

and can be built simply by:

$ gcc -onote_type_0 note_type_0.c

 

This is an example run showing the note data bytes required for a middle C (262Hz), played on channel 1, at close to full volume (atn 1 = -2dB) for 16 frames.

$ ./note_type_0 1 262 1 16
0x40, 0x06, 0x11, 0x10

 

The output is in C format, and can easily be pasted into an array like so:

#ifndef SOUND_DATA_AREA_H
#define SOUND_DATA_AREA_H

unsigned char output0[10];

const unsigned char sound0[] =
  {
    // measure 1, 16th notes, notes are in octave above middle C, at full volume.
    0x40, 0x4b, 0x02, 0x08, // D
    0x40, 0x93, 0x02, 0x08, // E
    0x40, 0xba, 0x02, 0x08, // F
    0x40, 0x10, 0x03, 0x08, // G
    0x40, 0x70, 0x03, 0x08, // A
    0x40, 0xa4, 0x03, 0x08, // Ab

    // measure 2, 16 notes
    0x40, 0x2a, 0x02, 0x08, // Db
    0x40, 0xa4, 0x03, 0x08, // Ab
    0x40, 0x70, 0x03, 0x08, // A
    0x40, 0x10, 0x03, 0x08, // G
    0x40, 0xba, 0x02, 0x08, // F
    0x40, 0x93, 0x02, 0x08, // E
  };

const void *soundTable[] = {&sound0,&output0};

#endif /* SOUND_DATA_AREA_H */

 

Next post will be about the tool I use to generate the rests.

-Thom

Link to comment
Share on other sites

This tool will generate rests of up to 31 frames (half a second), which are one byte long.

/**
 * @brief Simple tool to generate a rest of up to 31 frames.
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see LICENSE for details.
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

#define REQUIRED_ARGC 2

typedef struct _note_type_rest
{
  unsigned char duration : 5;
  bool rest : 1;
  unsigned char channel : 2;
} NoteTypeRest;

NoteTypeRest rest;

int main(int argc, char* argv[])
{
  unsigned char *rp=NULL; // pointer to the rest struct as a byte.
  
  if (argc < REQUIRED_ARGC) // display message if not enough params, and exit
    {
      printf("%s <channel-0-to-3> <1-to-31-frames>\n",argv[0]);
      exit(1);
    }

  // Fill out the fields
  rest.rest = true;
  rest.channel = atoi(argv[1]);
  rest.duration = atoi(argv[2]);

  // Set byte sized pointer to the rest struct, so we can get it as a whole byte.
  rp = (unsigned char *)&rest;

  // Print the result
  printf("0x%02x,\n",rp[0]);

  // and exit
  return 0;
}

 

It's here on github:

https://github.com/tschak909/os7lib/blob/main/examples/sound_demo/src/sound_demo.c

 

example runs

$ ./note_type_rest 1 31
0x7f,

$ ./note_type_rest 1 1
0x61,


This demonstrates one of the cases where C really shines, in its pointer operations. By casting a pointer to complex structures, we can not only easily iterate over groups of those structures (like arrays), but we can represent that structure in totally different ways simply by how we cast it.

 

In this case, we need these structures represented as the bytes that need to go into ROM, so we simply cast an unsigned char (aka a byte) pointer to the beginning of that struct, and we can read from it like an array.

 

THIS DOES MEAN THAT IF THIS IS RUN ON A BIG ENDIAN SYSTEM (like a Motorola 68000 or a PowerPC), THAT THE BYTES WILL BE REVERSED, AND THAT THE STRUCTS WOULD NEED TO BE FLIPPED UPSIDE DOWN, but we take advantage of the assumption that this is running on an x86-64 system, which is little endian, like the Z80, to make the code really simple.

 

-Thom

Link to comment
Share on other sites

Turns out, I had made a small error in my note_type_0 tool, where I was passing an absolute frequency value, rather than a frequency divisor value.

 

Since this is a divisor, this means the higher the number, the lower the pitch.

 

The F0 to F9 values mentioned above are directly mapped onto the sound chip registers, and constitute a frequency divider, the data sheet states:

image.thumb.png.e13da25351951c65fdbe12d200f068ff.png

The Colecovision runs at 3.579545MHz, which can be converted to 3579545Hz for this equation.

 

(I had a very embarrassing moment where I needed to ask those closest to me how to flip the equation around so that I could solve for n, instead of f. oops) ;)

 

Since we have 10 bits for the frequency divider, a value of 1023 (all 10 bits set), will give us:

109.345827224 = 3579545 / (32 * 1023)
  f               N              n

with all bits set, this gives a lowest frequency of 109.3Hz

 

Conversely, if we set n to 1 (we can't divide by zero), we get:

111860.78125 = 3579545 / (32 * 1)

which, yeah, 111kHz, which does indeed seem silly, because the best of human hearing tops out at 20kHz on good days, but, that's the nature of divide-down oscillators. :) Very non-linear. But all these early sound chips were like this.

 

So this establishes our bounds. Adding this function into our note_type_0 code, which also adds a bounds check so that we catch 109 as 0x3FF:

#define SYSTEM_CLOCK_IN_HZ 3579545

unsigned short freq_to_div(unsigned short f)
{
  unsigned short n = SYSTEM_CLOCK_IN_HZ / (32 * f);

  if (n>1023)
    n=1023;

  return n;
}

 

With this, we can now return to doing note transcription of the first piece, "Invention in D minor."

 

The first two measure start as:

image.thumb.png.b4663f8fdbe989575037b34efcef2ca1.png

We see the key signature of D minor, a single B flat, so we take that into consideration, 3/8ths time signature gives the 16ths notes a triplet feel, and this piece is in Allegro, approximately 96 to 100 beats per minute (adjusted to taste), and these first two measures are continuous runs of 16th notes, that climb up and down the scale, with each measure being essentially a reflection of the other.

 

Since we're using type 0 notes, which have a constant pitch and volume (attenuation) throughout the life of each note, we only need to know the pitch of each note, and its length in frames.

 

The former is easily enough derived using either a table of note names to frequencies, or by using a note to frequency calculator, such as:

https://www.omnicalculator.com/other/note-frequency

 

The latter can of course be calculated, or, in my case, I estimated and listened, and came up with a value of 8 frames for 16th notes at the desired tempo.

 

With all this information, we can break down the notes, first measure: D E F G A Bb, second measure: C# Bb A G F E.

 

What about octave?

 

This is one case where I made a judgement call.

 

The bottom most note that is possible is an A that is one octave below middle C, and this will pose a problem later in the piece, as the second part moves through the piece. So I need to transpose everything up one octave, so that it fits in the available frequency range of what the tone generators of sound chip can do (yes, there are ways around this, but they are way outside of the scope of what I am documenting here!). I take this into consideration when selecting notes to convert into frequencies, and with this in mind, I select the following frequencies for each note:

 

  • D - 587Hz (all decimals simply chopped off)
  • E - 659Hz 
  • F - 698Hz
  • G - 784Hz
  • A - 880Hz
  • Bb - 932Hz
  • C# - 554Hz

And if we use the note_type_0 tool, with the values we've come up with (and wanting to use channel 1), we get:

[thomc@TMA-2 tools]$ ./note_type_0 
./note_type_0 <channel-0-to-3> <freq-in-hz> <atn> <duration-in-frames>
[thomc@TMA-2 tools]$ ./note_type_0 1 587 0 8
0x40, 0xbe, 0x00, 0x08,
[thomc@TMA-2 tools]$ ./note_type_0 1 659 0 8
0x40, 0xa9, 0x00, 0x08,
[thomc@TMA-2 tools]$ ./note_type_0 1 698 0 8
0x40, 0xa0, 0x00, 0x08,
[thomc@TMA-2 tools]$ ./note_type_0 1 784 0 8
0x40, 0x8e, 0x00, 0x08,
[thomc@TMA-2 tools]$ ./note_type_0 1 880 0 8
0x40, 0x7f, 0x00, 0x08,
[thomc@TMA-2 tools]$ ./note_type_0 1 932 0 8
0x40, 0x78, 0x00, 0x08,
[thomc@TMA-2 tools]$ ./note_type_0 1 554 0 8
0x40, 0xc9, 0x00, 0x08,

 

Arranging those values into the sound data area, we get this for the first two measures:

const unsigned char sound0[] =
  {
    // measure 1, 16th notes, notes are in octave above middle C, at full volume.
    0x40, 0xbe, 0x00, 0x08, // D
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x78, 0x00, 0x08, // Ab

    // measure 2, also 16th notes, a reflection of the previous measure.
    0x40, 0xc9, 0x00, 0x08, // Db
    0x40, 0x78, 0x00, 0x08, // Ab
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0xa9, 0x00, 0x08, // E
    0x00, // end of song (so it doesn't go off into the weeds)
  };

 

The sound package also needs temporary areas to hold output values, We provide four of them here:

unsigned char output0[10], output1[10], output2[10], output3[10];

 

And we build the top level table which maps sounds to output areas:

const void *soundTable[] = {&sound0,&output0,&sound0,&output1,&sound0,&output2,&sound0,&output3};

 

With that, we just need a main program to use and play the data.

/**
 * @brief Sound Demo 
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see COPYING for details
 */

#include <os7.h>
#include <interrupt.h>
#include <arch/z80.h>
#include <intrinsic.h>
#include <stdbool.h>
#include "sound_data_area.h"

const char signon[32]="OS7 SOUND DEMONSTRATION";

static void nmi()
{
  M_PRESERVE_ALL;
  play_songs();
  sound_man();
  VDP_STATUS_BYTE = read_register();
  M_RESTORE_ALL;
}

void main(void)
{
  mode_1();
  sound_init(4,&soundTable);
  add_raster_int(nmi);
  load_ascii();
  fill_vram(0x2000,32,0xF4);
  write_register(0x07,0x04);
  put_vram(PATTERN_NAME_TABLE,0x04,signon,sizeof(signon));
  write_register(0x01,0xE0);
  play_it(1);
  while(1);
}

 

So to use what we've made, we have to place, in this order:

play_songs();
sound_man();

into our NMI routine, so that the sound engine can be updated at every frame.

 

and we need to add a call to sound_init() before the nmi is attached:

sound_init(4,&soundTable);

This indicates that 4 output areas are defined, which all currently point to sound0, which is our first tune.

 

And a single call to play_it, will start the tune, which runs in the NMI, so other things can happen:

  play_it(1);

 

Compile that, and we get:

 

(ROM also attached)

sound_demo.rom

 

 

 

Link to comment
Share on other sites

I'm just curious about the DK example you gave.

This falls into my work with removing these PASCAL call entries from Smurf Rescue and replacing them with the standard routines.

When calling the BIOS functions from a higher level language, in your case, I noticed something.
 

When you call the BIOS functions it, you, or the compiler, puts the BIOS jump address into HL, then passes it to the stack then onto IY then.....

There is a lot of overhead just to do a simple call.

Is this the way C reconciles with merging with assembly code or was this a routine you came up with on your own.

 

Edited by Captain Cozmos
Link to comment
Share on other sites

1 hour ago, Captain Cozmos said:

I'm just curious about the DK example you gave.

This falls into my work with removing these PASCAL call entries from Smurf Rescue and replacing them with the standard routines.

When calling the BIOS functions from a higher level language, in your case, I noticed something.
 

When you call the BIOS functions it, you, or the compiler, puts the BIOS jump address into HL, then passes it to the stack then onto IY then.....

There is a lot of overhead just to do a simple call.

Is this the way C reconciles with merging with assembly code or was this a routine you came up with on your own.

 

it is all about parameter marshalling, that is, getting the parameters from the function declaration, and putting them where they can be dealt with. Usually, these parameters go onto the stack, so they can be generically iterated.

 

You can cut some of that overhead if you specify a fast-call convention to use registers where possible, but then the compiler explicitly decides what registers to use.

 

So yeah, just one of those things we have to take on the chin while using high level languages with arbitrary assembly language routines.

 

-Thom

Link to comment
Share on other sites

A Day and a half later, and the transcription of Bach's Invention in D is finished. Both a video and the ROM are attached.

ROM: Bach_Invention_in_D_Minor_BWV_775.rom

Source code: https://github.com/tschak909/os7lib/tree/main/examples/sound_demo/src

 

And the piece transcribed was Bach Invention in D Minor (catalog #775 in the BMV):

image.thumb.png.ea899089cefde6b200e2c7c71c5b59c9.png

The piece itself uses nothing but type 0 notes, this is why the notes themselves have no volume envelope. For this demonstration, it's sufficient.

 

sound_demo.c contains:

 

/**
 * @brief Sound Demo 
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see COPYING for details
 */

#include <os7.h>
#include <interrupt.h>
#include <arch/z80.h>
#include <intrinsic.h>
#include <stdbool.h>
#include "sound_data_area.h"

const char signon[32]="OS7 SOUND DEMONSTRATION";
const char title1[]="INVENTION #4";
const char title2[]="IN D MINOR";
const char title3[]="BY J.S. BACH";
const char title4[]="BWV 775";
const char title5[]="TRANSCRIBED IN 2023 BY";
const char title6[]="THOMAS CHERRYHOMES";

static void nmi()
{
  M_PRESERVE_ALL;
  play_songs();
  sound_man();
  VDP_STATUS_BYTE = read_register();
  M_RESTORE_ALL;
}

void main(void)
{
  mode_1();
  sound_init(4,&soundTable);
  add_raster_int(nmi);
  load_ascii();
  fill_vram(0x2000,32,0xF4);
  write_register(0x07,0x04);
  put_vram(PATTERN_NAME_TABLE,0x04,signon,sizeof(signon));
  put_vram(PATTERN_NAME_TABLE,137,title1,sizeof(title1));
  put_vram(PATTERN_NAME_TABLE,170,title2,sizeof(title2));
  put_vram(PATTERN_NAME_TABLE,297,title3,sizeof(title3));
  put_vram(PATTERN_NAME_TABLE,363,title4,sizeof(title4));
  put_vram(PATTERN_NAME_TABLE,644,title5,sizeof(title5));
  put_vram(PATTERN_NAME_TABLE,678,title6,sizeof(title6));
  write_register(0x01,0xE0);
  play_it(1);
  play_it(2);
  while(1);
}

 

There is enough here, to set up the NMI, put the sign-on text on the screen, and launch the tune, which, as you can see is fired by two seperate play_it() statements, one for each hand. This works, because play_it will set up the necessary song pointers before the NMI runs, so that they both start at the same time.

 

The sound init, needs a minimum of 4 sound data areas, and a pointer to its table. Since we only need two parts, the other two sound areas simply have an END marker (0xFF), so they don't ever play anything.

const void *soundTable[] = {&sound0,&output0,&sound1,&output1,&sound2,&output2,&sound2,&output3};

But we do need to make sure that there are output areas defined for each of these data areas, too:

unsigned char output0[10], output1[10], output2[10], output3[10];

And we need to make sure that the two sound data areas we aren't using, are reasonably filled with a STOP.

const unsigned char sound2[] = {0x00};

Because sound areas 3 and 4 need to play the same thing, we can simply point them at the same area, and everything is okay.

 

Finally we have the sound data to go into each output, one data area for each hand:

 

const unsigned char sound0[] =
  {
    // measure 1, 16th notes, notes are in octave above middle C, at full volume.
    0x40, 0xbe, 0x00, 0x08, // D
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x78, 0x00, 0x08, // Ab

    // measure 2, also 16th notes, a reflection of the previous measure.
    0x40, 0xc9, 0x00, 0x08, // Db
    0x40, 0x78, 0x00, 0x08, // Ab
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0xa9, 0x00, 0x08, // E

    // measure 3, eighth notes
    0x40, 0xa0, 0x00, 0x10, // F
    0x40, 0x7f, 0x00, 0x10, // A
    0x40, 0x5f, 0x00, 0x10, // D

    // measure 4, eighth notes
    0x40, 0x5f, 0x00, 0x10, // G
    0x40, 0x32, 0x00, 0x10, // C#
    0x40, 0x2a, 0x00, 0x10, // E

    // measure 5, sixteenth notes
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x47, 0x00, 0x08, // G 
    0x40, 0x3f, 0x00, 0x08, // A 
    0x40, 0x3c, 0x00, 0x08, // Bb

    // measure 6, sixteenth notes
    0x40, 0xbe, 0x00, 0x08, // C#
    0x40, 0x3c, 0x00, 0x08, // Bb
    0x40, 0x3f, 0x00, 0x08, // A
    0x40, 0x47, 0x00, 0x08, // G
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x54, 0x00, 0x08, // E   

    // measure 7, sixteenth notes
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x47, 0x00, 0x08, // G 
    0x40, 0x3f, 0x00, 0x08, // A 

    // measure 8, sixteenth notes
    0x40, 0x78, 0x00, 0x08, // Ab
    0x40, 0x1f, 0x00, 0x08, // A
    0x40, 0x23, 0x00, 0x08, // G
    0x40, 0x28, 0x00, 0x08, // F
    0x40, 0x2a, 0x00, 0x08, // E
    0x40, 0x2f, 0x00, 0x08, // D

    // measure 9, sixteenth notes
    0x40, 0x2a, 0x00, 0x08, // E
    0x40, 0xd5, 0x00, 0x08, // C
    0x40, 0x2f, 0x00, 0x08, // D
    0x40, 0x2a, 0x00, 0x08, // E
    0x40, 0x28, 0x00, 0x08, // F
    0x40, 0x23, 0x00, 0x08, // G

    // measure 10, sixteenth notes
    0x40, 0x3f, 0x00, 0x08, // A
    0x40, 0x23, 0x00, 0x08, // G ^
    0x40, 0x25, 0x00, 0x08, // F ^
    0x40, 0x2a, 0x00, 0x08, // E ^
    0x40, 0x2f, 0x00, 0x08, // D ^
    0x40, 0x35, 0x00, 0x08, // C ^

    // Measure 11, sixteenth notes
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x50, 0x00, 0x08, // F

    // Measure 12, 8th note, two 8th rests
    0x40, 0x8e, 0x00, 0x10, // G
    0x70,                   // 8th note rest
    0x70,                   // 8th note rest

    // Measure 13, 16 notes
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x54, 0x00, 0x08, // E

    // Measure 14, 8th note, 8th rest, 8th note (tied)
    0x40, 0xa0, 0x00, 0x10, // F
    0x70,                   // 8th note rest
    0x40, 0x78, 0x00, 0x10, // Bb

    // Measure 15, 8th notes
    0x40, 0x78, 0x00, 0x10, // Bb
    0x40, 0x7f, 0x00, 0x10, // A
    0x40, 0x8e, 0x00, 0x10, // G

    // Measure 16, 16th notes
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x78, 0x00, 0x08, // Bb
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0xa9, 0x00, 0x08, // E

    // Measure 17, 2 16ths, a dotted 8th, and a 16th
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0x8e, 0x00, 0x18, // G (dotted)
    0x40, 0xa0, 0x00, 0x08, // F

    // Measure 18, 8th notes
    0x40, 0xa0, 0x00, 0x10, // F
    0x40, 0x6a, 0x00, 0x10, // C
    0x40, 0x6a, 0x00, 0x10, // C

    // Measure 19, trill
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D

    // Measure 20, trill
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D

    // Measure 21, trill
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D
    0x40, 0x6a, 0x00, 0x04, // C
    0x40, 0x5f, 0x00, 0x04, // D

    // Measure 22, 16th notes
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x78, 0x00, 0x08, // Bb
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0xa9, 0x00, 0x08, // E

    // Measure 23, 16th notes
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0xbe, 0x00, 0x08, // D
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0x97, 0x00, 0x08, // F#
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0x7f, 0x00, 0x08, // A

    // Measure 24, 16th notes
    0x40, 0x78, 0x00, 0x08, // Bb
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0xbe, 0x00, 0x08, // D

    // Measure 25, 16th notes
    0x40, 0x78, 0x00, 0x08, // Bb
    0x40, 0xd5, 0x00, 0x08, // C
    0x40, 0xbe, 0x00, 0x08, // D
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0x8e, 0x00, 0x08, // G

    // Measure 26, 16th notes
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x71, 0x00, 0x08, // Bn
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x50, 0x00, 0x08, // F

    // Measure 27, 16th notes
    0x40, 0x86, 0x00, 0x08, // G#
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x71, 0x00, 0x08, // Bn

    // Measure 28, 16th notes
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x71, 0x00, 0x08, // Bn
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x71, 0x00, 0x08, // Bn
    0x40, 0x7f, 0x00, 0x08, // A

    // Measure 29, 16th notes
    0x40, 0x86, 0x00, 0x08, // G#
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x86, 0x00, 0x08, // G#
    0x40, 0x97, 0x00, 0x08, // F# 
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0xbe, 0x00, 0x08, // D

    // Measure 30, 16th notes
    0x40, 0xd5, 0x00, 0x08, // C
    0x40, 0xbe, 0x00, 0x08, // D
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0x97, 0x00, 0x08, // F# 
    0x40, 0x86, 0x00, 0x08, // G#
    0x40, 0x7f, 0x00, 0x08, // A

    // Measure 31, 16th notes
    0x40, 0xbe, 0x00, 0x08, // D
    0x40, 0x6a, 0x00, 0x08, // C ^
    0x40, 0x71, 0x00, 0x08, // Bn
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x86, 0x00, 0x08, // G#
    0x40, 0x97, 0x00, 0x08, // F# 

    // Measure 32, 16th notes
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0x97, 0x00, 0x08, // F# 
    0x40, 0x86, 0x00, 0x08, // G#
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x71, 0x00, 0x08, // Bn
    0x40, 0x6a, 0x00, 0x08, // C

    // Measure 33, 16th notes
    0x40, 0x97, 0x00, 0x08, // F# 
    0x40, 0x54, 0x00, 0x08, // E ^
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x71, 0x00, 0x08, // Bn
    0x40, 0x7f, 0x00, 0x08, // A
    
    // Measure 34, 16th notes
    0x40, 0x86, 0x00, 0x08, // G#
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x86, 0x00, 0x08, // G#
    0x40, 0x97, 0x00, 0x08, // F# 
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0xbe, 0x00, 0x08, // D

    // Measure 35, 16th notes
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x71, 0x00, 0x08, // Bn

    // Measure 36, 4 16th notes, 1 8th note
    0x40, 0x3f, 0x00, 0x08, // A
    0x40, 0x43, 0x00, 0x08, // G#
    0x40, 0x4b, 0x00, 0x08, // F#
    0x40, 0x54, 0x00, 0x10, // E

    // Measure 37 2 16ths, dotted 8th, 1 16th
    0x40, 0x3f, 0x00, 0x08, // A
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x71, 0x00, 0x18, // Bn (dotted 8th)
    0x40, 0x7f, 0x00, 0x08, // A

    // Measure 38 Dotted 8th, 3 16ths
    0x40, 0x7f, 0x00, 0x18, // A (dotted 8th)
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x78, 0x00, 0x08, // Bb
    0x40, 0x6a, 0x00, 0x08, // C

    // Measure 39, 8th notes
    0x40, 0xbe, 0x00, 0x10, // D
    0x40, 0x97, 0x00, 0x10, // F#
    0x40, 0x7f, 0x00, 0x10, // A

    // Measure 40, 16th notes
    0x40, 0x78, 0x00, 0x08, // Bb
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x78, 0x00, 0x08, // Bb
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x5f, 0x00, 0x08, // D

    // Measure 41, 16th notes
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0x5f, 0x00, 0x08, // D ^
    0x40, 0x6a, 0x00, 0x08, // C ^
    0x40, 0x78, 0x00, 0x08, // Bb ^
    0x40, 0x7f, 0x00, 0x08, // A ^
    0x40, 0x8e, 0x00, 0x08, // G ^

    // Measure 42, 8th note, 2 16th notes, 8th note
    0x40, 0x7f, 0x00, 0x10, // A
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x50, 0x00, 0x10, // F

    // Measure 43, 2 8th notes, 8th rest
    0x40, 0x8e, 0x00, 0x10, // G
    0x40, 0x54, 0x00, 0x10, // E ^
    0x70,                   // 8th note rest

    // Measure 44, 16th notes
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x47, 0x00, 0x08, // G
    0x40, 0x3f, 0x00, 0x08, // A
    0x40, 0x3b, 0x00, 0x08, // Bb

    // Measure 45, 16th notes
    0x40, 0x64, 0x00, 0x08, // C#
    0x40, 0x3c, 0x00, 0x08, // Bb ^
    0x40, 0x3f, 0x00, 0x08, // A
    0x40, 0x47, 0x00, 0x08, // G
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x54, 0x00, 0x08, // E

    // Measure 46, 8th notes
    0x40, 0x50, 0x00, 0x10, // F
    0x40, 0x5f, 0x00, 0x10, // D
    0x40, 0x8e, 0x00, 0x10, // G

    // Measure 47, 16th notes
    0x40, 0x8e, 0x00, 0x10, // G
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x64, 0x00, 0x08, // C#
    0x40, 0x54, 0x00, 0x08, // E
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x64, 0x00, 0x08, // C#

    // Measure 48, 2 16th, 1 dotted eighth, 1 16th
    0x40, 0x50, 0x00, 0x08, // F
    0x40, 0x71, 0x00, 0x08, // Bn
    0x40, 0x64, 0x00, 0x18, // C#
    0x40, 0x5f, 0x00, 0x08, // D

    // Measure 49, 16th notes
    0x40, 0x5f, 0x00, 0x08, // D
    0x40, 0x6a, 0x00, 0x08, // C
    0x40, 0x78, 0x00, 0x08, // Bb
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x8e, 0x00, 0x08, // G
    0x40, 0xa0, 0x00, 0x08, // F

    // Measure 50, 16th notes
    0x40, 0x78, 0x00, 0x08, // Bb
    0x40, 0xc9, 0x00, 0x08, // C#
    0x40, 0xbe, 0x00, 0x08, // D
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0xa0, 0x00, 0x08, // F
    0x40, 0x8e, 0x00, 0x08, // G

    // Measure 51, 2 16ths, 1 8th, 2 16ths
    0x40, 0x7f, 0x00, 0x08, // A
    0x40, 0x5f, 0x00, 0x08, // D ^
    0x40, 0xa0, 0x00, 0x10, // F 
    0x40, 0xa9, 0x00, 0x08, // E
    0x40, 0xbe, 0x00, 0x08, // D

    // Measure 52 fermata
    0x40, 0xbe, 0x00, 0x30, // D

    
    0xFF, // end of song (so it doesn't go off into the weeds)
  };

const unsigned char sound1[] =
  {
    // Measure 1, rests
    0xa8,
    0xa8,
    0xa8,
    0xa8,
    0xa8,
    0xa8,

    // Measure 2, rests
    0xa8,
    0xa8,
    0xa8,
    0xa8,
    0xa8,
    0xa8,

    // Measure 3, 16th notes, octave above
    0x80, 0x7c, 0x01, 0x08, // D
    0x80, 0x52, 0x01, 0x08, // E
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0xfe, 0x00, 0x08, // A 
    0x80, 0xf0, 0x00, 0x08, // Bb

    // Measure 4, 16th notes, octave above
    0x80, 0x93, 0x01, 0x08, // C#
    0x80, 0xf0, 0x00, 0x08, // Bb
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x52, 0x01, 0x08, // E

    // Measure 5, 8th notes
    0x80, 0x40, 0x01, 0x10,
    0x80, 0xfe, 0x00, 0x10,
    0x80, 0xbe, 0x00, 0x10,

    // Measure 6, 8th notes
    0x80, 0x54, 0x00, 0x10,
    0x80, 0x1d, 0x01, 0x10,
    0x80, 0x93, 0x01, 0x10,

    // Measure 7, 8th notes
    0x80, 0x7c, 0x01, 0x10, // D
    0x80, 0xfe, 0x00, 0x10, // A 
    0x80, 0x40, 0x01, 0x10, // F

    // Measure 8, 8th notes
    0x80, 0x1d, 0x01, 0x10, // G
    0x80, 0xfe, 0x00, 0x10, // A
    0x80, 0xf0, 0x00, 0x10, // Bb

    // Measure 9, 8th notes
    0x80, 0xaa, 0x01, 0x10, // C
    0x80, 0xd5, 0x00, 0x10, // C ^
    0x80, 0x52, 0x01, 0x10, // E

    // Measure 10, 8th notes
    0x80, 0x40, 0x01, 0x10, // F
    0x80, 0x1d, 0x01, 0x10, // G
    0x80, 0xfe, 0x00, 0x10, // A

    // Measure 11, 16th notes
    0x80, 0xf0, 0x00, 0x08, // Bb
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0xf0, 0x00, 0x08, // Bb
    0x80, 0xd5, 0x00, 0x08, // C
    0x80, 0xbe, 0x00, 0x08, // D

    // Measure 12, 16th notes
    0x80, 0x52, 0x01, 0x08, // E
    0x80, 0xbe, 0x00, 0x08, // D ^
    0x80, 0xd5, 0x00, 0x08, // C ^
    0x80, 0xf0, 0x00, 0x08, // Bb ^
    0x80, 0xfe, 0x00, 0x08, // A ^
    0x80, 0x1d, 0x01, 0x08, // G ^

    // Measure 13, 16th notes
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0xf0, 0x00, 0x08, // Bb ^
    0x80, 0xd5, 0x00, 0x08, // C

    // Measure 14, 16th notes
    0x80, 0x7c, 0x01, 0x08, // D
    0x80, 0xd5, 0x00, 0x08, // C
    0x80, 0xf0, 0x00, 0x08, // Bb
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0x40, 0x01, 0x08, // F
    
    // Measure 15, 16th notes
    0x80, 0x52, 0x01, 0x08, // E
    0x80, 0xaa, 0x01, 0x08, // C
    0x80, 0x7d, 0x01, 0x08, // D
    0x80, 0x52, 0x01, 0x08, // E
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x1d, 0x01, 0x08, // G

    // Measure 16, 16th notes
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x52, 0x01, 0x08, // E
    0x80, 0x7c, 0x01, 0x08, // D
    0x80, 0xaa, 0x01, 0x08, // C

    // Measure 17, 2 16ths, 2 8ths
    0x80, 0x7c, 0x01, 0x08, // D
    0x80, 0xe0, 0x01, 0x08, // Bb
    0x80, 0xaa, 0x01, 0x10, // C
    0x80, 0x55, 0x03, 0x10, // C v

    // Measure 18, 16th notes
    0x80, 0x7f, 0x02, 0x08, // F
    0x80, 0x3a, 0x02, 0x08, // G
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0xe0, 0x01, 0x08, // Bb
    0x80, 0xaa, 0x01, 0x08, // C
    0x80, 0x7c, 0x01, 0x08, // D

    // Measure 19, 16th notes
    0x80, 0xa5, 0x02, 0x08, // E
    0x80, 0x7c, 0x01, 0x08, // D
    0x80, 0xaa, 0x01, 0x08, // C
    0x80, 0xe0, 0x01, 0x08, // Bb
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0x3a, 0x02, 0x08, // G

    // Measure 20, 16th notes
    0x80, 0xf8, 0x03, 0x08, // A
    0x80, 0xbc, 0x03, 0x08, // Bb
    0x80, 0x5c, 0x03, 0x08, // C
    0x80, 0xf8, 0x02, 0x08, // D
    0x80, 0xa5, 0x02, 0x08, // E
    0x80, 0x7f, 0x02, 0x08, // F

    // Measure 21, 16th notes
    0x80, 0x3a, 0x02, 0x08, // G
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x52, 0x01, 0x08, // E
    0x80, 0x7c, 0x01, 0x08, // D
    0x80, 0xaa, 0x01, 0x08, // C
    0x80, 0xe0, 0x01, 0x08, // Bb

    // Measure 22, 16th notes
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0xc4, 0x01, 0x08, // B
    0x80, 0xaa, 0x01, 0x08, // C
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0xc4, 0x01, 0x08, // B
    0x80, 0xaa, 0x01, 0x08, // C

    // Measure 23, 8th note, two 8th rests
    0x80, 0x5c, 0x02, 0x10, // F#
    0xb0,                   // 8th note rest
    0xb0,                   // 8th note rest

    // Measure 24, 16th notes
    0x80, 0x3a, 0x02, 0x08, // G
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0xe0, 0x01, 0x08, // Bb
    0x80, 0x3a, 0x02, 0x08, // G
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0xe0, 0x01, 0x08, // Bb

    // Measure 25, 8th note, two 8th rests
    0x80, 0xa5, 0x02, 0x08,
    0xb0,                   // 8th note rest
    0xb0,                   // 8th note rest
    
    // Measure 26, 8th notes
    0x80, 0x7f, 0x02, 0x10, // F
    0x80, 0x7d, 0x01, 0x10, // D ^
    0x80, 0xe0, 0x01, 0x10, // Bb

    // Measure 27, 8th notes
    0x80, 0xc4, 0x01, 0x10, // Bn
    0x80, 0x19, 0x02, 0x10, // G#
    0x80, 0xaa, 0x02, 0x10, // E
   
    // Measure 28, 16th notes
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0x1c, 0x02, 0x08, // G#
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0xc4, 0x01, 0x08, // Bn
    0x80, 0xaa, 0x01, 0x08, // C
    0x80, 0x7c, 0x01, 0x08, // D

    // Measure 29, trills
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F

    // Measure 30, trills
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F

    // Measure 31, trills
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F

    // Measure 32, oh look, more trills
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F

    // Measure 33, the last of the trills
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F
    0x80, 0x54, 0x01, 0x04, // E
    0x80, 0x40, 0x01, 0x04, // F

    // Measure 34, eighth notes
    0x80, 0x54, 0x01, 0x10, // E
    0x80, 0xa9, 0x00, 0x10, // E ^
    0x80, 0xbe, 0x00, 0x10, // D

    // Measure 35, eighth notes
    0x80, 0xaa, 0x01, 0x10, // C
    0x80, 0xc4, 0x01, 0x10, // Bn
    0x80, 0xfc, 0x01, 0x10, // A

    /// Measure 36, eighth notes
    0x80, 0xbe, 0x00, 0x10, // D
    0x80, 0xa9, 0x00, 0x10, // E
    0x80, 0xa0, 0x00, 0x10, // F

    // Measure 37, eighth notes
    0x80, 0xbe, 0x00, 0x10, // D
    0x80, 0xa9, 0x00, 0x10, // E
    0x80, 0x52, 0x01, 0x10, // E v

    // Measure 38, 16th notes
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0xfc, 0x01, 0x08, // A v
    0x80, 0xe0, 0x01, 0x08, // Bb
    0x80, 0xaa, 0x01, 0x08, // C
    0x80, 0x7c, 0x01, 0x08, // D
    0x80, 0x67, 0x01, 0x08, // Eb

    // Measure 39, 16th notes
    0x80, 0x5c, 0x02, 0x08, // F#
    0x80, 0x67, 0x01, 0x08, // Eb
    0x80, 0x7c, 0x01, 0x08, // D
    0x80, 0xaa, 0x01, 0x08, // C
    0x80, 0xe0, 0x01, 0x08, // Bb
    0x80, 0xfc, 0x01, 0x08, // A

    // Measure 40, dotted eighth, three 16th notes
    0x80, 0x3a, 0x02, 0x18, // G
    0x80, 0x3a, 0x02, 0x08, // G
    0x80, 0xfc, 0x01, 0x08, // A
    0x80, 0xe0, 0x01, 0x08, // Bb

    // Measure 41, eighth notes
    0x80, 0x55, 0x03, 0x10, // C
    0x80, 0x3a, 0x02, 0x10, // G
    0x80, 0xaa, 0x01, 0x10, // C

    // Measure 42, 16th notes
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0xe2, 0x00, 0x08, // Bn
    0x80, 0xc9, 0x00, 0x08, // C#
    0x80, 0xbe, 0x00, 0x08, // D

    // Measure 43, 16th notes
    0x80, 0x52, 0x01, 0x08, // E
    0x80, 0xbe, 0x00, 0x08, // D
    0x80, 0xc9, 0x00, 0x08, // C#
    0x80, 0xe2, 0x00, 0x08, // Bn
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0x1d, 0x01, 0x08, // G

    // Measure 44, 8th notes
    0x80, 0x40, 0x01, 0x10, // F
    0x80, 0xfe, 0x00, 0x10, // A
    0x80, 0xbe, 0x00, 0x10, // D ^

    // Measure 45, 8th notes
    0x80, 0x52, 0x01, 0x10, // E
    0x80, 0x1d, 0x01, 0x10, // G
    0x80, 0xc9, 0x00, 0x10, // C#

    // Measure 46, 16th notes
    0x80, 0x7c, 0x01, 0x08, // D
    0x80, 0x52, 0x01, 0x08, // E
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0xf0, 0x00, 0x08, // Bb

    // Measure 47, 16th notes
    0x80, 0x93, 0x01, 0x08, // C#
    0x80, 0xf0, 0x00, 0x08, // Bb
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x52, 0x01, 0x08, // E

    // Measure 48, 2 16th notes, 2 8th notes
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0xfe, 0x00, 0x10, // A
    0x80, 0xfc, 0x01, 0x10, // A v

    // Measure 49, 1 dotted eight, three 16th notes
    0x80, 0xc4, 0x03, 0x18, // Bb
    0x80, 0x5c, 0x03, 0x08, // C
    0x80, 0xc4, 0x03, 0x08, // Bb
    0x80, 0xf8, 0x03, 0x08, // A

    // Measure 50, 16th notes
    0x80, 0x3a, 0x02, 0x08, // G v
    0x80, 0xf0, 0x00, 0x08, // Bb
    0x80, 0xfe, 0x00, 0x08, // A
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x54, 0x01, 0x08, // E

    // Measure 51, 2 16th notes, 2 8th notes
    0x80, 0x40, 0x01, 0x08, // F
    0x80, 0x1d, 0x01, 0x08, // G
    0x80, 0xfe, 0x00, 0x10, // A
    0x80, 0xfc, 0x01, 0x10,

    // Measure 52, fermata
    0x80, 0xf8, 0x02, 0x30, // D

    
    0xFF, // end of song (so it doesn't go off into the weeds)

 

Note the first byte in each line, this is the header block, and specifies the output channel, as well as the note type. The note_type_0 and note_type_rest tools were used here to provide the data needed for each note, for example, for the first two notes of sound0, 8th notes, that last 8 frames, at full volume (0):

[thomc@TMA-2 tools]$ ./note_type_0 1 587 0 8
0x40, 0xbe, 0x00, 0x08,
[thomc@TMA-2 tools]$ ./note_type_0 1 659 0 8
0x40, 0xa9, 0x00, 0x08,

 

And for rests, this is the rest used that's the first part of sound1 (an 8th note rest, which lasts for eight frames)

[thomc@TMA-2 tools]$ ./note_type_rest 2 8
0xa8,

 

I took great care, to format each entry, so that each note is on its own line. and make note of each measure, so that the music piece could be properly transcribed. I did have to transpose the piece up an octave, because of the limited frequency range of the SN76489 sound chip.

 

But hopefully, this stands as a solid and well documented example of how to use the ColecoVision OS7 sound routines to play music in the background of a game .

 

-Thom

Edited by tschak909
  • Like 2
Link to comment
Share on other sites

The OS7 contains routines to read, debounce, and decode each of the two joysticks, their keypads, and spinners (if available).

 

While there are individual routines to read and decode the controllers, the only routine you actually need for most use is the POLLER routine.

 

The POLLER routine can be called during any loop, or even via the NMI, and has no parameters.

 

The result of the POLLER command places the controller data, fully decoded, into the CONTROLLER DATA area, which is specified in the cartridge header. With Z88DK, this can be specified as a pragma:

-pragma-define:CRT_COLECO_BIOS_CONTROLLER_SIZE=12

 

Which defines a controller data area that is exposed in the arch/coleco.h header

#ifndef ARCH_COLECO_H
#define ARCH_COLECO_H

#include <stdint.h>

// Access to the buffers that are used by OS7
//
// Configure the size using the following pragmas:
//
// -pragma-define:CRT_COLECO_SPRITE_NAME_SIZE=nn
// -pragma-define:CRT_COLECO_SPRITE_ORDER_SIZE=nn
// -pragma-define:CRT_COLECO_BIOS_BUFFER_SIZE=nn
// -pragma-define:CRT_COLECO_BIOS_CONTROLLER_SIZE=nn
//
extern uint8_t  os7_sprite_order_table[];
extern uint8_t  os7_sprite_name_table[];
extern uint8_t  os7_bios_buffer[];
extern uint8_t  os7_bios_controller[];

#endif

 

So this means, that in the NMI, we just need to do a call to:

poller();

 

And the data winds up in os7_bios_controller[], which we can easily read by casting as a pointer to the ControllerData struct in os7.h

/**
 * @brief A structure for the controller data area
 */
typedef struct
{
  unsigned char player1_enable;
  unsigned char player2_enable;
  struct
  {
    unsigned char left_button;
    unsigned char joystick;
    unsigned char spinner_count;
    unsigned char right_button;
    unsigned char keyboard;
  } player1;
  struct
  {
    unsigned char left_button;
    unsigned char joystick;
    unsigned char spinner_count;
    unsigned char right_button;
    unsigned char keyboard;
  } player2;
} ControllerData;

ControllerData *c = (ControllerData *)os7_controller_data;

 

And we can, for example, act on the joystick of player 1 with:

if (c->player_1.joystick & 0x01) // joystick pointing up
  go_up();

 

There is a test program in the os7 examples:

https://github.com/tschak909/os7lib/tree/main/examples/controller_test

 

/**
 * @brief Controller Demo
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see COPYING for details
 */

#include <os7.h>
#include <interrupt.h>
#include <arch/z80.h>
#include <intrinsic.h>
#include <stdbool.h>
#include <arch/coleco.h>

// Bits to mask for joystick directions
#define JOYSTICK_UP_BIT 0x01
#define JOYSTICK_RIGHT_BIT 0x02
#define JOYSTICK_DOWN_BIT 0x04
#define JOYSTICK_LEFT_BIT 0x08
#define JOYSTICK_FIRE_BIT 0x40

// VDP positions relative to start of nametable
#define P1_UP_POS 197
#define P1_RIGHT_POS 264
#define P1_DOWN_POS 324
#define P1_LEFT_POS 256
#define P1_LBUT_POS 128
#define P1_RBUT_POS 137
#define P1_KP1_POS 419
#define P1_KP2_POS 422
#define P1_KP3_POS 425
#define P1_KP4_POS 515
#define P1_KP5_POS 518
#define P1_KP6_POS 521
#define P1_KP7_POS 611
#define P1_KP8_POS 614
#define P1_KP9_POS 617
#define P1_KPA_POS 707
#define P1_KP0_POS 710
#define P1_KPP_POS 713
#define P2_UP_POS 216
#define P2_RIGHT_POS 283
#define P2_DOWN_POS 343
#define P2_LEFT_POS 275
#define P2_LBUT_POS 147
#define P2_RBUT_POS 156
#define P2_KP1_POS 429
#define P2_KP2_POS 432
#define P2_KP3_POS 435
#define P2_KP4_POS 534
#define P2_KP5_POS 537
#define P2_KP6_POS 540
#define P2_KP7_POS 630
#define P2_KP8_POS 633
#define P2_KP9_POS 636
#define P2_KPA_POS 726
#define P2_KP0_POS 729
#define P2_KPP_POS 732
#define P1_HEADER_POS 66
#define P2_HEADER_POS 85

const char signon[]="OS7 CONTROLLER TEST";
const char p1_header[]="PLAYER  1";
const char p2_header[]="PLAYER  2";
const char up_1[] = "UP";
const char up_0[] = "  ";
const char down_1[] = "DOWN";
const char down_0[] = "    ";
const char left_1[] = "LEFT";
const char left_0[] = "    ";
const char right_1[] = "RIGHT";
const char right_0[] = "     ";
const char lbut_1[] = "LBUT";
const char lbut_0[] = "    ";
const char rbut_1[] = "RBUT";
const char rbut_0[] = "    ";
const char kp_1_1[] = "1";
const char kp_1_0[] = " ";
const char kp_2_1[] = "2";
const char kp_2_0[] = " ";
const char kp_3_1[] = "3";
const char kp_3_0[] = " ";
const char kp_4_1[] = "4";
const char kp_4_0[] = " ";
const char kp_5_1[] = "5";
const char kp_5_0[] = " ";
const char kp_6_1[] = "6";
const char kp_6_0[] = " ";
const char kp_7_1[] = "7";
const char kp_7_0[] = " ";
const char kp_8_1[] = "8";
const char kp_8_0[] = " ";
const char kp_9_1[] = "9";
const char kp_9_0[] = " ";
const char kp_0_1[] = "0";
const char kp_0_0[] = " ";
const char kp_A_1[] = "*";
const char kp_A_0[] = " ";
const char kp_P_1[] = "#";
const char kp_P_0[] = " ";

ControllerData *c = (ControllerData *)os7_bios_controller; // defined in $Z88DK/include/arch/coleco.h

void show_controllers(void)
{    
  put_vram(PATTERN_NAME_TABLE,P1_UP_POS,c->player1.joystick & JOYSTICK_UP_BIT ? up_1 : up_0,sizeof(up_1));
  put_vram(PATTERN_NAME_TABLE,P1_RIGHT_POS,c->player1.joystick & JOYSTICK_RIGHT_BIT ? right_1 : right_0,sizeof(right_1));
  put_vram(PATTERN_NAME_TABLE,P1_DOWN_POS,c->player1.joystick & JOYSTICK_DOWN_BIT ? down_1 : down_0,sizeof(down_1));
  put_vram(PATTERN_NAME_TABLE,P1_LEFT_POS,c->player1.joystick & JOYSTICK_LEFT_BIT ? left_1 : left_0,sizeof(left_1));
  put_vram(PATTERN_NAME_TABLE,P1_LBUT_POS,c->player1.left_button & JOYSTICK_FIRE_BIT ? lbut_1 : lbut_0,sizeof(lbut_1));
  put_vram(PATTERN_NAME_TABLE,P1_RBUT_POS,c->player1.right_button & JOYSTICK_FIRE_BIT ? rbut_1 : rbut_0,sizeof(rbut_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP1_POS,c->player1.keyboard == 0x01 ? kp_1_1 : kp_1_0,sizeof(kp_1_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP2_POS,c->player1.keyboard == 0x02 ? kp_2_1 : kp_2_0,sizeof(kp_2_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP3_POS,c->player1.keyboard == 0x03 ? kp_3_1 : kp_3_0,sizeof(kp_3_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP4_POS,c->player1.keyboard == 0x04 ? kp_4_1 : kp_4_0,sizeof(kp_4_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP5_POS,c->player1.keyboard == 0x05 ? kp_5_1 : kp_5_0,sizeof(kp_5_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP6_POS,c->player1.keyboard == 0x06 ? kp_6_1 : kp_6_0,sizeof(kp_6_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP7_POS,c->player1.keyboard == 0x07 ? kp_7_1 : kp_7_0,sizeof(kp_7_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP8_POS,c->player1.keyboard == 0x08 ? kp_8_1 : kp_8_0,sizeof(kp_8_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP9_POS,c->player1.keyboard == 0x09 ? kp_9_1 : kp_9_0,sizeof(kp_9_1));
  put_vram(PATTERN_NAME_TABLE,P1_KP0_POS,c->player1.keyboard == 0x00 ? kp_0_1 : kp_0_0,sizeof(kp_0_1));
  put_vram(PATTERN_NAME_TABLE,P1_KPA_POS,c->player1.keyboard == 0x0a ? kp_A_1 : kp_A_0,sizeof(kp_A_1));
  put_vram(PATTERN_NAME_TABLE,P1_KPP_POS,c->player1.keyboard == 0x0b ? kp_P_1 : kp_P_0,sizeof(kp_P_1));

  put_vram(PATTERN_NAME_TABLE,P2_UP_POS,c->player2.joystick & JOYSTICK_UP_BIT ? up_1 : up_0,sizeof(up_1));
  put_vram(PATTERN_NAME_TABLE,P2_RIGHT_POS,c->player2.joystick & JOYSTICK_RIGHT_BIT ? right_1 : right_0,sizeof(right_1));
  put_vram(PATTERN_NAME_TABLE,P2_DOWN_POS,c->player2.joystick & JOYSTICK_DOWN_BIT ? down_1 : down_0,sizeof(down_1));
  put_vram(PATTERN_NAME_TABLE,P2_LEFT_POS,c->player2.joystick & JOYSTICK_LEFT_BIT ? left_1 : left_0,sizeof(left_1));
  put_vram(PATTERN_NAME_TABLE,P2_LBUT_POS,c->player2.left_button & JOYSTICK_FIRE_BIT ? lbut_1 : lbut_0,sizeof(lbut_1));
  put_vram(PATTERN_NAME_TABLE,P2_RBUT_POS,c->player2.right_button & JOYSTICK_FIRE_BIT ? rbut_1 : rbut_0,sizeof(rbut_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP1_POS,c->player2.keyboard == 0x01 ? kp_1_1 : kp_1_0,sizeof(kp_1_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP2_POS,c->player2.keyboard == 0x02 ? kp_2_1 : kp_2_0,sizeof(kp_2_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP3_POS,c->player2.keyboard == 0x03 ? kp_3_1 : kp_3_0,sizeof(kp_3_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP4_POS,c->player2.keyboard == 0x04 ? kp_4_1 : kp_4_0,sizeof(kp_4_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP5_POS,c->player2.keyboard == 0x05 ? kp_5_1 : kp_5_0,sizeof(kp_5_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP6_POS,c->player2.keyboard == 0x06 ? kp_6_1 : kp_6_0,sizeof(kp_6_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP7_POS,c->player2.keyboard == 0x07 ? kp_7_1 : kp_7_0,sizeof(kp_7_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP8_POS,c->player2.keyboard == 0x08 ? kp_8_1 : kp_8_0,sizeof(kp_8_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP9_POS,c->player2.keyboard == 0x09 ? kp_9_1 : kp_9_0,sizeof(kp_9_1));
  put_vram(PATTERN_NAME_TABLE,P2_KP0_POS,c->player2.keyboard == 0x00 ? kp_0_1 : kp_0_0,sizeof(kp_0_1));
  put_vram(PATTERN_NAME_TABLE,P2_KPA_POS,c->player2.keyboard == 0x0a ? kp_A_1 : kp_A_0,sizeof(kp_A_1));
  put_vram(PATTERN_NAME_TABLE,P2_KPP_POS,c->player2.keyboard == 0x0b ? kp_P_1 : kp_P_0,sizeof(kp_P_1));
}

static void nmi()
{
  M_PRESERVE_ALL;
  poller();
  VDP_STATUS_BYTE = read_register();
  M_RESTORE_ALL;
}

void main(void)
{
  mode_1();
  load_ascii();
  c->player1_enable = 0xFF; // read everything
  c->player2_enable = 0xFF; // read everything
  add_raster_int(nmi);
  fill_vram(0x2000,32,0xF4);
  write_register(0x07,0x04);
  put_vram(PATTERN_NAME_TABLE,0x04,signon,sizeof(signon));
  put_vram(PATTERN_NAME_TABLE,P1_HEADER_POS,p1_header,sizeof(p1_header));
  put_vram(PATTERN_NAME_TABLE,P2_HEADER_POS,p2_header,sizeof(p2_header));
  write_register(0x01,0xE0);

  while(1)
    {
      if (!(VDP_STATUS_BYTE & 0x80))
	show_controllers();      
    }
}

 

The only thing not mentioned before, is that we only update the controllers WHEN we're not in the midst of the VDP interrupt, by checking the VDP status byte, that gets updated every NMI.

 

-Thom

controller_test.rom

Link to comment
Share on other sites

2 hours ago, tschak909 said:

I'm still looking for concrete examples of the MOBILE OBJECT type to put into an example, and I have yet to find any Coleco games that actually have used this object type. Am I hallucinating? Maybe Carnival? @Captain Cozmos?

 

-THom

I'll disassemble Carnival but what I understand to be mobile objects would be patterns that can travel in all 8 directions.
Clarify if I am wrong.

If I am correct then that would be Frenzy, 100%.
Each one of those robots are made up of patterns which do go in all 8 directions and have pattern sets to match up, down, left and right plus diagonal.
Being they are 16x16 they have to have the diagonal in between to match for 16x16.


When I wrote my pattern routines I used that model as an example but I did not use the BIOS format or routines.  All of mine, so far are 8x8 which means less diagonal patterns.


The robots in Frenzy take up the majority of the games space as far as patterns go.

What I would like to see in action is the AND/OR function where patterns are merged together.
I know the limitations of 1 color per line in the 8x8 matrix so I am curious to see how Coleco handled this.

Edited by Captain Cozmos
Link to comment
Share on other sites

13 hours ago, Captain Cozmos said:

I'll disassemble Carnival but what I understand to be mobile objects would be patterns that can travel in all 8 directions.
Clarify if I am wrong.

If I am correct then that would be Frenzy, 100%.
Each one of those robots are made up of patterns which do go in all 8 directions and have pattern sets to match up, down, left and right plus diagonal.
Being they are 16x16 they have to have the diagonal in between to match for 16x16.


When I wrote my pattern routines I used that model as an example but I did not use the BIOS format or routines.  All of mine, so far are 8x8 which means less diagonal patterns.


The robots in Frenzy take up the majority of the games space as far as patterns go.

What I would like to see in action is the AND/OR function where patterns are merged together.
I know the limitations of 1 color per line in the 8x8 matrix so I am curious to see how Coleco handled this.

The color merging is arguably the hairiest part of the whole PUT_OBJ code. It makes my head hurt.

 

(but for others, I will paste what the programming guide says about the bkg mode parameter, which goes in B)

 

(yes, all of this is relevant, please read carefully)

 

image.thumb.png.8891ad455ee9efa35ec096ed680c9d6c.png

image.thumb.png.36994ac90fe70d12de26fa247c86d895.png

image.thumb.png.3a1272b9a62738af758ec59300f56630.png

-Thom

 

 

Link to comment
Share on other sites

I did the bulk of the Carnival disassembly last night after I read your post.  It took around two hours or so to sort it out.

Most of the tables are identified, I am working on the fine tuning now.
I will post it in the main thread under my "I disassembled" series.

So far all I can see are the patterns go left and right, no diagonals with patterns in between every 2 movements or some deal.

The bear, flying duck, gun and a few other little things are all sprites.

The rotating pipes may be just a pre-rendered set of patterns and not necessarily an object but I should find out soon enough.

It's a fairly simple game in terms of programing. 

 

 

 

EDIT......

 

Doing a deep dive on this one.

Labeling as much as I can.

However, preliminary results may not be what you were hoping for.

 

This is actually my second edit.

 

After labeling all the patterns I discovered that program cycles register 4 (patterns) and 3 (colors) which rotates the pipes and changes their colors.

All the other NPC's are objects.  One set facing left and one set facing left.

 

Edited by Captain Cozmos
Link to comment
Share on other sites

Am currently trying to figure out why, even though I set up a deferred write queue:

#define QUEUE_SIZE 16

TimerTable tt[8];
TimerData td[2];

unsigned char deferred_writer_queue[QUEUE_SIZE*WRITER_QUEUE_ENTRY_SIZE];

static void vdp_nmi(void)
{
  M_PRESERVE_ALL;
  time_mgr();
  writer();
  VDP_STATUS_BYTE = read_register();
  M_RESTORE_ALL;
}

void init(void)
{
  init_timer(&tt,&td);
  init_writer(&deferred_writer_queue,QUEUE_SIZE);
  add_raster_int(vdp_nmi);
}

 

when I keep deferred writes off, I can reliably write, because it just goes to VRAM, but of course, the amount of time needed is preempted by the NMI, so occasionally there is screen corruption...

 

 

And when I turn deferred writes on...

/**
 * DEFER_WRITES - RAM location to turn on/off deferred writes
 */
#define DEFER_WRITES (*(unsigned char *)0x73c6)

// ...
  
/**
 * @brief plot 10 targs, and go down the screen
 */
void targ(void)
{
  targ_init();

  // Set initial positions
  for (int i=0;i<10;i++)
    {
      activate(&targSMO[i],false);
      targStatus[i].x=(i*24)+16;
      targStatus[i].y=40;
      targStatus[i].frame=0;
    }

  // I turn this on, and all hell breaks loose. Why?!
  DEFER_WRITES=true;
  
  // The main loop here, update position
  while(true)
    {
      for (int i=0;i<10;i++)
        {
          if (targStatus[i].frame==0x08)
            {
              targStatus[i].frame=0;
              targStatus[i].y+=8;
              if (targStatus[i].y>160)
                return;
            }
          else
            targStatus[i].frame+=2;

          put_obj(&targSMO[i],0);
        }

    }
}

 

All hell breaks loose because the stack explodes...

 

 

What am I missing, here? ....

 

-Thom

 

 

Link to comment
Share on other sites

I am honestly a bit confused as to when to use deferred writes versus the direct writes, mentioned here in chapter 4 of the Colecovision Programmer's Guide:

https://archive.org/details/colecovision-programmers-guide-revision-5/page/n79/mode/2up

 

I have tried:

 

* Leaving deferred on (causes a stack overflow due to the queue not being emptied in time), have tried this with and without an end of frame timer.

* Watching the VDP status register at the bottom of the loop that does 10 PUT_OBJ calls

* doing fewer PUT_OBJs

 

and various combinations of each.

 

I can indeed turn off the VDP interrupts and I can draw all 10 semi-mobile objects, but this is not usable for real work.

 

There IS the DEFER_WRITES flag (0x73C5), but it is unclear when this should and should not be set.

 

Anyone have any potential insight on how this flag should be used?

 

-Thom

Link to comment
Share on other sites

I had initially hoped to leave the targ rider enemies as semi-mobile (and pre-shifted) tiles, to eliminate potential flicker, but, at least for now, this presents a bit of NMI contention that I would need to work out, so I put that to the side, and re-implemented the targ riders as sprites.

 

There are 10 Targ sprites that start each level, and they all start on the same scan-line.

 

Those of you familiar with the TMS 9918 VDP know that while there are registers to store the attributes for each of the 32 available sprites, there are only four active shifters that get combined with the final picture, thus the VDP will take the attributes of the four lowest numbered sprites on a line, and place them into the four available shifters to be combined to get the output pixels. This means that if you want to display any other sprites that may be present on that same horizontal line, you need to adjust the sprite attribute table from frame to frame, to re-order the sprites so the next four can be displayed, and so on. This is a very common problem, and Coleco decided to provide a sprite priority re-ordering infrastructure in OS7 to address this.

 

The OS7 manual explains it thusly:

image.thumb.png.044e6f0088c9698ab80deb32d70a91e4.png

The way this is implemented is to provide two tables in CPU RAM, one providing a CPU local copy of the sprite attribute table, and another providing an ordering table. The former table is handled by OS7 and does not need to be programmer modified, while the programmer modifies the latter table to change the order in which the sprite attributes are copied into VRAM. To use these two tables, a routine WR_SPR_NAM_TBL is provided, intended to be called during your NMI routine, which iterates through both of these tables, and copying each entry into VRAM in the order desired. An additional function, INIT_SPR_ORDER is provided to initialize the sprite order table, before use.

 

You can see the format of each of these tables in the OS7 manual:

image.thumb.png.9d0697075f06714f1c3131509e7479e2.png

 

So you may wish to multiplex the display of 8 sprites, you'd call init_spr_order in your initialization routine as:

void init()
{
   // ...
   init_spr_order(32); // initialize all 32 possible sprites in order.
}

 

and in your NMI, you'd have the corresponding function:

void nmi()
{
  // other stuff, WRITER, POLLER, PLAY_SONGS, etc.
  wr_spr_nam_tbl(32); // Must match the # you intiialized in init_spr_order!
}

 

We also need to reserve memory for both the sprite order and sprite name table copies in RAM, and with Z88DK's recent changes, these can be specified as link time pragmas:

LDFLAGS += -pragma-define:CRT_ORG_BSS=0x702C -pragma-define:CRT_COLECO_SPRITE_NAME_SIZE=128 -pragma-define:CRT_COLECO_SPRITE_ORDER_SIZE=32 -pragma-define:CRT_COLECO_BIOS_BUFFER_SIZE=32 -pragma_define:CRT_COLECO_BIOS_CONTROLLER_SIZE=12 -pragma-define:REGISTER_SP=0x73B8

 

Some of the other pragmas shown here set the BSS to 0x702C, so that it is out of the way of the sound routines, and we set the stack pointer to 0x73B8, so it doesn't override some global values that OS7 sets (such as storing the VDP mode bit, the random number generator, and others)

 

These pragmas also cause the cartridge header to be updated with the correct values pointing to these variables, so that OS7 can use them.

 

How does PUT_OBJ() know to multiplex the sprites?

 

There is a global variable called MUX_SPRITES, which points to address 0x73C7, which if set to TRUE (1), PUT_VRAM() will place any sprite writes into the copied name table in RAM instead of writing directly to VRAM.

void init(void)
{
  init_spr_order(32);
  MUX_SPRITES=true;
  init_timer(&tt,&td);
  init_writer(&deferred_writer_queue,QUEUE_SIZE);
  add_raster_int(vdp_nmi);
}

 

The WR_SPR_NAM _TBL call will do the actual writes during the NMI, sorting on the fly, depending on the contents of the sprite order table.

 

How do I re-order the sprites?

 

By changing the contents of the sprite order variable, in Z88DK, this is defined in arch/coleco.h
 

#ifndef ARCH_COLECO_H
#define ARCH_COLECO_H

#include <stdint.h>

// Access to the buffers that are used by OS7
//
// Configure the size using the following pragmas:
//
// -pragma-define:CRT_COLECO_SPRITE_NAME_SIZE=nn
// -pragma-define:CRT_COLECO_SPRITE_ORDER_SIZE=nn
// -pragma-define:CRT_COLECO_BIOS_BUFFER_SIZE=nn
// -pragma-define:CRT_COLECO_BIOS_CONTROLLER_SIZE=nn
//
extern uint8_t  os7_sprite_order_table[];
extern uint8_t  os7_sprite_name_table[];
extern uint8_t  os7_bios_buffer[];
extern uint8_t  os7_bios_controller[];

#endif

 

In the case of Targ's targ() routine:

https://github.com/tschak909/colecovision-targ/blob/main/src/targ.c#L155

/**
 * @brief update targ positions
 */
void targ(void)
{
  for (unsigned char i=0;i<10;i++)
    {
      targ_status[i].y+=1;

      if (targ_status[i].y>176)
        targ_status[i].y=40;
            
      put_obj(targ_obj[i],0);
    }

  // Rotate sprite order so all sprites can be seen
  // FIXME: move this into its own block of code
  for (unsigned char i=0;i<15;i++)
    {
      os7_sprite_order_table[i+1] -= 0x04;
      os7_sprite_order_table[i+1] &= 0x0F;
    }
}

 

we shift the order of each of the sprites down (after sprite 0 which is the wummel) by 4, so that they can appear in the next frame.

 

That's it. Sprite multiplexing done easy. :)

 

You can see it from a debugger's point of view in GearColeco:

and on real hardware:

-Thom 

 

targ.rom

Edited by tschak909
dropped a rom in here too.
Link to comment
Share on other sites

  • 1 month later...
47 minutes ago, pearsoe said:

@tschak909 Could your issues with Mobile objects be related to the PUT_MOBILE bug mentioned in the "Colecovision Coding Guide" on page 2?

http://adamarchive.org/archive/Technical/CV Tech Manuals/ColecoVision Coding Guide (2005) (NewColeco).pdf

Am taking great pains to avoid the particular bugs here, so no. -Thom

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...