~rabbits/famicom-cookbook

2b7096f77eed93d2f100bb931b6ae96d59a0fbf5 — Devine Lu Linvega 3 years ago e0f0162
Reorganizing examples
64 files changed, 2167 insertions(+), 1991 deletions(-)

M README.md
M docs/apu_notes.txt
A docs/apu_ref.txt
D examples/animation-bounce/build.sh
D examples/animation-bounce/src/cart.asm
A examples/animation-follow.asm
D examples/animation-follow/build.sh
D examples/animation-follow/src/cart.asm
D examples/animation-follow/src/sprite.chr
A examples/animation-sine.asm
D examples/animation-sine/build.sh
D examples/animation-sine/src/cart.asm
D examples/animation-sine/src/sprite.chr
D examples/animation-sine/tools/index.js
A examples/audio-tone.asm
D examples/audio/notes.txt
D examples/audio/src/cart.asm
D examples/audio/src/sprite.chr
A examples/background-attributes.asm
D examples/background-attributes/build.sh
D examples/background-attributes/src/cart.asm
D examples/background-attributes/src/head.asm
D examples/background-attributes/src/main.asm
D examples/background-attributes/src/sprite.chr
D examples/background-attributes/src/tables.asm
D examples/background-attributes/src/vectors.asm
M examples/background-change/src/head.asm
M examples/background-edit/src/head.asm
M examples/background-full/src/head.asm
M examples/background-multiple/src/head.asm
M examples/background-partial/src/head.asm
M examples/background-single/src/cart.asm
M examples/bit-display/src/head.asm
M examples/bit-masks/src/cart.asm
M examples/bit-rotate/src/cart.asm
R examples/{audio/build.sh => build.sh}
R examples/{audio/cart.nes => cart.nes}
M examples/collision-push/src/head.asm
M examples/collision/src/cart.asm
M examples/controls-debugger/src/head.asm
M examples/controls-display/src/head.asm
M examples/controls-dragdrop/src/head.asm
M examples/controls-reset/src/head.asm
M examples/controls-timer/src/cart.asm
M examples/controls/src/head.asm
M examples/fceux-debugger/src/head.asm
M examples/fceux-udp/src/head.asm
M examples/gui/src/head.asm
M examples/math/src/head.asm
M examples/renderer/src/head.asm
M examples/shuffle-draw/src/cart.asm
M examples/shuffle/src/cart.asm
M examples/sprite-loop/src/head.asm
M examples/sprite-mirror/src/head.asm
M examples/sprite-spritesheet/src/head.asm
R examples/{animation-bounce/src/sprite.chr => sprite.chr}
M examples/sprite/src/head.asm
M examples/text/src/head.asm
M examples/timers/src/head.asm
M projects/opn2/src/head.asm
M tables/notes.asm
A tables/notes2.asm
M tools/lin6.c
R examples/animation-bounce/tools/index.js => tools/sin.js
M README.md => README.md +1 -1
@@ 1,6 1,6 @@
# The Famicom Cookbook

This is a collection of tools and example files to make [Famicom games](https://100r.co/site/6502_assembly.html).
This is a collection of tools and example files to make [Famicom games](https://wiki.xxiivv.com/site/famicom.html) in [6502 Assembly](https://wiki.xxiivv.com/site/assembly.html).

We had a hard time finding examples for the asm6 assembler, most examples out there we could find made use of compilers and toolchains which were exclusively available on Windows, so we've put this together from bits and pieces of example projects we migrated from either nesasm3 or cc65, to the simpler asm6 compiler. We use the [Nasu Editor](https://100r.co/site/nasu.html) to create spritesheets and nametables.


M docs/apu_notes.txt => docs/apu_notes.txt +1 -1
@@ 155,4 155,4 @@ Octave 8
|E  |9.606193779   |$00A       |10548.08182   |
|F  |9.010913882   |$009       |11175.30341   |
|Gb |8.44904446  |$008       |11839.82153   |
----------------------------------------------------------
\ No newline at end of file
----------------------------------------------------------

A docs/apu_ref.txt => docs/apu_ref.txt +804 -0
@@ 0,0 1,804 @@
NES APU Sound Hardware Reference
--------------------------------

This reference covers Nintendo Entertainment System (NES) sound hardware in as
much detail as I know. It is intended primarily to assist in the implementation
of emulators and might also be useful as a programmer reference.

Tables, diagrams, and formulas are formatted for a mono-spaced font, like
Courier. 

The latest version is kept at http://www.slack.net/~ant/nes-emu/apu_ref.txt


-----
Intro
-----

This reference is based on the results of tests I have run on a 1988-model US
NTSC NES which contains the G revision of the 2A03 CPU/APU and the NES-CPU-07
version of the main board. PAL hardware will be covered once tests are
performed on it. Feel free to incorporate this information in references and
other documentation.

While implementing a NES sound emulator, even after reading the available
documentation I still had many unanswered questions, so I made a simple
development cartridge to test on a real NES. This was very successful and
revealed many new details. My notes consisted of differences from existing
documentation, but this didn't seem to be a very reliable way to release my
findings, so I decided to write a concise reference.

For those familiar with NESSOUND.TXT and DMC.TXT, the following differences
should be specifically noted:

Corrections:
    - DMC table entry $D should be $2A0 instead of $2A8
    - Frame sequencer
    - Square's duty generator

Clarifications:
    - DMC
    - Triangle's linear counter
    - Length Counter operation and status register behavior

It should go without saying that the model presented here probably doesn't
match the actual logic gate arrangement in the NES. It makes no difference how
the hardware is implemented, as long as its behavior matches what is described
here

Corrections, questions and additions are welcome. I keep up with the forum at
http://nesdev.parodius.com/ and can be contacted via blargg at the mail.com
domain.


-----
To Do
-----

- See if comprehensive emulator test ROM is even practical.

- Probe complete power-up state and reset state.

- Test PAL hardware to determine APU frame rate, DMC and noise period tables.

- Check complete behavior of each unit of each channel to be sure common units
behave the same for all channels.

- Double-check details on NES hardware again.

- Determine post-DAC filtering done before output.


--------
Overview
--------

The APU is composed of five channels: square 1, square 2, triangle, noise,
delta modulation channel (DMC). Each has a variable-rate timer clocking a
waveform generator, and various modulators driven by low-frequency clocks from
a frame sequencer. The DMC plays samples while the other channels play
waveforms. The waveform channels have duration control, some have a volume
envelope unit, and a couple have a frequency sweep unit.

        Square 1/Square 2

$4000/4 ddle nnnn   duty, loop env/disable length, env disable, vol/env
period
$4001/5 eppp nsss   enable sweep, period, negative, shift
$4002/6 pppp pppp   period low
$4003/7 llll lppp   length index, period high

        Triangle

$4008   clll llll   control, linear counter load
$400A   pppp pppp   period low
$400B   llll lppp   length index, period high

        Noise

$400C   --le nnnn   loop env/disable length, env disable, vol/env period
$400E   s--- pppp   short mode, period index
$400F   llll l---   length index

        DMC

$4010   il-- ffff   IRQ enable, loop, frequency index
$4011   -ddd dddd   DAC
$4012   aaaa aaaa   sample address
$4013   llll llll   sample length

        Common

$4015   ---d nt21   length ctr enable: DMC, noise, triangle, pulse 2, 1
$4017   fd-- ----   5-frame cycle, disable frame interrupt

        Status (read)

$4015   if-d nt21   DMC IRQ, frame IRQ, length counter statuses


------
Basics
------

Hexadecimal values are prefixed by a $ except for some single-hex-digit
sequences where it's clear that they are hex. Bits are numbered from 0 to 7,
corresponding with the least to most significant bits of a byte; bit n has a
binary weight of 2^n.

A flag is a two-state variable that can be either set or clear. When
implemented in a bit, clear = 0 and set = 1.

A divider outputs a clock every n input clocks, where n is the divider's
period. It contains a counter which is decremented on the arrival of each
clock. When it reaches 0, it is reloaded with the period and an output clock is
generated. Resetting a divider reloads its counter without generating an output
clock. Changing a divider's period doesn't affect its current count.

A sequencer generates a series of values or events based on the repetition of a
series of steps, starting with the first. When clocked the next step of the
sequence is generated.

In the block diagrams, the triangular symbol is a control gate; if control is
non-zero, the input is passed unchanged to the output, otherwise the output is
0.

        control
           | 
           v 
          |\
    in -->| >-- out
          |/


Except for the status register, all other registers are write-only. The "value
of the register" refers to the last value written to the register.

The NTSC NES has a master clock based on a 21.47727 MHz crystal which is
divided by 12 to obtain a ~1.79 MHz clock source. Both clocks are used by the
APU.

The CPU's IRQ line is level-sensitive, so the APU's interrupt flags must be
cleared once a CPU IRQ is acknowledged, otherwise the CPU will immediately be
interrupt again once its inhibit flag is cleared.

In general, the APU is a collection of many independent units which are always
running in parallel. Modification of a channel's parameter usually affects only
one sub-unit and doesn't take effect until that unit's next internal cycle
begins.

Each section begins with an overview and an optional block diagram, which
provide a framework for the information that follows. In order to reduce
ambiguity, there is very little re-statement of information.


---------------
Frame Sequencer
---------------

The frame sequencer contains a divider and a sequencer which clocks various
units.

The divider generates an output clock rate of just under 240 Hz, and appears to
be derived by dividing the 21.47727 MHz system clock by 89490. The sequencer is
clocked by the divider's output.

On a write to $4017, the divider and sequencer are reset, then the sequencer is
configured. Two sequences are available, and frame IRQ generation can be
disabled.

    mi-- ----       mode, IRQ disable

If the mode flag is clear, the 4-step sequence is selected, otherwise the
5-step sequence is selected and the sequencer is immediately clocked once.

    f = set interrupt flag
    l = clock length counters and sweep units
    e = clock envelopes and triangle's linear counter

mode 0: 4-step  effective rate (approx)
---------------------------------------
    - - - f      60 Hz
    - l - l     120 Hz
    e e e e     240 Hz

mode 1: 5-step  effective rate (approx)
---------------------------------------
    - - - - -   (interrupt flag never set)
    l - l - -    96 Hz
    e e e e -   192 Hz

At any time if the interrupt flag is set and the IRQ disable is clear, the
CPU's IRQ line is asserted.


--------------
Length Counter
--------------

A length counter allows automatic duration control. Counting can be halted and
the counter can be disabled by clearing the appropriate bit in the status
register, which immediately sets the counter to 0 and keeps it there.

The halt flag is in the channel's first register. For the square and noise
channels, it is bit 5, and for the triangle, bit 7:

    --h- ----       halt (noise and square channels)
    h--- ----       halt (triangle channel)

Note that the bit position for the halt flag is also mapped to another flag in
the Envelope Generator (noise and square) or Linear Counter (triangle).

Unless disabled, a write the channel's fourth register immediately reloads the
counter with the value from a lookup table, based on the index formed by the
upper 5 bits:

    iiii i---       length index
    
    bits  bit 3
    7-4   0   1
        -------
    0   $0A $FE
    1   $14 $02
    2   $28 $04
    3   $50 $06
    4   $A0 $08
    5   $3C $0A
    6   $0E $0C
    7   $1A $0E
    8   $0C $10
    9   $18 $12
    A   $30 $14
    B   $60 $16
    C   $C0 $18
    D   $48 $1A
    E   $10 $1C
    F   $20 $1E

See the clarifications section for a possible explanation for the values left
column of the table.

When clocked by the frame sequencer, if the halt flag is clear and the counter
is non-zero, it is decremented.


---------------
Status Register
---------------

The status register at $4015 allows control and query of the channels' length
counters, and query of the DMC and frame interrupts. It is the only register
which can also be read.

When $4015 is read, the status of the channels' length counters and bytes
remaining in the current DMC sample, and interrupt flags are returned.
Afterwards the Frame Sequencer's frame interrupt flag is cleared.

    if-d nt21
    
    IRQ from DMC
    frame interrupt
    DMC sample bytes remaining > 0
    triangle length counter > 0
    square 2 length counter > 0
    square 1 length counter > 0

When $4015 is written to, the channels' length counter enable flags are set, 
the DMC is possibly started or stopped, and the DMC's IRQ occurred flag is
cleared.

    ---d nt21   DMC, noise, triangle, square 2, square 1

If d is set and the DMC's DMA reader has no more sample bytes to fetch, the DMC
sample is restarted. If d is clear then the DMA reader's sample bytes remaining
is set to 0.


------------------
Envelope Generator
------------------

An envelope generator can generate a constant volume or a saw envelope with
optional looping. It contains a divider and a counter.

A channel's first register controls the envelope:

    --ld nnnn       loop, disable, n

Note that the bit position for the loop flag is also mapped to a flag in the
Length Counter.

The divider's period is set to n + 1.

When clocked by the frame sequencer, one of two actions occurs: if there was a
write to the fourth channel register since the last clock, the counter is set
to 15 and the divider is reset, otherwise the divider is clocked.

When the divider outputs a clock, one of two actions occurs: if loop is set and
counter is zero, it is set to 15, otherwise if counter is non-zero, it is
decremented.

When disable is set, the channel's volume is n, otherwise it is the value in
the counter. Unless overridden by some other condition, the channel's DAC
receives the channel's volume value.


-----
Timer
-----

All channels use a timer which is a divider driven by the ~1.79 MHz clock.

The noise channel and DMC use lookup tables to set the timer's period. For the
square and triangle channels, the third and fourth registers form an 11-bit
value and the divider's period is set to this value *plus one*.

    llll llll       low 8 bits of period   (third register)
    ---- -hhh       upper 3 bits of period (fourth register)


----------
Sweep Unit
----------

The sweep unit can adjust a square channel's period periodically. It contains a
divider and a shifter.

A channel's second register configures the sweep unit:

    eppp nsss       enable, period, negate, shift

The divider's period is set to p + 1.

The shifter continuously calculates a result based on the channel's period. The
channel's period (from the third and fourth registers) is first shifted right
by s bits. If negate is set, the shifted value's bits are inverted, and on the
second square channel, the inverted value is incremented by 1. The resulting
value is added with the channel's current period, yielding the final result.

When the sweep unit is clocked, the divider is *first* clocked and then if
there was a write to the sweep register since the last sweep clock, the divider
is reset.

When the channel's period is less than 8 or the result of the shifter is
greater than $7FF, the channel's DAC receives 0 and the sweep unit doesn't
change the channel's period. Otherwise, if the sweep unit is enabled and the
shift count is greater than 0, when the divider outputs a clock, the channel's
period in the third and fourth registers are updated with the result of the
shifter.


--------------
Square Channel
--------------

                   +---------+    +---------+
                   |  Sweep  |--->|Timer / 2|
                   +---------+    +---------+
                        |              |
                        |              v 
                        |         +---------+    +---------+
                        |         |Sequencer|    | Length  |
                        |         +---------+    +---------+
                        |              |              |
                        v              v              v
    +---------+        |\             |\             |\          +---------+
    |Envelope |------->| >----------->| >----------->| >-------->|   DAC   |
    +---------+        |/             |/             |/          +---------+

There are two square channels beginning at registers $4000 and $4004. Each
contains the following: Envelope Generator, Sweep Unit, Timer with
divide-by-two on the output, 8-step sequencer, Length Counter.

$4000/$4004: duty, envelope
$4001/$4005: sweep unit
$4002/$4006: period low
$4003/$4007: reload length counter, period high

In addition to the envelope, the first register controls the duty cycle of the
square wave, without resetting the position of the sequencer:

    dd-- ----       duty cycle select
    
    d   waveform sequence
    ---------------------
         _       1
    0   - ------ 0 (12.5%)

         __      1
    1   -  ----- 0 (25%)

         ____    1
    2   -    --- 0 (50%)

        _  _____ 1
    3    --      0 (25% negated)


When the fourth register is written to, the sequencer is restarted.

The sequencer is clocked by the divided timer output.

When the sequencer output is low, the DAC receives 0.


--------------
Linear Counter
--------------

The Linear Counter serves as a second more-accurate duration counter for the
triangle channel. It contains a counter and an internal halt flag.

Register $4008 contains a control flag and reload value:

    crrr rrrr   control flag, reload value

Note that the bit position for the control flag is also mapped to a flag in the
Length Counter.

When register $400B is written to, the halt flag is set.

When clocked by the frame sequencer, the following actions occur in order:

    1) If halt flag is set, set counter to reload value, otherwise if counter
is non-zero, decrement it.

    2) If control flag is clear, clear halt flag.


