flat assembler
Message board for the users of flat assembler.

Index > Tutorials and Examples > 64 bit Linux SDL Audio example

Author
Thread Post new topic Reply to topic
ProphetOfDoom



Joined: 08 Aug 2008
Posts: 120
Location: UK
ProphetOfDoom 09 Feb 2013, 21:26
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_Init:qword
extrn 'SDL_Quit' as SDL_Quit:qword
extrn 'SDL_OpenAudio' as SDL_OpenAudio:qword
extrn 'SDL_CloseAudio' as SDL_CloseAudio:qword
extrn 'SDL_PauseAudio' as SDL_PauseAudio:qword
extrn 'SDL_Delay' as SDL_Delay:qword

extrn 'exit' as exit:qword

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 = sin(angle) * 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-2025, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.