Added x86 asm and C wav writer and player examples.

Specifically:
* Added win32, elf32 and elf64 asm player and wav writers using winmm.
* Added dsound player in C.
* Separated the ALL target and the examples; introduced a new examples target.
This commit is contained in:
Alexander Kraus 2023-08-28 23:54:04 +02:00 committed by Veikko Sariola
parent a439a4fa48
commit 607e5b5da0
17 changed files with 886 additions and 34 deletions

View File

@ -167,9 +167,15 @@ wat2wasm --enable-bulk-memory test_chords.wat
#### Examples
The folder `examples/code` contains usage examples in C. If you want to target smaller executable sizes, using a compressing linker
The folder `examples/code` contains usage examples for Sointu with winmm und dsound playback under Windows and asound playback under Unix. Source code is available in C and x86 assembly (win32, elf32 and elf64 versions).
To build the examples, use `ninja examples`.
If you want to target smaller executable sizes, using a compressing linker
like [Crinkler](https://github.com/runestubbe/Crinkler) on Windows is recommended.
The linux examples use ALSA and need libasound2-dev (or libasound2-dev:386) installed. The 386 version also needs pipewire-alsa:386 installed, which is not there by default.
### Native virtual machine
The native bridge allows Go to call the sointu compiled x86 native

View File

@ -1,34 +1,68 @@
# this fixes a bug in creating a static library from asm, similar to
# https://discourse.cmake.org/t/building-lib-file-from-asm-cmake-bug/1959
# but for NASM
if(MSVC)
set(CMAKE_ASM_NASM_CREATE_STATIC_LIBRARY "<CMAKE_AR> /OUT:<TARGET> <LINK_FLAGS> <OBJECTS>")
endif()
add_custom_command(
COMMAND
${compilecmd} -arch=${arch} -o physics_girl_st.asm "${PROJECT_SOURCE_DIR}/examples/patches/physics_girl_st.yml"
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
"${PROJECT_SOURCE_DIR}/examples/patches/physics_girl_st.yml"
OUTPUT
physics_girl_st.asm
physics_girl_st.h
physics_girl_st.inc
COMMENT
"Compiling ${PROJECT_SOURCE_DIR}/examples/patches/physics-girl-st.yml..."
COMMAND
${compilecmd} -arch=${arch} -o physics_girl_st.asm "${PROJECT_SOURCE_DIR}/examples/patches/physics_girl_st.yml"
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
"${PROJECT_SOURCE_DIR}/examples/patches/physics_girl_st.yml"
OUTPUT
physics_girl_st.asm
physics_girl_st.h
physics_girl_st.inc
COMMENT
"Compiling ${PROJECT_SOURCE_DIR}/examples/patches/physics-girl-st.yml..."
)
add_library(physics_girl_st physics_girl_st.asm)
add_dependencies(physics_girl_st sointu-compiler)
if(WIN32)
add_executable(cplay
cplay.windows.c
physics_girl_st.h
)
target_link_libraries(cplay PRIVATE winmm)
add_executable(cplay-winmm
cplay.windows.winmm.c
physics_girl_st.h
)
target_link_libraries(cplay-winmm PRIVATE winmm)
target_link_libraries(cplay-winmm PRIVATE physics_girl_st)
target_include_directories(cplay-winmm PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
add_dependencies(examples cplay-winmm)
add_executable(cplay-directsound
cplay.windows.directsound.c
physics_girl_st.h
)
target_link_libraries(cplay-directsound PRIVATE dsound ws2_32 ucrt)
target_link_libraries(cplay-directsound PRIVATE physics_girl_st)
target_include_directories(cplay-directsound PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
add_dependencies(examples cplay-directsound)
elseif(UNIX)
add_executable(cplay
cplay.unix.c
physics_girl_st.h
)
target_link_libraries(cplay PRIVATE asound pthread)
target_link_options(cplay PRIVATE -z noexecstack -no-pie)
add_executable(cplay
cplay.unix.c
physics_girl_st.h
)
target_link_libraries(cplay PRIVATE asound pthread)
target_link_options(cplay PRIVATE -z noexecstack -no-pie)
target_link_libraries(cplay PRIVATE physics_girl_st)
target_include_directories(cplay PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
add_dependencies(examples cplay)
endif()
target_link_libraries(cplay PRIVATE physics_girl_st)
target_include_directories(cplay PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
add_executable(cwav
cwav.c
physics_girl_st.h
)
if(WIN32)
target_compile_definitions(cwav PRIVATE _CRT_SECURE_NO_WARNINGS)
elseif(UNIX)
target_link_options(cwav PRIVATE -z noexecstack -no-pie)
endif()
target_link_libraries(cwav PRIVATE physics_girl_st)
target_include_directories(cwav PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
add_dependencies(examples cwav)

View File

@ -20,7 +20,19 @@ int main(int argc, char **args) {
// Play the track.
snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
snd_pcm_set_params(pcm_handle, SND_PCM_FORMAT_FLOAT, SND_PCM_ACCESS_RW_INTERLEAVED, SU_CHANNEL_COUNT, SU_SAMPLE_RATE, 0, SU_LENGTH_IN_SAMPLES);
snd_pcm_set_params(
pcm_handle,
#ifdef SU_SAMPLE_FLOAT
SND_PCM_FORMAT_FLOAT,
#else // SU_SAMPLE_FLOAT
SND_PCM_FORMAT_S16_LE,
#endif // SU_SAMPLE_FLOAT
SND_PCM_ACCESS_RW_INTERLEAVED,
SU_CHANNEL_COUNT,
SU_SAMPLE_RATE,
0,
SU_LENGTH_IN_SAMPLES
);
snd_pcm_writei(pcm_handle, sound_buffer, SU_LENGTH_IN_SAMPLES);
return 0;

View File

@ -0,0 +1,75 @@
#include <stdio.h>
#include <stdint.h>
#include "physics_girl_st.h"
#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN
#include <Windows.h>
#include "mmsystem.h"
#include "mmreg.h"
#define CINTERFACE
#include <dsound.h>
#ifndef DSBCAPS_TRUEPLAYPOSITION // Not defined in MinGW dsound headers, so let's add it
#define DSBCAPS_TRUEPLAYPOSITION 0x00080000
#endif
SUsample sound_buffer[SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT];
WAVEFORMATEX wave_format = {
#ifdef SU_SAMPLE_FLOAT
WAVE_FORMAT_IEEE_FLOAT,
#else
WAVE_FORMAT_PCM,
#endif
SU_CHANNEL_COUNT,
SU_SAMPLE_RATE,
SU_SAMPLE_RATE * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT,
SU_SAMPLE_SIZE * SU_CHANNEL_COUNT,
SU_SAMPLE_SIZE*8,
0
};
DSBUFFERDESC buffer_description = {
sizeof(DSBUFFERDESC),
DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_TRUEPLAYPOSITION,
SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT,
0,
&wave_format,
0
};
int main(int argc, char **args) {
// Load gm.dls if necessary.
#ifdef SU_LOAD_GMDLS
su_load_gmdls();
#endif // SU_LOAD_GMDLS
HWND hWnd = GetForegroundWindow();
if(hWnd == NULL) {
hWnd = GetDesktopWindow();
}
LPDIRECTSOUND direct_sound;
LPDIRECTSOUNDBUFFER direct_sound_buffer;
DirectSoundCreate(0, &direct_sound, 0);
IDirectSound_SetCooperativeLevel(direct_sound, hWnd, DSSCL_PRIORITY);
IDirectSound_CreateSoundBuffer(direct_sound, &buffer_description, &direct_sound_buffer, NULL);
LPVOID p1;
DWORD l1;
IDirectSoundBuffer_Lock(direct_sound_buffer, 0, SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE, &p1, &l1, NULL, NULL, 0);
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)su_render_song, p1, 0, 0);
IDirectSoundBuffer_Play(direct_sound_buffer, 0, 0, 0);
// We need to handle windows messages properly while playing, as waveOutWrite is async.
MSG msg = {0};
DWORD last_play_cursor = 0;
for(DWORD play_cursor = 0; play_cursor >= last_play_cursor; IDirectSoundBuffer_GetCurrentPosition(direct_sound_buffer, (DWORD*)&play_cursor, NULL)) {
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
last_play_cursor = play_cursor;
}
return 0;
}

View File

@ -9,7 +9,7 @@
SUsample sound_buffer[SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT];
HWAVEOUT wave_out_handle;
WAVEFORMATEX WaveFMT = {
WAVEFORMATEX wave_format = {
#ifdef SU_SAMPLE_FLOAT
WAVE_FORMAT_IEEE_FLOAT,
#else
@ -22,7 +22,7 @@ WAVEFORMATEX WaveFMT = {
SU_SAMPLE_SIZE*8,
0
};
WAVEHDR WaveHDR = {
WAVEHDR wave_header = {
(LPSTR)sound_buffer,
SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT,
0,
@ -32,7 +32,7 @@ WAVEHDR WaveHDR = {
0,
0
};
MMTIME MMTime = {
MMTIME mmtime = {
TIME_SAMPLES,
0
};
@ -48,11 +48,11 @@ int main(int argc, char **args) {
// We render in the background while playing already. Fortunately,
// Windows is slow with the calls below, so we're not worried that
// we don't have enough samples ready before the track starts.
waveOutOpen(&wave_out_handle, WAVE_MAPPER, &WaveFMT, 0, 0, CALLBACK_NULL);
waveOutWrite(wave_out_handle, &WaveHDR, sizeof(WaveHDR));
waveOutOpen(&wave_out_handle, WAVE_MAPPER, &wave_format, 0, 0, CALLBACK_NULL);
waveOutWrite(wave_out_handle, &wave_header, sizeof(wave_header));
// We need to handle windows messages properly while playing, as waveOutWrite is async.
for(MSG msg = {0}; MMTime.u.sample != SU_LENGTH_IN_SAMPLES; waveOutGetPosition(wave_out_handle, &MMTime, sizeof(MMTIME))) {
for(MSG msg = {0}; mmtime.u.sample != SU_LENGTH_IN_SAMPLES; waveOutGetPosition(wave_out_handle, &mmtime, sizeof(MMTIME))) {
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageA(&msg);

72
examples/code/C/cwav.c Normal file
View File

@ -0,0 +1,72 @@
#include <stdio.h>
#include <stdint.h>
#include "physics_girl_st.h"
#define WAVE_FORMAT_PCM 0x1
#define WAVE_FORMAT_IEEE_FLOAT 0x3
static SUsample sound_buffer[SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT];
#pragma pack(push, 1)
typedef struct {
char riff[4];
uint32_t file_size;
char wavefmt[8];
} riff_header_t;
typedef struct {
char data[4];
uint32_t data_size;
} data_header_t;
typedef struct {
riff_header_t riff_header;
uint32_t riff_header_size;
uint16_t sample_type;
uint16_t channel_count;
uint32_t sample_rate;
uint32_t bytes_per_second;
uint16_t bytes_per_channel;
uint16_t bits_per_sample;
data_header_t data_header;
} wave_header_t;
#pragma pack(pop)
int main(int argc, char **args) {
wave_header_t wave_header = {
.riff_header = (riff_header_t) {
.riff = "RIFF",
.file_size = sizeof(wave_header_t) + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT,
.wavefmt = "WAVEfmt ",
},
.riff_header_size = sizeof(riff_header_t),
#ifdef SU_SAMPLE_FLOAT
.sample_type = WAVE_FORMAT_IEEE_FLOAT,
#else // SU_SAMPLE_FLOAT
.sample_type = WAVE_FORMAT_PCM,
#endif // SU_SAMPLE_FLOAT
.channel_count = SU_CHANNEL_COUNT,
.sample_rate = SU_SAMPLE_RATE,
.bytes_per_second = SU_SAMPLE_SIZE * SU_SAMPLE_RATE * SU_CHANNEL_COUNT,
.bytes_per_channel = SU_SAMPLE_SIZE * SU_CHANNEL_COUNT,
.bits_per_sample = SU_SAMPLE_SIZE * 8,
.data_header = (data_header_t) {
.data = "data",
.data_size = sizeof(data_header_t) + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
}
};
// Load gm.dls if necessary.
#ifdef SU_LOAD_GMDLS
su_load_gmdls();
#endif // SU_LOAD_GMDLS
su_render_song(sound_buffer);
FILE *file = fopen("physics_girl_st.wav", "wb");
fwrite(&wave_header, sizeof(wave_header_t), 1, file);
fwrite((uint8_t *)sound_buffer, 1, SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT, file);
fclose(file);
return 0;
}

View File

@ -1 +1,5 @@
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
add_custom_target(examples)
add_subdirectory(asm)
add_subdirectory(C)

View File

@ -0,0 +1,10 @@
if(WIN32)
set(CMAKE_ASM_NASM_OBJECT_FORMAT win32)
elseif(UNIX)
set(CMAKE_ASM_NASM_OBJECT_FORMAT elf32)
endif()
set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> <INCLUDES> <DEFINES> <FLAGS> -f ${CMAKE_ASM_NASM_OBJECT_FORMAT} -o <OBJECT> <SOURCE>")
add_asm_example(asmplay "${PROJECT_SOURCE_DIR}/examples/patches/physics_girl_st.yml" 386 32 "winmm" "asound;pthread")
add_asm_example(asmwav "${PROJECT_SOURCE_DIR}/examples/patches/physics_girl_st.yml" 386 32 "" "")
target_compile_definitions(asmwav-386 PRIVATE FILENAME="physics_girl_st.wav")

View File

@ -0,0 +1,81 @@
%include TRACK_INCLUDE
%define SND_PCM_FORMAT_S16_LE 0x2
%define SND_PCM_FORMAT_FLOAT 0xE
%define SND_PCM_ACCESS_RW_INTERLEAVED 0x3
%define SND_PCM_STREAM_PLAYBACK 0x0
section .bss
sound_buffer:
resb SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
render_thread:
resd 1
pcm_handle:
resd 1
section .data
default_device:
db "default", 0
section .text
symbols:
extern pthread_create
extern sleep
extern snd_pcm_open
extern snd_pcm_set_params
extern snd_pcm_writei
global main
main:
; elf32 uses the cdecl calling convention. This is more readable imo ;)
; Prologue
push ebp
mov ebp, esp
sub esp, 0x10
; Unix does not have gm.dls, no need to ifdef and setup here.
; We render in the background while playing already.
push sound_buffer
lea eax, su_render_song
push eax
push 0
push render_thread
call pthread_create
; We can't start playing too early or the missing samples will be audible.
push 0x2
call sleep
; Play the track.
push 0x0
push SND_PCM_STREAM_PLAYBACK
push default_device
push pcm_handle
call snd_pcm_open
push SU_LENGTH_IN_SAMPLES
push 0
push SU_SAMPLE_RATE
push SU_CHANNEL_COUNT
push SND_PCM_ACCESS_RW_INTERLEAVED
%ifdef SU_SAMPLE_FLOAT
push SND_PCM_FORMAT_FLOAT
%else ; SU_SAMPLE_FLOAT
push SND_PCM_FORMAT_S16_LE
%endif ; SU_SAMPLE_FLOAT
push dword [pcm_handle]
call snd_pcm_set_params
push SU_LENGTH_IN_SAMPLES
push sound_buffer
push dword [pcm_handle]
call snd_pcm_writei
exit:
; At least we can skip the epilogue :)
leave
ret

View File

@ -0,0 +1,120 @@
%define MANGLED
%include TRACK_INCLUDE
%define WAVE_FORMAT_PCM 0x1
%define WAVE_FORMAT_IEEE_FLOAT 0x3
%define WHDR_PREPARED 0x2
%define WAVE_MAPPER 0xFFFFFFFF
%define TIME_SAMPLES 0x2
%define PM_REMOVE 0x1
section .bss
sound_buffer:
resb SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
wave_out_handle:
resd 1
msg:
resd 1
message:
resd 7
section .data
wave_format:
%ifdef SU_SAMPLE_FLOAT
dw WAVE_FORMAT_IEEE_FLOAT
%else ; SU_SAMPLE_FLOAT
dw WAVE_FORMAT_PCM
%endif ; SU_SAMPLE_FLOAT
dw SU_CHANNEL_COUNT
dd SU_SAMPLE_RATE
dd SU_SAMPLE_SIZE * SU_SAMPLE_RATE * SU_CHANNEL_COUNT
dw SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
dw SU_SAMPLE_SIZE * 8
dw 0
wave_header:
dd sound_buffer
dd SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
times 2 dd 0
dd WHDR_PREPARED
times 4 dd 0
wave_header_end:
mmtime:
dd TIME_SAMPLES
sample:
times 2 dd 0
mmtime_end:
section .text
symbols:
extern _CreateThread@24
extern _waveOutOpen@24
extern _waveOutWrite@12
extern _waveOutGetPosition@12
extern _PeekMessageA@20
extern _TranslateMessage@4
extern _DispatchMessageA@4
global _mainCRTStartup
_mainCRTStartup:
; win32 uses the cdecl calling convention. This is more readable imo ;)
; We can also skip the prologue; Windows doesn't mind.
%ifdef SU_LOAD_GMDLS
call _su_load_gmdls
%endif ; SU_LOAD_GMDLS
times 2 push 0
push sound_buffer
lea eax, _su_render_song@4
push eax
times 2 push 0
call _CreateThread@24
; We render in the background while playing already. Fortunately,
; Windows is slow with the calls below, so we're not worried that
; we don't have enough samples ready before the track starts.
times 3 push 0
push wave_format
push WAVE_MAPPER
push wave_out_handle
call _waveOutOpen@24
push wave_header_end - wave_header
push wave_header
push dword [wave_out_handle]
call _waveOutWrite@12
; We need to handle windows messages properly while playing, as waveOutWrite is async.
mainloop:
dispatchloop:
push PM_REMOVE
times 3 push 0
push msg
call _PeekMessageA@20
jz dispatchloop_end
push msg
call _TranslateMessage@4
push msg
call _DispatchMessageA@4
jmp dispatchloop
dispatchloop_end:
push mmtime_end - mmtime
push mmtime
push dword [wave_out_handle]
call _waveOutGetPosition@12
cmp dword [sample], SU_LENGTH_IN_SAMPLES
jne mainloop
exit:
; At least we can skip the epilogue :)
leave
ret

View File

@ -0,0 +1,91 @@
%include TRACK_INCLUDE
%define WAVE_FORMAT_PCM 0x1
%define WAVE_FORMAT_IEEE_FLOAT 0x3
section .bss
sound_buffer:
resb SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
file:
resd 1
section .data
; Change the filename over -DFILENAME="yourfilename.wav"
filename:
db FILENAME, 0
format:
db "wb", 0
; This is the wave file header.
wave_file:
db "RIFF"
dd wave_file_end + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT - wave_file
db "WAVE"
db "fmt "
wave_format_end:
dd wave_format_end - wave_file
%ifdef SU_SAMPLE_FLOAT
dw WAVE_FORMAT_IEEE_FLOAT
%else ; SU_SAMPLE_FLOAT
dw WAVE_FORMAT_PCM
%endif ; SU_SAMPLE_FLOAT
dw SU_CHANNEL_COUNT
dd SU_SAMPLE_RATE
dd SU_SAMPLE_SIZE * SU_SAMPLE_RATE * SU_CHANNEL_COUNT
dw SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
dw SU_SAMPLE_SIZE * 8
wave_header_end:
db "data"
dd wave_file_end + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT - wave_header_end
wave_file_end:
section .text
symbols:
extern fopen
extern fwrite
extern fclose
global main
main:
; elf32 uses the cdecl calling convention. This is more readable imo ;)
; Prologue
push ebp
mov ebp, esp
sub esp, 0x10
; Unix does not have gm.dls, no need to ifdef and setup here.
; We render the complete track here.
push sound_buffer
call su_render_song
; Now we open the file and save the track.
push format
push filename
call fopen
mov dword [file], eax
; Write header
push dword [file]
push 0x1
push wave_file_end - wave_file
push wave_file
call fwrite
; write data
push dword [file]
push 0x1
push SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
push sound_buffer
call fwrite
push dword [file]
call fclose
exit:
; At least we can skip the epilogue :)
leave
ret

View File

@ -0,0 +1,102 @@
%define MANGLED
%include TRACK_INCLUDE
%define WAVE_FORMAT_PCM 0x1
%define WAVE_FORMAT_IEEE_FLOAT 0x3
%define FILE_ATTRIBUTE_NORMAL 0x00000080
%define CREATE_ALWAYS 2
%define GENERIC_WRITE 0x40000000
section .bss
sound_buffer:
resb SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
file:
resd 1
bytes_written:
resd 1
section .data
; Change the filename over -DFILENAME="yourfilename.wav"
filename:
db FILENAME, 0
; This is the wave file header.
wave_file:
db "RIFF"
dd wave_file_end + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT - wave_file
db "WAVE"
db "fmt "
wave_format_end:
dd wave_format_end - wave_file
%ifdef SU_SAMPLE_FLOAT
dw WAVE_FORMAT_IEEE_FLOAT
%else ; SU_SAMPLE_FLOAT
dw WAVE_FORMAT_PCM
%endif ; SU_SAMPLE_FLOAT
dw SU_CHANNEL_COUNT
dd SU_SAMPLE_RATE
dd SU_SAMPLE_SIZE * SU_SAMPLE_RATE * SU_CHANNEL_COUNT
dw SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
dw SU_SAMPLE_SIZE * 8
wave_header_end:
db "data"
dd wave_file_end + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT - wave_header_end
wave_file_end:
section .text
symbols:
extern _CreateFileA@28
extern _WriteFile@20
extern _CloseHandle@4
global _mainCRTStartup
_mainCRTStartup:
; Prologue
push ebp
mov ebp, esp
sub esp, 0x10
%ifdef SU_LOAD_GMDLS
call _su_load_gmdls
%endif ; SU_LOAD_GMDLS
; We render the complete track here.
push sound_buffer
call _su_render_song@4
; Now we open the file and save the track.
push 0x0
push FILE_ATTRIBUTE_NORMAL
push CREATE_ALWAYS
push 0x0
push 0x0
push GENERIC_WRITE
push filename
call _CreateFileA@28
mov dword [file], eax
; This is the WAV header
push 0x0
push bytes_written
push wave_file_end - wave_file
push wave_file
push dword [file]
call _WriteFile@20
; There we write the actual samples
push 0x0
push bytes_written
push SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE
push sound_buffer
push dword [file]
call _WriteFile@20
push dword [file]
call _CloseHandle@4
exit:
; At least we can skip the epilogue :)
leave
ret

View File

@ -0,0 +1,58 @@
# identifier: Name of the example
# songfile: File path of the song YAML file.
# architecture: 386 or amd64
# abi: 32 or 64
# windows_libraries: All libraries that you need to link on Windows
# unix_libraries: All libraries that you need to link on unix
function(add_asm_example identifier songfile architecture sizeof_void_ptr windows_libraries unix_libraries)
get_filename_component(songprefix ${songfile} NAME_WE)
# Generate the song assembly file
add_custom_command(
COMMAND
${compilecmd} -arch=${architecture} -o ${songprefix}_${architecture}.asm ${songfile}
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
${songfile}
OUTPUT
${songprefix}_${architecture}.asm
${songprefix}_${architecture}.h
${songprefix}_${architecture}.inc
COMMENT
"Compiling ${PROJECT_SOURCE_DIR}/examples/patches/physics-girl-st.yml..."
)
# Platform dependent options
if(WIN32)
set(abi win)
set(libraries ${windows_libraries})
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(link_options -nostartfiles)
endif()
elseif(UNIX)
set(abi elf)
set(link_options -z noexecstack -no-pie)
set(libraries ${unix_libraries})
endif()
# Add target
add_executable(${identifier}-${architecture}
${identifier}.${abi}${sizeof_void_ptr}.asm
${songprefix}_${architecture}.asm
${songprefix}_${architecture}.inc
)
set_target_properties(${identifier}-${architecture} PROPERTIES ASM_NASM_COMPILE_OPTIONS -f${abi}${sizeof_void_ptr})
target_include_directories(${identifier}-${architecture} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
set_target_properties(${identifier}-${architecture} PROPERTIES LINKER_LANGUAGE C)
target_link_options(${identifier}-${architecture} PRIVATE -m${sizeof_void_ptr} ${link_options})
target_link_libraries(${identifier}-${architecture} PRIVATE ${libraries})
target_compile_definitions(${identifier}-${architecture} PRIVATE TRACK_INCLUDE="${songprefix}_${architecture}.inc")
# Set up dependencies
add_dependencies(${identifier}-${architecture} sointu-compiler)
add_dependencies(examples ${identifier}-${architecture})
endfunction()
add_subdirectory(386)
add_subdirectory(amd64)

View File

@ -0,0 +1,12 @@
if(WIN32)
set(CMAKE_ASM_NASM_OBJECT_FORMAT win64)
elseif(UNIX)
set(CMAKE_ASM_NASM_OBJECT_FORMAT elf64)
endif()
set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> <INCLUDES> <DEFINES> <FLAGS> -f ${CMAKE_ASM_NASM_OBJECT_FORMAT} -o <OBJECT> <SOURCE>")
if(UNIX)
add_asm_example(asmplay "${PROJECT_SOURCE_DIR}/examples/patches/physics_girl_st.yml" amd64 64 "winmm" "asound;pthread")
add_asm_example(asmwav "${PROJECT_SOURCE_DIR}/examples/patches/physics_girl_st.yml" amd64 64 "" "")
target_compile_definitions(asmwav-amd64 PRIVATE FILENAME="physics_girl_st.wav")
endif()

View File

@ -0,0 +1,81 @@
%include TRACK_INCLUDE
%define SND_PCM_FORMAT_S16_LE 0x2
%define SND_PCM_FORMAT_FLOAT 0xE
%define SND_PCM_ACCESS_RW_INTERLEAVED 0x3
%define SND_PCM_STREAM_PLAYBACK 0x0
section .bss
sound_buffer:
resb SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
render_thread:
resq 1
pcm_handle:
resq 1
section .data
default_device:
db "default", 0
section .text
symbols:
extern pthread_create
extern sleep
extern snd_pcm_open
extern snd_pcm_set_params
extern snd_pcm_writei
global main
main:
; Prologue
push rbp
mov rbp, rsp
sub rsp, 0x10
; Unix does not have gm.dls, no need to ifdef and setup here.
; We render in the background while playing already.
mov rcx, sound_buffer
lea rdx, su_render_song
mov rsi, 0x0
mov rdi, render_thread
call pthread_create
; We can't start playing too early or the missing samples will be audible.
mov edi, 0x2
call sleep
; Play the track.
mov rdi, pcm_handle
mov rsi, default_device
mov rdx, SND_PCM_STREAM_PLAYBACK
mov rcx, 0x0
call snd_pcm_open
; This is unfortunate. amd64 ABI calling convention kicks in.
; now we have to maintain the stack pointer :/
mov rdi, qword [pcm_handle]
sub rsp, 0x8
push SU_LENGTH_IN_SAMPLES
%ifdef SU_SAMPLE_FLOAT
mov rsi, SND_PCM_FORMAT_FLOAT
%else ; SU_SAMPLE_FLOAT
mov rsi, SND_PCM_FORMAT_S16_LE
%endif ; SU_SAMPLE_FLOAT
mov rdx, SND_PCM_ACCESS_RW_INTERLEAVED
mov rcx, SU_CHANNEL_COUNT
mov r8d, SU_SAMPLE_RATE
mov r9d, 0x0
call snd_pcm_set_params
mov rdi, qword [pcm_handle]
mov rsi, sound_buffer
mov rdx, SU_LENGTH_IN_SAMPLES
call snd_pcm_writei
exit:
; At least we can skip the epilogue :)
leave
ret

View File

@ -0,0 +1,91 @@
%include TRACK_INCLUDE
%define WAVE_FORMAT_PCM 0x1
%define WAVE_FORMAT_IEEE_FLOAT 0x3
section .bss
sound_buffer:
resb SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
file:
resq 1
section .data
; Change the filename over -DFILENAME="yourfilename.wav"
filename:
db FILENAME, 0
format:
db "wb", 0
; This is the wave file header.
wave_file:
db "RIFF"
dd wave_file_end + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT - wave_file
db "WAVE"
db "fmt "
wave_format_end:
dd wave_format_end - wave_file
%ifdef SU_SAMPLE_FLOAT
dw WAVE_FORMAT_IEEE_FLOAT
%else ; SU_SAMPLE_FLOAT
dw WAVE_FORMAT_PCM
%endif ; SU_SAMPLE_FLOAT
dw SU_CHANNEL_COUNT
dd SU_SAMPLE_RATE
dd SU_SAMPLE_SIZE * SU_SAMPLE_RATE * SU_CHANNEL_COUNT
dw SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
dw SU_SAMPLE_SIZE * 8
wave_header_end:
db "data"
dd wave_file_end + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT - wave_header_end
wave_file_end:
section .text
symbols:
extern fopen
extern fwrite
extern fclose
global main
main:
; elf32 uses the cdecl calling convention. This is more readable imo ;)
; Prologue
push rbp
mov rbp, rsp
sub rsp, 0x10
; Unix does not have gm.dls, no need to ifdef and setup here.
; We render the complete track here.
mov rdi, sound_buffer
call su_render_song
; Now we open the file and save the track.
mov rsi, format
mov rdi, filename
call fopen
mov qword [file], rax
; Write header
mov rcx, qword [file]
mov rdx, 0x1
mov rsi, wave_file_end - wave_file
mov rdi, wave_file
call fwrite
; write data
mov rcx, qword [file]
mov rdx, 0x1
mov rsi, SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT
mov rdi, sound_buffer
call fwrite
mov rdi, qword [file]
call fclose
exit:
; At least we can skip the epilogue :)
leave
ret

View File

@ -37,9 +37,12 @@
%define SU_SYNC
{{- end}}
section _su_symbols text
_su_symbols:
%ifdef MANGLED
extern _su_render_song@4
%else ; MANGLED
extern su_render_song
%endif ; MANGLED
{{- if gt (.SampleOffsets | len) 0}}
extern _su_load_gmdls