----------------
Triangle Channel
----------------

                   +---------+    +---------+
                   |LinearCtr|    | Length  |
                   +---------+    +---------+
                        |              |
                        v              v
    +---------+        |\             |\         +---------+    +---------+ 
    |  Timer  |------->| >----------->| >------->|Sequencer|--->|   DAC   |
    +---------+        |/             |/         +---------+    +---------+ 

The triangle channel contains the following: Timer, 32-step sequencer, Length
Counter, Linear Counter, 4-bit DAC.

$4008: length counter disable, linear counter
$400A: period low
$400B: length counter reload, period high

When the timer generates a clock and the Length Counter and Linear Counter both
have a non-zero count, the sequencer is clocked.

The sequencer feeds the following repeating 32-step sequence to the DAC:

    F E D C B A 9 8 7 6 5 4 3 2 1 0 0 1 2 3 4 5 6 7 8 9 A B C D E F

At the lowest two periods ($400B = 0 and $400A = 0 or 1), the resulting
frequency is so high that the DAC effectively outputs a value half way between
7 and 8.


-------------   
Noise Channel
-------------

    +---------+    +---------+    +---------+
    |  Timer  |--->| Random  |    | Length  |
    +---------+    +---------+    +---------+
                        |              |
                        v              v
    +---------+        |\             |\         +---------+
    |Envelope |------->| >----------->| >------->|   DAC   |
    +---------+        |/             |/         +---------+

The noise channel starts at register $400C and contains the following: Length
Counter, Envelope Generator, Timer, 15-bit right shift register with feedback,
4-bit DAC.

$400C: envelope
$400E: mode, period
$400F: reload length counter                   

Register $400E sets the random generator mode and timer period based on a 4-bit
index into a period table:

    m--- iiii       mode, period index 
    
    i   timer period
    ----------------
    0     $004
    1     $008
    2     $010
    3     $020
    4     $040
    5     $060
    6     $080
    7     $0A0
    8     $0CA
    9     $0FE
    A     $17C
    B     $1FC
    C     $2FA
    D     $3F8
    E     $7F2
    F     $FE4

The shift register is clocked by the timer and the vacated bit 14 is filled
with the exclusive-OR of *pre-shifted* bits 0 and 1 (mode = 0) or bits 0 and 6
(mode = 1), resulting in 32767-bit and 93-bit sequences, respectively.

When bit 0 of the shift register is set, the DAC receives 0.

On power-up, the shift register is loaded with the value 1.


------------------------------
Delta Modulation Channel (DMC)
------------------------------

    +----------+    +---------+
    |DMA Reader|    |  Timer  |
    +----------+    +---------+
         |               |
         |               v
    +----------+    +---------+     +---------+     +---------+ 
    |  Buffer  |----| Output  |---->| Counter |---->|   DAC   |
    +----------+    +---------+     +---------+     +---------+ 

The DMC can output samples composed of 1-bit deltas and its DAC can be directly
changed. It contains the following: DMA reader, interrupt flag, sample buffer,
Timer, output unit, 7-bit counter tied to 7-bit DAC.

$4010: mode, frequency
$4011: DAC
$4012: address
$4013: length

On power-up, the DAC counter contains 0.

Register $4010 sets the interrupt enable, loop, and timer period. If the new
interrupt enabled status is clear, the interrupt flag is cleared.

    il-- ffff       interrupt enabled, loop, frequency index
    
    f   period
    ----------
    0   $1AC
    1   $17C
    2   $154
    3   $140
    4   $11E
    5   $0FE
    6   $0E2
    7   $0D6
    8   $0BE
    9   $0A0
    A   $08E
    B   $080
    C   $06A
    D   $054
    E   $048
    F   $036

A write to $4011 sets the counter and DAC to a new value:

    -ddd dddd       new DAC value

Sample Buffer
-------------
The sample buffer either holds a single sample byte or is empty. It is filled
by the DMA reader and can only be emptied by the output unit, so once loaded
with a sample it will be eventually output.

DMA Reader
----------
The DMA reader fills the sample buffer with successive bytes from the current
sample, whenever it becomes empty. It has an address counter and a bytes remain
counter.

When the DMC sample is restarted, the address counter is set to register $4012
* $40 + $C000 and the bytes counter is set to register $4013 * $10 + 1.

When the sample buffer is in an empty state and the bytes counter is non-zero,
the following occur: The sample buffer is filled with the next sample byte read
from memory at the current address, subject to whatever mapping hardware is
present (the same as CPU memory accesses). The address is incremented; if it
exceeds $FFFF, it is wrapped around to $8000. The bytes counter is decremented;
if it becomes zero and the loop flag is set, the sample is restarted (see
above), otherwise if the bytes counter becomes zero and the interrupt enabled
flag is set, the interrupt flag is set.

When the DMA reader accesses a byte of memory, the CPU is suspended for 4 clock
cycles.

Output Unit
-----------
The output unit continually outputs complete sample bytes or silences of equal
duration. It contains an 8-bit right shift register, a counter, and a silence
flag.

When an output cycle is started, the counter is loaded with 8 and if the sample
buffer is empty, the silence flag is set, otherwise the silence flag is cleared
and the sample buffer is emptied into the shift register.

On the arrival of a clock from the timer, the following actions occur in order:

    1. If the silence flag is clear, bit 0 of the shift register is applied to
the DAC counter: If bit 0 is clear and the counter is greater than 1, the
counter is decremented by 2, otherwise if bit 0 is set and the counter is less
than 126, the counter is incremented by 2.

    1) The shift register is clocked.
    
    2) The counter is decremented. If it becomes zero, a new cycle is started.


----------
DAC Output
----------

The DACs for each channel are implemented in a way that causes non-linearity
and interaction between channels, so calculation of the resulting amplitude is
somewhat involved.

The normalized audio output level is the sum of two groups of channels:

    output = square_out + tnd_out
    
    
                          95.88
    square_out = -----------------------
                        8128
                 ----------------- + 100
                 square1 + square2


                          159.79
    tnd_out = ------------------------------
                          1
              ------------------------ + 100
              triangle   noise    dmc
              -------- + ----- + -----
                8227     12241   22638


where triangle, noise, dmc, square1 and square2 are the values fed to their
DACs. The dmc ranges from 0 to 127 and the others range from 0 to 15. When the
sub-denominator of a group is zero, its output is 0. The output ranges from 0.0
to 1.0.


Implementation Using Lookup Table 
---------------------------------
The formulas can be efficiently implemented using two lookup tables: a 31-entry
table for the two square channels and a 203-entry table for the remaining
channels (due to the approximation of tnd_out, the numerators are adjusted
slightly to preserve the normalized output range).

    square_table [n] = 95.52 / (8128.0 / n + 100)
    
    square_out = square_table [square1 + square2]
    
The latter table is approximated (within 4%) by using a base unit close to the
DMC's DAC.

    tnd_table [n] = 163.67 / (24329.0 / n + 100)
    
    tnd_out = tnd_table [3 * triangle + 2 * noise + dmc]


Linear Approximation
--------------------
A linear approximation can also be used, which results in slightly louder DMC
samples, but otherwise fairly accurate operation since the wave channels use a
small portion of the transfer curve. The overall volume will be reduced due to
the headroom required by the DMC approximation.

    square_out = 0.00752 * (square1 + square2)
    
    tnd_out = 0.00851 * triangle + 0.00494 * noise + 0.00335 * dmc

This linear approximation neglects the attenuating effect the DMC has when its
DAC is in the upper level. This factor can be calculated using the main formula
to form a ratio, and precalculated into a 128-entry lookup table.

                     tnd_out(triangle=15,dmc=d) - tnd_out(triangle=0,dmc=d)
    attenuation(d) = ------------------------------------------------------
                                  tnd_out(triangle=15,dmc=0)

-------------------
Unreliable Behavior
-------------------

(The following behaviors probably don't need to be emulated due to their
unreliability since stable code will avoid invoking it, and since their
behavior is somewhat difficult to precisely predict.)

If the frame IRQ is set just as register $4015 is being read, it seems to be
ignored (similar to polling $2002 for the vbl flag).

The DMC's DMA reader seems to check for an empty buffer every few CPU cycles,
rather than every cycle or continuously.

Writing to the DAC register ($4011) while a sample is playing sometimes has no
effect, probably because the DMC's output unit is clocking the counter at the
same moment as the write.


--------------
Clarifications
--------------

(The following are meant only as re-statements of the main content, rather than
additions of new content.)

Because the envelope loop and length counter disable flags are mapped to the
same bit, the length counter can't be used while the envelope is in loop mode.
Similar applies to the triangle channel, where the linear counter and length
counter are both controlled by the same bit in register $4008.

Unlike the other waveform channels, the triangle channel is silenced by
stopping its waveform at whatever phase it's at, rather than causing zero to be
sent to its DAC.

The length counter table seems to be set up for standard note durations for 4/4
time at 160 bpm and 180 bpm. If bit 3 is 0, the following results (Dn is bit n
of the fourth channel register):

            180bpm  160bpm
    D6-D4   D7=0    D7=1    note
    -------------------------------
    $00     10      12      16th
    $01     20      24      8th
    $02     40      48      4th (one beat)
    $03     80      96      half
    $04     160     192     whole
    $05     60      72      4th dotted
    $06     14      16      8th triplet (*3 = a 4th)
    $07     26      32      4th triplet (*3 = a half)


-------------
Collaborators
-------------

Brad Taylor's NESSOUND.TXT and DMC.TXT as a starting point for testing.
NTSC NES for testing on.
Nesdev forum for feedback.
xodnizel for testing results, correction to DMC table, feedback.
Bloopaws/Draci for feedback, possible explanation of length counter table
values.


-------
History
-------

2003.12.01
    Made development cartridge and started testing on NES hardware.

2003.12.14
    Started project.

2003.12.20
    A few draft sections were posted to Nesdev or e-mailed privately.

2004.01.02
    Draft version posted to Nesdev. Corrected tnd_table formula.

2004.01.02
    Corrected incorrect "correction" to tnd_table formula. Double-checked them.

2004.01.03
    Corrected envelope flag name to "disable" (it was named "enable").
    Added effective frequencies of frame sequencer outputs.
    Added Overview, Unreliable Behavior, Clarifications, and Collaborators
