2600 Cookbook: Music Basics

Problem:
You'd like some music in your Atari program.

Solution:
Set AUDC0 or AUDC1 to the appropriate voice, AUDF0 or AUDF1 to the right pitch, and AUDV0 or AUDV1 to the correct volume. Update as neccesary. :-)

Discussion:
Atari music is a bit odd. It has certain quirks that make it a bit difficult to transcribe "real" music into it, but it does a distinctive sound all its own.

You have two voices to play with, and these voices are pretty much all you have to deal with any music and any special effects in your game.

At any given time, the rightmost 4 bits of each audio control register (AUDC0 or AUDC1) determine what "kind" of noise tha voice makes. The 16 possible settings have some "pure tone" and some "noise" settings. Often, experimenting is the best way to find the right sound for what you're trying to do. Here is the table from SPG:
D3 D2 D1 D0 Type of noise or division
0000set to 1
00014 bit poly
0010div 15 -> 4 bit poly
00115 bit poly -> 4 bit poly
0100div 2 : pure tone
0101div 2 : pure tone
0110div 31 : pure tone
01115 bit poly -> div 2
10009 bit poly (white noise)
10015 bit poly
1010div 31 : pure tone
1011set last 4 bits to 1
1100div 6 : pure tone
1101div 6 : pure tone
1110div 93 : pure tone
11115 bit poly div 6


The rightmost 4 bits of AUDV0 and AUDV1 determine the volume: 0000 is off, 1111 is the loudest.

The frequency registers, AUDF0 and AUDF1, determine the pitch. The challenge is that the tuning doesn't quite line up with typical (well, western) music, because you're starting with a base tone (as set by AUDC0/1) and then dividing that between 1 and 32. (You set the 5 rightmost bits of AUDF0/1, and this value is one less than what the base frequency will be divided by.

UNDER CONSTRUCTION: NEED MORE INFO ON CALCULATING FREQUENCIES --WAS THERE A PROGRAM FOR THIS??

For NTSC, you get 60 frames a second. It's fairly straight forward to build an engine that runs every grame and updates the audio registers as neccesary. This Recipe's Example code is a simplified, not-optimized engine to do just that.

References:
Example:
;
; simple music playing routine
; by Kirk Israel
; prototype for JoustPong title screen


	processor 6502
	include vcs.h
	include macro.h
	org $F000

	SEG.U VARS 
	ORG $80

;each voice uses 4 bytes of RAM
;set aside and label memory

musicRiffNotePointer ds 2
musicRiffNoteCounter ds 1
musicRiffNoteTimeLeft ds 1

musicBeatNotePointer ds 2
musicBeatNoteCounter ds 1
musicBeatNoteTimeLeft ds 1


;music has 16 notes, beat has 12...
;(actually 8 notes, 8 silences, 6 notes, 6 silences...)
MUSICRIFF_NOTECOUNT = #16
MUSICBEAT_NOTECOUNT = #12


	SEG CODE
	org $F000


;generic start up stuff...
Start
	CLEAN_START

	
	LDA #$00   ;start with a black background
	STA COLUBK	

	LDA #14
	STA COLUPF
	
	;music riff uses bassy sound
	LDA #10
	STA AUDC0

	;beat uses percussive sound
	LDA #8
	STA AUDC1

;VSYNC time
MainLoop
	VERTICAL_SYNC
	LDA #43	
	STA TIM64T	


	;musicRiffNoteTimeLeft holds how much
	;time the current note is being held for.
	;when it hits zero, it's time to start 
	;playing the next note
	
	DEC musicRiffNoteTimeLeft
	BPL DoneWithChangingNote

	;if we're here, we're changing notes
	;musicRiffNoteCounter is which note we're on.

	DEC musicRiffNoteCounter
	BPL DoneCheckResetNoteCounter
	
	;If it's zero, we need to reset the counter... 

	LDA #MUSICRIFF_NOTECOUNT-1
	STA musicRiffNoteCounter

DoneCheckResetNoteCounter
	;we're changing the pitch being played
	;put the note counter into Y and use it as an offset
	;into some tables...
	LDY musicRiffNoteCounter

	;load how long we're holding this note from table, store in musicRiffNoteTimeLeft 
	LDA MusicLengthData,Y
	STA musicRiffNoteTimeLeft
	DEC musicRiffNoteTimeLeft ;correct for an 'off by one error'
	
	;load the pitch of this note from table
	LDA MusicPitchData,Y 
	
	;-1 = we're resting, not playing
	BMI ZeroOutSound
	
	;put note pitch into AUDF0
	STA AUDF0
	;...at a sensible volume
	LDA #8 
	STA AUDV0 
	JMP DoneSettingPitchAndVolume
ZeroOutSound 
	;freq. was negative, silence out
	LDA #0	;silence
	STA AUDV0 
DoneSettingPitchAndVolume

DoneWithChangingNote


	;do the same thing for the the beat....


	DEC musicBeatNoteTimeLeft
	BPL DoneWithChangingBeat

	DEC musicBeatNoteCounter
	BPL DoneCheckResetBeatCounter

	LDA #MUSICBEAT_NOTECOUNT-1
	STA musicBeatNoteCounter

DoneCheckResetBeatCounter
	LDY musicBeatNoteCounter
	
	LDA BeatLengthData,Y
	STA musicBeatNoteTimeLeft
	
	DEC musicBeatNoteTimeLeft
	
	LDA BeatPitchData,Y 
	
	BMI ZeroOutBeatSound

	STA AUDF1
	LDA #8
	STA AUDV1
	JMP DoneSettingBeatPitchAndVolume
ZeroOutBeatSound	
	LDA #0
	STA AUDV1
DoneSettingBeatPitchAndVolume
DoneWithChangingBeat

	;for diagnostic reasons to track down an off by
	;one error, I blasted the counter of how many frames
	;left for the current sound to the playfield 
	;then slowed z26 down to a super slow speed:
	;      z26 -r4 
	; or somesuch
	; then I kept it in just for something to look at :-)
	
	LDA musicRiffNoteTimeLeft
	STA PF1

	LDA musicBeatNoteTimeLeft
	STA PF2



