Writing a MOD player

Programming the 6502 microprocessor and its relatives in assembly and other languages.
Post Reply
tomaitheous
Posts: 24
Joined: 22 Sep 2007

Writing a MOD player

Post by tomaitheous »

I've been inspired by Sockmaster's MOD player running on a 68B09E @ 1.9mhz and a CPU feed DAC (not sure if he uses an interrupt system or not).

I'm writing the player for the huc6280 @ 7.16mhz (a rockwell variant) and a six channel 5bit DAC, each with a 32 sample buffer. Setting the system timer interrupt to 2hkz, I can output 64khz channels per channel. The problem is I can't do variable frequency that way. I'm going to have to (roughly) interpolate the variable frequencies into the fixed frequency of 64khz. Do you guys have any experience with this? Interpolating a sample's frequency into a fixed frequency output?

Also, I have info on the MOD format that explains all the details I need to know but.. the note frequency has me confused. The original Amiga had a max frequency playback of 28khz, but some of the "legal" octave range into 32khz and above (C for octave 3 starts at 33khz).


-Rich
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

Post by GARTHWILSON »

I'm not familiar with MOD or the HUC6280, but I hope this will be relevant anyway.

Interpolation, if you want low granularity of frequency, requires multiplication and division which won't be fast enough, and it will still introduce a little distortion unless you pour on a ton more processing power to run transforms.

However, if you have a timer like the 6522's T1 that can generate the interrupts, you can just change the interrupt rate, on the fly, by writing a new value to the latches that feed the counters when they roll over, and then feed one sample out with each interrupt.  The ISR will be pretty simple.  The RMS jitter you'll get from interrupts with a 6502 running at 7.16MHz will give better than 7 bits of accuracy at 5kHz for a good D/A, and more accuracy at lower frequencies.  (Note that this is the analog output frequency.  The sampling rate will be much higher.)  Actually, there are tricks to further improve on that, but as is, an 8-bit converter giving basically every bit correct for inputs below $80 is better than the 5-bit converter anyway.  If you have other interrupts going at the same time, it would be good to put this one on NMI since the timing requirements are so stringent.

My 6502 interrupts primer touches on that.  For an 8-bit converter, there are lots of simple parallel-input ones like the DAC0808 which you can connect to one of the ports of the 6522, and all you have to do to update the D/A's output is to write to the port, using a single STA XXXX instruction.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
tomaitheous
Posts: 24
Joined: 22 Sep 2007

Post by tomaitheous »

GARTHWILSON wrote:
I'm not familiar with MOD or the HUC6280, but I hope this will be relevant anyway.

Interpolation, if you want low granularity of frequency, requires multiplication and division which won't be fast enough, and it will still introduce a little distortion unless you pour on a ton more processing power to run transforms.

However, if you have a timer like the 6522's T1 that can generate the interrupts, you can just change the interrupt rate, on the fly, by writing a new value to the latches that feed the counters when they roll over, and then feed one sample out with each interrupt. The ISR will be pretty simple. The RMS jitter you'll get from interrupts with a 6502 running at 7.16MHz will give better than 7 bits of accuracy at 5kHz for a good D/A, and more accuracy at lower frequencies. (Note that this is the analog output frequency. The sampling rate will be much higher.) Actually, there are tricks to further improve on that, but as is, an 8-bit converter giving basically every bit correct for inputs below $80 is better than the 5-bit converter anyway. If you have other interrupts going at the same time, it would be good to put this one on NMI since the timing requirements are so stringent.
Well, the problem is that I only have one timer and at that the smallest interval per decrement is 1024 cycles. This doesn't work for the 32 byte buffer mode of the audio unit.

There is a direct feed mode where the buffer is disabled and you directly write bytes to the appropriate port, but the timer isn't refined enough to handle the small differences between notes(sample frequency), let alone to handle all the channels. Plus the timer is limited to a max frequency of 7khz.

So I'm pretty much stuck with interpolating the samples variable frequencies into the fixed output. I should note that I don't have the option of adding/changing the hardware because user base.
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