sections.

2004.01.04
    Adjusted linear approximation (difficult to find a compromise).
    A few minor edits.

2004.01.30
    First release. Probably won't be doing much with it for a while.


D examples/animation-bounce/build.sh => examples/animation-bounce/build.sh +0 -17
@@ 1,17 0,0 @@
#!/bin/bash

# Remove old

rm cart.nes

# Lint source

node ../../tools/lint6502.js

# Build

../../assembler/asm6 src/cart.asm cart.nes

# Run

fceux cart.nes
\ No newline at end of file

D examples/animation-bounce/src/cart.asm => examples/animation-bounce/src/cart.asm +0 -174
@@ 1,174 0,0 @@

;; ines header

  .db  "NES", $1a              ; identification of the iNES header
  .db  PRG_COUNT               ; number of 16KB PRG-ROM pages
  .db  $01                     ; number of 8KB CHR-ROM pages
  .db  $70|MIRRORING           ; mapper 7
  .dsb $09, $00                ; clear the remaining bytes
  .fillvalue $FF               ; Sets all unused space in rom to value $FF

;; constants

PRG_COUNT       = 1            ; 1 = 16KB, 2 = 32KB
MIRRORING       = %0001
PPU_Control         .equ $2000
PPU_Mask            .equ $2001
PPU_Status          .equ $2002
PPU_Scroll          .equ $2005
PPU_Address         .equ $2006
PPU_Data            .equ $2007
spriteRAM           .equ $0200

;;

  .org $C000

;; reset

RESET:                         ; 
  SEI                          ; disable IRQs
  CLD                          ; disable decimal mode
  LDX #$40
  STX $4017                    ; disable APU frame IRQ
  LDX #$FF
  TXS                          ; Set up stack
  INX                          ; now X = 0
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2

;;

LoadPalettes:                  ; 
  LDA $2002                    ; read PPU status to reset the high/low latch
  LDA #$3F
  STA $2006                    ; write the high byte of $3F00 address
  LDA #$00
  STA $2006                    ; write the low byte of $3F00 address
  LDX #$00
@loop:                         ; 
  LDA palette, x               ; load palette byte
  STA $2007                    ; write to PPU
  INX                          ; set index to next byte
  CPX #$20
  BNE @loop                    ; if x = $20, 32 bytes copied, all done

;;

DrawSprite1:                   ; 
  LDA #$88
  STA $0200                    ; set tile.y pos
  LDA #$05
  STA $0201                    ; set tile.id
  LDA #$00
  STA $0202                    ; set tile.attribute
  LDA #$88
  STA $0203                    ; set tile.x pos
DrawSprite2:                   ; 
  LDA #$88
  STA $0204                    ; set tile.y pos
  LDA #$06
  STA $0205                    ; set tile.id
  LDA #$00
  STA $0206                    ; set tile.attribute
  LDA #$80
  STA $0207                    ; set tile.x pos
  LDA #%10000000               ; enable NMI, sprites from Pattern Table 0
  STA $2000
  LDA #%00010000               ; enable sprites
  STA $2001

;; jump back to Forever, infinite loop

Forever:                       ; 
  JMP Forever

;;

NMI:                           ; 
  LDA #$00
  STA $2003                    ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014                    ; set the high byte (02) of the RAM address, start the transfer
  JSR animate
  RTI                          ; return from interrupt

;;

animate:                       ; 
  ; reg $10 position
  ; reg $11 direction
@flip                          ; load pos, compare with min/max, flip sprite
  LDA $10                      ; 
  CMP #$20                     ; min
  BEQ @flipright
  CMP #$50                     ; max
  BEQ @flipleft
  JMP @move
@flipright:                    ; 
  LDA #$01
  STA $11
  JMP @move
@flipleft:                     ; 
  LDA #$00
  STA $11
@move:                         ; move pos base on direction
  LDA $11                      ; load direction
  CMP #$01
  BEQ @inc
@movedec:                          ; 
  DEC $10
  JMP @done
@moveinc:                          ; 
  INC $10
@done:                         ; 
  LDA $10                      ; load pos
  STA $0207                    ; write sprite
  RTS

;; TABLES

  .org $E000

;;

palette:                       ; 
  .db $0F,$0F,$0F,$0F,  $0F,$20,$20,$0F,  $0F,$20,$0F,$20,  $0F,$20,$20,$20; background palette
  .db $0F,$27,$17,$07,  $0F,$20,$10,$00,  $0F,$1C,$15,$14,  $0F,$02,$38,$3C; sprite palette
sin:                           ; 
  .db $40,$46,$4c,$52,$58,$5e,$63,$68,$6d,$71,$75,$78,$7b,$7d
  .db $7e,$7f,$80,$7f,$7e,$7d,$7b,$78,$75,$71,$6d,$68,$63,$5e
  .db $58,$52,$4c,$46,$47,$3a,$34,$2e,$28,$22,$1d,$18,$13,$0f
  .db $0b,$08,$05,$03,$02,$01,$00,$01,$02,$03,$05,$08,$0b,$0f
  .db $13,$18,$1d,$22,$28,$2e,$34,$3a

;; vectors

  .pad $FFFA
  .dw NMI
  .dw RESET
  .dw 0

;; sprite

  .incbin "src/sprite.chr"
\ No newline at end of file

A examples/animation-follow.asm => examples/animation-follow.asm +235 -0
@@ 0,0 1,235 @@

;; iNES header

	.db "NES", $1a ; identification of the iNES header
	.db 1 ; number of 16KB PRG-ROM pages
	.db $01 ; number of 8KB CHR-ROM pages
	.db $00 ; NROM
	.dsb $09,$00 ; clear the remaining bytes
	.fillvalue $FF ; Sets all unused space in rom to value $FF

;; variables

	.enum $0000 ; Zero Page variables
	pos_x                   .dsb 1
	pos_y                   .dsb 1
	.ende 

;; constants

	.org $C000

;;  RESET

RESET: 
	SEI                                ; disable IRQs
	CLD                                ; disable decimal mode
	LDX #$40
	STX $4017                          ; disable APU frame IRQ
	LDX #$FF
	TXS                                ; Set up stack
	INX                                ; now X = 0
	STX $2000                          ; disable NMI
	STX $2001                          ; disable rendering
	STX $4010                          ; disable DMC IRQs

Vblankwait1: 
	BIT $2002
	BPL Vblankwait1

Clrmem: 
	LDA #$00
	STA $0000, x
	STA $0100, x
	STA $0300, x
	STA $0400, x
	STA $0500, x
	STA $0600, x
	STA $0700, x
	LDA #$FE
	STA $0200, x                       ; move all sprites off screen
	INX 
	BNE Clrmem

Vblankwait2: 
	BIT $2002
	BPL Vblankwait2

;; palette

LoadPalettes: 
	LDA $2002                          ; read PPU status to reset the high/low latch
	LDA #$3F
	STA $2006                          ; write the high byte of $3F00 address
	LDA #$00
	STA $2006                          ; write the low byte of $3F00 address
	LDX #$00                           ; start out at 0
@loop: 
	LDA Palette, x                     ; load data from address (palette + the value in x)
	STA $2007                          ; write to PPU
	INX                                ; X = X + 1
	CPX #$20                           ; Compare X to hex $10, decimal 16 - copying 16 bytes = 4 sprites
	BNE @loop

;; sprite

CreateSprite: 
	LDA #$88
	STA $0200                          ; set tile.y pos
	LDA #$05
	STA $0201                          ; set tile.id
	LDA #$00
	STA $0202                          ; set tile.attribute
	LDA #$88
	STA $0203                          ; set tile.x pos

CreateFollower: 
	LDA #$88
	STA $0204                          ; set tile.y pos
	LDA #$05
	STA $0205                          ; set tile.id
	LDA #$00
	STA $0206                          ; set tile.attribute
	LDA #$88
	STA $0207                          ; set tile.x pos

;; enable sprites

	LDA #%10000000                     ; enable NMI, sprites from Pattern Table 1
	STA $2000
	LDA #%00010000                     ; enable sprites
	STA $2001

;; jump back to Forever, infinite loop

Forever: 
	JMP Forever

;; nmi

NMI: 
	LDA #$00
	STA $2003                          ; set the low byte (00) of the RAM address
	LDA #$02
	STA $4014                          ; set the high byte (02) of the RAM address, start the transfer

;; update

	JSR UpdateFollower

;; controls

LatchController: 
	LDA #$01
	STA $4016
	LDA #$00
	STA $4016                          ; tell both the controllers to latch buttons
@ReadA: 
	LDA $4016
	AND #%00000001                     ; only look at BIT 0
	BEQ @ReadB
	LDA #$06                           ; sprite tile
	STA $0201
@ReadB: 
	LDA $4016
	AND #%00000001                     ; only look at BIT 0
	BEQ @ReadSel
	LDA #$06                           ; sprite tile
	STA $0201
@ReadSel: 
	LDA $4016
	AND #%00000001                     ; only look at BIT 0
	BEQ @ReadStart
	LDA #$06                           ; sprite tile
	STA $0201
@ReadStart: 
	LDA $4016
	AND #%00000001                     ; only look at BIT 0
	BEQ @ReadUp
	LDA #$06                           ; sprite tile
	STA $0201
@ReadUp: 
	LDA $4016
	AND #%00000001                     ; only look at BIT 0
	BEQ @ReadDown
	DEC $0200
	DEC $0200
	LDA #$01                           ; sprite tile
	STA $0201
@ReadDown: 
	LDA $4016
	AND #%00000001                     ; only look at BIT 0
	BEQ @ReadLeft
	INC $0200
	INC $0200
	LDA #$02                           ; sprite tile
	STA $0201
@ReadLeft: 
	LDA $4016
	AND #%00000001                     ; only look at BIT 0
	BEQ @ReadRight
	DEC $0203
	DEC $0203
	LDA #$03                           ; sprite tile
	STA $0201
@ReadRight: 
	LDA $4016
	AND #%00000001                     ; only look at BIT 0
	BEQ @done
	INC $0203
	INC $0203
	LDA #$04                           ; sprite tile
	STA $0201
@done: 
	RTI                                ; return from interrupt

UpdateFollower: 
	JSR UpdateFollowerX
	JSR UpdateFollowerY
	RTS 

;; x

UpdateFollowerX: 
	LDA $0207                          ; follower x
	CMP $0203                          ; sprite x
	BEQ @done
	BCC @inc
	DEC $0207
	RTS 
@inc: 
	INC $0207
@done: 
	RTS 

;; y

UpdateFollowerY: 
	LDA $0204                          ; follower x
	CMP $0200                          ; sprite x
	BEQ @done
	BCC @inc
	DEC $0204
	RTS 
@inc: 
	INC $0204
@done: 
	RTS 
	.org $E000

;; tables

Palette: 
	.db $0F,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3A,$3B,$3C,$3D,$3E,$0F
	.db $0F,$3B,$15,$0F,$0F,$02,$0F,$0F,$0F,$1C,$15,$14,$31,$02,$38,$3C

;; Vectors

	.pad $FFFA
	.dw NMI
	.dw RESET
	.dw 0

;; sprite

	.incbin "sprite.chr"

D examples/animation-follow/build.sh => examples/animation-follow/build.sh +0 -17
@@ 1,17 0,0 @@
#!/bin/bash

# Remove old

rm cart.nes

# Lint source

node ../../tools/lint6502.js

# Build

../../assembler/asm6 src/cart.asm cart.nes

# Run

fceux cart.nes
\ No newline at end of file

D examples/animation-follow/src/cart.asm => examples/animation-follow/src/cart.asm +0 -255
@@ 1,255 0,0 @@

;; iNES HEADER

  .db  "NES", $1a              ; identification of the iNES header
  .db  PRG_COUNT               ; number of 16KB PRG-ROM pages
  .db  $01                     ; number of 8KB CHR-ROM pages
  .db  $70|MIRRORING           ; mapper 7
  .dsb $09, $00                ; clear the remaining bytes
  .fillvalue $FF               ; Sets all unused space in rom to value $FF

;; VARIABLES

  .enum $0000                  ; Zero Page variables
pos_x                   .dsb 1
pos_y                   .dsb 1
  .ende

;; CONSTANTS

PRG_COUNT       = 1            ; 1 = 16KB, 2 = 32KB
MIRRORING       = %0001
PPU_Control         .equ $2000
PPU_Mask            .equ $2001
PPU_Status          .equ $2002
PPU_Scroll          .equ $2005
PPU_Address         .equ $2006
PPU_Data            .equ $2007
spriteRAM           .equ $0200

;;

  .org $C000

;;  RESET

RESET:                         ; 
  SEI                          ; disable IRQs
  CLD                          ; disable decimal mode
  LDX #$40
  STX $4017                    ; disable APU frame IRQ
  LDX #$FF
  TXS                          ; Set up stack
  INX                          ; now X = 0
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2

;; palette

LoadPalettes:                  ; 
  LDA $2002                    ; read PPU status to reset the high/low latch
  LDA #$3F
  STA $2006                    ; write the high byte of $3F00 address
  LDA #$00
  STA $2006                    ; write the low byte of $3F00 address
  LDX #$00                     ; start out at 0
@loop:                         ; 
  LDA palette, x               ; load data from address (palette + the value in x)
  STA $2007                    ; write to PPU
  INX                          ; X = X + 1
  CPX #$20                     ; Compare X to hex $10, decimal 16 - copying 16 bytes = 4 sprites
  BNE @loop

