flat assembler
Message board for the users of flat assembler.

flat assembler > Examples and Tutorials > 64 bit Linux SDL Audio example

Author
Thread Post new topic Reply to topic
ProphetOfDoom



Joined: 08 Aug 2008
Posts: 120
Location: UK
This plays Ode to Joy by Beethoven in sine waves. It was the first 64 bit Linux program I wrote. I think it's quite well commented. Tested on Fedora and Ubuntu. You'll need the program SDL and its development package (which provides sdl-config).

Code:
format ELF64

public main

extrn 'SDL_Init' as SDL_Initqword
extrn 'SDL_Quit' as SDL_Quitqword
extrn 'SDL_OpenAudio' as SDL_OpenAudioqword
extrn 'SDL_CloseAudio' as SDL_CloseAudioqword
extrn 'SDL_PauseAudio' as SDL_PauseAudioqword
extrn 'SDL_Delay' as SDL_Delayqword

extrn 'exit' as exitqword

AUDIOS16LSB equ 0x8010
SDL_INIT_AUDIO equ 0x00000010

struc SDL_AudioSpec

        .freq dd ?
        .format dw ?
        .channels db ?
        .silence db ?
        .samples dw ?
        dw ?
        .size dd ?
        .callback dq ?
        .userdata dq ?


SAMPLE_RATE equ 44100 ; samples per second
TEMPO equ 160 ; beats per minute
CROTCHETS_PER_SECOND equ 4 * TEMPO / 60 ; quarter beats per second
CROTCHET_DURATION equ 1000 / CROTCHETS_PER_SECOND ; length of a quarter beat in ms
SAMPLES_PER_CROTCHET equ SAMPLE_RATE / CROTCHETS_PER_SECOND ; samples per quarter beat

; macro for declaration of note data
macro NOTE f,d

        local .frequency, .duration
        .frequency dq f ; floating point frequency in Hz
        .duration dq d ; duration in quarter notes


section '.data' writable

audio_desired SDL_AudioSpec

NEGATIVE_ONE dq -1.0
MAX_SIGNED_SHORT dq 32767.0 ; max signed value that can be stored in a 2 byte integer
TWO_TIMES_PI dq 6.2831
ANGLE dq 0.0 ; current input to sine function
STEP_SIZE dq 0.0 ; rate at which to step through sine function
AMPLITUDE dq 1.0 ; current volume
SAMPLES_THIS_NOTE dq 0 ; samples REMAINING for the current note
CROTCHETS_THIS_NOTE dq 0 ; current note length in quarter beats
TEMP dq 0
SAMPLE dw 0 ; storage for integer audio sample
align 8
MUSIC_PLAYING dq 1 ; controls when the program ends
MUSIC_REST dq 0 ; is the current note a rest?

NOTE_POINTER dq MUSIC ; pointer to current note

; frequencies for the notes

G2 equ 195.998
C3 equ 261.626
D3 equ 293.664
E3 equ 329.628
F3 equ 349.228
G3 equ 391.996

; music melody

MUSIC

NOTE E3,2
NOTE 0,2
NOTE E3,2
NOTE 0,2
NOTE F3,2
NOTE 0,2
NOTE G3, 2
NOTE 0,2
NOTE G3, 2
NOTE 0,2
NOTE F3, 2
NOTE 0,2
NOTE E3, 2
NOTE 0,2
NOTE D3, 2
NOTE 0,2
NOTE C3, 2
NOTE 0,2
NOTE C3, 2
NOTE 0,2
NOTE D3, 2
NOTE 0,2
NOTE E3, 2
NOTE 0,2
NOTE E3, 4
NOTE 0,2
NOTE D3, 2
NOTE D3, 6
NOTE 0, 2

NOTE E3,2
NOTE 0,2
NOTE E3,2
NOTE 0,2
NOTE F3,2
NOTE 0,2
NOTE G3, 2
NOTE 0,2
NOTE G3, 2
NOTE 0,2
NOTE F3, 2
NOTE 0,2
NOTE E3, 2
NOTE 0,2
NOTE D3, 2
NOTE 0,2
NOTE C3, 2
NOTE 0,2
NOTE C3, 2
NOTE 0,2
NOTE D3, 2
NOTE 0,2
NOTE E3, 2
NOTE 0,2
NOTE D3, 4
NOTE 0,2
NOTE C3, 2
NOTE C3, 6
NOTE 0, 2

NOTE D3, 6
NOTE 0, 2
NOTE E3, 2
NOTE 0, 2
NOTE C3, 2
NOTE 0, 2
NOTE D3, 4
NOTE E3, 2
NOTE F3, 2
NOTE E3, 2
NOTE 0, 2
NOTE C3, 2
NOTE 0, 2
NOTE D3, 4
NOTE E3, 2
NOTE F3, 2
NOTE E3, 2
NOTE 0, 2
NOTE D3, 2
NOTE 0, 2
NOTE C3, 2
NOTE 0, 2
NOTE D3, 2
NOTE 0, 2
NOTE G2, 6
NOTE 0, 2

NOTE E3,2
NOTE 0,2
NOTE E3,2
NOTE 0,2
NOTE F3,2
NOTE 0,2
NOTE G3, 2
NOTE 0,2
NOTE G3, 2
NOTE 0,2
NOTE F3, 2
NOTE 0,2
NOTE E3, 2
NOTE 0,2
NOTE D3, 2
NOTE 0,2
NOTE C3, 2
NOTE 0,2
NOTE C3, 2
NOTE 0,2
NOTE D3, 2
NOTE 0,2
NOTE E3, 2
NOTE 0,2
NOTE D3, 4
NOTE 0,2
NOTE C3, 2
NOTE C3, 6
NOTE 0, 2