Post by GARTHWILSON »

Depending on the amount of memory you have available, you might be able to make up a large look-up table for the interpolation in order to avoid the calculation intensiveness.  It could take a long time to calculate the table the first time, but after that the numbers are there free for the looking up.  I've done this with 128KB tables of logarithms, sines, etc., for 16-bit fixed-point and scaled-integer math.  If you don't have enough memory, do these computers have some kind of plug-in slot like the Commodore 64 did?  You could put more memory there, or a 65c22 to get the extra counter, or something like that.

(Edited 6/25/12 to add link to look-up tables web pages for super fast, accurate 16-bit math)
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
tomaitheous
Posts: 24
Joined: 22 Sep 2007

Post by tomaitheous »

I have up to 3 memory configurations available, depending on what format I'm using for the system. The first configuration is a standard cartridge setup with 2.5 megs of cart space and 8k of ram, the second is 8k ram + 256k ram + 32k I/O ram, and the third is 8k ram + 256k ram + +32k I/O ram + 2megs of I/O port ram (I/O interface has automatic incrementing/decrementing pointers). I'm aiming more towards the second configuration since it's the most popular. If I could keep the lookup table with in the 16k-32k range, that'd be great. Though the emphasis is more on speed at the moment. I'd like to get CPU resource down to 30% or lower for the MOD playback routine (on the 65CS02 @ 7.16mhz).

Anyway, I guess I should get some working code up first, then start looking for optimizations.
tomaitheous
Posts: 24
Joined: 22 Sep 2007

Post by tomaitheous »

I got the frequency scaler up and working. Just a simple fixed point math LUT. The 8bit whole number + 8bit float are the counter for reading the sample data to the DAC (or a buffer for DMA). I made a 100hz increment table from from 0khz to 32khz divided by my output frequency of 15.7khz (using the scanline interrupt).

It sounds great. I'm not sure I even need to add any sort of interpolation on top of the scaler for the overhead involved. Well, maybe for a 7khz PCM driver version I might.

I decided to switch to a 10bit output and mix all sound channels into a single channel to keep overhead down of writing to multiple DACs per interrupt.
scrottie
Posts: 1
Joined: 24 Jan 2014

Re: Writing a MOD player

Post by scrottie »

Google dropped me here when I was trying to find 6502 assembly source to a MOD file player. AtariAge had a thread, http://atariage.com/forums/topic/210972 ... miga-mods/, about mod file players for the Atari 8 bit home computers. Peeking through the contents of the Atari disk images, there are no .asm files. Boy is it silly for five or six different people to re-invent this same wheel and not share their work. Any leads appreciated.
tomaitheous
Posts: 24
Joined: 22 Sep 2007

Re: Writing a MOD player

Post by tomaitheous »

What's the target system that you're writing this player for? I ended up writing a converter and a custom MOD player. Basically re-arranging some stuffs so that they are speedier and such. The player code I wrote, is too specific to the system to really use for anything else - even if it is 65x based.

I got a lot of MOD docs from the net, and the format was pretty straight forward. If anything, it was small details like FX that were a pain. Some start on the current 'tick' and some start on the next tick. But other than that, it's pretty straight forward. I just did a straight aliasing nearest-neighbor method, since it was all interrupt driven DAC updates.
tebe6502
Posts: 2
Joined: 05 Oct 2018
Contact:

Re: Writing a MOD player

Post by tebe6502 »

Atari XE/XL, POKEY 6bit, CPU 65816 (HighMem)

sample_loop (IRQ 15KHz)

Code: Select all

irq
	.ia16

	sta regA
	stx regX

	.ia8

	lda #0
	sta.l IRQEN
	lda #1
	sta.l IRQEN


v0	lda #0
	sta.l audc1

v1	lda #0
	sta.l audc2

v2	lda #0
	sta.l audc3


; ---
; ---	AUDC 1
; ---

	clc

ist_0	lda #0
iad0_m	adc #0
	sta ist_0+1
	lda p_0c+1