;; sprite

CreateSprite:                  ; 
  LDA #$88
  STA $0200                    ; set tile.y pos
  LDA #$05
  STA $0201                    ; set tile.id
  LDA #$00
  STA $0202                    ; set tile.attribute
  LDA #$88
  STA $0203                    ; set tile.x pos
CreateFollower:                ; 
  LDA #$88
  STA $0204                    ; set tile.y pos
  LDA #$05
  STA $0205                    ; set tile.id
  LDA #$00
  STA $0206                    ; set tile.attribute
  LDA #$88
  STA $0207                    ; set tile.x pos

;; enable sprites

  LDA #%10000000               ; enable NMI, sprites from Pattern Table 1
  STA $2000
  LDA #%00010000               ; enable sprites
  STA $2001

;; jump back to Forever, infinite loop

Forever:                       ; 
  JMP Forever

;; nmi

NMI:                           ; 
  LDA #$00
  STA $2003                    ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014                    ; set the high byte (02) of the RAM address, start the transfer

;; update

  JSR updateFollower

;; controls

LatchController:               ; 
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016                    ; tell both the controllers to latch buttons
ReadA:                         ; 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ ReadADone
  LDA #$06                     ; sprite tile
  STA $0201
ReadADone:                     ; handling this button is done
ReadB:                         ; 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ ReadBDone
  LDA #$06                     ; sprite tile
  STA $0201
ReadBDone:                     ; handling this button is done
ReadSel:                       ; 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ ReadSelDone
  LDA #$06                     ; sprite tile
  STA $0201
ReadSelDone:                   ; handling this button is done
ReadStart:                     ; 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ ReadStartDone
  LDA #$06                     ; sprite tile
  STA $0201
ReadStartDone:                 ; handling this button is done
ReadUp:                        ; 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ ReadUpDone
  DEC $0200
  DEC $0200
  LDA #$01                     ; sprite tile
  STA $0201
ReadUpDone:                    ; handling this button is done
ReadDown:                      ; 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ ReadDownDone
  INC $0200
  INC $0200
  LDA #$02                     ; sprite tile
  STA $0201
ReadDownDone:                  ; handling this button is done
ReadLeft:                      ; 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ ReadLeftDone
  DEC $0203
  DEC $0203
  LDA #$03                     ; sprite tile
  STA $0201
ReadLeftDone:                  ; handling this button is done
ReadRight:                     ; 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ ReadRightDone
  INC $0203
  INC $0203
  LDA #$04                     ; sprite tile
  STA $0201
ReadRightDone:                 ; handling this button is done
  RTI                          ; return from interrupt

;;

updateFollower:                ; 
  JSR updateFollowerX
  JSR updateFollowerY
  RTS

;; x

updateFollowerX:               ; 
  LDA $0207                    ; follower x
  CMP $0203                    ; sprite x
  BEQ @done
  BCC @inc
  DEC $0207
  RTS
@inc:                          ; 
  INC $0207
@done:                         ; 
  RTS

;; y

updateFollowerY:               ; 
  LDA $0204                    ; follower x
  CMP $0200                    ; sprite x
  BEQ @done
  BCC @inc
  DEC $0204
  RTS
@inc:                          ; 
  INC $0204
@done:                         ; 
  RTS

;;

  .org $E000

;; tables

palette:                       ; 
  .db $0F,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3A,$3B,$3C,$3D,$3E,$0F
  .db $0F,$3B,$15,$0F,$0F,$02,$0F,$0F,$0F,$1C,$15,$14,$31,$02,$38,$3C

;; Vectors

  .pad $FFFA
  .dw NMI
  .dw RESET
  .dw 0

;; sprite

  .incbin "src/sprite.chr"
\ No newline at end of file

D examples/animation-follow/src/sprite.chr => examples/animation-follow/src/sprite.chr +0 -0
A examples/animation-sine.asm => examples/animation-sine.asm +141 -0
@@ 0,0 1,141 @@

;; iNES header

	.db "NES", $1a ; identification of the iNES header
	.db 1 ; number of 16KB PRG-ROM pages
	.db $01 ; number of 8KB CHR-ROM pages
	.db $00 ; NROM
	.dsb $09,$00 ; clear the remaining bytes
	.fillvalue $FF ; Sets all unused space in rom to value $FF

;; constants

	.org $C000

;; reset

RESET: 
	SEI                                ; disable IRQs
	CLD                                ; disable decimal mode
	LDX #$40
	STX $4017                          ; disable APU frame IRQ
	LDX #$FF
	TXS                                ; Set up stack
	INX                                ; now X = 0
	STX $2000                          ; disable NMI
	STX $2001                          ; disable rendering
	STX $4010                          ; disable DMC IRQs

Vblankwait1: ; First wait for vblank to make sure PPU is ready
	BIT $2002
	BPL Vblankwait1

Clrmem: 
	LDA #$00
	STA $0000, x
	STA $0100, x
	STA $0300, x
	STA $0400, x
	STA $0500, x
	STA $0600, x
	STA $0700, x
	LDA #$FE
	STA $0200, x                       ; move all sprites off screen
	INX 
	BNE Clrmem

Vblankwait2: ; Second wait for vblank, PPU is ready after this
	BIT $2002
	BPL Vblankwait2

LoadPalettes: 
	LDA $2002                          ; read PPU status to reset the high/low latch
	LDA #$3F
	STA $2006                          ; write the high byte of $3F00 address
	LDA #$00
	STA $2006                          ; write the low byte of $3F00 address
	LDX #$00
@loop: 
	LDA Palette, x                     ; load palette byte
	STA $2007                          ; write to PPU
	INX                                ; set index to next byte
	CPX #$20
	BNE @loop                          ; if x = $20, 32 bytes copied, all done

DrawSprite1: 
	LDA #$88
	STA $0200                          ; set tile.y pos
	LDA #$05
	STA $0201                          ; set tile.id
	LDA #$00
	STA $0202                          ; set tile.attribute
	LDA #$88
	STA $0203                          ; set tile.x pos

DrawSprite2: 
	LDA #$88
	STA $0204                          ; set tile.y pos
	LDA #$06
	STA $0205                          ; set tile.id
	LDA #$00
	STA $0206                          ; set tile.attribute
	LDA #$80
	STA $0207                          ; set tile.x pos
	LDA #%10000000                     ; enable NMI, sprites from Pattern Table 0
	STA $2000
	LDA #%00010000                     ; enable sprites
	STA $2001

;; jump back to Forever, infinite loop

Forever: 
	JMP Forever

NMI: 
	LDA #$00
	STA $2003                          ; set the low byte (00) of the RAM address
	LDA #$02
	STA $4014                          ; set the high byte (02) of the RAM address, start the transfer
	JSR Animate
	RTI                                ; return from interrupt

Animate: 
	LDX $00
	LDA Sin, x
	CLC 
	ADC #$30
	STA $0207
	INC $00
	LDA $00
	CMP #$40
	BNE @done
	LDA #$00
	STA $00
@done: 
	RTS 

;; TABLES

	.org $E000

Palette: 
	.db $0F,$0F,$0F,$0F,  $0F,$20,$20,$0F,  $0F,$20,$0F,$20,  $0F,$20,$20,$20 ; background palette
	.db $0F,$27,$17,$07,  $0F,$20,$10,$00,  $0F,$1C,$15,$14,  $0F,$02,$38,$3C ; sprite palette

Sin: 
	.db $40,$46,$4c,$52,$58,$5e,$63,$68,$6d,$71,$75,$78,$7b,$7d
	.db $7e,$7f,$80,$7f,$7e,$7d,$7b,$78,$75,$71,$6d,$68,$63,$5e
	.db $58,$52,$4c,$46,$47,$3a,$34,$2e,$28,$22,$1d,$18,$13,$0f
	.db $0b,$08,$05,$03,$02,$01,$00,$01,$02,$03,$05,$08,$0b,$0f
	.db $13,$18,$1d,$22,$28,$2e,$34,$3a

;; vectors

	.pad $FFFA
	.dw NMI
	.dw RESET
	.dw 0

;; sprite

	.incbin "sprite.chr"

D examples/animation-sine/build.sh => examples/animation-sine/build.sh +0 -17
@@ 1,17 0,0 @@
#!/bin/bash

# Remove old

rm cart.nes

# Lint source

node ../../tools/lint6502.js

# Build

../../assembler/asm6 src/cart.asm cart.nes

# Run

fceux cart.nes
\ No newline at end of file

D examples/animation-sine/src/cart.asm => examples/animation-sine/src/cart.asm +0 -155
@@ 1,155 0,0 @@

;; ines header

  .db  "NES", $1a              ; identification of the iNES header
  .db  PRG_COUNT               ; number of 16KB PRG-ROM pages
  .db  $01                     ; number of 8KB CHR-ROM pages
  .db  $70|MIRRORING           ; mapper 7
  .dsb $09, $00                ; clear the remaining bytes
  .fillvalue $FF               ; Sets all unused space in rom to value $FF

;; constants

PRG_COUNT       = 1            ; 1 = 16KB, 2 = 32KB
MIRRORING       = %0001
PPU_Control         .equ $2000
PPU_Mask            .equ $2001
PPU_Status          .equ $2002
PPU_Scroll          .equ $2005
PPU_Address         .equ $2006
PPU_Data            .equ $2007
spriteRAM           .equ $0200

;;

  .org $C000

;; reset

RESET:                         ; 
  SEI                          ; disable IRQs
  CLD                          ; disable decimal mode
  LDX #$40
  STX $4017                    ; disable APU frame IRQ
  LDX #$FF
  TXS                          ; Set up stack
  INX                          ; now X = 0
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2

;;

LoadPalettes:                  ; 
  LDA $2002                    ; read PPU status to reset the high/low latch
  LDA #$3F
  STA $2006                    ; write the high byte of $3F00 address
  LDA #$00
  STA $2006                    ; write the low byte of $3F00 address
  LDX #$00
@loop:                         ; 
  LDA palette, x               ; load palette byte
  STA $2007                    ; write to PPU
  INX                          ; set index to next byte
  CPX #$20
  BNE @loop                    ; if x = $20, 32 bytes copied, all done

;;

DrawSprite1:                   ; 
  LDA #$88
  STA $0200                    ; set tile.y pos
  LDA #$05
  STA $0201                    ; set tile.id
  LDA #$00
  STA $0202                    ; set tile.attribute
  LDA #$88
  STA $0203                    ; set tile.x pos
DrawSprite2:                   ; 
  LDA #$88
  STA $0204                    ; set tile.y pos
  LDA #$06
  STA $0205                    ; set tile.id
  LDA #$00
  STA $0206                    ; set tile.attribute
  LDA #$80
  STA $0207                    ; set tile.x pos
  LDA #%10000000               ; enable NMI, sprites from Pattern Table 0
  STA $2000
  LDA #%00010000               ; enable sprites
  STA $2001

;; jump back to Forever, infinite loop

Forever:                       ; 
  JMP Forever

;;

NMI:                           ; 
  LDA #$00
  STA $2003                    ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014                    ; set the high byte (02) of the RAM address, start the transfer
  JSR animate
  RTI                          ; return from interrupt
animate:                       ; 
  LDX $00
  LDA sin, x
  CLC
  ADC #$30
  STA $0207
  INC $00
  LDA $00
  CMP #$40
  BNE @done
  LDA #$00
  STA $00
@done:                         ; 
  RTS

;; TABLES

  .org $E000

;;

palette:                       ; 
  .db $0F,$0F,$0F,$0F,  $0F,$20,$20,$0F,  $0F,$20,$0F,$20,  $0F,$20,$20,$20; background palette
  .db $0F,$27,$17,$07,  $0F,$20,$10,$00,  $0F,$1C,$15,$14,  $0F,$02,$38,$3C; sprite palette
sin:                           ; 
  .db $40,$46,$4c,$52,$58,$5e,$63,$68,$6d,$71,$75,$78,$7b,$7d
  .db $7e,$7f,$80,$7f,$7e,$7d,$7b,$78,$75,$71,$6d,$68,$63,$5e
  .db $58,$52,$4c,$46,$47,$3a,$34,$2e,$28,$22,$1d,$18,$13,$0f
  .db $0b,$08,$05,$03,$02,$01,$00,$01,$02,$03,$05,$08,$0b,$0f
  .db $13,$18,$1d,$22,$28,$2e,$34,$3a

;; vectors

  .pad $FFFA
  .dw NMI
  .dw RESET
  .dw 0

;; sprite

  .incbin "src/sprite.chr"
\ No newline at end of file

D examples/animation-sine/src/sprite.chr => examples/animation-sine/src/sprite.chr +0 -0
D examples/animation-sine/tools/index.js => examples/animation-sine/tools/index.js +0 -21
@@ 1,21 0,0 @@
console.log('\n\nsin:')

const steps = 64
const amplitude = 64
const values = []

function toHex (int) {
  return '$' + int.toString(16).padStart(2, '0')
}

function format (acc, item, key) {
  return `${acc}${item}${key !== steps - 1 ? ',' : ''}${(key + 1) % 14 === 0 ? '\n  ' : ''}`
}

for (let i = 0; i < steps; i++) {
  const value = Math.sin(2 * Math.PI * i / steps) * amplitude
  const hex = toHex(parseInt(value) + amplitude)
  values.push(hex)
}

console.log('  ' + values.reduce(format, '') + '\n\n')