dq -1.0 ; note data ends

section '.text' executable

main

push rbp
mov rbp, rsp

mov rax, 0
mov rdi, SDL_INIT_AUDIO
call SDL_Init

mov audio_desired.freq, SAMPLE_RATE
mov audio_desired.format, AUDIOS16LSB
mov audio_desired.channels, 1
mov audio_desired.silence, 0
mov audio_desired.samples, 4096
mov audio_desired.size, 8192
mov audio_desired.callback, audio_callback
mov audio_desired.userdata, 0

mov rax, 0
mov rdi, audio_desired
mov rsi, 0
call SDL_OpenAudio

; unpause audio
mov rax, 0
mov rdi, 0
call SDL_PauseAudio 

call next_note

; loop till we hit the last note
.main_loop

; small delay to avoid maxing out CPU
mov rax, 0
mov rdi, CROTCHET_DURATION
call SDL_Delay

mov rax, MUSIC_PLAYING
cmp rax, 0
jz .exit_main_loop

jmp .main_loop
.exit_main_loop

mov rax, 0
call SDL_CloseAudio

mov rax, 0
call SDL_Quit

mov rax, 0
mov rdi, 0
call exit

; SDL calls this function when it requires audio data

audio_callback

push rbx rbp r12 r13 r14 r15
sub rsp, 8

; get the number of bytes of audio samples
; divide by two because each sample comprises 2 bytes
; store in rbx

mov rax, rdx
mov rbx, 2
mov rdx, 0
idiv rbx
mov rbx, rax

mov rdi, rsi ; put audio buffer pointer in rdi

; loop to write the audio samples into the buffer

mov rcx, 0 ; initialise counter

.loop

; if there are no samples remaining to be written for the current note, progress to the next note
cmp SAMPLES_THIS_NOTE, 0
jnz .skip_next_note

push rdi rcx
call next_note
pop rcx rdi

.skip_next_note

cmp rcx, rbx ; exit the loop if we've reached the end of the audio buffer
jge .exit_loop

cmp MUSIC_REST, 1 ; if the current note is a rest, skip writing the samples
je .rest

; calculate amplitude volume
; amplitude = samples_still_unwritten / crotchets_this_note * samples_per_crotchet
mov rax, CROTCHETS_THIS_NOTE
imul rax, SAMPLES_PER_CROTCHET
mov TEMP, rax
fild TEMP
mov rsi, SAMPLES_THIS_NOTE
mov TEMP, rsi
fild TEMP
fdivrp st1, st
fstp qword AMPLITUDE
fwait

; obtain our sine value and store it in memory as a 2 byte word
; sample = sinangle * amplitude * max_signed_short
fld qword AMPLITUDE
fld qword MAX_SIGNED_SHORT
fld qword ANGLE
fsin
fmulp st1, st
fmulp st1, st
fistp word SAMPLE

; transfer the sample into the audio buffer
mov si, SAMPLE
mov word rdi + rcx * 2, si

; increase angle by step size

fld qword STEP_SIZE
fld qword ANGLE
faddp st1, st

; compare the angle to 2.0 * Pi

fcom qword TWO_TIMES_PI 
fstsw ax
fwait
sahf
jb .skip ; if the angle is less than 2.0 * Pi, skip the next instruction

fsub qword TWO_TIMES_PI ; reduce the angle by 2.0 * Pi to bring it back within the range 0.0 to 2.0 * Pi

.skip
fstp qword ANGLE ; write the new angle

.rest

dec SAMPLES_THIS_NOTE ; decrease sample counter

inc rcx ; increase offset into audio buffer

jmp .loop
.exit_loop

add rsp, 8
pop r15 r14 r13 r12 rbp rbx
ret

next_note

push rbx

; restore amplitude volume to 1.0

fld1
fstp qword AMPLITUDE

; load the new note's frequency measured in Hz and duration measured in quarter-notes
; also store the length of the note in quarter-notes, and the length of the note in samples

mov rsi, NOTE_POINTER
mov rsi, rsi + 8
mov CROTCHETS_THIS_NOTE, rsi
imul rsi, SAMPLES_PER_CROTCHET
mov SAMPLES_THIS_NOTE, rsi
mov rbx, NOTE_POINTER
mov rbx, rbx

cmp rbx, NEGATIVE_ONE; negative one marks the end of the music
jne .skip

mov MUSIC_PLAYING, 0 ; tell the main thread the program has finished

pop rbx
ret

.skip

cmp rbx, 0.0 ; if frequency is 0.0, the note is a rest so next few calculations are not needed
jz .rest

mov MUSIC_REST, 0 ; not a rest

; calculate the rate at which we need to step through the sine function's domain
; step_size = 2.0 * Pi / sample_rate / frequency
mov TEMP, rbx
fld TEMP
mov TEMP, SAMPLE_RATE
fild TEMP
fdivrp st1, st
fld TWO_TIMES_PI
fdivrp st1, st
fstp STEP_SIZE
fwait

jmp .skip_rest
.rest
mov MUSIC_REST, 1 ; note is a rest
.skip_rest

; progress to the next note

mov rbx, NOTE_POINTER
add rbx, 16
mov NOTE_POINTER, rbx

mov ANGLE, 0.0 ; reset angle for new frequency

pop rbx
ret
    


To assemble, link, run:

Code:
fasm beep.asm
gcc beep.o `sdl-config --libs` -lc
./a.out
    
Post 09 Feb 2013, 21:26
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2019, Tomasz Grysztar.

Powered by rwasa.