A Soft Baa
Paula speaks: one looping square wave, and three farmyard sounds made of nothing but pitch, volume and time — a baa on the hop, a squelch for a loss, a rising bleat at the pen.
The farm has a voice now.
Hold the stick and she trots upfield to a staccato baa-baa-baa. Lose her and the lane answers with a low squelch. Get her home and the pen greets her with a rising bleat. Three sounds, and they’re all the same eight bytes — what differs is pitch, volume and time, which is most of what sound design is.
Listen to a real run — the trot, and (this being an honest farmyard) one squelch along the way:
The Display

One home, one lost, the third sheep up — the run you just heard.
Paula Plays Loops
Paula’s model is unlike the beepers and tone chips of the 8-bit world: each of her four channels loops a sample from Chip RAM on its own DMA. Point the channel at some bytes, tell her how many, how fast (the period — bigger is lower), and how loud; switch the channel’s DMA on; she plays it round and round until you stop her, costing the CPU nothing.
Our “sample” is the humblest one possible — one cycle of a square wave, eight signed bytes:
squarewave: dc.b 64,64,64,64,-64,-64,-64,-64
Loop that fast enough and it’s a tone. Every pitch in the game is this same wave at a different period. (Real sampled sound — a recorded baa — is the same machinery with more bytes; that’s Shatter Point’s rung, and now you know exactly what it’ll cost.)
Two Notes and a Clock
playsound:
lea squarewave,a0
move.l a0,AUD0LC(a5) ; The wave to loop
move.w #4,AUD0LEN(a5) ; Four words = eight samples
move.w d0,AUD0PER(a5) ; Pitch now...
move.w d1,sndper2 ; ...pitch later
move.w d3,AUD0VOL(a5)
move.w d2,sndtimer
lsr.w #1,d2
move.w d2,sndhalf ; Where the wobble happens
move.w #$8001,DMACON(a5) ; SET + AUD0EN: sing
rts
;──────────────────────────────────────────────────────────────
; soundtick — once per frame: wobble at halfway, stop on time
;──────────────────────────────────────────────────────────────
soundtick:
tst.w sndtimer
beq.s .quiet
subq.w #1,sndtimer
bne.s .wobble
move.w #$0001,DMACON(a5) ; CLR + AUD0EN: hush
move.w #0,AUD0VOL(a5)
rts
.wobble:
move.w sndtimer,d0
cmp.w sndhalf,d0 ; Halfway through?
bne.s .quiet
move.w sndper2,AUD0PER(a5) ; The second note
.quiet:
rts
playsound is the entire voice: wave, length, pitch, volume, DMA on — and three words of state for soundtick to act on, once per frame. At halfway, the period slides to the second note; at zero, DMA off and volume down. That mid-flight slide is the whole character of each sound: the baa droops (1800 → 2100), the squelch sags (2800 → 3600), the bleat rises (1700 → 1250). Pitch movement is emotion — down is deflating, up is delight — and it costs one register write.
One design decision is worth saying out loud: new sounds steal the channel. A baa interrupted by a squelch just becomes the squelch — no queue, no mixing. With one channel that’s not laziness, it’s correctness: the newest event is the one that matters. (Paula has four channels; giving the sheep her own and the world another is exactly where this grows next.)
The events were all built in earlier units — the hop in steer, the loss in checksquash, the arrival in trypen — so wiring the voice is four lines at each site. Sound bolted onto clean event points is cheap; sound retrofitted into tangled logic isn’t. You’ve been earning this unit since Unit 3.
Experiment: Tune the Farmyard
BAA_PER1 equ 900— a lamb.equ 3000— a sheep the size of a barn. Pitch is character.- Swap each sound’s two periods. The baa now rises, the bleat droops. Listen to how wrong the same events feel — then put joy back where it belongs.
BAA_FRAMES equ 12and hold up: the baas merge into a drone (each hop re-steals the channel before the last baa ends). The first cut of this unit shipped exactly that by accident — five frames is what made the trot staccato.- A gentler wave:
dc.b 0,45,64,45,0,-45,-64,-45— eight points of a sine-ish curve. Same notes, rounder voice. The waveform is timbre; the period is pitch; you now control both.
The Complete Code
;──────────────────────────────────────────────────────────────
; FLOCK - A sheep-crossing arcade game for the Commodore Amiga
; Unit 11: A Soft Baa
;
; Paula speaks. One channel, one eight-byte square wave in
; Chip RAM, and three farmyard sounds built from nothing but
; period (pitch), volume and time: a wobbling baa on the hop,
; a low squelch for a loss, a rising bleat at the pen. Each
; sound is two periods and a duration — the wobble between
; them is what makes a baa a baa.
;──────────────────────────────────────────────────────────────
;══════════════════════════════════════════════════════════════
; TWEAKABLE VALUES — Change these and see what happens!
;══════════════════════════════════════════════════════════════
; Colours are $0RGB (4 bits per component, values 0-F)
COLOUR_FOLD_GRASS equ $0480 ; The fold's pasture
COLOUR_HEDGE equ $0350 ; Hedgerow between fold and stream
COLOUR_WATER equ $036A ; The stream
COLOUR_BANK equ $0350 ; Grassy bank below the stream
COLOUR_LANE equ $0666 ; The lane's tarmac
COLOUR_VERGE equ $0350 ; Verge below the lane
COLOUR_FIELD equ $0470 ; The field where the flock waits
COLOUR_FENCE equ $0531 ; Pen walls (bitplane, fold band)
COLOUR_WOOD equ $0852 ; The footbridge (bitplane, stream band)
COLOUR_DASH equ $0EEE ; Lane markings (bitplane, lane band)
COLOUR_TUFT equ $0360 ; Spare (bitplane, grass bands)
COLOUR_WOOL equ $0EEE ; The sheep's fleece (sprite colour 1)
COLOUR_FACE equ $0210 ; Her face, ears and tail (sprite colour 2)
COLOUR_SHADE equ $0BBB ; Fleece shading (sprite colour 3)
COLOUR_TRACTOR equ $0B20 ; The tractor's bodywork (sprite colour 1)
COLOUR_TYRE equ $0210 ; Wheels and trim (sprite colour 2)
COLOUR_CAB equ $0999 ; The cab roof (sprite colour 3)
COLOUR_WOODWORK equ $0742 ; The hay cart's bed (sprites 4-5)
COLOUR_HAY equ $0C92 ; Its heaped load
COLOUR_ROVER equ $0364 ; The Land Rover's paint (sprites 6-7)
COLOUR_ROOF equ $0AAA ; Its roof panel
; Where each band begins (screen row 0-255, top to bottom)
ROW_HEDGE equ 40
ROW_STREAM equ 48
ROW_BANK equ 80
ROW_LANE equ 96
ROW_VERGE equ 160
ROW_FIELD equ 176
; Where the sheep starts, and how she moves
SHEEP_X equ 152
SHEEP_Y equ 200
STEP equ 8 ; Pixels per hop
COOLDOWN equ 6 ; Frames between hops
; The traffic: three lanes, three rhythms
TRACTOR_Y equ 98 ; Top lane
TRACTOR_SPEED equ 2 ; Steady, rightward
ROVER_Y equ 120 ; Middle lane
ROVER_SPEED equ -3 ; Fast, leftward
CART_Y equ 142 ; Bottom lane — crossed first
CART_SPEED equ -1 ; Plodding, leftward
; How long the world stops when a sheep is lost
SQUASH_BEAT equ 25 ; Frames of stillness
; The flock
FLOCK_SIZE equ 5 ; Sheep in hand at the start
; The fold's pens
PEN_ROW equ 16 ; Where a resident sheep settles
FENCE_Y equ 24 ; She stops here unless a pen is open
PEN_BEAT equ 15 ; Frames of calm after a penning
; What things are worth
ROAD_POINTS equ 5 ; Surviving the road (per sheep)
PEN_POINTS equ 25 ; A sheep safely home
ROW_BANK_TOP equ 88 ; Past here, the road is behind her
; The farmyard's voice: each sound is two periods (pitch
; slides from the first to the second halfway through), a
; volume, and a length in frames. Bigger period = lower note.
BAA_PER1 equ 1800 ; The hop: a soft baa,
BAA_PER2 equ 2100 ; dropping as it ends
BAA_FRAMES equ 5
BAA_VOL equ 40
SPLAT_PER1 equ 2800 ; The loss: low and flat,
SPLAT_PER2 equ 3600 ; sagging lower
SPLAT_FRAMES equ 16
SPLAT_VOL equ 60
BLEAT_PER1 equ 1700 ; The pen: contented,
BLEAT_PER2 equ 1250 ; rising
BLEAT_FRAMES equ 18
BLEAT_VOL equ 50
; The HUD strip at the foot of the screen
ROW_HUD equ 240
COLOUR_HUD equ $0231 ; The strip itself
COLOUR_ICON equ $0EEE ; Sheep icons (bitplane, HUD band)
;══════════════════════════════════════════════════════════════
; HARDWARE REGISTERS
;══════════════════════════════════════════════════════════════
CUSTOM equ $dff000
DMACON equ $096 ; DMA control (write)
INTENA equ $09a ; Interrupt enable (write)
INTREQ equ $09c ; Interrupt request (write)
COP1LC equ $080 ; Copper list pointer
COPJMP1 equ $088 ; Copper restart strobe
VPOSR equ $004 ; Beam position
JOY1DAT equ $00c ; Joystick, control port 2
CLXDAT equ $00e ; Collision data (read clears it!)
CLXCON equ $098 ; Collision control
AUD0LC equ $0a0 ; Audio channel 0: sample address
AUD0LEN equ $0a4 ; sample length (words)
AUD0PER equ $0a6 ; period (pitch)
AUD0VOL equ $0a8 ; volume (0-64)
BPLCON0 equ $100 ; Bitplane control
BPLCON1 equ $102 ; Scroll
BPLCON2 equ $104 ; Priority
BPL1MOD equ $108 ; Odd plane modulo
DDFSTRT equ $092 ; Display data fetch start
DDFSTOP equ $094 ; Display data fetch stop
DIWSTRT equ $08e ; Display window start
DIWSTOP equ $090 ; Display window stop
BPL1PTH equ $0e0 ; Bitplane 1 pointer (high)
BPL1PTL equ $0e2 ; Bitplane 1 pointer (low)
BPL2PTH equ $0e4 ; Bitplane 2 pointer (high)
BPL2PTL equ $0e6 ; Bitplane 2 pointer (low)
BPL2MOD equ $10a ; Even plane modulo
SPR0PTH equ $120 ; Sprite 0 pointer (high)
COLOR00 equ $180 ; Background colour
COLOR01 equ $182 ; Bitplane colour 1
COLOR02 equ $184 ; Bitplane colour 2 (plane 2)
COLOR03 equ $186 ; Bitplane colour 3 (both planes)
COLOR17 equ $1a2 ; Sprite 0/1 colour 1
COLOR18 equ $1a4 ; Sprite 0/1 colour 2
COLOR19 equ $1a6 ; Sprite 0/1 colour 3
COLOR21 equ $1aa ; Sprite 2/3 colour 1
COLOR22 equ $1ac ; Sprite 2/3 colour 2
COLOR23 equ $1ae ; Sprite 2/3 colour 3
COLOR25 equ $1b2 ; Sprite 4/5 colour 1
COLOR26 equ $1b4 ; Sprite 4/5 colour 2
COLOR27 equ $1b6 ; Sprite 4/5 colour 3
COLOR29 equ $1ba ; Sprite 6/7 colour 1
COLOR30 equ $1bc ; Sprite 6/7 colour 2
COLOR31 equ $1be ; Sprite 6/7 colour 3
ROW_BYTES equ 40 ; 320 pixels / 8
;══════════════════════════════════════════════════════════════
; CODE (Chip RAM — the Copper, planes and sprites live here)
;══════════════════════════════════════════════════════════════
section code,code_c
start:
lea CUSTOM,a5 ; A5 = custom chip base ($DFF000)
; --- Take over the machine ---
move.w #$7fff,INTENA(a5) ; Disable all interrupts
move.w #$7fff,INTREQ(a5) ; Clear pending interrupts
move.w #$7fff,DMACON(a5) ; Disable all DMA
; --- Point the Copper's bitplane MOVEs at our planes ---
lea copbpl,a1
lea plane,a0
move.l a0,d0
move.w d0,6(a1) ; Low word into the BPL1PTL move
swap d0
move.w d0,2(a1) ; High word into the BPL1PTH move
lea plane2,a0
move.l a0,d0
move.w d0,14(a1) ; And the same for plane 2
swap d0
move.w d0,10(a1)
; --- Point sprite 0 at the sheep, the rest at nothing ---
lea copsprites,a1 ; Eight pointer pairs in the list
lea sheep0,a0
move.l a0,d0
move.w d0,6(a1) ; Sprite 0 low word
swap d0
move.w d0,2(a1) ; Sprite 0 high word
lea nullspr,a0 ; Sprites 1-7: an empty sprite
move.l a0,d0
moveq #7-1,d6
.nulls:
lea 8(a1),a1 ; Next pointer pair in the list
move.w d0,6(a1)
swap d0
move.w d0,2(a1)
swap d0
dbf d6,.nulls
; --- ...except the traffic: sprites 2, 4 and 6.
; Each vehicle gets the EVEN sprite of its own pair, so
; each lives in its own collision group: tractor in 2/3
; (bit 9 against the sheep), cart in 4/5 (bit 10),
; Land Rover in 6/7 (bit 11) — and its own palette.
lea copsprites+16,a1 ; Sprite 2: the tractor
lea tractor,a0
move.l a0,d0
move.w d0,6(a1)
swap d0
move.w d0,2(a1)
lea copsprites+32,a1 ; Sprite 4: the hay cart
lea cart,a0
move.l a0,d0
move.w d0,6(a1)
swap d0
move.w d0,2(a1)
lea copsprites+48,a1 ; Sprite 6: the Land Rover
lea rover,a0
move.l a0,d0
move.w d0,6(a1)
swap d0
move.w d0,2(a1)
; --- Arm collision detection ---
move.w #$0000,CLXCON(a5) ; Even sprites always take part;
; we need nothing extra for 0-vs-2
move.w CLXDAT(a5),d0 ; Prime: reading clears the latches
; --- Draw the farmyard's detail into the bitplane ---
bsr drawfarmyard
bsr drawflock ; The flock in hand, bottom-left
bsr drawscore ; And the score, bottom-right
; --- Place the sheep at her starting spot ---
bsr updsprite
; --- Install Copper list ---
lea copperlist,a0
move.l a0,COP1LC(a5)
move.w d0,COPJMP1(a5) ; Strobe: restart Copper from COP1LC
; --- Enable DMA ---
move.w #$83a0,DMACON(a5) ; SET + DMAEN + BPLEN + COPEN + SPREN
; === Main Loop ===
mainloop:
; Wait for vertical blank — in two phases. If we only
; waited FOR line 0, a fast loop body could finish while
; the beam is still ON line 0 and run again in the same
; frame. Wait to leave line 0 first, then to reach it.
move.l #$1ff00,d1 ; Mask: bits 8-16 of beam position
.vbleave:
move.l VPOSR(a5),d0 ; Read beam position
and.l d1,d0 ; Isolate line number
beq.s .vbleave ; Loop while still on line 0
.vbwait:
move.l VPOSR(a5),d0 ; Read beam position
and.l d1,d0 ; Isolate line number
bne.s .vbwait ; Loop until line 0 again
bsr steer ; Read the stick, maybe hop
bsr drivelanes ; All the traffic, one mover
bsr checksquash ; Did the lane win?
bsr soundtick ; Wobble, and fall silent on time
bsr updsprite ; Position is data: rewrite POS/CTL
bsr showframe ; Point sprite 0 at this step's image
; Check left mouse button (active low at CIAA)
btst #6,$bfe001 ; CIAA Port A, bit 6
bne.s mainloop ; Not pressed — keep going
; Button pressed — halt
.halt:
bra.s .halt
;══════════════════════════════════════════════════════════════
; STEER — read the joystick, hop the sheep
;
; JOY1DAT is control port 2. The decode is famously sideways:
; right = bit 1 left = bit 9
; down = bit 0 XOR bit 1 up = bit 8 XOR bit 9
; One XOR of the register with itself-shifted turns the two
; awkward pairs into plain testable bits.
;
; A hop is STEP pixels; COOLDOWN frames must pass between hops
; — that's what makes her *step* like a sheep rather than glide
; like a cursor.
;══════════════════════════════════════════════════════════════
steer:
tst.w won ; Fold full — nothing to steer
bne.s .frozen
tst.w gameover ; No flock, no shepherd
bne.s .frozen
tst.w squashtimer ; Mid squash-beat? She can't move
beq.s .alive
.frozen: rts
.alive:
tst.w cooldown
beq.s .ready
subq.w #1,cooldown ; Still mid-hop rhythm — wait
rts
.ready:
move.w JOY1DAT(a5),d0 ; Read the stick
move.w d0,d1
lsr.w #1,d1
eor.w d0,d1 ; Now: bit 0 = down, bit 8 = up
btst #8,d1 ; Up?
beq.s .notup
cmp.w #FENCE_Y,sheepy ; At the fence line?
bgt.s .climb
bsr trypen ; Only a pen lets her past
bra .done
.climb:
sub.w #STEP,sheepy
bra.s .stepped
.notup:
btst #0,d1 ; Down?
beq.s .notdown
add.w #STEP,sheepy
bra.s .stepped
.notdown:
btst #9,d0 ; Left?
beq.s .notleft
sub.w #STEP,sheepx
bra.s .stepped
.notleft:
btst #1,d0 ; Right?
beq .done ; Stick centred — no hop
add.w #STEP,sheepx
.stepped:
move.w #COOLDOWN,cooldown ; Set the hop rhythm
eori.w #1,curframe ; The other feet, next picture
; --- The hop has a voice ---
move.w #BAA_PER1,d0
move.w #BAA_PER2,d1
move.w #BAA_FRAMES,d2
move.w #BAA_VOL,d3
bsr playsound
; --- First time past the road? That's worth something.
tst.w roadflag
bne.s .scored
cmp.w #ROW_BANK_TOP,sheepy
bgt.s .scored
move.w #1,roadflag
add.w #ROAD_POINTS,score
bsr drawscore
.scored:
; --- Hold her inside the farm ---
tst.w sheepx
bge.s .xlow
clr.w sheepx
.xlow: cmp.w #320-16,sheepx
ble.s .xhigh
move.w #320-16,sheepx
.xhigh: cmp.w #FENCE_Y,sheepy ; The fence is the ceiling;
bge.s .ylow ; pens are the only way past
move.w #FENCE_Y,sheepy
.ylow: cmp.w #ROW_HUD-16,sheepy ; The HUD strip is not a pasture
ble.s .done
move.w #ROW_HUD-16,sheepy
.done:
rts
;══════════════════════════════════════════════════════════════
; DRIVELANES — advance all the traffic
;
; Unit 5's mover, made data. Each vehicle is a row in vehtab —
; WHERE its x lives, and how fast it moves (signed: negative
; drives leftward). One loop walks the table: add the speed,
; wrap off whichever edge the speed points at, store. Adding a
; vehicle to the game is adding a row to the table.
;══════════════════════════════════════════════════════════════
drivelanes:
lea vehtab,a2
moveq #3-1,d6 ; Three vehicles
.veh:
move.l (a2)+,a0 ; A0 = where this one's x lives
move.w (a2)+,d1 ; D1 = its speed (signed)
move.w (a0),d0
add.w d1,d0
tst.w d1
bmi.s .leftward
cmp.w #320,d0 ; Rightward: clear of the right edge?
blt.s .store
move.w #-16,d0 ; Re-enter from the left
bra.s .store
.leftward:
cmp.w #-16,d0 ; Leftward: clear of the left edge?
bgt.s .store
move.w #320,d0 ; Re-enter from the right
.store:
move.w d0,(a0)
dbf d6,.veh
rts
vehtab: dc.l tractx
dc.w TRACTOR_SPEED
dc.l cartx
dc.w CART_SPEED
dc.l roverx
dc.w ROVER_SPEED
;══════════════════════════════════════════════════════════════
; CHECKSQUASH — read the collision latches, judge the lane
;
; CLXDAT accumulates collisions as Denise draws, and READING
; IT CLEARS IT — so read it exactly once per frame and keep
; the copy. Bit 9 means "sprite 0 or 1 touched sprite 2 or 3":
; our sheep met our tractor, pixel against pixel. The hardware
; compared every overlapping pixel pair for us, for free.
;══════════════════════════════════════════════════════════════
checksquash:
tst.w squashtimer ; Already mid-beat?
beq.s .watch
subq.w #1,squashtimer ; Count the stillness down
bne.s .out
move.w CLXDAT(a5),d0 ; Beat over: flush the contact
.out: rts ; that accumulated during it
.watch:
tst.w gameover ; Nothing left to lose?
bne.s .safe
move.w CLXDAT(a5),d0 ; Read once — this clears it
and.w #$0e00,d0 ; Bits 9/10/11: the sheep against
beq.s .safe ; ANY of the three vehicle groups
; --- Squashed. One fewer in hand. ---
move.w #SPLAT_PER1,d0
move.w #SPLAT_PER2,d1
move.w #SPLAT_FRAMES,d2
move.w #SPLAT_VOL,d3
bsr playsound
subq.w #1,lives
bsr drawflock ; Redraw the strip
tst.w lives
bgt.s .next ; Sheep remain — send the next one
move.w #1,gameover ; The field is empty
rts
.next:
move.w #SHEEP_X,sheepx ; The next sheep steps up
move.w #SHEEP_Y,sheepy
clr.w roadflag ; A fresh road for the next one
move.w #SQUASH_BEAT,squashtimer
.safe:
rts
;══════════════════════════════════════════════════════════════
; SETPOS — pack screen (x, y) into one sprite's POS/CTL
; a0 = sprite structure d0 = x d1 = y
;
; Unit 3's packing, generalised: any sprite, any position. Beam
; coordinates: VSTART = y + $2C, HSTART = x + $80 — and the
; ninth bits ride in CTL's low flags.
;══════════════════════════════════════════════════════════════
setpos:
add.w #$2c,d1 ; D1 = VSTART (beam line)
move.w d1,d2
add.w #16,d2 ; D2 = VSTOP (16 rows tall)
add.w #$80,d0 ; D0 = HSTART (beam position)
; POS = VSTART[7:0] << 8 | HSTART[8:1]
move.w d1,d3
lsl.w #8,d3
move.w d0,d4
lsr.w #1,d4
and.w #$ff,d4
or.w d4,d3
move.w d3,(a0) ; Write POS
; CTL = VSTOP[7:0] << 8 | V8START<<2 | V8STOP<<1 | H0START
move.w d2,d3
and.w #$ff,d3
lsl.w #8,d3
btst #8,d1 ; VSTART's ninth bit
beq.s .nv8s
or.w #%100,d3
.nv8s: btst #8,d2 ; VSTOP's ninth bit
beq.s .nv8e
or.w #%010,d3
.nv8e: btst #0,d0 ; HSTART's odd-pixel bit
beq.s .nh0
or.w #%001,d3
.nh0: move.w d3,2(a0) ; Write CTL
rts
;══════════════════════════════════════════════════════════════
; UPDSPRITE — place every sprite for this frame
;
; One routine owns every position write: the sheep (both step
; images, so whichever showframe picks she stands in the same
; place) and the tractor.
;══════════════════════════════════════════════════════════════
updsprite:
lea sheep0,a0
move.w sheepx,d0
move.w sheepy,d1
bsr setpos
lea sheep1,a0
move.w sheepx,d0
move.w sheepy,d1
bsr setpos
lea tractor,a0
move.w tractx,d0
move.w #TRACTOR_Y,d1
bsr setpos
lea cart,a0
move.w cartx,d0
move.w #CART_Y,d1
bsr setpos
lea rover,a0
move.w roverx,d0
move.w #ROVER_Y,d1
bsr setpos
rts
;══════════════════════════════════════════════════════════════
; SHOWFRAME — point sprite 0 at this step's image
;
; Animation is nothing but choosing which data the channel
; fetches. The Copper list's sprite 0 pointer words are
; rewritten with whichever picture curframe names — the same
; poke the startup code did, now done every frame.
;══════════════════════════════════════════════════════════════
showframe:
lea nullspr,a0 ; Game over, or the whole flock
tst.w gameover ; home: no sheep on the move
bne.s .picked
tst.w won
bne.s .picked
lea sheep0,a0
tst.w curframe
beq.s .picked
lea sheep1,a0
.picked:
move.l a0,d0
lea copsprites,a1
move.w d0,6(a1) ; Sprite 0 low word
swap d0
move.w d0,2(a1) ; Sprite 0 high word
rts
;══════════════════════════════════════════════════════════════
; DRAW THE FARMYARD (unchanged from Unit 1)
;══════════════════════════════════════════════════════════════
drawfarmyard:
; --- The fold's pens (rows 4-35) ---
moveq #0,d0 ; x = byte 0
moveq #4,d1 ; row 4
moveq #ROW_BYTES,d2 ; full width
moveq #4,d3 ; 4 rows thick
bsr rectfill
lea penposts,a2 ; Post positions (byte columns)
moveq #6-1,d6 ; Six posts
.posts:
moveq #0,d0
move.b (a2)+,d0 ; x = next post column
moveq #8,d1 ; rows 8-35
moveq #1,d2 ; one byte wide
moveq #28,d3
bsr rectfill
dbf d6,.posts
; --- The footbridge (rows 48-79, mid-stream) ---
moveq #18,d0 ; byte 18 = pixel 144
moveq #ROW_STREAM,d1
moveq #4,d2 ; 32 pixels wide
moveq #32,d3 ; the stream's full height
bsr rectfill
; --- Lane markings: two dashed lines (rows 116, 136) ---
moveq #116,d1
bsr dashline
move.w #136,d1
; falls through
;──────────────────────────────────────────────────────────────
; dashline — a row of dashes across the lane
; d1 = starting row. 2 bytes on, 2 bytes off, 4 rows thick.
;──────────────────────────────────────────────────────────────
dashline:
moveq #0,d0 ; x = byte 0
.dash:
move.w d1,-(sp) ; rectfill trashes d1
move.w d0,-(sp) ; ...and d0
moveq #2,d2 ; 2 bytes of dash
moveq #4,d3 ; 4 rows thick
bsr rectfill
move.w (sp)+,d0
move.w (sp)+,d1
addq.w #4,d0 ; next dash 4 bytes along
cmp.w #ROW_BYTES,d0
blt.s .dash
rts
;──────────────────────────────────────────────────────────────
; rectfill — set a byte-aligned rectangle of pixels
; d0 = x (bytes) d1 = row d2 = width (bytes) d3 = height
; Trashes d1, d4, d5, a0, a1.
;──────────────────────────────────────────────────────────────
rectfill:
lea plane,a0
move.w d1,d4
mulu #ROW_BYTES,d4 ; row * 40
add.w d0,d4 ; + x
adda.w d4,a0 ; A0 = first byte of the rectangle
move.w d3,d4 ; D4 = rows to go
.row:
movea.l a0,a1
move.w d2,d5 ; D5 = bytes to go
.col:
move.b #$ff,(a1)+ ; 8 pixels on
subq.w #1,d5
bne.s .col
lea ROW_BYTES(a0),a0 ; down one row
subq.w #1,d4
bne.s .row
rts
penposts: dc.b 0,8,16,24,32,39 ; Byte columns of the six posts
even
;══════════════════════════════════════════════════════════════
; TRYPEN — at the fence, try to enter the pen she's facing
;
; Each pentab row is a pen: the span of sheep-centre x values
; it accepts, the byte column its resident glyph is drawn at,
; and a flag byte that remembers it's taken. A hit pens her:
; the resident appears (plane 2 — white among the brown
; fences), the next sheep steps up, and a full fold wins.
;══════════════════════════════════════════════════════════════
trypen:
move.w sheepx,d0
addq.w #8,d0 ; D0 = her centre
lea pentab,a2
moveq #5-1,d6
.pen:
cmp.w (a2),d0 ; Left of this pen?
blt.s .nextpen
cmp.w 2(a2),d0 ; Right of it?
bgt.s .nextpen
tst.b 5(a2) ; Already taken?
bne.s .nextpen
; --- She's in. A resident for the fold. ---
move.b #1,5(a2)
move.w 4(a2),d0 ; Glyph byte column
and.w #$ff00,d0
lsr.w #8,d0
bsr penglyph
move.w #BLEAT_PER1,d0 ; A contented sound
move.w #BLEAT_PER2,d1
move.w #BLEAT_FRAMES,d2
move.w #BLEAT_VOL,d3
bsr playsound
add.w #PEN_POINTS,score ; A sheep safely home
bsr drawscore
move.w #SHEEP_X,sheepx ; The next sheep steps up
move.w #SHEEP_Y,sheepy
clr.w roadflag ; Her road is still ahead of her
move.w #PEN_BEAT,squashtimer
subq.w #1,unpenned ; A full fold wins
bne.s .out
move.w #1,won
.out:
rts
.nextpen:
addq.l #6,a2
dbf d6,.pen
rts ; Fence, post or a full pen: no way through
;──────────────────────────────────────────────────────────────
; penglyph — stamp the resident-sheep glyph into PLANE 2
; d0 = x (bytes). Row is PEN_ROW; the glyph is the HUD icon.
;──────────────────────────────────────────────────────────────
penglyph:
lea plane2,a0
move.w #PEN_ROW,d4
mulu #ROW_BYTES,d4
add.w d0,d4
adda.w d4,a0
lea sheepicon,a2
moveq #8-1,d4
.row:
move.b (a2)+,(a0)
lea ROW_BYTES(a0),a0
dbf d4,.row
rts
; Per pen: centre-x span (min, max), glyph byte
; column (high byte of the word), taken flag, pad
pentab: dc.w 8,63
dc.b 4,0
even
dc.w 72,127
dc.b 12,0
even
dc.w 136,191
dc.b 20,0
even
dc.w 200,255
dc.b 28,0
even
dc.w 264,311
dc.b 36,0
even
;══════════════════════════════════════════════════════════════
; PLAYSOUND — start a sound on Paula channel 0
; d0 = starting period d1 = second period (from halfway)
; d2 = duration (frames) d3 = volume (0-64)
;
; Paula plays a looping sample on its own DMA: point the
; channel at the wave, say how long it is and how fast to
; step through it (the period), set a volume, switch the DMA
; on. Everything after that is timing: soundtick slides the
; period at halfway and shuts the channel up when time runs
; out. New sounds steal the channel — the farmyard talks over
; itself rather than queueing politely.
;══════════════════════════════════════════════════════════════
playsound:
lea squarewave,a0
move.l a0,AUD0LC(a5) ; The wave to loop
move.w #4,AUD0LEN(a5) ; Four words = eight samples
move.w d0,AUD0PER(a5) ; Pitch now...
move.w d1,sndper2 ; ...pitch later
move.w d3,AUD0VOL(a5)
move.w d2,sndtimer
lsr.w #1,d2
move.w d2,sndhalf ; Where the wobble happens
move.w #$8001,DMACON(a5) ; SET + AUD0EN: sing
rts
;──────────────────────────────────────────────────────────────
; soundtick — once per frame: wobble at halfway, stop on time
;──────────────────────────────────────────────────────────────
soundtick:
tst.w sndtimer
beq.s .quiet
subq.w #1,sndtimer
bne.s .wobble
move.w #$0001,DMACON(a5) ; CLR + AUD0EN: hush
move.w #0,AUD0VOL(a5)
rts
.wobble:
move.w sndtimer,d0
cmp.w sndhalf,d0 ; Halfway through?
bne.s .quiet
move.w sndper2,AUD0PER(a5) ; The second note
.quiet:
rts
;══════════════════════════════════════════════════════════════
; DRAWFLOCK — the sheep in hand, as icons on the HUD strip
;
; One 8x8 glyph per sheep still in hand, drawn at the bottom
; left; the strip is cleared first so a lost sheep disappears.
; The icons are bitplane pixels — the HUD band's COLOR01 makes
; them white, the same per-band trick as the fence and dashes.
;══════════════════════════════════════════════════════════════
drawflock:
; Clear the icon area (a row of byte-rectangles)
moveq #1,d0 ; From byte 1
move.w #ROW_HUD+4,d1
moveq #12,d2 ; Room for the whole flock
moveq #8,d3
bsr rectclear
; One glyph per sheep in hand
move.w lives,d7
ble.s .none ; Empty hand, empty strip
moveq #1,d6 ; First icon at byte 1
.icons:
move.w d6,d0
move.w #ROW_HUD+4,d1
lea sheepicon,a2
bsr drawglyph
addq.w #2,d6 ; Two bytes along for the next
subq.w #1,d7
bne.s .icons
.none:
rts
;──────────────────────────────────────────────────────────────
; drawglyph — copy an 8-row, 1-byte-wide glyph into the plane
; d0 = x (bytes) d1 = row a2 = glyph (8 bytes)
; Trashes d1, d4, a0.
;──────────────────────────────────────────────────────────────
drawglyph:
lea plane,a0
move.w d1,d4
mulu #ROW_BYTES,d4
add.w d0,d4
adda.w d4,a0
moveq #8-1,d4
.row:
move.b (a2)+,(a0)
lea ROW_BYTES(a0),a0
dbf d4,.row
rts
;──────────────────────────────────────────────────────────────
; rectclear — rectfill's opposite: clear a byte-aligned block
; d0 = x (bytes) d1 = row d2 = width (bytes) d3 = height
; Trashes d1, d4, d5, a0, a1.
;──────────────────────────────────────────────────────────────
rectclear:
lea plane,a0
move.w d1,d4
mulu #ROW_BYTES,d4
add.w d0,d4
adda.w d4,a0
move.w d3,d4
.row:
movea.l a0,a1
move.w d2,d5
.col:
clr.b (a1)+
subq.w #1,d5
bne.s .col
lea ROW_BYTES(a0),a0
subq.w #1,d4
bne.s .row
rts
sheepicon: dc.b %00100100 ; A sheep, in eight bytes:
dc.b %01111110 ; ears up top,
dc.b %11111111 ; a fat woolly middle,
dc.b %11111111
dc.b %11111111
dc.b %01111110
dc.b %00111100 ; tapering to
dc.b %00011000 ; a little tail
even
;══════════════════════════════════════════════════════════════
; DRAWSCORE — four decimal digits at the strip's right end
;
; The score lives as one binary word; the display is decimal.
; DIVU by 10 peels the digits off the right: the remainder is
; the next digit, the quotient carries on. Stamp them right to
; left with the same drawglyph the icons use — each digit is
; just a glyph in the font table.
;══════════════════════════════════════════════════════════════
drawscore:
move.w score,d7 ; D7 = what's left to convert
moveq #38,d5 ; Rightmost digit's byte column
moveq #4-1,d6 ; Four digits
.digit:
moveq #0,d0
move.w d7,d0
divu #10,d0 ; Quotient low, remainder high
move.l d0,d1
swap d1 ; D1 = this digit (0-9)
move.w d0,d7 ; D7 = the rest
; Find the digit's glyph: font + digit*8
lea digitfont,a2
add.w d1,d1
add.w d1,d1
add.w d1,d1 ; digit * 8
adda.w d1,a2
move.w d5,d0 ; Byte column
move.w #ROW_HUD+4,d1
bsr drawglyph
subq.w #1,d5 ; Next digit to the left
dbf d6,.digit
rts
digitfont: ; 0-9, one byte per row, eight rows each
dc.b %01111100,%11000110,%11001110,%11010110,%11100110,%11000110,%01111100,0 ; 0
dc.b %00011000,%00111000,%00011000,%00011000,%00011000,%00011000,%01111110,0 ; 1
dc.b %01111100,%11000110,%00000110,%00111100,%01100000,%11000000,%11111110,0 ; 2
dc.b %01111100,%11000110,%00000110,%00111100,%00000110,%11000110,%01111100,0 ; 3
dc.b %00011100,%00111100,%01101100,%11001100,%11111110,%00001100,%00001100,0 ; 4
dc.b %11111110,%11000000,%11111100,%00000110,%00000110,%11000110,%01111100,0 ; 5
dc.b %01111100,%11000000,%11111100,%11000110,%11000110,%11000110,%01111100,0 ; 6
dc.b %11111110,%00000110,%00001100,%00011000,%00110000,%00110000,%00110000,0 ; 7
dc.b %01111100,%11000110,%01111100,%11000110,%11000110,%11000110,%01111100,0 ; 8
dc.b %01111100,%11000110,%11000110,%01111110,%00000110,%00000110,%01111100,0 ; 9
even
;══════════════════════════════════════════════════════════════
; COPPER LIST — the farmyard, plus eight sprite pointers
;══════════════════════════════════════════════════════════════
copperlist:
; --- Display setup ---
dc.w DIWSTRT,$2c81 ; Window: top-left
dc.w DIWSTOP,$2cc1 ; Window: bottom-right
dc.w DDFSTRT,$0038 ; Fetch start (lores)
dc.w DDFSTOP,$00d0 ; Fetch stop
dc.w BPLCON0,$2200 ; 2 bitplanes, colour burst on
dc.w BPLCON1,$0000 ; No scroll
dc.w BPLCON2,$0024 ; Sprites in front of playfield
dc.w BPL1MOD,$0000 ; No modulo — rows pack tight
dc.w BPL2MOD,$0000
copbpl:
dc.w BPL1PTH,$0000 ; Plane addresses, poked in
dc.w BPL1PTL,$0000 ; by the CPU at startup
dc.w BPL2PTH,$0000
dc.w BPL2PTL,$0000
copsprites:
dc.w SPR0PTH+0,$0000 ; Sprite 0: the sheep (poked in)
dc.w SPR0PTH+2,$0000
dc.w SPR0PTH+4,$0000 ; Sprites 1-7: the null sprite
dc.w SPR0PTH+6,$0000
dc.w SPR0PTH+8,$0000
dc.w SPR0PTH+10,$0000
dc.w SPR0PTH+12,$0000
dc.w SPR0PTH+14,$0000
dc.w SPR0PTH+16,$0000
dc.w SPR0PTH+18,$0000
dc.w SPR0PTH+20,$0000
dc.w SPR0PTH+22,$0000
dc.w SPR0PTH+24,$0000
dc.w SPR0PTH+26,$0000
dc.w SPR0PTH+28,$0000
dc.w SPR0PTH+30,$0000
; --- Plane-2 colours: a resident sheep is white wherever
; she settles (colour 2 = plane 2 alone; colour 3 = both
; planes — fence-and-sheep never overlap, white is safe)
dc.w COLOR02,$0EEE
dc.w COLOR03,$0EEE
; --- The sheep's colours (sprites 0-1 share 17-19) ---
dc.w COLOR17,COLOUR_WOOL
dc.w COLOR18,COLOUR_FACE
dc.w COLOR19,COLOUR_SHADE
; --- The tractor's colours (sprites 2-3 share 21-23) ---
dc.w COLOR21,COLOUR_TRACTOR
dc.w COLOR22,COLOUR_TYRE
dc.w COLOR23,COLOUR_CAB
; --- The hay cart's (sprites 4-5 share 25-27) ---
dc.w COLOR25,COLOUR_WOODWORK
dc.w COLOR26,COLOUR_TYRE
dc.w COLOR27,COLOUR_HAY
; --- The Land Rover's (sprites 6-7 share 29-31) ---
dc.w COLOR29,COLOUR_ROVER
dc.w COLOR30,COLOUR_TYRE
dc.w COLOR31,COLOUR_ROOF
; --- THE FOLD (from the top of the frame) ---
dc.w COLOR00,COLOUR_FOLD_GRASS
dc.w COLOR01,COLOUR_FENCE ; Pixels here are fence
; --- HEDGEROW (row 40) ---
dc.w $5401,$fffe ; Wait: line $2C+40 = $54
dc.w COLOR00,COLOUR_HEDGE
dc.w COLOR01,COLOUR_TUFT
; --- THE STREAM (row 48) ---
dc.w $5c01,$fffe ; Wait: line $2C+48 = $5C
dc.w COLOR00,COLOUR_WATER
dc.w COLOR01,COLOUR_WOOD ; Pixels here are bridge
; --- THE BANK (row 80) ---
dc.w $7c01,$fffe ; Wait: line $2C+80 = $7C
dc.w COLOR00,COLOUR_BANK
dc.w COLOR01,COLOUR_TUFT
; --- THE LANE (row 96) ---
dc.w $8c01,$fffe ; Wait: line $2C+96 = $8C
dc.w COLOR00,COLOUR_LANE
dc.w COLOR01,COLOUR_DASH ; Pixels here are markings
; --- THE VERGE (row 160) ---
dc.w $cc01,$fffe ; Wait: line $2C+160 = $CC
dc.w COLOR00,COLOUR_VERGE
dc.w COLOR01,COLOUR_TUFT
; --- THE FIELD (row 176, down to row 239) ---
dc.w $dc01,$fffe ; Wait: line $2C+176
dc.w COLOR00,COLOUR_FIELD
dc.w COLOR01,COLOUR_TUFT
; --- THE HUD STRIP (row 240) ---
; Row 240 is beam line $11C — past 255, which the Copper's
; 8-bit comparator can't name directly. The classic trick:
; wait for the very end of line 255, THEN wait for the low
; byte. The first wait carries you across the boundary.
dc.w $ffdf,$fffe ; To the end of line 255
dc.w $1c01,$fffe ; Then line $11C & $FF = $1C
dc.w COLOR00,COLOUR_HUD
dc.w COLOR01,COLOUR_ICON
; --- END OF COPPER LIST ---
dc.w $ffff,$fffe ; Wait for impossible position
;══════════════════════════════════════════════════════════════
; THE SHEEP — sprite 0, two step images
;
; Same sheep, two pictures. Step image 0: front-left and
; back-right feet planted. Step image 1: the other diagonal,
; tail swung the other way. Alternate them as she hops and
; she waddles. The control words are written by updsprite.
;══════════════════════════════════════════════════════════════
section data,data_c
sheep0:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (fleece) plane B (face/shade/feet)
dc.w %0000000000000000,%0000100000010000 ; ..ears..
dc.w %0000000000000000,%0000011111100000 ; ..head..
dc.w %0000000000000000,%0000001111000000 ; ..face..
dc.w %0000111111110000,%0000000000000000 ; fleece ruff
dc.w %0011111111111100,%0000000000000000 ; shoulders
dc.w %0111111111111110,%1000000000000000 ; < front foot
dc.w %0111111111111110,%1001000000001000 ; < + flecks
dc.w %0111111111111110,%0000000000000000
dc.w %0111111111111110,%0000001001000000 ; shade flecks
dc.w %0111111111111110,%0000000000000001 ; back foot >
dc.w %0111111111111110,%0000100000010001 ; + flecks >
dc.w %0011111111111100,%0000000000000000 ; haunches
dc.w %0011111111111100,%0000000000000000
dc.w %0001111111111000,%0000000000000000
dc.w %0000111111110000,%0000000000000000 ; rump
dc.w %0000000000000000,%0000001100000000 ; tail, left
dc.w 0,0 ; End of sprite
sheep1:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (fleece) plane B (face/shade/feet)
dc.w %0000000000000000,%0000100000010000 ; ..ears..
dc.w %0000000000000000,%0000011111100000 ; ..head..
dc.w %0000000000000000,%0000001111000000 ; ..face..
dc.w %0000111111110000,%0000000000000000 ; fleece ruff
dc.w %0011111111111100,%0000000000000000 ; shoulders
dc.w %0111111111111110,%0000000000000001 ; front foot >
dc.w %0111111111111110,%0001000000001001 ; + flecks >
dc.w %0111111111111110,%0000000000000000
dc.w %0111111111111110,%0000001001000000 ; shade flecks
dc.w %0111111111111110,%1000000000000000 ; < back foot
dc.w %0111111111111110,%1000100000010000 ; < + flecks
dc.w %0011111111111100,%0000000000000000 ; haunches
dc.w %0011111111111100,%0000000000000000
dc.w %0001111111111000,%0000000000000000
dc.w %0000111111110000,%0000000000000000 ; rump
dc.w %0000000000000000,%0000000011000000 ; tail, right
dc.w 0,0 ; End of sprite
;══════════════════════════════════════════════════════════════
; THE TRACTOR — sprite 2
;
; Big rear wheels on the left, small front wheels and the
; bonnet pointing right — the way it drives. Red bodywork,
; dark tyres, a grey cab roof. Its own palette: sprites 2-3
; share colours 21-23.
;══════════════════════════════════════════════════════════════
tractor:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (body/cab) plane B (tyres/cab)
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0111110000000000 ; rear wheel
dc.w %0000000000000000,%0111110000011110 ; + front wheel
dc.w %0000000000000000,%0111110000011110
dc.w %0000001111111100,%0111110000000000 ; chassis
dc.w %0111111111111110,%0000111110000000 ; body + cab
dc.w %0111111111111110,%0000111110000000
dc.w %0111111111111110,%0000111110000000
dc.w %0111111111111110,%0000111110000000
dc.w %0000001111111100,%0111110000000000 ; chassis
dc.w %0000000000000000,%0111110000011110
dc.w %0000000000000000,%0111110000011110 ; + front wheel
dc.w %0000000000000000,%0111110000000000 ; rear wheel
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w 0,0 ; End of sprite
nullspr: dc.w 0,0 ; A sprite that displays nothing
dc.w 0,0
; --- The sheep's state ---
sheepx: dc.w SHEEP_X ; Screen x (0-304)
sheepy: dc.w SHEEP_Y ; Screen y (0-240)
cooldown: dc.w 0 ; Frames until the next hop
curframe: dc.w 0 ; Which step image: 0 or 1
;══════════════════════════════════════════════════════════════
; THE HAY CART — sprite 4
;
; Plods leftward: a wooden bed, a heaped load of hay, and
; wheels at the corners. Wood and hay get their own palette
; (sprites 4-5 share colours 25-27).
;══════════════════════════════════════════════════════════════
cart:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (wood/hay) plane B (wheels/hay)
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0110000000000110 ; wheels
dc.w %0011111111111100,%0110000000000110
dc.w %0011111111111100,%0110000000000110
dc.w %0011111111111100,%0000111111110000 ; hay rises
dc.w %0011111111111100,%0001111111111000
dc.w %0011111111111100,%0001111111111000
dc.w %0011111111111100,%0001111111111000
dc.w %0011111111111100,%0001111111111000
dc.w %0011111111111100,%0000111111110000 ; hay falls
dc.w %0011111111111100,%0110000000000110
dc.w %0011111111111100,%0110000000000110 ; wheels
dc.w %0000000000000000,%0110000000000110
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w 0,0 ; End of sprite
;══════════════════════════════════════════════════════════════
; THE LAND ROVER — sprite 6
;
; The farmer's in a hurry. Boxy paintwork, a roof panel set
; back from the bonnet (it drives leftward, so the bonnet is
; the left end), wheels at the corners. Sprites 6-7 share
; colours 29-31.
;══════════════════════════════════════════════════════════════
rover:
dc.w 0 ; POS — written by updsprite
dc.w 0 ; CTL — written by updsprite
; plane A (paint/roof) plane B (wheels/roof)
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0011000000001100 ; wheels
dc.w %0111111111111110,%0011000000001100
dc.w %0111111111111110,%0011000000001100
dc.w %0111111111111110,%0000001111111000 ; roof panel,
dc.w %0111111111111110,%0000001111111000 ; set back
dc.w %0111111111111110,%0000001111111000 ; from the
dc.w %0111111111111110,%0000001111111000 ; bonnet
dc.w %0111111111111110,%0000001111111000
dc.w %0111111111111110,%0000001111111000
dc.w %0111111111111110,%0011000000001100
dc.w %0111111111111110,%0011000000001100 ; wheels
dc.w %0000000000000000,%0011000000001100
dc.w %0000000000000000,%0000000000000000
dc.w %0000000000000000,%0000000000000000
dc.w 0,0 ; End of sprite
; --- The traffic's state ---
tractx: dc.w -16 ; The tractor enters from the left
cartx: dc.w 300 ; The cart from the right
roverx: dc.w 160 ; The Rover mid-lane, flat out
; --- The squash beat ---
squashtimer: dc.w 0 ; Frames of stillness remaining
; --- The flock ---
lives: dc.w FLOCK_SIZE ; Sheep in hand
gameover: dc.w 0 ; 1 = the field is empty
won: dc.w 0 ; 1 = every pen is full
unpenned: dc.w 5 ; Pens still to fill
score: dc.w 0 ; Points so far
roadflag: dc.w 0 ; This sheep has crossed the road
; --- The voice ---
sndtimer: dc.w 0 ; Frames of sound remaining
sndhalf: dc.w 0 ; When to slide the pitch
sndper2: dc.w 0 ; The pitch to slide to
squarewave: dc.b 64,64,64,64,-64,-64,-64,-64 ; One cycle, eight samples
even
;══════════════════════════════════════════════════════════════
; THE BITPLANE (Chip RAM)
;══════════════════════════════════════════════════════════════
plane: ds.b ROW_BYTES*256 ; Plane 1: fence, bridge, dashes, icons
plane2: ds.b ROW_BYTES*256 ; Plane 2: the fold's residents
If It Doesn’t Work
- Silence? Audio DMA is its own enable:
$8001toDMACON(SET + AUD0EN) at trigger time — the display’s$83A0doesn’t cover it. And the wave must live in Chip RAM (data_c); Paula can’t fetch from anywhere else. - A sound that never stops?
soundtickmust run every frame and must clear the channel ($0001— CLR + AUD0EN) when the timer dies. A forgotten volume write leaves a faint whine even with DMA off. - Pops and clicks at sound boundaries? The wave is interrupted mid-cycle when a new sound steals the channel — at these volumes it reads as farmyard texture, but the experiment with longer waves will expose it. Fading volume over the last frames is the classic cure.
- Wrong pitches everywhere? Period is samples-per-step in clock ticks: PAL Paula steps at 3,546,895 ticks/sec, and our wave is 8 samples long — so frequency = 3546895 ÷ (period × 8). Check your arithmetic against a pitch you trust.
Try This
- A near-miss whistle. Unit 6’s Try This built a near-miss detector; give it a voice — short, high, quiet. Suddenly the lane taunts.
- Channel 1. Move the world’s sounds (squelch) to
AUD1LC/AUD1PER/AUD1VOL($0B0–$0B8, DMA bit 1) and keep the sheep on channel 0. Two voices, no stealing — the start of a mixer. - The win deserves a fanfare. Three notes, rising, on the
wontransition — threeplaysoundcalls won’t work (each steals the last). What’s the smallest change tosoundtickthat plays a sequence? Sketch it; Unit 17’s title music will want your answer.
What You’ve Learnt
- Paula’s model — looping samples from Chip RAM on per-channel DMA: address, length, period, volume, enable. Tones are the shortest possible sample.
- Period arithmetic — pitch = clock ÷ (period × sample length); bigger period, lower note.
- Two-note sound design — a pitch slide at halfway gives each event its emotional shape; down deflates, up delights.
- Channel stealing — one channel, newest sound wins: correct for feedback sounds, and the motivation for a second channel.
- Sound bolts onto events — clean event points from Units 3–9 made the voice a four-line addition at each site.
What’s Next
That’s Arc 1’s systems complete: the farmyard, a steerable animated sheep, deterministic traffic, collision, lives, the fold, score and sound — a whole road-crossing game. What remains for the arc is making it finish properly: levels that escalate, the clean-level bonus, and the game-over and victory beats — the polish pass where it stops being systems and becomes a thing you’d hand someone.