A examples/audio-tone.asm => examples/audio-tone.asm +134 -0
@@ 0,0 1,134 @@

;; iNES header

	.db "NES", $1a ; identification of the iNES header
	.db 1 ; number of 16KB PRG-ROM pages
	.db $01 ; number of 8KB CHR-ROM pages
	.db $00 ; NROM
	.dsb $09,$00 ; clear the remaining bytes
	.fillvalue $FF ; Sets all unused space in rom to value $FF

;; CONSTANTS

	PPUCTRL                 .equ $2000 
	PPUMASK                 .equ $2001 
	PPUSTATUS               .equ $2002 
	SPRADDR                 .equ $2003 
	PPUSCROLL               .equ $2005 
	PPUADDR                 .equ $2006 
	PPUDATA                 .equ $2007 
	SPRDMA                  .equ $4014 
	APUCH1VOL               .equ $4000 ; APU
	APUCH1SWP               .equ $4001 
	APUCH1FRQ               .equ $4002 
	APUCH1LEN               .equ $4003 
	APUCH2VOL               .equ $4004 
	APUCH2SWP               .equ $4005 
	APUCH2FRQ               .equ $4006 
	APUCH2LEN               .equ $4007 
	APUCH3CNT               .equ $4008 
	APUCH3SWP               .equ $4009 
	APUCH3FRQ               .equ $400a 
	APUCH3LEN               .equ $400b 
	APUCH4VOL               .equ $400c 
	APUCH4SWP               .equ $400d 
	APUCH4FRQ               .equ $400e 
	APUCH4LEN               .equ $400f 
	APUCTRL                 .equ $4015 

;; reset

	.org $C000

RESET: 
	SEI                                ; disable IRQs
	CLD                                ; disable decimal mode
	LDX #$40
	STX $4017                          ; disable APU frame IRQ
	LDX #$FF
	TXS                                ; Set up stack
	INX                                ; now X = 0
	STX PPUCTRL                        ; disable NMI
	STX $2001                          ; disable rendering
	STX $4010                          ; disable DMC IRQs

Vblankwait1: 
	BIT PPUSTATUS
	BPL Vblankwait1

Clrmem: 
	LDA #$00
	STA $0000, x
	STA $0100, x
	STA $0300, x
	STA $0400, x
	STA $0500, x
	STA $0600, x
	STA $0700, x
	LDA #$FE
	STA $0200, x                       ; move all sprites off screen
	INX 
	BNE Clrmem

Vblankwait2: 
	BIT PPUSTATUS
	BPL Vblankwait2

;; main

PlaySound: 
	; Enable sound channels
	LDA #%00000111
	STA APUCTRL                        ; enable Square 1, Square 2 AND Triangle
	; Square 1
	LDA #%00111000                     ; Duty 00, Length Counter Disabled, Saw Envelopes disabled, Volume 8
	STA APUCH1VOL
	LDA #$C9                           ; 0C9 is a C# in NTSC mode
	STA APUCH1FRQ                      ; low 8 bits of period
	LDA #$00
	STA APUCH1LEN                      ; high 3 bits of period
	; Square 2
	LDA #%01110110                     ; Duty 01, Volume 6
	STA APUCH2VOL
	LDA #$A9                           ; $0A9 is an E in NTSC mode
	STA APUCH2FRQ
	LDA #$00
	STA APUCH2LEN
	; Triangle
	LDA #$81                           ; disable internal counters, channel on
	STA APUCH3CNT
	LDA #$42                           ; $042 is a G# in NTSC mode
	STA APUCH3FRQ
	LDA #$00
	STA APUCH3LEN

Forever: 
	JMP Forever                        ; jump back to Forever, infinite loop

NMI: 
	LDA #$00
	STA SPRADDR                        ; set the low byte (00) of the RAM address
	LDA #$02
	STA SPRDMA                         ; set the high byte (02) of the RAM address, start the transfer

;; This is the PPU clean up section, so rendering the next frame starts properly.

	LDA #%10010000                     ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
	STA PPUCTRL
	LDA #%00011110                     ; enable sprites, enable background, no clipping on left side
	STA $2001
	LDA #$00
	STA PPUSCROLL
	STA PPUSCROLL
	RTI                                ; return from interrupt

;; vectors

	.pad $FFFA
	.dw NMI
	.dw RESET
	.dw 0

;; sprites

	.incbin "sprite.chr"

D examples/audio/notes.txt => examples/audio/notes.txt +0 -306
@@ 1,306 0,0 @@
Square 2

Last time we produced a beep on the Square 1 channel by making writes to $4000-$4003.  Now we'll learn how to do it with Square 2.  This is very easy because Square 1 and Square 2 are almost identical.  We control the Square 2 channel with ports $4004-$4007, and they more or less mirror Square 1's $4000-4003.

SQ2_ENV ($4004)

76543210
||||||||
||||++++- Volume
|||+----- Saw Envelope Disable (0: use internal counter for volume; 1: use Volume for volume)
||+------ Length Counter Disable (0: use Length Counter; 1: disable Length Counter)
++------- Duty Cycle


SQ2_SWEEP ($4005)
Skip this for now.  This port, incidentally, is where Square 2 differs from Square 1.


SQ2_LO ($4006)

76543210
||||||||
++++++++- Low 8-bits of period


SQ2_HI ($4007)

76543210
||||||||
|||||+++- High 3-bits of period
+++++---- Length Counter

To produce a sound, first we enable the channel via $4015:

    lda #%00000010 ;enable Square 2
    sta $4015
    
Then we write to the Square 2 ports:

    lda #%00111000 ;Duty Cycle 00, Volume 8 (half volume)
    sta $4004
 
    lda #$A9   ;$0A9 is an E in NTSC mode
    sta $4006
 
    lda #$00
    sta $4007
    
Except for sweeps, the Square 2 channel works just like the Square 1 channel.
    
Triangle

The Triangle channel produces triangle waveforms which have a smooth sound to them.  Think of the flute-like melody in the Dragon Warrior overland song.  That's the Triangle.

Unlike the Square channels, we have no control over the Triangle channel's volume or tone.  It makes only one type of sound and it's either on (playing) or off (silent).  We manipulate the Triangle channel via ports $4008-$400B.

TRI_CTRL ($4008)

76543210
||||||||
|+++++++- Value
+-------- Control Flag (0: use internal counters; 1: disable internal counters)

The triangle channel has two internal counters that can be used to automatically control note duration.  We are going to disable them so that we can control note length manually.  We will set the Control Flag to 1 and forget about it.

When the internal counters are disabled, Value controls whether the channel is on or off.  To silence the channel, set Value to 0.  To turn the channel on (ie, unsilence), set Value to any non-zero value.  Here are some examples:

    lda #%10000000 ;silence the Triangle channel
    sta $4008
 
    lda #%10000001 ;Triangle channel on
    sta $4008
 
    lda #%10001111 ;Triangle channel on
    sta $4008
 
    lda #%11111111 ;Triangle channel on
    sta $4008
    
Note that the last three examples are functionally the same.  Any non-zero value in Value makes the Triangle channel play.

Unused Port
$4009 is unused

