Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ------------------------------------------------------------
- // Ultimate 64 / Ultimate-II+
- // Copy a signed 8-bit PCM sample from C64 RAM to REU,
- // then play it using Ultimate Audio channel 0.
- //
- // Very simple example player by Scan/House Designs/DESiRE
- // Richard Loerakker
- //
- // This is not a complete music player.
- // It only demonstrates the minimal steps needed to:
- //
- // 1. copy sample data from C64 memory to REU memory
- // 2. point Ultimate Audio channel 0 to that REU sample
- // 3. set length, rate, volume and pan
- // 4. start playback
- //
- // Other programmers can build on this for sample triggers,
- // drum playback, speech, tracker-style routines, or SID +
- // Ultimate Audio hybrid demos.
- //
- // Compile using Kick Assembler:
- // http://www.theweb.dk/KickAssembler/
- //
- // Sample format:
- //
- // RAW header-less
- // Mono
- // Signed 8-bit PCM
- // 8000 Hz in this example
- //
- // Using Audacity:
- //
- // Open an audio file
- // Set project/sample rate to 8000 Hz
- // Convert audio to mono
- // Optionally apply normalize/compressor/etc.
- // Select the audio you want to export
- // File -> Export Audio
- // Format: Other uncompressed files
- // Header: RAW (header-less)
- // Encoding: Signed 8-bit PCM
- //
- // Notes:
- //
- // Ultimate Audio 8-bit PCM expects signed sample data.
- // Do not use unsigned 8-bit PCM unless you convert it first.
- //
- .var sampleBin = LoadBinary("sample.raw")
- .const SAMPLE_LEN = sampleBin.getSize()
- .if (SAMPLE_LEN > $c000) {
- .error "This sample is too big, use a smaller one"
- }
- // ------------------------------------------------------------
- // IMPORTANT:
- //
- // Ultimate Audio sample rate is derived from a divider.
- // Bigger divider = slower playback.
- // Smaller divider = faster playback.
- //
- // Approximate values from 6.25 MHz:
- //
- // 4000 Hz -> 1563
- // 8000 Hz -> 781
- // 11025 Hz -> 567
- // 16000 Hz -> 391
- // 22050 Hz -> 283
- // 44100 Hz -> 142
- // 48000 Hz -> 130
- //
- // If your sample plays 2x too fast, double this value.
- // If it plays 4x too fast, multiply by 4.
- //
- // ------------------------------------------------------------
- .const SAMPLE_RATE_DIVIDER = 781 // 8000 Hz-ish
- // ------------------------------------------------------------
- // REU registers
- // ------------------------------------------------------------
- .const REU_STATUS = $DF00
- .const REU_COMMAND = $DF01
- .const REU_C64ADDR = $DF02 // low, high
- .const REU_REUADDR = $DF04 // low, high, bank
- .const REU_LENGTH = $DF07 // low, high
- .const REU_IRQMASK = $DF09
- .const REU_ADDRCTRL = $DF0A
- .const REU_CMD_C64_TO_REU_IMMEDIATE = %10010000
- // bit 7 = execute
- // bit 4 = immediate
- // bits 1-0 = 00, C64 -> REU
- // ------------------------------------------------------------
- // Ultimate Audio channel 0
- // ------------------------------------------------------------
- .const UA0_BASE = $DF20
- .const UA0_CONTROL = UA0_BASE + $00
- .const UA0_VOLUME = UA0_BASE + $01
- .const UA0_PAN = UA0_BASE + $02
- .const UA0_START = UA0_BASE + $04 // 4 bytes, big endian
- .const UA0_LENGTH = UA0_BASE + $09 // 3 bytes, big endian
- .const UA0_RATE = UA0_BASE + $0E // 2 bytes, big endian
- .const UA0_IRQCLEAR = UA0_BASE + $1F
- .const UA_GATE = %00000001
- .const UA_REPEAT = %00000010
- .const UA_IRQ = %00000100
- .const UA_MODE_8BIT = %00000000
- .const UA_MODE_16BIT = %00010000
- // ------------------------------------------------------------
- // BASIC start
- // ------------------------------------------------------------
- * = $0801 "BASIC"
- :BasicUpstart2(start)
- * = $0810 "Code"
- start:
- sei
- // Make sure I/O is visible at $D000-$DFFF, swap out BASIC ROM
- lda #$36
- sta $01
- // Put Ultimate Audio into a known quiet state
- jsr ua_init_silent
- // Copy sample from C64 RAM to REU
- jsr copy_sample_to_reu
- // Play it
- jsr play_sample_from_reu
- cli
- rts
- // ------------------------------------------------------------
- // Clear Ultimate Audio register area $DF20-$DFFF
- //
- // This deliberately does NOT touch $DF00-$DF1F,
- // because that area contains the REU registers.
- // ------------------------------------------------------------
- ua_init_silent:
- lda #$00
- ldx #$20
- clear_loop:
- sta $DF00,x
- inx
- bne clear_loop
- // Give the audio core a tiny moment to see gate=0 everywhere
- jsr ua_short_delay
- rts
- // ------------------------------------------------------------
- // Stop channel 0 cleanly
- // ------------------------------------------------------------
- ua_stop_ch0:
- lda #$00
- sta UA0_CONTROL
- lda #$ff
- sta UA0_IRQCLEAR
- jsr ua_short_delay
- rts
- // ------------------------------------------------------------
- // Small delay after gate-off
- // Not beautiful. Does the job. Humanity survives another day.
- // ------------------------------------------------------------
- ua_short_delay:
- ldx #$08
- outer:
- ldy #$00
- inner:
- dey
- bne inner
- dex
- bne outer
- rts
- // ------------------------------------------------------------
- // Copy embedded sample from C64 RAM to REU address $000000
- // ------------------------------------------------------------
- copy_sample_to_reu:
- // Clear old REU status, just in case
- lda REU_STATUS
- // Let both C64 and REU addresses increment during DMA
- lda #$00
- sta REU_ADDRCTRL
- // No REU IRQs
- lda #$00
- sta REU_IRQMASK
- // C64 source address = sample_data
- lda #<sample_data
- sta REU_C64ADDR + 0
- lda #>sample_data
- sta REU_C64ADDR + 1
- // REU destination address = $000000
- lda #$00
- sta REU_REUADDR + 0 // REU addr low
- sta REU_REUADDR + 1 // REU addr high
- sta REU_REUADDR + 2 // REU bank
- // Transfer length, little endian for REU
- lda #<SAMPLE_LEN
- sta REU_LENGTH + 0
- lda #>SAMPLE_LEN
- sta REU_LENGTH + 1
- // Execute C64 -> REU DMA immediately
- lda #REU_CMD_C64_TO_REU_IMMEDIATE
- sta REU_COMMAND
- // Turn back on BASIC ROM
- lda #$37
- sta $01
- // Clear/read status after DMA
- lda REU_STATUS
- rts
- // ------------------------------------------------------------
- // Play sample from REU address $000000 using Ultimate Audio ch0
- // ------------------------------------------------------------
- play_sample_from_reu:
- // Force clean gate-off before programming/retriggering
- jsr ua_stop_ch0
- // Volume: 0..63
- lda #$3f
- sta UA0_VOLUME
- // Pan: try center-ish
- lda #$07
- sta UA0_PAN
- // Sample start address is 4 bytes, big endian.
- //
- // REU address $000000 is addressed by Ultimate Audio as:
- // $01 00 00 00
- lda #$01
- sta UA0_START + 0
- lda #$00
- sta UA0_START + 1
- sta UA0_START + 2
- sta UA0_START + 3
- // Sample length is 3 bytes, big endian.
- // For this test SAMPLE_LEN is < 64K, so top byte is 0.
- lda #$00
- sta UA0_LENGTH + 0
- lda #>SAMPLE_LEN
- sta UA0_LENGTH + 1
- lda #<SAMPLE_LEN
- sta UA0_LENGTH + 2
- // Sample rate divider is 2 bytes, big endian.
- lda #>SAMPLE_RATE_DIVIDER
- sta UA0_RATE + 0
- lda #<SAMPLE_RATE_DIVIDER
- sta UA0_RATE + 1
- // Clear pending channel IRQ/status
- lda #$ff
- sta UA0_IRQCLEAR
- // Start one-shot 8-bit sample:
- // gate=1, repeat=0, irq=0, mode=8-bit
- lda #(UA_GATE | UA_MODE_8BIT)
- sta UA0_CONTROL
- rts
- // ------------------------------------------------------------
- // Embedded sample data
- // ------------------------------------------------------------
- * = $1000 "Sample data"
- sample_data:
- .fill sampleBin.getSize(), sampleBin.get(i)
- sample_end:
Advertisement