WaitForVblankEnd
	LDA INTIM	
	BNE WaitForVblankEnd	
	LDY #191 	

	STA WSYNC	
	STA VBLANK  	
;main scanline loop...
ScanLoop 
	STA WSYNC 	
	DEY		
	BNE ScanLoop	

	LDA #2		
	STA WSYNC  	
	STA VBLANK 	
	LDX #30		
OverScanWait
	STA WSYNC
	DEX
	BNE OverScanWait
	JMP  MainLoop      


; music data is stored "backwards"
; pitch of -1 = silence
; length is given in frames

MusicPitchData
	.byte #-1
	.byte #17
	.byte #-1
	.byte #17
	.byte #-1
	.byte #16
	.byte #-1
	.byte #16
	.byte #-1
	.byte #15
	.byte #-1
	.byte #15
	.byte #-1
	.byte #18
	.byte #-1
	.byte #18



MusicLengthData
	.byte #40
	.byte #20
	.byte #10
	.byte #26
	.byte #40
	.byte #20
	.byte #10
	.byte #26
	.byte #40
	.byte #20
	.byte #10
	.byte #26
	.byte #40
	.byte #20
	.byte #10
	.byte #26





BeatPitchData
	.byte #-1
	.byte #120
	.byte #-1
	.byte #40
	.byte #-1
	.byte #120
	.byte #-1
	.byte #120
	.byte #-1
	.byte #40
	.byte #-1
	.byte #120
	
BeatLengthData
	.byte #16
	.byte #2
	.byte #4
	.byte #2
	.byte #10
	.byte #2
	.byte #22
	.byte #2
	.byte #10
	.byte #2
	.byte #22
	.byte #2


	org $FFFC
	.word Start
	.word Start


Back to 2600 Cookbook