Setting the Note
$400A and $400B control the period of the wave, or in other words what note you hear (A, C#, G, etc).  Like the Squares, Triangle periods are 11-bits long.  $400A holds the low 8-bits and $400B holds the high 3-bits of the period.  We'll learn more about periods next week, but for now just know that changing the values written to these ports will change the note that is played.

TRI_LO ($400A)

76543210
||||||||
++++++++- Low 8-bits of period

TRI_HI ($400B)

76543210
||||||||
|||||+++- High 3-bits of period
+++++---- Length Counter

The Length Counter, if enabled, controls how long the note is played.  We disabled it up in the $4008 section, so we can forget about it for now.

Here is some code to play an eternal beep on the Triangle channel:

    lda #%00000100 ;enable Triangle channel
    sta $4015

    lda #%10000001 ;disable counters, non-zero Value turns channel on
    sta $4008
 
    lda #$42   ;a period of $042 plays a G# in NTSC mode.
    sta $400A
 
    lda #$00
    sta $400B
    
Multiple Beeps
We now know how to use the Square 1, Square 2 and Triangle channels to make sound.  It doesn't take too much extra work to make them all play at the same time.  We just have to enable all three channels via $4015 and then write to the ports.  Here's some code that will play a C#m chord (C# E G#) using the knowledge we have gained up to now:

    lda #%00000111  ;enable Sq1, Sq2 and Tri channels
    sta $4015
 
    ;Square 1
    lda #%00111000  ;Duty 00, Volume 8 (half volume)
    sta $4000
    lda #$C9        ;$0C9 is a C# in NTSC mode
    sta $4002       ;low 8 bits of period
    lda #$00
    sta $4003       ;high 3 bits of period
 
    ;Square 2
    lda #%01110110  ;Duty 01, Volume 6
    sta $4004
    lda #$A9        ;$0A9 is an E in NTSC mode
    sta $4006
    lda #$00
    sta $4007
 
    ;Triangle
    lda #%10000001  ;Triangle channel on
    sta $4008
    lda #$42        ;$042 is a G# in NTSC mode
    sta $400A
    lda #$00
    sta $400B


Last Week: Square 2 and Triangle Basics

This week: We will learn about periods and build a period lookup table that spans 8 octaves.

Periods

In the last two lessons, I've been giving you the values to plug into the 11-bit periods for the Square and Triangle channels.  I haven't been giving you an explanation of what a period is, or where I got those numbers.  So this week we're going to learn about periods.

What is a period?
A period refers to the length of a wave, or rather the time length of the repeating part of a wave.  Take a look at this square wave (x-axis is time):

Notice how it is repeating.  It starts high and remains high for 2 time units.  Then it goes low and remains low for 2 time units.  Then it repeats.  When we say period, we are talking about the horizontal time length of this repeating wave.  In this case, the period is 4 time units.  The longer a period is, the lower the note will sound.   Conversely, the shorter a period is, the higher the note will sound.  Look at these 3 Square waves:

Period = 6 time units

Period = 4 time units

Period = 1 time unit

The top wave has the longest period (6 time units) and it will sound the lowest.  The bottom wave has a short period (1 time unit) and will sound higher than the other two.

On the NES, we write an 11-bit period to the APU ports.  The smaller the number, the shorter the period, the higher the note.  Larger numbers = longer periods = lower notes.  Look at the following code snippets that write an 11-bit period to the Square 1 ports:

    lda #$C9
    sta $4002
    lda #$05
    sta $4003 ;period $5C9: large number = long period = low note
 
    ;----
 
    lda #$09
    sta $4002
    lda #$00
    sta $4003 ;period $009: small number = short period = very high note

Periods -> Notes
So how do we know which 11-bit period values correspond to which notes?  The magic forumla is:

    P = C/(F*16) - 1
    
    P = Period
    C = CPU speed (in Hz)
    F = Frequency of the note (also in Hz).  
    
The value of C differs between NTSC and PAL machines, which is why a game made for NTSC will sound funny on a PAL NES, and vice-versa.

To find the period values for notes, we will have to look up note frequencies and plug them into the formula.  Or we can cross our fingers and hope somebody has already done the work for us and put the answers in an easy-to-read table.  Lucky for us a cool fellow named Celius has done just that, for both NTSC and PAL.  Here are the charts:

http://www.freewebs.com/the_bott/NotesTableNTSC.txt
http://www.freewebs.com/the_bott/NotesTablePAL.txt

Lookup Tables
It is fairly common practice to store period values in a lookup table.  A lookup table is a table of pre-calculated data stored in ROM.  Like an answer sheet.  Lookup tables are used to cut down on complicated, time-consuming calculations.  Let's look at a trivial example.  Let's say you want a subroutine that takes a value in A and returns 3^A.  If you took the brute-force approach, you might write something like this:

multiplier .rs 1

; takes a value (0-5) in A and returns 3^A
three_to_the_a:
    bne .not_zero
    lda #$01        ;3^0 is 1
    rts
.not_zero:
    tay
    lda #$03
.loop:    
    sta multiplier
    dey
    beq .done
    clc
    adc multiplier
    adc multiplier
    jmp .loop
.done:
    rts
    
It works, but it's not very pretty.  Here is how we would do it with a lookup table:

;lookup table with pre-calculated answers
powers_of_3:
    .byte 1, 3, 9, 27, 81, 243
 
three_to_the_a:
    tay
    lda powers_of_3, y
    rts
    
Easier to code.  Easier to read.  And it runs faster too.

NESASM3 Tip#1: Local Labels
You may have noticed in the above example that I put a period in front of some labels: .done, .loop, .not_zero.  NESASM3 treats these as local labels.  There are two types of labels: global and local.  A global label exists across the whole program and must be unique.  A local label only exists between two global labels.  This means that we can reuse the names of local labels - they only need to be unique within their scope.  Using local labels saves you the trouble of having to create unique names for common case labels (like looping).  I tend to use local labels for all labels that occur within subroutines.  To make a label local, stick a period in front of it.
    
Note Lookup Table
Let's take Celius's tables and turn them into a note lookup table. Period values are 11 bits so we will need to define our lookup table using words.  Note that .word is the same as .dw.  Here is a note_table for NTSC:

;Note: octaves in music traditionally start from C, not A.  
;      I've adjusted my octave numbers to reflect this.
note_table:
    .word                                                                $07F1, $0780, $0713 ; A1-B1 ($00-$02)
    .word $06AD, $064D, $05F3, $059D, $054D, $0500, $04B8, $0475, $0435, $03F8, $03BF, $0389 ; C2-B2 ($03-$0E)
    .word $0356, $0326, $02F9, $02CE, $02A6, $027F, $025C, $023A, $021A, $01FB, $01DF, $01C4 ; C3-B3 ($0F-$1A)
    .word $01AB, $0193, $017C, $0167, $0151, $013F, $012D, $011C, $010C, $00FD, $00EF, $00E2 ; C4-B4 ($1B-$26)
    .word $00D2, $00C9, $00BD, $00B3, $00A9, $009F, $0096, $008E, $0086, $007E, $0077, $0070 ; C5-B5 ($27-$32)
    .word $006A, $0064, $005E, $0059, $0054, $004F, $004B, $0046, $0042, $003F, $003B, $0038 ; C6-B6 ($33-$3E)
    .word $0034, $0031, $002F, $002C, $0029, $0027, $0025, $0023, $0021, $001F, $001D, $001B ; C7-B7 ($3F-$4A)
    .word $001A, $0018, $0017, $0015, $0014, $0013, $0012, $0011, $0010, $000F, $000E, $000D ; C8-B8 ($4B-$56)
    .word $000C, $000C, $000B, $000A, $000A, $0009, $0008                                    ; C9-F#9 ($57-$5D)

Once we have a note lookup table, we use the note we want as an index into the table and pull the period values from it, like this:
    
    lda #$0C            ;the 13th entry in the table (A2)
    asl a               ;multiply by 2 because we are indexing into a table of words
    tay
    lda note_table, y   ;read the low byte of the period
    sta $4002           ;write to SQ1_LO
    lda note_table+1, y ;read the high byte of the period
    sta $4003           ;write to SQ1_HI
    
To make it easier to know which index to use for each note, we can create a list of symbols:

;Note: octaves in music traditionally start at C, not A

;Octave 1
A1 = $00    ;"1" means octave 1.
As1 = $01   ;"s" means "sharp"
Bb1 = $01   ;"b" means "flat".  A# == Bb
B1 = $02

;Octave 2
C2 = $03
Cs2 = $04
Db2 = $04
D2 = $05
;...
A2 = $0C
As2 = $0D
Bb2 = $0D
B2 = $0E

;Octave 3
C3 = $0F
;... etc

Now we can use our new symbols instead of the actual index values:

    lda #A2             ;A2.  #A2 will evaluate to #$0C
    asl a               ;multiply by 2 because we are indexing into a table of words
    tay
    lda note_table, y   ;read the low byte of the period
    sta $4002           ;write to SQ1_LO
    lda note_table+1, y ;read the high byte of the period
    sta $4003           ;write to SQ1_HI
    
And if later we want to have a series of notes, symbols are much easier to read and alter:

sound_data:
    .byte C3, E3, G3, B3, C4, E4, G4, B4, C5 ; Cmaj7 (CEGB)
 
sound_data_no_symbols:
    .byte $0F, $13, $16, $1A, $1B, $1F, $22, $26, $27 ;same as above, but hard to read. Cmaj7 (CEGB)
\ No newline at end of file

D examples/audio/src/cart.asm => examples/audio/src/cart.asm +0 -134
@@ 1,134 0,0 @@

;; iNES header

	.db "NES", $1a ; identification of the iNES header
	.db 1 ; number of 16KB PRG-ROM pages
	.db $01 ; number of 8KB CHR-ROM pages
	.db $00 ; NROM
	.dsb $09,$00 ; clear the remaining bytes
	.fillvalue $FF ; Sets all unused space in rom to value $FF

;; CONSTANTS

PPUCTRL             .equ $2000 
PPUMASK             .equ $2001 
PPUSTATUS           .equ $2002 
SPRADDR             .equ $2003 
PPUSCROLL           .equ $2005 
PPUADDR             .equ $2006 
PPUDATA             .equ $2007 
SPRDMA              .equ $4014 
APUCH1VOL           .equ $4000 ; APU
APUCH1SWP           .equ $4001 
APUCH1FRQ           .equ $4002 
APUCH1LEN           .equ $4003 
APUCH2VOL           .equ $4004 
APUCH2SWP           .equ $4005 
APUCH2FRQ           .equ $4006 
APUCH2LEN           .equ $4007 
APUCH3CNT           .equ $4008 
APUCH3SWP           .equ $4009 
APUCH3FRQ           .equ $400a 
APUCH3LEN           .equ $400b 
APUCH4VOL           .equ $400c 
APUCH4SWP           .equ $400d 
APUCH4FRQ           .equ $400e 
APUCH4LEN           .equ $400f 
APUCTRL             .equ $4015 

;; reset

	.org $C000

RESET:
	SEI                          ; disable IRQs
	CLD                          ; disable decimal mode
	LDX #$40
	STX $4017                    ; disable APU frame IRQ
	LDX #$FF
	TXS                          ; Set up stack
	INX                          ; now X = 0
	STX PPUCTRL                  ; disable NMI
	STX $2001                    ; disable rendering
	STX $4010                    ; disable DMC IRQs

Vblankwait1:
	BIT PPUSTATUS
	BPL Vblankwait1

Clrmem:
	LDA #$00
	STA $0000, x
	STA $0100, x
	STA $0300, x
	STA $0400, x
	STA $0500, x
	STA $0600, x
	STA $0700, x
	LDA #$FE
	STA $0200, x                 ; move all sprites off screen
	INX 
	BNE Clrmem

Vblankwait2:
	BIT PPUSTATUS
	BPL Vblankwait2

;; main

PlaySound:
	; Enable sound channels
	LDA #%00000111
	STA APUCTRL                  ; enable Square 1, Square 2 AND Triangle
	; Square 1
	LDA #%00111000               ; Duty 00, Length Counter Disabled, Saw Envelopes disabled, Volume 8
	STA APUCH1VOL
	LDA #$C9                     ; 0C9 is a C# in NTSC mode
	STA APUCH1FRQ                ; low 8 bits of period
	LDA #$00
	STA APUCH1LEN                ; high 3 bits of period
	; Square 2
	LDA #%01110110               ; Duty 01, Volume 6
	STA APUCH2VOL
	LDA #$A9                     ; $0A9 is an E in NTSC mode
	STA APUCH2FRQ
	LDA #$00
	STA APUCH2LEN
	; Triangle
	LDA #$81                     ; disable internal counters, channel on
	STA APUCH3CNT
	LDA #$42                     ; $042 is a G# in NTSC mode
	STA APUCH3FRQ
	LDA #$00
	STA APUCH3LEN

Forever:
	JMP Forever                  ; jump back to Forever, infinite loop

NMI:
	LDA #$00
	STA SPRADDR                  ; set the low byte (00) of the RAM address
	LDA #$02
	STA SPRDMA                   ; set the high byte (02) of the RAM address, start the transfer

;; This is the PPU clean up section, so rendering the next frame starts properly.

	LDA #%10010000               ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
	STA PPUCTRL
	LDA #%00011110               ; enable sprites, enable background, no clipping on left side
	STA $2001
	LDA #$00                     ;
	STA PPUSCROLL
	STA PPUSCROLL
	RTI                          ; return from interrupt

;; vectors

	.pad $FFFA
	.dw NMI
	.dw RESET
	.dw 0

;; sprites

	.incbin "src/sprite.chr"

D examples/audio/src/sprite.chr => examples/audio/src/sprite.chr +0 -0
A examples/background-attributes.asm => examples/background-attributes.asm +199 -0
@@ 0,0 1,199 @@

;; iNES header

	.db "NES", $1a ; identification of the iNES header
	.db 1 ; number of 16KB PRG-ROM pages
	.db $01 ; number of 8KB CHR-ROM pages
	.db $00 ; NROM
	.dsb $09,$00 ; clear the remaining bytes
	.fillvalue $FF ; Sets all unused space in rom to value $FF

;; variables

	.enum $0000 ; Zero Page variables
	bglb                    .dsb 1
	bghb                    .dsb 1
	.ende 
	.org $C000

;; Reset

RESET: 
	SEI                                ; disable IRQs
	CLD                                ; disable decimal mode
	LDX #$40
	STX $4017                          ; disable APU frame IRQ
	LDX #$FF
	TXS                                ; Set up stack
	INX                                ; now X = 0
	STX $2000                          ; disable NMI
	STX $2001                          ; disable rendering
	STX $4010                          ; disable DMC IRQs

Vblankwait1: ; First wait for vblank to make sure PPU is ready
	BIT $2002
	BPL Vblankwait1

Clrmem: 
	LDA #$00
	STA $0000, x
	STA $0100, x
	STA $0300, x
	STA $0400, x
	STA $0500, x
	STA $0600, x
	STA $0700, x
	LDA #$FE
	STA $0200, x                       ; move all sprites off screen
	INX 
	BNE Clrmem

Vblankwait2: ; Second wait for vblank, PPU is ready after this
	BIT $2002
	BPL Vblankwait2
	JSR LoadBackground
	JSR LoadPalettes
	JSR LoadAttributes

EnableSprites: 
	LDA #%10010000                     ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
	STA $2000
	LDA #%00011110                     ; enable sprites, enable background, no clipping on left side
	STA $2001
	LDA #$00                           ; No background scrolling
	STA $2006
	STA $2006
	STA $2005
	STA $2005

Forever: 
	JMP Forever                        ; jump back to Forever, infinite loop

LoadBackground: 
	LDA $2002
	LDA #$20
	STA $2006
	LDA #$00
	STA $2006
	LDA #<Background                   ; Loading the #LOW(var) byte in asm6
	STA bglb
	LDA #>Background                   ; Loading the #HIGH(var) byte in asm6
	STA bghb
	LDX #$00
	LDY #$00

LoadBackgroundLoop: 
	LDA (bglb), y
	STA $2007
	INY 
	CPY #$00
	BNE LoadBackgroundLoop
	INC bghb
	INX 
	CPX #$04
	BNE LoadBackgroundLoop
	RTS 

LoadPalettes: 
	LDA $2002
	LDA #$3F
	STA $2006
	LDA #$00
	STA $2006
	LDX #$00

LoadPalettesLoop: 
	LDA Palettes, x
	STA $2007
	INX 
	CPX #$20
	BNE LoadPalettesLoop
	RTS 

LoadAttributes: 
	LDA $2002
	LDA #$23
	STA $2006
	LDA #$C0
	STA $2006
	LDX #$00

LoadAttributesLoop: 
	LDA Attributes, x
	STA $2007
	INX 
	CPX #$40
	BNE LoadAttributesLoop
	RTS 

NMI: 
	LDA #$00
	STA $2003                          ; set the low byte (00) of the RAM address
	LDA #$02
	STA $4014                          ; set the high byte (02) of the RAM address, start the transfer
	RTI                                ; return from interrupt

;; tables

	.org $E000

Attributes: 
	.db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000
	.db %11111111, %10101010, %01010101, %00000000, %00000000, %00000000, %00000000, %00000000
	.db %11001100, %10001000, %01000100, %00000000, %00000000, %00000000, %00000000, %00000000
	.db %11000000, %10000000, %01000000, %00000000, %00000000, %00000000, %00000000, %00000000
	.db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000
	.db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000
	.db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000
	.db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000

Palettes: 
	.db $0f,$0f,$3B,$30,  $0f,$16,$3B,$30,  $0f,$19,$3B,$30,  $0f,$1C,$3B,$30 ; background palette
	.db $0f,$23,$3B,$30,  $22,$3B,$17,$0F,  $3B,$3B,$3B,$0F,  $22,$27,$34,$0F ; sprite palette

Sprites: 
	;   vert tile attr horiz
	.db $80, $32, $00, $80 ;sprite 0
	.db $80, $33, $00, $88 ;sprite 1
	.db $88, $34, $00, $80 ;sprite 2
	.db $88, $35, $00, $88 ;sprite 3

Background: 
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
	.db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
	.db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07

;; vectors

	.pad $FFFA
	.dw NMI
	.dw RESET
	.dw 0
	.incbin "sprite.chr"

D examples/background-attributes/build.sh => examples/background-attributes/build.sh +0 -12
@@ 1,12 0,0 @@
#!/bin/bash

# Remove old
rm cart.nes

# Build

../../assembler/asm6 src/cart.asm cart.nes

# Run

fceux cart.nes
\ No newline at end of file

D examples/background-attributes/src/cart.asm => examples/background-attributes/src/cart.asm +0 -9
@@ 1,9 0,0 @@
include "src/head.asm"
    
include "src/main.asm"

include "src/tables.asm"

include "src/vectors.asm"

    .incbin "src/sprite.chr"

D examples/background-attributes/src/head.asm => examples/background-attributes/src/head.asm +0 -83
@@ 1,83 0,0 @@
;;;;;;;;;;;;;;;;;;;;;;;
;;;   iNES HEADER   ;;;
;;;;;;;;;;;;;;;;;;;;;;;

    .db  "NES", $1a     ;identification of the iNES header
    .db  PRG_COUNT      ;number of 16KB PRG-ROM pages
    .db  $01            ;number of 8KB CHR-ROM pages
    .db  $70|MIRRORING  ;mapper 7
    .dsb $09, $00       ;clear the remaining bytes

    .fillvalue $FF      ; Sets all unused space in rom to value $FF

;;;;;;;;;;;;;;;;;;;;;
;;;   VARIABLES   ;;;
;;;;;;;;;;;;;;;;;;;;;

    .enum $0000 ; Zero Page variables

pointerBackgroundLowByte  .dsb 1
pointerBackgroundHighByte .dsb 1

    .ende

    .enum $0400 ; Variables at $0400. Can start on any RAM page

sleeping        .dsb 1

    .ende

;;;;;;;;;;;;;;;;;;;;;
;;;   CONSTANTS   ;;;
;;;;;;;;;;;;;;;;;;;;;

PRG_COUNT       = 1       ;1 = 16KB, 2 = 32KB
MIRRORING       = %0001

PPU_Control     .equ $2000
PPU_Mask        .equ $2001
PPU_Status      .equ $2002
PPU_Scroll      .equ $2005
PPU_Address     .equ $2006
PPU_Data        .equ $2007

spriteRAM       .equ $0200
    .org $C000
    
;;;;;;;;;;;;;;;;;
;;;   RESET   ;;;
;;;;;;;;;;;;;;;;;

RESET:
  SEI          ; disable IRQs
  CLD          ; disable decimal mode
  LDX #$40
  STX $4017    ; disable APU frame IRQ
  LDX #$FF
  TXS          ; Set up stack
  INX          ; now X = 0
  STX $2000    ; disable NMI
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1

clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file

D examples/background-attributes/src/main.asm => examples/background-attributes/src/main.asm +0 -83
@@ 1,83 0,0 @@
  JSR LoadBackground
  JSR LoadPalettes
  JSR LoadAttributes

EnableSprites:
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  STA $2000
  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001
  
  LDA #$00         ; No background scrolling
  STA $2006
  STA $2006
  STA $2005
  STA $2005

Forever:
  JMP Forever     ;jump back to Forever, infinite loop

LoadBackground:
  LDA $2002
  LDA #$20
  STA $2006
  LDA #$00
  STA $2006

  LDA #<background ; Loading the #LOW(var) byte in asm6
  STA pointerBackgroundLowByte
  LDA #>background ; Loading the #HIGH(var) byte in asm6
  STA pointerBackgroundHighByte

  LDX #$00
  LDY #$00
LoadBackgroundLoop:
  LDA (pointerBackgroundLowByte), y
  STA $2007
  INY
  CPY #$00
  BNE LoadBackgroundLoop
  INC pointerBackgroundHighByte
  INX
  CPX #$04
  BNE LoadBackgroundLoop
  RTS

LoadPalettes:
  LDA $2002
  LDA #$3F
  STA $2006
  LDA #$00
  STA $2006

  LDX #$00
LoadPalettesLoop:
  LDA palettes, x
  STA $2007
  INX
  CPX #$20
  BNE LoadPalettesLoop
  RTS

LoadAttributes:
  LDA $2002
  LDA #$23
  STA $2006
  LDA #$C0
  STA $2006
  LDX #$00
LoadAttributesLoop:
  LDA attributes, x
  STA $2007
  INX
  CPX #$40
  BNE LoadAttributesLoop
  RTS

NMI:
  LDA #$00
  STA $2003       ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014       ; set the high byte (02) of the RAM address, start the transfer
  
  RTI             ; return from interrupt
\ No newline at end of file

D examples/background-attributes/src/sprite.chr => examples/background-attributes/src/sprite.chr +0 -0
D examples/background-attributes/src/tables.asm => examples/background-attributes/src/tables.asm +0 -60
@@ 1,60 0,0 @@
;;;;;;;;;;;;;;;;;;
;;;   TABLES   ;;;
;;;;;;;;;;;;;;;;;;

; Two bits of the attribute table byte are assigned to each 2x2 area.

  .org $E000

attributes:
  .db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000
  .db %11111111, %10101010, %01010101, %00000000, %00000000, %00000000, %00000000, %00000000
  .db %11001100, %10001000, %01000100, %00000000, %00000000, %00000000, %00000000, %00000000
  .db %11000000, %10000000, %01000000, %00000000, %00000000, %00000000, %00000000, %00000000
  .db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000
  .db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000
  .db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000
  .db %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000

palettes:
  .db $0f,$0f,$3B,$30,  $0f,$16,$3B,$30,  $0f,$19,$3B,$30,  $0f,$1C,$3B,$30   ;;background palette
  .db $0f,$23,$3B,$30,  $22,$3B,$17,$0F,  $3B,$3B,$3B,$0F,  $22,$27,$34,$0F   ;;sprite palette

sprites:
  ;   vert tile attr horiz
  .db $80, $32, $00, $80   ;sprite 0
  .db $80, $33, $00, $88   ;sprite 1
  .db $88, $34, $00, $80   ;sprite 2
  .db $88, $35, $00, $88   ;sprite 3

background:
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07
  .db $02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03,$02,$03
  .db $06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07,$06,$07

D examples/background-attributes/src/vectors.asm => examples/background-attributes/src/vectors.asm +0 -9
@@ 1,9 0,0 @@
;;;;;;;;;;;;;;;;;;;
;;;   VECTORS   ;;;
;;;;;;;;;;;;;;;;;;;

    .pad $FFFA

    .dw NMI
    .dw RESET
    .dw 0
\ No newline at end of file

M examples/background-change/src/head.asm => examples/background-change/src/head.asm +6 -6
@@ 87,11 87,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 103,9 103,9 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
  BPL Vblankwait2


M examples/background-edit/src/head.asm => examples/background-edit/src/head.asm +6 -6
@@ 62,11 62,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 78,8 78,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/background-full/src/head.asm => examples/background-full/src/head.asm +6 -6
@@ 32,10 32,10 @@ RESET:                         ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 47,7 47,7 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/background-multiple/src/head.asm => examples/background-multiple/src/head.asm +6 -6
@@ 60,11 60,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 76,8 76,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/background-partial/src/head.asm => examples/background-partial/src/head.asm +6 -6
@@ 54,11 54,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 70,8 70,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/background-single/src/cart.asm => examples/background-single/src/cart.asm +6 -6
@@ 22,10 22,10 @@ RESET:                         ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 37,10 37,10 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
  BPL Vblankwait2

;; Main


M examples/bit-display/src/head.asm => examples/bit-display/src/head.asm +6 -6
@@ 57,10 57,10 @@ RESET:                         ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 72,7 72,7 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/bit-masks/src/cart.asm => examples/bit-masks/src/cart.asm +6 -6
@@ 27,10 27,10 @@ __RESET:                       ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 42,10 42,10 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
  BPL Vblankwait2

;;


M examples/bit-rotate/src/cart.asm => examples/bit-rotate/src/cart.asm +6 -6
@@ 27,10 27,10 @@ __RESET:                       ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 42,10 42,10 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
  BPL Vblankwait2

;;


R examples/audio/build.sh => examples/build.sh +12 -6
@@ 1,5 1,7 @@
#!/bin/bash

target=audio-tone.asm

# Remove old cart
if test -f "cart.nes"; then
  echo "Removing old cart."


@@ 7,27 9,31 @@ if test -f "cart.nes"; then
fi

# If asm6 is not built, build it.
if test -f "../../tools/asm6"; then
if test -f "../tools/asm6"; then
  echo "Assembler is ready."
else 
  echo "Compiling assembler."
  tcc ../../tools/asm6.c -o ../../tools/asm6
  cc -std=c99 -Wall ../tools/asm6.c -o ../tools/asm6
fi

# If lin6 is not built, build it.
if test -f "../../tools/lin6"; then
if test -f "../tools/lin6"; then
  echo "Linter is ready."
else 
  echo "Compiling linter."
  tcc ../../tools/lin6.c -o ../../tools/lin6
  cc -std=c89 -Wall ../tools/lin6.c -o ../tools/lin6
fi

# Lint project
../../tools/lin6 -i "src/cart.asm"
echo "Linting source."
../tools/lin6 $target > "linted-$target"

rm $target
mv "linted-$target" $target

# Build new cart
echo "Assembling new cart."
../../tools/asm6 src/cart.asm cart.nes
../tools/asm6 $target cart.nes

# Run
fceux cart.nes

R examples/audio/cart.nes => examples/cart.nes +0 -0
M examples/collision-push/src/head.asm => examples/collision-push/src/head.asm +6 -6
@@ 65,11 65,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 81,8 81,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/collision/src/cart.asm => examples/collision/src/cart.asm +6 -6
@@ 58,10 58,10 @@ RESET:                         ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 73,10 73,10 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
  BPL Vblankwait2

;;


M examples/controls-debugger/src/head.asm => examples/controls-debugger/src/head.asm +6 -6
@@ 62,11 62,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 78,8 78,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/controls-display/src/head.asm => examples/controls-display/src/head.asm +6 -6
@@ 64,11 64,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 80,8 80,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/controls-dragdrop/src/head.asm => examples/controls-dragdrop/src/head.asm +6 -6
@@ 72,11 72,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 88,8 88,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/controls-reset/src/head.asm => examples/controls-reset/src/head.asm +6 -6
@@ 60,10 60,10 @@ RESET:                         ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 75,7 75,7 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/controls-timer/src/cart.asm => examples/controls-timer/src/cart.asm +6 -6
@@ 41,10 41,10 @@ RESET:                         ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 56,10 56,10 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
  BPL Vblankwait2

;;


M examples/controls/src/head.asm => examples/controls/src/head.asm +6 -6
@@ 64,11 64,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 80,8 80,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/fceux-debugger/src/head.asm => examples/fceux-debugger/src/head.asm +6 -6
@@ 57,11 57,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 73,8 73,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/fceux-udp/src/head.asm => examples/fceux-udp/src/head.asm +6 -6
@@ 64,11 64,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 80,8 80,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/gui/src/head.asm => examples/gui/src/head.asm +6 -6
@@ 59,11 59,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 75,8 75,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/math/src/head.asm => examples/math/src/head.asm +6 -6
@@ 51,11 51,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 67,8 67,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/renderer/src/head.asm => examples/renderer/src/head.asm +6 -6
@@ 61,11 61,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 77,8 77,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/shuffle-draw/src/cart.asm => examples/shuffle-draw/src/cart.asm +6 -6
@@ 45,10 45,10 @@ RESET:                         ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 60,10 60,10 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
  BPL Vblankwait2

;;


M examples/shuffle/src/cart.asm => examples/shuffle/src/cart.asm +6 -6
@@ 45,10 45,10 @@ RESET:                         ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 60,10 60,10 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
  BPL Vblankwait2

;;


M examples/sprite-loop/src/head.asm => examples/sprite-loop/src/head.asm +6 -6
@@ 58,11 58,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 74,8 74,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/sprite-mirror/src/head.asm => examples/sprite-mirror/src/head.asm +6 -6
@@ 64,11 64,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 80,8 80,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/sprite-spritesheet/src/head.asm => examples/sprite-spritesheet/src/head.asm +6 -6
@@ 64,11 64,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 80,8 80,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

R examples/animation-bounce/src/sprite.chr => examples/sprite.chr +0 -0
M examples/sprite/src/head.asm => examples/sprite/src/head.asm +6 -6
@@ 64,11 64,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 80,8 80,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/text/src/head.asm => examples/text/src/head.asm +6 -6
@@ 64,11 64,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 80,8 80,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M examples/timers/src/head.asm => examples/timers/src/head.asm +6 -6
@@ 54,11 54,11 @@ RESET:
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
Vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
  BPL Vblankwait1

clrmem:
Clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 70,8 70,8 @@ clrmem:
  LDA #$FE
  STA $0200, x    ;move all sprites off screen
  INX
  BNE clrmem
  BNE Clrmem
   
vblankwait2:      ; Second wait for vblank, PPU is ready after this
Vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M projects/opn2/src/head.asm => projects/opn2/src/head.asm +6 -6
@@ 32,10 32,10 @@ RESET:                         ;
  STX $2000                    ; disable NMI
  STX $2001                    ; disable rendering
  STX $4010                    ; disable DMC IRQs
vblankwait1:                   ; First wait for vblank to make sure PPU is ready
Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1
clrmem:                        ; 
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x


@@ 47,7 47,7 @@ clrmem:                        ;
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE clrmem
vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2
\ No newline at end of file
  BPL Vblankwait2
\ No newline at end of file

M tables/notes.asm => tables/notes.asm +11 -14
@@ 1,16 1,15 @@
;; NTSC Period Lookup Table.
;; http://www.freewebs.com/the_bott/NotesTableNTSC.txt

;; NTSC Period Lookup Table.  Thanks Celius!

note_table:                    ; 
    .word                                                                $07F1, $0780, $0713; A1-B1 ($00-$02)
    .word $06AD, $064D, $05F3, $059D, $054D, $0500, $04B8, $0475, $0435, $03F8, $03BF, $0389; C2-B2 ($03-$0E)
    .word $0356, $0326, $02F9, $02CE, $02A6, $027F, $025C, $023A, $021A, $01FB, $01DF, $01C4; C3-B3 ($0F-$1A)
    .word $01AB, $0193, $017C, $0167, $0151, $013F, $012D, $011C, $010C, $00FD, $00EF, $00E2; C4-B4 ($1B-$26)
    .word $00D2, $00C9, $00BD, $00B3, $00A9, $009F, $0096, $008E, $0086, $007E, $0077, $0070; C5-B5 ($27-$32)
    .word $006A, $0064, $005E, $0059, $0054, $004F, $004B, $0046, $0042, $003F, $003B, $0038; C6-B6 ($33-$3E)
    .word $0034, $0031, $002F, $002C, $0029, $0027, $0025, $0023, $0021, $001F, $001D, $001B; C7-B7 ($3F-$4A)
    .word $001A, $0018, $0017, $0015, $0014, $0013, $0012, $0011, $0010, $000F, $000E, $000D; C8-B8 ($4B-$56)
    .word $000C, $000C, $000B, $000A, $000A, $0009, $0008; C9-F#9 ($57-$5D)
.word                                                                $07F1, $0780, $0713; A1-B1 ($00-$02)
.word $06AD, $064D, $05F3, $059D, $054D, $0500, $04B8, $0475, $0435, $03F8, $03BF, $0389; C2-B2 ($03-$0E)
.word $0356, $0326, $02F9, $02CE, $02A6, $027F, $025C, $023A, $021A, $01FB, $01DF, $01C4; C3-B3 ($0F-$1A)
.word $01AB, $0193, $017C, $0167, $0151, $013F, $012D, $011C, $010C, $00FD, $00EF, $00E2; C4-B4 ($1B-$26)
.word $00D2, $00C9, $00BD, $00B3, $00A9, $009F, $0096, $008E, $0086, $007E, $0077, $0070; C5-B5 ($27-$32)
.word $006A, $0064, $005E, $0059, $0054, $004F, $004B, $0046, $0042, $003F, $003B, $0038; C6-B6 ($33-$3E)
.word $0034, $0031, $002F, $002C, $0029, $0027, $0025, $0023, $0021, $001F, $001D, $001B; C7-B7 ($3F-$4A)
.word $001A, $0018, $0017, $0015, $0014, $0013, $0012, $0011, $0010, $000F, $000E, $000D; C8-B8 ($4B-$56)
.word $000C, $000C, $000B, $000A, $000A, $0009, $0008; C9-F#9 ($57-$5D)

;; Note: octaves in music traditionally start at C, not A



@@ 147,5 146,3 @@ E9 = $5b
F9 = $5c
Fs9 = $5d
Gb9 = $5d

;; http://www.freewebs.com/the_bott/NotesTableNTSC.txt

A tables/notes2.asm => tables/notes2.asm +29 -0
@@ 0,0 1,29 @@
; NTSC period table generated by mktables.py

periodTableLo:
  .byte $f1,$7f,$13,$ad,$4d,$f3,$9d,$4c,$00,$b8,$74,$34
  .byte $f8,$bf,$89,$56,$26,$f9,$ce,$a6,$80,$5c,$3a,$1a
  .byte $fb,$df,$c4,$ab,$93,$7c,$67,$52,$3f,$2d,$1c,$0c
  .byte $fd,$ef,$e1,$d5,$c9,$bd,$b3,$a9,$9f,$96,$8e,$86
  .byte $7e,$77,$70,$6a,$64,$5e,$59,$54,$4f,$4b,$46,$42
  .byte $3f,$3b,$38,$34,$31,$2f,$2c,$29,$27,$25,$23,$21
  .byte $1f,$1d,$1b,$1a,$18,$17,$15,$14

periodTableHi:
  .byte $07,$07,$07,$06,$06,$05,$05,$05,$05,$04,$04,$04
  .byte $03,$03,$03,$03,$03,$02,$02,$02,$02,$02,$02,$02
  .byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .byte $00,$00,$00,$00,$00,$00,$00,$00
  
; Set triangle frequency to note code in X register

lda periodTableHi,x
lsr a
sta $400B

lda periodTableLo,x
ror a
sta $400A

M tools/lin6.c => tools/lin6.c +414 -431
@@ 1,450 1,433 @@
/* Lin6
  Version 1.1
/* 
  Lin6
  Version 1.3
  https://wiki.xxiivv.com/lin6
*/

/* Rules:
  Variable names must always be lowercase.
  Variable names are always padded to tab1.
  Variable length are always padded to col25.
  Variable comments are always padded to col32.
  Constant names must always be uppercase.
  Constant names are always padded to tab1.
  Constant length are always padded to col21.
  Constant comments are always padded to col32.
  Label names must always be capitalized.
  Label names aalways end with :.
  Label names always end with :.
  Label names are always preceeded with a linebreak.
  Label comments are always padded to col32.
  Directive names are always padded to col2.
  Directive comments are always padded to col32.
  Label comments are never padded.
  Directive names are always padded to tab1.
  Directive names are always lowercase.
  Directive comments are never padded.
  Opcode names are always uppercase.
  Opcode names are always padded to col2.
  Opcode names are always padded to tab1.
  Opcode comments are always padded to col32.
  Inline comments are always padded to col2.
  Spacing comments are always preceeded and followed by with a linebreak.
  Inline comments are always padded to tab1.
  Spacing comments are always preceeded and followed with a linebreak.
*/

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

#define VERSION "1.1"
#define VERSION "1.3"
#define BUFLEN 512

char *OPCODES[] = {"ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI",
                   "BNE", "BPL", "BRK", "BVC", "BVS", "CLC", "CLD", "CLI",
                   "CLV", "CMP", "CPX", "CPY", "DEC", "DEX", "DEY", "EOR",
                   "INC", "INX", "INY", "JMP", "JSR", "LDA", "LDX", "LDY",
                   "LSR", "NOP", "ORA", "PHA", "PHP", "PLA", "PLP", "ROL",
                   "ROR", "RTI", "RTS", "SBC", "SEC", "SED", "SEI", "STA",
                   "STX", "STY", "TAX", "TAY", "TSX", "TXA", "TXS", "TYA"};

void substr(char *src, char *dest, int from, int to) {
  memcpy(dest, src + from, to);
  dest[to] = '\0';
}

void trimstr(char *src, char *dest) {
  /* leading whitespace */
  int a, b, len = strlen(src) + 1;
  for (a = 0; a < len; a++) {
    if (src[a] != ' ' && src[a] != '\t') {
      break;
    }
  }
  /* traling whitespace */
  for (b = 2; b < len; b++) {
    if (src[len - b] != ' ' && src[len - b] != '\n') {
      break;
    }
  }
  substr(src, dest, a, len - b - a + 1);
}

void fillstr(char *src, int limit, char c) {
  int i, len = strlen(src);
  for (i = len; i < limit; i++) {
    src[i] = c;
  }
  src[limit] = '\0';
}

void swapcstr(char *src, char a, char b) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    src[i] = src[i] == a ? b : src[i];
  }
}

