=== P·HYDRA DOCUMENTATION ===
The "Operation Codes, With Details, Please!" File

(OPS, ver L5L)



00  RETPAGE
--
RETPAGE sets the low byte of the playhead's instruction address to zero, sending it back to the first instruction on the current memory page. Since program memory is blanked to zeros when P·HYDRA loads, then all sequences automatically loop.


01 - 04  SETF1...4
--   --
Sets the value of POKEY's frequency registers (AUDF1-4). These operations take the first operand as a MODE setting, and the second operand as VALUE. The MODEs are:

  00 - use SECOND OPERAND value directly
  01 - use "            " as a scale index
  02 - add "            " to current AUDF value
  03 - add "            " to current INTERVAL value
  04 - use REGISTER LOW BYTE directly
  05 - use "               " as scale index
  06 - add "               " to current AUDF value
  07 - add "               " to current INTERVAL value
  08 - use RANDOM value directly
  09 - use "    " value as scale index
  0A - add "    " value to current AUDF value
  0B - add "    " value to current INTERVAL value

So, the four low bits of the [mode] operand are:
  source----|   |----method
           / \ / \
   0 0 0 0 0 0 0 0


05 - 08  SETC1...4
--   --
Sets the value of POKEY's voice control registers (AUDC1-4). These operations take the first operand as a MODE setting, and the second operand as a VALUE. The modes are:

  00 - Overwrite the AUDCx register with VALUE 
  01 - Set only the distortion bits of AUDCx with VALUE
  02 - Set the volume bits of AUDCx with VALUE
  03 - Add VALUE to AUDCx's distortion bits
  04 - Add VALUE to AUDCx's volume bits

In some future version I may rewrite this to be more like the SETFx operations, using direct, register, and random sources. 


09  SETCTL
--
Sets the value of AUDCTL, POKEY's control register. Again, we use the first operand as a MODE value to determine how to use the second operand's VALUE.

  00 - Store the VALUE directly to AUDCTL
  01 - Perform XOR with VALUE and AUDCTL
  02 - Perform AND with VALUE and AUDCTL
  03 - Perform OR with VALUE and AUDCTL


0A  HOP
--
Makes the current playhead hop over the number of instructions specified by operand 1. If operand 1 is zero, then the value of the low byte of the playhead's register is used instead. Operand 2 is ignored.


0B  JMP
--
Set the current playhead's program counter directly. Operand 1 sets the low byte, operand 2 sets the high byte. 

Yes, you can crash the computer by jumping to a weird location. Should I accident-proof this a bit better? Yeah, probably.


0C  STOR
--
Stores the passed word value to the playhead's register. 


0D  MAC
--
Jump to a macro sequence (aka, subroutine). Stores the playhead's current position to it's macro return address register, then performs a JMP.


0E  MACRET
--
Return from a macro sequence. Sets the current playhead's program counter to the value of its macro return address register.


0F  OPNOP
--
Does nothing. Really. But it still has a delay value, so if you need a delay of more than 60 ticks, line-up a few OPNOPs.


10  STOP
--
Disables the current playhead. Leaves its register, program counter, etc. untouched. 


19 - 1C  SETC1R, SETC2R, SETC3R. SETC4R  (SET AUDC from Register)
--   --
Set the value of POKEY's voice control registers (AUDC1-4) from the value of the current playhead's register ...in theory.

I'm looking at the source code right now, and all of these operations just have a comment that says "AIN'T WROTE THIS YET", so... not implemented at this time, apparently!


1D  SETCTLR  (SET AUDCTL from Register)
--
Set the value of AUDCTL, POKEY's control register, from the value of the current playhead's register.

  *** NOT IMPLEMENTED : Same as the SETCxR operatons. 


1E  MODR  (MODify Register)
--
Modify the value of the current playhead's register. The value of operand 1 determines what to do, and operand 2 determines how much.

Operand 1 - Result
---------   ------
        0 - Perform a 16-bit adition with operand 2
        1 - XOR with operand 2
        2 - AND with operand 2
        3 - OR with operand 2
       >3 - 8-bit addition with operand 2


1F  HOPNE  (HOP on Not-Equal)
--
Skip the next operation if the playhead's register is not equal to the passed value.


20 JMPONZ  (JuMP On Zero)
--
Set the current playhead's program counter to the passed address (same as a JMP) IF the playhead's register equals zero.


