Jump to content

Photo

My Music Engine


7 replies to this topic

#1 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • 286 posts
  • Location:Virginia, USA

Posted Sat Apr 8, 2017 2:05 PM

We're putting the band back together!

 

I've never attempted to write a music engine or really look into one.  It wasn't that hard but I bet someone knows a more efficient and/or fancy way to do it.   (Yes I know my table is inside the loop but its just easier to look at that way for now.  Also I snagged the note values from Kirk Israel) I haven't quite figured out why I get a high pitched beep when the song repeats either.

MusicRoutine:
	dec NoteDuration	; Have we gone 30 or 60 frames yet?
	bne OSwait		; If not, then skip to the end
	inc NoteCounter		; Go to the next note
	inc NoteCounter		; Twice because durations are in the table as well
	ldx NoteCounter		; Put the note location in the index
	lda Tune,x			; Load the frequency
	sta AUDF0			; Store the frequency
	lda Tune,x+1		; Load the duration
	sta NoteDuration	; Store the new duration in RAM
	lda NoteCounter		; See if its time to repeat the song
	cmp #72				; 36 notes plus 36 note durations
	beq ResetCounter	; if so, reset the counter
	jmp OSwait			; Don't reset the counter
	
ResetCounter
	lda #0
	sta NoteCounter
	jmp OSwait
	
Tune: 					; 35 notes and pauses
	.byte 0,0,24,30,27,30,30,30,27,30,24,28,-1,2,24,28,-1,2,24,60 	;9 notes
	.byte 27,28,-1,2,27,28,-1,2,27,60,24,30,20,28,-1,2,20,60 		;9
	.byte 24,30,27,30,30,30,27,30,24,28,-1,2,24,28,-1,2,24,28,-1,2 	;10
	.byte 24,30,27,28,-1,2,27,30,24,30,27,30,30,120 				;7

OSwait:

Attached Files



#2 Kylearan OFFLINE  

Kylearan

    Chopper Commander

  • 201 posts

Posted Thu Apr 13, 2017 4:51 AM

I haven't quite figured out why I get a high pitched beep when the song repeats either.

 

You're loading and storing frequency and duration values based on NoteCounter before testing if it has reached #72. That means your routine will use values #72 and #73 from your table as well, which are no longer valid tune values but in fact are the values of the "sta WSYNC" immediately following the table (which is $85 $02, resulting in a frequency value of $85 played for 2 frames).

 

Check for #70 instead and it should work. Or better yet, do the check before you fetch tune data and you should be able to get rid of the two leading 0 bytes in your data as well.

 