void cpystr(char *src, char *dest) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    dest[i] = src[i];
  }
  dest[len] = '\0';
}

void ucstr(char *src) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    src[i] = toupper(src[i]);
  }
}

void lcstr(char *src) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    src[i] = tolower(src[i]);
  }
}

int index_of_comment(char *src) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    if (src[i] == ';') {
      return i;
    }
  }
  return -1;
}

bool opcode_exists(char *opcode) {
  int i;
  for (i = 0; i < 56; i++) {
    if (strcmp(OPCODES[i], opcode) == 0) {
      return true;
    }
  }
  return false;
}

bool file_exists(char *filename) {
  FILE *file = fopen(filename, "r");
  if (file != NULL) {
    fclose(file);
    return true;
  }
  return false;
}

void set_line_key(char *src, char *dest) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    if (src[i] == ' ') {
      break;
    }
  }
  substr(src, dest, 0, i);
  trimstr(dest, dest);
}

void set_line_value(char *src, char *dest) {
  bool step = false;
  int i, comment = index_of_comment(src), len = strlen(src);
  if (comment > -1) {
    len = comment;
  }
  for (i = 0; i < len; i++) {
    if (src[i] != ' ' && step == true) {
      break;
    } else if (src[i] == ' ') {
      step = true;
    }
  }
  substr(src, dest, i, len - i);
  trimstr(dest, dest);
}