iad0_s	adc #0
	bcc ext_0

	inc p_0c+2
	bne ext_0

ire0_s	lda #0
	sta p_0c+2
ire0_m	lda #0

ext_0	sta p_0c+1


; ---
; ---	AUDC 2
; ---

	clc

ist_1	lda #0
iad1_m	adc #0
	sta ist_1+1
	lda p_1c+1
iad1_s	adc #0
	bcc ext_1

	inc p_1c+2
	bne ext_1

ire1_s	lda #0
	sta p_1c+2
ire1_m	lda #0

ext_1	sta p_1c+1


; ---
; ---	AUDC 3
; ---

	clc

ist_2	lda #0
iad2_m	adc #0
	sta ist_2+1
	lda p_2c+1
iad2_s	adc #0
	bcc ext_2

	inc p_2c+2
	bne ext_2

ire2_s	lda #0
	sta p_2c+2
ire2_m	lda #0

ext_2	sta p_2c+1


; ---
; ---	AUDC 4
; ---

	clc

ist_3	lda #0
iad3_m	adc #0
	sta ist_3+1
	lda p_3c+1
iad3_s	adc #0
	bcc ext_3

	inc p_3c+2
	bne ext_3

ire3_s	lda #0
	sta p_3c+2
ire3_m	lda #0

ext_3	sta p_3c+1


p_0c	lda.l sample_start+$FFFF	; ch #1
	sta ivol0+1
p_1c	lda.l sample_start+$FFFF	; ch #2
	sta ivol1+1
p_2c	lda.l sample_start+$FFFF	; ch #3
	sta ivol2+1
p_3c	lda.l sample_start+$FFFF	; ch #4
	sta ivol3+1

	clc
ivol0	lda volume
ivol1	adc volume
ivol2	adc volume
ivol3	adc volume

	tax

	lda vol6bit,x
	sta v0+1

	lda vol6bit+$100,x
	sta v1+1

	lda vol6bit+$200,x
	sta v2+1

	.ia16

	lda.w #0
regA	equ *-2

	ldx.w #0
regX	equ *-2

	rti

main_loop (VBL)

Code: Select all

mainloop

	.ai16

	sta regA
	stx regX
	sty regY

	.ai8

	dec cnts
	seq
	jmp nmiExit

	stz patend

	ldy track_pos

*---------------------------
* track  0

i_0	;ldy #1
	lda [pat1],y
	sta i_0c+1
	and #$1f
	beq i_0c
	tax
	sta nr0
	lda adr.tivol-1,x
	sta playloop.ivol0+2

i_0c	ldx EFFECT
	beq i_0f
	cpx #$40
	bne @+
	;ldy #2
	lda [pat2],y
	sta playloop.ivol0+2
@	cpx #$c0
	bne @+
	;ldy #2
	lda [pat2],y
	sta pause
@	cpx #$80
	bne i_0f
	stx patend

i_0f	;ldy #0
	lda [pat0],y
	beq i_1
	tax
	lda tadcl-1,x
	sta playloop.iad0_m+1
	lda tadch-1,x
	sta playloop.iad0_s+1

;	lda #0
;	sta playloop.ist_0+1

	ldx nr0
	txa
	add ^sample_start-1
	sta playloop.p_0c+3

	lda adr.tstrl-1,x
	sta playloop.p_0c+1
	lda adr.tstrh-1,x
	sta playloop.p_0c+2

	lda adr.trepl-1,x
	sta playloop.ire0_m+1
	lda adr.treph-1,x
	sta playloop.ire0_s+1

* track 1

i_1	iny

	;ldy #4
	lda [pat1],y
	sta i_1c+1
	and #$1f
	beq i_1c
	tax
	sta nr1
	lda adr.tivol-1,x
	sta playloop.ivol1+2

i_1c	ldx EFFECT
	beq i_1f
	cpx #$40
	bne @+
	;ldy #5
	lda [pat2],y
	sta playloop.ivol1+2