(Or so I think. Haven't tested it. :-))


Edited by Kylearan, Thu Apr 13, 2017 4:52 AM.


#3 Derek Andrews OFFLINE  

Derek Andrews

    Space Invader

  • 25 posts
  • Location:Nova Scotia

Posted Wed Apr 19, 2017 6:43 PM

I have something very similar that I have written for the Interton family of games in Signetics 2650. A couple of enhancements though. I set up equates to make it easier to convert the music to code:

 

midD        equ    $1A       ; tones
midE        equ    $17
midF        equ    $15
midG        equ    $13
midA        equ    $11
midB        equ    $0F
silent        equ    0
crotchet    equ    12             ; durations
minim        equ    24
inter        equ    3
musicend    equ    $ff           ;stop
 

'inter' is a short pause I insert between notes.

'music end' is used by the subroutine to see when the tune has ended.

It is also possible to pass the start address of the music data to the subroutine via an indirect RAM address. Thus I would have a standalone subroutine that simply needs the music code in a block of data with a 'musicend' byte to indicate its termination. This could be one of many tunes. Simply set the address of the block of music you want to play in to RAM and call the subroutine.

 

This is what the music looks like:

 

twinkle:
    db    silent
    db    inter
    db    midD
    db    crotchet
    db    silent
    db    inter
    db    midD
    db    crotchet
    db    silent
    db    inter    
    db    midA
    db    crotchet ......

 

... db    minim
    db    silent
    db    musicend
 



#4 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • Topic Starter
  • 286 posts
  • Location:Virginia, USA

Posted Thu Apr 20, 2017 8:14 AM

 

You're loading and storing frequency and duration values based on NoteCounter before testing if it has reached #72. That means your routine will use values #72 and #73 from your table as well, which are no longer valid tune values but in fact are the values of the "sta WSYNC" immediately following the table (which is $85 $02, resulting in a frequency value of $85 played for 2 frames).

 

Check for #70 instead and it should work. Or better yet, do the check before you fetch tune data and you should be able to get rid of the two leading 0 bytes in your data as well.

 

(Or so I think. Haven't tested it. :-))

 

That was pretty much what was going on- thanks...  But to fix it, what I did was load NoteCounter into the index register (x) before I increment NoteCounter twice (instead of the other way around)  which allowed me to get rid of those two zeros and check for #70 instead of #72 as well.

 

I still had a split second high pitched beep when I would first launch it which I believe was an effect of initializing the volume level at $7 at the start of the program.  So I also initialized AUDF0, with the first note of the song so its now unnoticeable.

 

Which brings me to the next question...  Using this method, there seems to be that there are no frequency values that would make true silence between notes- correct?  (The separations I have between identical notes is actually a different note, not a break.)

Attached Files



#5 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • Topic Starter
  • 286 posts
  • Location:Virginia, USA

Posted Thu Apr 27, 2017 5:51 AM

I have something very similar that I have written for the Interton family of games in Signetics 2650. A couple of enhancements though. I set up equates to make it easier to convert the music to code:

 

midD        equ    $1A       ; tones
midE        equ    $17
midF        equ    $15
midG        equ    $13
midA        equ    $11
midB        equ    $0F
silent        equ    0
crotchet    equ    12             ; durations
minim        equ    24
inter        equ    3
musicend    equ    $ff           ;stop
 

'inter' is a short pause I insert between notes.

'music end' is used by the subroutine to see when the tune has ended.

It is also possible to pass the start address of the music data to the subroutine via an indirect RAM address. Thus I would have a standalone subroutine that simply needs the music code in a block of data with a 'musicend' byte to indicate its termination. This could be one of many tunes. Simply set the address of the block of music you want to play in to RAM and call the subroutine.

 

Cool I think I'll do something like this.  Took me a minute to figure out your British music terms..


Edited by BNE Jeff, Thu Apr 27, 2017 5:55 AM.


#6 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • Topic Starter
  • 286 posts
  • Location:Virginia, USA

Posted Sat Apr 29, 2017 1:15 PM

OK..  I created a digital version that is working, but is way,way out of tune.  I've re-checked my math but can't quite figure out where I went wrong,

 

Well commented .asm attached if anyone wants to take a look.. ;-)

; Digital Mary Had a Little Lamb
; 
; 29 April 2017

; Mary Had A Little Lamb
; EDCDEEE
; DDDEGG
; EDCDEEE
; EDDEDC

; dasm MHALL.asm -f3 -v0 -sMHALL.sym -lMHALL.lst -oMHALL.bin
; Attempt to produce a simple in-tune song

    PROCESSOR 6502
    
    include vcs.h      
    
;==============================================================================
; Define Notes
;==============================================================================

C_FIVE    = 141  ; C5 is 523.25 Hz. 6507 runs at 1,193,333 Hz.
		; 1,193,333/523.25= 2280.6 machine cycles
		; Divide by 2 for each half of the square wave
		; equals 1140.3 machine cycles
		; the wait loops are 8 cycles which means I need to loop
		; through approximately 142 times for each half wave
		; Subtract 1 for the 5 cycle delay in initial AUDV0 loadings
D_FIVE    = 125  ; 
E_FIVE    = 112  ; 
F_FIVE    = 105  ; 
G_FIVE    = 94   ; 
A_FIVE    = 83   ; 
B_FIVE    = 74   ; 

END_SONG  = 0	 ;

WHOLE_NOTE = 255
HALF_NOTE = 128
QUARTER_NOTE = 64

 
    SEG.U VARS
    
    ORG $80             

    ; Start of Cartridge
    ; Tell DASM to start here. RAM begins at $80
    
Hold5:			ds 1	; Used to hold wave high or low
CurrentNoteDuration: 	ds 1	; Quarter note, half note, etc
CurrentNote:		ds 1	; Which note the song is on

    ; define the segment for code
    SEG CODE    
    
    ; 2K ROM starts at $F800, 4K ROM starts at $F000
    ORG $F800
    
     
InitSystem:

	sei			; Set Interrupt
	cld  			; Clear the decimal bit.
	ldx #$FF		; Start at the top of the stack
	txs			; Transfer to the stack
	lda #0		
ClearMem:
	sta 0,X		; Store zero at (0+X)
	dex		; Do all of RAM
	bne ClearMem	; Repeat if we are not down to zero

;==============================================================================
; Load Tune
;==============================================================================

LoadTune:
	lda Song,y		; Load the current note of the song (half wave value)
	sta Hold5		; Store it in RAM
	lda Song,y+1		; Load the duration of the note
	sta CurrentNoteDuration ; Store it in RAM
           
Tone:
	lda	#$0F          ; 2 set volume high for high part of square wave
    sta AUDV0           ; 3
;==============================================================================
; Wave High
;==============================================================================
WaitHigh:			; Loop to hold high part of wave
	dec Hold5		; 5 
	bne WaitHigh		; 3 8 8 machine cycles per loop through
WaitHighFine:			; Future use for fine tuning notes with a 5 cycle loop
	;dex			; Future use for fine tuning notes with a 5 cycle loop
	;bne WaitHighFine	; 
	lda Song,y		; 4 Re-load the current note of the song
	sta Hold5		; 3 Store it in RAM for low half of the wave

;==============================================================================
; Wave Low
;==============================================================================
Interval:
    lda #$00            ; 2 Turn off volume for bottom of square wave
    sta AUDV0           ; 3
WaitLow:				; 
	dec Hold5			; 5
	bne WaitLow			; 3 8 Holds 8 with the branch taken
WaitLowFine:
	;dex				; Future use
	;bne WaitLowFine		; 
	
;==============================================================================
; Next CurrentNote?
;==============================================================================
	dec CurrentNoteDuration ; 5
	bne Tone		; 3
;==============================================================================
; Get Next CurrentNote
;==============================================================================	
	inc CurrentNote			; Increment to next note
	inc CurrentNote			; Again to skip over duration
	lda CurrentNote
	tay				; Put it in index
	lda Song,y			; Load the note
	beq StartOver			; if END_SONG then go to reset
	sta Hold5			; otherwise, store in RAM
	lda Song,y+1			; load the note duration
	sta CurrentNoteDuration	        ; Store it in RAM
    jmp LoadTune			; Back to the top
    
StartOver:
	ldy #0
	sty CurrentNote
	sty Hold5
	sty CurrentNoteDuration
	jmp LoadTune
    
Song:
    byte E_FIVE,HALF_NOTE,D_FIVE,HALF_NOTE,C_FIVE,HALF_NOTE
    byte D_FIVE,HALF_NOTE,E_FIVE,HALF_NOTE,E_FIVE,HALF_NOTE,E_FIVE,WHOLE_NOTE
    byte D_FIVE,HALF_NOTE,D_FIVE,HALF_NOTE,D_FIVE,WHOLE_NOTE
    byte E_FIVE,HALF_NOTE,G_FIVE,HALF_NOTE,G_FIVE,WHOLE_NOTE
    byte E_FIVE,HALF_NOTE,D_FIVE,HALF_NOTE,C_FIVE,HALF_NOTE	
    byte D_FIVE,HALF_NOTE,E_FIVE,HALF_NOTE,E_FIVE,HALF_NOTE,E_FIVE,HALF_NOTE
    byte E_FIVE,HALF_NOTE,D_FIVE,HALF_NOTE,D_FIVE,HALF_NOTE	
    byte E_FIVE,HALF_NOTE,D_FIVE,HALF_NOTE,C_FIVE,WHOLE_NOTE	
    byte END_SONG


; End of Cartridge


    ORG $FFFA        ; set address to 6507 Interrupt Vectors 
    .WORD InitSystem ; NMI
    .WORD InitSystem ; RESET
    .WORD InitSystem ; IRQ

Attached Files



#7 RevEng OFFLINE  

RevEng

    River Patroller

  • 4,669 posts
  • Bitnik
  • Location:Canada

Posted Sat Apr 29, 2017 5:05 PM

This isn't valid syntax: "lda Song,y+1"

Use this instead: "lda Song+1,y"

#8 BNE Jeff OFFLINE  

BNE Jeff

    Moonsweeper

  • Topic Starter
  • 286 posts
  • Location:Virginia, USA

Posted Sat Apr 29, 2017 5:34 PM

Thanks!  I changed it, but strangely- it didn't make a difference.






0 user(s) are browsing this forum

0 members, 0 guests, 0 anonymous users