21 - 26  GOHED1-6
--   --
Activates a playhead, sets its program counter to the fist byte of the page given in operand 1, and sets the low byte of its register to the value of operand 2 IF operand 2 is nonzero.


30  GOPCM
--
Start playback of the loaded PCM waveform on POKEY voice 4. Operand 1 sets the sample rate -- that is, operand 1 is a divisor for POKEY's main clock, so the following values give the corresponding sample rates.


op1  15kHz clock (15,699.9 Hz)   64kHz clock (63,921 Hz)
---  -------------------------   -----------------------
 2   7849.95 Hz                  31960.5
 4   3924.975                    15980.25
 8   1962.4875                   7990.125
16   981.24375                   3995.0625
32   490.621875                  1997.53125 

And so-on.


31  NOPCM
--
Disables PCM playback on POKEY voice 4.


32  GOFM
--
Enable frequency modulation on POKEY voice 4. Operands 1 and 2 do nothing here.


33  SETFMF  (SET FM Frequency)
--
Set the frequency of the sine oscillator/FM operator.

The low nybble of operand 1 sets the target:

  x0 --special--
  x1 OPF0 (sample skip)
  x2 AUDF4 (sample rate)
  x3 INTRVLF (scale index)
  x4 FMTRANSP (transposion)

The high nybble determines the operation

  0x --special--
  1x SET from OPERAND2
  2x SET from REGISTER LOW BYTE
  3x SET from RANDOM
  4x ADD from OPERAND2
  5x ADD from REGISTER LOW
  6x ADD from RANDOM

Here's how this works: there's a 256-byte PCM table of a sine wave in memory, and we use the POKEY high-resolution timers to read that table and set the volume of POKEY voice 4 to produce a sine wave sound, or we use the values of the sine table to set/modify the pitch of voice 4 to do some crude FM.

The pitch of the sine operator is determined by setting a sample SKIP VALUE, and a sample RATE. 

The sample rate is determined by the value of AUDF4, and that value is a divider of POKEY's main clock frequency. So, with POKEY on its 15KHz clock (15,699.9 Hz), an AUDF4 value of 2 sets the sample rate to 7849.95Hz,  while a value of 4 sets the sample rate to 3924.975 Hz, and so-on.

The skip value, stored in a zero-page register we call OPF0, determines how many samples are read from the sine table before skipping one. So the pitch of the sine oscillator is determined by:

  SAMPLE_RATE / (SAMPLE_LENGTH - (SAMPLE_LENGTH / SKIP_VALUE)

Put simply, if imprecisely: incrementing the sample rate raises pitch, incrementing the skip value lowers pitch. 

Now, for our sanity there's a table of rate/skip values representing a 5-octave chromatic scale starting at C0 (based on A=440Hz) stored in the program, and we can set the sine oscillator to one of those tones by simply setting a scale index (INTRVLF), rather than setting OPF4 and AUDF4 individually.

The "--special--" function listed above is simply this: when operand 1 is set to "00", the sine/FM oscillator will match the frequency of POKEY voice 1.


34  SETFMC  (SET FM Controls)
--
Sets FM/sine oscillator configuration.

The first operand determines what is to be done:

00 - Change FM/Sine configuration
01 - Change FM amount
>01 - Set sine volume ...?

When operand 1 is 0, the value of operand 2 sets the configuration:

00 - FM/Sine OFF
40 - Sine oscillator ON
80 - FM ON
C0 - FM & Sine oscillator ON

When setting the FM amount (operand 1 = 1), only the three lowest bits of operand 2 are used, for 8 levels of intensity. Seven, really, because a value of 0 mean no FM.

When setting the sine wave volume (operand 1 > 1).... I have to experiment with this.


35  SETCVOX  (SET distortion C timbre)
--
The source says that this sets "distortion C voice. Toggle between 64 and 128". 

As I recall, when a POKEY voice is in distortion C mode, it actually produces two different timbres which are non-linearly interleaved, but with the use of a lookup table you can get a nearly equal-tempered musical scale out of it. This sets which lookup table to use, and thus which timbre is produced.  


3B  SETSCALE
--
Set the scale table vector. 
...


3C  PEEK
--
Load a byte from memory into the current playhead's register (low byte).
...

3D  PEEKW  (PEEK Word)
--
Load a word from memory into the current playhead's register.
...


3E  POKE
--
Copy the low byte of the current playhead's register to the specified memory location.
...


3F  POKEW  (POKE Word)
--
Copy the value of the playhead's register to the specified memory location.
...