@	cpx #$c0
	bne @+
	;ldy #5
	lda [pat2],y
	sta pause
@	cpx #$80
	bne i_1f
	stx patend

i_1f	;ldy #3
	lda [pat0],y
	beq i_2
	tax
	lda tadcl-1,x
	sta playloop.iad1_m+1
	lda tadch-1,x
	sta playloop.iad1_s+1

;	lda #0
;	sta playloop.ist_1+1

	ldx nr1
	txa
	add ^sample_start-1
	sta playloop.p_1c+3

	lda adr.tstrl-1,x
	sta playloop.p_1c+1
	lda adr.tstrh-1,x
	sta playloop.p_1c+2

	lda adr.trepl-1,x
	sta playloop.ire1_m+1
	lda adr.treph-1,x
	sta playloop.ire1_s+1

* track 2

i_2	iny

	;ldy #7
	lda [pat1],y
	sta i_2c+1
	and #$1f
	beq i_2c
	tax
	sta nr2
	lda adr.tivol-1,x
	sta playloop.ivol2+2

i_2c	ldx EFFECT
	beq i_2f
	cpx #$40
	bne @+
	;ldy #8
	lda [pat2],y
	sta playloop.ivol2+2
@	cpx #$c0
	bne @+
	;ldy #8
	lda [pat2],y
	sta pause
@	cpx #$80
	bne i_2f
	stx patend

i_2f	;ldy #6
	lda [pat0],y
	beq i_3
	tax
	lda tadcl-1,x
	sta playloop.iad2_m+1
	lda tadch-1,x
	sta playloop.iad2_s+1

;	lda #0
;	sta playloop.ist_2+1

	ldx nr2
	txa
	add ^sample_start-1
	sta playloop.p_2c+3

	lda adr.tstrl-1,x
	sta playloop.p_2c+1
	lda adr.tstrh-1,x
	sta playloop.p_2c+2

	lda adr.trepl-1,x
	sta playloop.ire2_m+1
	lda adr.treph-1,x
	sta playloop.ire2_s+1

* track 3

i_3	iny

	;ldy #10
	lda [pat1],y
	sta i_3c+1
	and #$1f
	beq i_3c
	tax
	sta nr3
	lda adr.tivol-1,x
	sta playloop.ivol3+2

i_3c	ldx EFFECT
	beq i_3f
	cpx #$40
	bne @+
	;ldy #11
	lda [pat2],y
	sta playloop.ivol3+2
@	cpx #$c0
	bne @+
	;ldy #11
	lda [pat2],y
	sta pause
@	cpx #$80
	bne i_3f
	stx patend

i_3f	;ldy #9
	lda [pat0],y
	beq i_e
	tax
	lda tadcl-1,x
	sta playloop.iad3_m+1
	lda tadch-1,x
	sta playloop.iad3_s+1

;	lda #0
;	sta playloop.ist_3+1

	ldx nr3
	txa
	add ^sample_start-1
	sta playloop.p_3c+3

	lda adr.tstrl-1,x
	sta playloop.p_3c+1
	lda adr.tstrh-1,x
	sta playloop.p_3c+2

	lda adr.trepl-1,x
	sta playloop.ire3_m+1
	lda adr.treph-1,x
	sta playloop.ire3_s+1

i_e
	lda patend
	bne i_en

	iny
	sty track_pos
	bne i_end

i_en	inc patno
	ldx patno
patmax	cpx #0
	bcc i_ens

	lda #6
	sta pause
patres	ldx #0
	stx patno

i_ens	ldy adr.ORDER,x
	sty pat0+1
	iny
	sty pat1+1
	iny
	sty pat2+1

	stz track_pos

i_end
	lda pause
	sta cnts

nmiExit
	lda.l consol
	cmp #$06
	bne skp

	lda #$2c	; bit *
	sta stop

skp

	.ia16

	lda.w #0
regA	equ *-2

	ldx.w #0
regX	equ *-2

	ldy.w #0
regY	equ *-2

	rti
Post Reply