void set_line_comment(char *src, char *dest) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    if (src[i] == ';') {
      break;
    }
  }
  substr(src, dest, i, len - i);
  trimstr(dest, dest);
}

bool has_comment(char *line) {
  int i, len = strlen(line);
  for (i = 0; i < len; i++) {
    if (line[i] == ';') {
      return true;
    }
  }
  return false;
}

bool is_capital(char *str) { return str[0] == toupper(str[0]); }

bool is_lower(char *str) {
  int i, len = strlen(str);
  for (i = 0; i < len; i++) {
    if (str[i] != tolower(str[i])) {
      return false;
    }
  }
  return true;
}

bool is_upper(char *str) {
  int i, len = strlen(str);
  for (i = 0; i < len; i++) {
    if (str[i] != toupper(str[i])) {
      return false;
    }
  }
  return true;
}

bool is_label(char *line) {
  int i, len = strlen(line);
  for (i = 0; i < len; i++) {
    if (line[i] == ' ' || line[i] == ';') {
      return false;
    }
    if (line[i] == ':') {
      return true;
    }
  }
  return false;
}

bool is_opcode(char *line) {
  char opcode[4];
  substr(line, opcode, 0, 3);
  ucstr(opcode);
  return opcode_exists(opcode);
}

bool is_directive(char *line) { return line[0] == '.'; }

bool is_spacer_comment(char *line) { return line[0] == ';' && line[1] == ';'; }

bool is_inline_comment(char *line) { return line[0] == ';' && line[1] == ' '; }

bool is_variable(char *line) {
  int i, len = strlen(line);
  for (i = 0; i < len; i++) {
    if (line[i] == '.' && line[i + 1] == 'd' && line[i + 2] == 's' &&
        line[i + 3] == 'b') {
      return true;
    }
  }
  return false;
}

bool is_constant(char *line) {
  int i, len = strlen(line);
  for (i = 0; i < len; i++) {
    if (line[i] == '.' && line[i + 1] == 'e' && line[i + 2] == 'q' &&
        line[i + 3] == 'u') {
      return true;
    }
  }
  return false;
}

void save(char *filepath, char *output, bool do_inplace) {
  if (do_inplace) {
    FILE *fw = fopen(filepath, "w");
    fprintf(fw, "%s", output);
    fclose(fw);
  } else {
    printf("%s\n", output);
  }
}

void lint(char *filepath, char *output, bool do_debug) {
  FILE *fr = fopen(filepath, "r");
  int id = 0, len, count = 0, opcodes = 0, directives = 0, spacers = 0,
      labels = 0, variables = 0, constants = 0, comments = 0;
  bool skipped_last = false;
  char line[BUFLEN];
  char trimed[BUFLEN];
  while (fgets(line, BUFLEN, fr)) {
    id++;
    trimstr(line, trimed);
    swapcstr(trimed, '\t', ' ');
    len = strlen(trimed);
    if (len < 3) {
      continue;
    }
    if (is_label(trimed)) {
      char key[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_comment(trimed, comment);
      if (!is_capital(key)) {
        printf("Lin6: Label %s not capitalized, at %s:%d.\n", key, filepath,
               id);
      }
      if (key[0] != '@' && !skipped_last) {
        strcat(output, "\n");
      }
      if (has_comment(line)) {
        fillstr(key, 30, ' ');
        strcat(output, key);
        strcat(output, " ");
        strcat(output, comment);
      } else {
        strcat(output, key);
      }
      skipped_last = false;
      labels++;
    } else if (is_opcode(trimed)) {
      char key[BUFLEN];
      char value[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_value(trimed, value);
      set_line_comment(trimed, comment);
      ucstr(key);
      strcat(output, "\t");
      strcat(output, key);
      strcat(output, " ");
      if (has_comment(line)) {
        fillstr(value, 24, ' ');
        strcat(output, value);
        strcat(output, " ");
        strcat(output, comment);
      } else {
        strcat(output, value);
      }
      skipped_last = false;
      opcodes++;
    } else if (is_directive(trimed)) {
      char key[BUFLEN];
      char value[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_value(trimed, value);
      set_line_comment(trimed, comment);
      strcat(output, "\t");
      strcat(output, key);
      strcat(output, " ");
      if (has_comment(line)) {
        strcat(output, value);
        strcat(output, " ");
        strcat(output, comment);
      } else {
        strcat(output, value);
      }
      skipped_last = false;
      directives++;
    } else if (is_inline_comment(trimed)) {
      strcat(output, "\t");
      strcat(output, trimed);
      skipped_last = false;
      comments++;
    } else if (is_spacer_comment(trimed)) {
      strcat(output, "\n");
      strcat(output, trimed);
      strcat(output, "\n");
      skipped_last = true;
      spacers++;
    } else if (is_variable(trimed)) {
      char key[BUFLEN];
      char value[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_value(trimed, value);
      set_line_comment(trimed, comment);
      fillstr(key, 24, ' ');
      if (!is_lower(key)) {
        printf("Lin6: Variable %s not lowercase, at %s:%d.\n", key, filepath,
               id);
      }
      strcat(output, key);
      strcat(output, value);
      strcat(output, " ");
      strcat(output, comment);
      skipped_last = false;
      variables++;
    } else if (is_constant(trimed)) {
      char key[BUFLEN];
      char value[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_value(trimed, value);
      set_line_comment(trimed, comment);
      fillstr(key, 20, ' ');
      if (!is_upper(key)) {
        printf("Lin6: Constant %s not uppercase, at %s:%d.\n", key, filepath,
               id);
      }
      strcat(output, key);
      strcat(output, value);
      strcat(output, " ");
      strcat(output, comment);
      skipped_last = false;
      constants++;
    } else {
      printf("Lin6: Unknown line-type, at %s:%d.\n", filepath, id);
      strcat(output, line);
    }
    strcat(output, "\n");
    count++;